window.SELF_UPDATE_BASE_URL = "https://cloud.appspace.com/update";window.appspace = {"packageFor":"brightsign","capabilities":[""]};(function webpackUniversalModuleDefinition(root, factory) {
	if(typeof exports === 'object' && typeof module === 'object')
		module.exports = factory();
	else if(typeof define === 'function' && define.amd)
		define("AppspaceCloudDeviceUtils", [], factory);
	else if(typeof exports === 'object')
		exports["AppspaceCloudDeviceUtils"] = factory();
	else
		root["AppspaceCloudDeviceUtils"] = factory();
})(this, () => {
return /******/ (() => { // webpackBootstrap
/******/ 	"use strict";
/******/ 	var __webpack_modules__ = ({

/***/ "./src/index.ts":
/*!**********************!*\
  !*** ./src/index.ts ***!
  \**********************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {


var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
    for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
__exportStar(__webpack_require__(/*! ./validator */ "./src/validator/index.ts"), exports);


/***/ }),

/***/ "./src/validator/index.ts":
/*!********************************!*\
  !*** ./src/validator/index.ts ***!
  \********************************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {


var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
    for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
__exportStar(__webpack_require__(/*! ./validator */ "./src/validator/validator.ts"), exports);


/***/ }),

/***/ "./src/validator/validator.ts":
/*!************************************!*\
  !*** ./src/validator/validator.ts ***!
  \************************************/
/***/ ((__unused_webpack_module, exports) => {


Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.Validator = void 0;
var Validator = /** @class */ (function () {
    function Validator() {
    }
    Validator.hasEmptyFields = function (fields) {
        return Object.values(fields).some(function (value) { return !value; });
    };
    return Validator;
}());
exports.Validator = Validator;


/***/ })

/******/ 	});
/************************************************************************/
/******/ 	// The module cache
/******/ 	var __webpack_module_cache__ = {};
/******/ 	
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/ 		// Check if module is in cache
/******/ 		var cachedModule = __webpack_module_cache__[moduleId];
/******/ 		if (cachedModule !== undefined) {
/******/ 			return cachedModule.exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = __webpack_module_cache__[moduleId] = {
/******/ 			// no module.id needed
/******/ 			// no module.loaded needed
/******/ 			exports: {}
/******/ 		};
/******/ 	
/******/ 		// Execute the module function
/******/ 		__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ 	
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/ 	
/************************************************************************/
/******/ 	
/******/ 	// startup
/******/ 	// Load entry module and return exports
/******/ 	// This entry module is referenced by other modules so it can't be inlined
/******/ 	var __webpack_exports__ = __webpack_require__("./src/index.ts");
/******/ 	
/******/ 	return __webpack_exports__;
/******/ })()
;
});
(function webpackUniversalModuleDefinition(root, factory) {
	if(typeof exports === 'object' && typeof module === 'object')
		module.exports = factory(require("@appspace-cloud/device-utils"));
	else if(typeof define === 'function' && define.amd)
		define("AppspaceCloudDeviceMedia", ["@appspace-cloud/device-utils"], factory);
	else if(typeof exports === 'object')
		exports["AppspaceCloudDeviceMedia"] = factory(require("@appspace-cloud/device-utils"));
	else
		root["AppspaceCloudDeviceMedia"] = factory(root["AppspaceCloudDeviceUtils"]);
})(this, (__WEBPACK_EXTERNAL_MODULE__appspace_cloud_device_utils__) => {
return /******/ (() => { // webpackBootstrap
/******/ 	"use strict";
/******/ 	var __webpack_modules__ = ({

/***/ "./src/index.ts":
/*!**********************!*\
  !*** ./src/index.ts ***!
  \**********************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {


var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
    for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
__exportStar(__webpack_require__(/*! ./playout-metric */ "./src/playout-metric/index.ts"), exports);


/***/ }),

/***/ "./src/playout-metric/base-playout-metric.service.ts":
/*!***********************************************************!*\
  !*** ./src/playout-metric/base-playout-metric.service.ts ***!
  \***********************************************************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {


var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
    return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (g && (g = 0, op[0] && (_ = 0)), _) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.BasePlayoutMetricService = void 0;
var playout_metric_constants_1 = __webpack_require__(/*! ./playout-metric.constants */ "./src/playout-metric/playout-metric.constants.ts");
var BasePlayoutMetricService = /** @class */ (function () {
    function BasePlayoutMetricService(logService) {
        this.passiveMetricToProcess = [];
        this._isStopped = false;
        this._isSuspended = false;
        this.passiveMetricQueue = [];
        this.lastSubmitPassiveMetricTimeMs = Date.now();
        this.processQueueTimeoutHandler = undefined;
        this.logService = logService;
    }
    BasePlayoutMetricService.prototype.addPassiveMetric = function (metric) {
        if (this.isStopped) {
            this.logService.trace(playout_metric_constants_1.PlayoutMetricConstants.LogKey, 'Metric service is stopped, skip');
            return;
        }
        if (!this.processPlayoutMetric(metric)) {
            return;
        }
        this.logService.trace(playout_metric_constants_1.PlayoutMetricConstants.LogKey, "Adding passive metric for media zone data source ".concat(metric.widgetDatasourceId));
        this.logService.trace(playout_metric_constants_1.PlayoutMetricConstants.LogKey, "Content ".concat(metric.contentId, " played for ").concat(metric.endTime.getTime() - metric.startTime.getTime(), "ms"));
        this.passiveMetricQueue.push(metric);
    };
    BasePlayoutMetricService.prototype.clearPendingMetrics = function () {
        this.passiveMetricQueue = [];
    };
    BasePlayoutMetricService.prototype.startProcessingQueue = function () {
        var _this = this;
        this.cancelProcessingQueue();
        this.processQueueTimeoutHandler = window.setTimeout(function () {
            _this.processQueue();
        }, BasePlayoutMetricService.ProcessQueueIntervalMs);
    };
    BasePlayoutMetricService.prototype.cancelProcessingQueue = function () {
        if (!this.processQueueTimeoutHandler) {
            return;
        }
        window.clearTimeout(this.processQueueTimeoutHandler);
        this.processQueueTimeoutHandler = undefined;
    };
    BasePlayoutMetricService.prototype.stop = function () {
        this._isStopped = true;
        this.passiveMetricQueue = [];
        this.passiveMetricToProcess = [];
        this.cancelProcessingQueue();
    };
    BasePlayoutMetricService.prototype.resume = function () {
        this._isSuspended = false;
        this._isStopped = false;
        this.startProcessingQueue();
    };
    BasePlayoutMetricService.prototype.suspend = function () {
        this._isSuspended = true;
        this.passiveMetricQueue = [];
        this.passiveMetricToProcess = [];
    };
    Object.defineProperty(BasePlayoutMetricService.prototype, "isSuspended", {
        get: function () {
            return this._isSuspended;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(BasePlayoutMetricService.prototype, "isStopped", {
        get: function () {
            return this._isStopped;
        },
        enumerable: false,
        configurable: true
    });
    // Override if need to process playout metric before adding to queue
    // _metric is with underscore to tell linter that this parameter is not used
    BasePlayoutMetricService.prototype.processPlayoutMetric = function (_metric) {
        return true;
    };
    BasePlayoutMetricService.prototype.processQueue = function () {
        return __awaiter(this, void 0, void 0, function () {
            var timeDifferenceMs, startSendingTime, endSendingTime, error_1, endSendingTime, errorDetails, message;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        // if no item, reset last submit time and restart timeout
                        if (this.passiveMetricQueue.length === 0) {
                            this.resetLastSubmitPassiveMetricTime();
                            this.startProcessingQueue();
                            return [2 /*return*/];
                        }
                        timeDifferenceMs = Date.now() - this.lastSubmitPassiveMetricTimeMs;
                        // if queue size is less than threshold and time difference is less than threshold, restart timeout
                        if (this.passiveMetricQueue.length < BasePlayoutMetricService.SubmitThresholdSize && timeDifferenceMs < BasePlayoutMetricService.SubmitThresholdTimeMs) {
                            this.startProcessingQueue();
                            return [2 /*return*/];
                        }
                        if (this.isSuspended) {
                            this.logService.trace(playout_metric_constants_1.PlayoutMetricConstants.LogKey, 'Metric service is suspended, skip');
                            this.startProcessingQueue();
                            return [2 /*return*/];
                        }
                        // process a maximum of 10 records
                        if (!this.passiveMetricToProcess.length) {
                            this.passiveMetricToProcess = this.passiveMetricQueue.splice(0, BasePlayoutMetricService.SubmitThresholdSize);
                        }
                        else {
                            this.logService.trace(playout_metric_constants_1.PlayoutMetricConstants.LogKey, "Retry submitting existing ".concat(this.passiveMetricToProcess.length, " passive metrics"));
                        }
                        startSendingTime = Date.now();
                        _a.label = 1;
                    case 1:
                        _a.trys.push([1, 3, 4, 5]);
                        return [4 /*yield*/, this.submitMetrics(this.passiveMetricToProcess)];
                    case 2:
                        _a.sent();
                        endSendingTime = Date.now();
                        this.logService.debug(playout_metric_constants_1.PlayoutMetricConstants.LogKey, "Submitted ".concat(this.passiveMetricToProcess.length, " passive metrics took ").concat(endSendingTime - startSendingTime, "ms"));
                        this.passiveMetricToProcess = [];
                        this.resetLastSubmitPassiveMetricTime();
                        return [3 /*break*/, 5];
                    case 3:
                        error_1 = _a.sent();
                        endSendingTime = Date.now();
                        errorDetails = error_1 instanceof Error ? error_1.message : JSON.stringify(error_1);
                        message = "Error submitting metrics after ".concat(endSendingTime - startSendingTime, "ms, retrying... Error: ").concat(errorDetails);
                        // todo: need to limit retries
                        this.logService.error(playout_metric_constants_1.PlayoutMetricConstants.LogKey, message);
                        return [3 /*break*/, 5];
                    case 4:
                        this.startProcessingQueue();
                        return [7 /*endfinally*/];
                    case 5: return [2 /*return*/];
                }
            });
        });
    };
    BasePlayoutMetricService.prototype.resetLastSubmitPassiveMetricTime = function () {
        this.lastSubmitPassiveMetricTimeMs = Date.now();
    };
    BasePlayoutMetricService.ProcessQueueIntervalMs = 10000;
    BasePlayoutMetricService.SubmitThresholdTimeMs = 300000; // 5 minutes
    BasePlayoutMetricService.SubmitThresholdSize = 10;
    return BasePlayoutMetricService;
}());
exports.BasePlayoutMetricService = BasePlayoutMetricService;


/***/ }),

/***/ "./src/playout-metric/index.ts":
/*!*************************************!*\
  !*** ./src/playout-metric/index.ts ***!
  \*************************************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {


var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
    for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
__exportStar(__webpack_require__(/*! ./playout-metric-medianet.service */ "./src/playout-metric/playout-metric-medianet.service.ts"), exports);
__exportStar(__webpack_require__(/*! ./playout-metric-analytics.service */ "./src/playout-metric/playout-metric-analytics.service.ts"), exports);
__exportStar(__webpack_require__(/*! ./playout-metric.interface */ "./src/playout-metric/playout-metric.interface.ts"), exports);
__exportStar(__webpack_require__(/*! ./playout-metric-schedule.interface */ "./src/playout-metric/playout-metric-schedule.interface.ts"), exports);


/***/ }),

/***/ "./src/playout-metric/playout-metric-analytics.service.ts":
/*!****************************************************************!*\
  !*** ./src/playout-metric/playout-metric-analytics.service.ts ***!
  \****************************************************************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {


var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        if (typeof b !== "function" && b !== null)
            throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
    return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (g && (g = 0, op[0] && (_ = 0)), _) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.PlayoutMetricAnalyticsService = void 0;
var base_playout_metric_service_1 = __webpack_require__(/*! ./base-playout-metric.service */ "./src/playout-metric/base-playout-metric.service.ts");
var playout_metric_constants_1 = __webpack_require__(/*! ./playout-metric.constants */ "./src/playout-metric/playout-metric.constants.ts");
var playout_metric_utils_1 = __webpack_require__(/*! ./playout-metric.utils */ "./src/playout-metric/playout-metric.utils.ts");
var device_utils_1 = __webpack_require__(/*! @appspace-cloud/device-utils */ "@appspace-cloud/device-utils");
var PlayoutMetricAnalyticsService = /** @class */ (function (_super) {
    __extends(PlayoutMetricAnalyticsService, _super);
    function PlayoutMetricAnalyticsService(logService, constellationServicePost) {
        var _this = _super.call(this, logService) || this;
        _this.constellationServicePost = constellationServicePost;
        return _this;
    }
    PlayoutMetricAnalyticsService.prototype.submitMetrics = function (passiveMetrics) {
        return __awaiter(this, void 0, void 0, function () {
            var deviceId, analyticsPlayoutMetrics, url, header, error_1, errorMessage, httpError;
            var _this = this;
            var _a;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        this.logService.debug(playout_metric_constants_1.PlayoutMetricConstants.LogKey, "Submitting ".concat(this.passiveMetricToProcess.length, " passive metrics"));
                        deviceId = (_a = passiveMetrics[0]) === null || _a === void 0 ? void 0 : _a.playerGuid;
                        if (!deviceId) {
                            throw new Error('No device id found in passive metrics');
                        }
                        analyticsPlayoutMetrics = passiveMetrics
                            .map(function (metric) {
                            var requiredFields = {
                                deviceType: metric.deviceType,
                                channelId: metric.channelGuid,
                                contentId: metric.contentGuid,
                                contentType: playout_metric_utils_1.PlayoutMetricUtils.toAnalyticsContentType(metric.contentType),
                                contentDuration: metric.contentDuration,
                                accountId: metric.accountGuid,
                            };
                            var hasEmptyFields = device_utils_1.Validator.hasEmptyFields(requiredFields);
                            if (hasEmptyFields) {
                                _this.logService.warn(playout_metric_constants_1.PlayoutMetricConstants.LogKey, "Skipping metric with missing fields: ".concat(metric.contentId));
                                return null;
                            }
                            return __assign(__assign({}, requiredFields), { cardTemplateId: metric.cardTemplateId, cardThemeId: metric.cardThemeId, startTime: metric.startTime.toISOString(), endTime: metric.endTime.toISOString(), deviceGroupId: metric.deviceGroupGuid, deviceTags: metric.deviceTags });
                        })
                            .filter(function (metric) { return metric !== null; });
                        url = "".concat(playout_metric_constants_1.PlayoutMetricConstants.AnalyticsSubmitUrl).concat(deviceId);
                        header = {
                            'Content-Type': 'application/json',
                        };
                        _b.label = 1;
                    case 1:
                        _b.trys.push([1, 3, , 4]);
                        return [4 /*yield*/, this.constellationServicePost(url, JSON.stringify(analyticsPlayoutMetrics), header)];
                    case 2:
                        _b.sent();
                        return [3 /*break*/, 4];
                    case 3:
                        error_1 = _b.sent();
                        errorMessage = 'Failed to submit metrics: ';
                        httpError = error_1;
                        errorMessage += httpError.xhr && httpError.xhr.status && httpError.xhr.responseText
                            ? "code: ".concat(httpError.xhr.status, ", message: ").concat(httpError.xhr.responseText)
                            : "".concat(error_1);
                        throw new Error(errorMessage);
                    case 4: return [2 /*return*/];
                }
            });
        });
    };
    return PlayoutMetricAnalyticsService;
}(base_playout_metric_service_1.BasePlayoutMetricService));
exports.PlayoutMetricAnalyticsService = PlayoutMetricAnalyticsService;


/***/ }),

/***/ "./src/playout-metric/playout-metric-medianet.service.ts":
/*!***************************************************************!*\
  !*** ./src/playout-metric/playout-metric-medianet.service.ts ***!
  \***************************************************************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {


var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        if (typeof b !== "function" && b !== null)
            throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
    return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (g && (g = 0, op[0] && (_ = 0)), _) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.PlayoutMetricMedianetService = void 0;
var base_playout_metric_service_1 = __webpack_require__(/*! ./base-playout-metric.service */ "./src/playout-metric/base-playout-metric.service.ts");
var playout_metric_constants_1 = __webpack_require__(/*! ./playout-metric.constants */ "./src/playout-metric/playout-metric.constants.ts");
var playout_metric_utils_1 = __webpack_require__(/*! ./playout-metric.utils */ "./src/playout-metric/playout-metric.utils.ts");
var PlayoutMetricMedianetService = /** @class */ (function (_super) {
    __extends(PlayoutMetricMedianetService, _super);
    function PlayoutMetricMedianetService(logService, handlerPost) {
        var _this = _super.call(this, logService) || this;
        _this.metricSchedules = [];
        _this.dayPartCache = new Map();
        _this.handlerPost = handlerPost;
        return _this;
    }
    PlayoutMetricMedianetService.prototype.clearAllMetricSchedule = function () {
        this.metricSchedules = [];
        this.dayPartCache.clear();
    };
    PlayoutMetricMedianetService.prototype.registerMetricSchedule = function (schedule) {
        var existingIndex = this.metricSchedules.findIndex(function (s) { return s.widgetDatasourceId === schedule.zoneId; });
        var newSchedule = {
            id: schedule.id,
            widgetDatasourceId: schedule.zoneId,
            startTime: new Date(schedule.startTime),
            endTime: new Date(schedule.endTime),
            days: [],
        };
        // 0 = everyday
        // 1 = weekday
        // 2 = weekend
        switch (schedule.day) {
            case 0:
                newSchedule.days = [0, 1, 2, 3, 4, 5, 6];
                break;
            case 1:
                newSchedule.days = [1, 2, 3, 4, 5];
                break;
            case 2:
                newSchedule.days = [0, 6];
                break;
        }
        if (existingIndex !== -1) {
            this.logService.trace(playout_metric_constants_1.PlayoutMetricConstants.LogKey, "Replacing metric schedule for media zone data source ".concat(schedule.zoneId));
            this.metricSchedules[existingIndex] = newSchedule;
            return;
        }
        this.logService.trace(playout_metric_constants_1.PlayoutMetricConstants.LogKey, "Adding metric schedule for media zone data source ".concat(schedule.zoneId));
        this.metricSchedules.push(newSchedule);
    };
    PlayoutMetricMedianetService.prototype.stop = function () {
        _super.prototype.stop.call(this);
        this.dayPartCache.clear();
    };
    PlayoutMetricMedianetService.prototype.submitMetrics = function (passiveMetrics) {
        return __awaiter(this, void 0, void 0, function () {
            var queryString, url, header, errorMessage, response, error_1;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        this.logService.debug(playout_metric_constants_1.PlayoutMetricConstants.LogKey, "Submitting ".concat(passiveMetrics.length, " passive metrics"));
                        queryString = playout_metric_utils_1.PlayoutMetricUtils.toQueryString(passiveMetrics);
                        url = "".concat(playout_metric_constants_1.PlayoutMetricConstants.MedianetHandlerUrl, "?metric=").concat(queryString);
                        header = {
                            'Content-Type': 'text/plain',
                        };
                        _a.label = 1;
                    case 1:
                        _a.trys.push([1, 3, 4, 5]);
                        return [4 /*yield*/, this.handlerPost(url, '', header, 10000)];
                    case 2:
                        response = _a.sent();
                        if (response && response.data.trim() === 'metric successful added') {
                            return [2 /*return*/];
                        }
                        errorMessage = "Unexpected response: ".concat(response.data);
                        return [3 /*break*/, 5];
                    case 3:
                        error_1 = _a.sent();
                        errorMessage = "Failed to submit metrics: ".concat(error_1);
                        return [3 /*break*/, 5];
                    case 4:
                        if (!errorMessage) {
                            return [2 /*return*/];
                        }
                        throw new Error(errorMessage);
                    case 5: return [2 /*return*/];
                }
            });
        });
    };
    PlayoutMetricMedianetService.prototype.processPlayoutMetric = function (metric) {
        var schedule = this.metricSchedules.find(function (s) { return s.widgetDatasourceId === metric.widgetDatasourceId; });
        if (!schedule) {
            this.logService.trace(playout_metric_constants_1.PlayoutMetricConstants.LogKey, "No playout metric schedule found for media zone data source ".concat(metric.widgetDatasourceId));
            return false;
        }
        var currentTime = new Date();
        var dayPart = this.getDayPart(currentTime, schedule);
        if (currentTime >= dayPart.start && currentTime < dayPart.end && schedule.days.includes(currentTime.getDay())) {
            metric.scheduleId = schedule.id;
            return true;
        }
        this.logService.trace(playout_metric_constants_1.PlayoutMetricConstants.LogKey, "Media zone data source ".concat(metric.widgetDatasourceId, " is not in valid range schedule, skip"));
        return false;
    };
    PlayoutMetricMedianetService.prototype.getDayPart = function (currentTime, schedule) {
        var dayPartKey = "".concat(currentTime.getFullYear(), "-").concat(currentTime.getMonth(), "-").concat(currentTime.getDay());
        var dayPart = this.dayPartCache.get(dayPartKey);
        if (dayPart) {
            return dayPart;
        }
        // generate day part from schedule
        var start = new Date(currentTime);
        start.setHours(schedule.startTime.getHours());
        start.setMinutes(schedule.startTime.getMinutes());
        start.setSeconds(schedule.startTime.getSeconds());
        start.setMilliseconds(0);
        var end = new Date(currentTime);
        end.setHours(schedule.endTime.getHours());
        end.setMinutes(schedule.endTime.getMinutes());
        end.setSeconds(schedule.endTime.getSeconds());
        end.setMilliseconds(0);
        // if ends at 00:00:00, set to 23:59:59
        if (end.getHours() === 0 && end.getMinutes() === 0 && end.getSeconds() === 0) {
            end.setHours(23);
            end.setMinutes(59);
            end.setSeconds(59);
        }
        this.dayPartCache.set(dayPartKey, { start: start, end: end });
        return { start: start, end: end };
    };
    return PlayoutMetricMedianetService;
}(base_playout_metric_service_1.BasePlayoutMetricService));
exports.PlayoutMetricMedianetService = PlayoutMetricMedianetService;


/***/ }),

/***/ "./src/playout-metric/playout-metric-schedule.interface.ts":
/*!*****************************************************************!*\
  !*** ./src/playout-metric/playout-metric-schedule.interface.ts ***!
  \*****************************************************************/
/***/ ((__unused_webpack_module, exports) => {


Object.defineProperty(exports, "__esModule", ({ value: true }));


/***/ }),

/***/ "./src/playout-metric/playout-metric.constants.ts":
/*!********************************************************!*\
  !*** ./src/playout-metric/playout-metric.constants.ts ***!
  \********************************************************/
/***/ ((__unused_webpack_module, exports) => {


Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.PlayoutMetricConstants = void 0;
exports.PlayoutMetricConstants = {
    LogKey: 'appspace.signs.metric.service',
    AnalyticsSubmitUrl: '/api/v3/analytics/playout/devices/',
    MedianetHandlerUrl: '{{appspace-url}}/medianetintegrationservice/httpplayoutmetricsservices.ashx',
    AnalyticsContentTypeMapping: {
        'slideshow': 'Slide_Show',
        'dynamicmedia': 'Dynamic_Media',
        'digitaltelevision': 'Digital_Television',
        'webpage': 'Web_Page',
        'web': 'Web_Page',
        'externalcontent': 'External_Content',
        'external content': 'External_Content',
        'htmltext': 'HTML_Text',
        'html text': 'HTML_Text',
    },
};


/***/ }),

/***/ "./src/playout-metric/playout-metric.interface.ts":
/*!********************************************************!*\
  !*** ./src/playout-metric/playout-metric.interface.ts ***!
  \********************************************************/
/***/ ((__unused_webpack_module, exports) => {


Object.defineProperty(exports, "__esModule", ({ value: true }));


/***/ }),

/***/ "./src/playout-metric/playout-metric.utils.ts":
/*!****************************************************!*\
  !*** ./src/playout-metric/playout-metric.utils.ts ***!
  \****************************************************/
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {


Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.PlayoutMetricUtils = void 0;
var playout_metric_constants_1 = __webpack_require__(/*! ./playout-metric.constants */ "./src/playout-metric/playout-metric.constants.ts");
exports.PlayoutMetricUtils = {
    toQueryString: function (metrics) {
        return metrics.map(function (m) { return "cid:".concat(m.contentId, ";plays:").concat(m.startTime.getTime(), ";playe:").concat(m.endTime.getTime(), ";pid:").concat(m.playerGuid, ";aid:").concat(m.applicationId, ";sid:").concat(m.scheduleId, ";mzid:").concat(m.mediaZoneDatasourceId, ";wid:").concat(m.widgetDatasourceId); }).join(',');
    },
    toAnalyticsContentType: function (contentType) {
        return !contentType ? undefined : playout_metric_constants_1.PlayoutMetricConstants.AnalyticsContentTypeMapping[contentType.toLowerCase()] || contentType;
    }
};


/***/ }),

/***/ "@appspace-cloud/device-utils":
/*!******************************************************************************************************************************************************************************!*\
  !*** external {"commonjs":"@appspace-cloud/device-utils","commonjs2":"@appspace-cloud/device-utils","amd":"@appspace-cloud/device-utils","root":"AppspaceCloudDeviceUtils"} ***!
  \******************************************************************************************************************************************************************************/
/***/ ((module) => {

module.exports = __WEBPACK_EXTERNAL_MODULE__appspace_cloud_device_utils__;

/***/ })

/******/ 	});
/************************************************************************/
/******/ 	// The module cache
/******/ 	var __webpack_module_cache__ = {};
/******/ 	
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/ 		// Check if module is in cache
/******/ 		var cachedModule = __webpack_module_cache__[moduleId];
/******/ 		if (cachedModule !== undefined) {
/******/ 			return cachedModule.exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = __webpack_module_cache__[moduleId] = {
/******/ 			// no module.id needed
/******/ 			// no module.loaded needed
/******/ 			exports: {}
/******/ 		};
/******/ 	
/******/ 		// Execute the module function
/******/ 		__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ 	
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/ 	
/************************************************************************/
/******/ 	
/******/ 	// startup
/******/ 	// Load entry module and return exports
/******/ 	// This entry module is referenced by other modules so it can't be inlined
/******/ 	var __webpack_exports__ = __webpack_require__("./src/index.ts");
/******/ 	
/******/ 	return __webpack_exports__;
/******/ })()
;
});
(function webpackUniversalModuleDefinition(root, factory) {
	if(typeof exports === 'object' && typeof module === 'object')
		module.exports = factory();
	else if(typeof define === 'function' && define.amd)
		define("AppspaceCloudDeviceAppUpdate", [], factory);
	else if(typeof exports === 'object')
		exports["AppspaceCloudDeviceAppUpdate"] = factory();
	else
		root["AppspaceCloudDeviceAppUpdate"] = factory();
})(this, () => {
return /******/ (() => { // webpackBootstrap
/******/ 	"use strict";
/******/ 	var __webpack_modules__ = ({

/***/ "./src/enums/app-update.enum.ts":
/*!**************************************!*\
  !*** ./src/enums/app-update.enum.ts ***!
  \**************************************/
/***/ ((__unused_webpack_module, exports) => {


Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.AppUpdateEnum = void 0;
exports.AppUpdateEnum = {
    AppPackageFile: 'app-package.json',
    AutoUpdateConfigurationUpdatedEvent: 'auto-update-configuration-updated',
    AppUpdateConfigurationUpdatedEvent: 'app-update-configuration-updated',
};


/***/ }),

/***/ "./src/index.ts":
/*!**********************!*\
  !*** ./src/index.ts ***!
  \**********************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {


var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
    for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
__exportStar(__webpack_require__(/*! ./services */ "./src/services/index.ts"), exports);


/***/ }),

/***/ "./src/services/app-update.service.ts":
/*!********************************************!*\
  !*** ./src/services/app-update.service.ts ***!
  \********************************************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {


var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
    return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (g && (g = 0, op[0] && (_ = 0)), _) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.AppUpdateService = void 0;
var app_update_enum_1 = __webpack_require__(/*! ../enums/app-update.enum */ "./src/enums/app-update.enum.ts");
/**
 * Service for managing application updates on device platforms.
 *
 * The AppUpdateService provides a comprehensive solution for handling application updates
 * across different device platforms. It supports various update capabilities including
 * scheduling, frequency control, downgrade prevention, and pinning mechanisms.
 *
 * Key Features:
 * - Automated update checking via constellation service integration
 * - Platform-specific update handling through configurable handlers
 * - Package validation and version verification
 * - Flexible capability configuration for different platform requirements
 * - Comprehensive logging and error handling
 *
 * The service works by querying a constellation service for available updates,
 * validating update packages, and coordinating with platform-specific handlers
 * to execute updates when appropriate. It supports both immediate and scheduled
 * update execution based on platform capabilities.
 */
var AppUpdateService = /** @class */ (function () {
    /**
     * Creates a new AppUpdateService instance.
     *
     * Initializes the service with the necessary dependencies and configuration.
     * The capabilities parameter is merged with default capabilities to ensure
     * all required properties are present.
     *
     * @param capabilities - Update capabilities configuration defining what update features are supported
     * @param logService - Logging service instance for recording service activities and errors
     * @param logKey - Unique identifier used for log categorization and filtering
     * @param requestConstellationService - Service for making HTTP requests to the constellation API
     * @param getFile - Function responsible for downloading and retrieving files from URLs
     * @param deviceInformation - Contains device ID, app version, and package type information
     * @param handler - Optional platform-specific handler for executing update operations
     */
    function AppUpdateService(capabilities, logService, logKey, requestConstellationService, getFile, deviceInformation, handler) {
        this.DEFAULT_APP_UPDATE_CAPABILITIES = {
            scheduling: false,
            frequency: false,
            downgrade: false,
            pinning: false,
        };
        this.capabilities = __assign(__assign({}, this.DEFAULT_APP_UPDATE_CAPABILITIES), capabilities);
        this.logService = logService;
        this.logKey = logKey;
        this.requestConstellationService = requestConstellationService;
        this.getFile = getFile;
        this.deviceId = deviceInformation.deviceId;
        this.appVersion = deviceInformation.appVersion;
        this.appPackageType = deviceInformation.appPackageType;
        this.handler = handler;
    }
    /**
     * Starts the app update workflow and checks for available updates.
     *
     * This is the primary entry point for the update service. It initiates the update
     * checking process by querying the constellation service for available updates.
     * The method handles error cases gracefully and logs appropriate messages
     * throughout the process.
     *
     * Workflow:
     * - Logs the start of the update workflow and current capabilities
     * - Checks for available updates via the constellation service
     * - Handles errors from the update check process
     * - Sets the update URL if no update is available (to maintain current version)
     * - Logs the current update URL for reference
     *
     * @returns Promise that resolves when the workflow initialization is complete
     */
    AppUpdateService.prototype.start = function () {
        return __awaiter(this, void 0, void 0, function () {
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        this.log('debug', 'Starting app update workflow');
                        this.log('debug', "Device app update capabilities: ".concat(JSON.stringify(this.capabilities)));
                        return [4 /*yield*/, this.setCurrentUpdateUrl()];
                    case 1:
                        _a.sent();
                        return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Stops the app update workflow.
     *
     * Gracefully terminates the update workflow and performs any necessary cleanup.
     * This method is designed to be idempotent and safe to call multiple times.
     *
     * @returns Always returns true to indicate successful stop operation
     */
    AppUpdateService.prototype.stop = function () {
        this.log('debug', 'Stopping app update workflow');
        return true;
    };
    /**
     * Checks if app updates are supported on this platform.
     *
     * Determines whether the current platform and configuration supports
     * application updates. This is primarily based on the presence of
     * a valid device ID, which is required for update operations.
     *
     * @returns Promise that resolves to true if updates are supported, false otherwise
     */
    AppUpdateService.prototype.isSupported = function () {
        return __awaiter(this, void 0, void 0, function () {
            return __generator(this, function (_a) {
                return [2 /*return*/, Boolean(this.deviceId)];
            });
        });
    };
    /**
     * Executes an app update if one is available.
     *
     * This method performs a complete update execution workflow. It first checks
     * for available updates and then proceeds with the update if one is found.
     * The method will reject the promise if no update is required or if the
     * update information is incomplete.
     *
     * Process:
     * - Checks for available updates via the constellation service
     * - Validates that update information is complete (URL and version)
     * - Sets the update URL and version for the platform handler
     * - Executes the actual update through the platform handler
     *
     * @returns Promise that resolves when the update execution is complete
     */
    AppUpdateService.prototype.updateApp = function () {
        return __awaiter(this, void 0, void 0, function () {
            var checkUpdateResult, isUpdateUrlSet;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        this.log('debug', 'Executing app update');
                        return [4 /*yield*/, this.checkUpdate()];
                    case 1:
                        checkUpdateResult = _a.sent();
                        if (!checkUpdateResult.isUpdateAvailable || !checkUpdateResult.downloadUrl || !checkUpdateResult.version) {
                            return [2 /*return*/, Promise.reject("No app update required: ".concat(checkUpdateResult.errorMessage))];
                        }
                        return [4 /*yield*/, this.setUpdateUrl(checkUpdateResult.downloadUrl, checkUpdateResult.version)];
                    case 2:
                        isUpdateUrlSet = _a.sent();
                        if (!isUpdateUrlSet) {
                            return [2 /*return*/, Promise.reject('Failed to set update URL')];
                        }
                        return [4 /*yield*/, this.doUpdateApp()];
                    case 3:
                        _a.sent();
                        return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Handles application update events and triggers appropriate actions.
     *
     * This method provides an event-driven interface for the update service,
     * allowing external components to notify the service of configuration changes
     * or other relevant events. The method processes different event types and
     * executes corresponding update workflows.
     *
     * Supported Events:
     * - `auto-update-configuration-updated`: Triggered when auto-update settings change
     *   - When `data` is falsy (false, 0, '', null, undefined, NaN): Enables manual update mode
     *     by setting the current update URL to ensure the app remains on the current version
     *   - When `data` is truthy: No action is taken (auto-update remains enabled)
     * - `app-update-configuration-updated`: Triggered when app update feature flags change
     *   - Expects `data` to be an `AppUpdateConfiguration` object
     *   - Updates the app update configuration through the platform handler
     *   - The configuration is applied asynchronously and errors are logged
     *
     * The method is designed to be synchronous and non-blocking, making it safe
     * to call from event handlers or other synchronous contexts.
     *
     * @param event - The event type identifier (case-sensitive)
     * @param data - Event-specific data payload (type depends on event type)
     */
    AppUpdateService.prototype.onEvent = function (event, data) {
        switch (event) {
            case app_update_enum_1.AppUpdateEnum.AutoUpdateConfigurationUpdatedEvent:
                var isAutoUpdateEnabled = data;
                if (!isAutoUpdateEnabled) {
                    // Set the current update URL for manual update
                    this.setCurrentUpdateUrl();
                }
                break;
            case app_update_enum_1.AppUpdateEnum.AppUpdateConfigurationUpdatedEvent:
                this.setUpdateConfiguration(data);
                break;
            default:
                break;
        }
    };
    /**
     * Logs a message using the configured log service.
     *
     * Provides a centralized logging mechanism that uses the configured
     * log service with the appropriate log level and message. The log key
     * is automatically included for proper categorization.
     *
     * @param level - Log level (trace, debug, info, warn, error, fatal)
     * @param message - Message to log
     */
    AppUpdateService.prototype.log = function (level, message) {
        this.logService[level](this.logKey, message);
    };
    /**
     * Validates an update package.
     *
     * Performs comprehensive validation of an update package by downloading
     * and examining the package information file. This includes checking
     * package type compatibility and version consistency.
     *
     * Validation checks:
     * - Package information file exists and is accessible
     * - Package type matches the current application's package type
     * - Package version matches the expected version (if provided)
     *
     * @param downloadUrl - Base download URL for the package
     * @param version - Expected version of the package
     * @returns Promise that resolves to true if valid, false otherwise
     */
    AppUpdateService.prototype.validatePackage = function (downloadUrl, version) {
        return __awaiter(this, void 0, void 0, function () {
            var packageInfo, error_1;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        _a.trys.push([0, 2, , 3]);
                        return [4 /*yield*/, this.getFile("".concat(downloadUrl, "/").concat(app_update_enum_1.AppUpdateEnum.AppPackageFile))];
                    case 1:
                        packageInfo = _a.sent();
                        if (!packageInfo) {
                            this.log('error', 'Package information not found');
                            return [2 /*return*/, false];
                        }
                        if (packageInfo.packageType !== this.appPackageType) {
                            this.log('error', "Invalid package type (expected: ".concat(this.appPackageType, ", actual: ").concat(packageInfo.packageType, ")"));
                            return [2 /*return*/, false];
                        }
                        if (version && packageInfo.version !== version) {
                            this.log('error', "Invalid package version (expected: ".concat(version, ", actual: ").concat(packageInfo.version, ")"));
                            return [2 /*return*/, false];
                        }
                        return [2 /*return*/, true];
                    case 2:
                        error_1 = _a.sent();
                        this.log('error', "Error validating update package: ".concat(error_1 instanceof Error ? error_1.message : JSON.stringify(error_1)));
                        return [2 /*return*/, false];
                    case 3: return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Sets the app update configuration through the platform handler.
     *
     * This method applies app update configuration settings (such as feature flags)
     * to the platform-specific handler. The configuration is typically used to enable
     * or disable app update functionality dynamically.
     *
     * The method gracefully handles cases where:
     * - No handler is provided (logs debug message and returns)
     * - Handler doesn't support configuration updates (logs debug message and returns)
     * - Errors occur during configuration update (logs error and continues)
     *
     * @param configuration - The app update configuration to apply, containing feature flag settings
     * @returns Promise that resolves when the configuration update is complete (or skipped)
     */
    AppUpdateService.prototype.setUpdateConfiguration = function (configuration) {
        return __awaiter(this, void 0, void 0, function () {
            var error_2;
            var _a;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        if (!((_a = this.handler) === null || _a === void 0 ? void 0 : _a.setUpdateConfiguration)) {
                            this.log('debug', 'No update handler provided to set app update configuration');
                            return [2 /*return*/];
                        }
                        this.log('debug', "Setting app update configuration to ".concat(JSON.stringify(configuration)));
                        _b.label = 1;
                    case 1:
                        _b.trys.push([1, 3, , 4]);
                        return [4 /*yield*/, this.handler.setUpdateConfiguration(configuration)];
                    case 2:
                        _b.sent();
                        return [3 /*break*/, 4];
                    case 3:
                        error_2 = _b.sent();
                        this.log('error', "Error setting app update configuration: ".concat(error_2 instanceof Error ? error_2.message : JSON.stringify(error_2)));
                        return [3 /*break*/, 4];
                    case 4: return [2 /*return*/];
                }
            });
        });
    };
    AppUpdateService.prototype.setCurrentUpdateUrl = function () {
        return __awaiter(this, void 0, void 0, function () {
            var checkUpdateResult, currentUpdateUrl, error_3, errorMessage;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        _a.trys.push([0, 5, , 6]);
                        return [4 /*yield*/, this.checkUpdate()];
                    case 1:
                        checkUpdateResult = _a.sent();
                        // Check if checkUpdate returned an error
                        if (checkUpdateResult.errorMessage) {
                            this.log('error', "Error checking update: ".concat(checkUpdateResult.errorMessage));
                            return [2 /*return*/];
                        }
                        if (!((checkUpdateResult.isFallback || !checkUpdateResult.isUpdateAvailable) && checkUpdateResult.downloadUrl && checkUpdateResult.version)) return [3 /*break*/, 3];
                        // Set the update URL to ensure that the app remains on the current or fallback app version
                        return [4 /*yield*/, this.setUpdateUrl(checkUpdateResult.downloadUrl, checkUpdateResult.version)];
                    case 2:
                        // Set the update URL to ensure that the app remains on the current or fallback app version
                        _a.sent();
                        _a.label = 3;
                    case 3: return [4 /*yield*/, this.getUpdateUrl()];
                    case 4:
                        currentUpdateUrl = _a.sent();
                        if (!currentUpdateUrl) {
                            return [2 /*return*/];
                        }
                        this.log('info', "Current update URL: ".concat(currentUpdateUrl));
                        return [3 /*break*/, 6];
                    case 5:
                        error_3 = _a.sent();
                        errorMessage = error_3 instanceof Error ? error_3.message : JSON.stringify(error_3);
                        this.log('error', "Error checking update: ".concat(errorMessage));
                        return [3 /*break*/, 6];
                    case 6: return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Checks if an update is supported by the platform.
     *
     * Determines whether a specific update is compatible with the current
     * platform by delegating to the platform-specific handler if available.
     * If no handler is provided, the update is considered supported by default.
     *
     * @param downloadUrl - URL of the update to check for platform compatibility
     * @returns Promise that resolves to true if supported, false otherwise
     */
    AppUpdateService.prototype.isUpdateSupported = function (downloadUrl) {
        return __awaiter(this, void 0, void 0, function () {
            var _a;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        if (!((_a = this.handler) === null || _a === void 0 ? void 0 : _a.isSupported)) {
                            return [2 /*return*/, true];
                        }
                        return [4 /*yield*/, this.handler.isSupported(downloadUrl)];
                    case 1: return [2 /*return*/, _b.sent()];
                }
            });
        });
    };
    /**
     * Checks for available app updates by querying the constellation service.
     *
     * This method performs the following operations:
     * 1. Makes a GET request to the constellation service API endpoint for app updates
     * 2. Validates that the update is supported by the platform using the handler
     * 3. Sets the update URL and version if the update is valid and supported
     * 4. Compares the available version with the current app version
     * 5. Logs appropriate messages based on the update status
     *
     * The method handles errors gracefully by:
     * - Catching constellation service request failures and returning error information
     * - Checking platform support and returning appropriate status
     * - Comparing versions and determining update availability
     *
     * @returns Promise that resolves to CheckUpdateResult containing update information and status
     */
    AppUpdateService.prototype.checkUpdate = function () {
        return __awaiter(this, void 0, void 0, function () {
            var errorMessage, checkUpdateResult, result, isUpdateSupported, targetVersion, targetDownloadUrl, error_4;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        errorMessage = '';
                        checkUpdateResult = {
                            isUpdateAvailable: false,
                            isFallback: false,
                        };
                        _a.label = 1;
                    case 1:
                        _a.trys.push([1, 4, , 5]);
                        return [4 /*yield*/, this.requestConstellationService({
                                url: "api/v3/devicegateway/devices/".concat(this.deviceId, "/appupdate"),
                                method: 'GET',
                                contentType: 'application/json',
                            })];
                    case 2:
                        result = _a.sent();
                        return [4 /*yield*/, this.isUpdateSupported(result.downloadUrl)];
                    case 3:
                        isUpdateSupported = _a.sent();
                        if (!isUpdateSupported) {
                            checkUpdateResult.errorMessage = 'Update is not supported';
                            return [2 /*return*/, checkUpdateResult];
                        }
                        targetVersion = result.version;
                        targetDownloadUrl = result.downloadUrl;
                        if (result.isObsolete) {
                            this.log('warn', "Target app update version [".concat(targetVersion, "] is obsolete"));
                            if (result.fallbackVersion) {
                                this.log('warn', "Using fallback version [".concat(result.fallbackVersion.version, "] for obsolete update"));
                                targetVersion = result.fallbackVersion.version;
                                targetDownloadUrl = result.fallbackVersion.downloadUrl;
                                checkUpdateResult.isFallback = true;
                            }
                            else {
                                this.log('warn', 'No fallback version found for obsolete update');
                                return [2 /*return*/, checkUpdateResult];
                            }
                        }
                        checkUpdateResult.downloadUrl = targetDownloadUrl;
                        checkUpdateResult.version = targetVersion;
                        if (targetVersion !== this.appVersion) {
                            this.log('info', "App update required [current: ".concat(this.appVersion, ", available: ").concat(targetVersion, "]"));
                            checkUpdateResult.isUpdateAvailable = true;
                            return [2 /*return*/, checkUpdateResult];
                        }
                        checkUpdateResult.errorMessage = errorMessage;
                        return [2 /*return*/, checkUpdateResult];
                    case 4:
                        error_4 = _a.sent();
                        errorMessage = "Error retrieving app update information: ".concat(error_4 instanceof Error ? error_4.message : JSON.stringify(error_4));
                        checkUpdateResult.errorMessage = errorMessage;
                        return [2 /*return*/, checkUpdateResult];
                    case 5: return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Gets the current update URL from the handler.
     *
     * Retrieves the currently configured update URL from the platform-specific
     * handler. This is useful for checking what update is currently set
     * for execution on the platform.
     *
     * @returns Promise that resolves to the current update URL, or empty string if no handler
     */
    AppUpdateService.prototype.getUpdateUrl = function () {
        return __awaiter(this, void 0, void 0, function () {
            var _a;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        if (!((_a = this.handler) === null || _a === void 0 ? void 0 : _a.getUpdateUrl)) {
                            this.log('debug', 'No update handler provided to get update URL');
                            return [2 /*return*/, ''];
                        }
                        return [4 /*yield*/, this.handler.getUpdateUrl()];
                    case 1: return [2 /*return*/, _b.sent()];
                }
            });
        });
    };
    /**
     * Sets the update URL using the handler.
     *
     * Configures the platform-specific handler with a new update URL and version.
     * This method performs package validation before setting the URL to ensure
     * the update is valid and compatible with the current application.
     *
     * Process:
     * - Validates the update package for type and version compatibility
     * - Logs the update URL being set
     * - Delegates to the platform handler to actually set the URL
     * - Handles any errors that occur during the process
     *
     * @param downloadUrl - URL to set as the update URL
     * @param version - Version of the update
     * @returns Promise that resolves when the URL is set
     */
    AppUpdateService.prototype.setUpdateUrl = function (downloadUrl, version) {
        return __awaiter(this, void 0, void 0, function () {
            var isPackageValid, error_5;
            var _a;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        if (!((_a = this.handler) === null || _a === void 0 ? void 0 : _a.setUpdateUrl)) {
                            this.log('debug', 'No update handler provided to set update URL');
                            return [2 /*return*/, true];
                        }
                        downloadUrl = downloadUrl.toLowerCase();
                        return [4 /*yield*/, this.validatePackage(downloadUrl, version)];
                    case 1:
                        isPackageValid = _b.sent();
                        if (!isPackageValid) {
                            this.log('error', 'Update package is invalid');
                            return [2 /*return*/, false];
                        }
                        this.log('info', "Setting update URL to ".concat(downloadUrl));
                        _b.label = 2;
                    case 2:
                        _b.trys.push([2, 4, , 5]);
                        return [4 /*yield*/, this.handler.setUpdateUrl(downloadUrl)];
                    case 3:
                        _b.sent();
                        return [2 /*return*/, true];
                    case 4:
                        error_5 = _b.sent();
                        this.log('error', "Error setting update URL: ".concat(error_5 instanceof Error ? error_5.message : JSON.stringify(error_5)));
                        return [2 /*return*/, false];
                    case 5: return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Executes the update using the handler.
     *
     * Delegates the actual update execution to the platform-specific handler.
     * This method assumes that the update URL has already been set and validated
     * by previous operations.
     *
     * @returns Promise that resolves with the execution result
     */
    AppUpdateService.prototype.executeUpdate = function () {
        return __awaiter(this, void 0, void 0, function () {
            var _a;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        if (!((_a = this.handler) === null || _a === void 0 ? void 0 : _a.executeUpdate)) {
                            this.log('debug', 'No update handler provided to execute update');
                            return [2 /*return*/];
                        }
                        this.log('debug', 'Executing update');
                        return [4 /*yield*/, this.handler.executeUpdate()];
                    case 1: return [2 /*return*/, _b.sent()];
                }
            });
        });
    };
    /**
     * Performs the app update operation.
     *
     * Orchestrates the final update execution by checking platform capabilities
     * and delegating to the appropriate execution method. This method ensures
     * that updates are only executed when the platform supports scheduling.
     *
     * @returns Promise that resolves with the update result
     */
    AppUpdateService.prototype.doUpdateApp = function () {
        return __awaiter(this, void 0, void 0, function () {
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (!this.capabilities.scheduling) {
                            this.log('info', 'App update scheduling is not supported');
                            return [2 /*return*/];
                        }
                        return [4 /*yield*/, this.executeUpdate()];
                    case 1: return [2 /*return*/, _a.sent()];
                }
            });
        });
    };
    return AppUpdateService;
}());
exports.AppUpdateService = AppUpdateService;


/***/ }),

/***/ "./src/services/index.ts":
/*!*******************************!*\
  !*** ./src/services/index.ts ***!
  \*******************************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {


var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
    for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
__exportStar(__webpack_require__(/*! ./app-update.service */ "./src/services/app-update.service.ts"), exports);


/***/ })

/******/ 	});
/************************************************************************/
/******/ 	// The module cache
/******/ 	var __webpack_module_cache__ = {};
/******/ 	
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/ 		// Check if module is in cache
/******/ 		var cachedModule = __webpack_module_cache__[moduleId];
/******/ 		if (cachedModule !== undefined) {
/******/ 			return cachedModule.exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = __webpack_module_cache__[moduleId] = {
/******/ 			// no module.id needed
/******/ 			// no module.loaded needed
/******/ 			exports: {}
/******/ 		};
/******/ 	
/******/ 		// Execute the module function
/******/ 		__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ 	
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/ 	
/************************************************************************/
/******/ 	
/******/ 	// startup
/******/ 	// Load entry module and return exports
/******/ 	// This entry module is referenced by other modules so it can't be inlined
/******/ 	var __webpack_exports__ = __webpack_require__("./src/index.ts");
/******/ 	
/******/ 	return __webpack_exports__;
/******/ })()
;
});
(function webpackUniversalModuleDefinition(root, factory) {
	if(typeof exports === 'object' && typeof module === 'object')
		module.exports = factory();
	else if(typeof define === 'function' && define.amd)
		define("AppspaceCloudDeviceCommand", [], factory);
	else if(typeof exports === 'object')
		exports["AppspaceCloudDeviceCommand"] = factory();
	else
		root["AppspaceCloudDeviceCommand"] = factory();
})(this, () => {
return /******/ (() => { // webpackBootstrap
/******/ 	"use strict";
/******/ 	var __webpack_modules__ = ({

/***/ "./src/enums/command.enum.ts":
/*!***********************************!*\
  !*** ./src/enums/command.enum.ts ***!
  \***********************************/
/***/ ((__unused_webpack_module, exports) => {


Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.HttpMethod = exports.CommandOperation = exports.CommandEnum = void 0;
exports.CommandEnum = {
    DebounceDelayMs: 1000,
};
exports.CommandOperation = {
    Screenshot: 'screenshot',
};
exports.HttpMethod = {
    POST: 'POST',
    GET: 'GET',
    PUT: 'PUT',
    DELETE: 'DELETE',
};


/***/ }),

/***/ "./src/handlers/index.ts":
/*!*******************************!*\
  !*** ./src/handlers/index.ts ***!
  \*******************************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {


var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
    for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
__exportStar(__webpack_require__(/*! ./screenshot.handler */ "./src/handlers/screenshot.handler.ts"), exports);


/***/ }),

/***/ "./src/handlers/screenshot.handler.ts":
/*!********************************************!*\
  !*** ./src/handlers/screenshot.handler.ts ***!
  \********************************************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {


var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
    return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (g && (g = 0, op[0] && (_ = 0)), _) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.ScreenshotHandler = void 0;
var command_enum_1 = __webpack_require__(/*! ../enums/command.enum */ "./src/enums/command.enum.ts");
/**
 * Screenshot command handler.
 *
 * This handler processes screenshot commands by:
 * 1. Retrieving the screenshot file from the provided URL
 * 2. Uploading the screenshot to the constellation API with proper content type
 * 3. Returning the upload result
 *
 * The handler validates device ID availability, checks feature and platform support,
 * and handles errors gracefully with comprehensive logging.
 */
var ScreenshotHandler = /** @class */ (function () {
    /**
     * Creates a new ScreenshotHandler instance.
     *
     * @param logService - Logging service instance for recording handler activities and errors
     * @param logKey - Unique identifier used for log categorization and filtering
     * @param retrieveFile - Function to retrieve a file from a URL, returning a FileResult with data and mimeType
     * @param requestConstellationService - Service for making HTTP requests to the constellation API
     * @param checkFeatureSupport - Function to check if a feature is supported, returns boolean or null
     * @param checkPlatformSupport - Function to check if the platform version meets the minimum requirement, returns a Promise<boolean>
     */
    function ScreenshotHandler(logService, logKey, retrieveFile, requestConstellationService, checkFeatureSupport, checkPlatformSupport) {
        this.PLATFORM_VERSION_REQUIREMENTS = {
            CLOUD: {
                MIN: '2601.0.0',
                MAX: '2601.0.0'
            },
            GLOBAL: '2601.0.0'
        };
        this.logService = logService;
        this.logKey = logKey;
        this.retrieveFile = retrieveFile;
        this.requestConstellationService = requestConstellationService;
        this.checkFeatureSupport = checkFeatureSupport;
        this.checkPlatformSupport = checkPlatformSupport;
    }
    /**
     * Checks if the screenshot operation is supported by the device.
     *
     * Validates both feature support (screenshot.supported) and platform version
     * requirements. Returns true only if both checks pass.
     *
     * @returns Promise that resolves to true if both feature and platform support checks pass, false otherwise
     */
    ScreenshotHandler.prototype.isOperationSupported = function () {
        return __awaiter(this, void 0, void 0, function () {
            var isFeatureSupported, isPlatformSupported;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        isFeatureSupported = Boolean(this.checkFeatureSupport(ScreenshotHandler.SCREENSHOT_SUPPORTED_FEATURE));
                        return [4 /*yield*/, this.checkPlatformSupport(this.PLATFORM_VERSION_REQUIREMENTS)];
                    case 1:
                        isPlatformSupported = _a.sent();
                        return [2 /*return*/, isFeatureSupported && isPlatformSupported];
                }
            });
        });
    };
    /**
     * Handles screenshot command execution.
     *
     * Validates device ID availability, retrieves the screenshot file from the provided URL,
     * and uploads it to the constellation API. Returns error results if validation fails,
     * file retrieval fails, or upload fails.
     *
     * @param command - The command to execute, must contain an id and parameters with subjectId
     * @param context - Optional context information for command execution, may contain deviceId
     * @param result - The execution result containing the screenshot file URL as a string
     * @returns Promise that resolves to the command execution result with success status and data or error message
     */
    ScreenshotHandler.prototype.handle = function (command, context, result) {
        return __awaiter(this, void 0, void 0, function () {
            var deviceId, screenshotFileUrl, retrieveResult;
            var _a;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        this.log('debug', 'Screenshot handler executed');
                        deviceId = (context === null || context === void 0 ? void 0 : context.deviceId) || ((_a = command.parameters) === null || _a === void 0 ? void 0 : _a.subjectId);
                        if (!deviceId) {
                            this.log('error', 'Device ID not found in command context');
                            return [2 /*return*/, {
                                    success: false,
                                    message: 'Device ID not found in command context'
                                }];
                        }
                        if (!result) {
                            this.log('error', 'Screenshot result not found');
                            return [2 /*return*/, {
                                    success: false,
                                    message: 'Empty screenshot result'
                                }];
                        }
                        screenshotFileUrl = result;
                        return [4 /*yield*/, this.retrieveScreenshotFile(screenshotFileUrl)];
                    case 1:
                        retrieveResult = _b.sent();
                        if (!retrieveResult.success || !retrieveResult.file) {
                            return [2 /*return*/, retrieveResult];
                        }
                        return [4 /*yield*/, this.uploadScreenshot(retrieveResult.file, deviceId, command.id)];
                    case 2: return [2 /*return*/, _b.sent()];
                }
            });
        });
    };
    /**
     * Retrieves the screenshot file from the provided URL.
     *
     * Fetches the file as a blob to preserve binary data and MIME type information.
     * Handles errors gracefully and returns a structured error result on failure.
     *
     * @param fileUrl - The URL of the screenshot file to retrieve
     * @returns Promise that resolves to a CommandResult with the file (FileResult) on success, or an error result with message on failure
     */
    ScreenshotHandler.prototype.retrieveScreenshotFile = function (fileUrl) {
        return __awaiter(this, void 0, void 0, function () {
            var screenshotFile, error_1, errorMessage;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        _a.trys.push([0, 2, , 3]);
                        return [4 /*yield*/, this.retrieveFile(fileUrl, { responseType: ScreenshotHandler.RESPONSE_TYPE_BLOB })];
                    case 1:
                        screenshotFile = _a.sent();
                        return [2 /*return*/, {
                                success: true,
                                file: screenshotFile
                            }];
                    case 2:
                        error_1 = _a.sent();
                        errorMessage = error_1 instanceof Error ? error_1.message : JSON.stringify(error_1);
                        this.log('error', "Error retrieving screenshot file from URL: ".concat(errorMessage));
                        return [2 /*return*/, {
                                success: false,
                                message: "Failed to retrieve screenshot file: ".concat(errorMessage)
                            }];
                    case 3: return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Uploads the screenshot file to the constellation API.
     *
     * Uploads the screenshot as binary data with the appropriate content type header.
     * Uses processData: false to prevent data transformation during upload.
     *
     * @param screenshotFile - The screenshot file to upload, containing data (Blob) and mimeType
     * @param deviceId - The device ID to include in the upload URL
     * @param commandId - The command ID to include in the upload URL
     * @returns Promise that resolves to the command execution result with success status and upload response data or error message
     */
    ScreenshotHandler.prototype.uploadScreenshot = function (screenshotFile, deviceId, commandId) {
        return __awaiter(this, void 0, void 0, function () {
            var screenshotUploadResult, error_2, errorMessage;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        _a.trys.push([0, 2, , 3]);
                        return [4 /*yield*/, this.requestConstellationService({
                                url: "api/v3/devicegateway/devices/".concat(deviceId, "/commands/").concat(commandId, "/results"),
                                method: command_enum_1.HttpMethod.POST,
                                contentType: screenshotFile.mimeType,
                                data: screenshotFile.data,
                                processData: false,
                            })];
                    case 1:
                        screenshotUploadResult = _a.sent();
                        this.log('debug', "Screenshot uploaded successfully: ".concat(screenshotUploadResult));
                        return [2 /*return*/, {
                                success: true,
                                data: screenshotUploadResult
                            }];
                    case 2:
                        error_2 = _a.sent();
                        errorMessage = error_2 instanceof Error ? error_2.message : JSON.stringify(error_2);
                        this.log('error', "Error uploading screenshot to constellation API: ".concat(errorMessage));
                        return [2 /*return*/, {
                                success: false,
                                message: "Failed to upload screenshot: ".concat(errorMessage)
                            }];
                    case 3: return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Logs a message using the configured log service.
     *
     * @param level - Log level (trace, debug, info, warn, error, fatal)
     * @param message - Message to log
     */
    ScreenshotHandler.prototype.log = function (level, message) {
        this.logService[level](this.logKey, message);
    };
    ScreenshotHandler.RESPONSE_TYPE_BLOB = 'blob';
    ScreenshotHandler.SCREENSHOT_SUPPORTED_FEATURE = 'screenshot.supported';
    return ScreenshotHandler;
}());
exports.ScreenshotHandler = ScreenshotHandler;


/***/ }),

/***/ "./src/index.ts":
/*!**********************!*\
  !*** ./src/index.ts ***!
  \**********************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {


var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
    for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
__exportStar(__webpack_require__(/*! ./services */ "./src/services/index.ts"), exports);


/***/ }),

/***/ "./src/services/command.service.ts":
/*!*****************************************!*\
  !*** ./src/services/command.service.ts ***!
  \*****************************************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {


var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
    return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (g && (g = 0, op[0] && (_ = 0)), _) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.CommandService = void 0;
var command_enum_1 = __webpack_require__(/*! ../enums/command.enum */ "./src/enums/command.enum.ts");
var handlers_1 = __webpack_require__(/*! ../handlers */ "./src/handlers/index.ts");
/**
 * Service for managing and executing device commands.
 *
 * The CommandService provides a comprehensive solution for handling commands
 * on device platforms. It supports command registration, execution, and
 * result handling with comprehensive logging and error management.
 *
 * Key Features:
 * - Command handler registration and management
 * - Command execution with context support
 * - Debounced command queuing (1 second delay) to handle rapid command sequences
 * - Sequential command processing to prevent race conditions
 * - Event-driven command processing
 * - Comprehensive logging and error handling
 * - Flexible command result handling
 * - Feature and platform version support validation
 *
 * The service works by maintaining a registry of command handlers and
 * executing commands through the appropriate handler when requested. Commands
 * are debounced and queued to ensure proper ordering and prevent conflicts.
 */
var CommandService = /** @class */ (function () {
    /**
     * Creates a new CommandService instance.
     *
     * Initializes the service with the necessary dependencies and registers
     * command handlers internally.
     *
     * @param logService - Logging service instance for recording service activities and errors
     * @param logKey - Unique identifier used for log categorization and filtering
     * @param checkFeatureSupport - Function to check if a feature is supported
     * @param checkPlatformSupport - Function to check if the platform version meets the minimum requirement
     * @param executeCommand - Function to execute a command
     * @param postCommandExecution - Function to perform post-command execution actions
     * @param requestConstellationService - Service for making HTTP requests to the constellation API
     * @param retrieveFile - Function to retrieve a file
     */
    function CommandService(logService, logKey, checkFeatureSupport, checkPlatformSupport, executeCommand, postCommandExecution, requestConstellationService, retrieveFile) {
        this.logService = logService;
        this.logKey = logKey;
        this.handlers = new Map();
        this.checkFeatureSupport = checkFeatureSupport;
        this.checkPlatformSupport = checkPlatformSupport;
        this.requestConstellationService = requestConstellationService;
        this.executeCommand = executeCommand;
        this.postCommandExecution = postCommandExecution;
        this.retrieveFile = retrieveFile;
        this.commandQueue = [];
        this.isProcessingQueue = false;
        this.debounceTimer = undefined;
    }
    /**
     * Starts the command service.
     *
     * Initializes the service and prepares it for command processing.
     * This method can be used to perform any necessary setup operations.
     *
     */
    CommandService.prototype.start = function () {
        this.log('info', 'Command service started');
        this.registerHandlers();
    };
    /**
     * Stops the command service.
     *
     * Gracefully terminates the service and performs any necessary cleanup.
     * This method is designed to be idempotent and safe to call multiple times.
     */
    CommandService.prototype.stop = function () {
        this.cancelDebounceTimer();
        this.log('info', 'Command service stopped');
    };
    /**
     * Processes command execution results using the appropriate handler.
     *
     * This method performs the following operations:
     * 1. Validates that a handler exists for the command type
     * 2. Executes the handler with the command, context, and execution result
     * 3. Logs the execution result
     * 4. Returns the command result
     *
     * @param command - The command that was executed
     * @param result - The execution result from the command execution
     * @param context - Optional context information for command execution
     * @returns Promise that resolves to the command execution result
     */
    CommandService.prototype.processExecution = function (command, result, context) {
        return __awaiter(this, void 0, void 0, function () {
            var operationKey, errorMessage, operationHandler, errorMessage, handlerResult, error_1, errorMessage;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        this.log('debug', "Processing execution for command: ".concat(command.operation, " (id: ").concat(command.id, ")"));
                        operationKey = Object.values(command_enum_1.CommandOperation).find(function (op) { return op === command.operation.toLowerCase(); });
                        if (!operationKey) {
                            errorMessage = "Unknown command operation: ".concat(command.operation);
                            this.log('error', errorMessage);
                            return [2 /*return*/, {
                                    success: false,
                                    message: errorMessage
                                }];
                        }
                        operationHandler = this.handlers.get(operationKey);
                        if (!operationHandler) {
                            errorMessage = "No handler registered for command type: ".concat(command.operation);
                            this.log('error', errorMessage);
                            return [2 /*return*/, {
                                    success: false,
                                    message: errorMessage
                                }];
                        }
                        _a.label = 1;
                    case 1:
                        _a.trys.push([1, 3, , 4]);
                        return [4 /*yield*/, operationHandler.handle(command, context, result)];
                    case 2:
                        handlerResult = _a.sent();
                        if (handlerResult.success) {
                            this.log('info', "Command executed successfully: ".concat(command.operation, " (id: ").concat(command.id, ")"));
                        }
                        else {
                            this.log('warn', "Command execution failed: ".concat(command.operation, " (id: ").concat(command.id, ") - ").concat(handlerResult.message || 'Unknown error'));
                        }
                        return [2 /*return*/, handlerResult];
                    case 3:
                        error_1 = _a.sent();
                        errorMessage = error_1 instanceof Error ? error_1.message : JSON.stringify(error_1);
                        this.log('error', "Error executing command: ".concat(command.operation, " (id: ").concat(command.id, ") - ").concat(errorMessage));
                        return [2 /*return*/, {
                                success: false,
                                message: errorMessage
                            }];
                    case 4: return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Checks if a command operation is supported by the device.
     *
     * Verifies that a handler exists for the operation and, if the handler
     * implements isOperationSupported, validates that the operation is supported
     * by the current device configuration.
     *
     * @param command - The command operation type to check
     * @returns Promise that resolves to true if a handler exists and the operation is supported, false otherwise
     */
    CommandService.prototype.isOperationSupported = function (command) {
        return __awaiter(this, void 0, void 0, function () {
            var handler, _a;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        handler = this.handlers.get(command);
                        if (!handler) {
                            return [2 /*return*/, false];
                        }
                        _a = !handler.isOperationSupported;
                        if (_a) return [3 /*break*/, 2];
                        return [4 /*yield*/, handler.isOperationSupported()];
                    case 1:
                        _a = (_b.sent());
                        _b.label = 2;
                    case 2: return [2 /*return*/, _a];
                }
            });
        });
    };
    /**
     * Registers all command handlers.
     *
     * Initializes and registers handlers for all supported command operations.
     * Currently registers the ScreenshotHandler. This method is called during
     * service startup to set up the handler registry.
     */
    CommandService.prototype.registerHandlers = function () {
        // Register screenshot handler
        this.handlers.set(command_enum_1.CommandOperation.Screenshot, new handlers_1.ScreenshotHandler(this.logService, this.logKey, this.retrieveFile, this.requestConstellationService, this.checkFeatureSupport, this.checkPlatformSupport));
    };
    /**
     * Logs a message using the configured log service.
     *
     * Provides a centralized logging mechanism that uses the configured
     * log service with the appropriate log level and message. The log key
     * is automatically included for proper categorization.
     *
     * @param level - Log level (trace, debug, info, warn, error, fatal)
     * @param message - Message to log
     */
    CommandService.prototype.log = function (level, message) {
        this.logService[level](this.logKey, message);
    };
    /**
     * Executes a command using the configured execute command function.
     *
     * Wraps the command execution with error handling and logging. If execution
     * fails, the error is logged and the promise is rejected with the error message
     * (as a string for Error instances, or JSON stringified for other error types).
     *
     * @param command - The command to execute
     * @returns Promise that resolves to the command execution result
     * @throws Rejects with an error message (string) if command execution fails
     */
    CommandService.prototype.doExecuteCommand = function (command) {
        return __awaiter(this, void 0, void 0, function () {
            var error_2, errorMessage;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        _a.trys.push([0, 2, , 3]);
                        return [4 /*yield*/, this.executeCommand(command)];
                    case 1: return [2 /*return*/, _a.sent()];
                    case 2:
                        error_2 = _a.sent();
                        errorMessage = error_2 instanceof Error ? error_2.message : JSON.stringify(error_2);
                        this.log('error', "Error executing command: ".concat(command.operation, " - ").concat(errorMessage));
                        return [2 /*return*/, Promise.reject(errorMessage)];
                    case 3: return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Performs post-command execution actions.
     *
     * Executes any cleanup or follow-up actions after a command has been processed.
     *
     * @param command - The command that was executed
     */
    CommandService.prototype.doPostCommandExecution = function (command) {
        return __awaiter(this, void 0, void 0, function () {
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4 /*yield*/, this.postCommandExecution(command)];
                    case 1:
                        _a.sent();
                        return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Cancels the debounce timer if it exists.
     */
    CommandService.prototype.cancelDebounceTimer = function () {
        if (!this.debounceTimer) {
            return;
        }
        window.clearTimeout(this.debounceTimer);
        this.debounceTimer = undefined;
    };
    /**
     * Starts the debounce timer to process the queue after the configured delay.
     *
     * Cancels any existing timer before starting a new one. If queue processing
     * fails, the error is logged but does not prevent the timer from completing.
     */
    CommandService.prototype.startDebounceTimer = function () {
        var _this = this;
        this.cancelDebounceTimer();
        this.debounceTimer = window.setTimeout(function () {
            _this.debounceTimer = undefined;
            // Process the queue after debounce period
            _this.processQueue().catch(function (error) {
                _this.log('error', "Error processing command queue: ".concat(error instanceof Error ? error.message : JSON.stringify(error)));
            });
        }, command_enum_1.CommandEnum.DebounceDelayMs);
    };
    /**
     * Processes a single command from the payload.
     *
     * Validates operation support, converts the payload to internal Command format,
     * executes the command, processes the result through the appropriate handler,
     * and performs post-execution actions.
     *
     * @param payload - The command payload to process
     */
    CommandService.prototype.processCommand = function (payload) {
        return __awaiter(this, void 0, void 0, function () {
            var operation, commandId, subjectId, userId, command, context, executionResult;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        operation = payload.Operation;
                        commandId = payload.CommandId;
                        subjectId = payload.SubjectId;
                        userId = payload.UserId;
                        return [4 /*yield*/, this.isOperationSupported(operation)];
                    case 1:
                        // Check if operation is supported
                        if (!(_a.sent())) {
                            this.log('warn', "Command operation [".concat(operation, "] not supported due to failure to meet the feature or platform requirements"));
                            return [2 /*return*/];
                        }
                        command = {
                            id: commandId || '',
                            operation: operation,
                            payload: payload,
                            parameters: {
                                subjectId: subjectId,
                                subjectType: payload.SubjectType,
                                userId: userId,
                                commandId: commandId
                            }
                        };
                        context = {
                            deviceId: subjectId,
                            timestamp: Date.now(),
                            metadata: {
                                topic: payload.Topic,
                                originalPayload: payload
                            }
                        };
                        return [4 /*yield*/, this.doExecuteCommand(command)];
                    case 2:
                        executionResult = _a.sent();
                        // Process the execution result
                        return [4 /*yield*/, this.processExecution(command, executionResult, context)];
                    case 3:
                        // Process the execution result
                        _a.sent();
                        return [4 /*yield*/, this.doPostCommandExecution(command)];
                    case 4:
                        _a.sent();
                        return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Processes commands from the queue sequentially.
     *
     * This method ensures that commands are processed one at a time to prevent
     * race conditions and ensure proper ordering. If processing a command fails,
     * the error is logged and processing continues with the next command in the queue.
     */
    CommandService.prototype.processQueue = function () {
        return __awaiter(this, void 0, void 0, function () {
            var payload, error_3;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (this.isProcessingQueue || this.commandQueue.length === 0) {
                            return [2 /*return*/];
                        }
                        this.isProcessingQueue = true;
                        _a.label = 1;
                    case 1:
                        if (!(this.commandQueue.length > 0)) return [3 /*break*/, 6];
                        payload = this.commandQueue.shift();
                        if (!payload) {
                            return [3 /*break*/, 6];
                        }
                        _a.label = 2;
                    case 2:
                        _a.trys.push([2, 4, , 5]);
                        return [4 /*yield*/, this.processCommand(payload)];
                    case 3:
                        _a.sent();
                        return [3 /*break*/, 5];
                    case 4:
                        error_3 = _a.sent();
                        this.log('error', "Error processing queued command: ".concat(payload.Operation, " - ").concat(error_3 instanceof Error ? error_3.message : JSON.stringify(error_3)));
                        return [3 /*break*/, 5];
                    case 5: return [3 /*break*/, 1];
                    case 6:
                        this.isProcessingQueue = false;
                        return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Handles device command messages with debouncing and queuing.
     *
     * Processes device command messages by:
     * 1. Debouncing commands by userId + operation (1 second)
     * 2. Taking the last command when multiple commands arrive for the same user+operation
     * 3. Queuing commands for sequential processing
     * 4. Extracting the operation type and validating support
     * 5. Converting the payload to an internal Command format
     * 6. Creating command context with device and metadata information
     * 7. Executing the command
     * 8. Processing the execution result through the appropriate handler
     *
     * Logs command information and handles unsupported operations.
     *
     * @param payload - The device command message to process
     */
    CommandService.prototype.onCommandReceived = function (payload) {
        return __awaiter(this, void 0, void 0, function () {
            var operation, userId, existingIndex;
            return __generator(this, function (_a) {
                operation = payload.Operation;
                userId = payload.UserId;
                this.log('info', "Received device command: ".concat(JSON.stringify(payload)));
                existingIndex = this.commandQueue.findIndex(function (queuedPayload) { return queuedPayload.UserId === userId && queuedPayload.Operation === operation; });
                if (existingIndex !== -1) {
                    this.commandQueue.splice(existingIndex, 1);
                }
                // Add the new command payload to the queue
                this.commandQueue.push(payload);
                // Start debounce timer (will cancel existing timer if present)
                this.startDebounceTimer();
                return [2 /*return*/];
            });
        });
    };
    return CommandService;
}());
exports.CommandService = CommandService;


/***/ }),

/***/ "./src/services/index.ts":
/*!*******************************!*\
  !*** ./src/services/index.ts ***!
  \*******************************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {


var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
    for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
__exportStar(__webpack_require__(/*! ./command.service */ "./src/services/command.service.ts"), exports);


/***/ })

/******/ 	});
/************************************************************************/
/******/ 	// The module cache
/******/ 	var __webpack_module_cache__ = {};
/******/ 	
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/ 		// Check if module is in cache
/******/ 		var cachedModule = __webpack_module_cache__[moduleId];
/******/ 		if (cachedModule !== undefined) {
/******/ 			return cachedModule.exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = __webpack_module_cache__[moduleId] = {
/******/ 			// no module.id needed
/******/ 			// no module.loaded needed
/******/ 			exports: {}
/******/ 		};
/******/ 	
/******/ 		// Execute the module function
/******/ 		__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ 	
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/ 	
/************************************************************************/
/******/ 	
/******/ 	// startup
/******/ 	// Load entry module and return exports
/******/ 	// This entry module is referenced by other modules so it can't be inlined
/******/ 	var __webpack_exports__ = __webpack_require__("./src/index.ts");
/******/ 	
/******/ 	return __webpack_exports__;
/******/ })()
;
});
/*!
 * jQuery JavaScript Library v3.6.0
 * https://jquery.com/
 *
 * Includes Sizzle.js
 * https://sizzlejs.com/
 *
 * Copyright OpenJS Foundation and other contributors
 * Released under the MIT license
 * https://jquery.org/license
 *
 * Date: 2021-03-02T17:08Z
 */
( function( global, factory ) {

	"use strict";

	if ( typeof module === "object" && typeof module.exports === "object" ) {

		// For CommonJS and CommonJS-like environments where a proper `window`
		// is present, execute the factory and get jQuery.
		// For environments that do not have a `window` with a `document`
		// (such as Node.js), expose a factory as module.exports.
		// This accentuates the need for the creation of a real `window`.
		// e.g. var jQuery = require("jquery")(window);
		// See ticket #14549 for more info.
		module.exports = global.document ?
			factory( global, true ) :
			function( w ) {
				if ( !w.document ) {
					throw new Error( "jQuery requires a window with a document" );
				}
				return factory( w );
			};
	} else {
		factory( global );
	}

// Pass this if window is not defined yet
} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {

// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
// enough that all such attempts are guarded in a try block.
"use strict";

var arr = [];

var getProto = Object.getPrototypeOf;

var slice = arr.slice;

var flat = arr.flat ? function( array ) {
	return arr.flat.call( array );
} : function( array ) {
	return arr.concat.apply( [], array );
};


var push = arr.push;

var indexOf = arr.indexOf;

var class2type = {};

var toString = class2type.toString;

var hasOwn = class2type.hasOwnProperty;

var fnToString = hasOwn.toString;

var ObjectFunctionString = fnToString.call( Object );

var support = {};

var isFunction = function isFunction( obj ) {

		// Support: Chrome <=57, Firefox <=52
		// In some browsers, typeof returns "function" for HTML <object> elements
		// (i.e., `typeof document.createElement( "object" ) === "function"`).
		// We don't want to classify *any* DOM node as a function.
		// Support: QtWeb <=3.8.5, WebKit <=534.34, wkhtmltopdf tool <=0.12.5
		// Plus for old WebKit, typeof returns "function" for HTML collections
		// (e.g., `typeof document.getElementsByTagName("div") === "function"`). (gh-4756)
		return typeof obj === "function" && typeof obj.nodeType !== "number" &&
			typeof obj.item !== "function";
	};


var isWindow = function isWindow( obj ) {
		return obj != null && obj === obj.window;
	};


var document = window.document;



	var preservedScriptAttributes = {
		type: true,
		src: true,
		nonce: true,
		noModule: true
	};

	function DOMEval( code, node, doc ) {
		doc = doc || document;

		var i, val,
			script = doc.createElement( "script" );

		script.text = code;
		if ( node ) {
			for ( i in preservedScriptAttributes ) {

				// Support: Firefox 64+, Edge 18+
				// Some browsers don't support the "nonce" property on scripts.
				// On the other hand, just using `getAttribute` is not enough as
				// the `nonce` attribute is reset to an empty string whenever it
				// becomes browsing-context connected.
				// See https://github.com/whatwg/html/issues/2369
				// See https://html.spec.whatwg.org/#nonce-attributes
				// The `node.getAttribute` check was added for the sake of
				// `jQuery.globalEval` so that it can fake a nonce-containing node
				// via an object.
				val = node[ i ] || node.getAttribute && node.getAttribute( i );
				if ( val ) {
					script.setAttribute( i, val );
				}
			}
		}
		doc.head.appendChild( script ).parentNode.removeChild( script );
	}


function toType( obj ) {
	if ( obj == null ) {
		return obj + "";
	}

	// Support: Android <=2.3 only (functionish RegExp)
	return typeof obj === "object" || typeof obj === "function" ?
		class2type[ toString.call( obj ) ] || "object" :
		typeof obj;
}
/* global Symbol */
// Defining this global in .eslintrc.json would create a danger of using the global
// unguarded in another place, it seems safer to define global only for this module



var
	version = "3.6.0",

	// Define a local copy of jQuery
	jQuery = function( selector, context ) {

		// The jQuery object is actually just the init constructor 'enhanced'
		// Need init if jQuery is called (just allow error to be thrown if not included)
		return new jQuery.fn.init( selector, context );
	};

jQuery.fn = jQuery.prototype = {

	// The current version of jQuery being used
	jquery: version,

	constructor: jQuery,

	// The default length of a jQuery object is 0
	length: 0,

	toArray: function() {
		return slice.call( this );
	},

	// Get the Nth element in the matched element set OR
	// Get the whole matched element set as a clean array
	get: function( num ) {

		// Return all the elements in a clean array
		if ( num == null ) {
			return slice.call( this );
		}

		// Return just the one element from the set
		return num < 0 ? this[ num + this.length ] : this[ num ];
	},

	// Take an array of elements and push it onto the stack
	// (returning the new matched element set)
	pushStack: function( elems ) {

		// Build a new jQuery matched element set
		var ret = jQuery.merge( this.constructor(), elems );

		// Add the old object onto the stack (as a reference)
		ret.prevObject = this;

		// Return the newly-formed element set
		return ret;
	},

	// Execute a callback for every element in the matched set.
	each: function( callback ) {
		return jQuery.each( this, callback );
	},

	map: function( callback ) {
		return this.pushStack( jQuery.map( this, function( elem, i ) {
			return callback.call( elem, i, elem );
		} ) );
	},

	slice: function() {
		return this.pushStack( slice.apply( this, arguments ) );
	},

	first: function() {
		return this.eq( 0 );
	},

	last: function() {
		return this.eq( -1 );
	},

	even: function() {
		return this.pushStack( jQuery.grep( this, function( _elem, i ) {
			return ( i + 1 ) % 2;
		} ) );
	},

	odd: function() {
		return this.pushStack( jQuery.grep( this, function( _elem, i ) {
			return i % 2;
		} ) );
	},

	eq: function( i ) {
		var len = this.length,
			j = +i + ( i < 0 ? len : 0 );
		return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
	},

	end: function() {
		return this.prevObject || this.constructor();
	},

	// For internal use only.
	// Behaves like an Array's method, not like a jQuery method.
	push: push,
	sort: arr.sort,
	splice: arr.splice
};

jQuery.extend = jQuery.fn.extend = function() {
	var options, name, src, copy, copyIsArray, clone,
		target = arguments[ 0 ] || {},
		i = 1,
		length = arguments.length,
		deep = false;

	// Handle a deep copy situation
	if ( typeof target === "boolean" ) {
		deep = target;

		// Skip the boolean and the target
		target = arguments[ i ] || {};
		i++;
	}

	// Handle case when target is a string or something (possible in deep copy)
	if ( typeof target !== "object" && !isFunction( target ) ) {
		target = {};
	}

	// Extend jQuery itself if only one argument is passed
	if ( i === length ) {
		target = this;
		i--;
	}

	for ( ; i < length; i++ ) {

		// Only deal with non-null/undefined values
		if ( ( options = arguments[ i ] ) != null ) {

			// Extend the base object
			for ( name in options ) {
				copy = options[ name ];

				// Prevent Object.prototype pollution
				// Prevent never-ending loop
				if ( name === "__proto__" || target === copy ) {
					continue;
				}

				// Recurse if we're merging plain objects or arrays
				if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
					( copyIsArray = Array.isArray( copy ) ) ) ) {
					src = target[ name ];

					// Ensure proper type for the source value
					if ( copyIsArray && !Array.isArray( src ) ) {
						clone = [];
					} else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) {
						clone = {};
					} else {
						clone = src;
					}
					copyIsArray = false;

					// Never move original objects, clone them
					target[ name ] = jQuery.extend( deep, clone, copy );

				// Don't bring in undefined values
				} else if ( copy !== undefined ) {
					target[ name ] = copy;
				}
			}
		}
	}

	// Return the modified object
	return target;
};

jQuery.extend( {

	// Unique for each copy of jQuery on the page
	expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),

	// Assume jQuery is ready without the ready module
	isReady: true,

	error: function( msg ) {
		throw new Error( msg );
	},

	noop: function() {},

	isPlainObject: function( obj ) {
		var proto, Ctor;

		// Detect obvious negatives
		// Use toString instead of jQuery.type to catch host objects
		if ( !obj || toString.call( obj ) !== "[object Object]" ) {
			return false;
		}

		proto = getProto( obj );

		// Objects with no prototype (e.g., `Object.create( null )`) are plain
		if ( !proto ) {
			return true;
		}

		// Objects with prototype are plain iff they were constructed by a global Object function
		Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;
		return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
	},

	isEmptyObject: function( obj ) {
		var name;

		for ( name in obj ) {
			return false;
		}
		return true;
	},

	// Evaluates a script in a provided context; falls back to the global one
	// if not specified.
	globalEval: function( code, options, doc ) {
		DOMEval( code, { nonce: options && options.nonce }, doc );
	},

	each: function( obj, callback ) {
		var length, i = 0;

		if ( isArrayLike( obj ) ) {
			length = obj.length;
			for ( ; i < length; i++ ) {
				if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
					break;
				}
			}
		} else {
			for ( i in obj ) {
				if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
					break;
				}
			}
		}

		return obj;
	},

	// results is for internal usage only
	makeArray: function( arr, results ) {
		var ret = results || [];

		if ( arr != null ) {
			if ( isArrayLike( Object( arr ) ) ) {
				jQuery.merge( ret,
					typeof arr === "string" ?
						[ arr ] : arr
				);
			} else {
				push.call( ret, arr );
			}
		}

		return ret;
	},

	inArray: function( elem, arr, i ) {
		return arr == null ? -1 : indexOf.call( arr, elem, i );
	},

	// Support: Android <=4.0 only, PhantomJS 1 only
	// push.apply(_, arraylike) throws on ancient WebKit
	merge: function( first, second ) {
		var len = +second.length,
			j = 0,
			i = first.length;

		for ( ; j < len; j++ ) {
			first[ i++ ] = second[ j ];
		}

		first.length = i;

		return first;
	},

	grep: function( elems, callback, invert ) {
		var callbackInverse,
			matches = [],
			i = 0,
			length = elems.length,
			callbackExpect = !invert;

		// Go through the array, only saving the items
		// that pass the validator function
		for ( ; i < length; i++ ) {
			callbackInverse = !callback( elems[ i ], i );
			if ( callbackInverse !== callbackExpect ) {
				matches.push( elems[ i ] );
			}
		}

		return matches;
	},

	// arg is for internal usage only
	map: function( elems, callback, arg ) {
		var length, value,
			i = 0,
			ret = [];

		// Go through the array, translating each of the items to their new values
		if ( isArrayLike( elems ) ) {
			length = elems.length;
			for ( ; i < length; i++ ) {
				value = callback( elems[ i ], i, arg );

				if ( value != null ) {
					ret.push( value );
				}
			}

		// Go through every key on the object,
		} else {
			for ( i in elems ) {
				value = callback( elems[ i ], i, arg );

				if ( value != null ) {
					ret.push( value );
				}
			}
		}

		// Flatten any nested arrays
		return flat( ret );
	},

	// A global GUID counter for objects
	guid: 1,

	// jQuery.support is not used in Core but other projects attach their
	// properties to it so it needs to exist.
	support: support
} );

if ( typeof Symbol === "function" ) {
	jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ];
}

// Populate the class2type map
jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
	function( _i, name ) {
		class2type[ "[object " + name + "]" ] = name.toLowerCase();
	} );

function isArrayLike( obj ) {

	// Support: real iOS 8.2 only (not reproducible in simulator)
	// `in` check used to prevent JIT error (gh-2145)
	// hasOwn isn't used here due to false negatives
	// regarding Nodelist length in IE
	var length = !!obj && "length" in obj && obj.length,
		type = toType( obj );

	if ( isFunction( obj ) || isWindow( obj ) ) {
		return false;
	}

	return type === "array" || length === 0 ||
		typeof length === "number" && length > 0 && ( length - 1 ) in obj;
}
var Sizzle =
/*!
 * Sizzle CSS Selector Engine v2.3.6
 * https://sizzlejs.com/
 *
 * Copyright JS Foundation and other contributors
 * Released under the MIT license
 * https://js.foundation/
 *
 * Date: 2021-02-16
 */
( function( window ) {
var i,
	support,
	Expr,
	getText,
	isXML,
	tokenize,
	compile,
	select,
	outermostContext,
	sortInput,
	hasDuplicate,

	// Local document vars
	setDocument,
	document,
	docElem,
	documentIsHTML,
	rbuggyQSA,
	rbuggyMatches,
	matches,
	contains,

	// Instance-specific data
	expando = "sizzle" + 1 * new Date(),
	preferredDoc = window.document,
	dirruns = 0,
	done = 0,
	classCache = createCache(),
	tokenCache = createCache(),
	compilerCache = createCache(),
	nonnativeSelectorCache = createCache(),
	sortOrder = function( a, b ) {
		if ( a === b ) {
			hasDuplicate = true;
		}
		return 0;
	},

	// Instance methods
	hasOwn = ( {} ).hasOwnProperty,
	arr = [],
	pop = arr.pop,
	pushNative = arr.push,
	push = arr.push,
	slice = arr.slice,

	// Use a stripped-down indexOf as it's faster than native
	// https://jsperf.com/thor-indexof-vs-for/5
	indexOf = function( list, elem ) {
		var i = 0,
			len = list.length;
		for ( ; i < len; i++ ) {
			if ( list[ i ] === elem ) {
				return i;
			}
		}
		return -1;
	},

	booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" +
		"ismap|loop|multiple|open|readonly|required|scoped",

	// Regular expressions

	// http://www.w3.org/TR/css3-selectors/#whitespace
	whitespace = "[\\x20\\t\\r\\n\\f]",

	// https://www.w3.org/TR/css-syntax-3/#ident-token-diagram
	identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace +
		"?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+",

	// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
	attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +

		// Operator (capture 2)
		"*([*^$|!~]?=)" + whitespace +

		// "Attribute values must be CSS identifiers [capture 5]
		// or strings [capture 3 or capture 4]"
		"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" +
		whitespace + "*\\]",

	pseudos = ":(" + identifier + ")(?:\\((" +

		// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
		// 1. quoted (capture 3; capture 4 or capture 5)
		"('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +

		// 2. simple (capture 6)
		"((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +

		// 3. anything else (capture 2)
		".*" +
		")\\)|)",

	// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
	rwhitespace = new RegExp( whitespace + "+", "g" ),
	rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" +
		whitespace + "+$", "g" ),

	rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
	rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace +
		"*" ),
	rdescend = new RegExp( whitespace + "|>" ),

	rpseudo = new RegExp( pseudos ),
	ridentifier = new RegExp( "^" + identifier + "$" ),

	matchExpr = {
		"ID": new RegExp( "^#(" + identifier + ")" ),
		"CLASS": new RegExp( "^\\.(" + identifier + ")" ),
		"TAG": new RegExp( "^(" + identifier + "|[*])" ),
		"ATTR": new RegExp( "^" + attributes ),
		"PSEUDO": new RegExp( "^" + pseudos ),
		"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" +
			whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" +
			whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
		"bool": new RegExp( "^(?:" + booleans + ")$", "i" ),

		// For use in libraries implementing .is()
		// We use this for POS matching in `select`
		"needsContext": new RegExp( "^" + whitespace +
			"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace +
			"*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
	},

	rhtml = /HTML$/i,
	rinputs = /^(?:input|select|textarea|button)$/i,
	rheader = /^h\d$/i,

	rnative = /^[^{]+\{\s*\[native \w/,

	// Easily-parseable/retrievable ID or TAG or CLASS selectors
	rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,

	rsibling = /[+~]/,

	// CSS escapes
	// http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
	runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ),
	funescape = function( escape, nonHex ) {
		var high = "0x" + escape.slice( 1 ) - 0x10000;

		return nonHex ?

			// Strip the backslash prefix from a non-hex escape sequence
			nonHex :

			// Replace a hexadecimal escape sequence with the encoded Unicode code point
			// Support: IE <=11+
			// For values outside the Basic Multilingual Plane (BMP), manually construct a
			// surrogate pair
			high < 0 ?
				String.fromCharCode( high + 0x10000 ) :
				String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
	},

	// CSS string/identifier serialization
	// https://drafts.csswg.org/cssom/#common-serializing-idioms
	rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,
	fcssescape = function( ch, asCodePoint ) {
		if ( asCodePoint ) {

			// U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER
			if ( ch === "\0" ) {
				return "\uFFFD";
			}

			// Control characters and (dependent upon position) numbers get escaped as code points
			return ch.slice( 0, -1 ) + "\\" +
				ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " ";
		}

		// Other potentially-special ASCII characters get backslash-escaped
		return "\\" + ch;
	},

	// Used for iframes
	// See setDocument()
	// Removing the function wrapper causes a "Permission Denied"
	// error in IE
	unloadHandler = function() {
		setDocument();
	},

	inDisabledFieldset = addCombinator(
		function( elem ) {
			return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset";
		},
		{ dir: "parentNode", next: "legend" }
	);

// Optimize for push.apply( _, NodeList )
try {
	push.apply(
		( arr = slice.call( preferredDoc.childNodes ) ),
		preferredDoc.childNodes
	);

	// Support: Android<4.0
	// Detect silently failing push.apply
	// eslint-disable-next-line no-unused-expressions
	arr[ preferredDoc.childNodes.length ].nodeType;
} catch ( e ) {
	push = { apply: arr.length ?

		// Leverage slice if possible
		function( target, els ) {
			pushNative.apply( target, slice.call( els ) );
		} :

		// Support: IE<9
		// Otherwise append directly
		function( target, els ) {
			var j = target.length,
				i = 0;

			// Can't trust NodeList.length
			while ( ( target[ j++ ] = els[ i++ ] ) ) {}
			target.length = j - 1;
		}
	};
}

function Sizzle( selector, context, results, seed ) {
	var m, i, elem, nid, match, groups, newSelector,
		newContext = context && context.ownerDocument,

		// nodeType defaults to 9, since context defaults to document
		nodeType = context ? context.nodeType : 9;

	results = results || [];

	// Return early from calls with invalid selector or context
	if ( typeof selector !== "string" || !selector ||
		nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {

		return results;
	}

	// Try to shortcut find operations (as opposed to filters) in HTML documents
	if ( !seed ) {
		setDocument( context );
		context = context || document;

		if ( documentIsHTML ) {

			// If the selector is sufficiently simple, try using a "get*By*" DOM method
			// (excepting DocumentFragment context, where the methods don't exist)
			if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) {

				// ID selector
				if ( ( m = match[ 1 ] ) ) {

					// Document context
					if ( nodeType === 9 ) {
						if ( ( elem = context.getElementById( m ) ) ) {

							// Support: IE, Opera, Webkit
							// TODO: identify versions
							// getElementById can match elements by name instead of ID
							if ( elem.id === m ) {
								results.push( elem );
								return results;
							}
						} else {
							return results;
						}

					// Element context
					} else {

						// Support: IE, Opera, Webkit
						// TODO: identify versions
						// getElementById can match elements by name instead of ID
						if ( newContext && ( elem = newContext.getElementById( m ) ) &&
							contains( context, elem ) &&
							elem.id === m ) {

							results.push( elem );
							return results;
						}
					}

				// Type selector
				} else if ( match[ 2 ] ) {
					push.apply( results, context.getElementsByTagName( selector ) );
					return results;

				// Class selector
				} else if ( ( m = match[ 3 ] ) && support.getElementsByClassName &&
					context.getElementsByClassName ) {

					push.apply( results, context.getElementsByClassName( m ) );
					return results;
				}
			}

			// Take advantage of querySelectorAll
			if ( support.qsa &&
				!nonnativeSelectorCache[ selector + " " ] &&
				( !rbuggyQSA || !rbuggyQSA.test( selector ) ) &&

				// Support: IE 8 only
				// Exclude object elements
				( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) {

				newSelector = selector;
				newContext = context;

				// qSA considers elements outside a scoping root when evaluating child or
				// descendant combinators, which is not what we want.
				// In such cases, we work around the behavior by prefixing every selector in the
				// list with an ID selector referencing the scope context.
				// The technique has to be used as well when a leading combinator is used
				// as such selectors are not recognized by querySelectorAll.
				// Thanks to Andrew Dupont for this technique.
				if ( nodeType === 1 &&
					( rdescend.test( selector ) || rcombinators.test( selector ) ) ) {

					// Expand context for sibling selectors
					newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
						context;

					// We can use :scope instead of the ID hack if the browser
					// supports it & if we're not changing the context.
					if ( newContext !== context || !support.scope ) {

						// Capture the context ID, setting it first if necessary
						if ( ( nid = context.getAttribute( "id" ) ) ) {
							nid = nid.replace( rcssescape, fcssescape );
						} else {
							context.setAttribute( "id", ( nid = expando ) );
						}
					}

					// Prefix every selector in the list
					groups = tokenize( selector );
					i = groups.length;
					while ( i-- ) {
						groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " +
							toSelector( groups[ i ] );
					}
					newSelector = groups.join( "," );
				}

				try {
					push.apply( results,
						newContext.querySelectorAll( newSelector )
					);
					return results;
				} catch ( qsaError ) {
					nonnativeSelectorCache( selector, true );
				} finally {
					if ( nid === expando ) {
						context.removeAttribute( "id" );
					}
				}
			}
		}
	}

	// All others
	return select( selector.replace( rtrim, "$1" ), context, results, seed );
}

/**
 * Create key-value caches of limited size
 * @returns {function(string, object)} Returns the Object data after storing it on itself with
 *	property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
 *	deleting the oldest entry
 */
function createCache() {
	var keys = [];

	function cache( key, value ) {

		// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
		if ( keys.push( key + " " ) > Expr.cacheLength ) {

			// Only keep the most recent entries
			delete cache[ keys.shift() ];
		}
		return ( cache[ key + " " ] = value );
	}
	return cache;
}

/**
 * Mark a function for special use by Sizzle
 * @param {Function} fn The function to mark
 */
function markFunction( fn ) {
	fn[ expando ] = true;
	return fn;
}

/**
 * Support testing using an element
 * @param {Function} fn Passed the created element and returns a boolean result
 */
function assert( fn ) {
	var el = document.createElement( "fieldset" );

	try {
		return !!fn( el );
	} catch ( e ) {
		return false;
	} finally {

		// Remove from its parent by default
		if ( el.parentNode ) {
			el.parentNode.removeChild( el );
		}

		// release memory in IE
		el = null;
	}
}

/**
 * Adds the same handler for all of the specified attrs
 * @param {String} attrs Pipe-separated list of attributes
 * @param {Function} handler The method that will be applied
 */
function addHandle( attrs, handler ) {
	var arr = attrs.split( "|" ),
		i = arr.length;

	while ( i-- ) {
		Expr.attrHandle[ arr[ i ] ] = handler;
	}
}

/**
 * Checks document order of two siblings
 * @param {Element} a
 * @param {Element} b
 * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
 */
function siblingCheck( a, b ) {
	var cur = b && a,
		diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
			a.sourceIndex - b.sourceIndex;

	// Use IE sourceIndex if available on both nodes
	if ( diff ) {
		return diff;
	}

	// Check if b follows a
	if ( cur ) {
		while ( ( cur = cur.nextSibling ) ) {
			if ( cur === b ) {
				return -1;
			}
		}
	}

	return a ? 1 : -1;
}

/**
 * Returns a function to use in pseudos for input types
 * @param {String} type
 */
function createInputPseudo( type ) {
	return function( elem ) {
		var name = elem.nodeName.toLowerCase();
		return name === "input" && elem.type === type;
	};
}

/**
 * Returns a function to use in pseudos for buttons
 * @param {String} type
 */
function createButtonPseudo( type ) {
	return function( elem ) {
		var name = elem.nodeName.toLowerCase();
		return ( name === "input" || name === "button" ) && elem.type === type;
	};
}

/**
 * Returns a function to use in pseudos for :enabled/:disabled
 * @param {Boolean} disabled true for :disabled; false for :enabled
 */
function createDisabledPseudo( disabled ) {

	// Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable
	return function( elem ) {

		// Only certain elements can match :enabled or :disabled
		// https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled
		// https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
		if ( "form" in elem ) {

			// Check for inherited disabledness on relevant non-disabled elements:
			// * listed form-associated elements in a disabled fieldset
			//   https://html.spec.whatwg.org/multipage/forms.html#category-listed
			//   https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled
			// * option elements in a disabled optgroup
			//   https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled
			// All such elements have a "form" property.
			if ( elem.parentNode && elem.disabled === false ) {

				// Option elements defer to a parent optgroup if present
				if ( "label" in elem ) {
					if ( "label" in elem.parentNode ) {
						return elem.parentNode.disabled === disabled;
					} else {
						return elem.disabled === disabled;
					}
				}

				// Support: IE 6 - 11
				// Use the isDisabled shortcut property to check for disabled fieldset ancestors
				return elem.isDisabled === disabled ||

					// Where there is no isDisabled, check manually
					/* jshint -W018 */
					elem.isDisabled !== !disabled &&
					inDisabledFieldset( elem ) === disabled;
			}

			return elem.disabled === disabled;

		// Try to winnow out elements that can't be disabled before trusting the disabled property.
		// Some victims get caught in our net (label, legend, menu, track), but it shouldn't
		// even exist on them, let alone have a boolean value.
		} else if ( "label" in elem ) {
			return elem.disabled === disabled;
		}

		// Remaining elements are neither :enabled nor :disabled
		return false;
	};
}

/**
 * Returns a function to use in pseudos for positionals
 * @param {Function} fn
 */
function createPositionalPseudo( fn ) {
	return markFunction( function( argument ) {
		argument = +argument;
		return markFunction( function( seed, matches ) {
			var j,
				matchIndexes = fn( [], seed.length, argument ),
				i = matchIndexes.length;

			// Match elements found at the specified indexes
			while ( i-- ) {
				if ( seed[ ( j = matchIndexes[ i ] ) ] ) {
					seed[ j ] = !( matches[ j ] = seed[ j ] );
				}
			}
		} );
	} );
}

/**
 * Checks a node for validity as a Sizzle context
 * @param {Element|Object=} context
 * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
 */
function testContext( context ) {
	return context && typeof context.getElementsByTagName !== "undefined" && context;
}

// Expose support vars for convenience
support = Sizzle.support = {};

/**
 * Detects XML nodes
 * @param {Element|Object} elem An element or a document
 * @returns {Boolean} True iff elem is a non-HTML XML node
 */
isXML = Sizzle.isXML = function( elem ) {
	var namespace = elem && elem.namespaceURI,
		docElem = elem && ( elem.ownerDocument || elem ).documentElement;

	// Support: IE <=8
	// Assume HTML when documentElement doesn't yet exist, such as inside loading iframes
	// https://bugs.jquery.com/ticket/4833
	return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" );
};

/**
 * Sets document-related variables once based on the current document
 * @param {Element|Object} [doc] An element or document object to use to set the document
 * @returns {Object} Returns the current document
 */
setDocument = Sizzle.setDocument = function( node ) {
	var hasCompare, subWindow,
		doc = node ? node.ownerDocument || node : preferredDoc;

	// Return early if doc is invalid or already selected
	// Support: IE 11+, Edge 17 - 18+
	// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
	// two documents; shallow comparisons work.
	// eslint-disable-next-line eqeqeq
	if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) {
		return document;
	}

	// Update global variables
	document = doc;
	docElem = document.documentElement;
	documentIsHTML = !isXML( document );

	// Support: IE 9 - 11+, Edge 12 - 18+
	// Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936)
	// Support: IE 11+, Edge 17 - 18+
	// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
	// two documents; shallow comparisons work.
	// eslint-disable-next-line eqeqeq
	if ( preferredDoc != document &&
		( subWindow = document.defaultView ) && subWindow.top !== subWindow ) {

		// Support: IE 11, Edge
		if ( subWindow.addEventListener ) {
			subWindow.addEventListener( "unload", unloadHandler, false );

		// Support: IE 9 - 10 only
		} else if ( subWindow.attachEvent ) {
			subWindow.attachEvent( "onunload", unloadHandler );
		}
	}

	// Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only,
	// Safari 4 - 5 only, Opera <=11.6 - 12.x only
	// IE/Edge & older browsers don't support the :scope pseudo-class.
	// Support: Safari 6.0 only
	// Safari 6.0 supports :scope but it's an alias of :root there.
	support.scope = assert( function( el ) {
		docElem.appendChild( el ).appendChild( document.createElement( "div" ) );
		return typeof el.querySelectorAll !== "undefined" &&
			!el.querySelectorAll( ":scope fieldset div" ).length;
	} );

	/* Attributes
	---------------------------------------------------------------------- */

	// Support: IE<8
	// Verify that getAttribute really returns attributes and not properties
	// (excepting IE8 booleans)
	support.attributes = assert( function( el ) {
		el.className = "i";
		return !el.getAttribute( "className" );
	} );

	/* getElement(s)By*
	---------------------------------------------------------------------- */

	// Check if getElementsByTagName("*") returns only elements
	support.getElementsByTagName = assert( function( el ) {
		el.appendChild( document.createComment( "" ) );
		return !el.getElementsByTagName( "*" ).length;
	} );

	// Support: IE<9
	support.getElementsByClassName = rnative.test( document.getElementsByClassName );

	// Support: IE<10
	// Check if getElementById returns elements by name
	// The broken getElementById methods don't pick up programmatically-set names,
	// so use a roundabout getElementsByName test
	support.getById = assert( function( el ) {
		docElem.appendChild( el ).id = expando;
		return !document.getElementsByName || !document.getElementsByName( expando ).length;
	} );

	// ID filter and find
	if ( support.getById ) {
		Expr.filter[ "ID" ] = function( id ) {
			var attrId = id.replace( runescape, funescape );
			return function( elem ) {
				return elem.getAttribute( "id" ) === attrId;
			};
		};
		Expr.find[ "ID" ] = function( id, context ) {
			if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
				var elem = context.getElementById( id );
				return elem ? [ elem ] : [];
			}
		};
	} else {
		Expr.filter[ "ID" ] =  function( id ) {
			var attrId = id.replace( runescape, funescape );
			return function( elem ) {
				var node = typeof elem.getAttributeNode !== "undefined" &&
					elem.getAttributeNode( "id" );
				return node && node.value === attrId;
			};
		};

		// Support: IE 6 - 7 only
		// getElementById is not reliable as a find shortcut
		Expr.find[ "ID" ] = function( id, context ) {
			if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
				var node, i, elems,
					elem = context.getElementById( id );

				if ( elem ) {

					// Verify the id attribute
					node = elem.getAttributeNode( "id" );
					if ( node && node.value === id ) {
						return [ elem ];
					}

					// Fall back on getElementsByName
					elems = context.getElementsByName( id );
					i = 0;
					while ( ( elem = elems[ i++ ] ) ) {
						node = elem.getAttributeNode( "id" );
						if ( node && node.value === id ) {
							return [ elem ];
						}
					}
				}

				return [];
			}
		};
	}

	// Tag
	Expr.find[ "TAG" ] = support.getElementsByTagName ?
		function( tag, context ) {
			if ( typeof context.getElementsByTagName !== "undefined" ) {
				return context.getElementsByTagName( tag );

			// DocumentFragment nodes don't have gEBTN
			} else if ( support.qsa ) {
				return context.querySelectorAll( tag );
			}
		} :

		function( tag, context ) {
			var elem,
				tmp = [],
				i = 0,

				// By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
				results = context.getElementsByTagName( tag );

			// Filter out possible comments
			if ( tag === "*" ) {
				while ( ( elem = results[ i++ ] ) ) {
					if ( elem.nodeType === 1 ) {
						tmp.push( elem );
					}
				}

				return tmp;
			}
			return results;
		};

	// Class
	Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) {
		if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) {
			return context.getElementsByClassName( className );
		}
	};

	/* QSA/matchesSelector
	---------------------------------------------------------------------- */

	// QSA and matchesSelector support

	// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
	rbuggyMatches = [];

	// qSa(:focus) reports false when true (Chrome 21)
	// We allow this because of a bug in IE8/9 that throws an error
	// whenever `document.activeElement` is accessed on an iframe
	// So, we allow :focus to pass through QSA all the time to avoid the IE error
	// See https://bugs.jquery.com/ticket/13378
	rbuggyQSA = [];

	if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) {

		// Build QSA regex
		// Regex strategy adopted from Diego Perini
		assert( function( el ) {

			var input;

			// Select is set to empty string on purpose
			// This is to test IE's treatment of not explicitly
			// setting a boolean content attribute,
			// since its presence should be enough
			// https://bugs.jquery.com/ticket/12359
			docElem.appendChild( el ).innerHTML = "<a id='" + expando + "'></a>" +
				"<select id='" + expando + "-\r\\' msallowcapture=''>" +
				"<option selected=''></option></select>";

			// Support: IE8, Opera 11-12.16
			// Nothing should be selected when empty strings follow ^= or $= or *=
			// The test attribute must be unknown in Opera but "safe" for WinRT
			// https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
			if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) {
				rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
			}

			// Support: IE8
			// Boolean attributes and "value" are not treated correctly
			if ( !el.querySelectorAll( "[selected]" ).length ) {
				rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
			}

			// Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+
			if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
				rbuggyQSA.push( "~=" );
			}

			// Support: IE 11+, Edge 15 - 18+
			// IE 11/Edge don't find elements on a `[name='']` query in some cases.
			// Adding a temporary attribute to the document before the selection works
			// around the issue.
			// Interestingly, IE 10 & older don't seem to have the issue.
			input = document.createElement( "input" );
			input.setAttribute( "name", "" );
			el.appendChild( input );
			if ( !el.querySelectorAll( "[name='']" ).length ) {
				rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" +
					whitespace + "*(?:''|\"\")" );
			}

			// Webkit/Opera - :checked should return selected option elements
			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
			// IE8 throws error here and will not see later tests
			if ( !el.querySelectorAll( ":checked" ).length ) {
				rbuggyQSA.push( ":checked" );
			}

			// Support: Safari 8+, iOS 8+
			// https://bugs.webkit.org/show_bug.cgi?id=136851
			// In-page `selector#id sibling-combinator selector` fails
			if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) {
				rbuggyQSA.push( ".#.+[+~]" );
			}

			// Support: Firefox <=3.6 - 5 only
			// Old Firefox doesn't throw on a badly-escaped identifier.
			el.querySelectorAll( "\\\f" );
			rbuggyQSA.push( "[\\r\\n\\f]" );
		} );

		assert( function( el ) {
			el.innerHTML = "<a href='' disabled='disabled'></a>" +
				"<select disabled='disabled'><option/></select>";

			// Support: Windows 8 Native Apps
			// The type and name attributes are restricted during .innerHTML assignment
			var input = document.createElement( "input" );
			input.setAttribute( "type", "hidden" );
			el.appendChild( input ).setAttribute( "name", "D" );

			// Support: IE8
			// Enforce case-sensitivity of name attribute
			if ( el.querySelectorAll( "[name=d]" ).length ) {
				rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
			}

			// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
			// IE8 throws error here and will not see later tests
			if ( el.querySelectorAll( ":enabled" ).length !== 2 ) {
				rbuggyQSA.push( ":enabled", ":disabled" );
			}

			// Support: IE9-11+
			// IE's :disabled selector does not pick up the children of disabled fieldsets
			docElem.appendChild( el ).disabled = true;
			if ( el.querySelectorAll( ":disabled" ).length !== 2 ) {
				rbuggyQSA.push( ":enabled", ":disabled" );
			}

			// Support: Opera 10 - 11 only
			// Opera 10-11 does not throw on post-comma invalid pseudos
			el.querySelectorAll( "*,:x" );
			rbuggyQSA.push( ",.*:" );
		} );
	}

	if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches ||
		docElem.webkitMatchesSelector ||
		docElem.mozMatchesSelector ||
		docElem.oMatchesSelector ||
		docElem.msMatchesSelector ) ) ) ) {

		assert( function( el ) {

			// Check to see if it's possible to do matchesSelector
			// on a disconnected node (IE 9)
			support.disconnectedMatch = matches.call( el, "*" );

			// This should fail with an exception
			// Gecko does not error, returns false instead
			matches.call( el, "[s!='']:x" );
			rbuggyMatches.push( "!=", pseudos );
		} );
	}

	rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) );
	rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) );

	/* Contains
	---------------------------------------------------------------------- */
	hasCompare = rnative.test( docElem.compareDocumentPosition );

	// Element contains another
	// Purposefully self-exclusive
	// As in, an element does not contain itself
	contains = hasCompare || rnative.test( docElem.contains ) ?
		function( a, b ) {
			var adown = a.nodeType === 9 ? a.documentElement : a,
				bup = b && b.parentNode;
			return a === bup || !!( bup && bup.nodeType === 1 && (
				adown.contains ?
					adown.contains( bup ) :
					a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
			) );
		} :
		function( a, b ) {
			if ( b ) {
				while ( ( b = b.parentNode ) ) {
					if ( b === a ) {
						return true;
					}
				}
			}
			return false;
		};

	/* Sorting
	---------------------------------------------------------------------- */

	// Document order sorting
	sortOrder = hasCompare ?
	function( a, b ) {

		// Flag for duplicate removal
		if ( a === b ) {
			hasDuplicate = true;
			return 0;
		}

		// Sort on method existence if only one input has compareDocumentPosition
		var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
		if ( compare ) {
			return compare;
		}

		// Calculate position if both inputs belong to the same document
		// Support: IE 11+, Edge 17 - 18+
		// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
		// two documents; shallow comparisons work.
		// eslint-disable-next-line eqeqeq
		compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ?
			a.compareDocumentPosition( b ) :

			// Otherwise we know they are disconnected
			1;

		// Disconnected nodes
		if ( compare & 1 ||
			( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) {

			// Choose the first element that is related to our preferred document
			// Support: IE 11+, Edge 17 - 18+
			// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
			// two documents; shallow comparisons work.
			// eslint-disable-next-line eqeqeq
			if ( a == document || a.ownerDocument == preferredDoc &&
				contains( preferredDoc, a ) ) {
				return -1;
			}

			// Support: IE 11+, Edge 17 - 18+
			// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
			// two documents; shallow comparisons work.
			// eslint-disable-next-line eqeqeq
			if ( b == document || b.ownerDocument == preferredDoc &&
				contains( preferredDoc, b ) ) {
				return 1;
			}

			// Maintain original order
			return sortInput ?
				( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
				0;
		}

		return compare & 4 ? -1 : 1;
	} :
	function( a, b ) {

		// Exit early if the nodes are identical
		if ( a === b ) {
			hasDuplicate = true;
			return 0;
		}

		var cur,
			i = 0,
			aup = a.parentNode,
			bup = b.parentNode,
			ap = [ a ],
			bp = [ b ];

		// Parentless nodes are either documents or disconnected
		if ( !aup || !bup ) {

			// Support: IE 11+, Edge 17 - 18+
			// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
			// two documents; shallow comparisons work.
			/* eslint-disable eqeqeq */
			return a == document ? -1 :
				b == document ? 1 :
				/* eslint-enable eqeqeq */
				aup ? -1 :
				bup ? 1 :
				sortInput ?
				( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
				0;

		// If the nodes are siblings, we can do a quick check
		} else if ( aup === bup ) {
			return siblingCheck( a, b );
		}

		// Otherwise we need full lists of their ancestors for comparison
		cur = a;
		while ( ( cur = cur.parentNode ) ) {
			ap.unshift( cur );
		}
		cur = b;
		while ( ( cur = cur.parentNode ) ) {
			bp.unshift( cur );
		}

		// Walk down the tree looking for a discrepancy
		while ( ap[ i ] === bp[ i ] ) {
			i++;
		}

		return i ?

			// Do a sibling check if the nodes have a common ancestor
			siblingCheck( ap[ i ], bp[ i ] ) :

			// Otherwise nodes in our document sort first
			// Support: IE 11+, Edge 17 - 18+
			// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
			// two documents; shallow comparisons work.
			/* eslint-disable eqeqeq */
			ap[ i ] == preferredDoc ? -1 :
			bp[ i ] == preferredDoc ? 1 :
			/* eslint-enable eqeqeq */
			0;
	};

	return document;
};

Sizzle.matches = function( expr, elements ) {
	return Sizzle( expr, null, null, elements );
};

Sizzle.matchesSelector = function( elem, expr ) {
	setDocument( elem );

	if ( support.matchesSelector && documentIsHTML &&
		!nonnativeSelectorCache[ expr + " " ] &&
		( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
		( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {

		try {
			var ret = matches.call( elem, expr );

			// IE 9's matchesSelector returns false on disconnected nodes
			if ( ret || support.disconnectedMatch ||

				// As well, disconnected nodes are said to be in a document
				// fragment in IE 9
				elem.document && elem.document.nodeType !== 11 ) {
				return ret;
			}
		} catch ( e ) {
			nonnativeSelectorCache( expr, true );
		}
	}

	return Sizzle( expr, document, null, [ elem ] ).length > 0;
};

Sizzle.contains = function( context, elem ) {

	// Set document vars if needed
	// Support: IE 11+, Edge 17 - 18+
	// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
	// two documents; shallow comparisons work.
	// eslint-disable-next-line eqeqeq
	if ( ( context.ownerDocument || context ) != document ) {
		setDocument( context );
	}
	return contains( context, elem );
};

Sizzle.attr = function( elem, name ) {

	// Set document vars if needed
	// Support: IE 11+, Edge 17 - 18+
	// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
	// two documents; shallow comparisons work.
	// eslint-disable-next-line eqeqeq
	if ( ( elem.ownerDocument || elem ) != document ) {
		setDocument( elem );
	}

	var fn = Expr.attrHandle[ name.toLowerCase() ],

		// Don't get fooled by Object.prototype properties (jQuery #13807)
		val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
			fn( elem, name, !documentIsHTML ) :
			undefined;

	return val !== undefined ?
		val :
		support.attributes || !documentIsHTML ?
			elem.getAttribute( name ) :
			( val = elem.getAttributeNode( name ) ) && val.specified ?
				val.value :
				null;
};

Sizzle.escape = function( sel ) {
	return ( sel + "" ).replace( rcssescape, fcssescape );
};

Sizzle.error = function( msg ) {
	throw new Error( "Syntax error, unrecognized expression: " + msg );
};

/**
 * Document sorting and removing duplicates
 * @param {ArrayLike} results
 */
Sizzle.uniqueSort = function( results ) {
	var elem,
		duplicates = [],
		j = 0,
		i = 0;

	// Unless we *know* we can detect duplicates, assume their presence
	hasDuplicate = !support.detectDuplicates;
	sortInput = !support.sortStable && results.slice( 0 );
	results.sort( sortOrder );

	if ( hasDuplicate ) {
		while ( ( elem = results[ i++ ] ) ) {
			if ( elem === results[ i ] ) {
				j = duplicates.push( i );
			}
		}
		while ( j-- ) {
			results.splice( duplicates[ j ], 1 );
		}
	}

	// Clear input after sorting to release objects
	// See https://github.com/jquery/sizzle/pull/225
	sortInput = null;

	return results;
};

/**
 * Utility function for retrieving the text value of an array of DOM nodes
 * @param {Array|Element} elem
 */
getText = Sizzle.getText = function( elem ) {
	var node,
		ret = "",
		i = 0,
		nodeType = elem.nodeType;

	if ( !nodeType ) {

		// If no nodeType, this is expected to be an array
		while ( ( node = elem[ i++ ] ) ) {

			// Do not traverse comment nodes
			ret += getText( node );
		}
	} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {

		// Use textContent for elements
		// innerText usage removed for consistency of new lines (jQuery #11153)
		if ( typeof elem.textContent === "string" ) {
			return elem.textContent;
		} else {

			// Traverse its children
			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
				ret += getText( elem );
			}
		}
	} else if ( nodeType === 3 || nodeType === 4 ) {
		return elem.nodeValue;
	}

	// Do not include comment or processing instruction nodes

	return ret;
};

Expr = Sizzle.selectors = {

	// Can be adjusted by the user
	cacheLength: 50,

	createPseudo: markFunction,

	match: matchExpr,

	attrHandle: {},

	find: {},

	relative: {
		">": { dir: "parentNode", first: true },
		" ": { dir: "parentNode" },
		"+": { dir: "previousSibling", first: true },
		"~": { dir: "previousSibling" }
	},

	preFilter: {
		"ATTR": function( match ) {
			match[ 1 ] = match[ 1 ].replace( runescape, funescape );

			// Move the given value to match[3] whether quoted or unquoted
			match[ 3 ] = ( match[ 3 ] || match[ 4 ] ||
				match[ 5 ] || "" ).replace( runescape, funescape );

			if ( match[ 2 ] === "~=" ) {
				match[ 3 ] = " " + match[ 3 ] + " ";
			}

			return match.slice( 0, 4 );
		},

		"CHILD": function( match ) {

			/* matches from matchExpr["CHILD"]
				1 type (only|nth|...)
				2 what (child|of-type)
				3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
				4 xn-component of xn+y argument ([+-]?\d*n|)
				5 sign of xn-component
				6 x of xn-component
				7 sign of y-component
				8 y of y-component
			*/
			match[ 1 ] = match[ 1 ].toLowerCase();

			if ( match[ 1 ].slice( 0, 3 ) === "nth" ) {

				// nth-* requires argument
				if ( !match[ 3 ] ) {
					Sizzle.error( match[ 0 ] );
				}

				// numeric x and y parameters for Expr.filter.CHILD
				// remember that false/true cast respectively to 0/1
				match[ 4 ] = +( match[ 4 ] ?
					match[ 5 ] + ( match[ 6 ] || 1 ) :
					2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) );
				match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" );

				// other types prohibit arguments
			} else if ( match[ 3 ] ) {
				Sizzle.error( match[ 0 ] );
			}

			return match;
		},

		"PSEUDO": function( match ) {
			var excess,
				unquoted = !match[ 6 ] && match[ 2 ];

			if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) {
				return null;
			}

			// Accept quoted arguments as-is
			if ( match[ 3 ] ) {
				match[ 2 ] = match[ 4 ] || match[ 5 ] || "";

			// Strip excess characters from unquoted arguments
			} else if ( unquoted && rpseudo.test( unquoted ) &&

				// Get excess from tokenize (recursively)
				( excess = tokenize( unquoted, true ) ) &&

				// advance to the next closing parenthesis
				( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) {

				// excess is a negative index
				match[ 0 ] = match[ 0 ].slice( 0, excess );
				match[ 2 ] = unquoted.slice( 0, excess );
			}

			// Return only captures needed by the pseudo filter method (type and argument)
			return match.slice( 0, 3 );
		}
	},

	filter: {

		"TAG": function( nodeNameSelector ) {
			var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
			return nodeNameSelector === "*" ?
				function() {
					return true;
				} :
				function( elem ) {
					return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
				};
		},

		"CLASS": function( className ) {
			var pattern = classCache[ className + " " ];

			return pattern ||
				( pattern = new RegExp( "(^|" + whitespace +
					")" + className + "(" + whitespace + "|$)" ) ) && classCache(
						className, function( elem ) {
							return pattern.test(
								typeof elem.className === "string" && elem.className ||
								typeof elem.getAttribute !== "undefined" &&
									elem.getAttribute( "class" ) ||
								""
							);
				} );
		},

		"ATTR": function( name, operator, check ) {
			return function( elem ) {
				var result = Sizzle.attr( elem, name );

				if ( result == null ) {
					return operator === "!=";
				}
				if ( !operator ) {
					return true;
				}

				result += "";

				/* eslint-disable max-len */

				return operator === "=" ? result === check :
					operator === "!=" ? result !== check :
					operator === "^=" ? check && result.indexOf( check ) === 0 :
					operator === "*=" ? check && result.indexOf( check ) > -1 :
					operator === "$=" ? check && result.slice( -check.length ) === check :
					operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 :
					operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
					false;
				/* eslint-enable max-len */

			};
		},

		"CHILD": function( type, what, _argument, first, last ) {
			var simple = type.slice( 0, 3 ) !== "nth",
				forward = type.slice( -4 ) !== "last",
				ofType = what === "of-type";

			return first === 1 && last === 0 ?

				// Shortcut for :nth-*(n)
				function( elem ) {
					return !!elem.parentNode;
				} :

				function( elem, _context, xml ) {
					var cache, uniqueCache, outerCache, node, nodeIndex, start,
						dir = simple !== forward ? "nextSibling" : "previousSibling",
						parent = elem.parentNode,
						name = ofType && elem.nodeName.toLowerCase(),
						useCache = !xml && !ofType,
						diff = false;

					if ( parent ) {

						// :(first|last|only)-(child|of-type)
						if ( simple ) {
							while ( dir ) {
								node = elem;
								while ( ( node = node[ dir ] ) ) {
									if ( ofType ?
										node.nodeName.toLowerCase() === name :
										node.nodeType === 1 ) {

										return false;
									}
								}

								// Reverse direction for :only-* (if we haven't yet done so)
								start = dir = type === "only" && !start && "nextSibling";
							}
							return true;
						}

						start = [ forward ? parent.firstChild : parent.lastChild ];

						// non-xml :nth-child(...) stores cache data on `parent`
						if ( forward && useCache ) {

							// Seek `elem` from a previously-cached index

							// ...in a gzip-friendly way
							node = parent;
							outerCache = node[ expando ] || ( node[ expando ] = {} );

							// Support: IE <9 only
							// Defend against cloned attroperties (jQuery gh-1709)
							uniqueCache = outerCache[ node.uniqueID ] ||
								( outerCache[ node.uniqueID ] = {} );

							cache = uniqueCache[ type ] || [];
							nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
							diff = nodeIndex && cache[ 2 ];
							node = nodeIndex && parent.childNodes[ nodeIndex ];

							while ( ( node = ++nodeIndex && node && node[ dir ] ||

								// Fallback to seeking `elem` from the start
								( diff = nodeIndex = 0 ) || start.pop() ) ) {

								// When found, cache indexes on `parent` and break
								if ( node.nodeType === 1 && ++diff && node === elem ) {
									uniqueCache[ type ] = [ dirruns, nodeIndex, diff ];
									break;
								}
							}

						} else {

							// Use previously-cached element index if available
							if ( useCache ) {

								// ...in a gzip-friendly way
								node = elem;
								outerCache = node[ expando ] || ( node[ expando ] = {} );

								// Support: IE <9 only
								// Defend against cloned attroperties (jQuery gh-1709)
								uniqueCache = outerCache[ node.uniqueID ] ||
									( outerCache[ node.uniqueID ] = {} );

								cache = uniqueCache[ type ] || [];
								nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
								diff = nodeIndex;
							}

							// xml :nth-child(...)
							// or :nth-last-child(...) or :nth(-last)?-of-type(...)
							if ( diff === false ) {

								// Use the same loop as above to seek `elem` from the start
								while ( ( node = ++nodeIndex && node && node[ dir ] ||
									( diff = nodeIndex = 0 ) || start.pop() ) ) {

									if ( ( ofType ?
										node.nodeName.toLowerCase() === name :
										node.nodeType === 1 ) &&
										++diff ) {

										// Cache the index of each encountered element
										if ( useCache ) {
											outerCache = node[ expando ] ||
												( node[ expando ] = {} );

											// Support: IE <9 only
											// Defend against cloned attroperties (jQuery gh-1709)
											uniqueCache = outerCache[ node.uniqueID ] ||
												( outerCache[ node.uniqueID ] = {} );

											uniqueCache[ type ] = [ dirruns, diff ];
										}

										if ( node === elem ) {
											break;
										}
									}
								}
							}
						}

						// Incorporate the offset, then check against cycle size
						diff -= last;
						return diff === first || ( diff % first === 0 && diff / first >= 0 );
					}
				};
		},

		"PSEUDO": function( pseudo, argument ) {

			// pseudo-class names are case-insensitive
			// http://www.w3.org/TR/selectors/#pseudo-classes
			// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
			// Remember that setFilters inherits from pseudos
			var args,
				fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
					Sizzle.error( "unsupported pseudo: " + pseudo );

			// The user may use createPseudo to indicate that
			// arguments are needed to create the filter function
			// just as Sizzle does
			if ( fn[ expando ] ) {
				return fn( argument );
			}

			// But maintain support for old signatures
			if ( fn.length > 1 ) {
				args = [ pseudo, pseudo, "", argument ];
				return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
					markFunction( function( seed, matches ) {
						var idx,
							matched = fn( seed, argument ),
							i = matched.length;
						while ( i-- ) {
							idx = indexOf( seed, matched[ i ] );
							seed[ idx ] = !( matches[ idx ] = matched[ i ] );
						}
					} ) :
					function( elem ) {
						return fn( elem, 0, args );
					};
			}

			return fn;
		}
	},

	pseudos: {

		// Potentially complex pseudos
		"not": markFunction( function( selector ) {

			// Trim the selector passed to compile
			// to avoid treating leading and trailing
			// spaces as combinators
			var input = [],
				results = [],
				matcher = compile( selector.replace( rtrim, "$1" ) );

			return matcher[ expando ] ?
				markFunction( function( seed, matches, _context, xml ) {
					var elem,
						unmatched = matcher( seed, null, xml, [] ),
						i = seed.length;

					// Match elements unmatched by `matcher`
					while ( i-- ) {
						if ( ( elem = unmatched[ i ] ) ) {
							seed[ i ] = !( matches[ i ] = elem );
						}
					}
				} ) :
				function( elem, _context, xml ) {
					input[ 0 ] = elem;
					matcher( input, null, xml, results );

					// Don't keep the element (issue #299)
					input[ 0 ] = null;
					return !results.pop();
				};
		} ),

		"has": markFunction( function( selector ) {
			return function( elem ) {
				return Sizzle( selector, elem ).length > 0;
			};
		} ),

		"contains": markFunction( function( text ) {
			text = text.replace( runescape, funescape );
			return function( elem ) {
				return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1;
			};
		} ),

		// "Whether an element is represented by a :lang() selector
		// is based solely on the element's language value
		// being equal to the identifier C,
		// or beginning with the identifier C immediately followed by "-".
		// The matching of C against the element's language value is performed case-insensitively.
		// The identifier C does not have to be a valid language name."
		// http://www.w3.org/TR/selectors/#lang-pseudo
		"lang": markFunction( function( lang ) {

			// lang value must be a valid identifier
			if ( !ridentifier.test( lang || "" ) ) {
				Sizzle.error( "unsupported lang: " + lang );
			}
			lang = lang.replace( runescape, funescape ).toLowerCase();
			return function( elem ) {
				var elemLang;
				do {
					if ( ( elemLang = documentIsHTML ?
						elem.lang :
						elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) {

						elemLang = elemLang.toLowerCase();
						return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
					}
				} while ( ( elem = elem.parentNode ) && elem.nodeType === 1 );
				return false;
			};
		} ),

		// Miscellaneous
		"target": function( elem ) {
			var hash = window.location && window.location.hash;
			return hash && hash.slice( 1 ) === elem.id;
		},

		"root": function( elem ) {
			return elem === docElem;
		},

		"focus": function( elem ) {
			return elem === document.activeElement &&
				( !document.hasFocus || document.hasFocus() ) &&
				!!( elem.type || elem.href || ~elem.tabIndex );
		},

		// Boolean properties
		"enabled": createDisabledPseudo( false ),
		"disabled": createDisabledPseudo( true ),

		"checked": function( elem ) {

			// In CSS3, :checked should return both checked and selected elements
			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
			var nodeName = elem.nodeName.toLowerCase();
			return ( nodeName === "input" && !!elem.checked ) ||
				( nodeName === "option" && !!elem.selected );
		},

		"selected": function( elem ) {

			// Accessing this property makes selected-by-default
			// options in Safari work properly
			if ( elem.parentNode ) {
				// eslint-disable-next-line no-unused-expressions
				elem.parentNode.selectedIndex;
			}

			return elem.selected === true;
		},

		// Contents
		"empty": function( elem ) {

			// http://www.w3.org/TR/selectors/#empty-pseudo
			// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
			//   but not by others (comment: 8; processing instruction: 7; etc.)
			// nodeType < 6 works because attributes (2) do not appear as children
			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
				if ( elem.nodeType < 6 ) {
					return false;
				}
			}
			return true;
		},

		"parent": function( elem ) {
			return !Expr.pseudos[ "empty" ]( elem );
		},

		// Element/input types
		"header": function( elem ) {
			return rheader.test( elem.nodeName );
		},

		"input": function( elem ) {
			return rinputs.test( elem.nodeName );
		},

		"button": function( elem ) {
			var name = elem.nodeName.toLowerCase();
			return name === "input" && elem.type === "button" || name === "button";
		},

		"text": function( elem ) {
			var attr;
			return elem.nodeName.toLowerCase() === "input" &&
				elem.type === "text" &&

				// Support: IE<8
				// New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
				( ( attr = elem.getAttribute( "type" ) ) == null ||
					attr.toLowerCase() === "text" );
		},

		// Position-in-collection
		"first": createPositionalPseudo( function() {
			return [ 0 ];
		} ),

		"last": createPositionalPseudo( function( _matchIndexes, length ) {
			return [ length - 1 ];
		} ),

		"eq": createPositionalPseudo( function( _matchIndexes, length, argument ) {
			return [ argument < 0 ? argument + length : argument ];
		} ),

		"even": createPositionalPseudo( function( matchIndexes, length ) {
			var i = 0;
			for ( ; i < length; i += 2 ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		} ),

		"odd": createPositionalPseudo( function( matchIndexes, length ) {
			var i = 1;
			for ( ; i < length; i += 2 ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		} ),

		"lt": createPositionalPseudo( function( matchIndexes, length, argument ) {
			var i = argument < 0 ?
				argument + length :
				argument > length ?
					length :
					argument;
			for ( ; --i >= 0; ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		} ),

		"gt": createPositionalPseudo( function( matchIndexes, length, argument ) {
			var i = argument < 0 ? argument + length : argument;
			for ( ; ++i < length; ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		} )
	}
};

Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ];

// Add button/input type pseudos
for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
	Expr.pseudos[ i ] = createInputPseudo( i );
}
for ( i in { submit: true, reset: true } ) {
	Expr.pseudos[ i ] = createButtonPseudo( i );
}

// Easy API for creating new setFilters
function setFilters() {}
setFilters.prototype = Expr.filters = Expr.pseudos;
Expr.setFilters = new setFilters();

tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
	var matched, match, tokens, type,
		soFar, groups, preFilters,
		cached = tokenCache[ selector + " " ];

	if ( cached ) {
		return parseOnly ? 0 : cached.slice( 0 );
	}

	soFar = selector;
	groups = [];
	preFilters = Expr.preFilter;

	while ( soFar ) {

		// Comma and first run
		if ( !matched || ( match = rcomma.exec( soFar ) ) ) {
			if ( match ) {

				// Don't consume trailing commas as valid
				soFar = soFar.slice( match[ 0 ].length ) || soFar;
			}
			groups.push( ( tokens = [] ) );
		}

		matched = false;

		// Combinators
		if ( ( match = rcombinators.exec( soFar ) ) ) {
			matched = match.shift();
			tokens.push( {
				value: matched,

				// Cast descendant combinators to space
				type: match[ 0 ].replace( rtrim, " " )
			} );
			soFar = soFar.slice( matched.length );
		}

		// Filters
		for ( type in Expr.filter ) {
			if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] ||
				( match = preFilters[ type ]( match ) ) ) ) {
				matched = match.shift();
				tokens.push( {
					value: matched,
					type: type,
					matches: match
				} );
				soFar = soFar.slice( matched.length );
			}
		}

		if ( !matched ) {
			break;
		}
	}

	// Return the length of the invalid excess
	// if we're just parsing
	// Otherwise, throw an error or return tokens
	return parseOnly ?
		soFar.length :
		soFar ?
			Sizzle.error( selector ) :

			// Cache the tokens
			tokenCache( selector, groups ).slice( 0 );
};

function toSelector( tokens ) {
	var i = 0,
		len = tokens.length,
		selector = "";
	for ( ; i < len; i++ ) {
		selector += tokens[ i ].value;
	}
	return selector;
}

function addCombinator( matcher, combinator, base ) {
	var dir = combinator.dir,
		skip = combinator.next,
		key = skip || dir,
		checkNonElements = base && key === "parentNode",
		doneName = done++;

	return combinator.first ?

		// Check against closest ancestor/preceding element
		function( elem, context, xml ) {
			while ( ( elem = elem[ dir ] ) ) {
				if ( elem.nodeType === 1 || checkNonElements ) {
					return matcher( elem, context, xml );
				}
			}
			return false;
		} :

		// Check against all ancestor/preceding elements
		function( elem, context, xml ) {
			var oldCache, uniqueCache, outerCache,
				newCache = [ dirruns, doneName ];

			// We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching
			if ( xml ) {
				while ( ( elem = elem[ dir ] ) ) {
					if ( elem.nodeType === 1 || checkNonElements ) {
						if ( matcher( elem, context, xml ) ) {
							return true;
						}
					}
				}
			} else {
				while ( ( elem = elem[ dir ] ) ) {
					if ( elem.nodeType === 1 || checkNonElements ) {
						outerCache = elem[ expando ] || ( elem[ expando ] = {} );

						// Support: IE <9 only
						// Defend against cloned attroperties (jQuery gh-1709)
						uniqueCache = outerCache[ elem.uniqueID ] ||
							( outerCache[ elem.uniqueID ] = {} );

						if ( skip && skip === elem.nodeName.toLowerCase() ) {
							elem = elem[ dir ] || elem;
						} else if ( ( oldCache = uniqueCache[ key ] ) &&
							oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {

							// Assign to newCache so results back-propagate to previous elements
							return ( newCache[ 2 ] = oldCache[ 2 ] );
						} else {

							// Reuse newcache so results back-propagate to previous elements
							uniqueCache[ key ] = newCache;

							// A match means we're done; a fail means we have to keep checking
							if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) {
								return true;
							}
						}
					}
				}
			}
			return false;
		};
}

function elementMatcher( matchers ) {
	return matchers.length > 1 ?
		function( elem, context, xml ) {
			var i = matchers.length;
			while ( i-- ) {
				if ( !matchers[ i ]( elem, context, xml ) ) {
					return false;
				}
			}
			return true;
		} :
		matchers[ 0 ];
}

function multipleContexts( selector, contexts, results ) {
	var i = 0,
		len = contexts.length;
	for ( ; i < len; i++ ) {
		Sizzle( selector, contexts[ i ], results );
	}
	return results;
}

function condense( unmatched, map, filter, context, xml ) {
	var elem,
		newUnmatched = [],
		i = 0,
		len = unmatched.length,
		mapped = map != null;

	for ( ; i < len; i++ ) {
		if ( ( elem = unmatched[ i ] ) ) {
			if ( !filter || filter( elem, context, xml ) ) {
				newUnmatched.push( elem );
				if ( mapped ) {
					map.push( i );
				}
			}
		}
	}

	return newUnmatched;
}

function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
	if ( postFilter && !postFilter[ expando ] ) {
		postFilter = setMatcher( postFilter );
	}
	if ( postFinder && !postFinder[ expando ] ) {
		postFinder = setMatcher( postFinder, postSelector );
	}
	return markFunction( function( seed, results, context, xml ) {
		var temp, i, elem,
			preMap = [],
			postMap = [],
			preexisting = results.length,

			// Get initial elements from seed or context
			elems = seed || multipleContexts(
				selector || "*",
				context.nodeType ? [ context ] : context,
				[]
			),

			// Prefilter to get matcher input, preserving a map for seed-results synchronization
			matcherIn = preFilter && ( seed || !selector ) ?
				condense( elems, preMap, preFilter, context, xml ) :
				elems,

			matcherOut = matcher ?

				// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
				postFinder || ( seed ? preFilter : preexisting || postFilter ) ?

					// ...intermediate processing is necessary
					[] :

					// ...otherwise use results directly
					results :
				matcherIn;

		// Find primary matches
		if ( matcher ) {
			matcher( matcherIn, matcherOut, context, xml );
		}

		// Apply postFilter
		if ( postFilter ) {
			temp = condense( matcherOut, postMap );
			postFilter( temp, [], context, xml );

			// Un-match failing elements by moving them back to matcherIn
			i = temp.length;
			while ( i-- ) {
				if ( ( elem = temp[ i ] ) ) {
					matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem );
				}
			}
		}

		if ( seed ) {
			if ( postFinder || preFilter ) {
				if ( postFinder ) {

					// Get the final matcherOut by condensing this intermediate into postFinder contexts
					temp = [];
					i = matcherOut.length;
					while ( i-- ) {
						if ( ( elem = matcherOut[ i ] ) ) {

							// Restore matcherIn since elem is not yet a final match
							temp.push( ( matcherIn[ i ] = elem ) );
						}
					}
					postFinder( null, ( matcherOut = [] ), temp, xml );
				}

				// Move matched elements from seed to results to keep them synchronized
				i = matcherOut.length;
				while ( i-- ) {
					if ( ( elem = matcherOut[ i ] ) &&
						( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) {

						seed[ temp ] = !( results[ temp ] = elem );
					}
				}
			}

		// Add elements to results, through postFinder if defined
		} else {
			matcherOut = condense(
				matcherOut === results ?
					matcherOut.splice( preexisting, matcherOut.length ) :
					matcherOut
			);
			if ( postFinder ) {
				postFinder( null, results, matcherOut, xml );
			} else {
				push.apply( results, matcherOut );
			}
		}
	} );
}

function matcherFromTokens( tokens ) {
	var checkContext, matcher, j,
		len = tokens.length,
		leadingRelative = Expr.relative[ tokens[ 0 ].type ],
		implicitRelative = leadingRelative || Expr.relative[ " " ],
		i = leadingRelative ? 1 : 0,

		// The foundational matcher ensures that elements are reachable from top-level context(s)
		matchContext = addCombinator( function( elem ) {
			return elem === checkContext;
		}, implicitRelative, true ),
		matchAnyContext = addCombinator( function( elem ) {
			return indexOf( checkContext, elem ) > -1;
		}, implicitRelative, true ),
		matchers = [ function( elem, context, xml ) {
			var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
				( checkContext = context ).nodeType ?
					matchContext( elem, context, xml ) :
					matchAnyContext( elem, context, xml ) );

			// Avoid hanging onto element (issue #299)
			checkContext = null;
			return ret;
		} ];

	for ( ; i < len; i++ ) {
		if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) {
			matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ];
		} else {
			matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches );

			// Return special upon seeing a positional matcher
			if ( matcher[ expando ] ) {

				// Find the next relative operator (if any) for proper handling
				j = ++i;
				for ( ; j < len; j++ ) {
					if ( Expr.relative[ tokens[ j ].type ] ) {
						break;
					}
				}
				return setMatcher(
					i > 1 && elementMatcher( matchers ),
					i > 1 && toSelector(

					// If the preceding token was a descendant combinator, insert an implicit any-element `*`
					tokens
						.slice( 0, i - 1 )
						.concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } )
					).replace( rtrim, "$1" ),
					matcher,
					i < j && matcherFromTokens( tokens.slice( i, j ) ),
					j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ),
					j < len && toSelector( tokens )
				);
			}
			matchers.push( matcher );
		}
	}

	return elementMatcher( matchers );
}

function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
	var bySet = setMatchers.length > 0,
		byElement = elementMatchers.length > 0,
		superMatcher = function( seed, context, xml, results, outermost ) {
			var elem, j, matcher,
				matchedCount = 0,
				i = "0",
				unmatched = seed && [],
				setMatched = [],
				contextBackup = outermostContext,

				// We must always have either seed elements or outermost context
				elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ),

				// Use integer dirruns iff this is the outermost matcher
				dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ),
				len = elems.length;

			if ( outermost ) {

				// Support: IE 11+, Edge 17 - 18+
				// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
				// two documents; shallow comparisons work.
				// eslint-disable-next-line eqeqeq
				outermostContext = context == document || context || outermost;
			}

			// Add elements passing elementMatchers directly to results
			// Support: IE<9, Safari
			// Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
			for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) {
				if ( byElement && elem ) {
					j = 0;

					// Support: IE 11+, Edge 17 - 18+
					// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
					// two documents; shallow comparisons work.
					// eslint-disable-next-line eqeqeq
					if ( !context && elem.ownerDocument != document ) {
						setDocument( elem );
						xml = !documentIsHTML;
					}
					while ( ( matcher = elementMatchers[ j++ ] ) ) {
						if ( matcher( elem, context || document, xml ) ) {
							results.push( elem );
							break;
						}
					}
					if ( outermost ) {
						dirruns = dirrunsUnique;
					}
				}

				// Track unmatched elements for set filters
				if ( bySet ) {

					// They will have gone through all possible matchers
					if ( ( elem = !matcher && elem ) ) {
						matchedCount--;
					}

					// Lengthen the array for every element, matched or not
					if ( seed ) {
						unmatched.push( elem );
					}
				}
			}

			// `i` is now the count of elements visited above, and adding it to `matchedCount`
			// makes the latter nonnegative.
			matchedCount += i;

			// Apply set filters to unmatched elements
			// NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`
			// equals `i`), unless we didn't visit _any_ elements in the above loop because we have
			// no element matchers and no seed.
			// Incrementing an initially-string "0" `i` allows `i` to remain a string only in that
			// case, which will result in a "00" `matchedCount` that differs from `i` but is also
			// numerically zero.
			if ( bySet && i !== matchedCount ) {
				j = 0;
				while ( ( matcher = setMatchers[ j++ ] ) ) {
					matcher( unmatched, setMatched, context, xml );
				}

				if ( seed ) {

					// Reintegrate element matches to eliminate the need for sorting
					if ( matchedCount > 0 ) {
						while ( i-- ) {
							if ( !( unmatched[ i ] || setMatched[ i ] ) ) {
								setMatched[ i ] = pop.call( results );
							}
						}
					}

					// Discard index placeholder values to get only actual matches
					setMatched = condense( setMatched );
				}

				// Add matches to results
				push.apply( results, setMatched );

				// Seedless set matches succeeding multiple successful matchers stipulate sorting
				if ( outermost && !seed && setMatched.length > 0 &&
					( matchedCount + setMatchers.length ) > 1 ) {

					Sizzle.uniqueSort( results );
				}
			}

			// Override manipulation of globals by nested matchers
			if ( outermost ) {
				dirruns = dirrunsUnique;
				outermostContext = contextBackup;
			}

			return unmatched;
		};

	return bySet ?
		markFunction( superMatcher ) :
		superMatcher;
}

compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
	var i,
		setMatchers = [],
		elementMatchers = [],
		cached = compilerCache[ selector + " " ];

	if ( !cached ) {

		// Generate a function of recursive functions that can be used to check each element
		if ( !match ) {
			match = tokenize( selector );
		}
		i = match.length;
		while ( i-- ) {
			cached = matcherFromTokens( match[ i ] );
			if ( cached[ expando ] ) {
				setMatchers.push( cached );
			} else {
				elementMatchers.push( cached );
			}
		}

		// Cache the compiled function
		cached = compilerCache(
			selector,
			matcherFromGroupMatchers( elementMatchers, setMatchers )
		);

		// Save selector and tokenization
		cached.selector = selector;
	}
	return cached;
};

/**
 * A low-level selection function that works with Sizzle's compiled
 *  selector functions
 * @param {String|Function} selector A selector or a pre-compiled
 *  selector function built with Sizzle.compile
 * @param {Element} context
 * @param {Array} [results]
 * @param {Array} [seed] A set of elements to match against
 */
select = Sizzle.select = function( selector, context, results, seed ) {
	var i, tokens, token, type, find,
		compiled = typeof selector === "function" && selector,
		match = !seed && tokenize( ( selector = compiled.selector || selector ) );

	results = results || [];

	// Try to minimize operations if there is only one selector in the list and no seed
	// (the latter of which guarantees us context)
	if ( match.length === 1 ) {

		// Reduce context if the leading compound selector is an ID
		tokens = match[ 0 ] = match[ 0 ].slice( 0 );
		if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" &&
			context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) {

			context = ( Expr.find[ "ID" ]( token.matches[ 0 ]
				.replace( runescape, funescape ), context ) || [] )[ 0 ];
			if ( !context ) {
				return results;

			// Precompiled matchers will still verify ancestry, so step up a level
			} else if ( compiled ) {
				context = context.parentNode;
			}

			selector = selector.slice( tokens.shift().value.length );
		}

		// Fetch a seed set for right-to-left matching
		i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length;
		while ( i-- ) {
			token = tokens[ i ];

			// Abort if we hit a combinator
			if ( Expr.relative[ ( type = token.type ) ] ) {
				break;
			}
			if ( ( find = Expr.find[ type ] ) ) {

				// Search, expanding context for leading sibling combinators
				if ( ( seed = find(
					token.matches[ 0 ].replace( runescape, funescape ),
					rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) ||
						context
				) ) ) {

					// If seed is empty or no tokens remain, we can return early
					tokens.splice( i, 1 );
					selector = seed.length && toSelector( tokens );
					if ( !selector ) {
						push.apply( results, seed );
						return results;
					}

					break;
				}
			}
		}
	}

	// Compile and execute a filtering function if one is not provided
	// Provide `match` to avoid retokenization if we modified the selector above
	( compiled || compile( selector, match ) )(
		seed,
		context,
		!documentIsHTML,
		results,
		!context || rsibling.test( selector ) && testContext( context.parentNode ) || context
	);
	return results;
};

// One-time assignments

// Sort stability
support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando;

// Support: Chrome 14-35+
// Always assume duplicates if they aren't passed to the comparison function
support.detectDuplicates = !!hasDuplicate;

// Initialize against the default document
setDocument();

// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
// Detached nodes confoundingly follow *each other*
support.sortDetached = assert( function( el ) {

	// Should return 1, but returns 4 (following)
	return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1;
} );

// Support: IE<8
// Prevent attribute/property "interpolation"
// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
if ( !assert( function( el ) {
	el.innerHTML = "<a href='#'></a>";
	return el.firstChild.getAttribute( "href" ) === "#";
} ) ) {
	addHandle( "type|href|height|width", function( elem, name, isXML ) {
		if ( !isXML ) {
			return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
		}
	} );
}

// Support: IE<9
// Use defaultValue in place of getAttribute("value")
if ( !support.attributes || !assert( function( el ) {
	el.innerHTML = "<input/>";
	el.firstChild.setAttribute( "value", "" );
	return el.firstChild.getAttribute( "value" ) === "";
} ) ) {
	addHandle( "value", function( elem, _name, isXML ) {
		if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
			return elem.defaultValue;
		}
	} );
}

// Support: IE<9
// Use getAttributeNode to fetch booleans when getAttribute lies
if ( !assert( function( el ) {
	return el.getAttribute( "disabled" ) == null;
} ) ) {
	addHandle( booleans, function( elem, name, isXML ) {
		var val;
		if ( !isXML ) {
			return elem[ name ] === true ? name.toLowerCase() :
				( val = elem.getAttributeNode( name ) ) && val.specified ?
					val.value :
					null;
		}
	} );
}

return Sizzle;

} )( window );



jQuery.find = Sizzle;
jQuery.expr = Sizzle.selectors;

// Deprecated
jQuery.expr[ ":" ] = jQuery.expr.pseudos;
jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort;
jQuery.text = Sizzle.getText;
jQuery.isXMLDoc = Sizzle.isXML;
jQuery.contains = Sizzle.contains;
jQuery.escapeSelector = Sizzle.escape;




var dir = function( elem, dir, until ) {
	var matched = [],
		truncate = until !== undefined;

	while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) {
		if ( elem.nodeType === 1 ) {
			if ( truncate && jQuery( elem ).is( until ) ) {
				break;
			}
			matched.push( elem );
		}
	}
	return matched;
};


var siblings = function( n, elem ) {
	var matched = [];

	for ( ; n; n = n.nextSibling ) {
		if ( n.nodeType === 1 && n !== elem ) {
			matched.push( n );
		}
	}

	return matched;
};


var rneedsContext = jQuery.expr.match.needsContext;



function nodeName( elem, name ) {

	return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();

}
var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i );



// Implement the identical functionality for filter and not
function winnow( elements, qualifier, not ) {
	if ( isFunction( qualifier ) ) {
		return jQuery.grep( elements, function( elem, i ) {
			return !!qualifier.call( elem, i, elem ) !== not;
		} );
	}

	// Single element
	if ( qualifier.nodeType ) {
		return jQuery.grep( elements, function( elem ) {
			return ( elem === qualifier ) !== not;
		} );
	}

	// Arraylike of elements (jQuery, arguments, Array)
	if ( typeof qualifier !== "string" ) {
		return jQuery.grep( elements, function( elem ) {
			return ( indexOf.call( qualifier, elem ) > -1 ) !== not;
		} );
	}

	// Filtered directly for both simple and complex selectors
	return jQuery.filter( qualifier, elements, not );
}

jQuery.filter = function( expr, elems, not ) {
	var elem = elems[ 0 ];

	if ( not ) {
		expr = ":not(" + expr + ")";
	}

	if ( elems.length === 1 && elem.nodeType === 1 ) {
		return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [];
	}

	return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
		return elem.nodeType === 1;
	} ) );
};

jQuery.fn.extend( {
	find: function( selector ) {
		var i, ret,
			len = this.length,
			self = this;

		if ( typeof selector !== "string" ) {
			return this.pushStack( jQuery( selector ).filter( function() {
				for ( i = 0; i < len; i++ ) {
					if ( jQuery.contains( self[ i ], this ) ) {
						return true;
					}
				}
			} ) );
		}

		ret = this.pushStack( [] );

		for ( i = 0; i < len; i++ ) {
			jQuery.find( selector, self[ i ], ret );
		}

		return len > 1 ? jQuery.uniqueSort( ret ) : ret;
	},
	filter: function( selector ) {
		return this.pushStack( winnow( this, selector || [], false ) );
	},
	not: function( selector ) {
		return this.pushStack( winnow( this, selector || [], true ) );
	},
	is: function( selector ) {
		return !!winnow(
			this,

			// If this is a positional/relative selector, check membership in the returned set
			// so $("p:first").is("p:last") won't return true for a doc with two "p".
			typeof selector === "string" && rneedsContext.test( selector ) ?
				jQuery( selector ) :
				selector || [],
			false
		).length;
	}
} );


// Initialize a jQuery object


// A central reference to the root jQuery(document)
var rootjQuery,

	// A simple way to check for HTML strings
	// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
	// Strict HTML recognition (#11290: must start with <)
	// Shortcut simple #id case for speed
	rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,

	init = jQuery.fn.init = function( selector, context, root ) {
		var match, elem;

		// HANDLE: $(""), $(null), $(undefined), $(false)
		if ( !selector ) {
			return this;
		}

		// Method init() accepts an alternate rootjQuery
		// so migrate can support jQuery.sub (gh-2101)
		root = root || rootjQuery;

		// Handle HTML strings
		if ( typeof selector === "string" ) {
			if ( selector[ 0 ] === "<" &&
				selector[ selector.length - 1 ] === ">" &&
				selector.length >= 3 ) {

				// Assume that strings that start and end with <> are HTML and skip the regex check
				match = [ null, selector, null ];

			} else {
				match = rquickExpr.exec( selector );
			}

			// Match html or make sure no context is specified for #id
			if ( match && ( match[ 1 ] || !context ) ) {

				// HANDLE: $(html) -> $(array)
				if ( match[ 1 ] ) {
					context = context instanceof jQuery ? context[ 0 ] : context;

					// Option to run scripts is true for back-compat
					// Intentionally let the error be thrown if parseHTML is not present
					jQuery.merge( this, jQuery.parseHTML(
						match[ 1 ],
						context && context.nodeType ? context.ownerDocument || context : document,
						true
					) );

					// HANDLE: $(html, props)
					if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
						for ( match in context ) {

							// Properties of context are called as methods if possible
							if ( isFunction( this[ match ] ) ) {
								this[ match ]( context[ match ] );

							// ...and otherwise set as attributes
							} else {
								this.attr( match, context[ match ] );
							}
						}
					}

					return this;

				// HANDLE: $(#id)
				} else {
					elem = document.getElementById( match[ 2 ] );

					if ( elem ) {

						// Inject the element directly into the jQuery object
						this[ 0 ] = elem;
						this.length = 1;
					}
					return this;
				}

			// HANDLE: $(expr, $(...))
			} else if ( !context || context.jquery ) {
				return ( context || root ).find( selector );

			// HANDLE: $(expr, context)
			// (which is just equivalent to: $(context).find(expr)
			} else {
				return this.constructor( context ).find( selector );
			}

		// HANDLE: $(DOMElement)
		} else if ( selector.nodeType ) {
			this[ 0 ] = selector;
			this.length = 1;
			return this;

		// HANDLE: $(function)
		// Shortcut for document ready
		} else if ( isFunction( selector ) ) {
			return root.ready !== undefined ?
				root.ready( selector ) :

				// Execute immediately if ready is not present
				selector( jQuery );
		}

		return jQuery.makeArray( selector, this );
	};

// Give the init function the jQuery prototype for later instantiation
init.prototype = jQuery.fn;

// Initialize central reference
rootjQuery = jQuery( document );


var rparentsprev = /^(?:parents|prev(?:Until|All))/,

	// Methods guaranteed to produce a unique set when starting from a unique set
	guaranteedUnique = {
		children: true,
		contents: true,
		next: true,
		prev: true
	};

jQuery.fn.extend( {
	has: function( target ) {
		var targets = jQuery( target, this ),
			l = targets.length;

		return this.filter( function() {
			var i = 0;
			for ( ; i < l; i++ ) {
				if ( jQuery.contains( this, targets[ i ] ) ) {
					return true;
				}
			}
		} );
	},

	closest: function( selectors, context ) {
		var cur,
			i = 0,
			l = this.length,
			matched = [],
			targets = typeof selectors !== "string" && jQuery( selectors );

		// Positional selectors never match, since there's no _selection_ context
		if ( !rneedsContext.test( selectors ) ) {
			for ( ; i < l; i++ ) {
				for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) {

					// Always skip document fragments
					if ( cur.nodeType < 11 && ( targets ?
						targets.index( cur ) > -1 :

						// Don't pass non-elements to Sizzle
						cur.nodeType === 1 &&
							jQuery.find.matchesSelector( cur, selectors ) ) ) {

						matched.push( cur );
						break;
					}
				}
			}
		}

		return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched );
	},

	// Determine the position of an element within the set
	index: function( elem ) {

		// No argument, return index in parent
		if ( !elem ) {
			return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
		}

		// Index in selector
		if ( typeof elem === "string" ) {
			return indexOf.call( jQuery( elem ), this[ 0 ] );
		}

		// Locate the position of the desired element
		return indexOf.call( this,

			// If it receives a jQuery object, the first element is used
			elem.jquery ? elem[ 0 ] : elem
		);
	},

	add: function( selector, context ) {
		return this.pushStack(
			jQuery.uniqueSort(
				jQuery.merge( this.get(), jQuery( selector, context ) )
			)
		);
	},

	addBack: function( selector ) {
		return this.add( selector == null ?
			this.prevObject : this.prevObject.filter( selector )
		);
	}
} );

function sibling( cur, dir ) {
	while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {}
	return cur;
}

jQuery.each( {
	parent: function( elem ) {
		var parent = elem.parentNode;
		return parent && parent.nodeType !== 11 ? parent : null;
	},
	parents: function( elem ) {
		return dir( elem, "parentNode" );
	},
	parentsUntil: function( elem, _i, until ) {
		return dir( elem, "parentNode", until );
	},
	next: function( elem ) {
		return sibling( elem, "nextSibling" );
	},
	prev: function( elem ) {
		return sibling( elem, "previousSibling" );
	},
	nextAll: function( elem ) {
		return dir( elem, "nextSibling" );
	},
	prevAll: function( elem ) {
		return dir( elem, "previousSibling" );
	},
	nextUntil: function( elem, _i, until ) {
		return dir( elem, "nextSibling", until );
	},
	prevUntil: function( elem, _i, until ) {
		return dir( elem, "previousSibling", until );
	},
	siblings: function( elem ) {
		return siblings( ( elem.parentNode || {} ).firstChild, elem );
	},
	children: function( elem ) {
		return siblings( elem.firstChild );
	},
	contents: function( elem ) {
		if ( elem.contentDocument != null &&

			// Support: IE 11+
			// <object> elements with no `data` attribute has an object
			// `contentDocument` with a `null` prototype.
			getProto( elem.contentDocument ) ) {

			return elem.contentDocument;
		}

		// Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only
		// Treat the template element as a regular one in browsers that
		// don't support it.
		if ( nodeName( elem, "template" ) ) {
			elem = elem.content || elem;
		}

		return jQuery.merge( [], elem.childNodes );
	}
}, function( name, fn ) {
	jQuery.fn[ name ] = function( until, selector ) {
		var matched = jQuery.map( this, fn, until );

		if ( name.slice( -5 ) !== "Until" ) {
			selector = until;
		}

		if ( selector && typeof selector === "string" ) {
			matched = jQuery.filter( selector, matched );
		}

		if ( this.length > 1 ) {

			// Remove duplicates
			if ( !guaranteedUnique[ name ] ) {
				jQuery.uniqueSort( matched );
			}

			// Reverse order for parents* and prev-derivatives
			if ( rparentsprev.test( name ) ) {
				matched.reverse();
			}
		}

		return this.pushStack( matched );
	};
} );
var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g );



// Convert String-formatted options into Object-formatted ones
function createOptions( options ) {
	var object = {};
	jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) {
		object[ flag ] = true;
	} );
	return object;
}

/*
 * Create a callback list using the following parameters:
 *
 *	options: an optional list of space-separated options that will change how
 *			the callback list behaves or a more traditional option object
 *
 * By default a callback list will act like an event callback list and can be
 * "fired" multiple times.
 *
 * Possible options:
 *
 *	once:			will ensure the callback list can only be fired once (like a Deferred)
 *
 *	memory:			will keep track of previous values and will call any callback added
 *					after the list has been fired right away with the latest "memorized"
 *					values (like a Deferred)
 *
 *	unique:			will ensure a callback can only be added once (no duplicate in the list)
 *
 *	stopOnFalse:	interrupt callings when a callback returns false
 *
 */
jQuery.Callbacks = function( options ) {

	// Convert options from String-formatted to Object-formatted if needed
	// (we check in cache first)
	options = typeof options === "string" ?
		createOptions( options ) :
		jQuery.extend( {}, options );

	var // Flag to know if list is currently firing
		firing,

		// Last fire value for non-forgettable lists
		memory,

		// Flag to know if list was already fired
		fired,

		// Flag to prevent firing
		locked,

		// Actual callback list
		list = [],

		// Queue of execution data for repeatable lists
		queue = [],

		// Index of currently firing callback (modified by add/remove as needed)
		firingIndex = -1,

		// Fire callbacks
		fire = function() {

			// Enforce single-firing
			locked = locked || options.once;

			// Execute callbacks for all pending executions,
			// respecting firingIndex overrides and runtime changes
			fired = firing = true;
			for ( ; queue.length; firingIndex = -1 ) {
				memory = queue.shift();
				while ( ++firingIndex < list.length ) {

					// Run callback and check for early termination
					if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
						options.stopOnFalse ) {

						// Jump to end and forget the data so .add doesn't re-fire
						firingIndex = list.length;
						memory = false;
					}
				}
			}

			// Forget the data if we're done with it
			if ( !options.memory ) {
				memory = false;
			}

			firing = false;

			// Clean up if we're done firing for good
			if ( locked ) {

				// Keep an empty list if we have data for future add calls
				if ( memory ) {
					list = [];

				// Otherwise, this object is spent
				} else {
					list = "";
				}
			}
		},

		// Actual Callbacks object
		self = {

			// Add a callback or a collection of callbacks to the list
			add: function() {
				if ( list ) {

					// If we have memory from a past run, we should fire after adding
					if ( memory && !firing ) {
						firingIndex = list.length - 1;
						queue.push( memory );
					}

					( function add( args ) {
						jQuery.each( args, function( _, arg ) {
							if ( isFunction( arg ) ) {
								if ( !options.unique || !self.has( arg ) ) {
									list.push( arg );
								}
							} else if ( arg && arg.length && toType( arg ) !== "string" ) {

								// Inspect recursively
								add( arg );
							}
						} );
					} )( arguments );

					if ( memory && !firing ) {
						fire();
					}
				}
				return this;
			},

			// Remove a callback from the list
			remove: function() {
				jQuery.each( arguments, function( _, arg ) {
					var index;
					while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
						list.splice( index, 1 );

						// Handle firing indexes
						if ( index <= firingIndex ) {
							firingIndex--;
						}
					}
				} );
				return this;
			},

			// Check if a given callback is in the list.
			// If no argument is given, return whether or not list has callbacks attached.
			has: function( fn ) {
				return fn ?
					jQuery.inArray( fn, list ) > -1 :
					list.length > 0;
			},

			// Remove all callbacks from the list
			empty: function() {
				if ( list ) {
					list = [];
				}
				return this;
			},

			// Disable .fire and .add
			// Abort any current/pending executions
			// Clear all callbacks and values
			disable: function() {
				locked = queue = [];
				list = memory = "";
				return this;
			},
			disabled: function() {
				return !list;
			},

			// Disable .fire
			// Also disable .add unless we have memory (since it would have no effect)
			// Abort any pending executions
			lock: function() {
				locked = queue = [];
				if ( !memory && !firing ) {
					list = memory = "";
				}
				return this;
			},
			locked: function() {
				return !!locked;
			},

			// Call all callbacks with the given context and arguments
			fireWith: function( context, args ) {
				if ( !locked ) {
					args = args || [];
					args = [ context, args.slice ? args.slice() : args ];
					queue.push( args );
					if ( !firing ) {
						fire();
					}
				}
				return this;
			},

			// Call all the callbacks with the given arguments
			fire: function() {
				self.fireWith( this, arguments );
				return this;
			},

			// To know if the callbacks have already been called at least once
			fired: function() {
				return !!fired;
			}
		};

	return self;
};


function Identity( v ) {
	return v;
}
function Thrower( ex ) {
	throw ex;
}

function adoptValue( value, resolve, reject, noValue ) {
	var method;

	try {

		// Check for promise aspect first to privilege synchronous behavior
		if ( value && isFunction( ( method = value.promise ) ) ) {
			method.call( value ).done( resolve ).fail( reject );

		// Other thenables
		} else if ( value && isFunction( ( method = value.then ) ) ) {
			method.call( value, resolve, reject );

		// Other non-thenables
		} else {

			// Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer:
			// * false: [ value ].slice( 0 ) => resolve( value )
			// * true: [ value ].slice( 1 ) => resolve()
			resolve.apply( undefined, [ value ].slice( noValue ) );
		}

	// For Promises/A+, convert exceptions into rejections
	// Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in
	// Deferred#then to conditionally suppress rejection.
	} catch ( value ) {

		// Support: Android 4.0 only
		// Strict mode functions invoked without .call/.apply get global-object context
		reject.apply( undefined, [ value ] );
	}
}

jQuery.extend( {

	Deferred: function( func ) {
		var tuples = [

				// action, add listener, callbacks,
				// ... .then handlers, argument index, [final state]
				[ "notify", "progress", jQuery.Callbacks( "memory" ),
					jQuery.Callbacks( "memory" ), 2 ],
				[ "resolve", "done", jQuery.Callbacks( "once memory" ),
					jQuery.Callbacks( "once memory" ), 0, "resolved" ],
				[ "reject", "fail", jQuery.Callbacks( "once memory" ),
					jQuery.Callbacks( "once memory" ), 1, "rejected" ]
			],
			state = "pending",
			promise = {
				state: function() {
					return state;
				},
				always: function() {
					deferred.done( arguments ).fail( arguments );
					return this;
				},
				"catch": function( fn ) {
					return promise.then( null, fn );
				},

				// Keep pipe for back-compat
				pipe: function( /* fnDone, fnFail, fnProgress */ ) {
					var fns = arguments;

					return jQuery.Deferred( function( newDefer ) {
						jQuery.each( tuples, function( _i, tuple ) {

							// Map tuples (progress, done, fail) to arguments (done, fail, progress)
							var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ];

							// deferred.progress(function() { bind to newDefer or newDefer.notify })
							// deferred.done(function() { bind to newDefer or newDefer.resolve })
							// deferred.fail(function() { bind to newDefer or newDefer.reject })
							deferred[ tuple[ 1 ] ]( function() {
								var returned = fn && fn.apply( this, arguments );
								if ( returned && isFunction( returned.promise ) ) {
									returned.promise()
										.progress( newDefer.notify )
										.done( newDefer.resolve )
										.fail( newDefer.reject );
								} else {
									newDefer[ tuple[ 0 ] + "With" ](
										this,
										fn ? [ returned ] : arguments
									);
								}
							} );
						} );
						fns = null;
					} ).promise();
				},
				then: function( onFulfilled, onRejected, onProgress ) {
					var maxDepth = 0;
					function resolve( depth, deferred, handler, special ) {
						return function() {
							var that = this,
								args = arguments,
								mightThrow = function() {
									var returned, then;

									// Support: Promises/A+ section 2.3.3.3.3
									// https://promisesaplus.com/#point-59
									// Ignore double-resolution attempts
									if ( depth < maxDepth ) {
										return;
									}

									returned = handler.apply( that, args );

									// Support: Promises/A+ section 2.3.1
									// https://promisesaplus.com/#point-48
									if ( returned === deferred.promise() ) {
										throw new TypeError( "Thenable self-resolution" );
									}

									// Support: Promises/A+ sections 2.3.3.1, 3.5
									// https://promisesaplus.com/#point-54
									// https://promisesaplus.com/#point-75
									// Retrieve `then` only once
									then = returned &&

										// Support: Promises/A+ section 2.3.4
										// https://promisesaplus.com/#point-64
										// Only check objects and functions for thenability
										( typeof returned === "object" ||
											typeof returned === "function" ) &&
										returned.then;

									// Handle a returned thenable
									if ( isFunction( then ) ) {

										// Special processors (notify) just wait for resolution
										if ( special ) {
											then.call(
												returned,
												resolve( maxDepth, deferred, Identity, special ),
												resolve( maxDepth, deferred, Thrower, special )
											);

										// Normal processors (resolve) also hook into progress
										} else {

											// ...and disregard older resolution values
											maxDepth++;

											then.call(
												returned,
												resolve( maxDepth, deferred, Identity, special ),
												resolve( maxDepth, deferred, Thrower, special ),
												resolve( maxDepth, deferred, Identity,
													deferred.notifyWith )
											);
										}

									// Handle all other returned values
									} else {

										// Only substitute handlers pass on context
										// and multiple values (non-spec behavior)
										if ( handler !== Identity ) {
											that = undefined;
											args = [ returned ];
										}

										// Process the value(s)
										// Default process is resolve
										( special || deferred.resolveWith )( that, args );
									}
								},

								// Only normal processors (resolve) catch and reject exceptions
								process = special ?
									mightThrow :
									function() {
										try {
											mightThrow();
										} catch ( e ) {

											if ( jQuery.Deferred.exceptionHook ) {
												jQuery.Deferred.exceptionHook( e,
													process.stackTrace );
											}

											// Support: Promises/A+ section 2.3.3.3.4.1
											// https://promisesaplus.com/#point-61
											// Ignore post-resolution exceptions
											if ( depth + 1 >= maxDepth ) {

												// Only substitute handlers pass on context
												// and multiple values (non-spec behavior)
												if ( handler !== Thrower ) {
													that = undefined;
													args = [ e ];
												}

												deferred.rejectWith( that, args );
											}
										}
									};

							// Support: Promises/A+ section 2.3.3.3.1
							// https://promisesaplus.com/#point-57
							// Re-resolve promises immediately to dodge false rejection from
							// subsequent errors
							if ( depth ) {
								process();
							} else {

								// Call an optional hook to record the stack, in case of exception
								// since it's otherwise lost when execution goes async
								if ( jQuery.Deferred.getStackHook ) {
									process.stackTrace = jQuery.Deferred.getStackHook();
								}
								window.setTimeout( process );
							}
						};
					}

					return jQuery.Deferred( function( newDefer ) {

						// progress_handlers.add( ... )
						tuples[ 0 ][ 3 ].add(
							resolve(
								0,
								newDefer,
								isFunction( onProgress ) ?
									onProgress :
									Identity,
								newDefer.notifyWith
							)
						);

						// fulfilled_handlers.add( ... )
						tuples[ 1 ][ 3 ].add(
							resolve(
								0,
								newDefer,
								isFunction( onFulfilled ) ?
									onFulfilled :
									Identity
							)
						);

						// rejected_handlers.add( ... )
						tuples[ 2 ][ 3 ].add(
							resolve(
								0,
								newDefer,
								isFunction( onRejected ) ?
									onRejected :
									Thrower
							)
						);
					} ).promise();
				},

				// Get a promise for this deferred
				// If obj is provided, the promise aspect is added to the object
				promise: function( obj ) {
					return obj != null ? jQuery.extend( obj, promise ) : promise;
				}
			},
			deferred = {};

		// Add list-specific methods
		jQuery.each( tuples, function( i, tuple ) {
			var list = tuple[ 2 ],
				stateString = tuple[ 5 ];

			// promise.progress = list.add
			// promise.done = list.add
			// promise.fail = list.add
			promise[ tuple[ 1 ] ] = list.add;

			// Handle state
			if ( stateString ) {
				list.add(
					function() {

						// state = "resolved" (i.e., fulfilled)
						// state = "rejected"
						state = stateString;
					},

					// rejected_callbacks.disable
					// fulfilled_callbacks.disable
					tuples[ 3 - i ][ 2 ].disable,

					// rejected_handlers.disable
					// fulfilled_handlers.disable
					tuples[ 3 - i ][ 3 ].disable,

					// progress_callbacks.lock
					tuples[ 0 ][ 2 ].lock,

					// progress_handlers.lock
					tuples[ 0 ][ 3 ].lock
				);
			}

			// progress_handlers.fire
			// fulfilled_handlers.fire
			// rejected_handlers.fire
			list.add( tuple[ 3 ].fire );

			// deferred.notify = function() { deferred.notifyWith(...) }
			// deferred.resolve = function() { deferred.resolveWith(...) }
			// deferred.reject = function() { deferred.rejectWith(...) }
			deferred[ tuple[ 0 ] ] = function() {
				deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments );
				return this;
			};

			// deferred.notifyWith = list.fireWith
			// deferred.resolveWith = list.fireWith
			// deferred.rejectWith = list.fireWith
			deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
		} );

		// Make the deferred a promise
		promise.promise( deferred );

		// Call given func if any
		if ( func ) {
			func.call( deferred, deferred );
		}

		// All done!
		return deferred;
	},

	// Deferred helper
	when: function( singleValue ) {
		var

			// count of uncompleted subordinates
			remaining = arguments.length,

			// count of unprocessed arguments
			i = remaining,

			// subordinate fulfillment data
			resolveContexts = Array( i ),
			resolveValues = slice.call( arguments ),

			// the primary Deferred
			primary = jQuery.Deferred(),

			// subordinate callback factory
			updateFunc = function( i ) {
				return function( value ) {
					resolveContexts[ i ] = this;
					resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
					if ( !( --remaining ) ) {
						primary.resolveWith( resolveContexts, resolveValues );
					}
				};
			};

		// Single- and empty arguments are adopted like Promise.resolve
		if ( remaining <= 1 ) {
			adoptValue( singleValue, primary.done( updateFunc( i ) ).resolve, primary.reject,
				!remaining );

			// Use .then() to unwrap secondary thenables (cf. gh-3000)
			if ( primary.state() === "pending" ||
				isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) {

				return primary.then();
			}
		}

		// Multiple arguments are aggregated like Promise.all array elements
		while ( i-- ) {
			adoptValue( resolveValues[ i ], updateFunc( i ), primary.reject );
		}

		return primary.promise();
	}
} );


// These usually indicate a programmer mistake during development,
// warn about them ASAP rather than swallowing them by default.
var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;

jQuery.Deferred.exceptionHook = function( error, stack ) {

	// Support: IE 8 - 9 only
	// Console exists when dev tools are open, which can happen at any time
	if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) {
		window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack );
	}
};




jQuery.readyException = function( error ) {
	window.setTimeout( function() {
		throw error;
	} );
};




// The deferred used on DOM ready
var readyList = jQuery.Deferred();

jQuery.fn.ready = function( fn ) {

	readyList
		.then( fn )

		// Wrap jQuery.readyException in a function so that the lookup
		// happens at the time of error handling instead of callback
		// registration.
		.catch( function( error ) {
			jQuery.readyException( error );
		} );

	return this;
};

jQuery.extend( {

	// Is the DOM ready to be used? Set to true once it occurs.
	isReady: false,

	// A counter to track how many items to wait for before
	// the ready event fires. See #6781
	readyWait: 1,

	// Handle when the DOM is ready
	ready: function( wait ) {

		// Abort if there are pending holds or we're already ready
		if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
			return;
		}

		// Remember that the DOM is ready
		jQuery.isReady = true;

		// If a normal DOM Ready event fired, decrement, and wait if need be
		if ( wait !== true && --jQuery.readyWait > 0 ) {
			return;
		}

		// If there are functions bound, to execute
		readyList.resolveWith( document, [ jQuery ] );
	}
} );

jQuery.ready.then = readyList.then;

// The ready event handler and self cleanup method
function completed() {
	document.removeEventListener( "DOMContentLoaded", completed );
	window.removeEventListener( "load", completed );
	jQuery.ready();
}

// Catch cases where $(document).ready() is called
// after the browser event has already occurred.
// Support: IE <=9 - 10 only
// Older IE sometimes signals "interactive" too soon
if ( document.readyState === "complete" ||
	( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {

	// Handle it asynchronously to allow scripts the opportunity to delay ready
	window.setTimeout( jQuery.ready );

} else {

	// Use the handy event callback
	document.addEventListener( "DOMContentLoaded", completed );

	// A fallback to window.onload, that will always work
	window.addEventListener( "load", completed );
}




// Multifunctional method to get and set values of a collection
// The value/s can optionally be executed if it's a function
var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
	var i = 0,
		len = elems.length,
		bulk = key == null;

	// Sets many values
	if ( toType( key ) === "object" ) {
		chainable = true;
		for ( i in key ) {
			access( elems, fn, i, key[ i ], true, emptyGet, raw );
		}

	// Sets one value
	} else if ( value !== undefined ) {
		chainable = true;

		if ( !isFunction( value ) ) {
			raw = true;
		}

		if ( bulk ) {

			// Bulk operations run against the entire set
			if ( raw ) {
				fn.call( elems, value );
				fn = null;

			// ...except when executing function values
			} else {
				bulk = fn;
				fn = function( elem, _key, value ) {
					return bulk.call( jQuery( elem ), value );
				};
			}
		}

		if ( fn ) {
			for ( ; i < len; i++ ) {
				fn(
					elems[ i ], key, raw ?
						value :
						value.call( elems[ i ], i, fn( elems[ i ], key ) )
				);
			}
		}
	}

	if ( chainable ) {
		return elems;
	}

	// Gets
	if ( bulk ) {
		return fn.call( elems );
	}

	return len ? fn( elems[ 0 ], key ) : emptyGet;
};


// Matches dashed string for camelizing
var rmsPrefix = /^-ms-/,
	rdashAlpha = /-([a-z])/g;

// Used by camelCase as callback to replace()
function fcamelCase( _all, letter ) {
	return letter.toUpperCase();
}

// Convert dashed to camelCase; used by the css and data modules
// Support: IE <=9 - 11, Edge 12 - 15
// Microsoft forgot to hump their vendor prefix (#9572)
function camelCase( string ) {
	return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
}
var acceptData = function( owner ) {

	// Accepts only:
	//  - Node
	//    - Node.ELEMENT_NODE
	//    - Node.DOCUMENT_NODE
	//  - Object
	//    - Any
	return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
};




function Data() {
	this.expando = jQuery.expando + Data.uid++;
}

Data.uid = 1;

Data.prototype = {

	cache: function( owner ) {

		// Check if the owner object already has a cache
		var value = owner[ this.expando ];

		// If not, create one
		if ( !value ) {
			value = {};

			// We can accept data for non-element nodes in modern browsers,
			// but we should not, see #8335.
			// Always return an empty object.
			if ( acceptData( owner ) ) {

				// If it is a node unlikely to be stringify-ed or looped over
				// use plain assignment
				if ( owner.nodeType ) {
					owner[ this.expando ] = value;

				// Otherwise secure it in a non-enumerable property
				// configurable must be true to allow the property to be
				// deleted when data is removed
				} else {
					Object.defineProperty( owner, this.expando, {
						value: value,
						configurable: true
					} );
				}
			}
		}

		return value;
	},
	set: function( owner, data, value ) {
		var prop,
			cache = this.cache( owner );

		// Handle: [ owner, key, value ] args
		// Always use camelCase key (gh-2257)
		if ( typeof data === "string" ) {
			cache[ camelCase( data ) ] = value;

		// Handle: [ owner, { properties } ] args
		} else {

			// Copy the properties one-by-one to the cache object
			for ( prop in data ) {
				cache[ camelCase( prop ) ] = data[ prop ];
			}
		}
		return cache;
	},
	get: function( owner, key ) {
		return key === undefined ?
			this.cache( owner ) :

			// Always use camelCase key (gh-2257)
			owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ];
	},
	access: function( owner, key, value ) {

		// In cases where either:
		//
		//   1. No key was specified
		//   2. A string key was specified, but no value provided
		//
		// Take the "read" path and allow the get method to determine
		// which value to return, respectively either:
		//
		//   1. The entire cache object
		//   2. The data stored at the key
		//
		if ( key === undefined ||
				( ( key && typeof key === "string" ) && value === undefined ) ) {

			return this.get( owner, key );
		}

		// When the key is not a string, or both a key and value
		// are specified, set or extend (existing objects) with either:
		//
		//   1. An object of properties
		//   2. A key and value
		//
		this.set( owner, key, value );

		// Since the "set" path can have two possible entry points
		// return the expected data based on which path was taken[*]
		return value !== undefined ? value : key;
	},
	remove: function( owner, key ) {
		var i,
			cache = owner[ this.expando ];

		if ( cache === undefined ) {
			return;
		}

		if ( key !== undefined ) {

			// Support array or space separated string of keys
			if ( Array.isArray( key ) ) {

				// If key is an array of keys...
				// We always set camelCase keys, so remove that.
				key = key.map( camelCase );
			} else {
				key = camelCase( key );

				// If a key with the spaces exists, use it.
				// Otherwise, create an array by matching non-whitespace
				key = key in cache ?
					[ key ] :
					( key.match( rnothtmlwhite ) || [] );
			}

			i = key.length;

			while ( i-- ) {
				delete cache[ key[ i ] ];
			}
		}

		// Remove the expando if there's no more data
		if ( key === undefined || jQuery.isEmptyObject( cache ) ) {

			// Support: Chrome <=35 - 45
			// Webkit & Blink performance suffers when deleting properties
			// from DOM nodes, so set to undefined instead
			// https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted)
			if ( owner.nodeType ) {
				owner[ this.expando ] = undefined;
			} else {
				delete owner[ this.expando ];
			}
		}
	},
	hasData: function( owner ) {
		var cache = owner[ this.expando ];
		return cache !== undefined && !jQuery.isEmptyObject( cache );
	}
};
var dataPriv = new Data();

var dataUser = new Data();



//	Implementation Summary
//
//	1. Enforce API surface and semantic compatibility with 1.9.x branch
//	2. Improve the module's maintainability by reducing the storage
//		paths to a single mechanism.
//	3. Use the same single mechanism to support "private" and "user" data.
//	4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
//	5. Avoid exposing implementation details on user objects (eg. expando properties)
//	6. Provide a clear path for implementation upgrade to WeakMap in 2014

var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
	rmultiDash = /[A-Z]/g;

function getData( data ) {
	if ( data === "true" ) {
		return true;
	}

	if ( data === "false" ) {
		return false;
	}

	if ( data === "null" ) {
		return null;
	}

	// Only convert to a number if it doesn't change the string
	if ( data === +data + "" ) {
		return +data;
	}

	if ( rbrace.test( data ) ) {
		return JSON.parse( data );
	}

	return data;
}

function dataAttr( elem, key, data ) {
	var name;

	// If nothing was found internally, try to fetch any
	// data from the HTML5 data-* attribute
	if ( data === undefined && elem.nodeType === 1 ) {
		name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase();
		data = elem.getAttribute( name );

		if ( typeof data === "string" ) {
			try {
				data = getData( data );
			} catch ( e ) {}

			// Make sure we set the data so it isn't changed later
			dataUser.set( elem, key, data );
		} else {
			data = undefined;
		}
	}
	return data;
}

jQuery.extend( {
	hasData: function( elem ) {
		return dataUser.hasData( elem ) || dataPriv.hasData( elem );
	},

	data: function( elem, name, data ) {
		return dataUser.access( elem, name, data );
	},

	removeData: function( elem, name ) {
		dataUser.remove( elem, name );
	},

	// TODO: Now that all calls to _data and _removeData have been replaced
	// with direct calls to dataPriv methods, these can be deprecated.
	_data: function( elem, name, data ) {
		return dataPriv.access( elem, name, data );
	},

	_removeData: function( elem, name ) {
		dataPriv.remove( elem, name );
	}
} );

jQuery.fn.extend( {
	data: function( key, value ) {
		var i, name, data,
			elem = this[ 0 ],
			attrs = elem && elem.attributes;

		// Gets all values
		if ( key === undefined ) {
			if ( this.length ) {
				data = dataUser.get( elem );

				if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
					i = attrs.length;
					while ( i-- ) {

						// Support: IE 11 only
						// The attrs elements can be null (#14894)
						if ( attrs[ i ] ) {
							name = attrs[ i ].name;
							if ( name.indexOf( "data-" ) === 0 ) {
								name = camelCase( name.slice( 5 ) );
								dataAttr( elem, name, data[ name ] );
							}
						}
					}
					dataPriv.set( elem, "hasDataAttrs", true );
				}
			}

			return data;
		}

		// Sets multiple values
		if ( typeof key === "object" ) {
			return this.each( function() {
				dataUser.set( this, key );
			} );
		}

		return access( this, function( value ) {
			var data;

			// The calling jQuery object (element matches) is not empty
			// (and therefore has an element appears at this[ 0 ]) and the
			// `value` parameter was not undefined. An empty jQuery object
			// will result in `undefined` for elem = this[ 0 ] which will
			// throw an exception if an attempt to read a data cache is made.
			if ( elem && value === undefined ) {

				// Attempt to get data from the cache
				// The key will always be camelCased in Data
				data = dataUser.get( elem, key );
				if ( data !== undefined ) {
					return data;
				}

				// Attempt to "discover" the data in
				// HTML5 custom data-* attrs
				data = dataAttr( elem, key );
				if ( data !== undefined ) {
					return data;
				}

				// We tried really hard, but the data doesn't exist.
				return;
			}

			// Set the data...
			this.each( function() {

				// We always store the camelCased key
				dataUser.set( this, key, value );
			} );
		}, null, value, arguments.length > 1, null, true );
	},

	removeData: function( key ) {
		return this.each( function() {
			dataUser.remove( this, key );
		} );
	}
} );


jQuery.extend( {
	queue: function( elem, type, data ) {
		var queue;

		if ( elem ) {
			type = ( type || "fx" ) + "queue";
			queue = dataPriv.get( elem, type );

			// Speed up dequeue by getting out quickly if this is just a lookup
			if ( data ) {
				if ( !queue || Array.isArray( data ) ) {
					queue = dataPriv.access( elem, type, jQuery.makeArray( data ) );
				} else {
					queue.push( data );
				}
			}
			return queue || [];
		}
	},

	dequeue: function( elem, type ) {
		type = type || "fx";

		var queue = jQuery.queue( elem, type ),
			startLength = queue.length,
			fn = queue.shift(),
			hooks = jQuery._queueHooks( elem, type ),
			next = function() {
				jQuery.dequeue( elem, type );
			};

		// If the fx queue is dequeued, always remove the progress sentinel
		if ( fn === "inprogress" ) {
			fn = queue.shift();
			startLength--;
		}

		if ( fn ) {

			// Add a progress sentinel to prevent the fx queue from being
			// automatically dequeued
			if ( type === "fx" ) {
				queue.unshift( "inprogress" );
			}

			// Clear up the last queue stop function
			delete hooks.stop;
			fn.call( elem, next, hooks );
		}

		if ( !startLength && hooks ) {
			hooks.empty.fire();
		}
	},

	// Not public - generate a queueHooks object, or return the current one
	_queueHooks: function( elem, type ) {
		var key = type + "queueHooks";
		return dataPriv.get( elem, key ) || dataPriv.access( elem, key, {
			empty: jQuery.Callbacks( "once memory" ).add( function() {
				dataPriv.remove( elem, [ type + "queue", key ] );
			} )
		} );
	}
} );

jQuery.fn.extend( {
	queue: function( type, data ) {
		var setter = 2;

		if ( typeof type !== "string" ) {
			data = type;
			type = "fx";
			setter--;
		}

		if ( arguments.length < setter ) {
			return jQuery.queue( this[ 0 ], type );
		}

		return data === undefined ?
			this :
			this.each( function() {
				var queue = jQuery.queue( this, type, data );

				// Ensure a hooks for this queue
				jQuery._queueHooks( this, type );

				if ( type === "fx" && queue[ 0 ] !== "inprogress" ) {
					jQuery.dequeue( this, type );
				}
			} );
	},
	dequeue: function( type ) {
		return this.each( function() {
			jQuery.dequeue( this, type );
		} );
	},
	clearQueue: function( type ) {
		return this.queue( type || "fx", [] );
	},

	// Get a promise resolved when queues of a certain type
	// are emptied (fx is the type by default)
	promise: function( type, obj ) {
		var tmp,
			count = 1,
			defer = jQuery.Deferred(),
			elements = this,
			i = this.length,
			resolve = function() {
				if ( !( --count ) ) {
					defer.resolveWith( elements, [ elements ] );
				}
			};

		if ( typeof type !== "string" ) {
			obj = type;
			type = undefined;
		}
		type = type || "fx";

		while ( i-- ) {
			tmp = dataPriv.get( elements[ i ], type + "queueHooks" );
			if ( tmp && tmp.empty ) {
				count++;
				tmp.empty.add( resolve );
			}
		}
		resolve();
		return defer.promise( obj );
	}
} );
var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source;

var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" );


var cssExpand = [ "Top", "Right", "Bottom", "Left" ];

var documentElement = document.documentElement;



	var isAttached = function( elem ) {
			return jQuery.contains( elem.ownerDocument, elem );
		},
		composed = { composed: true };

	// Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only
	// Check attachment across shadow DOM boundaries when possible (gh-3504)
	// Support: iOS 10.0-10.2 only
	// Early iOS 10 versions support `attachShadow` but not `getRootNode`,
	// leading to errors. We need to check for `getRootNode`.
	if ( documentElement.getRootNode ) {
		isAttached = function( elem ) {
			return jQuery.contains( elem.ownerDocument, elem ) ||
				elem.getRootNode( composed ) === elem.ownerDocument;
		};
	}
var isHiddenWithinTree = function( elem, el ) {

		// isHiddenWithinTree might be called from jQuery#filter function;
		// in that case, element will be second argument
		elem = el || elem;

		// Inline style trumps all
		return elem.style.display === "none" ||
			elem.style.display === "" &&

			// Otherwise, check computed style
			// Support: Firefox <=43 - 45
			// Disconnected elements can have computed display: none, so first confirm that elem is
			// in the document.
			isAttached( elem ) &&

			jQuery.css( elem, "display" ) === "none";
	};



function adjustCSS( elem, prop, valueParts, tween ) {
	var adjusted, scale,
		maxIterations = 20,
		currentValue = tween ?
			function() {
				return tween.cur();
			} :
			function() {
				return jQuery.css( elem, prop, "" );
			},
		initial = currentValue(),
		unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),

		// Starting value computation is required for potential unit mismatches
		initialInUnit = elem.nodeType &&
			( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) &&
			rcssNum.exec( jQuery.css( elem, prop ) );

	if ( initialInUnit && initialInUnit[ 3 ] !== unit ) {

		// Support: Firefox <=54
		// Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144)
		initial = initial / 2;

		// Trust units reported by jQuery.css
		unit = unit || initialInUnit[ 3 ];

		// Iteratively approximate from a nonzero starting point
		initialInUnit = +initial || 1;

		while ( maxIterations-- ) {

			// Evaluate and update our best guess (doubling guesses that zero out).
			// Finish if the scale equals or crosses 1 (making the old*new product non-positive).
			jQuery.style( elem, prop, initialInUnit + unit );
			if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) {
				maxIterations = 0;
			}
			initialInUnit = initialInUnit / scale;

		}

		initialInUnit = initialInUnit * 2;
		jQuery.style( elem, prop, initialInUnit + unit );

		// Make sure we update the tween properties later on
		valueParts = valueParts || [];
	}

	if ( valueParts ) {
		initialInUnit = +initialInUnit || +initial || 0;

		// Apply relative offset (+=/-=) if specified
		adjusted = valueParts[ 1 ] ?
			initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] :
			+valueParts[ 2 ];
		if ( tween ) {
			tween.unit = unit;
			tween.start = initialInUnit;
			tween.end = adjusted;
		}
	}
	return adjusted;
}


var defaultDisplayMap = {};

function getDefaultDisplay( elem ) {
	var temp,
		doc = elem.ownerDocument,
		nodeName = elem.nodeName,
		display = defaultDisplayMap[ nodeName ];

	if ( display ) {
		return display;
	}

	temp = doc.body.appendChild( doc.createElement( nodeName ) );
	display = jQuery.css( temp, "display" );

	temp.parentNode.removeChild( temp );

	if ( display === "none" ) {
		display = "block";
	}
	defaultDisplayMap[ nodeName ] = display;

	return display;
}

function showHide( elements, show ) {
	var display, elem,
		values = [],
		index = 0,
		length = elements.length;

	// Determine new display value for elements that need to change
	for ( ; index < length; index++ ) {
		elem = elements[ index ];
		if ( !elem.style ) {
			continue;
		}

		display = elem.style.display;
		if ( show ) {

			// Since we force visibility upon cascade-hidden elements, an immediate (and slow)
			// check is required in this first loop unless we have a nonempty display value (either
			// inline or about-to-be-restored)
			if ( display === "none" ) {
				values[ index ] = dataPriv.get( elem, "display" ) || null;
				if ( !values[ index ] ) {
					elem.style.display = "";
				}
			}
			if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) {
				values[ index ] = getDefaultDisplay( elem );
			}
		} else {
			if ( display !== "none" ) {
				values[ index ] = "none";

				// Remember what we're overwriting
				dataPriv.set( elem, "display", display );
			}
		}
	}

	// Set the display of the elements in a second loop to avoid constant reflow
	for ( index = 0; index < length; index++ ) {
		if ( values[ index ] != null ) {
			elements[ index ].style.display = values[ index ];
		}
	}

	return elements;
}

jQuery.fn.extend( {
	show: function() {
		return showHide( this, true );
	},
	hide: function() {
		return showHide( this );
	},
	toggle: function( state ) {
		if ( typeof state === "boolean" ) {
			return state ? this.show() : this.hide();
		}

		return this.each( function() {
			if ( isHiddenWithinTree( this ) ) {
				jQuery( this ).show();
			} else {
				jQuery( this ).hide();
			}
		} );
	}
} );
var rcheckableType = ( /^(?:checkbox|radio)$/i );

var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i );

var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i );



( function() {
	var fragment = document.createDocumentFragment(),
		div = fragment.appendChild( document.createElement( "div" ) ),
		input = document.createElement( "input" );

	// Support: Android 4.0 - 4.3 only
	// Check state lost if the name is set (#11217)
	// Support: Windows Web Apps (WWA)
	// `name` and `type` must use .setAttribute for WWA (#14901)
	input.setAttribute( "type", "radio" );
	input.setAttribute( "checked", "checked" );
	input.setAttribute( "name", "t" );

	div.appendChild( input );

	// Support: Android <=4.1 only
	// Older WebKit doesn't clone checked state correctly in fragments
	support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;

	// Support: IE <=11 only
	// Make sure textarea (and checkbox) defaultValue is properly cloned
	div.innerHTML = "<textarea>x</textarea>";
	support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;

	// Support: IE <=9 only
	// IE <=9 replaces <option> tags with their contents when inserted outside of
	// the select element.
	div.innerHTML = "<option></option>";
	support.option = !!div.lastChild;
} )();


// We have to close these tags to support XHTML (#13200)
var wrapMap = {

	// XHTML parsers do not magically insert elements in the
	// same way that tag soup parsers do. So we cannot shorten
	// this by omitting <tbody> or other required elements.
	thead: [ 1, "<table>", "</table>" ],
	col: [ 2, "<table><colgroup>", "</colgroup></table>" ],
	tr: [ 2, "<table><tbody>", "</tbody></table>" ],
	td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],

	_default: [ 0, "", "" ]
};

wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
wrapMap.th = wrapMap.td;

// Support: IE <=9 only
if ( !support.option ) {
	wrapMap.optgroup = wrapMap.option = [ 1, "<select multiple='multiple'>", "</select>" ];
}


function getAll( context, tag ) {

	// Support: IE <=9 - 11 only
	// Use typeof to avoid zero-argument method invocation on host objects (#15151)
	var ret;

	if ( typeof context.getElementsByTagName !== "undefined" ) {
		ret = context.getElementsByTagName( tag || "*" );

	} else if ( typeof context.querySelectorAll !== "undefined" ) {
		ret = context.querySelectorAll( tag || "*" );

	} else {
		ret = [];
	}

	if ( tag === undefined || tag && nodeName( context, tag ) ) {
		return jQuery.merge( [ context ], ret );
	}

	return ret;
}


// Mark scripts as having already been evaluated
function setGlobalEval( elems, refElements ) {
	var i = 0,
		l = elems.length;

	for ( ; i < l; i++ ) {
		dataPriv.set(
			elems[ i ],
			"globalEval",
			!refElements || dataPriv.get( refElements[ i ], "globalEval" )
		);
	}
}


var rhtml = /<|&#?\w+;/;

function buildFragment( elems, context, scripts, selection, ignored ) {
	var elem, tmp, tag, wrap, attached, j,
		fragment = context.createDocumentFragment(),
		nodes = [],
		i = 0,
		l = elems.length;

	for ( ; i < l; i++ ) {
		elem = elems[ i ];

		if ( elem || elem === 0 ) {

			// Add nodes directly
			if ( toType( elem ) === "object" ) {

				// Support: Android <=4.0 only, PhantomJS 1 only
				// push.apply(_, arraylike) throws on ancient WebKit
				jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );

			// Convert non-html into a text node
			} else if ( !rhtml.test( elem ) ) {
				nodes.push( context.createTextNode( elem ) );

			// Convert html into DOM nodes
			} else {
				tmp = tmp || fragment.appendChild( context.createElement( "div" ) );

				// Deserialize a standard representation
				tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
				wrap = wrapMap[ tag ] || wrapMap._default;
				tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];

				// Descend through wrappers to the right content
				j = wrap[ 0 ];
				while ( j-- ) {
					tmp = tmp.lastChild;
				}

				// Support: Android <=4.0 only, PhantomJS 1 only
				// push.apply(_, arraylike) throws on ancient WebKit
				jQuery.merge( nodes, tmp.childNodes );

				// Remember the top-level container
				tmp = fragment.firstChild;

				// Ensure the created nodes are orphaned (#12392)
				tmp.textContent = "";
			}
		}
	}

	// Remove wrapper from fragment
	fragment.textContent = "";

	i = 0;
	while ( ( elem = nodes[ i++ ] ) ) {

		// Skip elements already in the context collection (trac-4087)
		if ( selection && jQuery.inArray( elem, selection ) > -1 ) {
			if ( ignored ) {
				ignored.push( elem );
			}
			continue;
		}

		attached = isAttached( elem );

		// Append to fragment
		tmp = getAll( fragment.appendChild( elem ), "script" );

		// Preserve script evaluation history
		if ( attached ) {
			setGlobalEval( tmp );
		}

		// Capture executables
		if ( scripts ) {
			j = 0;
			while ( ( elem = tmp[ j++ ] ) ) {
				if ( rscriptType.test( elem.type || "" ) ) {
					scripts.push( elem );
				}
			}
		}
	}

	return fragment;
}


var rtypenamespace = /^([^.]*)(?:\.(.+)|)/;

function returnTrue() {
	return true;
}

function returnFalse() {
	return false;
}

// Support: IE <=9 - 11+
// focus() and blur() are asynchronous, except when they are no-op.
// So expect focus to be synchronous when the element is already active,
// and blur to be synchronous when the element is not already active.
// (focus and blur are always synchronous in other supported browsers,
// this just defines when we can count on it).
function expectSync( elem, type ) {
	return ( elem === safeActiveElement() ) === ( type === "focus" );
}

// Support: IE <=9 only
// Accessing document.activeElement can throw unexpectedly
// https://bugs.jquery.com/ticket/13393
function safeActiveElement() {
	try {
		return document.activeElement;
	} catch ( err ) { }
}

function on( elem, types, selector, data, fn, one ) {
	var origFn, type;

	// Types can be a map of types/handlers
	if ( typeof types === "object" ) {

		// ( types-Object, selector, data )
		if ( typeof selector !== "string" ) {

			// ( types-Object, data )
			data = data || selector;
			selector = undefined;
		}
		for ( type in types ) {
			on( elem, type, selector, data, types[ type ], one );
		}
		return elem;
	}

	if ( data == null && fn == null ) {

		// ( types, fn )
		fn = selector;
		data = selector = undefined;
	} else if ( fn == null ) {
		if ( typeof selector === "string" ) {

			// ( types, selector, fn )
			fn = data;
			data = undefined;
		} else {

			// ( types, data, fn )
			fn = data;
			data = selector;
			selector = undefined;
		}
	}
	if ( fn === false ) {
		fn = returnFalse;
	} else if ( !fn ) {
		return elem;
	}

	if ( one === 1 ) {
		origFn = fn;
		fn = function( event ) {

			// Can use an empty set, since event contains the info
			jQuery().off( event );
			return origFn.apply( this, arguments );
		};

		// Use same guid so caller can remove using origFn
		fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
	}
	return elem.each( function() {
		jQuery.event.add( this, types, fn, data, selector );
	} );
}

/*
 * Helper functions for managing events -- not part of the public interface.
 * Props to Dean Edwards' addEvent library for many of the ideas.
 */
jQuery.event = {

	global: {},

	add: function( elem, types, handler, data, selector ) {

		var handleObjIn, eventHandle, tmp,
			events, t, handleObj,
			special, handlers, type, namespaces, origType,
			elemData = dataPriv.get( elem );

		// Only attach events to objects that accept data
		if ( !acceptData( elem ) ) {
			return;
		}

		// Caller can pass in an object of custom data in lieu of the handler
		if ( handler.handler ) {
			handleObjIn = handler;
			handler = handleObjIn.handler;
			selector = handleObjIn.selector;
		}

		// Ensure that invalid selectors throw exceptions at attach time
		// Evaluate against documentElement in case elem is a non-element node (e.g., document)
		if ( selector ) {
			jQuery.find.matchesSelector( documentElement, selector );
		}

		// Make sure that the handler has a unique ID, used to find/remove it later
		if ( !handler.guid ) {
			handler.guid = jQuery.guid++;
		}

		// Init the element's event structure and main handler, if this is the first
		if ( !( events = elemData.events ) ) {
			events = elemData.events = Object.create( null );
		}
		if ( !( eventHandle = elemData.handle ) ) {
			eventHandle = elemData.handle = function( e ) {

				// Discard the second event of a jQuery.event.trigger() and
				// when an event is called after a page has unloaded
				return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
					jQuery.event.dispatch.apply( elem, arguments ) : undefined;
			};
		}

		// Handle multiple events separated by a space
		types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
		t = types.length;
		while ( t-- ) {
			tmp = rtypenamespace.exec( types[ t ] ) || [];
			type = origType = tmp[ 1 ];
			namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();

			// There *must* be a type, no attaching namespace-only handlers
			if ( !type ) {
				continue;
			}

			// If event changes its type, use the special event handlers for the changed type
			special = jQuery.event.special[ type ] || {};

			// If selector defined, determine special event api type, otherwise given type
			type = ( selector ? special.delegateType : special.bindType ) || type;

			// Update special based on newly reset type
			special = jQuery.event.special[ type ] || {};

			// handleObj is passed to all event handlers
			handleObj = jQuery.extend( {
				type: type,
				origType: origType,
				data: data,
				handler: handler,
				guid: handler.guid,
				selector: selector,
				needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
				namespace: namespaces.join( "." )
			}, handleObjIn );

			// Init the event handler queue if we're the first
			if ( !( handlers = events[ type ] ) ) {
				handlers = events[ type ] = [];
				handlers.delegateCount = 0;

				// Only use addEventListener if the special events handler returns false
				if ( !special.setup ||
					special.setup.call( elem, data, namespaces, eventHandle ) === false ) {

					if ( elem.addEventListener ) {
						elem.addEventListener( type, eventHandle );
					}
				}
			}

			if ( special.add ) {
				special.add.call( elem, handleObj );

				if ( !handleObj.handler.guid ) {
					handleObj.handler.guid = handler.guid;
				}
			}

			// Add to the element's handler list, delegates in front
			if ( selector ) {
				handlers.splice( handlers.delegateCount++, 0, handleObj );
			} else {
				handlers.push( handleObj );
			}

			// Keep track of which events have ever been used, for event optimization
			jQuery.event.global[ type ] = true;
		}

	},

	// Detach an event or set of events from an element
	remove: function( elem, types, handler, selector, mappedTypes ) {

		var j, origCount, tmp,
			events, t, handleObj,
			special, handlers, type, namespaces, origType,
			elemData = dataPriv.hasData( elem ) && dataPriv.get( elem );

		if ( !elemData || !( events = elemData.events ) ) {
			return;
		}

		// Once for each type.namespace in types; type may be omitted
		types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
		t = types.length;
		while ( t-- ) {
			tmp = rtypenamespace.exec( types[ t ] ) || [];
			type = origType = tmp[ 1 ];
			namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();

			// Unbind all events (on this namespace, if provided) for the element
			if ( !type ) {
				for ( type in events ) {
					jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
				}
				continue;
			}

			special = jQuery.event.special[ type ] || {};
			type = ( selector ? special.delegateType : special.bindType ) || type;
			handlers = events[ type ] || [];
			tmp = tmp[ 2 ] &&
				new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" );

			// Remove matching events
			origCount = j = handlers.length;
			while ( j-- ) {
				handleObj = handlers[ j ];

				if ( ( mappedTypes || origType === handleObj.origType ) &&
					( !handler || handler.guid === handleObj.guid ) &&
					( !tmp || tmp.test( handleObj.namespace ) ) &&
					( !selector || selector === handleObj.selector ||
						selector === "**" && handleObj.selector ) ) {
					handlers.splice( j, 1 );

					if ( handleObj.selector ) {
						handlers.delegateCount--;
					}
					if ( special.remove ) {
						special.remove.call( elem, handleObj );
					}
				}
			}

			// Remove generic event handler if we removed something and no more handlers exist
			// (avoids potential for endless recursion during removal of special event handlers)
			if ( origCount && !handlers.length ) {
				if ( !special.teardown ||
					special.teardown.call( elem, namespaces, elemData.handle ) === false ) {

					jQuery.removeEvent( elem, type, elemData.handle );
				}

				delete events[ type ];
			}
		}

		// Remove data and the expando if it's no longer used
		if ( jQuery.isEmptyObject( events ) ) {
			dataPriv.remove( elem, "handle events" );
		}
	},

	dispatch: function( nativeEvent ) {

		var i, j, ret, matched, handleObj, handlerQueue,
			args = new Array( arguments.length ),

			// Make a writable jQuery.Event from the native event object
			event = jQuery.event.fix( nativeEvent ),

			handlers = (
				dataPriv.get( this, "events" ) || Object.create( null )
			)[ event.type ] || [],
			special = jQuery.event.special[ event.type ] || {};

		// Use the fix-ed jQuery.Event rather than the (read-only) native event
		args[ 0 ] = event;

		for ( i = 1; i < arguments.length; i++ ) {
			args[ i ] = arguments[ i ];
		}

		event.delegateTarget = this;

		// Call the preDispatch hook for the mapped type, and let it bail if desired
		if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
			return;
		}

		// Determine handlers
		handlerQueue = jQuery.event.handlers.call( this, event, handlers );

		// Run delegates first; they may want to stop propagation beneath us
		i = 0;
		while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
			event.currentTarget = matched.elem;

			j = 0;
			while ( ( handleObj = matched.handlers[ j++ ] ) &&
				!event.isImmediatePropagationStopped() ) {

				// If the event is namespaced, then each handler is only invoked if it is
				// specially universal or its namespaces are a superset of the event's.
				if ( !event.rnamespace || handleObj.namespace === false ||
					event.rnamespace.test( handleObj.namespace ) ) {

					event.handleObj = handleObj;
					event.data = handleObj.data;

					ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||
						handleObj.handler ).apply( matched.elem, args );

					if ( ret !== undefined ) {
						if ( ( event.result = ret ) === false ) {
							event.preventDefault();
							event.stopPropagation();
						}
					}
				}
			}
		}

		// Call the postDispatch hook for the mapped type
		if ( special.postDispatch ) {
			special.postDispatch.call( this, event );
		}

		return event.result;
	},

	handlers: function( event, handlers ) {
		var i, handleObj, sel, matchedHandlers, matchedSelectors,
			handlerQueue = [],
			delegateCount = handlers.delegateCount,
			cur = event.target;

		// Find delegate handlers
		if ( delegateCount &&

			// Support: IE <=9
			// Black-hole SVG <use> instance trees (trac-13180)
			cur.nodeType &&

			// Support: Firefox <=42
			// Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861)
			// https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click
			// Support: IE 11 only
			// ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343)
			!( event.type === "click" && event.button >= 1 ) ) {

			for ( ; cur !== this; cur = cur.parentNode || this ) {

				// Don't check non-elements (#13208)
				// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
				if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) {
					matchedHandlers = [];
					matchedSelectors = {};
					for ( i = 0; i < delegateCount; i++ ) {
						handleObj = handlers[ i ];

						// Don't conflict with Object.prototype properties (#13203)
						sel = handleObj.selector + " ";

						if ( matchedSelectors[ sel ] === undefined ) {
							matchedSelectors[ sel ] = handleObj.needsContext ?
								jQuery( sel, this ).index( cur ) > -1 :
								jQuery.find( sel, this, null, [ cur ] ).length;
						}
						if ( matchedSelectors[ sel ] ) {
							matchedHandlers.push( handleObj );
						}
					}
					if ( matchedHandlers.length ) {
						handlerQueue.push( { elem: cur, handlers: matchedHandlers } );
					}
				}
			}
		}

		// Add the remaining (directly-bound) handlers
		cur = this;
		if ( delegateCount < handlers.length ) {
			handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } );
		}

		return handlerQueue;
	},

	addProp: function( name, hook ) {
		Object.defineProperty( jQuery.Event.prototype, name, {
			enumerable: true,
			configurable: true,

			get: isFunction( hook ) ?
				function() {
					if ( this.originalEvent ) {
						return hook( this.originalEvent );
					}
				} :
				function() {
					if ( this.originalEvent ) {
						return this.originalEvent[ name ];
					}
				},

			set: function( value ) {
				Object.defineProperty( this, name, {
					enumerable: true,
					configurable: true,
					writable: true,
					value: value
				} );
			}
		} );
	},

	fix: function( originalEvent ) {
		return originalEvent[ jQuery.expando ] ?
			originalEvent :
			new jQuery.Event( originalEvent );
	},

	special: {
		load: {

			// Prevent triggered image.load events from bubbling to window.load
			noBubble: true
		},
		click: {

			// Utilize native event to ensure correct state for checkable inputs
			setup: function( data ) {

				// For mutual compressibility with _default, replace `this` access with a local var.
				// `|| data` is dead code meant only to preserve the variable through minification.
				var el = this || data;

				// Claim the first handler
				if ( rcheckableType.test( el.type ) &&
					el.click && nodeName( el, "input" ) ) {

					// dataPriv.set( el, "click", ... )
					leverageNative( el, "click", returnTrue );
				}

				// Return false to allow normal processing in the caller
				return false;
			},
			trigger: function( data ) {

				// For mutual compressibility with _default, replace `this` access with a local var.
				// `|| data` is dead code meant only to preserve the variable through minification.
				var el = this || data;

				// Force setup before triggering a click
				if ( rcheckableType.test( el.type ) &&
					el.click && nodeName( el, "input" ) ) {

					leverageNative( el, "click" );
				}

				// Return non-false to allow normal event-path propagation
				return true;
			},

			// For cross-browser consistency, suppress native .click() on links
			// Also prevent it if we're currently inside a leveraged native-event stack
			_default: function( event ) {
				var target = event.target;
				return rcheckableType.test( target.type ) &&
					target.click && nodeName( target, "input" ) &&
					dataPriv.get( target, "click" ) ||
					nodeName( target, "a" );
			}
		},

		beforeunload: {
			postDispatch: function( event ) {

				// Support: Firefox 20+
				// Firefox doesn't alert if the returnValue field is not set.
				if ( event.result !== undefined && event.originalEvent ) {
					event.originalEvent.returnValue = event.result;
				}
			}
		}
	}
};

// Ensure the presence of an event listener that handles manually-triggered
// synthetic events by interrupting progress until reinvoked in response to
// *native* events that it fires directly, ensuring that state changes have
// already occurred before other listeners are invoked.
function leverageNative( el, type, expectSync ) {

	// Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add
	if ( !expectSync ) {
		if ( dataPriv.get( el, type ) === undefined ) {
			jQuery.event.add( el, type, returnTrue );
		}
		return;
	}

	// Register the controller as a special universal handler for all event namespaces
	dataPriv.set( el, type, false );
	jQuery.event.add( el, type, {
		namespace: false,
		handler: function( event ) {
			var notAsync, result,
				saved = dataPriv.get( this, type );

			if ( ( event.isTrigger & 1 ) && this[ type ] ) {

				// Interrupt processing of the outer synthetic .trigger()ed event
				// Saved data should be false in such cases, but might be a leftover capture object
				// from an async native handler (gh-4350)
				if ( !saved.length ) {

					// Store arguments for use when handling the inner native event
					// There will always be at least one argument (an event object), so this array
					// will not be confused with a leftover capture object.
					saved = slice.call( arguments );
					dataPriv.set( this, type, saved );

					// Trigger the native event and capture its result
					// Support: IE <=9 - 11+
					// focus() and blur() are asynchronous
					notAsync = expectSync( this, type );
					this[ type ]();
					result = dataPriv.get( this, type );
					if ( saved !== result || notAsync ) {
						dataPriv.set( this, type, false );
					} else {
						result = {};
					}
					if ( saved !== result ) {

						// Cancel the outer synthetic event
						event.stopImmediatePropagation();
						event.preventDefault();

						// Support: Chrome 86+
						// In Chrome, if an element having a focusout handler is blurred by
						// clicking outside of it, it invokes the handler synchronously. If
						// that handler calls `.remove()` on the element, the data is cleared,
						// leaving `result` undefined. We need to guard against this.
						return result && result.value;
					}

				// If this is an inner synthetic event for an event with a bubbling surrogate
				// (focus or blur), assume that the surrogate already propagated from triggering the
				// native event and prevent that from happening again here.
				// This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the
				// bubbling surrogate propagates *after* the non-bubbling base), but that seems
				// less bad than duplication.
				} else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) {
					event.stopPropagation();
				}

			// If this is a native event triggered above, everything is now in order
			// Fire an inner synthetic event with the original arguments
			} else if ( saved.length ) {

				// ...and capture the result
				dataPriv.set( this, type, {
					value: jQuery.event.trigger(

						// Support: IE <=9 - 11+
						// Extend with the prototype to reset the above stopImmediatePropagation()
						jQuery.extend( saved[ 0 ], jQuery.Event.prototype ),
						saved.slice( 1 ),
						this
					)
				} );

				// Abort handling of the native event
				event.stopImmediatePropagation();
			}
		}
	} );
}

jQuery.removeEvent = function( elem, type, handle ) {

	// This "if" is needed for plain objects
	if ( elem.removeEventListener ) {
		elem.removeEventListener( type, handle );
	}
};

jQuery.Event = function( src, props ) {

	// Allow instantiation without the 'new' keyword
	if ( !( this instanceof jQuery.Event ) ) {
		return new jQuery.Event( src, props );
	}

	// Event object
	if ( src && src.type ) {
		this.originalEvent = src;
		this.type = src.type;

		// Events bubbling up the document may have been marked as prevented
		// by a handler lower down the tree; reflect the correct value.
		this.isDefaultPrevented = src.defaultPrevented ||
				src.defaultPrevented === undefined &&

				// Support: Android <=2.3 only
				src.returnValue === false ?
			returnTrue :
			returnFalse;

		// Create target properties
		// Support: Safari <=6 - 7 only
		// Target should not be a text node (#504, #13143)
		this.target = ( src.target && src.target.nodeType === 3 ) ?
			src.target.parentNode :
			src.target;

		this.currentTarget = src.currentTarget;
		this.relatedTarget = src.relatedTarget;

	// Event type
	} else {
		this.type = src;
	}

	// Put explicitly provided properties onto the event object
	if ( props ) {
		jQuery.extend( this, props );
	}

	// Create a timestamp if incoming event doesn't have one
	this.timeStamp = src && src.timeStamp || Date.now();

	// Mark it as fixed
	this[ jQuery.expando ] = true;
};

// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
jQuery.Event.prototype = {
	constructor: jQuery.Event,
	isDefaultPrevented: returnFalse,
	isPropagationStopped: returnFalse,
	isImmediatePropagationStopped: returnFalse,
	isSimulated: false,

	preventDefault: function() {
		var e = this.originalEvent;

		this.isDefaultPrevented = returnTrue;

		if ( e && !this.isSimulated ) {
			e.preventDefault();
		}
	},
	stopPropagation: function() {
		var e = this.originalEvent;

		this.isPropagationStopped = returnTrue;

		if ( e && !this.isSimulated ) {
			e.stopPropagation();
		}
	},
	stopImmediatePropagation: function() {
		var e = this.originalEvent;

		this.isImmediatePropagationStopped = returnTrue;

		if ( e && !this.isSimulated ) {
			e.stopImmediatePropagation();
		}

		this.stopPropagation();
	}
};

// Includes all common event props including KeyEvent and MouseEvent specific props
jQuery.each( {
	altKey: true,
	bubbles: true,
	cancelable: true,
	changedTouches: true,
	ctrlKey: true,
	detail: true,
	eventPhase: true,
	metaKey: true,
	pageX: true,
	pageY: true,
	shiftKey: true,
	view: true,
	"char": true,
	code: true,
	charCode: true,
	key: true,
	keyCode: true,
	button: true,
	buttons: true,
	clientX: true,
	clientY: true,
	offsetX: true,
	offsetY: true,
	pointerId: true,
	pointerType: true,
	screenX: true,
	screenY: true,
	targetTouches: true,
	toElement: true,
	touches: true,
	which: true
}, jQuery.event.addProp );

jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) {
	jQuery.event.special[ type ] = {

		// Utilize native event if possible so blur/focus sequence is correct
		setup: function() {

			// Claim the first handler
			// dataPriv.set( this, "focus", ... )
			// dataPriv.set( this, "blur", ... )
			leverageNative( this, type, expectSync );

			// Return false to allow normal processing in the caller
			return false;
		},
		trigger: function() {

			// Force setup before trigger
			leverageNative( this, type );

			// Return non-false to allow normal event-path propagation
			return true;
		},

		// Suppress native focus or blur as it's already being fired
		// in leverageNative.
		_default: function() {
			return true;
		},

		delegateType: delegateType
	};
} );

// Create mouseenter/leave events using mouseover/out and event-time checks
// so that event delegation works in jQuery.
// Do the same for pointerenter/pointerleave and pointerover/pointerout
//
// Support: Safari 7 only
// Safari sends mouseenter too often; see:
// https://bugs.chromium.org/p/chromium/issues/detail?id=470258
// for the description of the bug (it existed in older Chrome versions as well).
jQuery.each( {
	mouseenter: "mouseover",
	mouseleave: "mouseout",
	pointerenter: "pointerover",
	pointerleave: "pointerout"
}, function( orig, fix ) {
	jQuery.event.special[ orig ] = {
		delegateType: fix,
		bindType: fix,

		handle: function( event ) {
			var ret,
				target = this,
				related = event.relatedTarget,
				handleObj = event.handleObj;

			// For mouseenter/leave call the handler if related is outside the target.
			// NB: No relatedTarget if the mouse left/entered the browser window
			if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) {
				event.type = handleObj.origType;
				ret = handleObj.handler.apply( this, arguments );
				event.type = fix;
			}
			return ret;
		}
	};
} );

jQuery.fn.extend( {

	on: function( types, selector, data, fn ) {
		return on( this, types, selector, data, fn );
	},
	one: function( types, selector, data, fn ) {
		return on( this, types, selector, data, fn, 1 );
	},
	off: function( types, selector, fn ) {
		var handleObj, type;
		if ( types && types.preventDefault && types.handleObj ) {

			// ( event )  dispatched jQuery.Event
			handleObj = types.handleObj;
			jQuery( types.delegateTarget ).off(
				handleObj.namespace ?
					handleObj.origType + "." + handleObj.namespace :
					handleObj.origType,
				handleObj.selector,
				handleObj.handler
			);
			return this;
		}
		if ( typeof types === "object" ) {

			// ( types-object [, selector] )
			for ( type in types ) {
				this.off( type, selector, types[ type ] );
			}
			return this;
		}
		if ( selector === false || typeof selector === "function" ) {

			// ( types [, fn] )
			fn = selector;
			selector = undefined;
		}
		if ( fn === false ) {
			fn = returnFalse;
		}
		return this.each( function() {
			jQuery.event.remove( this, types, fn, selector );
		} );
	}
} );


var

	// Support: IE <=10 - 11, Edge 12 - 13 only
	// In IE/Edge using regex groups here causes severe slowdowns.
	// See https://connect.microsoft.com/IE/feedback/details/1736512/
	rnoInnerhtml = /<script|<style|<link/i,

	// checked="checked" or checked
	rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
	rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;

// Prefer a tbody over its parent table for containing new rows
function manipulationTarget( elem, content ) {
	if ( nodeName( elem, "table" ) &&
		nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) {

		return jQuery( elem ).children( "tbody" )[ 0 ] || elem;
	}

	return elem;
}

// Replace/restore the type attribute of script elements for safe DOM manipulation
function disableScript( elem ) {
	elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type;
	return elem;
}
function restoreScript( elem ) {
	if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) {
		elem.type = elem.type.slice( 5 );
	} else {
		elem.removeAttribute( "type" );
	}

	return elem;
}

function cloneCopyEvent( src, dest ) {
	var i, l, type, pdataOld, udataOld, udataCur, events;

	if ( dest.nodeType !== 1 ) {
		return;
	}

	// 1. Copy private data: events, handlers, etc.
	if ( dataPriv.hasData( src ) ) {
		pdataOld = dataPriv.get( src );
		events = pdataOld.events;

		if ( events ) {
			dataPriv.remove( dest, "handle events" );

			for ( type in events ) {
				for ( i = 0, l = events[ type ].length; i < l; i++ ) {
					jQuery.event.add( dest, type, events[ type ][ i ] );
				}
			}
		}
	}

	// 2. Copy user data
	if ( dataUser.hasData( src ) ) {
		udataOld = dataUser.access( src );
		udataCur = jQuery.extend( {}, udataOld );

		dataUser.set( dest, udataCur );
	}
}

// Fix IE bugs, see support tests
function fixInput( src, dest ) {
	var nodeName = dest.nodeName.toLowerCase();

	// Fails to persist the checked state of a cloned checkbox or radio button.
	if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
		dest.checked = src.checked;

	// Fails to return the selected option to the default selected state when cloning options
	} else if ( nodeName === "input" || nodeName === "textarea" ) {
		dest.defaultValue = src.defaultValue;
	}
}

function domManip( collection, args, callback, ignored ) {

	// Flatten any nested arrays
	args = flat( args );

	var fragment, first, scripts, hasScripts, node, doc,
		i = 0,
		l = collection.length,
		iNoClone = l - 1,
		value = args[ 0 ],
		valueIsFunction = isFunction( value );

	// We can't cloneNode fragments that contain checked, in WebKit
	if ( valueIsFunction ||
			( l > 1 && typeof value === "string" &&
				!support.checkClone && rchecked.test( value ) ) ) {
		return collection.each( function( index ) {
			var self = collection.eq( index );
			if ( valueIsFunction ) {
				args[ 0 ] = value.call( this, index, self.html() );
			}
			domManip( self, args, callback, ignored );
		} );
	}

	if ( l ) {
		fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored );
		first = fragment.firstChild;

		if ( fragment.childNodes.length === 1 ) {
			fragment = first;
		}

		// Require either new content or an interest in ignored elements to invoke the callback
		if ( first || ignored ) {
			scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
			hasScripts = scripts.length;

			// Use the original fragment for the last item
			// instead of the first because it can end up
			// being emptied incorrectly in certain situations (#8070).
			for ( ; i < l; i++ ) {
				node = fragment;

				if ( i !== iNoClone ) {
					node = jQuery.clone( node, true, true );

					// Keep references to cloned scripts for later restoration
					if ( hasScripts ) {

						// Support: Android <=4.0 only, PhantomJS 1 only
						// push.apply(_, arraylike) throws on ancient WebKit
						jQuery.merge( scripts, getAll( node, "script" ) );
					}
				}

				callback.call( collection[ i ], node, i );
			}

			if ( hasScripts ) {
				doc = scripts[ scripts.length - 1 ].ownerDocument;

				// Reenable scripts
				jQuery.map( scripts, restoreScript );

				// Evaluate executable scripts on first document insertion
				for ( i = 0; i < hasScripts; i++ ) {
					node = scripts[ i ];
					if ( rscriptType.test( node.type || "" ) &&
						!dataPriv.access( node, "globalEval" ) &&
						jQuery.contains( doc, node ) ) {

						if ( node.src && ( node.type || "" ).toLowerCase()  !== "module" ) {

							// Optional AJAX dependency, but won't run scripts if not present
							if ( jQuery._evalUrl && !node.noModule ) {
								jQuery._evalUrl( node.src, {
									nonce: node.nonce || node.getAttribute( "nonce" )
								}, doc );
							}
						} else {
							DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc );
						}
					}
				}
			}
		}
	}

	return collection;
}

function remove( elem, selector, keepData ) {
	var node,
		nodes = selector ? jQuery.filter( selector, elem ) : elem,
		i = 0;

	for ( ; ( node = nodes[ i ] ) != null; i++ ) {
		if ( !keepData && node.nodeType === 1 ) {
			jQuery.cleanData( getAll( node ) );
		}

		if ( node.parentNode ) {
			if ( keepData && isAttached( node ) ) {
				setGlobalEval( getAll( node, "script" ) );
			}
			node.parentNode.removeChild( node );
		}
	}

	return elem;
}

jQuery.extend( {
	htmlPrefilter: function( html ) {
		return html;
	},

	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
		var i, l, srcElements, destElements,
			clone = elem.cloneNode( true ),
			inPage = isAttached( elem );

		// Fix IE cloning issues
		if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&
				!jQuery.isXMLDoc( elem ) ) {

			// We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2
			destElements = getAll( clone );
			srcElements = getAll( elem );

			for ( i = 0, l = srcElements.length; i < l; i++ ) {
				fixInput( srcElements[ i ], destElements[ i ] );
			}
		}

		// Copy the events from the original to the clone
		if ( dataAndEvents ) {
			if ( deepDataAndEvents ) {
				srcElements = srcElements || getAll( elem );
				destElements = destElements || getAll( clone );

				for ( i = 0, l = srcElements.length; i < l; i++ ) {
					cloneCopyEvent( srcElements[ i ], destElements[ i ] );
				}
			} else {
				cloneCopyEvent( elem, clone );
			}
		}

		// Preserve script evaluation history
		destElements = getAll( clone, "script" );
		if ( destElements.length > 0 ) {
			setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
		}

		// Return the cloned set
		return clone;
	},

	cleanData: function( elems ) {
		var data, elem, type,
			special = jQuery.event.special,
			i = 0;

		for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) {
			if ( acceptData( elem ) ) {
				if ( ( data = elem[ dataPriv.expando ] ) ) {
					if ( data.events ) {
						for ( type in data.events ) {
							if ( special[ type ] ) {
								jQuery.event.remove( elem, type );

							// This is a shortcut to avoid jQuery.event.remove's overhead
							} else {
								jQuery.removeEvent( elem, type, data.handle );
							}
						}
					}

					// Support: Chrome <=35 - 45+
					// Assign undefined instead of using delete, see Data#remove
					elem[ dataPriv.expando ] = undefined;
				}
				if ( elem[ dataUser.expando ] ) {

					// Support: Chrome <=35 - 45+
					// Assign undefined instead of using delete, see Data#remove
					elem[ dataUser.expando ] = undefined;
				}
			}
		}
	}
} );

jQuery.fn.extend( {
	detach: function( selector ) {
		return remove( this, selector, true );
	},

	remove: function( selector ) {
		return remove( this, selector );
	},

	text: function( value ) {
		return access( this, function( value ) {
			return value === undefined ?
				jQuery.text( this ) :
				this.empty().each( function() {
					if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
						this.textContent = value;
					}
				} );
		}, null, value, arguments.length );
	},

	append: function() {
		return domManip( this, arguments, function( elem ) {
			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
				var target = manipulationTarget( this, elem );
				target.appendChild( elem );
			}
		} );
	},

	prepend: function() {
		return domManip( this, arguments, function( elem ) {
			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
				var target = manipulationTarget( this, elem );
				target.insertBefore( elem, target.firstChild );
			}
		} );
	},

	before: function() {
		return domManip( this, arguments, function( elem ) {
			if ( this.parentNode ) {
				this.parentNode.insertBefore( elem, this );
			}
		} );
	},

	after: function() {
		return domManip( this, arguments, function( elem ) {
			if ( this.parentNode ) {
				this.parentNode.insertBefore( elem, this.nextSibling );
			}
		} );
	},

	empty: function() {
		var elem,
			i = 0;

		for ( ; ( elem = this[ i ] ) != null; i++ ) {
			if ( elem.nodeType === 1 ) {

				// Prevent memory leaks
				jQuery.cleanData( getAll( elem, false ) );

				// Remove any remaining nodes
				elem.textContent = "";
			}
		}

		return this;
	},

	clone: function( dataAndEvents, deepDataAndEvents ) {
		dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
		deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;

		return this.map( function() {
			return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
		} );
	},

	html: function( value ) {
		return access( this, function( value ) {
			var elem = this[ 0 ] || {},
				i = 0,
				l = this.length;

			if ( value === undefined && elem.nodeType === 1 ) {
				return elem.innerHTML;
			}

			// See if we can take a shortcut and just use innerHTML
			if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
				!wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {

				value = jQuery.htmlPrefilter( value );

				try {
					for ( ; i < l; i++ ) {
						elem = this[ i ] || {};

						// Remove element nodes and prevent memory leaks
						if ( elem.nodeType === 1 ) {
							jQuery.cleanData( getAll( elem, false ) );
							elem.innerHTML = value;
						}
					}

					elem = 0;

				// If using innerHTML throws an exception, use the fallback method
				} catch ( e ) {}
			}

			if ( elem ) {
				this.empty().append( value );
			}
		}, null, value, arguments.length );
	},

	replaceWith: function() {
		var ignored = [];

		// Make the changes, replacing each non-ignored context element with the new content
		return domManip( this, arguments, function( elem ) {
			var parent = this.parentNode;

			if ( jQuery.inArray( this, ignored ) < 0 ) {
				jQuery.cleanData( getAll( this ) );
				if ( parent ) {
					parent.replaceChild( elem, this );
				}
			}

		// Force callback invocation
		}, ignored );
	}
} );

jQuery.each( {
	appendTo: "append",
	prependTo: "prepend",
	insertBefore: "before",
	insertAfter: "after",
	replaceAll: "replaceWith"
}, function( name, original ) {
	jQuery.fn[ name ] = function( selector ) {
		var elems,
			ret = [],
			insert = jQuery( selector ),
			last = insert.length - 1,
			i = 0;

		for ( ; i <= last; i++ ) {
			elems = i === last ? this : this.clone( true );
			jQuery( insert[ i ] )[ original ]( elems );

			// Support: Android <=4.0 only, PhantomJS 1 only
			// .get() because push.apply(_, arraylike) throws on ancient WebKit
			push.apply( ret, elems.get() );
		}

		return this.pushStack( ret );
	};
} );
var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );

var getStyles = function( elem ) {

		// Support: IE <=11 only, Firefox <=30 (#15098, #14150)
		// IE throws on elements created in popups
		// FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
		var view = elem.ownerDocument.defaultView;

		if ( !view || !view.opener ) {
			view = window;
		}

		return view.getComputedStyle( elem );
	};

var swap = function( elem, options, callback ) {
	var ret, name,
		old = {};

	// Remember the old values, and insert the new ones
	for ( name in options ) {
		old[ name ] = elem.style[ name ];
		elem.style[ name ] = options[ name ];
	}

	ret = callback.call( elem );

	// Revert the old values
	for ( name in options ) {
		elem.style[ name ] = old[ name ];
	}

	return ret;
};


var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );



( function() {

	// Executing both pixelPosition & boxSizingReliable tests require only one layout
	// so they're executed at the same time to save the second computation.
	function computeStyleTests() {

		// This is a singleton, we need to execute it only once
		if ( !div ) {
			return;
		}

		container.style.cssText = "position:absolute;left:-11111px;width:60px;" +
			"margin-top:1px;padding:0;border:0";
		div.style.cssText =
			"position:relative;display:block;box-sizing:border-box;overflow:scroll;" +
			"margin:auto;border:1px;padding:1px;" +
			"width:60%;top:1%";
		documentElement.appendChild( container ).appendChild( div );

		var divStyle = window.getComputedStyle( div );
		pixelPositionVal = divStyle.top !== "1%";

		// Support: Android 4.0 - 4.3 only, Firefox <=3 - 44
		reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12;

		// Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3
		// Some styles come back with percentage values, even though they shouldn't
		div.style.right = "60%";
		pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36;

		// Support: IE 9 - 11 only
		// Detect misreporting of content dimensions for box-sizing:border-box elements
		boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36;

		// Support: IE 9 only
		// Detect overflow:scroll screwiness (gh-3699)
		// Support: Chrome <=64
		// Don't get tricked when zoom affects offsetWidth (gh-4029)
		div.style.position = "absolute";
		scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12;

		documentElement.removeChild( container );

		// Nullify the div so it wouldn't be stored in the memory and
		// it will also be a sign that checks already performed
		div = null;
	}

	function roundPixelMeasures( measure ) {
		return Math.round( parseFloat( measure ) );
	}

	var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal,
		reliableTrDimensionsVal, reliableMarginLeftVal,
		container = document.createElement( "div" ),
		div = document.createElement( "div" );

	// Finish early in limited (non-browser) environments
	if ( !div.style ) {
		return;
	}

	// Support: IE <=9 - 11 only
	// Style of cloned element affects source element cloned (#8908)
	div.style.backgroundClip = "content-box";
	div.cloneNode( true ).style.backgroundClip = "";
	support.clearCloneStyle = div.style.backgroundClip === "content-box";

	jQuery.extend( support, {
		boxSizingReliable: function() {
			computeStyleTests();
			return boxSizingReliableVal;
		},
		pixelBoxStyles: function() {
			computeStyleTests();
			return pixelBoxStylesVal;
		},
		pixelPosition: function() {
			computeStyleTests();
			return pixelPositionVal;
		},
		reliableMarginLeft: function() {
			computeStyleTests();
			return reliableMarginLeftVal;
		},
		scrollboxSize: function() {
			computeStyleTests();
			return scrollboxSizeVal;
		},

		// Support: IE 9 - 11+, Edge 15 - 18+
		// IE/Edge misreport `getComputedStyle` of table rows with width/height
		// set in CSS while `offset*` properties report correct values.
		// Behavior in IE 9 is more subtle than in newer versions & it passes
		// some versions of this test; make sure not to make it pass there!
		//
		// Support: Firefox 70+
		// Only Firefox includes border widths
		// in computed dimensions. (gh-4529)
		reliableTrDimensions: function() {
			var table, tr, trChild, trStyle;
			if ( reliableTrDimensionsVal == null ) {
				table = document.createElement( "table" );
				tr = document.createElement( "tr" );
				trChild = document.createElement( "div" );

				table.style.cssText = "position:absolute;left:-11111px;border-collapse:separate";
				tr.style.cssText = "border:1px solid";

				// Support: Chrome 86+
				// Height set through cssText does not get applied.
				// Computed height then comes back as 0.
				tr.style.height = "1px";
				trChild.style.height = "9px";

				// Support: Android 8 Chrome 86+
				// In our bodyBackground.html iframe,
				// display for all div elements is set to "inline",
				// which causes a problem only in Android 8 Chrome 86.
				// Ensuring the div is display: block
				// gets around this issue.
				trChild.style.display = "block";

				documentElement
					.appendChild( table )
					.appendChild( tr )
					.appendChild( trChild );

				trStyle = window.getComputedStyle( tr );
				reliableTrDimensionsVal = ( parseInt( trStyle.height, 10 ) +
					parseInt( trStyle.borderTopWidth, 10 ) +
					parseInt( trStyle.borderBottomWidth, 10 ) ) === tr.offsetHeight;

				documentElement.removeChild( table );
			}
			return reliableTrDimensionsVal;
		}
	} );
} )();


function curCSS( elem, name, computed ) {
	var width, minWidth, maxWidth, ret,

		// Support: Firefox 51+
		// Retrieving style before computed somehow
		// fixes an issue with getting wrong values
		// on detached elements
		style = elem.style;

	computed = computed || getStyles( elem );

	// getPropertyValue is needed for:
	//   .css('filter') (IE 9 only, #12537)
	//   .css('--customProperty) (#3144)
	if ( computed ) {
		ret = computed.getPropertyValue( name ) || computed[ name ];

		if ( ret === "" && !isAttached( elem ) ) {
			ret = jQuery.style( elem, name );
		}

		// A tribute to the "awesome hack by Dean Edwards"
		// Android Browser returns percentage for some values,
		// but width seems to be reliably pixels.
		// This is against the CSSOM draft spec:
		// https://drafts.csswg.org/cssom/#resolved-values
		if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) {

			// Remember the original values
			width = style.width;
			minWidth = style.minWidth;
			maxWidth = style.maxWidth;

			// Put in the new values to get a computed value out
			style.minWidth = style.maxWidth = style.width = ret;
			ret = computed.width;

			// Revert the changed values
			style.width = width;
			style.minWidth = minWidth;
			style.maxWidth = maxWidth;
		}
	}

	return ret !== undefined ?

		// Support: IE <=9 - 11 only
		// IE returns zIndex value as an integer.
		ret + "" :
		ret;
}


function addGetHookIf( conditionFn, hookFn ) {

	// Define the hook, we'll check on the first run if it's really needed.
	return {
		get: function() {
			if ( conditionFn() ) {

				// Hook not needed (or it's not possible to use it due
				// to missing dependency), remove it.
				delete this.get;
				return;
			}

			// Hook needed; redefine it so that the support test is not executed again.
			return ( this.get = hookFn ).apply( this, arguments );
		}
	};
}


var cssPrefixes = [ "Webkit", "Moz", "ms" ],
	emptyStyle = document.createElement( "div" ).style,
	vendorProps = {};

// Return a vendor-prefixed property or undefined
function vendorPropName( name ) {

	// Check for vendor prefixed names
	var capName = name[ 0 ].toUpperCase() + name.slice( 1 ),
		i = cssPrefixes.length;

	while ( i-- ) {
		name = cssPrefixes[ i ] + capName;
		if ( name in emptyStyle ) {
			return name;
		}
	}
}

// Return a potentially-mapped jQuery.cssProps or vendor prefixed property
function finalPropName( name ) {
	var final = jQuery.cssProps[ name ] || vendorProps[ name ];

	if ( final ) {
		return final;
	}
	if ( name in emptyStyle ) {
		return name;
	}
	return vendorProps[ name ] = vendorPropName( name ) || name;
}


var

	// Swappable if display is none or starts with table
	// except "table", "table-cell", or "table-caption"
	// See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
	rdisplayswap = /^(none|table(?!-c[ea]).+)/,
	rcustomProp = /^--/,
	cssShow = { position: "absolute", visibility: "hidden", display: "block" },
	cssNormalTransform = {
		letterSpacing: "0",
		fontWeight: "400"
	};

function setPositiveNumber( _elem, value, subtract ) {

	// Any relative (+/-) values have already been
	// normalized at this point
	var matches = rcssNum.exec( value );
	return matches ?

		// Guard against undefined "subtract", e.g., when used as in cssHooks
		Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) :
		value;
}

function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) {
	var i = dimension === "width" ? 1 : 0,
		extra = 0,
		delta = 0;

	// Adjustment may not be necessary
	if ( box === ( isBorderBox ? "border" : "content" ) ) {
		return 0;
	}

	for ( ; i < 4; i += 2 ) {

		// Both box models exclude margin
		if ( box === "margin" ) {
			delta += jQuery.css( elem, box + cssExpand[ i ], true, styles );
		}

		// If we get here with a content-box, we're seeking "padding" or "border" or "margin"
		if ( !isBorderBox ) {

			// Add padding
			delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );

			// For "border" or "margin", add border
			if ( box !== "padding" ) {
				delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );

			// But still keep track of it otherwise
			} else {
				extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
			}

		// If we get here with a border-box (content + padding + border), we're seeking "content" or
		// "padding" or "margin"
		} else {

			// For "content", subtract padding
			if ( box === "content" ) {
				delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
			}

			// For "content" or "padding", subtract border
			if ( box !== "margin" ) {
				delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
			}
		}
	}

	// Account for positive content-box scroll gutter when requested by providing computedVal
	if ( !isBorderBox && computedVal >= 0 ) {

		// offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border
		// Assuming integer scroll gutter, subtract the rest and round down
		delta += Math.max( 0, Math.ceil(
			elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
			computedVal -
			delta -
			extra -
			0.5

		// If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter
		// Use an explicit zero to avoid NaN (gh-3964)
		) ) || 0;
	}

	return delta;
}

function getWidthOrHeight( elem, dimension, extra ) {

	// Start with computed style
	var styles = getStyles( elem ),

		// To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322).
		// Fake content-box until we know it's needed to know the true value.
		boxSizingNeeded = !support.boxSizingReliable() || extra,
		isBorderBox = boxSizingNeeded &&
			jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
		valueIsBorderBox = isBorderBox,

		val = curCSS( elem, dimension, styles ),
		offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 );

	// Support: Firefox <=54
	// Return a confounding non-pixel value or feign ignorance, as appropriate.
	if ( rnumnonpx.test( val ) ) {
		if ( !extra ) {
			return val;
		}
		val = "auto";
	}


	// Support: IE 9 - 11 only
	// Use offsetWidth/offsetHeight for when box sizing is unreliable.
	// In those cases, the computed value can be trusted to be border-box.
	if ( ( !support.boxSizingReliable() && isBorderBox ||

		// Support: IE 10 - 11+, Edge 15 - 18+
		// IE/Edge misreport `getComputedStyle` of table rows with width/height
		// set in CSS while `offset*` properties report correct values.
		// Interestingly, in some cases IE 9 doesn't suffer from this issue.
		!support.reliableTrDimensions() && nodeName( elem, "tr" ) ||

		// Fall back to offsetWidth/offsetHeight when value is "auto"
		// This happens for inline elements with no explicit setting (gh-3571)
		val === "auto" ||

		// Support: Android <=4.1 - 4.3 only
		// Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602)
		!parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) &&

		// Make sure the element is visible & connected
		elem.getClientRects().length ) {

		isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box";

		// Where available, offsetWidth/offsetHeight approximate border box dimensions.
		// Where not available (e.g., SVG), assume unreliable box-sizing and interpret the
		// retrieved value as a content box dimension.
		valueIsBorderBox = offsetProp in elem;
		if ( valueIsBorderBox ) {
			val = elem[ offsetProp ];
		}
	}

	// Normalize "" and auto
	val = parseFloat( val ) || 0;

	// Adjust for the element's box model
	return ( val +
		boxModelAdjustment(
			elem,
			dimension,
			extra || ( isBorderBox ? "border" : "content" ),
			valueIsBorderBox,
			styles,

			// Provide the current computed size to request scroll gutter calculation (gh-3589)
			val
		)
	) + "px";
}

jQuery.extend( {

	// Add in style property hooks for overriding the default
	// behavior of getting and setting a style property
	cssHooks: {
		opacity: {
			get: function( elem, computed ) {
				if ( computed ) {

					// We should always get a number back from opacity
					var ret = curCSS( elem, "opacity" );
					return ret === "" ? "1" : ret;
				}
			}
		}
	},

	// Don't automatically add "px" to these possibly-unitless properties
	cssNumber: {
		"animationIterationCount": true,
		"columnCount": true,
		"fillOpacity": true,
		"flexGrow": true,
		"flexShrink": true,
		"fontWeight": true,
		"gridArea": true,
		"gridColumn": true,
		"gridColumnEnd": true,
		"gridColumnStart": true,
		"gridRow": true,
		"gridRowEnd": true,
		"gridRowStart": true,
		"lineHeight": true,
		"opacity": true,
		"order": true,
		"orphans": true,
		"widows": true,
		"zIndex": true,
		"zoom": true
	},

	// Add in properties whose names you wish to fix before
	// setting or getting the value
	cssProps: {},

	// Get and set the style property on a DOM Node
	style: function( elem, name, value, extra ) {

		// Don't set styles on text and comment nodes
		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
			return;
		}

		// Make sure that we're working with the right name
		var ret, type, hooks,
			origName = camelCase( name ),
			isCustomProp = rcustomProp.test( name ),
			style = elem.style;

		// Make sure that we're working with the right name. We don't
		// want to query the value if it is a CSS custom property
		// since they are user-defined.
		if ( !isCustomProp ) {
			name = finalPropName( origName );
		}

		// Gets hook for the prefixed version, then unprefixed version
		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];

		// Check if we're setting a value
		if ( value !== undefined ) {
			type = typeof value;

			// Convert "+=" or "-=" to relative numbers (#7345)
			if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
				value = adjustCSS( elem, name, ret );

				// Fixes bug #9237
				type = "number";
			}

			// Make sure that null and NaN values aren't set (#7116)
			if ( value == null || value !== value ) {
				return;
			}

			// If a number was passed in, add the unit (except for certain CSS properties)
			// The isCustomProp check can be removed in jQuery 4.0 when we only auto-append
			// "px" to a few hardcoded values.
			if ( type === "number" && !isCustomProp ) {
				value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" );
			}

			// background-* props affect original clone's values
			if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
				style[ name ] = "inherit";
			}

			// If a hook was provided, use that value, otherwise just set the specified value
			if ( !hooks || !( "set" in hooks ) ||
				( value = hooks.set( elem, value, extra ) ) !== undefined ) {

				if ( isCustomProp ) {
					style.setProperty( name, value );
				} else {
					style[ name ] = value;
				}
			}

		} else {

			// If a hook was provided get the non-computed value from there
			if ( hooks && "get" in hooks &&
				( ret = hooks.get( elem, false, extra ) ) !== undefined ) {

				return ret;
			}

			// Otherwise just get the value from the style object
			return style[ name ];
		}
	},

	css: function( elem, name, extra, styles ) {
		var val, num, hooks,
			origName = camelCase( name ),
			isCustomProp = rcustomProp.test( name );

		// Make sure that we're working with the right name. We don't
		// want to modify the value if it is a CSS custom property
		// since they are user-defined.
		if ( !isCustomProp ) {
			name = finalPropName( origName );
		}

		// Try prefixed name followed by the unprefixed name
		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];

		// If a hook was provided get the computed value from there
		if ( hooks && "get" in hooks ) {
			val = hooks.get( elem, true, extra );
		}

		// Otherwise, if a way to get the computed value exists, use that
		if ( val === undefined ) {
			val = curCSS( elem, name, styles );
		}

		// Convert "normal" to computed value
		if ( val === "normal" && name in cssNormalTransform ) {
			val = cssNormalTransform[ name ];
		}

		// Make numeric if forced or a qualifier was provided and val looks numeric
		if ( extra === "" || extra ) {
			num = parseFloat( val );
			return extra === true || isFinite( num ) ? num || 0 : val;
		}

		return val;
	}
} );

jQuery.each( [ "height", "width" ], function( _i, dimension ) {
	jQuery.cssHooks[ dimension ] = {
		get: function( elem, computed, extra ) {
			if ( computed ) {

				// Certain elements can have dimension info if we invisibly show them
				// but it must have a current display style that would benefit
				return rdisplayswap.test( jQuery.css( elem, "display" ) ) &&

					// Support: Safari 8+
					// Table columns in Safari have non-zero offsetWidth & zero
					// getBoundingClientRect().width unless display is changed.
					// Support: IE <=11 only
					// Running getBoundingClientRect on a disconnected node
					// in IE throws an error.
					( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ?
					swap( elem, cssShow, function() {
						return getWidthOrHeight( elem, dimension, extra );
					} ) :
					getWidthOrHeight( elem, dimension, extra );
			}
		},

		set: function( elem, value, extra ) {
			var matches,
				styles = getStyles( elem ),

				// Only read styles.position if the test has a chance to fail
				// to avoid forcing a reflow.
				scrollboxSizeBuggy = !support.scrollboxSize() &&
					styles.position === "absolute",

				// To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991)
				boxSizingNeeded = scrollboxSizeBuggy || extra,
				isBorderBox = boxSizingNeeded &&
					jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
				subtract = extra ?
					boxModelAdjustment(
						elem,
						dimension,
						extra,
						isBorderBox,
						styles
					) :
					0;

			// Account for unreliable border-box dimensions by comparing offset* to computed and
			// faking a content-box to get border and padding (gh-3699)
			if ( isBorderBox && scrollboxSizeBuggy ) {
				subtract -= Math.ceil(
					elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
					parseFloat( styles[ dimension ] ) -
					boxModelAdjustment( elem, dimension, "border", false, styles ) -
					0.5
				);
			}

			// Convert to pixels if value adjustment is needed
			if ( subtract && ( matches = rcssNum.exec( value ) ) &&
				( matches[ 3 ] || "px" ) !== "px" ) {

				elem.style[ dimension ] = value;
				value = jQuery.css( elem, dimension );
			}

			return setPositiveNumber( elem, value, subtract );
		}
	};
} );

jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft,
	function( elem, computed ) {
		if ( computed ) {
			return ( parseFloat( curCSS( elem, "marginLeft" ) ) ||
				elem.getBoundingClientRect().left -
					swap( elem, { marginLeft: 0 }, function() {
						return elem.getBoundingClientRect().left;
					} )
			) + "px";
		}
	}
);

// These hooks are used by animate to expand properties
jQuery.each( {
	margin: "",
	padding: "",
	border: "Width"
}, function( prefix, suffix ) {
	jQuery.cssHooks[ prefix + suffix ] = {
		expand: function( value ) {
			var i = 0,
				expanded = {},

				// Assumes a single number if not a string
				parts = typeof value === "string" ? value.split( " " ) : [ value ];

			for ( ; i < 4; i++ ) {
				expanded[ prefix + cssExpand[ i ] + suffix ] =
					parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
			}

			return expanded;
		}
	};

	if ( prefix !== "margin" ) {
		jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
	}
} );

jQuery.fn.extend( {
	css: function( name, value ) {
		return access( this, function( elem, name, value ) {
			var styles, len,
				map = {},
				i = 0;

			if ( Array.isArray( name ) ) {
				styles = getStyles( elem );
				len = name.length;

				for ( ; i < len; i++ ) {
					map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
				}

				return map;
			}

			return value !== undefined ?
				jQuery.style( elem, name, value ) :
				jQuery.css( elem, name );
		}, name, value, arguments.length > 1 );
	}
} );


function Tween( elem, options, prop, end, easing ) {
	return new Tween.prototype.init( elem, options, prop, end, easing );
}
jQuery.Tween = Tween;

Tween.prototype = {
	constructor: Tween,
	init: function( elem, options, prop, end, easing, unit ) {
		this.elem = elem;
		this.prop = prop;
		this.easing = easing || jQuery.easing._default;
		this.options = options;
		this.start = this.now = this.cur();
		this.end = end;
		this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
	},
	cur: function() {
		var hooks = Tween.propHooks[ this.prop ];

		return hooks && hooks.get ?
			hooks.get( this ) :
			Tween.propHooks._default.get( this );
	},
	run: function( percent ) {
		var eased,
			hooks = Tween.propHooks[ this.prop ];

		if ( this.options.duration ) {
			this.pos = eased = jQuery.easing[ this.easing ](
				percent, this.options.duration * percent, 0, 1, this.options.duration
			);
		} else {
			this.pos = eased = percent;
		}
		this.now = ( this.end - this.start ) * eased + this.start;

		if ( this.options.step ) {
			this.options.step.call( this.elem, this.now, this );
		}

		if ( hooks && hooks.set ) {
			hooks.set( this );
		} else {
			Tween.propHooks._default.set( this );
		}
		return this;
	}
};

Tween.prototype.init.prototype = Tween.prototype;

Tween.propHooks = {
	_default: {
		get: function( tween ) {
			var result;

			// Use a property on the element directly when it is not a DOM element,
			// or when there is no matching style property that exists.
			if ( tween.elem.nodeType !== 1 ||
				tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) {
				return tween.elem[ tween.prop ];
			}

			// Passing an empty string as a 3rd parameter to .css will automatically
			// attempt a parseFloat and fallback to a string if the parse fails.
			// Simple values such as "10px" are parsed to Float;
			// complex values such as "rotate(1rad)" are returned as-is.
			result = jQuery.css( tween.elem, tween.prop, "" );

			// Empty strings, null, undefined and "auto" are converted to 0.
			return !result || result === "auto" ? 0 : result;
		},
		set: function( tween ) {

			// Use step hook for back compat.
			// Use cssHook if its there.
			// Use .style if available and use plain properties where available.
			if ( jQuery.fx.step[ tween.prop ] ) {
				jQuery.fx.step[ tween.prop ]( tween );
			} else if ( tween.elem.nodeType === 1 && (
				jQuery.cssHooks[ tween.prop ] ||
					tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) {
				jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
			} else {
				tween.elem[ tween.prop ] = tween.now;
			}
		}
	}
};

// Support: IE <=9 only
// Panic based approach to setting things on disconnected nodes
Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
	set: function( tween ) {
		if ( tween.elem.nodeType && tween.elem.parentNode ) {
			tween.elem[ tween.prop ] = tween.now;
		}
	}
};

jQuery.easing = {
	linear: function( p ) {
		return p;
	},
	swing: function( p ) {
		return 0.5 - Math.cos( p * Math.PI ) / 2;
	},
	_default: "swing"
};

jQuery.fx = Tween.prototype.init;

// Back compat <1.8 extension point
jQuery.fx.step = {};




var
	fxNow, inProgress,
	rfxtypes = /^(?:toggle|show|hide)$/,
	rrun = /queueHooks$/;

function schedule() {
	if ( inProgress ) {
		if ( document.hidden === false && window.requestAnimationFrame ) {
			window.requestAnimationFrame( schedule );
		} else {
			window.setTimeout( schedule, jQuery.fx.interval );
		}

		jQuery.fx.tick();
	}
}

// Animations created synchronously will run synchronously
function createFxNow() {
	window.setTimeout( function() {
		fxNow = undefined;
	} );
	return ( fxNow = Date.now() );
}

// Generate parameters to create a standard animation
function genFx( type, includeWidth ) {
	var which,
		i = 0,
		attrs = { height: type };

	// If we include width, step value is 1 to do all cssExpand values,
	// otherwise step value is 2 to skip over Left and Right
	includeWidth = includeWidth ? 1 : 0;
	for ( ; i < 4; i += 2 - includeWidth ) {
		which = cssExpand[ i ];
		attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
	}

	if ( includeWidth ) {
		attrs.opacity = attrs.width = type;
	}

	return attrs;
}

function createTween( value, prop, animation ) {
	var tween,
		collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ),
		index = 0,
		length = collection.length;
	for ( ; index < length; index++ ) {
		if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) {

			// We're done with this property
			return tween;
		}
	}
}

function defaultPrefilter( elem, props, opts ) {
	var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display,
		isBox = "width" in props || "height" in props,
		anim = this,
		orig = {},
		style = elem.style,
		hidden = elem.nodeType && isHiddenWithinTree( elem ),
		dataShow = dataPriv.get( elem, "fxshow" );

	// Queue-skipping animations hijack the fx hooks
	if ( !opts.queue ) {
		hooks = jQuery._queueHooks( elem, "fx" );
		if ( hooks.unqueued == null ) {
			hooks.unqueued = 0;
			oldfire = hooks.empty.fire;
			hooks.empty.fire = function() {
				if ( !hooks.unqueued ) {
					oldfire();
				}
			};
		}
		hooks.unqueued++;

		anim.always( function() {

			// Ensure the complete handler is called before this completes
			anim.always( function() {
				hooks.unqueued--;
				if ( !jQuery.queue( elem, "fx" ).length ) {
					hooks.empty.fire();
				}
			} );
		} );
	}

	// Detect show/hide animations
	for ( prop in props ) {
		value = props[ prop ];
		if ( rfxtypes.test( value ) ) {
			delete props[ prop ];
			toggle = toggle || value === "toggle";
			if ( value === ( hidden ? "hide" : "show" ) ) {

				// Pretend to be hidden if this is a "show" and
				// there is still data from a stopped show/hide
				if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
					hidden = true;

				// Ignore all other no-op show/hide data
				} else {
					continue;
				}
			}
			orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
		}
	}

	// Bail out if this is a no-op like .hide().hide()
	propTween = !jQuery.isEmptyObject( props );
	if ( !propTween && jQuery.isEmptyObject( orig ) ) {
		return;
	}

	// Restrict "overflow" and "display" styles during box animations
	if ( isBox && elem.nodeType === 1 ) {

		// Support: IE <=9 - 11, Edge 12 - 15
		// Record all 3 overflow attributes because IE does not infer the shorthand
		// from identically-valued overflowX and overflowY and Edge just mirrors
		// the overflowX value there.
		opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];

		// Identify a display type, preferring old show/hide data over the CSS cascade
		restoreDisplay = dataShow && dataShow.display;
		if ( restoreDisplay == null ) {
			restoreDisplay = dataPriv.get( elem, "display" );
		}
		display = jQuery.css( elem, "display" );
		if ( display === "none" ) {
			if ( restoreDisplay ) {
				display = restoreDisplay;
			} else {

				// Get nonempty value(s) by temporarily forcing visibility
				showHide( [ elem ], true );
				restoreDisplay = elem.style.display || restoreDisplay;
				display = jQuery.css( elem, "display" );
				showHide( [ elem ] );
			}
		}

		// Animate inline elements as inline-block
		if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) {
			if ( jQuery.css( elem, "float" ) === "none" ) {

				// Restore the original display value at the end of pure show/hide animations
				if ( !propTween ) {
					anim.done( function() {
						style.display = restoreDisplay;
					} );
					if ( restoreDisplay == null ) {
						display = style.display;
						restoreDisplay = display === "none" ? "" : display;
					}
				}
				style.display = "inline-block";
			}
		}
	}

	if ( opts.overflow ) {
		style.overflow = "hidden";
		anim.always( function() {
			style.overflow = opts.overflow[ 0 ];
			style.overflowX = opts.overflow[ 1 ];
			style.overflowY = opts.overflow[ 2 ];
		} );
	}

	// Implement show/hide animations
	propTween = false;
	for ( prop in orig ) {

		// General show/hide setup for this element animation
		if ( !propTween ) {
			if ( dataShow ) {
				if ( "hidden" in dataShow ) {
					hidden = dataShow.hidden;
				}
			} else {
				dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } );
			}

			// Store hidden/visible for toggle so `.stop().toggle()` "reverses"
			if ( toggle ) {
				dataShow.hidden = !hidden;
			}

			// Show elements before animating them
			if ( hidden ) {
				showHide( [ elem ], true );
			}

			/* eslint-disable no-loop-func */

			anim.done( function() {

				/* eslint-enable no-loop-func */

				// The final step of a "hide" animation is actually hiding the element
				if ( !hidden ) {
					showHide( [ elem ] );
				}
				dataPriv.remove( elem, "fxshow" );
				for ( prop in orig ) {
					jQuery.style( elem, prop, orig[ prop ] );
				}
			} );
		}

		// Per-property setup
		propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
		if ( !( prop in dataShow ) ) {
			dataShow[ prop ] = propTween.start;
			if ( hidden ) {
				propTween.end = propTween.start;
				propTween.start = 0;
			}
		}
	}
}

function propFilter( props, specialEasing ) {
	var index, name, easing, value, hooks;

	// camelCase, specialEasing and expand cssHook pass
	for ( index in props ) {
		name = camelCase( index );
		easing = specialEasing[ name ];
		value = props[ index ];
		if ( Array.isArray( value ) ) {
			easing = value[ 1 ];
			value = props[ index ] = value[ 0 ];
		}

		if ( index !== name ) {
			props[ name ] = value;
			delete props[ index ];
		}

		hooks = jQuery.cssHooks[ name ];
		if ( hooks && "expand" in hooks ) {
			value = hooks.expand( value );
			delete props[ name ];

			// Not quite $.extend, this won't overwrite existing keys.
			// Reusing 'index' because we have the correct "name"
			for ( index in value ) {
				if ( !( index in props ) ) {
					props[ index ] = value[ index ];
					specialEasing[ index ] = easing;
				}
			}
		} else {
			specialEasing[ name ] = easing;
		}
	}
}

function Animation( elem, properties, options ) {
	var result,
		stopped,
		index = 0,
		length = Animation.prefilters.length,
		deferred = jQuery.Deferred().always( function() {

			// Don't match elem in the :animated selector
			delete tick.elem;
		} ),
		tick = function() {
			if ( stopped ) {
				return false;
			}
			var currentTime = fxNow || createFxNow(),
				remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),

				// Support: Android 2.3 only
				// Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497)
				temp = remaining / animation.duration || 0,
				percent = 1 - temp,
				index = 0,
				length = animation.tweens.length;

			for ( ; index < length; index++ ) {
				animation.tweens[ index ].run( percent );
			}

			deferred.notifyWith( elem, [ animation, percent, remaining ] );

			// If there's more to do, yield
			if ( percent < 1 && length ) {
				return remaining;
			}

			// If this was an empty animation, synthesize a final progress notification
			if ( !length ) {
				deferred.notifyWith( elem, [ animation, 1, 0 ] );
			}

			// Resolve the animation and report its conclusion
			deferred.resolveWith( elem, [ animation ] );
			return false;
		},
		animation = deferred.promise( {
			elem: elem,
			props: jQuery.extend( {}, properties ),
			opts: jQuery.extend( true, {
				specialEasing: {},
				easing: jQuery.easing._default
			}, options ),
			originalProperties: properties,
			originalOptions: options,
			startTime: fxNow || createFxNow(),
			duration: options.duration,
			tweens: [],
			createTween: function( prop, end ) {
				var tween = jQuery.Tween( elem, animation.opts, prop, end,
					animation.opts.specialEasing[ prop ] || animation.opts.easing );
				animation.tweens.push( tween );
				return tween;
			},
			stop: function( gotoEnd ) {
				var index = 0,

					// If we are going to the end, we want to run all the tweens
					// otherwise we skip this part
					length = gotoEnd ? animation.tweens.length : 0;
				if ( stopped ) {
					return this;
				}
				stopped = true;
				for ( ; index < length; index++ ) {
					animation.tweens[ index ].run( 1 );
				}

				// Resolve when we played the last frame; otherwise, reject
				if ( gotoEnd ) {
					deferred.notifyWith( elem, [ animation, 1, 0 ] );
					deferred.resolveWith( elem, [ animation, gotoEnd ] );
				} else {
					deferred.rejectWith( elem, [ animation, gotoEnd ] );
				}
				return this;
			}
		} ),
		props = animation.props;

	propFilter( props, animation.opts.specialEasing );

	for ( ; index < length; index++ ) {
		result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts );
		if ( result ) {
			if ( isFunction( result.stop ) ) {
				jQuery._queueHooks( animation.elem, animation.opts.queue ).stop =
					result.stop.bind( result );
			}
			return result;
		}
	}

	jQuery.map( props, createTween, animation );

	if ( isFunction( animation.opts.start ) ) {
		animation.opts.start.call( elem, animation );
	}

	// Attach callbacks from options
	animation
		.progress( animation.opts.progress )
		.done( animation.opts.done, animation.opts.complete )
		.fail( animation.opts.fail )
		.always( animation.opts.always );

	jQuery.fx.timer(
		jQuery.extend( tick, {
			elem: elem,
			anim: animation,
			queue: animation.opts.queue
		} )
	);

	return animation;
}

jQuery.Animation = jQuery.extend( Animation, {

	tweeners: {
		"*": [ function( prop, value ) {
			var tween = this.createTween( prop, value );
			adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween );
			return tween;
		} ]
	},

	tweener: function( props, callback ) {
		if ( isFunction( props ) ) {
			callback = props;
			props = [ "*" ];
		} else {
			props = props.match( rnothtmlwhite );
		}

		var prop,
			index = 0,
			length = props.length;

		for ( ; index < length; index++ ) {
			prop = props[ index ];
			Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || [];
			Animation.tweeners[ prop ].unshift( callback );
		}
	},

	prefilters: [ defaultPrefilter ],

	prefilter: function( callback, prepend ) {
		if ( prepend ) {
			Animation.prefilters.unshift( callback );
		} else {
			Animation.prefilters.push( callback );
		}
	}
} );

jQuery.speed = function( speed, easing, fn ) {
	var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
		complete: fn || !fn && easing ||
			isFunction( speed ) && speed,
		duration: speed,
		easing: fn && easing || easing && !isFunction( easing ) && easing
	};

	// Go to the end state if fx are off
	if ( jQuery.fx.off ) {
		opt.duration = 0;

	} else {
		if ( typeof opt.duration !== "number" ) {
			if ( opt.duration in jQuery.fx.speeds ) {
				opt.duration = jQuery.fx.speeds[ opt.duration ];

			} else {
				opt.duration = jQuery.fx.speeds._default;
			}
		}
	}

	// Normalize opt.queue - true/undefined/null -> "fx"
	if ( opt.queue == null || opt.queue === true ) {
		opt.queue = "fx";
	}

	// Queueing
	opt.old = opt.complete;

	opt.complete = function() {
		if ( isFunction( opt.old ) ) {
			opt.old.call( this );
		}

		if ( opt.queue ) {
			jQuery.dequeue( this, opt.queue );
		}
	};

	return opt;
};

jQuery.fn.extend( {
	fadeTo: function( speed, to, easing, callback ) {

		// Show any hidden elements after setting opacity to 0
		return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show()

			// Animate to the value specified
			.end().animate( { opacity: to }, speed, easing, callback );
	},
	animate: function( prop, speed, easing, callback ) {
		var empty = jQuery.isEmptyObject( prop ),
			optall = jQuery.speed( speed, easing, callback ),
			doAnimation = function() {

				// Operate on a copy of prop so per-property easing won't be lost
				var anim = Animation( this, jQuery.extend( {}, prop ), optall );

				// Empty animations, or finishing resolves immediately
				if ( empty || dataPriv.get( this, "finish" ) ) {
					anim.stop( true );
				}
			};

		doAnimation.finish = doAnimation;

		return empty || optall.queue === false ?
			this.each( doAnimation ) :
			this.queue( optall.queue, doAnimation );
	},
	stop: function( type, clearQueue, gotoEnd ) {
		var stopQueue = function( hooks ) {
			var stop = hooks.stop;
			delete hooks.stop;
			stop( gotoEnd );
		};

		if ( typeof type !== "string" ) {
			gotoEnd = clearQueue;
			clearQueue = type;
			type = undefined;
		}
		if ( clearQueue ) {
			this.queue( type || "fx", [] );
		}

		return this.each( function() {
			var dequeue = true,
				index = type != null && type + "queueHooks",
				timers = jQuery.timers,
				data = dataPriv.get( this );

			if ( index ) {
				if ( data[ index ] && data[ index ].stop ) {
					stopQueue( data[ index ] );
				}
			} else {
				for ( index in data ) {
					if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
						stopQueue( data[ index ] );
					}
				}
			}

			for ( index = timers.length; index--; ) {
				if ( timers[ index ].elem === this &&
					( type == null || timers[ index ].queue === type ) ) {

					timers[ index ].anim.stop( gotoEnd );
					dequeue = false;
					timers.splice( index, 1 );
				}
			}

			// Start the next in the queue if the last step wasn't forced.
			// Timers currently will call their complete callbacks, which
			// will dequeue but only if they were gotoEnd.
			if ( dequeue || !gotoEnd ) {
				jQuery.dequeue( this, type );
			}
		} );
	},
	finish: function( type ) {
		if ( type !== false ) {
			type = type || "fx";
		}
		return this.each( function() {
			var index,
				data = dataPriv.get( this ),
				queue = data[ type + "queue" ],
				hooks = data[ type + "queueHooks" ],
				timers = jQuery.timers,
				length = queue ? queue.length : 0;

			// Enable finishing flag on private data
			data.finish = true;

			// Empty the queue first
			jQuery.queue( this, type, [] );

			if ( hooks && hooks.stop ) {
				hooks.stop.call( this, true );
			}

			// Look for any active animations, and finish them
			for ( index = timers.length; index--; ) {
				if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
					timers[ index ].anim.stop( true );
					timers.splice( index, 1 );
				}
			}

			// Look for any animations in the old queue and finish them
			for ( index = 0; index < length; index++ ) {
				if ( queue[ index ] && queue[ index ].finish ) {
					queue[ index ].finish.call( this );
				}
			}

			// Turn off finishing flag
			delete data.finish;
		} );
	}
} );

jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) {
	var cssFn = jQuery.fn[ name ];
	jQuery.fn[ name ] = function( speed, easing, callback ) {
		return speed == null || typeof speed === "boolean" ?
			cssFn.apply( this, arguments ) :
			this.animate( genFx( name, true ), speed, easing, callback );
	};
} );

// Generate shortcuts for custom animations
jQuery.each( {
	slideDown: genFx( "show" ),
	slideUp: genFx( "hide" ),
	slideToggle: genFx( "toggle" ),
	fadeIn: { opacity: "show" },
	fadeOut: { opacity: "hide" },
	fadeToggle: { opacity: "toggle" }
}, function( name, props ) {
	jQuery.fn[ name ] = function( speed, easing, callback ) {
		return this.animate( props, speed, easing, callback );
	};
} );

jQuery.timers = [];
jQuery.fx.tick = function() {
	var timer,
		i = 0,
		timers = jQuery.timers;

	fxNow = Date.now();

	for ( ; i < timers.length; i++ ) {
		timer = timers[ i ];

		// Run the timer and safely remove it when done (allowing for external removal)
		if ( !timer() && timers[ i ] === timer ) {
			timers.splice( i--, 1 );
		}
	}

	if ( !timers.length ) {
		jQuery.fx.stop();
	}
	fxNow = undefined;
};

jQuery.fx.timer = function( timer ) {
	jQuery.timers.push( timer );
	jQuery.fx.start();
};

jQuery.fx.interval = 13;
jQuery.fx.start = function() {
	if ( inProgress ) {
		return;
	}

	inProgress = true;
	schedule();
};

jQuery.fx.stop = function() {
	inProgress = null;
};

jQuery.fx.speeds = {
	slow: 600,
	fast: 200,

	// Default speed
	_default: 400
};


// Based off of the plugin by Clint Helfers, with permission.
// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/
jQuery.fn.delay = function( time, type ) {
	time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
	type = type || "fx";

	return this.queue( type, function( next, hooks ) {
		var timeout = window.setTimeout( next, time );
		hooks.stop = function() {
			window.clearTimeout( timeout );
		};
	} );
};


( function() {
	var input = document.createElement( "input" ),
		select = document.createElement( "select" ),
		opt = select.appendChild( document.createElement( "option" ) );

	input.type = "checkbox";

	// Support: Android <=4.3 only
	// Default value for a checkbox should be "on"
	support.checkOn = input.value !== "";

	// Support: IE <=11 only
	// Must access selectedIndex to make default options select
	support.optSelected = opt.selected;

	// Support: IE <=11 only
	// An input loses its value after becoming a radio
	input = document.createElement( "input" );
	input.value = "t";
	input.type = "radio";
	support.radioValue = input.value === "t";
} )();


var boolHook,
	attrHandle = jQuery.expr.attrHandle;

jQuery.fn.extend( {
	attr: function( name, value ) {
		return access( this, jQuery.attr, name, value, arguments.length > 1 );
	},

	removeAttr: function( name ) {
		return this.each( function() {
			jQuery.removeAttr( this, name );
		} );
	}
} );

jQuery.extend( {
	attr: function( elem, name, value ) {
		var ret, hooks,
			nType = elem.nodeType;

		// Don't get/set attributes on text, comment and attribute nodes
		if ( nType === 3 || nType === 8 || nType === 2 ) {
			return;
		}

		// Fallback to prop when attributes are not supported
		if ( typeof elem.getAttribute === "undefined" ) {
			return jQuery.prop( elem, name, value );
		}

		// Attribute hooks are determined by the lowercase version
		// Grab necessary hook if one is defined
		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
			hooks = jQuery.attrHooks[ name.toLowerCase() ] ||
				( jQuery.expr.match.bool.test( name ) ? boolHook : undefined );
		}

		if ( value !== undefined ) {
			if ( value === null ) {
				jQuery.removeAttr( elem, name );
				return;
			}

			if ( hooks && "set" in hooks &&
				( ret = hooks.set( elem, value, name ) ) !== undefined ) {
				return ret;
			}

			elem.setAttribute( name, value + "" );
			return value;
		}

		if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
			return ret;
		}

		ret = jQuery.find.attr( elem, name );

		// Non-existent attributes return null, we normalize to undefined
		return ret == null ? undefined : ret;
	},

	attrHooks: {
		type: {
			set: function( elem, value ) {
				if ( !support.radioValue && value === "radio" &&
					nodeName( elem, "input" ) ) {
					var val = elem.value;
					elem.setAttribute( "type", value );
					if ( val ) {
						elem.value = val;
					}
					return value;
				}
			}
		}
	},

	removeAttr: function( elem, value ) {
		var name,
			i = 0,

			// Attribute names can contain non-HTML whitespace characters
			// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
			attrNames = value && value.match( rnothtmlwhite );

		if ( attrNames && elem.nodeType === 1 ) {
			while ( ( name = attrNames[ i++ ] ) ) {
				elem.removeAttribute( name );
			}
		}
	}
} );

// Hooks for boolean attributes
boolHook = {
	set: function( elem, value, name ) {
		if ( value === false ) {

			// Remove boolean attributes when set to false
			jQuery.removeAttr( elem, name );
		} else {
			elem.setAttribute( name, name );
		}
		return name;
	}
};

jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) {
	var getter = attrHandle[ name ] || jQuery.find.attr;

	attrHandle[ name ] = function( elem, name, isXML ) {
		var ret, handle,
			lowercaseName = name.toLowerCase();

		if ( !isXML ) {

			// Avoid an infinite loop by temporarily removing this function from the getter
			handle = attrHandle[ lowercaseName ];
			attrHandle[ lowercaseName ] = ret;
			ret = getter( elem, name, isXML ) != null ?
				lowercaseName :
				null;
			attrHandle[ lowercaseName ] = handle;
		}
		return ret;
	};
} );




var rfocusable = /^(?:input|select|textarea|button)$/i,
	rclickable = /^(?:a|area)$/i;

jQuery.fn.extend( {
	prop: function( name, value ) {
		return access( this, jQuery.prop, name, value, arguments.length > 1 );
	},

	removeProp: function( name ) {
		return this.each( function() {
			delete this[ jQuery.propFix[ name ] || name ];
		} );
	}
} );

jQuery.extend( {
	prop: function( elem, name, value ) {
		var ret, hooks,
			nType = elem.nodeType;

		// Don't get/set properties on text, comment and attribute nodes
		if ( nType === 3 || nType === 8 || nType === 2 ) {
			return;
		}

		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {

			// Fix name and attach hooks
			name = jQuery.propFix[ name ] || name;
			hooks = jQuery.propHooks[ name ];
		}

		if ( value !== undefined ) {
			if ( hooks && "set" in hooks &&
				( ret = hooks.set( elem, value, name ) ) !== undefined ) {
				return ret;
			}

			return ( elem[ name ] = value );
		}

		if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
			return ret;
		}

		return elem[ name ];
	},

	propHooks: {
		tabIndex: {
			get: function( elem ) {

				// Support: IE <=9 - 11 only
				// elem.tabIndex doesn't always return the
				// correct value when it hasn't been explicitly set
				// https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
				// Use proper attribute retrieval(#12072)
				var tabindex = jQuery.find.attr( elem, "tabindex" );

				if ( tabindex ) {
					return parseInt( tabindex, 10 );
				}

				if (
					rfocusable.test( elem.nodeName ) ||
					rclickable.test( elem.nodeName ) &&
					elem.href
				) {
					return 0;
				}

				return -1;
			}
		}
	},

	propFix: {
		"for": "htmlFor",
		"class": "className"
	}
} );

// Support: IE <=11 only
// Accessing the selectedIndex property
// forces the browser to respect setting selected
// on the option
// The getter ensures a default option is selected
// when in an optgroup
// eslint rule "no-unused-expressions" is disabled for this code
// since it considers such accessions noop
if ( !support.optSelected ) {
	jQuery.propHooks.selected = {
		get: function( elem ) {

			/* eslint no-unused-expressions: "off" */

			var parent = elem.parentNode;
			if ( parent && parent.parentNode ) {
				parent.parentNode.selectedIndex;
			}
			return null;
		},
		set: function( elem ) {

			/* eslint no-unused-expressions: "off" */

			var parent = elem.parentNode;
			if ( parent ) {
				parent.selectedIndex;

				if ( parent.parentNode ) {
					parent.parentNode.selectedIndex;
				}
			}
		}
	};
}

jQuery.each( [
	"tabIndex",
	"readOnly",
	"maxLength",
	"cellSpacing",
	"cellPadding",
	"rowSpan",
	"colSpan",
	"useMap",
	"frameBorder",
	"contentEditable"
], function() {
	jQuery.propFix[ this.toLowerCase() ] = this;
} );




	// Strip and collapse whitespace according to HTML spec
	// https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace
	function stripAndCollapse( value ) {
		var tokens = value.match( rnothtmlwhite ) || [];
		return tokens.join( " " );
	}


function getClass( elem ) {
	return elem.getAttribute && elem.getAttribute( "class" ) || "";
}

function classesToArray( value ) {
	if ( Array.isArray( value ) ) {
		return value;
	}
	if ( typeof value === "string" ) {
		return value.match( rnothtmlwhite ) || [];
	}
	return [];
}

jQuery.fn.extend( {
	addClass: function( value ) {
		var classes, elem, cur, curValue, clazz, j, finalValue,
			i = 0;

		if ( isFunction( value ) ) {
			return this.each( function( j ) {
				jQuery( this ).addClass( value.call( this, j, getClass( this ) ) );
			} );
		}

		classes = classesToArray( value );

		if ( classes.length ) {
			while ( ( elem = this[ i++ ] ) ) {
				curValue = getClass( elem );
				cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );

				if ( cur ) {
					j = 0;
					while ( ( clazz = classes[ j++ ] ) ) {
						if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
							cur += clazz + " ";
						}
					}

					// Only assign if different to avoid unneeded rendering.
					finalValue = stripAndCollapse( cur );
					if ( curValue !== finalValue ) {
						elem.setAttribute( "class", finalValue );
					}
				}
			}
		}

		return this;
	},

	removeClass: function( value ) {
		var classes, elem, cur, curValue, clazz, j, finalValue,
			i = 0;

		if ( isFunction( value ) ) {
			return this.each( function( j ) {
				jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) );
			} );
		}

		if ( !arguments.length ) {
			return this.attr( "class", "" );
		}

		classes = classesToArray( value );

		if ( classes.length ) {
			while ( ( elem = this[ i++ ] ) ) {
				curValue = getClass( elem );

				// This expression is here for better compressibility (see addClass)
				cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );

				if ( cur ) {
					j = 0;
					while ( ( clazz = classes[ j++ ] ) ) {

						// Remove *all* instances
						while ( cur.indexOf( " " + clazz + " " ) > -1 ) {
							cur = cur.replace( " " + clazz + " ", " " );
						}
					}

					// Only assign if different to avoid unneeded rendering.
					finalValue = stripAndCollapse( cur );
					if ( curValue !== finalValue ) {
						elem.setAttribute( "class", finalValue );
					}
				}
			}
		}

		return this;
	},

	toggleClass: function( value, stateVal ) {
		var type = typeof value,
			isValidValue = type === "string" || Array.isArray( value );

		if ( typeof stateVal === "boolean" && isValidValue ) {
			return stateVal ? this.addClass( value ) : this.removeClass( value );
		}

		if ( isFunction( value ) ) {
			return this.each( function( i ) {
				jQuery( this ).toggleClass(
					value.call( this, i, getClass( this ), stateVal ),
					stateVal
				);
			} );
		}

		return this.each( function() {
			var className, i, self, classNames;

			if ( isValidValue ) {

				// Toggle individual class names
				i = 0;
				self = jQuery( this );
				classNames = classesToArray( value );

				while ( ( className = classNames[ i++ ] ) ) {

					// Check each className given, space separated list
					if ( self.hasClass( className ) ) {
						self.removeClass( className );
					} else {
						self.addClass( className );
					}
				}

			// Toggle whole class name
			} else if ( value === undefined || type === "boolean" ) {
				className = getClass( this );
				if ( className ) {

					// Store className if set
					dataPriv.set( this, "__className__", className );
				}

				// If the element has a class name or if we're passed `false`,
				// then remove the whole classname (if there was one, the above saved it).
				// Otherwise bring back whatever was previously saved (if anything),
				// falling back to the empty string if nothing was stored.
				if ( this.setAttribute ) {
					this.setAttribute( "class",
						className || value === false ?
							"" :
							dataPriv.get( this, "__className__" ) || ""
					);
				}
			}
		} );
	},

	hasClass: function( selector ) {
		var className, elem,
			i = 0;

		className = " " + selector + " ";
		while ( ( elem = this[ i++ ] ) ) {
			if ( elem.nodeType === 1 &&
				( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) {
				return true;
			}
		}

		return false;
	}
} );




var rreturn = /\r/g;

jQuery.fn.extend( {
	val: function( value ) {
		var hooks, ret, valueIsFunction,
			elem = this[ 0 ];

		if ( !arguments.length ) {
			if ( elem ) {
				hooks = jQuery.valHooks[ elem.type ] ||
					jQuery.valHooks[ elem.nodeName.toLowerCase() ];

				if ( hooks &&
					"get" in hooks &&
					( ret = hooks.get( elem, "value" ) ) !== undefined
				) {
					return ret;
				}

				ret = elem.value;

				// Handle most common string cases
				if ( typeof ret === "string" ) {
					return ret.replace( rreturn, "" );
				}

				// Handle cases where value is null/undef or number
				return ret == null ? "" : ret;
			}

			return;
		}

		valueIsFunction = isFunction( value );

		return this.each( function( i ) {
			var val;

			if ( this.nodeType !== 1 ) {
				return;
			}

			if ( valueIsFunction ) {
				val = value.call( this, i, jQuery( this ).val() );
			} else {
				val = value;
			}

			// Treat null/undefined as ""; convert numbers to string
			if ( val == null ) {
				val = "";

			} else if ( typeof val === "number" ) {
				val += "";

			} else if ( Array.isArray( val ) ) {
				val = jQuery.map( val, function( value ) {
					return value == null ? "" : value + "";
				} );
			}

			hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];

			// If set returns undefined, fall back to normal setting
			if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) {
				this.value = val;
			}
		} );
	}
} );

jQuery.extend( {
	valHooks: {
		option: {
			get: function( elem ) {

				var val = jQuery.find.attr( elem, "value" );
				return val != null ?
					val :

					// Support: IE <=10 - 11 only
					// option.text throws exceptions (#14686, #14858)
					// Strip and collapse whitespace
					// https://html.spec.whatwg.org/#strip-and-collapse-whitespace
					stripAndCollapse( jQuery.text( elem ) );
			}
		},
		select: {
			get: function( elem ) {
				var value, option, i,
					options = elem.options,
					index = elem.selectedIndex,
					one = elem.type === "select-one",
					values = one ? null : [],
					max = one ? index + 1 : options.length;

				if ( index < 0 ) {
					i = max;

				} else {
					i = one ? index : 0;
				}

				// Loop through all the selected options
				for ( ; i < max; i++ ) {
					option = options[ i ];

					// Support: IE <=9 only
					// IE8-9 doesn't update selected after form reset (#2551)
					if ( ( option.selected || i === index ) &&

							// Don't return options that are disabled or in a disabled optgroup
							!option.disabled &&
							( !option.parentNode.disabled ||
								!nodeName( option.parentNode, "optgroup" ) ) ) {

						// Get the specific value for the option
						value = jQuery( option ).val();

						// We don't need an array for one selects
						if ( one ) {
							return value;
						}

						// Multi-Selects return an array
						values.push( value );
					}
				}

				return values;
			},

			set: function( elem, value ) {
				var optionSet, option,
					options = elem.options,
					values = jQuery.makeArray( value ),
					i = options.length;

				while ( i-- ) {
					option = options[ i ];

					/* eslint-disable no-cond-assign */

					if ( option.selected =
						jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1
					) {
						optionSet = true;
					}

					/* eslint-enable no-cond-assign */
				}

				// Force browsers to behave consistently when non-matching value is set
				if ( !optionSet ) {
					elem.selectedIndex = -1;
				}
				return values;
			}
		}
	}
} );

// Radios and checkboxes getter/setter
jQuery.each( [ "radio", "checkbox" ], function() {
	jQuery.valHooks[ this ] = {
		set: function( elem, value ) {
			if ( Array.isArray( value ) ) {
				return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 );
			}
		}
	};
	if ( !support.checkOn ) {
		jQuery.valHooks[ this ].get = function( elem ) {
			return elem.getAttribute( "value" ) === null ? "on" : elem.value;
		};
	}
} );




// Return jQuery for attributes-only inclusion


support.focusin = "onfocusin" in window;


var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
	stopPropagationCallback = function( e ) {
		e.stopPropagation();
	};

jQuery.extend( jQuery.event, {

	trigger: function( event, data, elem, onlyHandlers ) {

		var i, cur, tmp, bubbleType, ontype, handle, special, lastElement,
			eventPath = [ elem || document ],
			type = hasOwn.call( event, "type" ) ? event.type : event,
			namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : [];

		cur = lastElement = tmp = elem = elem || document;

		// Don't do events on text and comment nodes
		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
			return;
		}

		// focus/blur morphs to focusin/out; ensure we're not firing them right now
		if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
			return;
		}

		if ( type.indexOf( "." ) > -1 ) {

			// Namespaced trigger; create a regexp to match event type in handle()
			namespaces = type.split( "." );
			type = namespaces.shift();
			namespaces.sort();
		}
		ontype = type.indexOf( ":" ) < 0 && "on" + type;

		// Caller can pass in a jQuery.Event object, Object, or just an event type string
		event = event[ jQuery.expando ] ?
			event :
			new jQuery.Event( type, typeof event === "object" && event );

		// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
		event.isTrigger = onlyHandlers ? 2 : 3;
		event.namespace = namespaces.join( "." );
		event.rnamespace = event.namespace ?
			new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) :
			null;

		// Clean up the event in case it is being reused
		event.result = undefined;
		if ( !event.target ) {
			event.target = elem;
		}

		// Clone any incoming data and prepend the event, creating the handler arg list
		data = data == null ?
			[ event ] :
			jQuery.makeArray( data, [ event ] );

		// Allow special events to draw outside the lines
		special = jQuery.event.special[ type ] || {};
		if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
			return;
		}

		// Determine event propagation path in advance, per W3C events spec (#9951)
		// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
		if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) {

			bubbleType = special.delegateType || type;
			if ( !rfocusMorph.test( bubbleType + type ) ) {
				cur = cur.parentNode;
			}
			for ( ; cur; cur = cur.parentNode ) {
				eventPath.push( cur );
				tmp = cur;
			}

			// Only add window if we got to document (e.g., not plain obj or detached DOM)
			if ( tmp === ( elem.ownerDocument || document ) ) {
				eventPath.push( tmp.defaultView || tmp.parentWindow || window );
			}
		}

		// Fire handlers on the event path
		i = 0;
		while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) {
			lastElement = cur;
			event.type = i > 1 ?
				bubbleType :
				special.bindType || type;

			// jQuery handler
			handle = ( dataPriv.get( cur, "events" ) || Object.create( null ) )[ event.type ] &&
				dataPriv.get( cur, "handle" );
			if ( handle ) {
				handle.apply( cur, data );
			}

			// Native handler
			handle = ontype && cur[ ontype ];
			if ( handle && handle.apply && acceptData( cur ) ) {
				event.result = handle.apply( cur, data );
				if ( event.result === false ) {
					event.preventDefault();
				}
			}
		}
		event.type = type;

		// If nobody prevented the default action, do it now
		if ( !onlyHandlers && !event.isDefaultPrevented() ) {

			if ( ( !special._default ||
				special._default.apply( eventPath.pop(), data ) === false ) &&
				acceptData( elem ) ) {

				// Call a native DOM method on the target with the same name as the event.
				// Don't do default actions on window, that's where global variables be (#6170)
				if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) {

					// Don't re-trigger an onFOO event when we call its FOO() method
					tmp = elem[ ontype ];

					if ( tmp ) {
						elem[ ontype ] = null;
					}

					// Prevent re-triggering of the same event, since we already bubbled it above
					jQuery.event.triggered = type;

					if ( event.isPropagationStopped() ) {
						lastElement.addEventListener( type, stopPropagationCallback );
					}

					elem[ type ]();

					if ( event.isPropagationStopped() ) {
						lastElement.removeEventListener( type, stopPropagationCallback );
					}

					jQuery.event.triggered = undefined;

					if ( tmp ) {
						elem[ ontype ] = tmp;
					}
				}
			}
		}

		return event.result;
	},

	// Piggyback on a donor event to simulate a different one
	// Used only for `focus(in | out)` events
	simulate: function( type, elem, event ) {
		var e = jQuery.extend(
			new jQuery.Event(),
			event,
			{
				type: type,
				isSimulated: true
			}
		);

		jQuery.event.trigger( e, null, elem );
	}

} );

jQuery.fn.extend( {

	trigger: function( type, data ) {
		return this.each( function() {
			jQuery.event.trigger( type, data, this );
		} );
	},
	triggerHandler: function( type, data ) {
		var elem = this[ 0 ];
		if ( elem ) {
			return jQuery.event.trigger( type, data, elem, true );
		}
	}
} );


// Support: Firefox <=44
// Firefox doesn't have focus(in | out) events
// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787
//
// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1
// focus(in | out) events fire after focus & blur events,
// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order
// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857
if ( !support.focusin ) {
	jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) {

		// Attach a single capturing handler on the document while someone wants focusin/focusout
		var handler = function( event ) {
			jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) );
		};

		jQuery.event.special[ fix ] = {
			setup: function() {

				// Handle: regular nodes (via `this.ownerDocument`), window
				// (via `this.document`) & document (via `this`).
				var doc = this.ownerDocument || this.document || this,
					attaches = dataPriv.access( doc, fix );

				if ( !attaches ) {
					doc.addEventListener( orig, handler, true );
				}
				dataPriv.access( doc, fix, ( attaches || 0 ) + 1 );
			},
			teardown: function() {
				var doc = this.ownerDocument || this.document || this,
					attaches = dataPriv.access( doc, fix ) - 1;

				if ( !attaches ) {
					doc.removeEventListener( orig, handler, true );
					dataPriv.remove( doc, fix );

				} else {
					dataPriv.access( doc, fix, attaches );
				}
			}
		};
	} );
}
var location = window.location;

var nonce = { guid: Date.now() };

var rquery = ( /\?/ );



// Cross-browser xml parsing
jQuery.parseXML = function( data ) {
	var xml, parserErrorElem;
	if ( !data || typeof data !== "string" ) {
		return null;
	}

	// Support: IE 9 - 11 only
	// IE throws on parseFromString with invalid input.
	try {
		xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" );
	} catch ( e ) {}

	parserErrorElem = xml && xml.getElementsByTagName( "parsererror" )[ 0 ];
	if ( !xml || parserErrorElem ) {
		jQuery.error( "Invalid XML: " + (
			parserErrorElem ?
				jQuery.map( parserErrorElem.childNodes, function( el ) {
					return el.textContent;
				} ).join( "\n" ) :
				data
		) );
	}
	return xml;
};


var
	rbracket = /\[\]$/,
	rCRLF = /\r?\n/g,
	rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
	rsubmittable = /^(?:input|select|textarea|keygen)/i;

function buildParams( prefix, obj, traditional, add ) {
	var name;

	if ( Array.isArray( obj ) ) {

		// Serialize array item.
		jQuery.each( obj, function( i, v ) {
			if ( traditional || rbracket.test( prefix ) ) {

				// Treat each array item as a scalar.
				add( prefix, v );

			} else {

				// Item is non-scalar (array or object), encode its numeric index.
				buildParams(
					prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]",
					v,
					traditional,
					add
				);
			}
		} );

	} else if ( !traditional && toType( obj ) === "object" ) {

		// Serialize object item.
		for ( name in obj ) {
			buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
		}

	} else {

		// Serialize scalar item.
		add( prefix, obj );
	}
}

// Serialize an array of form elements or a set of
// key/values into a query string
jQuery.param = function( a, traditional ) {
	var prefix,
		s = [],
		add = function( key, valueOrFunction ) {

			// If value is a function, invoke it and use its return value
			var value = isFunction( valueOrFunction ) ?
				valueOrFunction() :
				valueOrFunction;

			s[ s.length ] = encodeURIComponent( key ) + "=" +
				encodeURIComponent( value == null ? "" : value );
		};

	if ( a == null ) {
		return "";
	}

	// If an array was passed in, assume that it is an array of form elements.
	if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {

		// Serialize the form elements
		jQuery.each( a, function() {
			add( this.name, this.value );
		} );

	} else {

		// If traditional, encode the "old" way (the way 1.3.2 or older
		// did it), otherwise encode params recursively.
		for ( prefix in a ) {
			buildParams( prefix, a[ prefix ], traditional, add );
		}
	}

	// Return the resulting serialization
	return s.join( "&" );
};

jQuery.fn.extend( {
	serialize: function() {
		return jQuery.param( this.serializeArray() );
	},
	serializeArray: function() {
		return this.map( function() {

			// Can add propHook for "elements" to filter or add form elements
			var elements = jQuery.prop( this, "elements" );
			return elements ? jQuery.makeArray( elements ) : this;
		} ).filter( function() {
			var type = this.type;

			// Use .is( ":disabled" ) so that fieldset[disabled] works
			return this.name && !jQuery( this ).is( ":disabled" ) &&
				rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
				( this.checked || !rcheckableType.test( type ) );
		} ).map( function( _i, elem ) {
			var val = jQuery( this ).val();

			if ( val == null ) {
				return null;
			}

			if ( Array.isArray( val ) ) {
				return jQuery.map( val, function( val ) {
					return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
				} );
			}

			return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
		} ).get();
	}
} );


var
	r20 = /%20/g,
	rhash = /#.*$/,
	rantiCache = /([?&])_=[^&]*/,
	rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg,

	// #7653, #8125, #8152: local protocol detection
	rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
	rnoContent = /^(?:GET|HEAD)$/,
	rprotocol = /^\/\//,

	/* Prefilters
	 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
	 * 2) These are called:
	 *    - BEFORE asking for a transport
	 *    - AFTER param serialization (s.data is a string if s.processData is true)
	 * 3) key is the dataType
	 * 4) the catchall symbol "*" can be used
	 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
	 */
	prefilters = {},

	/* Transports bindings
	 * 1) key is the dataType
	 * 2) the catchall symbol "*" can be used
	 * 3) selection will start with transport dataType and THEN go to "*" if needed
	 */
	transports = {},

	// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
	allTypes = "*/".concat( "*" ),

	// Anchor tag for parsing the document origin
	originAnchor = document.createElement( "a" );

originAnchor.href = location.href;

// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
function addToPrefiltersOrTransports( structure ) {

	// dataTypeExpression is optional and defaults to "*"
	return function( dataTypeExpression, func ) {

		if ( typeof dataTypeExpression !== "string" ) {
			func = dataTypeExpression;
			dataTypeExpression = "*";
		}

		var dataType,
			i = 0,
			dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || [];

		if ( isFunction( func ) ) {

			// For each dataType in the dataTypeExpression
			while ( ( dataType = dataTypes[ i++ ] ) ) {

				// Prepend if requested
				if ( dataType[ 0 ] === "+" ) {
					dataType = dataType.slice( 1 ) || "*";
					( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func );

				// Otherwise append
				} else {
					( structure[ dataType ] = structure[ dataType ] || [] ).push( func );
				}
			}
		}
	};
}

// Base inspection function for prefilters and transports
function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {

	var inspected = {},
		seekingTransport = ( structure === transports );

	function inspect( dataType ) {
		var selected;
		inspected[ dataType ] = true;
		jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
			var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
			if ( typeof dataTypeOrTransport === "string" &&
				!seekingTransport && !inspected[ dataTypeOrTransport ] ) {

				options.dataTypes.unshift( dataTypeOrTransport );
				inspect( dataTypeOrTransport );
				return false;
			} else if ( seekingTransport ) {
				return !( selected = dataTypeOrTransport );
			}
		} );
		return selected;
	}

	return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
}

// A special extend for ajax options
// that takes "flat" options (not to be deep extended)
// Fixes #9887
function ajaxExtend( target, src ) {
	var key, deep,
		flatOptions = jQuery.ajaxSettings.flatOptions || {};

	for ( key in src ) {
		if ( src[ key ] !== undefined ) {
			( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
		}
	}
	if ( deep ) {
		jQuery.extend( true, target, deep );
	}

	return target;
}

/* Handles responses to an ajax request:
 * - finds the right dataType (mediates between content-type and expected dataType)
 * - returns the corresponding response
 */
function ajaxHandleResponses( s, jqXHR, responses ) {

	var ct, type, finalDataType, firstDataType,
		contents = s.contents,
		dataTypes = s.dataTypes;

	// Remove auto dataType and get content-type in the process
	while ( dataTypes[ 0 ] === "*" ) {
		dataTypes.shift();
		if ( ct === undefined ) {
			ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" );
		}
	}

	// Check if we're dealing with a known content-type
	if ( ct ) {
		for ( type in contents ) {
			if ( contents[ type ] && contents[ type ].test( ct ) ) {
				dataTypes.unshift( type );
				break;
			}
		}
	}

	// Check to see if we have a response for the expected dataType
	if ( dataTypes[ 0 ] in responses ) {
		finalDataType = dataTypes[ 0 ];
	} else {

		// Try convertible dataTypes
		for ( type in responses ) {
			if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) {
				finalDataType = type;
				break;
			}
			if ( !firstDataType ) {
				firstDataType = type;
			}
		}

		// Or just use first one
		finalDataType = finalDataType || firstDataType;
	}

	// If we found a dataType
	// We add the dataType to the list if needed
	// and return the corresponding response
	if ( finalDataType ) {
		if ( finalDataType !== dataTypes[ 0 ] ) {
			dataTypes.unshift( finalDataType );
		}
		return responses[ finalDataType ];
	}
}

/* Chain conversions given the request and the original response
 * Also sets the responseXXX fields on the jqXHR instance
 */
function ajaxConvert( s, response, jqXHR, isSuccess ) {
	var conv2, current, conv, tmp, prev,
		converters = {},

		// Work with a copy of dataTypes in case we need to modify it for conversion
		dataTypes = s.dataTypes.slice();

	// Create converters map with lowercased keys
	if ( dataTypes[ 1 ] ) {
		for ( conv in s.converters ) {
			converters[ conv.toLowerCase() ] = s.converters[ conv ];
		}
	}

	current = dataTypes.shift();

	// Convert to each sequential dataType
	while ( current ) {

		if ( s.responseFields[ current ] ) {
			jqXHR[ s.responseFields[ current ] ] = response;
		}

		// Apply the dataFilter if provided
		if ( !prev && isSuccess && s.dataFilter ) {
			response = s.dataFilter( response, s.dataType );
		}

		prev = current;
		current = dataTypes.shift();

		if ( current ) {

			// There's only work to do if current dataType is non-auto
			if ( current === "*" ) {

				current = prev;

			// Convert response if prev dataType is non-auto and differs from current
			} else if ( prev !== "*" && prev !== current ) {

				// Seek a direct converter
				conv = converters[ prev + " " + current ] || converters[ "* " + current ];

				// If none found, seek a pair
				if ( !conv ) {
					for ( conv2 in converters ) {

						// If conv2 outputs current
						tmp = conv2.split( " " );
						if ( tmp[ 1 ] === current ) {

							// If prev can be converted to accepted input
							conv = converters[ prev + " " + tmp[ 0 ] ] ||
								converters[ "* " + tmp[ 0 ] ];
							if ( conv ) {

								// Condense equivalence converters
								if ( conv === true ) {
									conv = converters[ conv2 ];

								// Otherwise, insert the intermediate dataType
								} else if ( converters[ conv2 ] !== true ) {
									current = tmp[ 0 ];
									dataTypes.unshift( tmp[ 1 ] );
								}
								break;
							}
						}
					}
				}

				// Apply converter (if not an equivalence)
				if ( conv !== true ) {

					// Unless errors are allowed to bubble, catch and return them
					if ( conv && s.throws ) {
						response = conv( response );
					} else {
						try {
							response = conv( response );
						} catch ( e ) {
							return {
								state: "parsererror",
								error: conv ? e : "No conversion from " + prev + " to " + current
							};
						}
					}
				}
			}
		}
	}

	return { state: "success", data: response };
}

jQuery.extend( {

	// Counter for holding the number of active queries
	active: 0,

	// Last-Modified header cache for next request
	lastModified: {},
	etag: {},

	ajaxSettings: {
		url: location.href,
		type: "GET",
		isLocal: rlocalProtocol.test( location.protocol ),
		global: true,
		processData: true,
		async: true,
		contentType: "application/x-www-form-urlencoded; charset=UTF-8",

		/*
		timeout: 0,
		data: null,
		dataType: null,
		username: null,
		password: null,
		cache: null,
		throws: false,
		traditional: false,
		headers: {},
		*/

		accepts: {
			"*": allTypes,
			text: "text/plain",
			html: "text/html",
			xml: "application/xml, text/xml",
			json: "application/json, text/javascript"
		},

		contents: {
			xml: /\bxml\b/,
			html: /\bhtml/,
			json: /\bjson\b/
		},

		responseFields: {
			xml: "responseXML",
			text: "responseText",
			json: "responseJSON"
		},

		// Data converters
		// Keys separate source (or catchall "*") and destination types with a single space
		converters: {

			// Convert anything to text
			"* text": String,

			// Text to html (true = no transformation)
			"text html": true,

			// Evaluate text as a json expression
			"text json": JSON.parse,

			// Parse text as xml
			"text xml": jQuery.parseXML
		},

		// For options that shouldn't be deep extended:
		// you can add your own custom options here if
		// and when you create one that shouldn't be
		// deep extended (see ajaxExtend)
		flatOptions: {
			url: true,
			context: true
		}
	},

	// Creates a full fledged settings object into target
	// with both ajaxSettings and settings fields.
	// If target is omitted, writes into ajaxSettings.
	ajaxSetup: function( target, settings ) {
		return settings ?

			// Building a settings object
			ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :

			// Extending ajaxSettings
			ajaxExtend( jQuery.ajaxSettings, target );
	},

	ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
	ajaxTransport: addToPrefiltersOrTransports( transports ),

	// Main method
	ajax: function( url, options ) {

		// If url is an object, simulate pre-1.5 signature
		if ( typeof url === "object" ) {
			options = url;
			url = undefined;
		}

		// Force options to be an object
		options = options || {};

		var transport,

			// URL without anti-cache param
			cacheURL,

			// Response headers
			responseHeadersString,
			responseHeaders,

			// timeout handle
			timeoutTimer,

			// Url cleanup var
			urlAnchor,

			// Request state (becomes false upon send and true upon completion)
			completed,

			// To know if global events are to be dispatched
			fireGlobals,

			// Loop variable
			i,

			// uncached part of the url
			uncached,

			// Create the final options object
			s = jQuery.ajaxSetup( {}, options ),

			// Callbacks context
			callbackContext = s.context || s,

			// Context for global events is callbackContext if it is a DOM node or jQuery collection
			globalEventContext = s.context &&
				( callbackContext.nodeType || callbackContext.jquery ) ?
				jQuery( callbackContext ) :
				jQuery.event,

			// Deferreds
			deferred = jQuery.Deferred(),
			completeDeferred = jQuery.Callbacks( "once memory" ),

			// Status-dependent callbacks
			statusCode = s.statusCode || {},

			// Headers (they are sent all at once)
			requestHeaders = {},
			requestHeadersNames = {},

			// Default abort message
			strAbort = "canceled",

			// Fake xhr
			jqXHR = {
				readyState: 0,

				// Builds headers hashtable if needed
				getResponseHeader: function( key ) {
					var match;
					if ( completed ) {
						if ( !responseHeaders ) {
							responseHeaders = {};
							while ( ( match = rheaders.exec( responseHeadersString ) ) ) {
								responseHeaders[ match[ 1 ].toLowerCase() + " " ] =
									( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] )
										.concat( match[ 2 ] );
							}
						}
						match = responseHeaders[ key.toLowerCase() + " " ];
					}
					return match == null ? null : match.join( ", " );
				},

				// Raw string
				getAllResponseHeaders: function() {
					return completed ? responseHeadersString : null;
				},

				// Caches the header
				setRequestHeader: function( name, value ) {
					if ( completed == null ) {
						name = requestHeadersNames[ name.toLowerCase() ] =
							requestHeadersNames[ name.toLowerCase() ] || name;
						requestHeaders[ name ] = value;
					}
					return this;
				},

				// Overrides response content-type header
				overrideMimeType: function( type ) {
					if ( completed == null ) {
						s.mimeType = type;
					}
					return this;
				},

				// Status-dependent callbacks
				statusCode: function( map ) {
					var code;
					if ( map ) {
						if ( completed ) {

							// Execute the appropriate callbacks
							jqXHR.always( map[ jqXHR.status ] );
						} else {

							// Lazy-add the new callbacks in a way that preserves old ones
							for ( code in map ) {
								statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
							}
						}
					}
					return this;
				},

				// Cancel the request
				abort: function( statusText ) {
					var finalText = statusText || strAbort;
					if ( transport ) {
						transport.abort( finalText );
					}
					done( 0, finalText );
					return this;
				}
			};

		// Attach deferreds
		deferred.promise( jqXHR );

		// Add protocol if not provided (prefilters might expect it)
		// Handle falsy url in the settings object (#10093: consistency with old signature)
		// We also use the url parameter if available
		s.url = ( ( url || s.url || location.href ) + "" )
			.replace( rprotocol, location.protocol + "//" );

		// Alias method option to type as per ticket #12004
		s.type = options.method || options.type || s.method || s.type;

		// Extract dataTypes list
		s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ];

		// A cross-domain request is in order when the origin doesn't match the current origin.
		if ( s.crossDomain == null ) {
			urlAnchor = document.createElement( "a" );

			// Support: IE <=8 - 11, Edge 12 - 15
			// IE throws exception on accessing the href property if url is malformed,
			// e.g. http://example.com:80x/
			try {
				urlAnchor.href = s.url;

				// Support: IE <=8 - 11 only
				// Anchor's host property isn't correctly set when s.url is relative
				urlAnchor.href = urlAnchor.href;
				s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !==
					urlAnchor.protocol + "//" + urlAnchor.host;
			} catch ( e ) {

				// If there is an error parsing the URL, assume it is crossDomain,
				// it can be rejected by the transport if it is invalid
				s.crossDomain = true;
			}
		}

		// Convert data if not already a string
		if ( s.data && s.processData && typeof s.data !== "string" ) {
			s.data = jQuery.param( s.data, s.traditional );
		}

		// Apply prefilters
		inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );

		// If request was aborted inside a prefilter, stop there
		if ( completed ) {
			return jqXHR;
		}

		// We can fire global events as of now if asked to
		// Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118)
		fireGlobals = jQuery.event && s.global;

		// Watch for a new set of requests
		if ( fireGlobals && jQuery.active++ === 0 ) {
			jQuery.event.trigger( "ajaxStart" );
		}

		// Uppercase the type
		s.type = s.type.toUpperCase();

		// Determine if request has content
		s.hasContent = !rnoContent.test( s.type );

		// Save the URL in case we're toying with the If-Modified-Since
		// and/or If-None-Match header later on
		// Remove hash to simplify url manipulation
		cacheURL = s.url.replace( rhash, "" );

		// More options handling for requests with no content
		if ( !s.hasContent ) {

			// Remember the hash so we can put it back
			uncached = s.url.slice( cacheURL.length );

			// If data is available and should be processed, append data to url
			if ( s.data && ( s.processData || typeof s.data === "string" ) ) {
				cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data;

				// #9682: remove data so that it's not used in an eventual retry
				delete s.data;
			}

			// Add or update anti-cache param if needed
			if ( s.cache === false ) {
				cacheURL = cacheURL.replace( rantiCache, "$1" );
				uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) +
					uncached;
			}

			// Put hash and anti-cache on the URL that will be requested (gh-1732)
			s.url = cacheURL + uncached;

		// Change '%20' to '+' if this is encoded form body content (gh-2658)
		} else if ( s.data && s.processData &&
			( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) {
			s.data = s.data.replace( r20, "+" );
		}

		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
		if ( s.ifModified ) {
			if ( jQuery.lastModified[ cacheURL ] ) {
				jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
			}
			if ( jQuery.etag[ cacheURL ] ) {
				jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
			}
		}

		// Set the correct header, if data is being sent
		if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
			jqXHR.setRequestHeader( "Content-Type", s.contentType );
		}

		// Set the Accepts header for the server, depending on the dataType
		jqXHR.setRequestHeader(
			"Accept",
			s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ?
				s.accepts[ s.dataTypes[ 0 ] ] +
					( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
				s.accepts[ "*" ]
		);

		// Check for headers option
		for ( i in s.headers ) {
			jqXHR.setRequestHeader( i, s.headers[ i ] );
		}

		// Allow custom headers/mimetypes and early abort
		if ( s.beforeSend &&
			( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) {

			// Abort if not done already and return
			return jqXHR.abort();
		}

		// Aborting is no longer a cancellation
		strAbort = "abort";

		// Install callbacks on deferreds
		completeDeferred.add( s.complete );
		jqXHR.done( s.success );
		jqXHR.fail( s.error );

		// Get transport
		transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );

		// If no transport, we auto-abort
		if ( !transport ) {
			done( -1, "No Transport" );
		} else {
			jqXHR.readyState = 1;

			// Send global event
			if ( fireGlobals ) {
				globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
			}

			// If request was aborted inside ajaxSend, stop there
			if ( completed ) {
				return jqXHR;
			}

			// Timeout
			if ( s.async && s.timeout > 0 ) {
				timeoutTimer = window.setTimeout( function() {
					jqXHR.abort( "timeout" );
				}, s.timeout );
			}

			try {
				completed = false;
				transport.send( requestHeaders, done );
			} catch ( e ) {

				// Rethrow post-completion exceptions
				if ( completed ) {
					throw e;
				}

				// Propagate others as results
				done( -1, e );
			}
		}

		// Callback for when everything is done
		function done( status, nativeStatusText, responses, headers ) {
			var isSuccess, success, error, response, modified,
				statusText = nativeStatusText;

			// Ignore repeat invocations
			if ( completed ) {
				return;
			}

			completed = true;

			// Clear timeout if it exists
			if ( timeoutTimer ) {
				window.clearTimeout( timeoutTimer );
			}

			// Dereference transport for early garbage collection
			// (no matter how long the jqXHR object will be used)
			transport = undefined;

			// Cache response headers
			responseHeadersString = headers || "";

			// Set readyState
			jqXHR.readyState = status > 0 ? 4 : 0;

			// Determine if successful
			isSuccess = status >= 200 && status < 300 || status === 304;

			// Get response data
			if ( responses ) {
				response = ajaxHandleResponses( s, jqXHR, responses );
			}

			// Use a noop converter for missing script but not if jsonp
			if ( !isSuccess &&
				jQuery.inArray( "script", s.dataTypes ) > -1 &&
				jQuery.inArray( "json", s.dataTypes ) < 0 ) {
				s.converters[ "text script" ] = function() {};
			}

			// Convert no matter what (that way responseXXX fields are always set)
			response = ajaxConvert( s, response, jqXHR, isSuccess );

			// If successful, handle type chaining
			if ( isSuccess ) {

				// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
				if ( s.ifModified ) {
					modified = jqXHR.getResponseHeader( "Last-Modified" );
					if ( modified ) {
						jQuery.lastModified[ cacheURL ] = modified;
					}
					modified = jqXHR.getResponseHeader( "etag" );
					if ( modified ) {
						jQuery.etag[ cacheURL ] = modified;
					}
				}

				// if no content
				if ( status === 204 || s.type === "HEAD" ) {
					statusText = "nocontent";

				// if not modified
				} else if ( status === 304 ) {
					statusText = "notmodified";

				// If we have data, let's convert it
				} else {
					statusText = response.state;
					success = response.data;
					error = response.error;
					isSuccess = !error;
				}
			} else {

				// Extract error from statusText and normalize for non-aborts
				error = statusText;
				if ( status || !statusText ) {
					statusText = "error";
					if ( status < 0 ) {
						status = 0;
					}
				}
			}

			// Set data for the fake xhr object
			jqXHR.status = status;
			jqXHR.statusText = ( nativeStatusText || statusText ) + "";

			// Success/Error
			if ( isSuccess ) {
				deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
			} else {
				deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
			}

			// Status-dependent callbacks
			jqXHR.statusCode( statusCode );
			statusCode = undefined;

			if ( fireGlobals ) {
				globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
					[ jqXHR, s, isSuccess ? success : error ] );
			}

			// Complete
			completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );

			if ( fireGlobals ) {
				globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );

				// Handle the global AJAX counter
				if ( !( --jQuery.active ) ) {
					jQuery.event.trigger( "ajaxStop" );
				}
			}
		}

		return jqXHR;
	},

	getJSON: function( url, data, callback ) {
		return jQuery.get( url, data, callback, "json" );
	},

	getScript: function( url, callback ) {
		return jQuery.get( url, undefined, callback, "script" );
	}
} );

jQuery.each( [ "get", "post" ], function( _i, method ) {
	jQuery[ method ] = function( url, data, callback, type ) {

		// Shift arguments if data argument was omitted
		if ( isFunction( data ) ) {
			type = type || callback;
			callback = data;
			data = undefined;
		}

		// The url can be an options object (which then must have .url)
		return jQuery.ajax( jQuery.extend( {
			url: url,
			type: method,
			dataType: type,
			data: data,
			success: callback
		}, jQuery.isPlainObject( url ) && url ) );
	};
} );

jQuery.ajaxPrefilter( function( s ) {
	var i;
	for ( i in s.headers ) {
		if ( i.toLowerCase() === "content-type" ) {
			s.contentType = s.headers[ i ] || "";
		}
	}
} );


jQuery._evalUrl = function( url, options, doc ) {
	return jQuery.ajax( {
		url: url,

		// Make this explicit, since user can override this through ajaxSetup (#11264)
		type: "GET",
		dataType: "script",
		cache: true,
		async: false,
		global: false,

		// Only evaluate the response if it is successful (gh-4126)
		// dataFilter is not invoked for failure responses, so using it instead
		// of the default converter is kludgy but it works.
		converters: {
			"text script": function() {}
		},
		dataFilter: function( response ) {
			jQuery.globalEval( response, options, doc );
		}
	} );
};


jQuery.fn.extend( {
	wrapAll: function( html ) {
		var wrap;

		if ( this[ 0 ] ) {
			if ( isFunction( html ) ) {
				html = html.call( this[ 0 ] );
			}

			// The elements to wrap the target around
			wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );

			if ( this[ 0 ].parentNode ) {
				wrap.insertBefore( this[ 0 ] );
			}

			wrap.map( function() {
				var elem = this;

				while ( elem.firstElementChild ) {
					elem = elem.firstElementChild;
				}

				return elem;
			} ).append( this );
		}

		return this;
	},

	wrapInner: function( html ) {
		if ( isFunction( html ) ) {
			return this.each( function( i ) {
				jQuery( this ).wrapInner( html.call( this, i ) );
			} );
		}

		return this.each( function() {
			var self = jQuery( this ),
				contents = self.contents();

			if ( contents.length ) {
				contents.wrapAll( html );

			} else {
				self.append( html );
			}
		} );
	},

	wrap: function( html ) {
		var htmlIsFunction = isFunction( html );

		return this.each( function( i ) {
			jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html );
		} );
	},

	unwrap: function( selector ) {
		this.parent( selector ).not( "body" ).each( function() {
			jQuery( this ).replaceWith( this.childNodes );
		} );
		return this;
	}
} );


jQuery.expr.pseudos.hidden = function( elem ) {
	return !jQuery.expr.pseudos.visible( elem );
};
jQuery.expr.pseudos.visible = function( elem ) {
	return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );
};




jQuery.ajaxSettings.xhr = function() {
	try {
		return new window.XMLHttpRequest();
	} catch ( e ) {}
};

var xhrSuccessStatus = {

		// File protocol always yields status code 0, assume 200
		0: 200,

		// Support: IE <=9 only
		// #1450: sometimes IE returns 1223 when it should be 204
		1223: 204
	},
	xhrSupported = jQuery.ajaxSettings.xhr();

support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
support.ajax = xhrSupported = !!xhrSupported;

jQuery.ajaxTransport( function( options ) {
	var callback, errorCallback;

	// Cross domain only allowed if supported through XMLHttpRequest
	if ( support.cors || xhrSupported && !options.crossDomain ) {
		return {
			send: function( headers, complete ) {
				var i,
					xhr = options.xhr();

				xhr.open(
					options.type,
					options.url,
					options.async,
					options.username,
					options.password
				);

				// Apply custom fields if provided
				if ( options.xhrFields ) {
					for ( i in options.xhrFields ) {
						xhr[ i ] = options.xhrFields[ i ];
					}
				}

				// Override mime type if needed
				if ( options.mimeType && xhr.overrideMimeType ) {
					xhr.overrideMimeType( options.mimeType );
				}

				// X-Requested-With header
				// For cross-domain requests, seeing as conditions for a preflight are
				// akin to a jigsaw puzzle, we simply never set it to be sure.
				// (it can always be set on a per-request basis or even using ajaxSetup)
				// For same-domain requests, won't change header if already provided.
				if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) {
					headers[ "X-Requested-With" ] = "XMLHttpRequest";
				}

				// Set headers
				for ( i in headers ) {
					xhr.setRequestHeader( i, headers[ i ] );
				}

				// Callback
				callback = function( type ) {
					return function() {
						if ( callback ) {
							callback = errorCallback = xhr.onload =
								xhr.onerror = xhr.onabort = xhr.ontimeout =
									xhr.onreadystatechange = null;

							if ( type === "abort" ) {
								xhr.abort();
							} else if ( type === "error" ) {

								// Support: IE <=9 only
								// On a manual native abort, IE9 throws
								// errors on any property access that is not readyState
								if ( typeof xhr.status !== "number" ) {
									complete( 0, "error" );
								} else {
									complete(

										// File: protocol always yields status 0; see #8605, #14207
										xhr.status,
										xhr.statusText
									);
								}
							} else {
								complete(
									xhrSuccessStatus[ xhr.status ] || xhr.status,
									xhr.statusText,

									// Support: IE <=9 only
									// IE9 has no XHR2 but throws on binary (trac-11426)
									// For XHR2 non-text, let the caller handle it (gh-2498)
									( xhr.responseType || "text" ) !== "text"  ||
									typeof xhr.responseText !== "string" ?
										{ binary: xhr.response } :
										{ text: xhr.responseText },
									xhr.getAllResponseHeaders()
								);
							}
						}
					};
				};

				// Listen to events
				xhr.onload = callback();
				errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" );

				// Support: IE 9 only
				// Use onreadystatechange to replace onabort
				// to handle uncaught aborts
				if ( xhr.onabort !== undefined ) {
					xhr.onabort = errorCallback;
				} else {
					xhr.onreadystatechange = function() {

						// Check readyState before timeout as it changes
						if ( xhr.readyState === 4 ) {

							// Allow onerror to be called first,
							// but that will not handle a native abort
							// Also, save errorCallback to a variable
							// as xhr.onerror cannot be accessed
							window.setTimeout( function() {
								if ( callback ) {
									errorCallback();
								}
							} );
						}
					};
				}

				// Create the abort callback
				callback = callback( "abort" );

				try {

					// Do send the request (this may raise an exception)
					xhr.send( options.hasContent && options.data || null );
				} catch ( e ) {

					// #14683: Only rethrow if this hasn't been notified as an error yet
					if ( callback ) {
						throw e;
					}
				}
			},

			abort: function() {
				if ( callback ) {
					callback();
				}
			}
		};
	}
} );




// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432)
jQuery.ajaxPrefilter( function( s ) {
	if ( s.crossDomain ) {
		s.contents.script = false;
	}
} );

// Install script dataType
jQuery.ajaxSetup( {
	accepts: {
		script: "text/javascript, application/javascript, " +
			"application/ecmascript, application/x-ecmascript"
	},
	contents: {
		script: /\b(?:java|ecma)script\b/
	},
	converters: {
		"text script": function( text ) {
			jQuery.globalEval( text );
			return text;
		}
	}
} );

// Handle cache's special case and crossDomain
jQuery.ajaxPrefilter( "script", function( s ) {
	if ( s.cache === undefined ) {
		s.cache = false;
	}
	if ( s.crossDomain ) {
		s.type = "GET";
	}
} );

// Bind script tag hack transport
jQuery.ajaxTransport( "script", function( s ) {

	// This transport only deals with cross domain or forced-by-attrs requests
	if ( s.crossDomain || s.scriptAttrs ) {
		var script, callback;
		return {
			send: function( _, complete ) {
				script = jQuery( "<script>" )
					.attr( s.scriptAttrs || {} )
					.prop( { charset: s.scriptCharset, src: s.url } )
					.on( "load error", callback = function( evt ) {
						script.remove();
						callback = null;
						if ( evt ) {
							complete( evt.type === "error" ? 404 : 200, evt.type );
						}
					} );

				// Use native DOM manipulation to avoid our domManip AJAX trickery
				document.head.appendChild( script[ 0 ] );
			},
			abort: function() {
				if ( callback ) {
					callback();
				}
			}
		};
	}
} );




var oldCallbacks = [],
	rjsonp = /(=)\?(?=&|$)|\?\?/;

// Default jsonp settings
jQuery.ajaxSetup( {
	jsonp: "callback",
	jsonpCallback: function() {
		var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce.guid++ ) );
		this[ callback ] = true;
		return callback;
	}
} );

// Detect, normalize options and install callbacks for jsonp requests
jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {

	var callbackName, overwritten, responseContainer,
		jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
			"url" :
			typeof s.data === "string" &&
				( s.contentType || "" )
					.indexOf( "application/x-www-form-urlencoded" ) === 0 &&
				rjsonp.test( s.data ) && "data"
		);

	// Handle iff the expected data type is "jsonp" or we have a parameter to set
	if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {

		// Get callback name, remembering preexisting value associated with it
		callbackName = s.jsonpCallback = isFunction( s.jsonpCallback ) ?
			s.jsonpCallback() :
			s.jsonpCallback;

		// Insert callback into url or form data
		if ( jsonProp ) {
			s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
		} else if ( s.jsonp !== false ) {
			s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
		}

		// Use data converter to retrieve json after script execution
		s.converters[ "script json" ] = function() {
			if ( !responseContainer ) {
				jQuery.error( callbackName + " was not called" );
			}
			return responseContainer[ 0 ];
		};

		// Force json dataType
		s.dataTypes[ 0 ] = "json";

		// Install callback
		overwritten = window[ callbackName ];
		window[ callbackName ] = function() {
			responseContainer = arguments;
		};

		// Clean-up function (fires after converters)
		jqXHR.always( function() {

			// If previous value didn't exist - remove it
			if ( overwritten === undefined ) {
				jQuery( window ).removeProp( callbackName );

			// Otherwise restore preexisting value
			} else {
				window[ callbackName ] = overwritten;
			}

			// Save back as free
			if ( s[ callbackName ] ) {

				// Make sure that re-using the options doesn't screw things around
				s.jsonpCallback = originalSettings.jsonpCallback;

				// Save the callback name for future use
				oldCallbacks.push( callbackName );
			}

			// Call if it was a function and we have a response
			if ( responseContainer && isFunction( overwritten ) ) {
				overwritten( responseContainer[ 0 ] );
			}

			responseContainer = overwritten = undefined;
		} );

		// Delegate to script
		return "script";
	}
} );




// Support: Safari 8 only
// In Safari 8 documents created via document.implementation.createHTMLDocument
// collapse sibling forms: the second one becomes a child of the first one.
// Because of that, this security measure has to be disabled in Safari 8.
// https://bugs.webkit.org/show_bug.cgi?id=137337
support.createHTMLDocument = ( function() {
	var body = document.implementation.createHTMLDocument( "" ).body;
	body.innerHTML = "<form></form><form></form>";
	return body.childNodes.length === 2;
} )();


// Argument "data" should be string of html
// context (optional): If specified, the fragment will be created in this context,
// defaults to document
// keepScripts (optional): If true, will include scripts passed in the html string
jQuery.parseHTML = function( data, context, keepScripts ) {
	if ( typeof data !== "string" ) {
		return [];
	}
	if ( typeof context === "boolean" ) {
		keepScripts = context;
		context = false;
	}

	var base, parsed, scripts;

	if ( !context ) {

		// Stop scripts or inline event handlers from being executed immediately
		// by using document.implementation
		if ( support.createHTMLDocument ) {
			context = document.implementation.createHTMLDocument( "" );

			// Set the base href for the created document
			// so any parsed elements with URLs
			// are based on the document's URL (gh-2965)
			base = context.createElement( "base" );
			base.href = document.location.href;
			context.head.appendChild( base );
		} else {
			context = document;
		}
	}

	parsed = rsingleTag.exec( data );
	scripts = !keepScripts && [];

	// Single tag
	if ( parsed ) {
		return [ context.createElement( parsed[ 1 ] ) ];
	}

	parsed = buildFragment( [ data ], context, scripts );

	if ( scripts && scripts.length ) {
		jQuery( scripts ).remove();
	}

	return jQuery.merge( [], parsed.childNodes );
};


/**
 * Load a url into a page
 */
jQuery.fn.load = function( url, params, callback ) {
	var selector, type, response,
		self = this,
		off = url.indexOf( " " );

	if ( off > -1 ) {
		selector = stripAndCollapse( url.slice( off ) );
		url = url.slice( 0, off );
	}

	// If it's a function
	if ( isFunction( params ) ) {

		// We assume that it's the callback
		callback = params;
		params = undefined;

	// Otherwise, build a param string
	} else if ( params && typeof params === "object" ) {
		type = "POST";
	}

	// If we have elements to modify, make the request
	if ( self.length > 0 ) {
		jQuery.ajax( {
			url: url,

			// If "type" variable is undefined, then "GET" method will be used.
			// Make value of this field explicit since
			// user can override it through ajaxSetup method
			type: type || "GET",
			dataType: "html",
			data: params
		} ).done( function( responseText ) {

			// Save response for use in complete callback
			response = arguments;

			self.html( selector ?

				// If a selector was specified, locate the right elements in a dummy div
				// Exclude scripts to avoid IE 'Permission Denied' errors
				jQuery( "<div>" ).append( jQuery.parseHTML( responseText ) ).find( selector ) :

				// Otherwise use the full result
				responseText );

		// If the request succeeds, this function gets "data", "status", "jqXHR"
		// but they are ignored because response was set above.
		// If it fails, this function gets "jqXHR", "status", "error"
		} ).always( callback && function( jqXHR, status ) {
			self.each( function() {
				callback.apply( this, response || [ jqXHR.responseText, status, jqXHR ] );
			} );
		} );
	}

	return this;
};




jQuery.expr.pseudos.animated = function( elem ) {
	return jQuery.grep( jQuery.timers, function( fn ) {
		return elem === fn.elem;
	} ).length;
};




jQuery.offset = {
	setOffset: function( elem, options, i ) {
		var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
			position = jQuery.css( elem, "position" ),
			curElem = jQuery( elem ),
			props = {};

		// Set position first, in-case top/left are set even on static elem
		if ( position === "static" ) {
			elem.style.position = "relative";
		}

		curOffset = curElem.offset();
		curCSSTop = jQuery.css( elem, "top" );
		curCSSLeft = jQuery.css( elem, "left" );
		calculatePosition = ( position === "absolute" || position === "fixed" ) &&
			( curCSSTop + curCSSLeft ).indexOf( "auto" ) > -1;

		// Need to be able to calculate position if either
		// top or left is auto and position is either absolute or fixed
		if ( calculatePosition ) {
			curPosition = curElem.position();
			curTop = curPosition.top;
			curLeft = curPosition.left;

		} else {
			curTop = parseFloat( curCSSTop ) || 0;
			curLeft = parseFloat( curCSSLeft ) || 0;
		}

		if ( isFunction( options ) ) {

			// Use jQuery.extend here to allow modification of coordinates argument (gh-1848)
			options = options.call( elem, i, jQuery.extend( {}, curOffset ) );
		}

		if ( options.top != null ) {
			props.top = ( options.top - curOffset.top ) + curTop;
		}
		if ( options.left != null ) {
			props.left = ( options.left - curOffset.left ) + curLeft;
		}

		if ( "using" in options ) {
			options.using.call( elem, props );

		} else {
			curElem.css( props );
		}
	}
};

jQuery.fn.extend( {

	// offset() relates an element's border box to the document origin
	offset: function( options ) {

		// Preserve chaining for setter
		if ( arguments.length ) {
			return options === undefined ?
				this :
				this.each( function( i ) {
					jQuery.offset.setOffset( this, options, i );
				} );
		}

		var rect, win,
			elem = this[ 0 ];

		if ( !elem ) {
			return;
		}

		// Return zeros for disconnected and hidden (display: none) elements (gh-2310)
		// Support: IE <=11 only
		// Running getBoundingClientRect on a
		// disconnected node in IE throws an error
		if ( !elem.getClientRects().length ) {
			return { top: 0, left: 0 };
		}

		// Get document-relative position by adding viewport scroll to viewport-relative gBCR
		rect = elem.getBoundingClientRect();
		win = elem.ownerDocument.defaultView;
		return {
			top: rect.top + win.pageYOffset,
			left: rect.left + win.pageXOffset
		};
	},

	// position() relates an element's margin box to its offset parent's padding box
	// This corresponds to the behavior of CSS absolute positioning
	position: function() {
		if ( !this[ 0 ] ) {
			return;
		}

		var offsetParent, offset, doc,
			elem = this[ 0 ],
			parentOffset = { top: 0, left: 0 };

		// position:fixed elements are offset from the viewport, which itself always has zero offset
		if ( jQuery.css( elem, "position" ) === "fixed" ) {

			// Assume position:fixed implies availability of getBoundingClientRect
			offset = elem.getBoundingClientRect();

		} else {
			offset = this.offset();

			// Account for the *real* offset parent, which can be the document or its root element
			// when a statically positioned element is identified
			doc = elem.ownerDocument;
			offsetParent = elem.offsetParent || doc.documentElement;
			while ( offsetParent &&
				( offsetParent === doc.body || offsetParent === doc.documentElement ) &&
				jQuery.css( offsetParent, "position" ) === "static" ) {

				offsetParent = offsetParent.parentNode;
			}
			if ( offsetParent && offsetParent !== elem && offsetParent.nodeType === 1 ) {

				// Incorporate borders into its offset, since they are outside its content origin
				parentOffset = jQuery( offsetParent ).offset();
				parentOffset.top += jQuery.css( offsetParent, "borderTopWidth", true );
				parentOffset.left += jQuery.css( offsetParent, "borderLeftWidth", true );
			}
		}

		// Subtract parent offsets and element margins
		return {
			top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
			left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
		};
	},

	// This method will return documentElement in the following cases:
	// 1) For the element inside the iframe without offsetParent, this method will return
	//    documentElement of the parent window
	// 2) For the hidden or detached element
	// 3) For body or html element, i.e. in case of the html node - it will return itself
	//
	// but those exceptions were never presented as a real life use-cases
	// and might be considered as more preferable results.
	//
	// This logic, however, is not guaranteed and can change at any point in the future
	offsetParent: function() {
		return this.map( function() {
			var offsetParent = this.offsetParent;

			while ( offsetParent && jQuery.css( offsetParent, "position" ) === "static" ) {
				offsetParent = offsetParent.offsetParent;
			}

			return offsetParent || documentElement;
		} );
	}
} );

// Create scrollLeft and scrollTop methods
jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
	var top = "pageYOffset" === prop;

	jQuery.fn[ method ] = function( val ) {
		return access( this, function( elem, method, val ) {

			// Coalesce documents and windows
			var win;
			if ( isWindow( elem ) ) {
				win = elem;
			} else if ( elem.nodeType === 9 ) {
				win = elem.defaultView;
			}

			if ( val === undefined ) {
				return win ? win[ prop ] : elem[ method ];
			}

			if ( win ) {
				win.scrollTo(
					!top ? val : win.pageXOffset,
					top ? val : win.pageYOffset
				);

			} else {
				elem[ method ] = val;
			}
		}, method, val, arguments.length );
	};
} );

// Support: Safari <=7 - 9.1, Chrome <=37 - 49
// Add the top/left cssHooks using jQuery.fn.position
// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
// Blink bug: https://bugs.chromium.org/p/chromium/issues/detail?id=589347
// getComputedStyle returns percent when specified for top/left/bottom/right;
// rather than make the css module depend on the offset module, just check for it here
jQuery.each( [ "top", "left" ], function( _i, prop ) {
	jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
		function( elem, computed ) {
			if ( computed ) {
				computed = curCSS( elem, prop );

				// If curCSS returns percentage, fallback to offset
				return rnumnonpx.test( computed ) ?
					jQuery( elem ).position()[ prop ] + "px" :
					computed;
			}
		}
	);
} );


// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
	jQuery.each( {
		padding: "inner" + name,
		content: type,
		"": "outer" + name
	}, function( defaultExtra, funcName ) {

		// Margin is only for outerHeight, outerWidth
		jQuery.fn[ funcName ] = function( margin, value ) {
			var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
				extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );

			return access( this, function( elem, type, value ) {
				var doc;

				if ( isWindow( elem ) ) {

					// $( window ).outerWidth/Height return w/h including scrollbars (gh-1729)
					return funcName.indexOf( "outer" ) === 0 ?
						elem[ "inner" + name ] :
						elem.document.documentElement[ "client" + name ];
				}

				// Get document width or height
				if ( elem.nodeType === 9 ) {
					doc = elem.documentElement;

					// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
					// whichever is greatest
					return Math.max(
						elem.body[ "scroll" + name ], doc[ "scroll" + name ],
						elem.body[ "offset" + name ], doc[ "offset" + name ],
						doc[ "client" + name ]
					);
				}

				return value === undefined ?

					// Get width or height on the element, requesting but not forcing parseFloat
					jQuery.css( elem, type, extra ) :

					// Set width or height on the element
					jQuery.style( elem, type, value, extra );
			}, type, chainable ? margin : undefined, chainable );
		};
	} );
} );


jQuery.each( [
	"ajaxStart",
	"ajaxStop",
	"ajaxComplete",
	"ajaxError",
	"ajaxSuccess",
	"ajaxSend"
], function( _i, type ) {
	jQuery.fn[ type ] = function( fn ) {
		return this.on( type, fn );
	};
} );




jQuery.fn.extend( {

	bind: function( types, data, fn ) {
		return this.on( types, null, data, fn );
	},
	unbind: function( types, fn ) {
		return this.off( types, null, fn );
	},

	delegate: function( selector, types, data, fn ) {
		return this.on( types, selector, data, fn );
	},
	undelegate: function( selector, types, fn ) {

		// ( namespace ) or ( selector, types [, fn] )
		return arguments.length === 1 ?
			this.off( selector, "**" ) :
			this.off( types, selector || "**", fn );
	},

	hover: function( fnOver, fnOut ) {
		return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
	}
} );

jQuery.each(
	( "blur focus focusin focusout resize scroll click dblclick " +
	"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
	"change select submit keydown keypress keyup contextmenu" ).split( " " ),
	function( _i, name ) {

		// Handle event binding
		jQuery.fn[ name ] = function( data, fn ) {
			return arguments.length > 0 ?
				this.on( name, null, data, fn ) :
				this.trigger( name );
		};
	}
);




// Support: Android <=4.0 only
// Make sure we trim BOM and NBSP
var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;

// Bind a function to a context, optionally partially applying any
// arguments.
// jQuery.proxy is deprecated to promote standards (specifically Function#bind)
// However, it is not slated for removal any time soon
jQuery.proxy = function( fn, context ) {
	var tmp, args, proxy;

	if ( typeof context === "string" ) {
		tmp = fn[ context ];
		context = fn;
		fn = tmp;
	}

	// Quick check to determine if target is callable, in the spec
	// this throws a TypeError, but we will just return undefined.
	if ( !isFunction( fn ) ) {
		return undefined;
	}

	// Simulated bind
	args = slice.call( arguments, 2 );
	proxy = function() {
		return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
	};

	// Set the guid of unique handler to the same of original handler, so it can be removed
	proxy.guid = fn.guid = fn.guid || jQuery.guid++;

	return proxy;
};

jQuery.holdReady = function( hold ) {
	if ( hold ) {
		jQuery.readyWait++;
	} else {
		jQuery.ready( true );
	}
};
jQuery.isArray = Array.isArray;
jQuery.parseJSON = JSON.parse;
jQuery.nodeName = nodeName;
jQuery.isFunction = isFunction;
jQuery.isWindow = isWindow;
jQuery.camelCase = camelCase;
jQuery.type = toType;

jQuery.now = Date.now;

jQuery.isNumeric = function( obj ) {

	// As of jQuery 3.0, isNumeric is limited to
	// strings and numbers (primitives or objects)
	// that can be coerced to finite numbers (gh-2662)
	var type = jQuery.type( obj );
	return ( type === "number" || type === "string" ) &&

		// parseFloat NaNs numeric-cast false positives ("")
		// ...but misinterprets leading-number strings, particularly hex literals ("0x...")
		// subtraction forces infinities to NaN
		!isNaN( obj - parseFloat( obj ) );
};

jQuery.trim = function( text ) {
	return text == null ?
		"" :
		( text + "" ).replace( rtrim, "" );
};



// Register as a named AMD module, since jQuery can be concatenated with other
// files that may use define, but not via a proper concatenation script that
// understands anonymous AMD modules. A named AMD is safest and most robust
// way to register. Lowercase jquery is used because AMD module names are
// derived from file names, and jQuery is normally delivered in a lowercase
// file name. Do this after creating the global so that if an AMD module wants
// to call noConflict to hide this version of jQuery, it will work.

// Note that for maximum portability, libraries that are not jQuery should
// declare themselves as anonymous modules, and avoid setting a global if an
// AMD loader is present. jQuery is a special case. For more information, see
// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon

if ( typeof define === "function" && define.amd ) {
	define( "jquery", [], function() {
		return jQuery;
	} );
}




var

	// Map over jQuery in case of overwrite
	_jQuery = window.jQuery,

	// Map over the $ in case of overwrite
	_$ = window.$;

jQuery.noConflict = function( deep ) {
	if ( window.$ === jQuery ) {
		window.$ = _$;
	}

	if ( deep && window.jQuery === jQuery ) {
		window.jQuery = _jQuery;
	}

	return jQuery;
};

// Expose jQuery and $ identifiers, even in AMD
// (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
// and CommonJS for browser emulators (#13566)
if ( typeof noGlobal === "undefined" ) {
	window.jQuery = window.$ = jQuery;
}




return jQuery;
} );

/**
 * @license AngularJS 1.8.11
 * (c) 2010-2020 Google LLC. http://angularjs.org
 * (c) 2025 Perforce
 * Use of these services or software is subject to specific restrictions and limitations. All rights are reserved.
 * License: MIT
 */
(function(window) {'use strict';

/* exported
  minErrConfig,
  errorHandlingConfig,
  isValidObjectMaxDepth
*/

var minErrConfig = {
  objectMaxDepth: 5,
  urlErrorParamsEnabled: true
};

/**
 * @ngdoc function
 * @name angular.errorHandlingConfig
 * @module ng
 * @kind function
 *
 * @description
 * Configure several aspects of error handling in AngularJS if used as a setter or return the
 * current configuration if used as a getter. The following options are supported:
 *
 * - **objectMaxDepth**: The maximum depth to which objects are traversed when stringified for error messages.
 *
 * Omitted or undefined options will leave the corresponding configuration values unchanged.
 *
 * @param {Object=} config - The configuration object. May only contain the options that need to be
 *     updated. Supported keys:
 *
 * * `objectMaxDepth`  **{Number}** - The max depth for stringifying objects. Setting to a
 *   non-positive or non-numeric value, removes the max depth limit.
 *   Default: 5
 *
 * * `urlErrorParamsEnabled`  **{Boolean}** - Specifies whether the generated error url will
 *   contain the parameters of the thrown error. Disabling the parameters can be useful if the
 *   generated error url is very long.
 *
 *   Default: true. When used without argument, it returns the current value.
 */
function errorHandlingConfig(config) {
  if (isObject(config)) {
    if (isDefined(config.objectMaxDepth)) {
      minErrConfig.objectMaxDepth = isValidObjectMaxDepth(config.objectMaxDepth) ? config.objectMaxDepth : NaN;
    }
    if (isDefined(config.urlErrorParamsEnabled) && isBoolean(config.urlErrorParamsEnabled)) {
      minErrConfig.urlErrorParamsEnabled = config.urlErrorParamsEnabled;
    }
  } else {
    return minErrConfig;
  }
}

/**
 * @private
 * @param {Number} maxDepth
 * @return {boolean}
 */
function isValidObjectMaxDepth(maxDepth) {
  return isNumber(maxDepth) && maxDepth > 0;
}


/**
 * @description
 *
 * This object provides a utility for producing rich Error messages within
 * AngularJS. It can be called as follows:
 *
 * var exampleMinErr = minErr('example');
 * throw exampleMinErr('one', 'This {0} is {1}', foo, bar);
 *
 * The above creates an instance of minErr in the example namespace. The
 * resulting error will have a namespaced error code of example.one.  The
 * resulting error will replace {0} with the value of foo, and {1} with the
 * value of bar. The object is not restricted in the number of arguments it can
 * take.
 *
 * If fewer arguments are specified than necessary for interpolation, the extra
 * interpolation markers will be preserved in the final string.
 *
 * Since data will be parsed statically during a build step, some restrictions
 * are applied with respect to how minErr instances are created and called.
 * Instances should have names of the form namespaceMinErr for a minErr created
 * using minErr('namespace'). Error codes, namespaces and template strings
 * should all be static strings, not variables or general expressions.
 *
 * @param {string} module The namespace to use for the new minErr instance.
 * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning
 *   error from returned function, for cases when a particular type of error is useful.
 * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance
 */

function minErr(module, ErrorConstructor) {
  ErrorConstructor = ErrorConstructor || Error;

  var url = 'https://errors.angularjs.org/1.8.11/';
  var regex = url.replace('.', '\\.') + '[\\s\\S]*';
  var errRegExp = new RegExp(regex, 'g');

  return function() {
    var code = arguments[0],
      template = arguments[1],
      message = '[' + (module ? module + ':' : '') + code + '] ',
      templateArgs = sliceArgs(arguments, 2).map(function(arg) {
        return toDebugString(arg, minErrConfig.objectMaxDepth);
      }),
      paramPrefix, i;

    // A minErr message has two parts: the message itself and the url that contains the
    // encoded message.
    // The message's parameters can contain other error messages which also include error urls.
    // To prevent the messages from getting too long, we strip the error urls from the parameters.

    message += template.replace(/\{\d+\}/g, function(match) {
      var index = +match.slice(1, -1);

      if (index < templateArgs.length) {
        return templateArgs[index].replace(errRegExp, '');
      }

      return match;
    });

    message += '\n' + url + (module ? module + '/' : '') + code;

    if (minErrConfig.urlErrorParamsEnabled) {
      for (i = 0, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
        message += paramPrefix + 'p' + i + '=' + encodeURIComponent(templateArgs[i]);
      }
    }

    return new ErrorConstructor(message);
  };
}

/* We need to tell ESLint what variables are being exported */
/* exported
  angular,
  msie,
  jqLite,
  jQuery,
  slice,
  splice,
  push,
  toString,
  minErrConfig,
  errorHandlingConfig,
  isValidObjectMaxDepth,
  ngMinErr,
  angularModule,
  uid,
  REGEX_STRING_REGEXP,
  VALIDITY_STATE_PROPERTY,

  lowercase,
  uppercase,
  nodeName_,
  isArrayLike,
  forEach,
  forEachSorted,
  reverseParams,
  nextUid,
  setHashKey,
  extend,
  toInt,
  inherit,
  merge,
  noop,
  identity,
  valueFn,
  isUndefined,
  isDefined,
  isObject,
  isBlankObject,
  isString,
  isNumber,
  isNumberNaN,
  isDate,
  isError,
  isArray,
  isFunction,
  isRegExp,
  isWindow,
  isScope,
  isFile,
  isFormData,
  isBlob,
  isBoolean,
  isPromiseLike,
  trim,
  escapeForRegexp,
  isElement,
  makeMap,
  includes,
  arrayRemove,
  copy,
  simpleCompare,
  equals,
  csp,
  jq,
  concat,
  sliceArgs,
  bind,
  toJsonReplacer,
  toJson,
  fromJson,
  convertTimezoneToLocal,
  timezoneToOffset,
  addDateMinutes,
  startingTag,
  tryDecodeURIComponent,
  parseKeyValue,
  toKeyValue,
  encodeUriSegment,
  encodeUriQuery,
  angularInit,
  bootstrap,
  getTestability,
  snake_case,
  bindJQuery,
  assertArg,
  assertArgFn,
  assertNotHasOwnProperty,
  getter,
  getBlockNodes,
  hasOwnProperty,
  createMap,
  stringify,
  UNSAFE_restoreLegacyJqLiteXHTMLReplacement,

  NODE_TYPE_ELEMENT,
  NODE_TYPE_ATTRIBUTE,
  NODE_TYPE_TEXT,
  NODE_TYPE_COMMENT,
  NODE_TYPE_DOCUMENT,
  NODE_TYPE_DOCUMENT_FRAGMENT
*/

////////////////////////////////////

/**
 * @ngdoc module
 * @name ng
 * @module ng
 * @installation
 * @description
 *
 * The ng module is loaded by default when an AngularJS application is started. The module itself
 * contains the essential components for an AngularJS application to function. The table below
 * lists a high level breakdown of each of the services/factories, filters, directives and testing
 * components available within this core module.
 *
 */

var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/;

// The name of a form control's ValidityState property.
// This is used so that it's possible for internal tests to create mock ValidityStates.
var VALIDITY_STATE_PROPERTY = 'validity';


var hasOwnProperty = Object.prototype.hasOwnProperty;

/**
 * @private
 *
 * @description Converts the specified string to lowercase.
 * @param {string} string String to be converted to lowercase.
 * @returns {string} Lowercased string.
 */
var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;};

/**
 * @private
 *
 * @description Converts the specified string to uppercase.
 * @param {string} string String to be converted to uppercase.
 * @returns {string} Uppercased string.
 */
var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;};


var
    msie,             // holds major version number for IE, or NaN if UA is not IE.
    jqLite,           // delay binding since jQuery could be loaded after us.
    jQuery,           // delay binding
    slice             = [].slice,
    splice            = [].splice,
    push              = [].push,
    toString          = Object.prototype.toString,
    getPrototypeOf    = Object.getPrototypeOf,
    ngMinErr          = minErr('ng'),

    /** @name angular */
    angular           = window.angular || (window.angular = {}),
    angularModule,
    uid               = 0;

// Support: IE 9-11 only
/**
 * documentMode is an IE-only property
 * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx
 */
msie = window.document.documentMode;


/**
 * @private
 * @param {*} obj
 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments,
 *                   String ...)
 */
function isArrayLike(obj) {

  // `null`, `undefined` and `window` are not array-like
  if (obj == null || isWindow(obj)) return false;

  // arrays, strings and jQuery/jqLite objects are array like
  // * jqLite is either the jQuery or jqLite constructor function
  // * we have to check the existence of jqLite first as this method is called
  //   via the forEach method when constructing the jqLite object in the first place
  if (isArray(obj) || isString(obj) || (jqLite && obj instanceof jqLite)) return true;

  // Support: iOS 8.2 (not reproducible in simulator)
  // "length" in obj used to prevent JIT error (gh-11508)
  var length = 'length' in Object(obj) && obj.length;

  // NodeList objects (with `item` method) and
  // other objects with suitable length characteristics are array-like
  return isNumber(length) && (length >= 0 && (length - 1) in obj || typeof obj.item === 'function');

}

/**
 * @ngdoc function
 * @name angular.forEach
 * @module ng
 * @kind function
 *
 * @description
 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an
 * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value`
 * is the value of an object property or an array element, `key` is the object property key or
 * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional.
 *
 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters
 * using the `hasOwnProperty` method.
 *
 * Unlike ES262's
 * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18),
 * providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just
 * return the value provided.
 *
   ```js
     var values = {name: 'misko', gender: 'male'};
     var log = [];
     angular.forEach(values, function(value, key) {
       this.push(key + ': ' + value);
     }, log);
     expect(log).toEqual(['name: misko', 'gender: male']);
   ```
 *
 * @param {Object|Array} obj Object to iterate over.
 * @param {Function} iterator Iterator function.
 * @param {Object=} context Object to become context (`this`) for the iterator function.
 * @returns {Object|Array} Reference to `obj`.
 */

function forEach(obj, iterator, context) {
  var key, length;
  if (obj) {
    if (isFunction(obj)) {
      for (key in obj) {
        if (key !== 'prototype' && key !== 'length' && key !== 'name' && obj.hasOwnProperty(key)) {
          iterator.call(context, obj[key], key, obj);
        }
      }
    } else if (isArray(obj) || isArrayLike(obj)) {
      var isPrimitive = typeof obj !== 'object';
      for (key = 0, length = obj.length; key < length; key++) {
        if (isPrimitive || key in obj) {
          iterator.call(context, obj[key], key, obj);
        }
      }
    } else if (obj.forEach && obj.forEach !== forEach) {
        obj.forEach(iterator, context, obj);
    } else if (isBlankObject(obj)) {
      // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
      for (key in obj) {
        iterator.call(context, obj[key], key, obj);
      }
    } else if (typeof obj.hasOwnProperty === 'function') {
      // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed
      for (key in obj) {
        if (obj.hasOwnProperty(key)) {
          iterator.call(context, obj[key], key, obj);
        }
      }
    } else {
      // Slow path for objects which do not have a method `hasOwnProperty`
      for (key in obj) {
        if (hasOwnProperty.call(obj, key)) {
          iterator.call(context, obj[key], key, obj);
        }
      }
    }
  }
  return obj;
}

function forEachSorted(obj, iterator, context) {
  var keys = Object.keys(obj).sort();
  for (var i = 0; i < keys.length; i++) {
    iterator.call(context, obj[keys[i]], keys[i]);
  }
  return keys;
}


/**
 * when using forEach the params are value, key, but it is often useful to have key, value.
 * @param {function(string, *)} iteratorFn
 * @returns {function(*, string)}
 */
function reverseParams(iteratorFn) {
  return function(value, key) {iteratorFn(key, value);};
}

/**
 * A consistent way of creating unique IDs in angular.
 *
 * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before
 * we hit number precision issues in JavaScript.
 *
 * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M
 *
 * @returns {number} an unique alpha-numeric string
 */
function nextUid() {
  return ++uid;
}


/**
 * Set or clear the hashkey for an object.
 * @param obj object
 * @param h the hashkey (!truthy to delete the hashkey)
 */
function setHashKey(obj, h) {
  if (h) {
    obj.$$hashKey = h;
  } else {
    delete obj.$$hashKey;
  }
}


function baseExtend(dst, objs, deep) {
  var h = dst.$$hashKey;

  for (var i = 0, ii = objs.length; i < ii; ++i) {
    var obj = objs[i];
    if (!isObject(obj) && !isFunction(obj)) continue;
    var keys = Object.keys(obj);
    for (var j = 0, jj = keys.length; j < jj; j++) {
      var key = keys[j];
      var src = obj[key];

      if (deep && isObject(src)) {
        if (isDate(src)) {
          dst[key] = new Date(src.valueOf());
        } else if (isRegExp(src)) {
          dst[key] = new RegExp(src);
        } else if (src.nodeName) {
          dst[key] = src.cloneNode(true);
        } else if (isElement(src)) {
          dst[key] = src.clone();
        } else {
          if (key !== '__proto__') {
            if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
            baseExtend(dst[key], [src], true);
          }
        }
      } else {
        dst[key] = src;
      }
    }
  }

  setHashKey(dst, h);
  return dst;
}

/**
 * @ngdoc function
 * @name angular.extend
 * @module ng
 * @kind function
 *
 * @description
 * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
 * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`.
 *
 * **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use
 * {@link angular.merge} for this.
 *
 * @param {Object} dst Destination object.
 * @param {...Object} src Source object(s).
 * @returns {Object} Reference to `dst`.
 */
function extend(dst) {
  return baseExtend(dst, slice.call(arguments, 1), false);
}


/**
* @ngdoc function
* @name angular.merge
* @module ng
* @kind function
*
* @description
* Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
* by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`.
*
* Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source
* objects, performing a deep copy.
*
* @deprecated
* sinceVersion="1.6.5"
* This function is deprecated, but will not be removed in the 1.x lifecycle.
* There are edge cases (see {@link angular.merge#known-issues known issues}) that are not
* supported by this function. We suggest using another, similar library for all-purpose merging,
* such as [lodash's merge()](https://lodash.com/docs/4.17.4#merge).
*
* @knownIssue
* This is a list of (known) object types that are not handled correctly by this function:
* - [`Blob`](https://developer.mozilla.org/docs/Web/API/Blob)
* - [`MediaStream`](https://developer.mozilla.org/docs/Web/API/MediaStream)
* - [`CanvasGradient`](https://developer.mozilla.org/docs/Web/API/CanvasGradient)
* - AngularJS {@link $rootScope.Scope scopes};
*
* `angular.merge` also does not support merging objects with circular references.
*
* @param {Object} dst Destination object.
* @param {...Object} src Source object(s).
* @returns {Object} Reference to `dst`.
*/
function merge(dst) {
  return baseExtend(dst, slice.call(arguments, 1), true);
}



function toInt(str) {
  return parseInt(str, 10);
}

var isNumberNaN = Number.isNaN || function isNumberNaN(num) {
  // eslint-disable-next-line no-self-compare
  return num !== num;
};


function inherit(parent, extra) {
  return extend(Object.create(parent), extra);
}

/**
 * @ngdoc function
 * @name angular.noop
 * @module ng
 * @kind function
 *
 * @description
 * A function that performs no operations. This function can be useful when writing code in the
 * functional style.
   ```js
     function foo(callback) {
       var result = calculateResult();
       (callback || angular.noop)(result);
     }
   ```
 */
function noop() {}
noop.$inject = [];


/**
 * @ngdoc function
 * @name angular.identity
 * @module ng
 * @kind function
 *
 * @description
 * A function that returns its first argument. This function is useful when writing code in the
 * functional style.
 *
   ```js
   function transformer(transformationFn, value) {
     return (transformationFn || angular.identity)(value);
   };

   // E.g.
   function getResult(fn, input) {
     return (fn || angular.identity)(input);
   };

   getResult(function(n) { return n * 2; }, 21);   // returns 42
   getResult(null, 21);                            // returns 21
   getResult(undefined, 21);                       // returns 21
   ```
 *
 * @param {*} value to be returned.
 * @returns {*} the value passed in.
 */
function identity($) {return $;}
identity.$inject = [];


function valueFn(value) {return function valueRef() {return value;};}

function hasCustomToString(obj) {
  return isFunction(obj.toString) && obj.toString !== toString;
}


/**
 * @ngdoc function
 * @name angular.isUndefined
 * @module ng
 * @kind function
 *
 * @description
 * Determines if a reference is undefined.
 *
 * @param {*} value Reference to check.
 * @returns {boolean} True if `value` is undefined.
 */
function isUndefined(value) {return typeof value === 'undefined';}


/**
 * @ngdoc function
 * @name angular.isDefined
 * @module ng
 * @kind function
 *
 * @description
 * Determines if a reference is defined.
 *
 * @param {*} value Reference to check.
 * @returns {boolean} True if `value` is defined.
 */
function isDefined(value) {return typeof value !== 'undefined';}


/**
 * @ngdoc function
 * @name angular.isObject
 * @module ng
 * @kind function
 *
 * @description
 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not
 * considered to be objects. Note that JavaScript arrays are objects.
 *
 * @param {*} value Reference to check.
 * @returns {boolean} True if `value` is an `Object` but not `null`.
 */
function isObject(value) {
  // http://jsperf.com/isobject4
  return value !== null && typeof value === 'object';
}


/**
 * Determine if a value is an object with a null prototype
 *
 * @returns {boolean} True if `value` is an `Object` with a null prototype
 */
function isBlankObject(value) {
  return value !== null && typeof value === 'object' && !getPrototypeOf(value);
}


/**
 * @ngdoc function
 * @name angular.isString
 * @module ng
 * @kind function
 *
 * @description
 * Determines if a reference is a `String`.
 *
 * @param {*} value Reference to check.
 * @returns {boolean} True if `value` is a `String`.
 */
function isString(value) {return typeof value === 'string';}


/**
 * @ngdoc function
 * @name angular.isNumber
 * @module ng
 * @kind function
 *
 * @description
 * Determines if a reference is a `Number`.
 *
 * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`.
 *
 * If you wish to exclude these then you can use the native
 * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite)
 * method.
 *
 * @param {*} value Reference to check.
 * @returns {boolean} True if `value` is a `Number`.
 */
function isNumber(value) {return typeof value === 'number';}


/**
 * @ngdoc function
 * @name angular.isDate
 * @module ng
 * @kind function
 *
 * @description
 * Determines if a value is a date.
 *
 * @param {*} value Reference to check.
 * @returns {boolean} True if `value` is a `Date`.
 */
function isDate(value) {
  return toString.call(value) === '[object Date]';
}


/**
 * @ngdoc function
 * @name angular.isArray
 * @module ng
 * @kind function
 *
 * @description
 * Determines if a reference is an `Array`.
 *
 * @param {*} value Reference to check.
 * @returns {boolean} True if `value` is an `Array`.
 */
function isArray(arr) {
  return Array.isArray(arr) || arr instanceof Array;
}

/**
 * @description
 * Determines if a reference is an `Error`.
 * Loosely based on https://www.npmjs.com/package/iserror
 *
 * @param {*} value Reference to check.
 * @returns {boolean} True if `value` is an `Error`.
 */
function isError(value) {
  var tag = toString.call(value);
  switch (tag) {
    case '[object Error]': return true;
    case '[object Exception]': return true;
    case '[object DOMException]': return true;
    default: return value instanceof Error;
  }
}

/**
 * @ngdoc function
 * @name angular.isFunction
 * @module ng
 * @kind function
 *
 * @description
 * Determines if a reference is a `Function`.
 *
 * @param {*} value Reference to check.
 * @returns {boolean} True if `value` is a `Function`.
 */
function isFunction(value) {return typeof value === 'function';}


/**
 * Determines if a value is a regular expression object.
 *
 * @private
 * @param {*} value Reference to check.
 * @returns {boolean} True if `value` is a `RegExp`.
 */
function isRegExp(value) {
  return toString.call(value) === '[object RegExp]';
}


/**
 * Checks if `obj` is a window object.
 *
 * @private
 * @param {*} obj Object to check
 * @returns {boolean} True if `obj` is a window obj.
 */
function isWindow(obj) {
  return obj && obj.window === obj;
}


function isScope(obj) {
  return obj && obj.$evalAsync && obj.$watch;
}


function isFile(obj) {
  return toString.call(obj) === '[object File]';
}


function isFormData(obj) {
  return toString.call(obj) === '[object FormData]';
}


function isBlob(obj) {
  return toString.call(obj) === '[object Blob]';
}


function isBoolean(value) {
  return typeof value === 'boolean';
}


function isPromiseLike(obj) {
  return obj && isFunction(obj.then);
}


var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array]$/;
function isTypedArray(value) {
  return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value));
}

function isArrayBuffer(obj) {
  return toString.call(obj) === '[object ArrayBuffer]';
}


var trim = function(value) {
  return isString(value) ? value.trim() : value;
};

// Copied from:
// http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021
// Prereq: s is a string.
var escapeForRegexp = function(s) {
  return s
    .replace(/([-()[\]{}+?*.$^|,:#<!\\])/g, '\\$1')
    // eslint-disable-next-line no-control-regex
    .replace(/\x08/g, '\\x08');
};


/**
 * @ngdoc function
 * @name angular.isElement
 * @module ng
 * @kind function
 *
 * @description
 * Determines if a reference is a DOM element (or wrapped jQuery element).
 *
 * @param {*} value Reference to check.
 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element).
 */
function isElement(node) {
  return !!(node &&
    (node.nodeName  // We are a direct element.
    || (node.prop && node.attr && node.find)));  // We have an on and find method part of jQuery API.
}

/**
 * @param str 'key1,key2,...'
 * @returns {object} in the form of {key1:true, key2:true, ...}
 */
function makeMap(str) {
  var obj = {}, items = str.split(','), i;
  for (i = 0; i < items.length; i++) {
    obj[items[i]] = true;
  }
  return obj;
}


function nodeName_(element) {
  return lowercase(element.nodeName || (element[0] && element[0].nodeName));
}

function includes(array, obj) {
  return Array.prototype.indexOf.call(array, obj) !== -1;
}

function arrayRemove(array, value) {
  var index = array.indexOf(value);
  if (index >= 0) {
    array.splice(index, 1);
  }
  return index;
}

/**
 * @ngdoc function
 * @name angular.copy
 * @module ng
 * @kind function
 *
 * @description
 * Creates a deep copy of `source`, which should be an object or an array. This functions is used
 * internally, mostly in the change-detection code. It is not intended as an all-purpose copy
 * function, and has several limitations (see below).
 *
 * * If no destination is supplied, a copy of the object or array is created.
 * * If a destination is provided, all of its elements (for arrays) or properties (for objects)
 *   are deleted and then all elements/properties from the source are copied to it.
 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned.
 * * If `source` is identical to `destination` an exception will be thrown.
 *
 * <br />
 *
 * <div class="alert alert-warning">
 *   Only enumerable properties are taken into account. Non-enumerable properties (both on `source`
 *   and on `destination`) will be ignored.
 * </div>
 *
 * <div class="alert alert-warning">
 *   `angular.copy` does not check if destination and source are of the same type. It's the
 *   developer's responsibility to make sure they are compatible.
 * </div>
 *
 * @knownIssue
 * This is a non-exhaustive list of object types / features that are not handled correctly by
 * `angular.copy`. Note that since this functions is used by the change detection code, this
 * means binding or watching objects of these types (or that include these types) might not work
 * correctly.
 * - [`File`](https://developer.mozilla.org/docs/Web/API/File)
 * - [`Map`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)
 * - [`ImageData`](https://developer.mozilla.org/docs/Web/API/ImageData)
 * - [`MediaStream`](https://developer.mozilla.org/docs/Web/API/MediaStream)
 * - [`Set`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Set)
 * - [`WeakMap`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WeakMap)
 * - [`getter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get)/
 *   [`setter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set)
 *
 * @param {*} source The source that will be used to make a copy. Can be any type, including
 *     primitives, `null`, and `undefined`.
 * @param {(Object|Array)=} destination Destination into which the source is copied. If provided,
 *     must be of the same type as `source`.
 * @returns {*} The copy or updated `destination`, if `destination` was specified.
 *
 * @example
  <example module="copyExample" name="angular-copy">
    <file name="index.html">
      <div ng-controller="ExampleController">
        <form novalidate class="simple-form">
          <label>Name: <input type="text" ng-model="user.name" /></label><br />
          <label>Age:  <input type="number" ng-model="user.age" /></label><br />
          Gender: <label><input type="radio" ng-model="user.gender" value="male" />male</label>
                  <label><input type="radio" ng-model="user.gender" value="female" />female</label><br />
          <button ng-click="reset()">RESET</button>
          <button ng-click="update(user)">SAVE</button>
        </form>
        <pre>form = {{user | json}}</pre>
        <pre>leader = {{leader | json}}</pre>
      </div>
    </file>
    <file name="script.js">
      // Module: copyExample
      angular.
        module('copyExample', []).
        controller('ExampleController', ['$scope', function($scope) {
          $scope.leader = {};

          $scope.reset = function() {
            // Example with 1 argument
            $scope.user = angular.copy($scope.leader);
          };

          $scope.update = function(user) {
            // Example with 2 arguments
            angular.copy(user, $scope.leader);
          };

          $scope.reset();
        }]);
    </file>
  </example>
 */
function copy(source, destination, maxDepth) {
  var stackSource = [];
  var stackDest = [];
  maxDepth = isValidObjectMaxDepth(maxDepth) ? maxDepth : NaN;

  if (destination) {
    if (isTypedArray(destination) || isArrayBuffer(destination)) {
      throw ngMinErr('cpta', 'Can\'t copy! TypedArray destination cannot be mutated.');
    }
    if (source === destination) {
      throw ngMinErr('cpi', 'Can\'t copy! Source and destination are identical.');
    }

    // Empty the destination object
    if (isArray(destination)) {
      destination.length = 0;
    } else {
      forEach(destination, function(value, key) {
        if (key !== '$$hashKey') {
          delete destination[key];
        }
      });
    }

    stackSource.push(source);
    stackDest.push(destination);
    return copyRecurse(source, destination, maxDepth);
  }

  return copyElement(source, maxDepth);

  function copyRecurse(source, destination, maxDepth) {
    maxDepth--;
    if (maxDepth < 0) {
      return '...';
    }
    var h = destination.$$hashKey;
    var key;
    if (isArray(source)) {
      for (var i = 0, ii = source.length; i < ii; i++) {
        destination.push(copyElement(source[i], maxDepth));
      }
    } else if (isBlankObject(source)) {
      // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
      for (key in source) {
        destination[key] = copyElement(source[key], maxDepth);
      }
    } else if (source && typeof source.hasOwnProperty === 'function') {
      // Slow path, which must rely on hasOwnProperty
      for (key in source) {
        if (source.hasOwnProperty(key)) {
          destination[key] = copyElement(source[key], maxDepth);
        }
      }
    } else {
      // Slowest path --- hasOwnProperty can't be called as a method
      for (key in source) {
        if (hasOwnProperty.call(source, key)) {
          destination[key] = copyElement(source[key], maxDepth);
        }
      }
    }
    setHashKey(destination, h);
    return destination;
  }

  function copyElement(source, maxDepth) {
    // Simple values
    if (!isObject(source)) {
      return source;
    }

    // Already copied values
    var index = stackSource.indexOf(source);
    if (index !== -1) {
      return stackDest[index];
    }

    if (isWindow(source) || isScope(source)) {
      throw ngMinErr('cpws',
        'Can\'t copy! Making copies of Window or Scope instances is not supported.');
    }

    var needsRecurse = false;
    var destination = copyType(source);

    if (destination === undefined) {
      destination = isArray(source) ? [] : Object.create(getPrototypeOf(source));
      needsRecurse = true;
    }

    stackSource.push(source);
    stackDest.push(destination);

    return needsRecurse
      ? copyRecurse(source, destination, maxDepth)
      : destination;
  }

  function copyType(source) {
    switch (toString.call(source)) {
      case '[object Int8Array]':
      case '[object Int16Array]':
      case '[object Int32Array]':
      case '[object Float32Array]':
      case '[object Float64Array]':
      case '[object Uint8Array]':
      case '[object Uint8ClampedArray]':
      case '[object Uint16Array]':
      case '[object Uint32Array]':
        return new source.constructor(copyElement(source.buffer), source.byteOffset, source.length);

      case '[object ArrayBuffer]':
        // Support: IE10
        if (!source.slice) {
          // If we're in this case we know the environment supports ArrayBuffer
          /* eslint-disable no-undef */
          var copied = new ArrayBuffer(source.byteLength);
          new Uint8Array(copied).set(new Uint8Array(source));
          /* eslint-enable */
          return copied;
        }
        return source.slice(0);

      case '[object Boolean]':
      case '[object Number]':
      case '[object String]':
      case '[object Date]':
        return new source.constructor(source.valueOf());

      case '[object RegExp]':
        /**
         * As per the ECMA standard, if a regular expression has any flag code other than "d", "g", "i", "m", "s",
         * "u" or "y" or if F contains any code unit more than once, it throws a SyntaxError exception.
         * https://tc39.es/ecma262/multipage/text-processing.html#sec-regexpinitialize
         */
        var re = new RegExp(source.source, source.toString().match(/[^/]{0,7}$/)[0]);
        re.lastIndex = source.lastIndex;
        return re;

      case '[object Blob]':
        return new source.constructor([source], {type: source.type});
    }

    if (isFunction(source.cloneNode)) {
      return source.cloneNode(true);
    }
  }
}


// eslint-disable-next-line no-self-compare
function simpleCompare(a, b) { return a === b || (a !== a && b !== b); }


/**
 * @ngdoc function
 * @name angular.equals
 * @module ng
 * @kind function
 *
 * @description
 * Determines if two objects or two values are equivalent. Supports value types, regular
 * expressions, arrays and objects.
 *
 * Two objects or values are considered equivalent if at least one of the following is true:
 *
 * * Both objects or values pass `===` comparison.
 * * Both objects or values are of the same type and all of their properties are equal by
 *   comparing them with `angular.equals`.
 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal)
 * * Both values represent the same regular expression (In JavaScript,
 *   /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual
 *   representation matches).
 *
 * During a property comparison, properties of `function` type and properties with names
 * that begin with `$` are ignored.
 *
 * Scope and DOMWindow objects are being compared only by identify (`===`).
 *
 * @param {*} o1 Object or value to compare.
 * @param {*} o2 Object or value to compare.
 * @returns {boolean} True if arguments are equal.
 *
 * @example
   <example module="equalsExample" name="equalsExample">
     <file name="index.html">
      <div ng-controller="ExampleController">
        <form novalidate>
          <h3>User 1</h3>
          Name: <input type="text" ng-model="user1.name">
          Age: <input type="number" ng-model="user1.age">

          <h3>User 2</h3>
          Name: <input type="text" ng-model="user2.name">
          Age: <input type="number" ng-model="user2.age">

          <div>
            <br/>
            <input type="button" value="Compare" ng-click="compare()">
          </div>
          User 1: <pre>{{user1 | json}}</pre>
          User 2: <pre>{{user2 | json}}</pre>
          Equal: <pre>{{result}}</pre>
        </form>
      </div>
    </file>
    <file name="script.js">
        angular.module('equalsExample', []).controller('ExampleController', ['$scope', function($scope) {
          $scope.user1 = {};
          $scope.user2 = {};
          $scope.compare = function() {
            $scope.result = angular.equals($scope.user1, $scope.user2);
          };
        }]);
    </file>
  </example>
 */
function equals(o1, o2) {
  if (o1 === o2) return true;
  if (o1 === null || o2 === null) return false;
  // eslint-disable-next-line no-self-compare
  if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN
  var t1 = typeof o1, t2 = typeof o2, length, key, keySet;
  if (t1 === t2 && t1 === 'object') {
    if (isArray(o1)) {
      if (!isArray(o2)) return false;
      if ((length = o1.length) === o2.length) {
        for (key = 0; key < length; key++) {
          if (!equals(o1[key], o2[key])) return false;
        }
        return true;
      }
    } else if (isDate(o1)) {
      if (!isDate(o2)) return false;
      return simpleCompare(o1.getTime(), o2.getTime());
    } else if (isRegExp(o1)) {
      if (!isRegExp(o2)) return false;
      return o1.toString() === o2.toString();
    } else {
      if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) ||
        isArray(o2) || isDate(o2) || isRegExp(o2)) return false;
      keySet = createMap();
      for (key in o1) {
        if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
        if (!equals(o1[key], o2[key])) return false;
        keySet[key] = true;
      }
      for (key in o2) {
        if (!(key in keySet) &&
            key.charAt(0) !== '$' &&
            isDefined(o2[key]) &&
            !isFunction(o2[key])) return false;
      }
      return true;
    }
  }
  return false;
}

var csp = function() {
  if (!isDefined(csp.rules)) {


    var ngCspElement = (window.document.querySelector('[ng-csp]') ||
                    window.document.querySelector('[data-ng-csp]'));

    if (ngCspElement) {
      var ngCspAttribute = ngCspElement.getAttribute('ng-csp') ||
                    ngCspElement.getAttribute('data-ng-csp');
      csp.rules = {
        noUnsafeEval: !ngCspAttribute || (ngCspAttribute.indexOf('no-unsafe-eval') !== -1),
        noInlineStyle: !ngCspAttribute || (ngCspAttribute.indexOf('no-inline-style') !== -1)
      };
    } else {
      csp.rules = {
        noUnsafeEval: noUnsafeEval(),
        noInlineStyle: false
      };
    }
  }

  return csp.rules;

  function noUnsafeEval() {
    try {
      // eslint-disable-next-line no-new, no-new-func
      new Function('');
      return false;
    } catch (e) {
      return true;
    }
  }
};

/**
 * @ngdoc directive
 * @module ng
 * @name ngJq
 *
 * @element ANY
 * @param {string=} ngJq the name of the library available under `window`
 * to be used for angular.element
 * @description
 * Use this directive to force the angular.element library.  This should be
 * used to force either jqLite by leaving ng-jq blank or setting the name of
 * the jquery variable under window (eg. jQuery).
 *
 * Since AngularJS looks for this directive when it is loaded (doesn't wait for the
 * DOMContentLoaded event), it must be placed on an element that comes before the script
 * which loads angular. Also, only the first instance of `ng-jq` will be used and all
 * others ignored.
 *
 * @example
 * This example shows how to force jqLite using the `ngJq` directive to the `html` tag.
 ```html
 <!doctype html>
 <html ng-app ng-jq>
 ...
 ...
 </html>
 ```
 * @example
 * This example shows how to use a jQuery based library of a different name.
 * The library name must be available at the top most 'window'.
 ```html
 <!doctype html>
 <html ng-app ng-jq="jQueryLib">
 ...
 ...
 </html>
 ```
 */
var jq = function() {
  if (isDefined(jq.name_)) return jq.name_;
  var el;
  var i, ii = ngAttrPrefixes.length, prefix, name;
  for (i = 0; i < ii; ++i) {
    prefix = ngAttrPrefixes[i];
    el = window.document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]');
    if (el) {
      name = el.getAttribute(prefix + 'jq');
      break;
    }
  }

  return (jq.name_ = name);
};

function concat(array1, array2, index) {
  return array1.concat(slice.call(array2, index));
}

function sliceArgs(args, startIndex) {
  return slice.call(args, startIndex || 0);
}


/**
 * @ngdoc function
 * @name angular.bind
 * @module ng
 * @kind function
 *
 * @description
 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for
 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also
 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as
 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application).
 *
 * @param {Object} self Context which `fn` should be evaluated in.
 * @param {function()} fn Function to be bound.
 * @param {...*} args Optional arguments to be prebound to the `fn` function call.
 * @returns {function()} Function that wraps the `fn` with all the specified bindings.
 */
function bind(self, fn) {
  var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : [];
  if (isFunction(fn) && !(fn instanceof RegExp)) {
    return curryArgs.length
      ? function() {
          return arguments.length
            ? fn.apply(self, concat(curryArgs, arguments, 0))
            : fn.apply(self, curryArgs);
        }
      : function() {
          return arguments.length
            ? fn.apply(self, arguments)
            : fn.call(self);
        };
  } else {
    // In IE, native methods are not functions so they cannot be bound (note: they don't need to be).
    return fn;
  }
}


function toJsonReplacer(key, value) {
  var val = value;

  if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') {
    val = undefined;
  } else if (isWindow(value)) {
    val = '$WINDOW';
  } else if (value &&  window.document === value) {
    val = '$DOCUMENT';
  } else if (isScope(value)) {
    val = '$SCOPE';
  }

  return val;
}


/**
 * @ngdoc function
 * @name angular.toJson
 * @module ng
 * @kind function
 *
 * @description
 * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be
 * stripped since AngularJS uses this notation internally.
 *
 * @param {Object|Array|Date|string|number|boolean} obj Input to be serialized into JSON.
 * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace.
 *    If set to an integer, the JSON output will contain that many spaces per indentation.
 * @returns {string|undefined} JSON-ified string representing `obj`.
 * @knownIssue
 *
 * The Safari browser throws a `RangeError` instead of returning `null` when it tries to stringify a `Date`
 * object with an invalid date value. The only reliable way to prevent this is to monkeypatch the
 * `Date.prototype.toJSON` method as follows:
 *
 * ```
 * var _DatetoJSON = Date.prototype.toJSON;
 * Date.prototype.toJSON = function() {
 *   try {
 *     return _DatetoJSON.call(this);
 *   } catch(e) {
 *     if (e instanceof RangeError) {
 *       return null;
 *     }
 *     throw e;
 *   }
 * };
 * ```
 *
 * See https://github.com/angular/angular.js/pull/14221 for more information.
 */
function toJson(obj, pretty) {
  if (isUndefined(obj)) return undefined;
  if (!isNumber(pretty)) {
    pretty = pretty ? 2 : null;
  }
  return JSON.stringify(obj, toJsonReplacer, pretty);
}


/**
 * @ngdoc function
 * @name angular.fromJson
 * @module ng
 * @kind function
 *
 * @description
 * Deserializes a JSON string.
 *
 * @param {string} json JSON string to deserialize.
 * @returns {Object|Array|string|number} Deserialized JSON string.
 */
function fromJson(json) {
  return isString(json)
      ? JSON.parse(json)
      : json;
}


var ALL_COLONS = /:/g;
function timezoneToOffset(timezone, fallback) {
  // Support: IE 9-11 only, Edge 13-15+
  // IE/Edge do not "understand" colon (`:`) in timezone
  timezone = timezone.replace(ALL_COLONS, '');
  var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
  return isNumberNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
}


function addDateMinutes(date, minutes) {
  date = new Date(date.getTime());
  date.setMinutes(date.getMinutes() + minutes);
  return date;
}


function convertTimezoneToLocal(date, timezone, reverse) {
  reverse = reverse ? -1 : 1;
  var dateTimezoneOffset = date.getTimezoneOffset();
  var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset);
  return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset));
}


/**
 * @returns {string} Returns the string representation of the element.
 */
function startingTag(element) {
  element = jqLite(element).clone().empty();
  var elemHtml = jqLite('<div></div>').append(element).html();
  try {
    return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) :
        elemHtml.
          match(/^(<[^>]+>)/)[1].
          replace(/^<([\w-]+)/, function(match, nodeName) {return '<' + lowercase(nodeName);});
  } catch (e) {
    return lowercase(elemHtml);
  }

}


/////////////////////////////////////////////////

/**
 * Tries to decode the URI component without throwing an exception.
 *
 * @private
 * @param str value potential URI component to check.
 * @returns {boolean} True if `value` can be decoded
 * with the decodeURIComponent function.
 */
function tryDecodeURIComponent(value) {
  try {
    return decodeURIComponent(value);
  } catch (e) {
    // Ignore any invalid uri component.
  }
}


/**
 * Parses an escaped url query string into key-value pairs.
 * @returns {Object.<string,boolean|Array>}
 */
function parseKeyValue(/**string*/keyValue) {
  var obj = {};
  forEach((keyValue || '').split('&'), function(keyValue) {
    var splitPoint, key, val;
    if (keyValue) {
      key = keyValue = keyValue.replace(/\+/g,'%20');
      splitPoint = keyValue.indexOf('=');
      if (splitPoint !== -1) {
        key = keyValue.substring(0, splitPoint);
        val = keyValue.substring(splitPoint + 1);
      }
      key = tryDecodeURIComponent(key);
      if (isDefined(key)) {
        val = isDefined(val) ? tryDecodeURIComponent(val) : true;
        if (!hasOwnProperty.call(obj, key)) {
          obj[key] = val;
        } else if (isArray(obj[key])) {
          obj[key].push(val);
        } else {
          obj[key] = [obj[key],val];
        }
      }
    }
  });
  return obj;
}

function toKeyValue(obj) {
  var parts = [];
  forEach(obj, function(value, key) {
    if (isArray(value)) {
      forEach(value, function(arrayValue) {
        parts.push(encodeUriQuery(key, true) +
                   (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true)));
      });
    } else {
    parts.push(encodeUriQuery(key, true) +
               (value === true ? '' : '=' + encodeUriQuery(value, true)));
    }
  });
  return parts.length ? parts.join('&') : '';
}


/**
 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
 * segments:
 *    segment       = *pchar
 *    pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
 *    pct-encoded   = "%" HEXDIG HEXDIG
 *    unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
 *    sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
 *                     / "*" / "+" / "," / ";" / "="
 */
function encodeUriSegment(val) {
  return encodeUriQuery(val, true).
             replace(/%26/gi, '&').
             replace(/%3D/gi, '=').
             replace(/%2B/gi, '+');
}


/**
 * This method is intended for encoding *key* or *value* parts of query component. We need a custom
 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be
 * encoded per http://tools.ietf.org/html/rfc3986:
 *    query         = *( pchar / "/" / "?" )
 *    pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
 *    unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
 *    pct-encoded   = "%" HEXDIG HEXDIG
 *    sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
 *                     / "*" / "+" / "," / ";" / "="
 */
function encodeUriQuery(val, pctEncodeSpaces) {
  return encodeURIComponent(val).
             replace(/%40/gi, '@').
             replace(/%3A/gi, ':').
             replace(/%24/g, '$').
             replace(/%2C/gi, ',').
             replace(/%3B/gi, ';').
             replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
}

var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-'];

function getNgAttribute(element, ngAttr) {
  var attr, i, ii = ngAttrPrefixes.length;
  for (i = 0; i < ii; ++i) {
    attr = ngAttrPrefixes[i] + ngAttr;
    if (isString(attr = element.getAttribute(attr))) {
      return attr;
    }
  }
  return null;
}

function allowAutoBootstrap(document) {
  var script = document.currentScript;

  if (!script) {
    // Support: IE 9-11 only
    // IE does not have `document.currentScript`
    return true;
  }

  // If the `currentScript` property has been clobbered just return false, since this indicates a probable attack
  if (!(script instanceof window.HTMLScriptElement || script instanceof window.SVGScriptElement)) {
    return false;
  }

  var attributes = script.attributes;
  var srcs = [attributes.getNamedItem('src'), attributes.getNamedItem('href'), attributes.getNamedItem('xlink:href')];

  return srcs.every(function(src) {
    if (!src) {
      return true;
    }
    if (!src.value) {
      return false;
    }

    var link = document.createElement('a');
    link.href = src.value;

    // NOTE: Safari 16 link.origin is empty for browser extensions!
    if (document.location.origin === link.origin) {
      // Same-origin resources are always allowed, even for banned URL schemes.
      return true;
    }
    // Disabled bootstrapping unless angular.js was loaded from a known scheme used on the web.
    // This is to prevent angular.js bundled with browser extensions from being used to bypass the
    // content security policy in web pages and other browser extensions.
    switch (link.protocol) {
      case 'http:':
      case 'https:':
      case 'ftp:':
      case 'blob:':
      case 'file:':
      case 'data:':
        return true;
      default:
        return false;
    }
  });
}

// Cached as it has to run during loading so that document.currentScript is available.
var isAutoBootstrapAllowed = allowAutoBootstrap(window.document);

/**
 * @ngdoc directive
 * @name ngApp
 * @module ng
 *
 * @element ANY
 * @param {angular.Module} ngApp an optional application
 *   {@link angular.module module} name to load.
 * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be
 *   created in "strict-di" mode. This means that the application will fail to invoke functions which
 *   do not use explicit function annotation (and are thus unsuitable for minification), as described
 *   in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in
 *   tracking down the root of these bugs.
 *
 * @description
 *
 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive
 * designates the **root element** of the application and is typically placed near the root element
 * of the page - e.g. on the `<body>` or `<html>` tags.
 *
 * There are a few things to keep in mind when using `ngApp`:
 * - only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp`
 *   found in the document will be used to define the root element to auto-bootstrap as an
 *   application. To run multiple applications in an HTML document you must manually bootstrap them using
 *   {@link angular.bootstrap} instead.
 * - AngularJS applications cannot be nested within each other.
 * - Do not use a directive that uses {@link ng.$compile#transclusion transclusion} on the same element as `ngApp`.
 *   This includes directives such as {@link ng.ngIf `ngIf`}, {@link ng.ngInclude `ngInclude`} and
 *   {@link ngRoute.ngView `ngView`}.
 *   Doing this misplaces the app {@link ng.$rootElement `$rootElement`} and the app's {@link auto.$injector injector},
 *   causing animations to stop working and making the injector inaccessible from outside the app.
 *
 * You can specify an **AngularJS module** to be used as the root module for the application.  This
 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It
 * should contain the application code needed or have dependencies on other modules that will
 * contain the code. See {@link angular.module} for more information.
 *
 * In the example below if the `ngApp` directive were not placed on the `html` element then the
 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}`
 * would not be resolved to `3`.
 *
 * @example
 *
 * ### Simple Usage
 *
 * `ngApp` is the easiest, and most common way to bootstrap an application.
 *
 <example module="ngAppDemo" name="ng-app">
   <file name="index.html">
   <div ng-controller="ngAppDemoController">
     I can add: {{a}} + {{b}} =  {{ a+b }}
   </div>
   </file>
   <file name="script.js">
   angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) {
     $scope.a = 1;
     $scope.b = 2;
   });
   </file>
 </example>
 *
 * @example
 *
 * ### With `ngStrictDi`
 *
 * Using `ngStrictDi`, you would see something like this:
 *
 <example ng-app-included="true" name="strict-di">
   <file name="index.html">
   <div ng-app="ngAppStrictDemo" ng-strict-di>
       <div ng-controller="GoodController1">
           I can add: {{a}} + {{b}} =  {{ a+b }}

           <p>This renders because the controller does not fail to
              instantiate, by using explicit annotation style (see
              script.js for details)
           </p>
       </div>

       <div ng-controller="GoodController2">
           Name: <input ng-model="name"><br />
           Hello, {{name}}!

           <p>This renders because the controller does not fail to
              instantiate, by using explicit annotation style
              (see script.js for details)
           </p>
       </div>

       <div ng-controller="BadController">
           I can add: {{a}} + {{b}} =  {{ a+b }}

           <p>The controller could not be instantiated, due to relying
              on automatic function annotations (which are disabled in
              strict mode). As such, the content of this section is not
              interpolated, and there should be an error in your web console.
           </p>
       </div>
   </div>
   </file>
   <file name="script.js">
   angular.module('ngAppStrictDemo', [])
     // BadController will fail to instantiate, due to relying on automatic function annotation,
     // rather than an explicit annotation
     .controller('BadController', function($scope) {
       $scope.a = 1;
       $scope.b = 2;
     })
     // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated,
     // due to using explicit annotations using the array style and $inject property, respectively.
     .controller('GoodController1', ['$scope', function($scope) {
       $scope.a = 1;
       $scope.b = 2;
     }])
     .controller('GoodController2', GoodController2);
     function GoodController2($scope) {
       $scope.name = 'World';
     }
     GoodController2.$inject = ['$scope'];
   </file>
   <file name="style.css">
   div[ng-controller] {
       margin-bottom: 1em;
       -webkit-border-radius: 4px;
       border-radius: 4px;
       border: 1px solid;
       padding: .5em;
   }
   div[ng-controller^=Good] {
       border-color: #d6e9c6;
       background-color: #dff0d8;
       color: #3c763d;
   }
   div[ng-controller^=Bad] {
       border-color: #ebccd1;
       background-color: #f2dede;
       color: #a94442;
       margin-bottom: 0;
   }
   </file>
 </example>
 */
function angularInit(element, bootstrap) {
  var appElement,
      module,
      config = {};

  // The element `element` has priority over any other element.
  forEach(ngAttrPrefixes, function(prefix) {
    var name = prefix + 'app';

    if (!appElement && element.hasAttribute && element.hasAttribute(name)) {
      appElement = element;
      module = element.getAttribute(name);
    }
  });
  forEach(ngAttrPrefixes, function(prefix) {
    var name = prefix + 'app';
    var candidate;

    if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) {
      appElement = candidate;
      module = candidate.getAttribute(name);
    }
  });
  if (appElement) {
    if (!isAutoBootstrapAllowed) {
      window.console.error('AngularJS: disabling automatic bootstrap. <script> protocol indicates ' +
          'an extension, document.location.href does not match.');
      return;
    }
    config.strictDi = getNgAttribute(appElement, 'strict-di') !== null;
    bootstrap(appElement, module ? [module] : [], config);
  }
}

/**
 * @ngdoc function
 * @name angular.bootstrap
 * @module ng
 * @description
 * Use this function to manually start up AngularJS application.
 *
 * For more information, see the {@link guide/bootstrap Bootstrap guide}.
 *
 * AngularJS will detect if it has been loaded into the browser more than once and only allow the
 * first loaded script to be bootstrapped and will report a warning to the browser console for
 * each of the subsequent scripts. This prevents strange results in applications, where otherwise
 * multiple instances of AngularJS try to work on the DOM.
 *
 * <div class="alert alert-warning">
 * **Note:** Protractor based end-to-end tests cannot use this function to bootstrap manually.
 * They must use {@link ng.directive:ngApp ngApp}.
 * </div>
 *
 * <div class="alert alert-warning">
 * **Note:** Do not bootstrap the app on an element with a directive that uses {@link ng.$compile#transclusion transclusion},
 * such as {@link ng.ngIf `ngIf`}, {@link ng.ngInclude `ngInclude`} and {@link ngRoute.ngView `ngView`}.
 * Doing this misplaces the app {@link ng.$rootElement `$rootElement`} and the app's {@link auto.$injector injector},
 * causing animations to stop working and making the injector inaccessible from outside the app.
 * </div>
 *
 * ```html
 * <!doctype html>
 * <html>
 * <body>
 * <div ng-controller="WelcomeController">
 *   {{greeting}}
 * </div>
 *
 * <script src="angular.js"></script>
 * <script>
 *   var app = angular.module('demo', [])
 *   .controller('WelcomeController', function($scope) {
 *       $scope.greeting = 'Welcome!';
 *   });
 *   angular.bootstrap(document, ['demo']);
 * </script>
 * </body>
 * </html>
 * ```
 *
 * @param {DOMElement} element DOM element which is the root of AngularJS application.
 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application.
 *     Each item in the array should be the name of a predefined module or a (DI annotated)
 *     function that will be invoked by the injector as a `config` block.
 *     See: {@link angular.module modules}
 * @param {Object=} config an object for defining configuration options for the application. The
 *     following keys are supported:
 *
 * * `strictDi` - disable automatic function annotation for the application. This is meant to
 *   assist in finding bugs which break minified code. Defaults to `false`.
 *
 * @returns {auto.$injector} Returns the newly created injector for this app.
 */
function bootstrap(element, modules, config) {
  if (!isObject(config)) config = {};
  var defaultConfig = {
    strictDi: false
  };
  config = extend(defaultConfig, config);
  var doBootstrap = function() {
    element = jqLite(element);

    if (element.injector()) {
      var tag = (element[0] === window.document) ? 'document' : startingTag(element);
      // Encode angle brackets to prevent input from being sanitized to empty string #8683.
      throw ngMinErr(
          'btstrpd',
          'App already bootstrapped with this element \'{0}\'',
          tag.replace(/</,'&lt;').replace(/>/,'&gt;'));
    }

    modules = modules || [];
    modules.unshift(['$provide', function($provide) {
      $provide.value('$rootElement', element);
    }]);

    if (config.debugInfoEnabled) {
      // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`.
      modules.push(['$compileProvider', function($compileProvider) {
        $compileProvider.debugInfoEnabled(true);
      }]);
    }

    modules.unshift('ng');
    var injector = createInjector(modules, config.strictDi);
    injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
       function bootstrapApply(scope, element, compile, injector) {
        scope.$apply(function() {
          element.data('$injector', injector);
          compile(element)(scope);
        });
      }]
    );
    return injector;
  };

  var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/;
  var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;

  if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) {
    config.debugInfoEnabled = true;
    window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, '');
  }

  if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
    return doBootstrap();
  }

  window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');
  angular.resumeBootstrap = function(extraModules) {
    forEach(extraModules, function(module) {
      modules.push(module);
    });
    return doBootstrap();
  };

  if (isFunction(angular.resumeDeferredBootstrap)) {
    angular.resumeDeferredBootstrap();
  }
}

/**
 * @ngdoc function
 * @name angular.reloadWithDebugInfo
 * @module ng
 * @description
 * Use this function to reload the current application with debug information turned on.
 * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`.
 *
 * See {@link ng.$compileProvider#debugInfoEnabled} for more.
 */
function reloadWithDebugInfo() {
  window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name;
  window.location.reload();
}

/**
 * @name angular.getTestability
 * @module ng
 * @description
 * Get the testability service for the instance of AngularJS on the given
 * element.
 * @param {DOMElement} element DOM element which is the root of AngularJS application.
 */
function getTestability(rootElement) {
  var injector = angular.element(rootElement).injector();
  if (!injector) {
    throw ngMinErr('test',
      'no injector found for element argument to getTestability');
  }
  return injector.get('$$testability');
}

var SNAKE_CASE_REGEXP = /[A-Z]/g;
function snake_case(name, separator) {
  separator = separator || '_';
  return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
    return (pos ? separator : '') + letter.toLowerCase();
  });
}

var bindJQueryFired = false;
function bindJQuery() {
  var originalCleanData;

  if (bindJQueryFired) {
    return;
  }

  // bind to jQuery if present;
  var jqName = jq();
  jQuery = isUndefined(jqName) ? window.jQuery :   // use jQuery (if present)
           !jqName             ? undefined     :   // use jqLite
                                 window[jqName];   // use jQuery specified by `ngJq`

  // Use jQuery if it exists with proper functionality, otherwise default to us.
  // AngularJS 1.2+ requires jQuery 1.7+ for on()/off() support.
  // AngularJS 1.3+ technically requires at least jQuery 2.1+ but it may work with older
  // versions. It will not work for sure with jQuery <1.7, though.
  if (jQuery && jQuery.fn.on) {
    jqLite = jQuery;
    extend(jQuery.fn, {
      scope: JQLitePrototype.scope,
      isolateScope: JQLitePrototype.isolateScope,
      controller: /** @type {?} */ (JQLitePrototype).controller,
      injector: JQLitePrototype.injector,
      inheritedData: JQLitePrototype.inheritedData
    });
  } else {
    jqLite = JQLite;
  }

  // All nodes removed from the DOM via various jqLite/jQuery APIs like .remove()
  // are passed through jqLite/jQuery.cleanData. Monkey-patch this method to fire
  // the $destroy event on all removed nodes.
  originalCleanData = jqLite.cleanData;
  jqLite.cleanData = function(elems) {
    var events;
    for (var i = 0, elem; (elem = elems[i]) != null; i++) {
      events = (jqLite._data(elem) || {}).events;
      if (events && events.$destroy) {
        jqLite(elem).triggerHandler('$destroy');
      }
    }
    originalCleanData(elems);
  };

  angular.element = jqLite;

  // Prevent double-proxying.
  bindJQueryFired = true;
}

/**
 * @ngdoc function
 * @name angular.UNSAFE_restoreLegacyJqLiteXHTMLReplacement
 * @module ng
 * @kind function
 *
 * @description
 * Restores the pre-1.8 behavior of jqLite that turns XHTML-like strings like
 * `<div /><span />` to `<div></div><span></span>` instead of `<div><span></span></div>`.
 * The new behavior is a security fix. Thus, if you need to call this function, please try to adjust
 * your code for this change and remove your use of this function as soon as possible.

 * Note that this only patches jqLite. If you use jQuery 3.5.0 or newer, please read the
 * [jQuery 3.5 upgrade guide](https://jquery.com/upgrade-guide/3.5/) for more details
 * about the workarounds.
 */
function UNSAFE_restoreLegacyJqLiteXHTMLReplacement() {
  JQLite.legacyXHTMLReplacement = true;
}

/**
 * throw error if the argument is falsy.
 */
function assertArg(arg, name, reason) {
  if (!arg) {
    throw ngMinErr('areq', 'Argument \'{0}\' is {1}', (name || '?'), (reason || 'required'));
  }
  return arg;
}

function assertArgFn(arg, name, acceptArrayAnnotation) {
  if (acceptArrayAnnotation && isArray(arg)) {
      arg = arg[arg.length - 1];
  }

  assertArg(isFunction(arg), name, 'not a function, got ' +
      (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg));
  return arg;
}

/**
 * throw error if the name given is hasOwnProperty
 * @param  {String} name    the name to test
 * @param  {String} context the context in which the name is used, such as module or directive
 */
function assertNotHasOwnProperty(name, context) {
  if (name === 'hasOwnProperty') {
    throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
  }
}

/**
 * Return the value accessible from the object by path. Any undefined traversals are ignored
 * @param {Object} obj starting object
 * @param {String} path path to traverse
 * @param {boolean} [bindFnToScope=true]
 * @returns {Object} value as accessible by path
 */
//TODO(misko): this function needs to be removed
function getter(obj, path, bindFnToScope) {
  if (!path) return obj;
  var keys = path.split('.');
  var key;
  var lastInstance = obj;
  var len = keys.length;

  for (var i = 0; i < len; i++) {
    key = keys[i];
    if (obj) {
      obj = (lastInstance = obj)[key];
    }
  }
  if (!bindFnToScope && isFunction(obj)) {
    return bind(lastInstance, obj);
  }
  return obj;
}

/**
 * Return the DOM siblings between the first and last node in the given array.
 * @param {Array} array like object
 * @returns {Array} the inputted object or a jqLite collection containing the nodes
 */
function getBlockNodes(nodes) {
  // TODO(perf): update `nodes` instead of creating a new object?
  var node = nodes[0];
  var endNode = nodes[nodes.length - 1];
  var blockNodes;

  for (var i = 1; node !== endNode && (node = node.nextSibling); i++) {
    if (blockNodes || nodes[i] !== node) {
      if (!blockNodes) {
        blockNodes = jqLite(slice.call(nodes, 0, i));
      }
      blockNodes.push(node);
    }
  }

  return blockNodes || nodes;
}


/**
 * Creates a new object without a prototype. This object is useful for lookup without having to
 * guard against prototypically inherited properties via hasOwnProperty.
 *
 * Related micro-benchmarks:
 * - http://jsperf.com/object-create2
 * - http://jsperf.com/proto-map-lookup/2
 * - http://jsperf.com/for-in-vs-object-keys2
 *
 * @returns {Object}
 */
function createMap() {
  return Object.create(null);
}

function stringify(value) {
  if (value == null) { // null || undefined
    return '';
  }
  switch (typeof value) {
    case 'string':
      break;
    case 'number':
      value = '' + value;
      break;
    default:
      if (hasCustomToString(value) && !isArray(value) && !isDate(value)) {
        value = value.toString();
      } else {
        value = toJson(value);
      }
  }

  return value;
}

var NODE_TYPE_ELEMENT = 1;
var NODE_TYPE_ATTRIBUTE = 2;
var NODE_TYPE_TEXT = 3;
var NODE_TYPE_COMMENT = 8;
var NODE_TYPE_DOCUMENT = 9;
var NODE_TYPE_DOCUMENT_FRAGMENT = 11;

/**
 * @ngdoc type
 * @name angular.Module
 * @module ng
 * @description
 *
 * Interface for configuring AngularJS {@link angular.module modules}.
 */

function setupModuleLoader(window) {

  var $injectorMinErr = minErr('$injector');
  var ngMinErr = minErr('ng');

  function ensure(obj, name, factory) {
    return obj[name] || (obj[name] = factory());
  }

  var angular = ensure(window, 'angular', Object);

  // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
  angular.$$minErr = angular.$$minErr || minErr;

  return ensure(angular, 'module', function() {
    /** @type {Object.<string, angular.Module>} */
    var modules = {};

    /**
     * @ngdoc function
     * @name angular.module
     * @module ng
     * @description
     *
     * The `angular.module` is a global place for creating, registering and retrieving AngularJS
     * modules.
     * All modules (AngularJS core or 3rd party) that should be available to an application must be
     * registered using this mechanism.
     *
     * Passing one argument retrieves an existing {@link angular.Module},
     * whereas passing more than one argument creates a new {@link angular.Module}
     *
     *
     * # Module
     *
     * A module is a collection of services, directives, controllers, filters, and configuration information.
     * `angular.module` is used to configure the {@link auto.$injector $injector}.
     *
     * ```js
     * // Create a new module
     * var myModule = angular.module('myModule', []);
     *
     * // register a new service
     * myModule.value('appName', 'MyCoolApp');
     *
     * // configure existing services inside initialization blocks.
     * myModule.config(['$locationProvider', function($locationProvider) {
     *   // Configure existing providers
     *   $locationProvider.hashPrefix('!');
     * }]);
     * ```
     *
     * Then you can create an injector and load your modules like this:
     *
     * ```js
     * var injector = angular.injector(['ng', 'myModule'])
     * ```
     *
     * However it's more likely that you'll just use
     * {@link ng.directive:ngApp ngApp} or
     * {@link angular.bootstrap} to simplify this process for you.
     *
     * @param {!string} name The name of the module to create or retrieve.
     * @param {!Array.<string>=} requires If specified then new module is being created. If
     *        unspecified then the module is being retrieved for further configuration.
     * @param {Function=} configFn Optional configuration function for the module. Same as
     *        {@link angular.Module#config Module#config()}.
     * @returns {angular.Module} new module with the {@link angular.Module} api.
     */
    return function module(name, requires, configFn) {

      var info = {};

      var assertNotHasOwnProperty = function(name, context) {
        if (name === 'hasOwnProperty') {
          throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
        }
      };

      assertNotHasOwnProperty(name, 'module');
      if (requires && modules.hasOwnProperty(name)) {
        modules[name] = null;
      }
      return ensure(modules, name, function() {
        if (!requires) {
          throw $injectorMinErr('nomod', 'Module \'{0}\' is not available! You either misspelled ' +
             'the module name or forgot to load it. If registering a module ensure that you ' +
             'specify the dependencies as the second argument.', name);
        }

        /** @type {!Array.<Array.<*>>} */
        var invokeQueue = [];

        /** @type {!Array.<Function>} */
        var configBlocks = [];

        /** @type {!Array.<Function>} */
        var runBlocks = [];

        var config = invokeLater('$injector', 'invoke', 'push', configBlocks);

        /** @type {angular.Module} */
        var moduleInstance = {
          // Private state
          _invokeQueue: invokeQueue,
          _configBlocks: configBlocks,
          _runBlocks: runBlocks,

          /**
           * @ngdoc method
           * @name angular.Module#info
           * @module ng
           *
           * @param {Object=} info Information about the module
           * @returns {Object|Module} The current info object for this module if called as a getter,
           *                          or `this` if called as a setter.
           *
           * @description
           * Read and write custom information about this module.
           * For example you could put the version of the module in here.
           *
           * ```js
           * angular.module('myModule', []).info({ version: '1.0.0' });
           * ```
           *
           * The version could then be read back out by accessing the module elsewhere:
           *
           * ```
           * var version = angular.module('myModule').info().version;
           * ```
           *
           * You can also retrieve this information during runtime via the
           * {@link $injector#modules `$injector.modules`} property:
           *
           * ```js
           * var version = $injector.modules['myModule'].info().version;
           * ```
           */
          info: function(value) {
            if (isDefined(value)) {
              if (!isObject(value)) throw ngMinErr('aobj', 'Argument \'{0}\' must be an object', 'value');
              info = value;
              return this;
            }
            return info;
          },

          /**
           * @ngdoc property
           * @name angular.Module#requires
           * @module ng
           *
           * @description
           * Holds the list of modules which the injector will load before the current module is
           * loaded.
           */
          requires: requires,

          /**
           * @ngdoc property
           * @name angular.Module#name
           * @module ng
           *
           * @description
           * Name of the module.
           */
          name: name,


          /**
           * @ngdoc method
           * @name angular.Module#provider
           * @module ng
           * @param {string} name service name
           * @param {Function} providerType Construction function for creating new instance of the
           *                                service.
           * @description
           * See {@link auto.$provide#provider $provide.provider()}.
           */
          provider: invokeLaterAndSetModuleName('$provide', 'provider'),

          /**
           * @ngdoc method
           * @name angular.Module#factory
           * @module ng
           * @param {string} name service name
           * @param {Function} providerFunction Function for creating new instance of the service.
           * @description
           * See {@link auto.$provide#factory $provide.factory()}.
           */
          factory: invokeLaterAndSetModuleName('$provide', 'factory'),

          /**
           * @ngdoc method
           * @name angular.Module#service
           * @module ng
           * @param {string} name service name
           * @param {Function} constructor A constructor function that will be instantiated.
           * @description
           * See {@link auto.$provide#service $provide.service()}.
           */
          service: invokeLaterAndSetModuleName('$provide', 'service'),

          /**
           * @ngdoc method
           * @name angular.Module#value
           * @module ng
           * @param {string} name service name
           * @param {*} object Service instance object.
           * @description
           * See {@link auto.$provide#value $provide.value()}.
           */
          value: invokeLater('$provide', 'value'),

          /**
           * @ngdoc method
           * @name angular.Module#constant
           * @module ng
           * @param {string} name constant name
           * @param {*} object Constant value.
           * @description
           * Because the constants are fixed, they get applied before other provide methods.
           * See {@link auto.$provide#constant $provide.constant()}.
           */
          constant: invokeLater('$provide', 'constant', 'unshift'),

           /**
           * @ngdoc method
           * @name angular.Module#decorator
           * @module ng
           * @param {string} name The name of the service to decorate.
           * @param {Function} decorFn This function will be invoked when the service needs to be
           *                           instantiated and should return the decorated service instance.
           * @description
           * See {@link auto.$provide#decorator $provide.decorator()}.
           */
          decorator: invokeLaterAndSetModuleName('$provide', 'decorator', configBlocks),

          /**
           * @ngdoc method
           * @name angular.Module#animation
           * @module ng
           * @param {string} name animation name
           * @param {Function} animationFactory Factory function for creating new instance of an
           *                                    animation.
           * @description
           *
           * **NOTE**: animations take effect only if the **ngAnimate** module is loaded.
           *
           *
           * Defines an animation hook that can be later used with
           * {@link $animate $animate} service and directives that use this service.
           *
           * ```js
           * module.animation('.animation-name', function($inject1, $inject2) {
           *   return {
           *     eventName : function(element, done) {
           *       //code to run the animation
           *       //once complete, then run done()
           *       return function cancellationFunction(element) {
           *         //code to cancel the animation
           *       }
           *     }
           *   }
           * })
           * ```
           *
           * See {@link ng.$animateProvider#register $animateProvider.register()} and
           * {@link ngAnimate ngAnimate module} for more information.
           */
          animation: invokeLaterAndSetModuleName('$animateProvider', 'register'),

          /**
           * @ngdoc method
           * @name angular.Module#filter
           * @module ng
           * @param {string} name Filter name - this must be a valid AngularJS expression identifier
           * @param {Function} filterFactory Factory function for creating new instance of filter.
           * @description
           * See {@link ng.$filterProvider#register $filterProvider.register()}.
           *
           * <div class="alert alert-warning">
           * **Note:** Filter names must be valid AngularJS {@link expression} identifiers, such as `uppercase` or `orderBy`.
           * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
           * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
           * (`myapp_subsection_filterx`).
           * </div>
           */
          filter: invokeLaterAndSetModuleName('$filterProvider', 'register'),

          /**
           * @ngdoc method
           * @name angular.Module#controller
           * @module ng
           * @param {string|Object} name Controller name, or an object map of controllers where the
           *    keys are the names and the values are the constructors.
           * @param {Function} constructor Controller constructor function.
           * @description
           * See {@link ng.$controllerProvider#register $controllerProvider.register()}.
           */
          controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'),

          /**
           * @ngdoc method
           * @name angular.Module#directive
           * @module ng
           * @param {string|Object} name Directive name, or an object map of directives where the
           *    keys are the names and the values are the factories.
           * @param {Function} directiveFactory Factory function for creating new instance of
           * directives.
           * @description
           * See {@link ng.$compileProvider#directive $compileProvider.directive()}.
           */
          directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'),

          /**
           * @ngdoc method
           * @name angular.Module#component
           * @module ng
           * @param {string|Object} name Name of the component in camelCase (i.e. `myComp` which will match `<my-comp>`),
           *    or an object map of components where the keys are the names and the values are the component definition objects.
           * @param {Object} options Component definition object (a simplified
           *    {@link ng.$compile#directive-definition-object directive definition object})
           *
           * @description
           * See {@link ng.$compileProvider#component $compileProvider.component()}.
           */
          component: invokeLaterAndSetModuleName('$compileProvider', 'component'),

          /**
           * @ngdoc method
           * @name angular.Module#config
           * @module ng
           * @param {Function} configFn Execute this function on module load. Useful for service
           *    configuration.
           * @description
           * Use this method to configure services by injecting their
           * {@link angular.Module#provider `providers`}, e.g. for adding routes to the
           * {@link ngRoute.$routeProvider $routeProvider}.
           *
           * Note that you can only inject {@link angular.Module#provider `providers`} and
           * {@link angular.Module#constant `constants`} into this function.
           *
           * For more about how to configure services, see
           * {@link providers#provider-recipe Provider Recipe}.
           */
          config: config,

          /**
           * @ngdoc method
           * @name angular.Module#run
           * @module ng
           * @param {Function} initializationFn Execute this function after injector creation.
           *    Useful for application initialization.
           * @description
           * Use this method to register work which should be performed when the injector is done
           * loading all modules.
           */
          run: function(block) {
            runBlocks.push(block);
            return this;
          }
        };

        if (configFn) {
          config(configFn);
        }

        return moduleInstance;

        /**
         * @param {string} provider
         * @param {string} method
         * @param {String=} insertMethod
         * @returns {angular.Module}
         */
        function invokeLater(provider, method, insertMethod, queue) {
          if (!queue) queue = invokeQueue;
          return function() {
            queue[insertMethod || 'push']([provider, method, arguments]);
            return moduleInstance;
          };
        }

        /**
         * @param {string} provider
         * @param {string} method
         * @returns {angular.Module}
         */
        function invokeLaterAndSetModuleName(provider, method, queue) {
          if (!queue) queue = invokeQueue;
          return function(recipeName, factoryFunction) {
            if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name;
            queue.push([provider, method, arguments]);
            return moduleInstance;
          };
        }
      });
    };
  });

}

/* global shallowCopy: true */

/**
 * Creates a shallow copy of an object, an array or a primitive.
 *
 * Assumes that there are no proto properties for objects.
 */
function shallowCopy(src, dst) {
  if (isArray(src)) {
    dst = dst || [];

    for (var i = 0, ii = src.length; i < ii; i++) {
      dst[i] = src[i];
    }
  } else if (isObject(src)) {
    dst = dst || {};

    for (var key in src) {
      if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) {
        dst[key] = src[key];
      }
    }
  }

  return dst || src;
}

/* exported toDebugString */

function serializeObject(obj, maxDepth) {
  var seen = [];

  // There is no direct way to stringify object until reaching a specific depth
  // and a very deep object can cause a performance issue, so we copy the object
  // based on this specific depth and then stringify it.
  if (isValidObjectMaxDepth(maxDepth)) {
    // This file is also included in `angular-loader`, so `copy()` might not always be available in
    // the closure. Therefore, it is lazily retrieved as `angular.copy()` when needed.
    obj = angular.copy(obj, null, maxDepth);
  }
  return JSON.stringify(obj, function(key, val) {
    val = toJsonReplacer(key, val);
    if (isObject(val)) {

      if (seen.indexOf(val) >= 0) return '...';

      seen.push(val);
    }
    return val;
  });
}

function toDebugString(obj, maxDepth) {
  if (typeof obj === 'function') {
    return obj.toString().replace(/ \{[\s\S]*$/, '');
  } else if (isUndefined(obj)) {
    return 'undefined';
  } else if (typeof obj !== 'string') {
    return serializeObject(obj, maxDepth);
  }
  return obj;
}

/* global angularModule: true,
  version: true,

  $CompileProvider,

  htmlAnchorDirective,
  inputDirective,
  hiddenInputBrowserCacheDirective,
  formDirective,
  scriptDirective,
  selectDirective,
  optionDirective,
  ngBindDirective,
  ngBindHtmlDirective,
  ngBindTemplateDirective,
  ngClassDirective,
  ngClassEvenDirective,
  ngClassOddDirective,
  ngCloakDirective,
  ngControllerDirective,
  ngFormDirective,
  ngHideDirective,
  ngIfDirective,
  ngIncludeDirective,
  ngIncludeFillContentDirective,
  ngInitDirective,
  ngNonBindableDirective,
  ngPluralizeDirective,
  ngRefDirective,
  ngRepeatDirective,
  ngShowDirective,
  ngStyleDirective,
  ngSwitchDirective,
  ngSwitchWhenDirective,
  ngSwitchDefaultDirective,
  ngOptionsDirective,
  ngTranscludeDirective,
  ngModelDirective,
  ngListDirective,
  ngChangeDirective,
  patternDirective,
  patternDirective,
  requiredDirective,
  requiredDirective,
  minlengthDirective,
  minlengthDirective,
  maxlengthDirective,
  maxlengthDirective,
  ngValueDirective,
  ngModelOptionsDirective,
  ngAttributeAliasDirectives,
  ngEventDirectives,

  $AnchorScrollProvider,
  $AnimateProvider,
  $CoreAnimateCssProvider,
  $$CoreAnimateJsProvider,
  $$CoreAnimateQueueProvider,
  $$AnimateRunnerFactoryProvider,
  $$AnimateAsyncRunFactoryProvider,
  $BrowserProvider,
  $CacheFactoryProvider,
  $ControllerProvider,
  $DateProvider,
  $DocumentProvider,
  $$IsDocumentHiddenProvider,
  $ExceptionHandlerProvider,
  $FilterProvider,
  $$ForceReflowProvider,
  $InterpolateProvider,
  $$IntervalFactoryProvider,
  $IntervalProvider,
  $HttpProvider,
  $HttpParamSerializerProvider,
  $HttpParamSerializerJQLikeProvider,
  $HttpBackendProvider,
  $xhrFactoryProvider,
  $jsonpCallbacksProvider,
  $LocationProvider,
  $LogProvider,
  $$MapProvider,
  $ParseProvider,
  $RootScopeProvider,
  $QProvider,
  $$QProvider,
  $$SanitizeUriProvider,
  $SceProvider,
  $SceDelegateProvider,
  $SnifferProvider,
  $$TaskTrackerFactoryProvider,
  $TemplateCacheProvider,
  $TemplateRequestProvider,
  $$TestabilityProvider,
  $TimeoutProvider,
  $$RAFProvider,
  $WindowProvider,
  $$jqLiteProvider,
  $$CookieReaderProvider
*/


/**
 * @ngdoc object
 * @name angular.version
 * @module ng
 * @description
 * An object that contains information about the current AngularJS version.
 *
 * This object has the following properties:
 *
 * - `full` – `{string}` – Full version string, such as "0.9.18".
 * - `major` – `{number}` – Major version number, such as "0".
 * - `minor` – `{number}` – Minor version number, such as "9".
 * - `dot` – `{number}` – Dot version number, such as "18".
 * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
 */
var version = {
  // These placeholder strings will be replaced by grunt's `build` task.
  // They need to be double- or single-quoted.
  full: '1.8.11',
  major: 1,
  minor: 8,
  dot: 11,
  codeName: 'openlogic-perforce'
};


function publishExternalAPI(angular) {
  extend(angular, {
    'errorHandlingConfig': errorHandlingConfig,
    'bootstrap': bootstrap,
    'copy': copy,
    'extend': extend,
    'merge': merge,
    'equals': equals,
    'element': jqLite,
    'forEach': forEach,
    'injector': createInjector,
    'noop': noop,
    'bind': bind,
    'toJson': toJson,
    'fromJson': fromJson,
    'identity': identity,
    'isUndefined': isUndefined,
    'isDefined': isDefined,
    'isString': isString,
    'isFunction': isFunction,
    'isObject': isObject,
    'isNumber': isNumber,
    'isElement': isElement,
    'isArray': isArray,
    'version': version,
    'isDate': isDate,
    'callbacks': {$$counter: 0},
    'getTestability': getTestability,
    'reloadWithDebugInfo': reloadWithDebugInfo,
    'UNSAFE_restoreLegacyJqLiteXHTMLReplacement': UNSAFE_restoreLegacyJqLiteXHTMLReplacement,
    '$$minErr': minErr,
    '$$csp': csp,
    '$$encodeUriSegment': encodeUriSegment,
    '$$encodeUriQuery': encodeUriQuery,
    '$$lowercase': lowercase,
    '$$stringify': stringify,
    '$$uppercase': uppercase
  });

  angularModule = setupModuleLoader(window);

  angularModule('ng', ['ngLocale'], ['$provide',
    function ngModule($provide) {
      // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it.
      $provide.provider({
        $$sanitizeUri: $$SanitizeUriProvider
      });
      $provide.provider('$compile', $CompileProvider).
        directive({
            a: htmlAnchorDirective,
            input: inputDirective,
            textarea: inputDirective,
            form: formDirective,
            script: scriptDirective,
            select: selectDirective,
            option: optionDirective,
            ngBind: ngBindDirective,
            ngBindHtml: ngBindHtmlDirective,
            ngBindTemplate: ngBindTemplateDirective,
            ngClass: ngClassDirective,
            ngClassEven: ngClassEvenDirective,
            ngClassOdd: ngClassOddDirective,
            ngCloak: ngCloakDirective,
            ngController: ngControllerDirective,
            ngForm: ngFormDirective,
            ngHide: ngHideDirective,
            ngIf: ngIfDirective,
            ngInclude: ngIncludeDirective,
            ngInit: ngInitDirective,
            ngNonBindable: ngNonBindableDirective,
            ngPluralize: ngPluralizeDirective,
            ngRef: ngRefDirective,
            ngRepeat: ngRepeatDirective,
            ngShow: ngShowDirective,
            ngStyle: ngStyleDirective,
            ngSwitch: ngSwitchDirective,
            ngSwitchWhen: ngSwitchWhenDirective,
            ngSwitchDefault: ngSwitchDefaultDirective,
            ngOptions: ngOptionsDirective,
            ngTransclude: ngTranscludeDirective,
            ngModel: ngModelDirective,
            ngList: ngListDirective,
            ngChange: ngChangeDirective,
            pattern: patternDirective,
            ngPattern: patternDirective,
            required: requiredDirective,
            ngRequired: requiredDirective,
            minlength: minlengthDirective,
            ngMinlength: minlengthDirective,
            maxlength: maxlengthDirective,
            ngMaxlength: maxlengthDirective,
            ngValue: ngValueDirective,
            ngModelOptions: ngModelOptionsDirective
        }).
        directive({
          ngInclude: ngIncludeFillContentDirective,
          input: hiddenInputBrowserCacheDirective
        }).
        directive(ngAttributeAliasDirectives).
        directive(ngEventDirectives);
      $provide.provider({
        $anchorScroll: $AnchorScrollProvider,
        $animate: $AnimateProvider,
        $animateCss: $CoreAnimateCssProvider,
        $$animateJs: $$CoreAnimateJsProvider,
        $$animateQueue: $$CoreAnimateQueueProvider,
        $$AnimateRunner: $$AnimateRunnerFactoryProvider,
        $$animateAsyncRun: $$AnimateAsyncRunFactoryProvider,
        $browser: $BrowserProvider,
        $cacheFactory: $CacheFactoryProvider,
        $controller: $ControllerProvider,
        $document: $DocumentProvider,
        $$isDocumentHidden: $$IsDocumentHiddenProvider,
        $exceptionHandler: $ExceptionHandlerProvider,
        $filter: $FilterProvider,
        $$forceReflow: $$ForceReflowProvider,
        $interpolate: $InterpolateProvider,
        $interval: $IntervalProvider,
        $$intervalFactory: $$IntervalFactoryProvider,
        $http: $HttpProvider,
        $httpParamSerializer: $HttpParamSerializerProvider,
        $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
        $httpBackend: $HttpBackendProvider,
        $xhrFactory: $xhrFactoryProvider,
        $jsonpCallbacks: $jsonpCallbacksProvider,
        $location: $LocationProvider,
        $log: $LogProvider,
        $parse: $ParseProvider,
        $rootScope: $RootScopeProvider,
        $q: $QProvider,
        $$q: $$QProvider,
        $sce: $SceProvider,
        $sceDelegate: $SceDelegateProvider,
        $sniffer: $SnifferProvider,
        $$taskTrackerFactory: $$TaskTrackerFactoryProvider,
        $templateCache: $TemplateCacheProvider,
        $templateRequest: $TemplateRequestProvider,
        $$testability: $$TestabilityProvider,
        $timeout: $TimeoutProvider,
        $window: $WindowProvider,
        $$rAF: $$RAFProvider,
        $$jqLite: $$jqLiteProvider,
        $$Map: $$MapProvider,
        $$cookieReader: $$CookieReaderProvider
      });
    }
  ])
  .info({ angularVersion: '1.8.11' });
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *     Any commits to this file should be reviewed with security in mind.  *
 *   Changes to this file can potentially create security vulnerabilities. *
 *          An approval from 2 Core members with history of modifying      *
 *                         this file is required.                          *
 *                                                                         *
 *  Does the change somehow allow for arbitrary javascript to be executed? *
 *    Or allows for someone to change the prototype of built-in objects?   *
 *     Or gives undesired access to variables likes document or window?    *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* global
  JQLitePrototype: true,
  BOOLEAN_ATTR: true,
  ALIASED_ATTR: true
*/

//////////////////////////////////
//JQLite
//////////////////////////////////

/**
 * @ngdoc function
 * @name angular.element
 * @module ng
 * @kind function
 *
 * @description
 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element.
 *
 * If jQuery is available, `angular.element` is an alias for the
 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element`
 * delegates to AngularJS's built-in subset of jQuery, called "jQuery lite" or **jqLite**.
 *
 * jqLite is a tiny, API-compatible subset of jQuery that allows
 * AngularJS to manipulate the DOM in a cross-browser compatible way. jqLite implements only the most
 * commonly needed functionality with the goal of having a very small footprint.
 *
 * To use `jQuery`, simply ensure it is loaded before the `angular.js` file. You can also use the
 * {@link ngJq `ngJq`} directive to specify that jqlite should be used over jQuery, or to use a
 * specific version of jQuery if multiple versions exist on the page.
 *
 * <div class="alert alert-info">**Note:** All element references in AngularJS are always wrapped with jQuery or
 * jqLite (such as the element argument in a directive's compile / link function). They are never raw DOM references.</div>
 *
 * <div class="alert alert-warning">**Note:** Keep in mind that this function will not find elements
 * by tag name / CSS selector. For lookups by tag name, try instead `angular.element(document).find(...)`
 * or `$document.find()`, or use the standard DOM APIs, e.g. `document.querySelectorAll()`.</div>
 *
 * ## AngularJS's jqLite
 * jqLite provides only the following jQuery methods:
 *
 * - [`addClass()`](http://api.jquery.com/addClass/) - Does not support a function as first argument
 * - [`after()`](http://api.jquery.com/after/)
 * - [`append()`](http://api.jquery.com/append/) - Contrary to jQuery, this doesn't clone elements
 *   so will not work correctly when invoked on a jqLite object containing more than one DOM node
 * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters
 * - [`bind()`](http://api.jquery.com/bind/) (_deprecated_, use [`on()`](http://api.jquery.com/on/)) - Does not support namespaces, selectors or eventData
 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors
 * - [`clone()`](http://api.jquery.com/clone/)
 * - [`contents()`](http://api.jquery.com/contents/)
 * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`.
 *   As a setter, does not convert numbers to strings or append 'px', and also does not have automatic property prefixing.
 * - [`data()`](http://api.jquery.com/data/)
 * - [`detach()`](http://api.jquery.com/detach/)
 * - [`empty()`](http://api.jquery.com/empty/)
 * - [`eq()`](http://api.jquery.com/eq/)
 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name
 * - [`hasClass()`](http://api.jquery.com/hasClass/)
 * - [`html()`](http://api.jquery.com/html/)
 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors
 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData
 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter
 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors
 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors
 * - [`prepend()`](http://api.jquery.com/prepend/)
 * - [`prop()`](http://api.jquery.com/prop/)
 * - [`ready()`](http://api.jquery.com/ready/) (_deprecated_, use `angular.element(callback)` instead of `angular.element(document).ready(callback)`)
 * - [`remove()`](http://api.jquery.com/remove/)
 * - [`removeAttr()`](http://api.jquery.com/removeAttr/) - Does not support multiple attributes
 * - [`removeClass()`](http://api.jquery.com/removeClass/) - Does not support a function as first argument
 * - [`removeData()`](http://api.jquery.com/removeData/)
 * - [`replaceWith()`](http://api.jquery.com/replaceWith/)
 * - [`text()`](http://api.jquery.com/text/)
 * - [`toggleClass()`](http://api.jquery.com/toggleClass/) - Does not support a function as first argument
 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers
 * - [`unbind()`](http://api.jquery.com/unbind/) (_deprecated_, use [`off()`](http://api.jquery.com/off/)) - Does not support namespaces or event object as parameter
 * - [`val()`](http://api.jquery.com/val/)
 * - [`wrap()`](http://api.jquery.com/wrap/)
 *
 * jqLite also provides a method restoring pre-1.8 insecure treatment of XHTML-like tags.
 * This legacy behavior turns input like `<div /><span />` to `<div></div><span></span>`
 * instead of `<div><span></span></div>` like version 1.8 & newer do. To restore it, invoke:
 * ```js
 * angular.UNSAFE_restoreLegacyJqLiteXHTMLReplacement();
 * ```
 * Note that this only patches jqLite. If you use jQuery 3.5.0 or newer, please read the
 * [jQuery 3.5 upgrade guide](https://jquery.com/upgrade-guide/3.5/) for more details
 * about the workarounds.
 *
 * ## jQuery/jqLite Extras
 * AngularJS also provides the following additional methods and events to both jQuery and jqLite:
 *
 * ### Events
 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event
 *    on all DOM nodes being removed.  This can be used to clean up any 3rd party bindings to the DOM
 *    element before it is removed.
 *
 * ### Methods
 * - `controller(name)` - retrieves the controller of the current element or its parent. By default
 *   retrieves controller associated with the `ngController` directive. If `name` is provided as
 *   camelCase directive name, then the controller for this directive will be retrieved (e.g.
 *   `'ngModel'`).
 * - `injector()` - retrieves the injector of the current element or its parent.
 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current
 *   element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to
 *   be enabled.
 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the
 *   current element. This getter should be used only on elements that contain a directive which starts a new isolate
 *   scope. Calling `scope()` on this element always returns the original non-isolate scope.
 *   Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled.
 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
 *   parent element is reached.
 *
 * @knownIssue You cannot spy on `angular.element` if you are using Jasmine version 1.x. See
 * https://github.com/angular/angular.js/issues/14251 for more information.
 *
 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
 * @returns {Object} jQuery object.
 */

JQLite.expando = 'ng339';

var jqCache = JQLite.cache = {},
    jqId = 1;

/*
 * !!! This is an undocumented "private" function !!!
 */
JQLite._data = function(node) {
  //jQuery always returns an object on cache miss
  return this.cache[node[this.expando]] || {};
};

function jqNextId() { return ++jqId; }


var DASH_LOWERCASE_REGEXP = /-([a-z])/g;
var MS_HACK_REGEXP = /^-ms-/;
var MOUSE_EVENT_MAP = { mouseleave: 'mouseout', mouseenter: 'mouseover' };
var jqLiteMinErr = minErr('jqLite');

/**
 * Converts kebab-case to camelCase.
 * There is also a special case for the ms prefix starting with a lowercase letter.
 * @param name Name to normalize
 */
function cssKebabToCamel(name) {
    return kebabToCamel(name.replace(MS_HACK_REGEXP, 'ms-'));
}

function fnCamelCaseReplace(all, letter) {
  return letter.toUpperCase();
}

/**
 * Converts kebab-case to camelCase.
 * @param name Name to normalize
 */
function kebabToCamel(name) {
  return name
    .replace(DASH_LOWERCASE_REGEXP, fnCamelCaseReplace);
}

var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/;
var HTML_REGEXP = /<|&#?\w+;/;
var TAG_NAME_REGEXP = /<([\w:-]+)/;
var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi;

// Table parts need to be wrapped with `<table>` or they're
// stripped to their contents when put in a div.
// XHTML parsers do not magically insert elements in the
// same way that tag soup parsers do, so we cannot shorten
// this by omitting <tbody> or other required elements.
var wrapMap = {
  thead: ['table'],
  col: ['colgroup', 'table'],
  tr: ['tbody', 'table'],
  td: ['tr', 'tbody', 'table']
};

wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
wrapMap.th = wrapMap.td;

// Support: IE <10 only
// IE 9 requires an option wrapper & it needs to have the whole table structure
// set up in advance; assigning `"<td></td>"` to `tr.innerHTML` doesn't work, etc.
var wrapMapIE9 = {
  option: [1, '<select multiple="multiple">', '</select>'],
  _default: [0, '', '']
};

for (var key in wrapMap) {
  var wrapMapValueClosing = wrapMap[key];
  var wrapMapValue = wrapMapValueClosing.slice().reverse();
  wrapMapIE9[key] = [wrapMapValue.length, '<' + wrapMapValue.join('><') + '>', '</' + wrapMapValueClosing.join('></') + '>'];
}

wrapMapIE9.optgroup = wrapMapIE9.option;

function jqLiteIsTextNode(html) {
  return !HTML_REGEXP.test(html);
}

function jqLiteAcceptsData(node) {
  // The window object can accept data but has no nodeType
  // Otherwise we are only interested in elements (1) and documents (9)
  var nodeType = node.nodeType;
  return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT;
}

function jqLiteHasData(node) {
  for (var key in jqCache[node.ng339]) {
    return true;
  }
  return false;
}

function jqLiteBuildFragment(html, context) {
  var tmp, tag, wrap, finalHtml,
      fragment = context.createDocumentFragment(),
      nodes = [], i;

  if (jqLiteIsTextNode(html)) {
    // Convert non-html into a text node
    nodes.push(context.createTextNode(html));
  } else {
    // Convert html into DOM nodes
    tmp = fragment.appendChild(context.createElement('div'));
    tag = (TAG_NAME_REGEXP.exec(html) || ['', ''])[1].toLowerCase();
    finalHtml = JQLite.legacyXHTMLReplacement ?
      html.replace(XHTML_TAG_REGEXP, '<$1></$2>') :
      html;

    if (msie < 10) {
      wrap = wrapMapIE9[tag] || wrapMapIE9._default;
      tmp.innerHTML = wrap[1] + finalHtml + wrap[2];

      // Descend through wrappers to the right content
      i = wrap[0];
      while (i--) {
        tmp = tmp.firstChild;
      }
    } else {
      wrap = wrapMap[tag] || [];

      // Create wrappers & descend into them
      i = wrap.length;
      while (--i > -1) {
        tmp.appendChild(window.document.createElement(wrap[i]));
        tmp = tmp.firstChild;
      }

      tmp.innerHTML = finalHtml;
    }

    nodes = concat(nodes, tmp.childNodes);

    tmp = fragment.firstChild;
    tmp.textContent = '';
  }

  // Remove wrapper from fragment
  fragment.textContent = '';
  fragment.innerHTML = ''; // Clear inner HTML
  forEach(nodes, function(node) {
    fragment.appendChild(node);
  });

  return fragment;
}

function jqLiteParseHTML(html, context) {
  context = context || window.document;
  var parsed;

  if ((parsed = SINGLE_TAG_REGEXP.exec(html))) {
    return [context.createElement(parsed[1])];
  }

  if ((parsed = jqLiteBuildFragment(html, context))) {
    return parsed.childNodes;
  }

  return [];
}

function jqLiteWrapNode(node, wrapper) {
  var parent = node.parentNode;

  if (parent) {
    parent.replaceChild(wrapper, node);
  }

  wrapper.appendChild(node);
}


// IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
var jqLiteContains = window.Node.prototype.contains || /** @this */ function(arg) {
  // eslint-disable-next-line no-bitwise
  return !!(this.compareDocumentPosition(arg) & 16);
};

/////////////////////////////////////////////
function JQLite(element) {
  if (element instanceof JQLite) {
    return element;
  }

  var argIsString;

  if (isString(element)) {
    element = trim(element);
    argIsString = true;
  }
  if (!(this instanceof JQLite)) {
    if (argIsString && element.charAt(0) !== '<') {
      throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element');
    }
    return new JQLite(element);
  }

  if (argIsString) {
    jqLiteAddNodes(this, jqLiteParseHTML(element));
  } else if (isFunction(element)) {
    jqLiteReady(element);
  } else {
    jqLiteAddNodes(this, element);
  }
}

function jqLiteClone(element) {
  return element.cloneNode(true);
}

function jqLiteDealoc(element, onlyDescendants) {
  if (!onlyDescendants && jqLiteAcceptsData(element)) jqLite.cleanData([element]);

  if (element.querySelectorAll) {
    jqLite.cleanData(element.querySelectorAll('*'));
  }
}

function isEmptyObject(obj) {
  var name;

  for (name in obj) {
    return false;
  }
  return true;
}

function removeIfEmptyData(element) {
  var expandoId = element.ng339;
  var expandoStore = expandoId && jqCache[expandoId];

  var events = expandoStore && expandoStore.events;
  var data = expandoStore && expandoStore.data;

  if ((!data || isEmptyObject(data)) && (!events || isEmptyObject(events))) {
    delete jqCache[expandoId];
    element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
  }
}

function jqLiteOff(element, type, fn, unsupported) {
  if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument');

  var expandoStore = jqLiteExpandoStore(element);
  var events = expandoStore && expandoStore.events;
  var handle = expandoStore && expandoStore.handle;

  if (!handle) return; //no listeners registered

  if (!type) {
    for (type in events) {
      if (type !== '$destroy') {
        element.removeEventListener(type, handle);
      }
      delete events[type];
    }
  } else {

    var removeHandler = function(type) {
      var listenerFns = events[type];
      if (isDefined(fn)) {
        arrayRemove(listenerFns || [], fn);
      }
      if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) {
        element.removeEventListener(type, handle);
        delete events[type];
      }
    };

    forEach(type.split(' '), function(type) {
      removeHandler(type);
      if (MOUSE_EVENT_MAP[type]) {
        removeHandler(MOUSE_EVENT_MAP[type]);
      }
    });
  }

  removeIfEmptyData(element);
}

function jqLiteRemoveData(element, name) {
  var expandoId = element.ng339;
  var expandoStore = expandoId && jqCache[expandoId];

  if (expandoStore) {
    if (name) {
      delete expandoStore.data[name];
    } else {
      expandoStore.data = {};
    }

    removeIfEmptyData(element);
  }
}


function jqLiteExpandoStore(element, createIfNecessary) {
  var expandoId = element.ng339,
      expandoStore = expandoId && jqCache[expandoId];

  if (createIfNecessary && !expandoStore) {
    element.ng339 = expandoId = jqNextId();
    expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined};
  }

  return expandoStore;
}


function jqLiteData(element, key, value) {
  if (jqLiteAcceptsData(element)) {
    var prop;

    var isSimpleSetter = isDefined(value);
    var isSimpleGetter = !isSimpleSetter && key && !isObject(key);
    var massGetter = !key;
    var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter);
    var data = expandoStore && expandoStore.data;

    if (isSimpleSetter) { // data('key', value)
      data[kebabToCamel(key)] = value;
    } else {
      if (massGetter) {  // data()
        return data;
      } else {
        if (isSimpleGetter) { // data('key')
          // don't force creation of expandoStore if it doesn't exist yet
          return data && data[kebabToCamel(key)];
        } else { // mass-setter: data({key1: val1, key2: val2})
          for (prop in key) {
            data[kebabToCamel(prop)] = key[prop];
          }
        }
      }
    }
  }
}

function jqLiteHasClass(element, selector) {
  if (!element.getAttribute) return false;
  return ((' ' + (element.getAttribute('class') || '') + ' ').replace(/[\n\t]/g, ' ').
      indexOf(' ' + selector + ' ') > -1);
}

function jqLiteRemoveClass(element, cssClasses) {
  if (cssClasses && element.setAttribute) {
    var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ')
                            .replace(/[\n\t]/g, ' ');
    var newClasses = existingClasses;

    forEach(cssClasses.split(' '), function(cssClass) {
      cssClass = trim(cssClass);
      newClasses = newClasses.replace(' ' + cssClass + ' ', ' ');
    });

    if (newClasses !== existingClasses) {
      element.setAttribute('class', trim(newClasses));
    }
  }
}

function jqLiteAddClass(element, cssClasses) {
  if (cssClasses && element.setAttribute) {
    var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ')
                            .replace(/[\n\t]/g, ' ');
    var newClasses = existingClasses;

    forEach(cssClasses.split(' '), function(cssClass) {
      cssClass = trim(cssClass);
      if (newClasses.indexOf(' ' + cssClass + ' ') === -1) {
        newClasses += cssClass + ' ';
      }
    });

    if (newClasses !== existingClasses) {
      element.setAttribute('class', trim(newClasses));
    }
  }
}


function jqLiteAddNodes(root, elements) {
  // THIS CODE IS VERY HOT. Don't make changes without benchmarking.

  if (elements) {

    // if a Node (the most common case)
    if (elements.nodeType) {
      root[root.length++] = elements;
    } else {
      var length = elements.length;

      // if an Array or NodeList and not a Window
      if (typeof length === 'number' && elements.window !== elements) {
        if (length) {
          for (var i = 0; i < length; i++) {
            root[root.length++] = elements[i];
          }
        }
      } else {
        root[root.length++] = elements;
      }
    }
  }
}


function jqLiteController(element, name) {
  return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller');
}

function jqLiteInheritedData(element, name, value) {
  // if element is the document object work with the html element instead
  // this makes $(document).scope() possible
  if (element.nodeType === NODE_TYPE_DOCUMENT) {
    element = element.documentElement;
  }
  var names = isArray(name) ? name : [name];

  while (element) {
    for (var i = 0, ii = names.length; i < ii; i++) {
      if (isDefined(value = jqLite.data(element, names[i]))) return value;
    }

    // If dealing with a document fragment node with a host element, and no parent, use the host
    // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM
    // to lookup parent controllers.
    element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host);
  }
}

function jqLiteEmpty(element) {
  jqLiteDealoc(element, true);
  while (element.firstChild) {
    element.removeChild(element.firstChild);
  }
}

function jqLiteRemove(element, keepData) {
  if (!keepData) jqLiteDealoc(element);
  var parent = element.parentNode;
  if (parent) parent.removeChild(element);
}


function jqLiteDocumentLoaded(action, win) {
  win = win || window;
  if (win.document.readyState === 'complete') {
    // Force the action to be run async for consistent behavior
    // from the action's point of view
    // i.e. it will definitely not be in a $apply
    win.setTimeout(action);
  } else {
    // No need to unbind this handler as load is only ever called once
    jqLite(win).on('load', action);
  }
}

function jqLiteReady(fn) {
  function trigger() {
    window.document.removeEventListener('DOMContentLoaded', trigger);
    window.removeEventListener('load', trigger);
    fn();
  }

  // check if document is already loaded
  if (window.document.readyState === 'complete') {
    window.setTimeout(fn);
  } else {
    // We can not use jqLite since we are not done loading and jQuery could be loaded later.

    // Works for modern browsers and IE9
    window.document.addEventListener('DOMContentLoaded', trigger);

    // Fallback to window.onload for others
    window.addEventListener('load', trigger);
  }
}

//////////////////////////////////////////
// Functions which are declared directly.
//////////////////////////////////////////
var JQLitePrototype = JQLite.prototype = {
  ready: jqLiteReady,
  toString: function() {
    var value = [];
    forEach(this, function(e) { value.push('' + e);});
    return '[' + value.join(', ') + ']';
  },

  eq: function(index) {
      return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]);
  },

  length: 0,
  push: push,
  sort: [].sort,
  splice: [].splice
};

//////////////////////////////////////////
// Functions iterating getter/setters.
// these functions return self on setter and
// value on get.
//////////////////////////////////////////
var BOOLEAN_ATTR = {};
forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) {
  BOOLEAN_ATTR[lowercase(value)] = value;
});
var BOOLEAN_ELEMENTS = {};
forEach('input,select,option,textarea,button,form,details'.split(','), function(value) {
  BOOLEAN_ELEMENTS[value] = true;
});
var ALIASED_ATTR = {
  'ngMinlength': 'minlength',
  'ngMaxlength': 'maxlength',
  'ngMin': 'min',
  'ngMax': 'max',
  'ngPattern': 'pattern',
  'ngStep': 'step'
};

function getBooleanAttrName(element, name) {
  // check dom last since we will most likely fail on name
  var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()];

  // booleanAttr is here twice to minimize DOM access
  return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr;
}

function getAliasedAttrName(name) {
  return ALIASED_ATTR[name];
}

forEach({
  data: jqLiteData,
  removeData: jqLiteRemoveData,
  hasData: jqLiteHasData,
  cleanData: function jqLiteCleanData(nodes) {
    for (var i = 0, ii = nodes.length; i < ii; i++) {
      jqLiteRemoveData(nodes[i]);
      jqLiteOff(nodes[i]);
    }
  }
}, function(fn, name) {
  JQLite[name] = fn;
});

forEach({
  data: jqLiteData,
  inheritedData: jqLiteInheritedData,

  scope: function(element) {
    // Can't use jqLiteData here directly so we stay compatible with jQuery!
    return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']);
  },

  isolateScope: function(element) {
    // Can't use jqLiteData here directly so we stay compatible with jQuery!
    return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate');
  },

  controller: jqLiteController,

  injector: function(element) {
    return jqLiteInheritedData(element, '$injector');
  },

  removeAttr: function(element, name) {
    element.removeAttribute(name);
  },

  hasClass: jqLiteHasClass,

  css: function(element, name, value) {
    name = cssKebabToCamel(name);

    if (isDefined(value)) {
      element.style[name] = value;
    } else {
      return element.style[name];
    }
  },

  attr: function(element, name, value) {
    var ret;
    var nodeType = element.nodeType;
    if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT ||
      !element.getAttribute) {
      return;
    }

    var lowercasedName = lowercase(name);
    var isBooleanAttr = BOOLEAN_ATTR[lowercasedName];

    if (isDefined(value)) {
      // setter

      if (value === null || (value === false && isBooleanAttr)) {
        element.removeAttribute(name);
      } else {
        element.setAttribute(name, isBooleanAttr ? lowercasedName : value);
      }
    } else {
      // getter

      ret = element.getAttribute(name);

      if (isBooleanAttr && ret !== null) {
        ret = lowercasedName;
      }
      // Normalize non-existing attributes to undefined (as jQuery).
      return ret === null ? undefined : ret;
    }
  },

  prop: function(element, name, value) {
    if (isDefined(value)) {
      element[name] = value;
    } else {
      return element[name];
    }
  },

  text: (function() {
    getText.$dv = '';
    return getText;

    function getText(element, value) {
      if (isUndefined(value)) {
        var nodeType = element.nodeType;
        return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : '';
      }
      element.textContent = value;
    }
  })(),

  val: function(element, value) {
    if (isUndefined(value)) {
      if (element.multiple && nodeName_(element) === 'select') {
        var result = [];
        forEach(element.options, function(option) {
          if (option.selected) {
            result.push(option.value || option.text);
          }
        });
        return result;
      }
      return element.value;
    }
    element.value = value;
  },

  html: function(element, value) {
    if (isUndefined(value)) {
      return element.innerHTML;
    }
    jqLiteDealoc(element, true);
    element.innerHTML = value;
  },

  empty: jqLiteEmpty
}, function(fn, name) {
  /**
   * Properties: writes return selection, reads return first value
   */
  JQLite.prototype[name] = function(arg1, arg2) {
    var i, key;
    var nodeCount = this.length;

    // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
    // in a way that survives minification.
    // jqLiteEmpty takes no arguments but is a setter.
    if (fn !== jqLiteEmpty &&
        (isUndefined((fn.length === 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) {
      if (isObject(arg1)) {

        // we are a write, but the object properties are the key/values
        for (i = 0; i < nodeCount; i++) {
          if (fn === jqLiteData) {
            // data() takes the whole object in jQuery
            fn(this[i], arg1);
          } else {
            for (key in arg1) {
              fn(this[i], key, arg1[key]);
            }
          }
        }
        // return self for chaining
        return this;
      } else {
        // we are a read, so read the first child.
        // TODO: do we still need this?
        var value = fn.$dv;
        // Only if we have $dv do we iterate over all, otherwise it is just the first element.
        var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount;
        for (var j = 0; j < jj; j++) {
          var nodeValue = fn(this[j], arg1, arg2);
          value = value ? value + nodeValue : nodeValue;
        }
        return value;
      }
    } else {
      // we are a write, so apply to all children
      for (i = 0; i < nodeCount; i++) {
        fn(this[i], arg1, arg2);
      }
      // return self for chaining
      return this;
    }
  };
});

function createEventHandler(element, events) {
  var eventHandler = function(event, type) {
    // jQuery specific api
    event.isDefaultPrevented = function() {
      return event.defaultPrevented;
    };

    var eventFns = events[type || event.type];
    var eventFnsLength = eventFns ? eventFns.length : 0;

    if (!eventFnsLength) return;

    if (isUndefined(event.immediatePropagationStopped)) {
      var originalStopImmediatePropagation = event.stopImmediatePropagation;
      event.stopImmediatePropagation = function() {
        event.immediatePropagationStopped = true;

        if (event.stopPropagation) {
          event.stopPropagation();
        }

        if (originalStopImmediatePropagation) {
          originalStopImmediatePropagation.call(event);
        }
      };
    }

    event.isImmediatePropagationStopped = function() {
      return event.immediatePropagationStopped === true;
    };

    // Some events have special handlers that wrap the real handler
    var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper;

    // Copy event handlers in case event handlers array is modified during execution.
    if ((eventFnsLength > 1)) {
      eventFns = shallowCopy(eventFns);
    }

    for (var i = 0; i < eventFnsLength; i++) {
      if (!event.isImmediatePropagationStopped()) {
        handlerWrapper(element, event, eventFns[i]);
      }
    }
  };

  // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all
  //       events on `element`
  eventHandler.elem = element;
  return eventHandler;
}

function defaultHandlerWrapper(element, event, handler) {
  handler.call(element, event);
}

function specialMouseHandlerWrapper(target, event, handler) {
  // Refer to jQuery's implementation of mouseenter & mouseleave
  // Read about mouseenter and mouseleave:
  // http://www.quirksmode.org/js/events_mouse.html#link8
  var related = event.relatedTarget;
  // For mousenter/leave call the handler if related is outside the target.
  // NB: No relatedTarget if the mouse left/entered the browser window
  if (!related || (related !== target && !jqLiteContains.call(target, related))) {
    handler.call(target, event);
  }
}

//////////////////////////////////////////
// Functions iterating traversal.
// These functions chain results into a single
// selector.
//////////////////////////////////////////
forEach({
  removeData: jqLiteRemoveData,

  on: function jqLiteOn(element, type, fn, unsupported) {
    if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters');

    // Do not add event handlers to non-elements because they will not be cleaned up.
    if (!jqLiteAcceptsData(element)) {
      return;
    }

    var expandoStore = jqLiteExpandoStore(element, true);
    var events = expandoStore.events;
    var handle = expandoStore.handle;

    if (!handle) {
      handle = expandoStore.handle = createEventHandler(element, events);
    }

    // http://jsperf.com/string-indexof-vs-split
    var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type];
    var i = types.length;

    var addHandler = function(type, specialHandlerWrapper, noEventListener) {
      var eventFns = events[type];

      if (!eventFns) {
        eventFns = events[type] = [];
        eventFns.specialHandlerWrapper = specialHandlerWrapper;
        if (type !== '$destroy' && !noEventListener) {
          element.addEventListener(type, handle);
        }
      }

      eventFns.push(fn);
    };

    while (i--) {
      type = types[i];
      if (MOUSE_EVENT_MAP[type]) {
        addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper);
        addHandler(type, undefined, true);
      } else {
        addHandler(type);
      }
    }
  },

  off: jqLiteOff,

  one: function(element, type, fn) {
    element = jqLite(element);

    //add the listener twice so that when it is called
    //you can remove the original function and still be
    //able to call element.off(ev, fn) normally
    element.on(type, function onFn() {
      element.off(type, fn);
      element.off(type, onFn);
    });
    element.on(type, fn);
  },

  replaceWith: function(element, replaceNode) {
    var index, parent = element.parentNode;
    jqLiteDealoc(element);
    forEach(new JQLite(replaceNode), function(node) {
      if (index) {
        parent.insertBefore(node, index.nextSibling);
      } else {
        parent.replaceChild(node, element);
      }
      index = node;
    });
  },

  children: function(element) {
    var children = [];
    forEach(element.childNodes, function(element) {
      if (element.nodeType === NODE_TYPE_ELEMENT) {
        children.push(element);
      }
    });
    return children;
  },

  contents: function(element) {
    return element.contentDocument || element.childNodes || [];
  },

  append: function(element, node) {
    var nodeType = element.nodeType;
    if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return;

    node = new JQLite(node);

    for (var i = 0, ii = node.length; i < ii; i++) {
      var child = node[i];
      element.appendChild(child);
    }
  },

  prepend: function(element, node) {
    if (element.nodeType === NODE_TYPE_ELEMENT) {
      var index = element.firstChild;
      forEach(new JQLite(node), function(child) {
        element.insertBefore(child, index);
      });
    }
  },

  wrap: function(element, wrapNode) {
    jqLiteWrapNode(element, jqLite(wrapNode).eq(0).clone()[0]);
  },

  remove: jqLiteRemove,

  detach: function(element) {
    jqLiteRemove(element, true);
  },

  after: function(element, newElement) {
    var index = element, parent = element.parentNode;

    if (parent) {
      newElement = new JQLite(newElement);

      for (var i = 0, ii = newElement.length; i < ii; i++) {
        var node = newElement[i];
        parent.insertBefore(node, index.nextSibling);
        index = node;
      }
    }
  },

  addClass: jqLiteAddClass,
  removeClass: jqLiteRemoveClass,

  toggleClass: function(element, selector, condition) {
    if (selector) {
      forEach(selector.split(' '), function(className) {
        var classCondition = condition;
        if (isUndefined(classCondition)) {
          classCondition = !jqLiteHasClass(element, className);
        }
        (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className);
      });
    }
  },

  parent: function(element) {
    var parent = element.parentNode;
    return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null;
  },

  next: function(element) {
    return element.nextElementSibling;
  },

  find: function(element, selector) {
    if (element.getElementsByTagName) {
      return element.getElementsByTagName(selector);
    } else {
      return [];
    }
  },

  clone: jqLiteClone,

  triggerHandler: function(element, event, extraParameters) {

    var dummyEvent, eventFnsCopy, handlerArgs;
    var eventName = event.type || event;
    var expandoStore = jqLiteExpandoStore(element);
    var events = expandoStore && expandoStore.events;
    var eventFns = events && events[eventName];

    if (eventFns) {
      // Create a dummy event to pass to the handlers
      dummyEvent = {
        preventDefault: function() { this.defaultPrevented = true; },
        isDefaultPrevented: function() { return this.defaultPrevented === true; },
        stopImmediatePropagation: function() { this.immediatePropagationStopped = true; },
        isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; },
        stopPropagation: noop,
        type: eventName,
        target: element
      };

      // If a custom event was provided then extend our dummy event with it
      if (event.type) {
        dummyEvent = extend(dummyEvent, event);
      }

      // Copy event handlers in case event handlers array is modified during execution.
      eventFnsCopy = shallowCopy(eventFns);
      handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent];

      forEach(eventFnsCopy, function(fn) {
        if (!dummyEvent.isImmediatePropagationStopped()) {
          fn.apply(element, handlerArgs);
        }
      });
    }
  }
}, function(fn, name) {
  /**
   * chaining functions
   */
  JQLite.prototype[name] = function(arg1, arg2, arg3) {
    var value;

    for (var i = 0, ii = this.length; i < ii; i++) {
      if (isUndefined(value)) {
        value = fn(this[i], arg1, arg2, arg3);
        if (isDefined(value)) {
          // any function which returns a value needs to be wrapped
          value = jqLite(value);
        }
      } else {
        jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3));
      }
    }
    return isDefined(value) ? value : this;
  };
});

// bind legacy bind/unbind to on/off
JQLite.prototype.bind = JQLite.prototype.on;
JQLite.prototype.unbind = JQLite.prototype.off;


// Provider for private $$jqLite service
/** @this */
function $$jqLiteProvider() {
  this.$get = function $$jqLite() {
    return extend(JQLite, {
      hasClass: function(node, classes) {
        if (node.attr) node = node[0];
        return jqLiteHasClass(node, classes);
      },
      addClass: function(node, classes) {
        if (node.attr) node = node[0];
        return jqLiteAddClass(node, classes);
      },
      removeClass: function(node, classes) {
        if (node.attr) node = node[0];
        return jqLiteRemoveClass(node, classes);
      }
    });
  };
}

/**
 * Computes a hash of an 'obj'.
 * Hash of a:
 *  string is string
 *  number is number as string
 *  object is either result of calling $$hashKey function on the object or uniquely generated id,
 *         that is also assigned to the $$hashKey property of the object.
 *
 * @param obj
 * @returns {string} hash string such that the same input will have the same hash string.
 *         The resulting string key is in 'type:hashKey' format.
 */
function hashKey(obj, nextUidFn) {
  var key = obj && obj.$$hashKey;

  if (key) {
    if (typeof key === 'function') {
      key = obj.$$hashKey();
    }
    return key;
  }

  var objType = typeof obj;
  if (objType === 'function' || (objType === 'object' && obj !== null)) {
    key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)();
  } else {
    key = objType + ':' + obj;
  }

  return key;
}

// A minimal ES2015 Map implementation.
// Should be bug/feature equivalent to the native implementations of supported browsers
// (for the features required in Angular).
// See https://kangax.github.io/compat-table/es6/#test-Map
var nanKey = Object.create(null);
function NgMapShim() {
  this._keys = [];
  this._values = [];
  this._lastKey = NaN;
  this._lastIndex = -1;
}
NgMapShim.prototype = {
  _idx: function(key) {
    if (key !== this._lastKey) {
      this._lastKey = key;
      this._lastIndex = this._keys.indexOf(key);
    }
    return this._lastIndex;
  },
  _transformKey: function(key) {
    return isNumberNaN(key) ? nanKey : key;
  },
  get: function(key) {
    key = this._transformKey(key);
    var idx = this._idx(key);
    if (idx !== -1) {
      return this._values[idx];
    }
  },
  has: function(key) {
    key = this._transformKey(key);
    var idx = this._idx(key);
    return idx !== -1;
  },
  set: function(key, value) {
    key = this._transformKey(key);
    var idx = this._idx(key);
    if (idx === -1) {
      idx = this._lastIndex = this._keys.length;
    }
    this._keys[idx] = key;
    this._values[idx] = value;

    // Support: IE11
    // Do not `return this` to simulate the partial IE11 implementation
  },
  delete: function(key) {
    key = this._transformKey(key);
    var idx = this._idx(key);
    if (idx === -1) {
      return false;
    }
    this._keys.splice(idx, 1);
    this._values.splice(idx, 1);
    this._lastKey = NaN;
    this._lastIndex = -1;
    return true;
  }
};

// For now, always use `NgMapShim`, even if `window.Map` is available. Some native implementations
// are still buggy (often in subtle ways) and can cause hard-to-debug failures. When native `Map`
// implementations get more stable, we can reconsider switching to `window.Map` (when available).
var NgMap = NgMapShim;

var $$MapProvider = [/** @this */function() {
  this.$get = [function() {
    return NgMap;
  }];
}];

/**
 * @ngdoc function
 * @module ng
 * @name angular.injector
 * @kind function
 *
 * @description
 * Creates an injector object that can be used for retrieving services as well as for
 * dependency injection (see {@link guide/di dependency injection}).
 *
 * @param {Array.<string|Function>} modules A list of module functions or their aliases. See
 *     {@link angular.module}. The `ng` module must be explicitly added.
 * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which
 *     disallows argument name annotation inference.
 * @returns {injector} Injector object. See {@link auto.$injector $injector}.
 *
 * @example
 * Typical usage
 * ```js
 *   // create an injector
 *   var $injector = angular.injector(['ng']);
 *
 *   // use the injector to kick off your application
 *   // use the type inference to auto inject arguments, or use implicit injection
 *   $injector.invoke(function($rootScope, $compile, $document) {
 *     $compile($document)($rootScope);
 *     $rootScope.$digest();
 *   });
 * ```
 *
 * Sometimes you want to get access to the injector of a currently running AngularJS app
 * from outside AngularJS. Perhaps, you want to inject and compile some markup after the
 * application has been bootstrapped. You can do this using the extra `injector()` added
 * to JQuery/jqLite elements. See {@link angular.element}.
 *
 * *This is fairly rare but could be the case if a third party library is injecting the
 * markup.*
 *
 * In the following example a new block of HTML containing a `ng-controller`
 * directive is added to the end of the document body by JQuery. We then compile and link
 * it into the current AngularJS scope.
 *
 * ```js
 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>');
 * $(document.body).append($div);
 *
 * angular.element(document).injector().invoke(function($compile) {
 *   var scope = angular.element($div).scope();
 *   $compile($div)(scope);
 * });
 * ```
 */


/**
 * @ngdoc module
 * @name auto
 * @installation
 * @description
 *
 * Implicit module which gets automatically added to each {@link auto.$injector $injector}.
 */

var ARROW_ARG = /^([^(]+?)=>/;
var FN_ARGS = /^[^(]*\(\s*([^)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var $injectorMinErr = minErr('$injector');

function stringifyFn(fn) {
  return Function.prototype.toString.call(fn);
}

function extractArgs(fn) {
  var fnText = stringifyFn(fn).replace(STRIP_COMMENTS, ''),
      args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS);
  return args;
}

function anonFn(fn) {
  // For anonymous functions, showing at the very least the function signature can help in
  // debugging.
  var args = extractArgs(fn);
  if (args) {
    return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')';
  }
  return 'fn';
}

function annotate(fn, strictDi, name) {
  var $inject,
      argDecl,
      last;

  if (typeof fn === 'function') {
    if (!($inject = fn.$inject)) {
      $inject = [];
      if (fn.length) {
        if (strictDi) {
          if (!isString(name) || !name) {
            name = fn.name || anonFn(fn);
          }
          throw $injectorMinErr('strictdi',
            '{0} is not using explicit annotation and cannot be invoked in strict mode', name);
        }
        argDecl = extractArgs(fn);
        forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
          arg.replace(FN_ARG, function(all, underscore, name) {
            $inject.push(name);
          });
        });
      }
      fn.$inject = $inject;
    }
  } else if (isArray(fn)) {
    last = fn.length - 1;
    assertArgFn(fn[last], 'fn');
    $inject = fn.slice(0, last);
  } else {
    assertArgFn(fn, 'fn', true);
  }
  return $inject;
}

///////////////////////////////////////

/**
 * @ngdoc service
 * @name $injector
 *
 * @description
 *
 * `$injector` is used to retrieve object instances as defined by
 * {@link auto.$provide provider}, instantiate types, invoke methods,
 * and load modules.
 *
 * The following always holds true:
 *
 * ```js
 *   var $injector = angular.injector();
 *   expect($injector.get('$injector')).toBe($injector);
 *   expect($injector.invoke(function($injector) {
 *     return $injector;
 *   })).toBe($injector);
 * ```
 *
 * ## Injection Function Annotation
 *
 * JavaScript does not have annotations, and annotations are needed for dependency injection. The
 * following are all valid ways of annotating function with injection arguments and are equivalent.
 *
 * ```js
 *   // inferred (only works if code not minified/obfuscated)
 *   $injector.invoke(function(serviceA){});
 *
 *   // annotated
 *   function explicit(serviceA) {};
 *   explicit.$inject = ['serviceA'];
 *   $injector.invoke(explicit);
 *
 *   // inline
 *   $injector.invoke(['serviceA', function(serviceA){}]);
 * ```
 *
 * ### Inference
 *
 * In JavaScript calling `toString()` on a function returns the function definition. The definition
 * can then be parsed and the function arguments can be extracted. This method of discovering
 * annotations is disallowed when the injector is in strict mode.
 * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the
 * argument names.
 *
 * ### `$inject` Annotation
 * By adding an `$inject` property onto a function the injection parameters can be specified.
 *
 * ### Inline
 * As an array of injection names, where the last item in the array is the function to call.
 */

/**
 * @ngdoc property
 * @name $injector#modules
 * @type {Object}
 * @description
 * A hash containing all the modules that have been loaded into the
 * $injector.
 *
 * You can use this property to find out information about a module via the
 * {@link angular.Module#info `myModule.info(...)`} method.
 *
 * For example:
 *
 * ```
 * var info = $injector.modules['ngAnimate'].info();
 * ```
 *
 * **Do not use this property to attempt to modify the modules after the application
 * has been bootstrapped.**
 */


/**
 * @ngdoc method
 * @name $injector#get
 *
 * @description
 * Return an instance of the service.
 *
 * @param {string} name The name of the instance to retrieve.
 * @param {string=} caller An optional string to provide the origin of the function call for error messages.
 * @return {*} The instance.
 */

/**
 * @ngdoc method
 * @name $injector#invoke
 *
 * @description
 * Invoke the method and supply the method arguments from the `$injector`.
 *
 * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are
 *   injected according to the {@link guide/di $inject Annotation} rules.
 * @param {Object=} self The `this` for the invoked method.
 * @param {Object=} locals Optional object. If preset then any argument names are read from this
 *                         object first, before the `$injector` is consulted.
 * @returns {*} the value returned by the invoked `fn` function.
 */

/**
 * @ngdoc method
 * @name $injector#has
 *
 * @description
 * Allows the user to query if the particular service exists.
 *
 * @param {string} name Name of the service to query.
 * @returns {boolean} `true` if injector has given service.
 */

/**
 * @ngdoc method
 * @name $injector#instantiate
 * @description
 * Create a new instance of JS type. The method takes a constructor function, invokes the new
 * operator, and supplies all of the arguments to the constructor function as specified by the
 * constructor annotation.
 *
 * @param {Function} Type Annotated constructor function.
 * @param {Object=} locals Optional object. If preset then any argument names are read from this
 * object first, before the `$injector` is consulted.
 * @returns {Object} new instance of `Type`.
 */

/**
 * @ngdoc method
 * @name $injector#annotate
 *
 * @description
 * Returns an array of service names which the function is requesting for injection. This API is
 * used by the injector to determine which services need to be injected into the function when the
 * function is invoked. There are three ways in which the function can be annotated with the needed
 * dependencies.
 *
 * #### Argument names
 *
 * The simplest form is to extract the dependencies from the arguments of the function. This is done
 * by converting the function into a string using `toString()` method and extracting the argument
 * names.
 * ```js
 *   // Given
 *   function MyController($scope, $route) {
 *     // ...
 *   }
 *
 *   // Then
 *   expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
 * ```
 *
 * You can disallow this method by using strict injection mode.
 *
 * This method does not work with code minification / obfuscation. For this reason the following
 * annotation strategies are supported.
 *
 * #### The `$inject` property
 *
 * If a function has an `$inject` property and its value is an array of strings, then the strings
 * represent names of services to be injected into the function.
 * ```js
 *   // Given
 *   var MyController = function(obfuscatedScope, obfuscatedRoute) {
 *     // ...
 *   }
 *   // Define function dependencies
 *   MyController['$inject'] = ['$scope', '$route'];
 *
 *   // Then
 *   expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
 * ```
 *
 * #### The array notation
 *
 * It is often desirable to inline Injected functions and that's when setting the `$inject` property
 * is very inconvenient. In these situations using the array notation to specify the dependencies in
 * a way that survives minification is a better choice:
 *
 * ```js
 *   // We wish to write this (not minification / obfuscation safe)
 *   injector.invoke(function($compile, $rootScope) {
 *     // ...
 *   });
 *
 *   // We are forced to write break inlining
 *   var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) {
 *     // ...
 *   };
 *   tmpFn.$inject = ['$compile', '$rootScope'];
 *   injector.invoke(tmpFn);
 *
 *   // To better support inline function the inline annotation is supported
 *   injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
 *     // ...
 *   }]);
 *
 *   // Therefore
 *   expect(injector.annotate(
 *      ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}])
 *    ).toEqual(['$compile', '$rootScope']);
 * ```
 *
 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to
 * be retrieved as described above.
 *
 * @param {boolean=} [strictDi=false] Disallow argument name annotation inference.
 *
 * @returns {Array.<string>} The names of the services which the function requires.
 */
/**
 * @ngdoc method
 * @name $injector#loadNewModules
 *
 * @description
 *
 * **This is a dangerous API, which you use at your own risk!**
 *
 * Add the specified modules to the current injector.
 *
 * This method will add each of the injectables to the injector and execute all of the config and run
 * blocks for each module passed to the method.
 *
 * If a module has already been loaded into the injector then it will not be loaded again.
 *
 * * The application developer is responsible for loading the code containing the modules; and for
 * ensuring that lazy scripts are not downloaded and executed more often that desired.
 * * Previously compiled HTML will not be affected by newly loaded directives, filters and components.
 * * Modules cannot be unloaded.
 *
 * You can use {@link $injector#modules `$injector.modules`} to check whether a module has been loaded
 * into the injector, which may indicate whether the script has been executed already.
 *
 * @example
 * Here is an example of loading a bundle of modules, with a utility method called `getScript`:
 *
 * ```javascript
 * app.factory('loadModule', function($injector) {
 *   return function loadModule(moduleName, bundleUrl) {
 *     return getScript(bundleUrl).then(function() { $injector.loadNewModules([moduleName]); });
 *   };
 * })
 * ```
 *
 * @param {Array<String|Function|Array>=} mods an array of modules to load into the application.
 *     Each item in the array should be the name of a predefined module or a (DI annotated)
 *     function that will be invoked by the injector as a `config` block.
 *     See: {@link angular.module modules}
 */


/**
 * @ngdoc service
 * @name $provide
 *
 * @description
 *
 * The {@link auto.$provide $provide} service has a number of methods for registering components
 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on
 * {@link angular.Module}.
 *
 * An AngularJS **service** is a singleton object created by a **service factory**.  These **service
 * factories** are functions which, in turn, are created by a **service provider**.
 * The **service providers** are constructor functions. When instantiated they must contain a
 * property called `$get`, which holds the **service factory** function.
 *
 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the
 * correct **service provider**, instantiating it and then calling its `$get` **service factory**
 * function to get the instance of the **service**.
 *
 * Often services have no configuration options and there is no need to add methods to the service
 * provider.  The provider will be no more than a constructor function with a `$get` property. For
 * these cases the {@link auto.$provide $provide} service has additional helper methods to register
 * services without specifying a provider.
 *
 * * {@link auto.$provide#provider provider(name, provider)} - registers a **service provider** with the
 *     {@link auto.$injector $injector}
 * * {@link auto.$provide#constant constant(name, obj)} - registers a value/object that can be accessed by
 *     providers and services.
 * * {@link auto.$provide#value value(name, obj)} - registers a value/object that can only be accessed by
 *     services, not providers.
 * * {@link auto.$provide#factory factory(name, fn)} - registers a service **factory function**
 *     that will be wrapped in a **service provider** object, whose `$get` property will contain the
 *     given factory function.
 * * {@link auto.$provide#service service(name, Fn)} - registers a **constructor function**
 *     that will be wrapped in a **service provider** object, whose `$get` property will instantiate
 *      a new object using the given constructor function.
 * * {@link auto.$provide#decorator decorator(name, decorFn)} - registers a **decorator function** that
 *      will be able to modify or replace the implementation of another service.
 *
 * See the individual methods for more information and examples.
 */

/**
 * @ngdoc method
 * @name $provide#provider
 * @description
 *
 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions
 * are constructor functions, whose instances are responsible for "providing" a factory for a
 * service.
 *
 * Service provider names start with the name of the service they provide followed by `Provider`.
 * For example, the {@link ng.$log $log} service has a provider called
 * {@link ng.$logProvider $logProvider}.
 *
 * Service provider objects can have additional methods which allow configuration of the provider
 * and its service. Importantly, you can configure what kind of service is created by the `$get`
 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a
 * method {@link ng.$logProvider#debugEnabled debugEnabled}
 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the
 * console or not.
 *
 * It is possible to inject other providers into the provider function,
 * but the injected provider must have been defined before the one that requires it.
 *
 * @param {string} name The name of the instance. NOTE: the provider will be available under `name +
                        'Provider'` key.
 * @param {(Object|function())} provider If the provider is:
 *
 *   - `Object`: then it should have a `$get` method. The `$get` method will be invoked using
 *     {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created.
 *   - `Constructor`: a new instance of the provider will be created using
 *     {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`.
 *
 * @returns {Object} registered provider instance

 * @example
 *
 * The following example shows how to create a simple event tracking service and register it using
 * {@link auto.$provide#provider $provide.provider()}.
 *
 * ```js
 *  // Define the eventTracker provider
 *  function EventTrackerProvider() {
 *    var trackingUrl = '/track';
 *
 *    // A provider method for configuring where the tracked events should been saved
 *    this.setTrackingUrl = function(url) {
 *      trackingUrl = url;
 *    };
 *
 *    // The service factory function
 *    this.$get = ['$http', function($http) {
 *      var trackedEvents = {};
 *      return {
 *        // Call this to track an event
 *        event: function(event) {
 *          var count = trackedEvents[event] || 0;
 *          count += 1;
 *          trackedEvents[event] = count;
 *          return count;
 *        },
 *        // Call this to save the tracked events to the trackingUrl
 *        save: function() {
 *          $http.post(trackingUrl, trackedEvents);
 *        }
 *      };
 *    }];
 *  }
 *
 *  describe('eventTracker', function() {
 *    var postSpy;
 *
 *    beforeEach(module(function($provide) {
 *      // Register the eventTracker provider
 *      $provide.provider('eventTracker', EventTrackerProvider);
 *    }));
 *
 *    beforeEach(module(function(eventTrackerProvider) {
 *      // Configure eventTracker provider
 *      eventTrackerProvider.setTrackingUrl('/custom-track');
 *    }));
 *
 *    it('tracks events', inject(function(eventTracker) {
 *      expect(eventTracker.event('login')).toEqual(1);
 *      expect(eventTracker.event('login')).toEqual(2);
 *    }));
 *
 *    it('saves to the tracking url', inject(function(eventTracker, $http) {
 *      postSpy = spyOn($http, 'post');
 *      eventTracker.event('login');
 *      eventTracker.save();
 *      expect(postSpy).toHaveBeenCalled();
 *      expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track');
 *      expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track');
 *      expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 });
 *    }));
 *  });
 * ```
 */

/**
 * @ngdoc method
 * @name $provide#factory
 * @description
 *
 * Register a **service factory**, which will be called to return the service instance.
 * This is short for registering a service where its provider consists of only a `$get` property,
 * which is the given service factory function.
 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to
 * configure your service in a provider.
 *
 * @param {string} name The name of the instance.
 * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation.
 *                      Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`.
 * @returns {Object} registered provider instance
 *
 * @example
 * Here is an example of registering a service
 * ```js
 *   $provide.factory('ping', ['$http', function($http) {
 *     return function ping() {
 *       return $http.send('/ping');
 *     };
 *   }]);
 * ```
 * You would then inject and use this service like this:
 * ```js
 *   someModule.controller('Ctrl', ['ping', function(ping) {
 *     ping();
 *   }]);
 * ```
 */


/**
 * @ngdoc method
 * @name $provide#service
 * @description
 *
 * Register a **service constructor**, which will be invoked with `new` to create the service
 * instance.
 * This is short for registering a service where its provider's `$get` property is a factory
 * function that returns an instance instantiated by the injector from the service constructor
 * function.
 *
 * Internally it looks a bit like this:
 *
 * ```
 * {
 *   $get: function() {
 *     return $injector.instantiate(constructor);
 *   }
 * }
 * ```
 *
 *
 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service
 * as a type/class.
 *
 * @param {string} name The name of the instance.
 * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function)
 *     that will be instantiated.
 * @returns {Object} registered provider instance
 *
 * @example
 * Here is an example of registering a service using
 * {@link auto.$provide#service $provide.service(class)}.
 * ```js
 *   var Ping = function($http) {
 *     this.$http = $http;
 *   };
 *
 *   Ping.$inject = ['$http'];
 *
 *   Ping.prototype.send = function() {
 *     return this.$http.get('/ping');
 *   };
 *   $provide.service('ping', Ping);
 * ```
 * You would then inject and use this service like this:
 * ```js
 *   someModule.controller('Ctrl', ['ping', function(ping) {
 *     ping.send();
 *   }]);
 * ```
 */


/**
 * @ngdoc method
 * @name $provide#value
 * @description
 *
 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a
 * number, an array, an object or a function. This is short for registering a service where its
 * provider's `$get` property is a factory function that takes no arguments and returns the **value
 * service**. That also means it is not possible to inject other services into a value service.
 *
 * Value services are similar to constant services, except that they cannot be injected into a
 * module configuration function (see {@link angular.Module#config}) but they can be overridden by
 * an AngularJS {@link auto.$provide#decorator decorator}.
 *
 * @param {string} name The name of the instance.
 * @param {*} value The value.
 * @returns {Object} registered provider instance
 *
 * @example
 * Here are some examples of creating value services.
 * ```js
 *   $provide.value('ADMIN_USER', 'admin');
 *
 *   $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 });
 *
 *   $provide.value('halfOf', function(value) {
 *     return value / 2;
 *   });
 * ```
 */


/**
 * @ngdoc method
 * @name $provide#constant
 * @description
 *
 * Register a **constant service** with the {@link auto.$injector $injector}, such as a string,
 * a number, an array, an object or a function. Like the {@link auto.$provide#value value}, it is not
 * possible to inject other services into a constant.
 *
 * But unlike {@link auto.$provide#value value}, a constant can be
 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot
 * be overridden by an AngularJS {@link auto.$provide#decorator decorator}.
 *
 * @param {string} name The name of the constant.
 * @param {*} value The constant value.
 * @returns {Object} registered instance
 *
 * @example
 * Here a some examples of creating constants:
 * ```js
 *   $provide.constant('SHARD_HEIGHT', 306);
 *
 *   $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']);
 *
 *   $provide.constant('double', function(value) {
 *     return value * 2;
 *   });
 * ```
 */


/**
 * @ngdoc method
 * @name $provide#decorator
 * @description
 *
 * Register a **decorator function** with the {@link auto.$injector $injector}. A decorator function
 * intercepts the creation of a service, allowing it to override or modify the behavior of the
 * service. The return value of the decorator function may be the original service, or a new service
 * that replaces (or wraps and delegates to) the original service.
 *
 * You can find out more about using decorators in the {@link guide/decorators} guide.
 *
 * @param {string} name The name of the service to decorate.
 * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be
 *    provided and should return the decorated service instance. The function is called using
 *    the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable.
 *    Local injection arguments:
 *
 *    * `$delegate` - The original service instance, which can be replaced, monkey patched, configured,
 *      decorated or delegated to.
 *
 * @example
 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting
 * calls to {@link ng.$log#error $log.warn()}.
 * ```js
 *   $provide.decorator('$log', ['$delegate', function($delegate) {
 *     $delegate.warn = $delegate.error;
 *     return $delegate;
 *   }]);
 * ```
 */


function createInjector(modulesToLoad, strictDi) {
  strictDi = (strictDi === true);
  var INSTANTIATING = {},
      providerSuffix = 'Provider',
      path = [],
      loadedModules = new NgMap(),
      providerCache = {
        $provide: {
            provider: supportObject(provider),
            factory: supportObject(factory),
            service: supportObject(service),
            value: supportObject(value),
            constant: supportObject(constant),
            decorator: decorator
          }
      },
      providerInjector = (providerCache.$injector =
          createInternalInjector(providerCache, function(serviceName, caller) {
            if (angular.isString(caller)) {
              path.push(caller);
            }
            throw $injectorMinErr('unpr', 'Unknown provider: {0}', path.join(' <- '));
          })),
      instanceCache = {},
      protoInstanceInjector =
          createInternalInjector(instanceCache, function(serviceName, caller) {
            var provider = providerInjector.get(serviceName + providerSuffix, caller);
            return instanceInjector.invoke(
                provider.$get, provider, undefined, serviceName);
          }),
      instanceInjector = protoInstanceInjector;

  providerCache['$injector' + providerSuffix] = { $get: valueFn(protoInstanceInjector) };
  instanceInjector.modules = providerInjector.modules = createMap();
  var runBlocks = loadModules(modulesToLoad);
  instanceInjector = protoInstanceInjector.get('$injector');
  instanceInjector.strictDi = strictDi;
  forEach(runBlocks, function(fn) { if (fn) instanceInjector.invoke(fn); });

  instanceInjector.loadNewModules = function(mods) {
    forEach(loadModules(mods), function(fn) { if (fn) instanceInjector.invoke(fn); });
  };


  return instanceInjector;

  ////////////////////////////////////
  // $provider
  ////////////////////////////////////

  function supportObject(delegate) {
    return function(key, value) {
      if (isObject(key)) {
        forEach(key, reverseParams(delegate));
      } else {
        return delegate(key, value);
      }
    };
  }

  function provider(name, provider_) {
    assertNotHasOwnProperty(name, 'service');
    if (isFunction(provider_) || isArray(provider_)) {
      provider_ = providerInjector.instantiate(provider_);
    }
    if (!provider_.$get) {
      throw $injectorMinErr('pget', 'Provider \'{0}\' must define $get factory method.', name);
    }
    return (providerCache[name + providerSuffix] = provider_);
  }

  function enforceReturnValue(name, factory) {
    return /** @this */ function enforcedReturnValue() {
      var result = instanceInjector.invoke(factory, this);
      if (isUndefined(result)) {
        throw $injectorMinErr('undef', 'Provider \'{0}\' must return a value from $get factory method.', name);
      }
      return result;
    };
  }

  function factory(name, factoryFn, enforce) {
    return provider(name, {
      $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
    });
  }

  function service(name, constructor) {
    return factory(name, ['$injector', function($injector) {
      return $injector.instantiate(constructor);
    }]);
  }

  function value(name, val) { return factory(name, valueFn(val), false); }

  function constant(name, value) {
    assertNotHasOwnProperty(name, 'constant');
    providerCache[name] = value;
    instanceCache[name] = value;
  }

  function decorator(serviceName, decorFn) {
    var origProvider = providerInjector.get(serviceName + providerSuffix),
        orig$get = origProvider.$get;

    origProvider.$get = function() {
      var origInstance = instanceInjector.invoke(orig$get, origProvider);
      return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
    };
  }

  ////////////////////////////////////
  // Module Loading
  ////////////////////////////////////
  function loadModules(modulesToLoad) {
    assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array');
    var runBlocks = [], moduleFn;
    forEach(modulesToLoad, function(module) {
      if (loadedModules.get(module)) return;
      loadedModules.set(module, true);

      function runInvokeQueue(queue) {
        var i, ii;
        for (i = 0, ii = queue.length; i < ii; i++) {
          var invokeArgs = queue[i],
              provider = providerInjector.get(invokeArgs[0]);

          provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
        }
      }

      try {
        if (isString(module)) {
          moduleFn = angularModule(module);
          instanceInjector.modules[module] = moduleFn;
          runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
          runInvokeQueue(moduleFn._invokeQueue);
          runInvokeQueue(moduleFn._configBlocks);
        } else if (isFunction(module)) {
            runBlocks.push(providerInjector.invoke(module));
        } else if (isArray(module)) {
            runBlocks.push(providerInjector.invoke(module));
        } else {
          assertArgFn(module, 'module');
        }
      } catch (e) {
        if (isArray(module)) {
          module = module[module.length - 1];
        }
        if (e.message && e.stack && e.stack.indexOf(e.message) === -1) {
          // Safari & FF's stack traces don't contain error.message content
          // unlike those of Chrome and IE
          // So if stack doesn't contain message, we create a new string that contains both.
          // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here.
          // eslint-disable-next-line no-ex-assign
          e = e.message + '\n' + e.stack;
        }
        throw $injectorMinErr('modulerr', 'Failed to instantiate module {0} due to:\n{1}',
                  module, e.stack || e.message || e);
      }
    });
    return runBlocks;
  }

  ////////////////////////////////////
  // internal Injector
  ////////////////////////////////////

  function createInternalInjector(cache, factory) {

    function getService(serviceName, caller) {
      if (cache.hasOwnProperty(serviceName)) {
        if (cache[serviceName] === INSTANTIATING) {
          throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
                    serviceName + ' <- ' + path.join(' <- '));
        }
        return cache[serviceName];
      } else {
        try {
          path.unshift(serviceName);
          cache[serviceName] = INSTANTIATING;
          cache[serviceName] = factory(serviceName, caller);
          return cache[serviceName];
        } catch (err) {
          if (cache[serviceName] === INSTANTIATING) {
            delete cache[serviceName];
          }
          throw err;
        } finally {
          path.shift();
        }
      }
    }


    function injectionArgs(fn, locals, serviceName) {
      var args = [],
          $inject = createInjector.$$annotate(fn, strictDi, serviceName);

      for (var i = 0, length = $inject.length; i < length; i++) {
        var key = $inject[i];
        if (typeof key !== 'string') {
          throw $injectorMinErr('itkn',
                  'Incorrect injection token! Expected service name as string, got {0}', key);
        }
        args.push(locals && locals.hasOwnProperty(key) ? locals[key] :
                                                         getService(key, serviceName));
      }
      return args;
    }

    function isClass(func) {
      // Support: IE 9-11 only
      // IE 9-11 do not support classes and IE9 leaks with the code below.
      if (msie || typeof func !== 'function') {
        return false;
      }
      var result = func.$$ngIsClass;
      if (!isBoolean(result)) {
        result = func.$$ngIsClass = /^class\b/.test(stringifyFn(func));
      }
      return result;
    }

    function invoke(fn, self, locals, serviceName) {
      if (typeof locals === 'string') {
        serviceName = locals;
        locals = null;
      }

      var args = injectionArgs(fn, locals, serviceName);
      if (isArray(fn)) {
        fn = fn[fn.length - 1];
      }

      if (!isClass(fn)) {
        // http://jsperf.com/angularjs-invoke-apply-vs-switch
        // #5388
        return fn.apply(self, args);
      } else {
        args.unshift(null);
        return new (Function.prototype.bind.apply(fn, args))();
      }
    }


    function instantiate(Type, locals, serviceName) {
      // Check if Type is annotated and use just the given function at n-1 as parameter
      // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
      var ctor = (isArray(Type) ? Type[Type.length - 1] : Type);
      var args = injectionArgs(Type, locals, serviceName);
      // Empty object at position 0 is ignored for invocation with `new`, but required.
      args.unshift(null);
      return new (Function.prototype.bind.apply(ctor, args))();
    }


    return {
      invoke: invoke,
      instantiate: instantiate,
      get: getService,
      annotate: createInjector.$$annotate,
      has: function(name) {
        return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
      }
    };
  }
}

createInjector.$$annotate = annotate;

/**
 * @ngdoc provider
 * @name $anchorScrollProvider
 * @this
 *
 * @description
 * Use `$anchorScrollProvider` to disable automatic scrolling whenever
 * {@link ng.$location#hash $location.hash()} changes.
 */
function $AnchorScrollProvider() {

  var autoScrollingEnabled = true;

  /**
   * @ngdoc method
   * @name $anchorScrollProvider#disableAutoScrolling
   *
   * @description
   * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to
   * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br />
   * Use this method to disable automatic scrolling.
   *
   * If automatic scrolling is disabled, one must explicitly call
   * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the
   * current hash.
   */
  this.disableAutoScrolling = function() {
    autoScrollingEnabled = false;
  };

  /**
   * @ngdoc service
   * @name $anchorScroll
   * @kind function
   * @requires $window
   * @requires $location
   * @requires $rootScope
   *
   * @description
   * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the
   * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified
   * in the
   * [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#an-indicated-part-of-the-document).
   *
   * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to
   * match any anchor whenever it changes. This can be disabled by calling
   * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}.
   *
   * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a
   * vertical scroll-offset (either fixed or dynamic).
   *
   * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of
   *                       {@link ng.$location#hash $location.hash()} will be used.
   *
   * @property {(number|function|jqLite)} yOffset
   * If set, specifies a vertical scroll-offset. This is often useful when there are fixed
   * positioned elements at the top of the page, such as navbars, headers etc.
   *
   * `yOffset` can be specified in various ways:
   * - **number**: A fixed number of pixels to be used as offset.<br /><br />
   * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return
   *   a number representing the offset (in pixels).<br /><br />
   * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from
   *   the top of the page to the element's bottom will be used as offset.<br />
   *   **Note**: The element will be taken into account only as long as its `position` is set to
   *   `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust
   *   their height and/or positioning according to the viewport's size.
   *
   * <br />
   * <div class="alert alert-warning">
   * In order for `yOffset` to work properly, scrolling should take place on the document's root and
   * not some child element.
   * </div>
   *
   * @example
     <example module="anchorScrollExample" name="anchor-scroll">
       <file name="index.html">
         <div id="scrollArea" ng-controller="ScrollController">
           <a ng-click="gotoBottom()">Go to bottom</a>
           <a id="bottom"></a> You're at the bottom!
         </div>
       </file>
       <file name="script.js">
         angular.module('anchorScrollExample', [])
           .controller('ScrollController', ['$scope', '$location', '$anchorScroll',
             function($scope, $location, $anchorScroll) {
               $scope.gotoBottom = function() {
                 // set the location.hash to the id of
                 // the element you wish to scroll to.
                 $location.hash('bottom');

                 // call $anchorScroll()
                 $anchorScroll();
               };
             }]);
       </file>
       <file name="style.css">
         #scrollArea {
           height: 280px;
           overflow: auto;
         }

         #bottom {
           display: block;
           margin-top: 2000px;
         }
       </file>
     </example>
   *
   * <hr />
   * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value).
   * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details.
   *
   * @example
     <example module="anchorScrollOffsetExample" name="anchor-scroll-offset">
       <file name="index.html">
         <div class="fixed-header" ng-controller="headerCtrl">
           <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]">
             Go to anchor {{x}}
           </a>
         </div>
         <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]">
           Anchor {{x}} of 5
         </div>
       </file>
       <file name="script.js">
         angular.module('anchorScrollOffsetExample', [])
           .run(['$anchorScroll', function($anchorScroll) {
             $anchorScroll.yOffset = 50;   // always scroll by 50 extra pixels
           }])
           .controller('headerCtrl', ['$anchorScroll', '$location', '$scope',
             function($anchorScroll, $location, $scope) {
               $scope.gotoAnchor = function(x) {
                 var newHash = 'anchor' + x;
                 if ($location.hash() !== newHash) {
                   // set the $location.hash to `newHash` and
                   // $anchorScroll will automatically scroll to it
                   $location.hash('anchor' + x);
                 } else {
                   // call $anchorScroll() explicitly,
                   // since $location.hash hasn't changed
                   $anchorScroll();
                 }
               };
             }
           ]);
       </file>
       <file name="style.css">
         body {
           padding-top: 50px;
         }

         .anchor {
           border: 2px dashed DarkOrchid;
           padding: 10px 10px 200px 10px;
         }

         .fixed-header {
           background-color: rgba(0, 0, 0, 0.2);
           height: 50px;
           position: fixed;
           top: 0; left: 0; right: 0;
         }

         .fixed-header > a {
           display: inline-block;
           margin: 5px 15px;
         }
       </file>
     </example>
   */
  this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) {
    var document = $window.document;

    // Helper function to get first anchor from a NodeList
    // (using `Array#some()` instead of `angular#forEach()` since it's more performant
    //  and working in all supported browsers.)
    function getFirstAnchor(list) {
      var result = null;
      Array.prototype.some.call(list, function(element) {
        if (nodeName_(element) === 'a') {
          result = element;
          return true;
        }
      });
      return result;
    }

    function getYOffset() {

      var offset = scroll.yOffset;

      if (isFunction(offset)) {
        offset = offset();
      } else if (isElement(offset)) {
        var elem = offset[0];
        var style = $window.getComputedStyle(elem);
        if (style.position !== 'fixed') {
          offset = 0;
        } else {
          offset = elem.getBoundingClientRect().bottom;
        }
      } else if (!isNumber(offset)) {
        offset = 0;
      }

      return offset;
    }

    function scrollTo(elem) {
      if (elem) {
        elem.scrollIntoView();

        var offset = getYOffset();

        if (offset) {
          // `offset` is the number of pixels we should scroll UP in order to align `elem` properly.
          // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the
          // top of the viewport.
          //
          // IF the number of pixels from the top of `elem` to the end of the page's content is less
          // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some
          // way down the page.
          //
          // This is often the case for elements near the bottom of the page.
          //
          // In such cases we do not need to scroll the whole `offset` up, just the difference between
          // the top of the element and the offset, which is enough to align the top of `elem` at the
          // desired position.
          var elemTop = elem.getBoundingClientRect().top;
          $window.scrollBy(0, elemTop - offset);
        }
      } else {
        $window.scrollTo(0, 0);
      }
    }

    function scroll(hash) {
      // Allow numeric hashes
      hash = isString(hash) ? hash : isNumber(hash) ? hash.toString() : $location.hash();
      var elm;

      // empty hash, scroll to the top of the page
      if (!hash) scrollTo(null);

      // element with given id
      else if ((elm = document.getElementById(hash))) scrollTo(elm);

      // first anchor with given name :-D
      else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm);

      // no element and hash === 'top', scroll to the top of the page
      else if (hash === 'top') scrollTo(null);
    }

    // does not scroll when user clicks on anchor link that is currently on
    // (no url change, no $location.hash() change), browser native does scroll
    if (autoScrollingEnabled) {
      $rootScope.$watch(function autoScrollWatch() {return $location.hash();},
        function autoScrollWatchAction(newVal, oldVal) {
          // skip the initial scroll if $location.hash is empty
          if (newVal === oldVal && newVal === '') return;

          jqLiteDocumentLoaded(function() {
            $rootScope.$evalAsync(scroll);
          });
        });
    }

    return scroll;
  }];
}

var $animateMinErr = minErr('$animate');
var ELEMENT_NODE = 1;
var NG_ANIMATE_CLASSNAME = 'ng-animate';

function mergeClasses(a,b) {
  if (!a && !b) return '';
  if (!a) return b;
  if (!b) return a;
  if (isArray(a)) a = a.join(' ');
  if (isArray(b)) b = b.join(' ');
  return a + ' ' + b;
}

function extractElementNode(element) {
  for (var i = 0; i < element.length; i++) {
    var elm = element[i];
    if (elm.nodeType === ELEMENT_NODE) {
      return elm;
    }
  }
}

function splitClasses(classes) {
  if (isString(classes)) {
    classes = classes.split(' ');
  }

  // Use createMap() to prevent class assumptions involving property names in
  // Object.prototype
  var obj = createMap();
  forEach(classes, function(klass) {
    // sometimes the split leaves empty string values
    // incase extra spaces were applied to the options
    if (klass.length) {
      obj[klass] = true;
    }
  });
  return obj;
}

// if any other type of options value besides an Object value is
// passed into the $animate.method() animation then this helper code
// will be run which will ignore it. While this patch is not the
// greatest solution to this, a lot of existing plugins depend on
// $animate to either call the callback (< 1.2) or return a promise
// that can be changed. This helper function ensures that the options
// are wiped clean incase a callback function is provided.
function prepareAnimateOptions(options) {
  return isObject(options)
      ? options
      : {};
}

var $$CoreAnimateJsProvider = /** @this */ function() {
  this.$get = noop;
};

// this is prefixed with Core since it conflicts with
// the animateQueueProvider defined in ngAnimate/animateQueue.js
var $$CoreAnimateQueueProvider = /** @this */ function() {
  var postDigestQueue = new NgMap();
  var postDigestElements = [];

  this.$get = ['$$AnimateRunner', '$rootScope',
       function($$AnimateRunner,   $rootScope) {
    return {
      enabled: noop,
      on: noop,
      off: noop,
      pin: noop,

      push: function(element, event, options, domOperation) {
        if (domOperation) {
          domOperation();
        }

        options = options || {};
        if (options.from) {
          element.css(options.from);
        }
        if (options.to) {
          element.css(options.to);
        }

        if (options.addClass || options.removeClass) {
          addRemoveClassesPostDigest(element, options.addClass, options.removeClass);
        }

        var runner = new $$AnimateRunner();

        // since there are no animations to run the runner needs to be
        // notified that the animation call is complete.
        runner.complete();
        return runner;
      }
    };


    function updateData(data, classes, value) {
      var changed = false;
      if (classes) {
        classes = isString(classes) ? classes.split(' ') :
                  isArray(classes) ? classes : [];
        forEach(classes, function(className) {
          if (className) {
            changed = true;
            data[className] = value;
          }
        });
      }
      return changed;
    }

    function handleCSSClassChanges() {
      forEach(postDigestElements, function(element) {
        var data = postDigestQueue.get(element);
        if (data) {
          var existing = splitClasses(element.attr('class'));
          var toAdd = '';
          var toRemove = '';
          forEach(data, function(status, className) {
            var hasClass = !!existing[className];
            if (status !== hasClass) {
              if (status) {
                toAdd += (toAdd.length ? ' ' : '') + className;
              } else {
                toRemove += (toRemove.length ? ' ' : '') + className;
              }
            }
          });

          forEach(element, function(elm) {
            if (toAdd) {
              jqLiteAddClass(elm, toAdd);
            }
            if (toRemove) {
              jqLiteRemoveClass(elm, toRemove);
            }
          });
          postDigestQueue.delete(element);
        }
      });
      postDigestElements.length = 0;
    }


    function addRemoveClassesPostDigest(element, add, remove) {
      var data = postDigestQueue.get(element) || {};

      var classesAdded = updateData(data, add, true);
      var classesRemoved = updateData(data, remove, false);

      if (classesAdded || classesRemoved) {

        postDigestQueue.set(element, data);
        postDigestElements.push(element);

        if (postDigestElements.length === 1) {
          $rootScope.$$postDigest(handleCSSClassChanges);
        }
      }
    }
  }];
};

/**
 * @ngdoc provider
 * @name $animateProvider
 *
 * @description
 * Default implementation of $animate that doesn't perform any animations, instead just
 * synchronously performs DOM updates and resolves the returned runner promise.
 *
 * In order to enable animations the `ngAnimate` module has to be loaded.
 *
 * To see the functional implementation check out `src/ngAnimate/animate.js`.
 */
var $AnimateProvider = ['$provide', /** @this */ function($provide) {
  var provider = this;
  var classNameFilter = null;
  var customFilter = null;

  this.$$registeredAnimations = Object.create(null);

   /**
   * @ngdoc method
   * @name $animateProvider#register
   *
   * @description
   * Registers a new injectable animation factory function. The factory function produces the
   * animation object which contains callback functions for each event that is expected to be
   * animated.
   *
   *   * `eventFn`: `function(element, ... , doneFunction, options)`
   *   The element to animate, the `doneFunction` and the options fed into the animation. Depending
   *   on the type of animation additional arguments will be injected into the animation function. The
   *   list below explains the function signatures for the different animation methods:
   *
   *   - setClass: function(element, addedClasses, removedClasses, doneFunction, options)
   *   - addClass: function(element, addedClasses, doneFunction, options)
   *   - removeClass: function(element, removedClasses, doneFunction, options)
   *   - enter, leave, move: function(element, doneFunction, options)
   *   - animate: function(element, fromStyles, toStyles, doneFunction, options)
   *
   *   Make sure to trigger the `doneFunction` once the animation is fully complete.
   *
   * ```js
   *   return {
   *     //enter, leave, move signature
   *     eventFn : function(element, done, options) {
   *       //code to run the animation
   *       //once complete, then run done()
   *       return function endFunction(wasCancelled) {
   *         //code to cancel the animation
   *       }
   *     }
   *   }
   * ```
   *
   * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to).
   * @param {Function} factory The factory function that will be executed to return the animation
   *                           object.
   */
  this.register = function(name, factory) {
    if (name && name.charAt(0) !== '.') {
      throw $animateMinErr('notcsel', 'Expecting class selector starting with \'.\' got \'{0}\'.', name);
    }

    var key = name + '-animation';
    provider.$$registeredAnimations[name.substr(1)] = key;
    $provide.factory(key, factory);
  };

  /**
   * @ngdoc method
   * @name $animateProvider#customFilter
   *
   * @description
   * Sets and/or returns the custom filter function that is used to "filter" animations, i.e.
   * determine if an animation is allowed or not. When no filter is specified (the default), no
   * animation will be blocked. Setting the `customFilter` value will only allow animations for
   * which the filter function's return value is truthy.
   *
   * This allows to easily create arbitrarily complex rules for filtering animations, such as
   * allowing specific events only, or enabling animations on specific subtrees of the DOM, etc.
   * Filtering animations can also boost performance for low-powered devices, as well as
   * applications containing a lot of structural operations.
   *
   * <div class="alert alert-success">
   *   **Best Practice:**
   *   Keep the filtering function as lean as possible, because it will be called for each DOM
   *   action (e.g. insertion, removal, class change) performed by "animation-aware" directives.
   *   See {@link guide/animations#which-directives-support-animations- here} for a list of built-in
   *   directives that support animations.
   *   Performing computationally expensive or time-consuming operations on each call of the
   *   filtering function can make your animations sluggish.
   * </div>
   *
   * **Note:** If present, `customFilter` will be checked before
   * {@link $animateProvider#classNameFilter classNameFilter}.
   *
   * @param {Function=} filterFn - The filter function which will be used to filter all animations.
   *   If a falsy value is returned, no animation will be performed. The function will be called
   *   with the following arguments:
   *   - **node** `{DOMElement}` - The DOM element to be animated.
   *   - **event** `{String}` - The name of the animation event (e.g. `enter`, `leave`, `addClass`
   *     etc).
   *   - **options** `{Object}` - A collection of options/styles used for the animation.
   * @return {Function} The current filter function or `null` if there is none set.
   */
  this.customFilter = function(filterFn) {
    if (arguments.length === 1) {
      customFilter = isFunction(filterFn) ? filterFn : null;
    }

    return customFilter;
  };

  /**
   * @ngdoc method
   * @name $animateProvider#classNameFilter
   *
   * @description
   * Sets and/or returns the CSS class regular expression that is checked when performing
   * an animation. Upon bootstrap the classNameFilter value is not set at all and will
   * therefore enable $animate to attempt to perform an animation on any element that is triggered.
   * When setting the `classNameFilter` value, animations will only be performed on elements
   * that successfully match the filter expression. This in turn can boost performance
   * for low-powered devices as well as applications containing a lot of structural operations.
   *
   * **Note:** If present, `classNameFilter` will be checked after
   * {@link $animateProvider#customFilter customFilter}. If `customFilter` is present and returns
   * false, `classNameFilter` will not be checked.
   *
   * @param {RegExp=} expression The className expression which will be checked against all animations
   * @return {RegExp} The current CSS className expression value. If null then there is no expression value
   */
  this.classNameFilter = function(expression) {
    if (arguments.length === 1) {
      classNameFilter = (expression instanceof RegExp) ? expression : null;
      if (classNameFilter) {
        var reservedRegex = new RegExp('[(\\s|\\/)]' + NG_ANIMATE_CLASSNAME + '[(\\s|\\/)]');
        if (reservedRegex.test(classNameFilter.toString())) {
          classNameFilter = null;
          throw $animateMinErr('nongcls', '$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME);
        }
      }
    }
    return classNameFilter;
  };

  this.$get = ['$$animateQueue', function($$animateQueue) {
    function domInsert(element, parentElement, afterElement) {
      // if for some reason the previous element was removed
      // from the dom sometime before this code runs then let's
      // just stick to using the parent element as the anchor
      if (afterElement) {
        var afterNode = extractElementNode(afterElement);
        if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) {
          afterElement = null;
        }
      }
      if (afterElement) {
        afterElement.after(element);
      } else {
        parentElement.prepend(element);
      }
    }

    /**
     * @ngdoc service
     * @name $animate
     * @description The $animate service exposes a series of DOM utility methods that provide support
     * for animation hooks. The default behavior is the application of DOM operations, however,
     * when an animation is detected (and animations are enabled), $animate will do the heavy lifting
     * to ensure that animation runs with the triggered DOM operation.
     *
     * By default $animate doesn't trigger any animations. This is because the `ngAnimate` module isn't
     * included and only when it is active then the animation hooks that `$animate` triggers will be
     * functional. Once active then all structural `ng-` directives will trigger animations as they perform
     * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`,
     * `ngShow`, `ngHide` and `ngMessages` also provide support for animations.
     *
     * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives.
     *
     * To learn more about enabling animation support, click here to visit the
     * {@link ngAnimate ngAnimate module page}.
     */
    return {
      // we don't call it directly since non-existant arguments may
      // be interpreted as null within the sub enabled function

      /**
       *
       * @ngdoc method
       * @name $animate#on
       * @kind function
       * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...)
       *    has fired on the given element or among any of its children. Once the listener is fired, the provided callback
       *    is fired with the following params:
       *
       * ```js
       * $animate.on('enter', container,
       *    function callback(element, phase) {
       *      // cool we detected an enter animation within the container
       *    }
       * );
       * ```
       *
       * <div class="alert alert-warning">
       * **Note**: Generally, the events that are fired correspond 1:1 to `$animate` method names,
       * e.g. {@link ng.$animate#addClass addClass()} will fire `addClass`, and {@link ng.ngClass}
       * will fire `addClass` if classes are added, and `removeClass` if classes are removed.
       * However, there are two exceptions:
       *
       * <ul>
       *   <li>if both an {@link ng.$animate#addClass addClass()} and a
       *   {@link ng.$animate#removeClass removeClass()} action are performed during the same
       *   animation, the event fired will be `setClass`. This is true even for `ngClass`.</li>
       *   <li>an {@link ng.$animate#animate animate()} call that adds and removes classes will fire
       *   the `setClass` event, but if it either removes or adds classes,
       *   it will fire `animate` instead.</li>
       * </ul>
       *
       * </div>
       *
       * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...)
       * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself
       *     as well as among its children
       * @param {Function} callback the callback function that will be fired when the listener is triggered.
       *
       * The arguments present in the callback function are:
       * * `element` - The captured DOM element that the animation was fired on.
       * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends).
       * * `data` - an object with these properties:
       *     * addClass - `{string|null}` - space-separated CSS classes to add to the element
       *     * removeClass - `{string|null}` - space-separated CSS classes to remove from the element
       *     * from - `{Object|null}` - CSS properties & values at the beginning of the animation
       *     * to - `{Object|null}` - CSS properties & values at the end of the animation
       *
       * Note that the callback does not trigger a scope digest. Wrap your call into a
       * {@link $rootScope.Scope#$apply scope.$apply} to propagate changes to the scope.
       */
      on: $$animateQueue.on,

      /**
       *
       * @ngdoc method
       * @name $animate#off
       * @kind function
       * @description Deregisters an event listener based on the event which has been associated with the provided element. This method
       * can be used in three different ways depending on the arguments:
       *
       * ```js
       * // remove all the animation event listeners listening for `enter`
       * $animate.off('enter');
       *
       * // remove listeners for all animation events from the container element
       * $animate.off(container);
       *
       * // remove all the animation event listeners listening for `enter` on the given element and its children
       * $animate.off('enter', container);
       *
       * // remove the event listener function provided by `callback` that is set
       * // to listen for `enter` on the given `container` as well as its children
       * $animate.off('enter', container, callback);
       * ```
       *
       * @param {string|DOMElement} event|container the animation event (e.g. enter, leave, move,
       * addClass, removeClass, etc...), or the container element. If it is the element, all other
       * arguments are ignored.
       * @param {DOMElement=} container the container element the event listener was placed on
       * @param {Function=} callback the callback function that was registered as the listener
       */
      off: $$animateQueue.off,

      /**
       * @ngdoc method
       * @name $animate#pin
       * @kind function
       * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists
       *    outside of the DOM structure of the AngularJS application. By doing so, any animation triggered via `$animate` can be issued on the
       *    element despite being outside the realm of the application or within another application. Say for example if the application
       *    was bootstrapped on an element that is somewhere inside of the `<body>` tag, but we wanted to allow for an element to be situated
       *    as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind
       *    that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association.
       *
       *    Note that this feature is only active when the `ngAnimate` module is used.
       *
       * @param {DOMElement} element the external element that will be pinned
       * @param {DOMElement} parentElement the host parent element that will be associated with the external element
       */
      pin: $$animateQueue.pin,

      /**
       *
       * @ngdoc method
       * @name $animate#enabled
       * @kind function
       * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This
       * function can be called in four ways:
       *
       * ```js
       * // returns true or false
       * $animate.enabled();
       *
       * // changes the enabled state for all animations
       * $animate.enabled(false);
       * $animate.enabled(true);
       *
       * // returns true or false if animations are enabled for an element
       * $animate.enabled(element);
       *
       * // changes the enabled state for an element and its children
       * $animate.enabled(element, true);
       * $animate.enabled(element, false);
       * ```
       *
       * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state
       * @param {boolean=} enabled whether or not the animations will be enabled for the element
       *
       * @return {boolean} whether or not animations are enabled
       */
      enabled: $$animateQueue.enabled,

      /**
       * @ngdoc method
       * @name $animate#cancel
       * @kind function
       * @description Cancels the provided animation and applies the end state of the animation.
       * Note that this does not cancel the underlying operation, e.g. the setting of classes or
       * adding the element to the DOM.
       *
       * @param {animationRunner} animationRunner An animation runner returned by an $animate function.
       *
       * @example
        <example module="animationExample" deps="angular-animate.js" animations="true" name="animate-cancel">
          <file name="app.js">
            angular.module('animationExample', ['ngAnimate']).component('cancelExample', {
              templateUrl: 'template.html',
              controller: function($element, $animate) {
                this.runner = null;

                this.addClass = function() {
                  this.runner = $animate.addClass($element.find('div'), 'red');
                  var ctrl = this;
                  this.runner.finally(function() {
                    ctrl.runner = null;
                  });
                };

                this.removeClass = function() {
                  this.runner = $animate.removeClass($element.find('div'), 'red');
                  var ctrl = this;
                  this.runner.finally(function() {
                    ctrl.runner = null;
                  });
                };

                this.cancel = function() {
                  $animate.cancel(this.runner);
                };
              }
            });
          </file>
          <file name="template.html">
            <p>
              <button id="add" ng-click="$ctrl.addClass()">Add</button>
              <button ng-click="$ctrl.removeClass()">Remove</button>
              <br>
              <button id="cancel" ng-click="$ctrl.cancel()" ng-disabled="!$ctrl.runner">Cancel</button>
              <br>
              <div id="target">CSS-Animated Text</div>
            </p>
          </file>
          <file name="index.html">
            <cancel-example></cancel-example>
          </file>
          <file name="style.css">
            .red-add, .red-remove {
              transition: all 4s cubic-bezier(0.250, 0.460, 0.450, 0.940);
            }

            .red,
            .red-add.red-add-active {
              color: #FF0000;
              font-size: 40px;
            }

            .red-remove.red-remove-active {
              font-size: 10px;
              color: black;
            }

          </file>
        </example>
       */
      cancel: function(runner) {
        if (runner.cancel) {
          runner.cancel();
        }
      },

      /**
       *
       * @ngdoc method
       * @name $animate#enter
       * @kind function
       * @description Inserts the element into the DOM either after the `after` element (if provided) or
       *   as the first child within the `parent` element and then triggers an animation.
       *   A promise is returned that will be resolved during the next digest once the animation
       *   has completed.
       *
       * @param {DOMElement} element the element which will be inserted into the DOM
       * @param {DOMElement} parent the parent element which will append the element as
       *   a child (so long as the after element is not present)
       * @param {DOMElement=} after the sibling element after which the element will be appended
       * @param {object=} options an optional collection of options/styles that will be applied to the element.
       *   The object can have the following properties:
       *
       *   - **addClass** - `{string}` - space-separated CSS classes to add to element
       *   - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
       *   - **removeClass** - `{string}` - space-separated CSS classes to remove from element
       *   - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
       *
       * @return {Runner} the animation runner
       */
      enter: function(element, parent, after, options) {
        parent = parent && jqLite(parent);
        after = after && jqLite(after);
        parent = parent || after.parent();
        domInsert(element, parent, after);
        return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options));
      },

      /**
       *
       * @ngdoc method
       * @name $animate#move
       * @kind function
       * @description Inserts (moves) the element into its new position in the DOM either after
       *   the `after` element (if provided) or as the first child within the `parent` element
       *   and then triggers an animation. A promise is returned that will be resolved
       *   during the next digest once the animation has completed.
       *
       * @param {DOMElement} element the element which will be moved into the new DOM position
       * @param {DOMElement} parent the parent element which will append the element as
       *   a child (so long as the after element is not present)
       * @param {DOMElement=} after the sibling element after which the element will be appended
       * @param {object=} options an optional collection of options/styles that will be applied to the element.
       *   The object can have the following properties:
       *
       *   - **addClass** - `{string}` - space-separated CSS classes to add to element
       *   - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
       *   - **removeClass** - `{string}` - space-separated CSS classes to remove from element
       *   - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
       *
       * @return {Runner} the animation runner
       */
      move: function(element, parent, after, options) {
        parent = parent && jqLite(parent);
        after = after && jqLite(after);
        parent = parent || after.parent();
        domInsert(element, parent, after);
        return $$animateQueue.push(element, 'move', prepareAnimateOptions(options));
      },

      /**
       * @ngdoc method
       * @name $animate#leave
       * @kind function
       * @description Triggers an animation and then removes the element from the DOM.
       * When the function is called a promise is returned that will be resolved during the next
       * digest once the animation has completed.
       *
       * @param {DOMElement} element the element which will be removed from the DOM
       * @param {object=} options an optional collection of options/styles that will be applied to the element.
       *   The object can have the following properties:
       *
       *   - **addClass** - `{string}` - space-separated CSS classes to add to element
       *   - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
       *   - **removeClass** - `{string}` - space-separated CSS classes to remove from element
       *   - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
       *
       * @return {Runner} the animation runner
       */
      leave: function(element, options) {
        return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() {
          element.remove();
        });
      },

      /**
       * @ngdoc method
       * @name $animate#addClass
       * @kind function
       *
       * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon
       *   execution, the addClass operation will only be handled after the next digest and it will not trigger an
       *   animation if element already contains the CSS class or if the class is removed at a later step.
       *   Note that class-based animations are treated differently compared to structural animations
       *   (like enter, move and leave) since the CSS classes may be added/removed at different points
       *   depending if CSS or JavaScript animations are used.
       *
       * @param {DOMElement} element the element which the CSS classes will be applied to
       * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces)
       * @param {object=} options an optional collection of options/styles that will be applied to the element.
       *   The object can have the following properties:
       *
       *   - **removeClass** - `{string}` - space-separated CSS classes to remove from element
       *   - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
       *   - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
       *
       * @return {Runner} animationRunner the animation runner
       */
      addClass: function(element, className, options) {
        options = prepareAnimateOptions(options);
        options.addClass = mergeClasses(options.addclass, className);
        return $$animateQueue.push(element, 'addClass', options);
      },

      /**
       * @ngdoc method
       * @name $animate#removeClass
       * @kind function
       *
       * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon
       *   execution, the removeClass operation will only be handled after the next digest and it will not trigger an
       *   animation if element does not contain the CSS class or if the class is added at a later step.
       *   Note that class-based animations are treated differently compared to structural animations
       *   (like enter, move and leave) since the CSS classes may be added/removed at different points
       *   depending if CSS or JavaScript animations are used.
       *
       * @param {DOMElement} element the element which the CSS classes will be applied to
       * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces)
       * @param {object=} options an optional collection of options/styles that will be applied to the element.
       *   The object can have the following properties:
       *
       *   - **addClass** - `{string}` - space-separated CSS classes to add to element
       *   - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
       *   - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
       *
       * @return {Runner} the animation runner
       */
      removeClass: function(element, className, options) {
        options = prepareAnimateOptions(options);
        options.removeClass = mergeClasses(options.removeClass, className);
        return $$animateQueue.push(element, 'removeClass', options);
      },

      /**
       * @ngdoc method
       * @name $animate#setClass
       * @kind function
       *
       * @description Performs both the addition and removal of a CSS classes on an element and (during the process)
       *    triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and
       *    `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has
       *    passed. Note that class-based animations are treated differently compared to structural animations
       *    (like enter, move and leave) since the CSS classes may be added/removed at different points
       *    depending if CSS or JavaScript animations are used.
       *
       * @param {DOMElement} element the element which the CSS classes will be applied to
       * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces)
       * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces)
       * @param {object=} options an optional collection of options/styles that will be applied to the element.
       *   The object can have the following properties:
       *
       *   - **addClass** - `{string}` - space-separated CSS classes to add to element
       *   - **removeClass** - `{string}` - space-separated CSS classes to remove from element
       *   - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
       *   - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
       *
       * @return {Runner} the animation runner
       */
      setClass: function(element, add, remove, options) {
        options = prepareAnimateOptions(options);
        options.addClass = mergeClasses(options.addClass, add);
        options.removeClass = mergeClasses(options.removeClass, remove);
        return $$animateQueue.push(element, 'setClass', options);
      },

      /**
       * @ngdoc method
       * @name $animate#animate
       * @kind function
       *
       * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element.
       * If any detected CSS transition, keyframe or JavaScript matches the provided className value, then the animation will take
       * on the provided styles. For example, if a transition animation is set for the given className, then the provided `from` and
       * `to` styles will be applied alongside the given transition. If the CSS style provided in `from` does not have a corresponding
       * style in `to`, the style in `from` is applied immediately, and no animation is run.
       * If a JavaScript animation is detected then the provided styles will be given in as function parameters into the `animate`
       * method (or as part of the `options` parameter):
       *
       * ```js
       * ngModule.animation('.my-inline-animation', function() {
       *   return {
       *     animate : function(element, from, to, done, options) {
       *       //animation
       *       done();
       *     }
       *   }
       * });
       * ```
       *
       * @param {DOMElement} element the element which the CSS styles will be applied to
       * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation.
       * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation.
       * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If
       *    this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element.
       *    (Note that if no animation is detected then this value will not be applied to the element.)
       * @param {object=} options an optional collection of options/styles that will be applied to the element.
       *   The object can have the following properties:
       *
       *   - **addClass** - `{string}` - space-separated CSS classes to add to element
       *   - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
       *   - **removeClass** - `{string}` - space-separated CSS classes to remove from element
       *   - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
       *
       * @return {Runner} the animation runner
       */
      animate: function(element, from, to, className, options) {
        options = prepareAnimateOptions(options);
        options.from = options.from ? extend(options.from, from) : from;
        options.to   = options.to   ? extend(options.to, to)     : to;

        className = className || 'ng-inline-animate';
        options.tempClasses = mergeClasses(options.tempClasses, className);
        return $$animateQueue.push(element, 'animate', options);
      }
    };
  }];
}];

var $$AnimateAsyncRunFactoryProvider = /** @this */ function() {
  this.$get = ['$$rAF', function($$rAF) {
    var waitQueue = [];

    function waitForTick(fn) {
      waitQueue.push(fn);
      if (waitQueue.length > 1) return;
      $$rAF(function() {
        for (var i = 0; i < waitQueue.length; i++) {
          waitQueue[i]();
        }
        waitQueue = [];
      });
    }

    return function() {
      var passed = false;
      waitForTick(function() {
        passed = true;
      });
      return function(callback) {
        if (passed) {
          callback();
        } else {
          waitForTick(callback);
        }
      };
    };
  }];
};

var $$AnimateRunnerFactoryProvider = /** @this */ function() {
  this.$get = ['$q', '$sniffer', '$$animateAsyncRun', '$$isDocumentHidden', '$timeout',
       function($q,   $sniffer,   $$animateAsyncRun,   $$isDocumentHidden,   $timeout) {

    var INITIAL_STATE = 0;
    var DONE_PENDING_STATE = 1;
    var DONE_COMPLETE_STATE = 2;

    AnimateRunner.chain = function(chain, callback) {
      var index = 0;

      next();
      function next() {
        if (index === chain.length) {
          callback(true);
          return;
        }

        chain[index](function(response) {
          if (response === false) {
            callback(false);
            return;
          }
          index++;
          next();
        });
      }
    };

    AnimateRunner.all = function(runners, callback) {
      var count = 0;
      var status = true;
      forEach(runners, function(runner) {
        runner.done(onProgress);
      });

      function onProgress(response) {
        status = status && response;
        if (++count === runners.length) {
          callback(status);
        }
      }
    };

    function AnimateRunner(host) {
      this.setHost(host);

      var rafTick = $$animateAsyncRun();
      var timeoutTick = function(fn) {
        $timeout(fn, 0, false);
      };

      this._doneCallbacks = [];
      this._tick = function(fn) {
        if ($$isDocumentHidden()) {
          timeoutTick(fn);
        } else {
          rafTick(fn);
        }
      };
      this._state = 0;
    }

    AnimateRunner.prototype = {
      setHost: function(host) {
        this.host = host || {};
      },

      done: function(fn) {
        if (this._state === DONE_COMPLETE_STATE) {
          fn();
        } else {
          this._doneCallbacks.push(fn);
        }
      },

      progress: noop,

      getPromise: function() {
        if (!this.promise) {
          var self = this;
          this.promise = $q(function(resolve, reject) {
            self.done(function(status) {
              if (status === false) {
                reject();
              } else {
                resolve();
              }
            });
          });
        }
        return this.promise;
      },

      then: function(resolveHandler, rejectHandler) {
        return this.getPromise().then(resolveHandler, rejectHandler);
      },

      'catch': function(handler) {
        return this.getPromise()['catch'](handler);
      },

      'finally': function(handler) {
        return this.getPromise()['finally'](handler);
      },

      pause: function() {
        if (this.host.pause) {
          this.host.pause();
        }
      },

      resume: function() {
        if (this.host.resume) {
          this.host.resume();
        }
      },

      end: function() {
        if (this.host.end) {
          this.host.end();
        }
        this._resolve(true);
      },

      cancel: function() {
        if (this.host.cancel) {
          this.host.cancel();
        }
        this._resolve(false);
      },

      complete: function(response) {
        var self = this;
        if (self._state === INITIAL_STATE) {
          self._state = DONE_PENDING_STATE;
          self._tick(function() {
            self._resolve(response);
          });
        }
      },

      _resolve: function(response) {
        if (this._state !== DONE_COMPLETE_STATE) {
          forEach(this._doneCallbacks, function(fn) {
            fn(response);
          });
          this._doneCallbacks.length = 0;
          this._state = DONE_COMPLETE_STATE;
        }
      }
    };

    return AnimateRunner;
  }];
};

/* exported $CoreAnimateCssProvider */

/**
 * @ngdoc service
 * @name $animateCss
 * @kind object
 * @this
 *
 * @description
 * This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included,
 * then the `$animateCss` service will actually perform animations.
 *
 * Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}.
 */
var $CoreAnimateCssProvider = function() {
  this.$get = ['$$rAF', '$q', '$$AnimateRunner', function($$rAF, $q, $$AnimateRunner) {

    return function(element, initialOptions) {
      // all of the animation functions should create
      // a copy of the options data, however, if a
      // parent service has already created a copy then
      // we should stick to using that
      var options = initialOptions || {};
      if (!options.$$prepared) {
        options = copy(options);
      }

      // there is no point in applying the styles since
      // there is no animation that goes on at all in
      // this version of $animateCss.
      if (options.cleanupStyles) {
        options.from = options.to = null;
      }

      if (options.from) {
        element.css(options.from);
        options.from = null;
      }

      var closed, runner = new $$AnimateRunner();
      return {
        start: run,
        end: run
      };

      function run() {
        $$rAF(function() {
          applyAnimationContents();
          if (!closed) {
            runner.complete();
          }
          closed = true;
        });
        return runner;
      }

      function applyAnimationContents() {
        if (options.addClass) {
          element.addClass(options.addClass);
          options.addClass = null;
        }
        if (options.removeClass) {
          element.removeClass(options.removeClass);
          options.removeClass = null;
        }
        if (options.to) {
          element.css(options.to);
          options.to = null;
        }
      }
    };
  }];
};

/* global getHash: true, stripHash: false */

function getHash(url) {
  var index = url.indexOf('#');
  return index === -1 ? '' : url.substr(index);
}

function trimEmptyHash(url) {
  return url.replace(/#$/, '');
}

/**
 * ! This is a private undocumented service !
 *
 * @name $browser
 * @requires $log
 * @description
 * This object has two goals:
 *
 * - hide all the global state in the browser caused by the window object
 * - abstract away all the browser specific features and inconsistencies
 *
 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser`
 * service, which can be used for convenient testing of the application without the interaction with
 * the real browser apis.
 */
/**
 * @param {object} window The global window object.
 * @param {object} document jQuery wrapped document.
 * @param {object} $log window.console or an object with the same interface.
 * @param {object} $sniffer $sniffer service
 */
function Browser(window, document, $log, $sniffer, $$taskTrackerFactory) {
  var self = this,
      location = window.location,
      history = window.history,
      setTimeout = window.setTimeout,
      clearTimeout = window.clearTimeout,
      pendingDeferIds = {},
      taskTracker = $$taskTrackerFactory($log);

  self.isMock = false;

  //////////////////////////////////////////////////////////////
  // Task-tracking API
  //////////////////////////////////////////////////////////////

  // TODO(vojta): remove this temporary api
  self.$$completeOutstandingRequest = taskTracker.completeTask;
  self.$$incOutstandingRequestCount = taskTracker.incTaskCount;

  // TODO(vojta): prefix this method with $$ ?
  self.notifyWhenNoOutstandingRequests = taskTracker.notifyWhenNoPendingTasks;

  //////////////////////////////////////////////////////////////
  // URL API
  //////////////////////////////////////////////////////////////

  var cachedState, lastHistoryState,
      lastBrowserUrl = location.href,
      baseElement = document.find('base'),
      pendingLocation = null,
      getCurrentState = !$sniffer.history ? noop : function getCurrentState() {
        try {
          return history.state;
        } catch (e) {
          // MSIE can reportedly throw when there is no state (UNCONFIRMED).
        }
      };

  cacheState();

  /**
   * @name $browser#url
   *
   * @description
   * GETTER:
   * Without any argument, this method just returns current value of `location.href` (with a
   * trailing `#` stripped of if the hash is empty).
   *
   * SETTER:
   * With at least one argument, this method sets url to new value.
   * If html5 history api supported, `pushState`/`replaceState` is used, otherwise
   * `location.href`/`location.replace` is used.
   * Returns its own instance to allow chaining.
   *
   * NOTE: this api is intended for use only by the `$location` service. Please use the
   * {@link ng.$location $location service} to change url.
   *
   * @param {string} url New url (when used as setter)
   * @param {boolean=} replace Should new url replace current history record?
   * @param {object=} state State object to use with `pushState`/`replaceState`
   */
  self.url = function(url, replace, state) {
    // In modern browsers `history.state` is `null` by default; treating it separately
    // from `undefined` would cause `$browser.url('/foo')` to change `history.state`
    // to undefined via `pushState`. Instead, let's change `undefined` to `null` here.
    if (isUndefined(state)) {
      state = null;
    }

    // Android Browser BFCache causes location, history reference to become stale.
    if (location !== window.location) location = window.location;
    if (history !== window.history) history = window.history;

    // setter
    if (url) {
      var sameState = lastHistoryState === state;

      // Normalize the inputted URL
      url = urlResolve(url).href;

      // Don't change anything if previous and current URLs and states match. This also prevents
      // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
      // See https://github.com/angular/angular.js/commit/ffb2701
      if (lastBrowserUrl === url && (!$sniffer.history || sameState)) {
        return self;
      }
      var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
      lastBrowserUrl = url;
      lastHistoryState = state;
      // Don't use history API if only the hash changed
      // due to a bug in IE10/IE11 which leads
      // to not firing a `hashchange` nor `popstate` event
      // in some cases (see #9143).
      if ($sniffer.history && (!sameBase || !sameState)) {
        history[replace ? 'replaceState' : 'pushState'](state, '', url);
        cacheState();
      } else {
        if (!sameBase) {
          pendingLocation = url;
        }
        if (replace) {
          location.replace(url);
        } else if (!sameBase) {
          location.href = url;
        } else {
          location.hash = getHash(url);
        }
        if (location.href !== url) {
          pendingLocation = url;
        }
      }
      if (pendingLocation) {
        pendingLocation = url;
      }
      return self;
    // getter
    } else {
      // - pendingLocation is needed as browsers don't allow to read out
      //   the new location.href if a reload happened or if there is a bug like in iOS 9 (see
      //   https://openradar.appspot.com/22186109).
      return trimEmptyHash(pendingLocation || location.href);
    }
  };

  /**
   * @name $browser#state
   *
   * @description
   * This method is a getter.
   *
   * Return history.state or null if history.state is undefined.
   *
   * @returns {object} state
   */
  self.state = function() {
    return cachedState;
  };

  var urlChangeListeners = [],
      urlChangeInit = false;

  function cacheStateAndFireUrlChange() {
    pendingLocation = null;
    fireStateOrUrlChange();
  }

  // This variable should be used *only* inside the cacheState function.
  var lastCachedState = null;
  function cacheState() {
    // This should be the only place in $browser where `history.state` is read.
    cachedState = getCurrentState();
    cachedState = isUndefined(cachedState) ? null : cachedState;

    // Prevent callbacks fo fire twice if both hashchange & popstate were fired.
    if (equals(cachedState, lastCachedState)) {
      cachedState = lastCachedState;
    }

    lastCachedState = cachedState;
    lastHistoryState = cachedState;
  }

  function fireStateOrUrlChange() {
    var prevLastHistoryState = lastHistoryState;
    cacheState();

    if (lastBrowserUrl === self.url() && prevLastHistoryState === cachedState) {
      return;
    }

    lastBrowserUrl = self.url();
    lastHistoryState = cachedState;
    forEach(urlChangeListeners, function(listener) {
      listener(self.url(), cachedState);
    });
  }

  /**
   * @name $browser#onUrlChange
   *
   * @description
   * Register callback function that will be called, when url changes.
   *
   * It's only called when the url is changed from outside of AngularJS:
   * - user types different url into address bar
   * - user clicks on history (forward/back) button
   * - user clicks on a link
   *
   * It's not called when url is changed by $browser.url() method
   *
   * The listener gets called with new url as parameter.
   *
   * NOTE: this api is intended for use only by the $location service. Please use the
   * {@link ng.$location $location service} to monitor url changes in AngularJS apps.
   *
   * @param {function(string)} listener Listener function to be called when url changes.
   * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous.
   */
  self.onUrlChange = function(callback) {
    // TODO(vojta): refactor to use node's syntax for events
    if (!urlChangeInit) {
      // We listen on both (hashchange/popstate) when available, as some browsers don't
      // fire popstate when user changes the address bar and don't fire hashchange when url
      // changed by push/replaceState

      // html5 history api - popstate event
      if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange);
      // hashchange event
      jqLite(window).on('hashchange', cacheStateAndFireUrlChange);

      urlChangeInit = true;
    }

    urlChangeListeners.push(callback);
    return callback;
  };

  /**
   * @private
   * Remove popstate and hashchange handler from window.
   *
   * NOTE: this api is intended for use only by $rootScope.
   */
  self.$$applicationDestroyed = function() {
    jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange);
  };

  /**
   * Checks whether the url has changed outside of AngularJS.
   * Needs to be exported to be able to check for changes that have been done in sync,
   * as hashchange/popstate events fire in async.
   */
  self.$$checkUrlChange = fireStateOrUrlChange;

  //////////////////////////////////////////////////////////////
  // Misc API
  //////////////////////////////////////////////////////////////

  /**
   * @name $browser#baseHref
   *
   * @description
   * Returns current <base href>
   * (always relative - without domain)
   *
   * @returns {string} The current base href
   */
  self.baseHref = function() {
    var href = baseElement.attr('href');
    return href ? href.replace(/^(https?:)?\/\/[^/]*/, '') : '';
  };

  /**
   * @name $browser#defer
   * @param {function()} fn A function, who's execution should be deferred.
   * @param {number=} [delay=0] Number of milliseconds to defer the function execution.
   * @param {string=} [taskType=DEFAULT_TASK_TYPE] The type of task that is deferred.
   * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`.
   *
   * @description
   * Executes a fn asynchronously via `setTimeout(fn, delay)`.
   *
   * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using
   * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed
   * via `$browser.defer.flush()`.
   *
   */
  self.defer = function(fn, delay, taskType) {
    var timeoutId;

    delay = delay || 0;
    taskType = taskType || taskTracker.DEFAULT_TASK_TYPE;

    taskTracker.incTaskCount(taskType);
    timeoutId = setTimeout(function() {
      delete pendingDeferIds[timeoutId];
      taskTracker.completeTask(fn, taskType);
    }, delay);
    pendingDeferIds[timeoutId] = taskType;

    return timeoutId;
  };


  /**
   * @name $browser#defer.cancel
   *
   * @description
   * Cancels a deferred task identified with `deferId`.
   *
   * @param {*} deferId Token returned by the `$browser.defer` function.
   * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
   *                    canceled.
   */
  self.defer.cancel = function(deferId) {
    if (pendingDeferIds.hasOwnProperty(deferId)) {
      var taskType = pendingDeferIds[deferId];
      delete pendingDeferIds[deferId];
      clearTimeout(deferId);
      taskTracker.completeTask(noop, taskType);
      return true;
    }
    return false;
  };

}

/** @this */
function $BrowserProvider() {
  this.$get = ['$window', '$log', '$sniffer', '$document', '$$taskTrackerFactory',
       function($window,   $log,   $sniffer,   $document,   $$taskTrackerFactory) {
    return new Browser($window, $document, $log, $sniffer, $$taskTrackerFactory);
  }];
}

/**
 * @ngdoc service
 * @name $cacheFactory
 * @this
 *
 * @description
 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to
 * them.
 *
 * ```js
 *
 *  var cache = $cacheFactory('cacheId');
 *  expect($cacheFactory.get('cacheId')).toBe(cache);
 *  expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined();
 *
 *  cache.put("key", "value");
 *  cache.put("another key", "another value");
 *
 *  // We've specified no options on creation
 *  expect(cache.info()).toEqual({id: 'cacheId', size: 2});
 *
 * ```
 *
 *
 * @param {string} cacheId Name or id of the newly created cache.
 * @param {object=} options Options object that specifies the cache behavior. Properties:
 *
 *   - `{number=}` `capacity` — turns the cache into LRU cache.
 *
 * @returns {object} Newly created cache object with the following set of methods:
 *
 * - `{object}` `info()` — Returns id, size, and options of cache.
 * - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns
 *   it.
 * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss.
 * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache.
 * - `{void}` `removeAll()` — Removes all cached values.
 * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory.
 *
 * @example
   <example module="cacheExampleApp" name="cache-factory">
     <file name="index.html">
       <div ng-controller="CacheController">
         <input ng-model="newCacheKey" placeholder="Key">
         <input ng-model="newCacheValue" placeholder="Value">
         <button ng-click="put(newCacheKey, newCacheValue)">Cache</button>

         <p ng-if="keys.length">Cached Values</p>
         <div ng-repeat="key in keys">
           <span ng-bind="key"></span>
           <span>: </span>
           <b ng-bind="cache.get(key)"></b>
         </div>

         <p>Cache Info</p>
         <div ng-repeat="(key, value) in cache.info()">
           <span ng-bind="key"></span>
           <span>: </span>
           <b ng-bind="value"></b>
         </div>
       </div>
     </file>
     <file name="script.js">
       angular.module('cacheExampleApp', []).
         controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) {
           $scope.keys = [];
           $scope.cache = $cacheFactory('cacheId');
           $scope.put = function(key, value) {
             if (angular.isUndefined($scope.cache.get(key))) {
               $scope.keys.push(key);
             }
             $scope.cache.put(key, angular.isUndefined(value) ? null : value);
           };
         }]);
     </file>
     <file name="style.css">
       p {
         margin: 10px 0 3px;
       }
     </file>
   </example>
 */
function $CacheFactoryProvider() {

  this.$get = function() {
    var caches = {};

    function cacheFactory(cacheId, options) {
      if (cacheId in caches) {
        throw minErr('$cacheFactory')('iid', 'CacheId \'{0}\' is already taken!', cacheId);
      }

      var size = 0,
          stats = extend({}, options, {id: cacheId}),
          data = createMap(),
          capacity = (options && options.capacity) || Number.MAX_VALUE,
          lruHash = createMap(),
          freshEnd = null,
          staleEnd = null;

      /**
       * @ngdoc type
       * @name $cacheFactory.Cache
       *
       * @description
       * A cache object used to store and retrieve data, primarily used by
       * {@link $templateRequest $templateRequest} and the {@link ng.directive:script script}
       * directive to cache templates and other data.
       *
       * ```js
       *  angular.module('superCache')
       *    .factory('superCache', ['$cacheFactory', function($cacheFactory) {
       *      return $cacheFactory('super-cache');
       *    }]);
       * ```
       *
       * Example test:
       *
       * ```js
       *  it('should behave like a cache', inject(function(superCache) {
       *    superCache.put('key', 'value');
       *    superCache.put('another key', 'another value');
       *
       *    expect(superCache.info()).toEqual({
       *      id: 'super-cache',
       *      size: 2
       *    });
       *
       *    superCache.remove('another key');
       *    expect(superCache.get('another key')).toBeUndefined();
       *
       *    superCache.removeAll();
       *    expect(superCache.info()).toEqual({
       *      id: 'super-cache',
       *      size: 0
       *    });
       *  }));
       * ```
       */
      return (caches[cacheId] = {

        /**
         * @ngdoc method
         * @name $cacheFactory.Cache#put
         * @kind function
         *
         * @description
         * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be
         * retrieved later, and incrementing the size of the cache if the key was not already
         * present in the cache. If behaving like an LRU cache, it will also remove stale
         * entries from the set.
         *
         * It will not insert undefined values into the cache.
         *
         * @param {string} key the key under which the cached data is stored.
         * @param {*} value the value to store alongside the key. If it is undefined, the key
         *    will not be stored.
         * @returns {*} the value stored.
         */
        put: function(key, value) {
          if (isUndefined(value)) return;
          if (capacity < Number.MAX_VALUE) {
            var lruEntry = lruHash[key] || (lruHash[key] = {key: key});

            refresh(lruEntry);
          }

          if (!(key in data)) size++;
          data[key] = value;

          if (size > capacity) {
            this.remove(staleEnd.key);
          }

          return value;
        },

        /**
         * @ngdoc method
         * @name $cacheFactory.Cache#get
         * @kind function
         *
         * @description
         * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object.
         *
         * @param {string} key the key of the data to be retrieved
         * @returns {*} the value stored.
         */
        get: function(key) {
          if (capacity < Number.MAX_VALUE) {
            var lruEntry = lruHash[key];

            if (!lruEntry) return;

            refresh(lruEntry);
          }

          return data[key];
        },


        /**
         * @ngdoc method
         * @name $cacheFactory.Cache#remove
         * @kind function
         *
         * @description
         * Removes an entry from the {@link $cacheFactory.Cache Cache} object.
         *
         * @param {string} key the key of the entry to be removed
         */
        remove: function(key) {
          if (capacity < Number.MAX_VALUE) {
            var lruEntry = lruHash[key];

            if (!lruEntry) return;

            if (lruEntry === freshEnd) freshEnd = lruEntry.p;
            if (lruEntry === staleEnd) staleEnd = lruEntry.n;
            link(lruEntry.n,lruEntry.p);

            delete lruHash[key];
          }

          if (!(key in data)) return;

          delete data[key];
          size--;
        },


        /**
         * @ngdoc method
         * @name $cacheFactory.Cache#removeAll
         * @kind function
         *
         * @description
         * Clears the cache object of any entries.
         */
        removeAll: function() {
          data = createMap();
          size = 0;
          lruHash = createMap();
          freshEnd = staleEnd = null;
        },


        /**
         * @ngdoc method
         * @name $cacheFactory.Cache#destroy
         * @kind function
         *
         * @description
         * Destroys the {@link $cacheFactory.Cache Cache} object entirely,
         * removing it from the {@link $cacheFactory $cacheFactory} set.
         */
        destroy: function() {
          data = null;
          stats = null;
          lruHash = null;
          delete caches[cacheId];
        },


        /**
         * @ngdoc method
         * @name $cacheFactory.Cache#info
         * @kind function
         *
         * @description
         * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}.
         *
         * @returns {object} an object with the following properties:
         *   <ul>
         *     <li>**id**: the id of the cache instance</li>
         *     <li>**size**: the number of entries kept in the cache instance</li>
         *     <li>**...**: any additional properties from the options object when creating the
         *       cache.</li>
         *   </ul>
         */
        info: function() {
          return extend({}, stats, {size: size});
        }
      });


      /**
       * makes the `entry` the freshEnd of the LRU linked list
       */
      function refresh(entry) {
        if (entry !== freshEnd) {
          if (!staleEnd) {
            staleEnd = entry;
          } else if (staleEnd === entry) {
            staleEnd = entry.n;
          }

          link(entry.n, entry.p);
          link(entry, freshEnd);
          freshEnd = entry;
          freshEnd.n = null;
        }
      }


      /**
       * bidirectionally links two entries of the LRU linked list
       */
      function link(nextEntry, prevEntry) {
        if (nextEntry !== prevEntry) {
          if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify
          if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify
        }
      }
    }


  /**
   * @ngdoc method
   * @name $cacheFactory#info
   *
   * @description
   * Get information about all the caches that have been created
   *
   * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info`
   */
    cacheFactory.info = function() {
      var info = {};
      forEach(caches, function(cache, cacheId) {
        info[cacheId] = cache.info();
      });
      return info;
    };


  /**
   * @ngdoc method
   * @name $cacheFactory#get
   *
   * @description
   * Get access to a cache object by the `cacheId` used when it was created.
   *
   * @param {string} cacheId Name or id of a cache to access.
   * @returns {object} Cache object identified by the cacheId or undefined if no such cache.
   */
    cacheFactory.get = function(cacheId) {
      return caches[cacheId];
    };


    return cacheFactory;
  };
}

/**
 * @ngdoc service
 * @name $templateCache
 * @this
 *
 * @description
 * `$templateCache` is a {@link $cacheFactory.Cache Cache object} created by the
 * {@link ng.$cacheFactory $cacheFactory}.
 *
 * The first time a template is used, it is loaded in the template cache for quick retrieval. You
 * can load templates directly into the cache in a `script` tag, by using {@link $templateRequest},
 * or by consuming the `$templateCache` service directly.
 *
 * Adding via the `script` tag:
 *
 * ```html
 *   <script type="text/ng-template" id="templateId.html">
 *     <p>This is the content of the template</p>
 *   </script>
 * ```
 *
 * **Note:** the `script` tag containing the template does not need to be included in the `head` of
 * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (e.g.
 * element with {@link ngApp} attribute), otherwise the template will be ignored.
 *
 * Adding via the `$templateCache` service:
 *
 * ```js
 * var myApp = angular.module('myApp', []);
 * myApp.run(function($templateCache) {
 *   $templateCache.put('templateId.html', 'This is the content of the template');
 * });
 * ```
 *
 * To retrieve the template later, simply use it in your component:
 * ```js
 * myApp.component('myComponent', {
 *    templateUrl: 'templateId.html'
 * });
 * ```
 *
 * or get it via the `$templateCache` service:
 * ```js
 * $templateCache.get('templateId.html')
 * ```
 *
 */
function $TemplateCacheProvider() {
  this.$get = ['$cacheFactory', function($cacheFactory) {
    return $cacheFactory('templates');
  }];
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *     Any commits to this file should be reviewed with security in mind.  *
 *   Changes to this file can potentially create security vulnerabilities. *
 *          An approval from 2 Core members with history of modifying      *
 *                         this file is required.                          *
 *                                                                         *
 *  Does the change somehow allow for arbitrary javascript to be executed? *
 *    Or allows for someone to change the prototype of built-in objects?   *
 *     Or gives undesired access to variables like document or window?    *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE!
 *
 * DOM-related variables:
 *
 * - "node" - DOM Node
 * - "element" - DOM Element or Node
 * - "$node" or "$element" - jqLite-wrapped node or element
 *
 *
 * Compiler related stuff:
 *
 * - "linkFn" - linking fn of a single directive
 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node
 * - "childLinkFn" -  function that aggregates all linking fns for child nodes of a particular node
 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList)
 */


/**
 * @ngdoc service
 * @name $compile
 * @kind function
 *
 * @description
 * Compiles an HTML string or DOM into a template and produces a template function, which
 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together.
 *
 * The compilation is a process of walking the DOM tree and matching DOM elements to
 * {@link ng.$compileProvider#directive directives}.
 *
 * <div class="alert alert-warning">
 * **Note:** This document is an in-depth reference of all directive options.
 * For a gentle introduction to directives with examples of common use cases,
 * see the {@link guide/directive directive guide}.
 * </div>
 *
 * ## Comprehensive Directive API
 *
 * There are many different options for a directive.
 *
 * The difference resides in the return value of the factory function.
 * You can either return a {@link $compile#directive-definition-object Directive Definition Object (see below)}
 * that defines the directive properties, or just the `postLink` function (all other properties will have
 * the default values).
 *
 * <div class="alert alert-success">
 * **Best Practice:** It's recommended to use the "directive definition object" form.
 * </div>
 *
 * Here's an example directive declared with a Directive Definition Object:
 *
 * ```js
 *   var myModule = angular.module(...);
 *
 *   myModule.directive('directiveName', function factory(injectables) {
 *     var directiveDefinitionObject = {
 *       {@link $compile#-priority- priority}: 0,
 *       {@link $compile#-template- template}: '<div></div>', // or // function(tElement, tAttrs) { ... },
 *       // or
 *       // {@link $compile#-templateurl- templateUrl}: 'directive.html', // or // function(tElement, tAttrs) { ... },
 *       {@link $compile#-transclude- transclude}: false,
 *       {@link $compile#-restrict- restrict}: 'A',
 *       {@link $compile#-templatenamespace- templateNamespace}: 'html',
 *       {@link $compile#-scope- scope}: false,
 *       {@link $compile#-controller- controller}: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
 *       {@link $compile#-controlleras- controllerAs}: 'stringIdentifier',
 *       {@link $compile#-bindtocontroller- bindToController}: false,
 *       {@link $compile#-require- require}: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
 *       {@link $compile#-multielement- multiElement}: false,
 *       {@link $compile#-compile- compile}: function compile(tElement, tAttrs, transclude) {
 *         return {
 *            {@link $compile#pre-linking-function pre}: function preLink(scope, iElement, iAttrs, controller) { ... },
 *            {@link $compile#post-linking-function post}: function postLink(scope, iElement, iAttrs, controller) { ... }
 *         }
 *         // or
 *         // return function postLink( ... ) { ... }
 *       },
 *       // or
 *       // {@link $compile#-link- link}: {
 *       //  {@link $compile#pre-linking-function pre}: function preLink(scope, iElement, iAttrs, controller) { ... },
 *       //  {@link $compile#post-linking-function post}: function postLink(scope, iElement, iAttrs, controller) { ... }
 *       // }
 *       // or
 *       // {@link $compile#-link- link}: function postLink( ... ) { ... }
 *     };
 *     return directiveDefinitionObject;
 *   });
 * ```
 *
 * <div class="alert alert-warning">
 * **Note:** Any unspecified options will use the default value. You can see the default values below.
 * </div>
 *
 * Therefore the above can be simplified as:
 *
 * ```js
 *   var myModule = angular.module(...);
 *
 *   myModule.directive('directiveName', function factory(injectables) {
 *     var directiveDefinitionObject = {
 *       link: function postLink(scope, iElement, iAttrs) { ... }
 *     };
 *     return directiveDefinitionObject;
 *     // or
 *     // return function postLink(scope, iElement, iAttrs) { ... }
 *   });
 * ```
 *
 * ### Life-cycle hooks
 * Directive controllers can provide the following methods that are called by AngularJS at points in the life-cycle of the
 * directive:
 * * `$onInit()` - Called on each controller after all the controllers on an element have been constructed and
 *   had their bindings initialized (and before the pre &amp; post linking functions for the directives on
 *   this element). This is a good place to put initialization code for your controller.
 * * `$onChanges(changesObj)` - Called whenever one-way (`<`) or interpolation (`@`) bindings are updated. The
 *   `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an
 *   object of the form `{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a
 *   component such as cloning the bound value to prevent accidental mutation of the outer value. Note that this will
 *   also be called when your bindings are initialized.
 * * `$doCheck()` - Called on each turn of the digest cycle. Provides an opportunity to detect and act on
 *   changes. Any actions that you wish to take in response to the changes that you detect must be
 *   invoked from this hook; implementing this has no effect on when `$onChanges` is called. For example, this hook
 *   could be useful if you wish to perform a deep equality check, or to check a Date object, changes to which would not
 *   be detected by AngularJS's change detector and thus not trigger `$onChanges`. This hook is invoked with no arguments;
 *   if detecting changes, you must store the previous value(s) for comparison to the current values.
 * * `$onDestroy()` - Called on a controller when its containing scope is destroyed. Use this hook for releasing
 *   external resources, watches and event handlers. Note that components have their `$onDestroy()` hooks called in
 *   the same order as the `$scope.$broadcast` events are triggered, which is top down. This means that parent
 *   components will have their `$onDestroy()` hook called before child components.
 * * `$postLink()` - Called after this controller's element and its children have been linked. Similar to the post-link
 *   function this hook can be used to set up DOM event handlers and do direct DOM manipulation.
 *   Note that child elements that contain `templateUrl` directives will not have been compiled and linked since
 *   they are waiting for their template to load asynchronously and their own compilation and linking has been
 *   suspended until that occurs.
 *
 * #### Comparison with life-cycle hooks in the new Angular
 * The new Angular also uses life-cycle hooks for its components. While the AngularJS life-cycle hooks are similar there are
 * some differences that you should be aware of, especially when it comes to moving your code from AngularJS to Angular:
 *
 * * AngularJS hooks are prefixed with `$`, such as `$onInit`. Angular hooks are prefixed with `ng`, such as `ngOnInit`.
 * * AngularJS hooks can be defined on the controller prototype or added to the controller inside its constructor.
 *   In Angular you can only define hooks on the prototype of the Component class.
 * * Due to the differences in change-detection, you may get many more calls to `$doCheck` in AngularJS than you would to
 *   `ngDoCheck` in Angular.
 * * Changes to the model inside `$doCheck` will trigger new turns of the digest loop, which will cause the changes to be
 *   propagated throughout the application.
 *   Angular does not allow the `ngDoCheck` hook to trigger a change outside of the component. It will either throw an
 *   error or do nothing depending upon the state of `enableProdMode()`.
 *
 * #### Life-cycle hook examples
 *
 * This example shows how you can check for mutations to a Date object even though the identity of the object
 * has not changed.
 *
 * <example name="doCheckDateExample" module="do-check-module">
 *   <file name="app.js">
 *     angular.module('do-check-module', [])
 *       .component('app', {
 *         template:
 *           'Month: <input ng-model="$ctrl.month" ng-change="$ctrl.updateDate()">' +
 *           'Date: {{ $ctrl.date }}' +
 *           '<test date="$ctrl.date"></test>',
 *         controller: function() {
 *           this.date = new Date();
 *           this.month = this.date.getMonth();
 *           this.updateDate = function() {
 *             this.date.setMonth(this.month);
 *           };
 *         }
 *       })
 *       .component('test', {
 *         bindings: { date: '<' },
 *         template:
 *           '<pre>{{ $ctrl.log | json }}</pre>',
 *         controller: function() {
 *           var previousValue;
 *           this.log = [];
 *           this.$doCheck = function() {
 *             var currentValue = this.date && this.date.valueOf();
 *             if (previousValue !== currentValue) {
 *               this.log.push('doCheck: date mutated: ' + this.date);
 *               previousValue = currentValue;
 *             }
 *           };
 *         }
 *       });
 *   </file>
 *   <file name="index.html">
 *     <app></app>
 *   </file>
 * </example>
 *
 * This example show how you might use `$doCheck` to trigger changes in your component's inputs even if the
 * actual identity of the component doesn't change. (Be aware that cloning and deep equality checks on large
 * arrays or objects can have a negative impact on your application performance.)
 *
 * <example name="doCheckArrayExample" module="do-check-module">
 *   <file name="index.html">
 *     <div ng-init="items = []">
 *       <button ng-click="items.push(items.length)">Add Item</button>
 *       <button ng-click="items = []">Reset Items</button>
 *       <pre>{{ items }}</pre>
 *       <test items="items"></test>
 *     </div>
 *   </file>
 *   <file name="app.js">
 *      angular.module('do-check-module', [])
 *        .component('test', {
 *          bindings: { items: '<' },
 *          template:
 *            '<pre>{{ $ctrl.log | json }}</pre>',
 *          controller: function() {
 *            this.log = [];
 *
 *            this.$doCheck = function() {
 *              if (this.items_ref !== this.items) {
 *                this.log.push('doCheck: items changed');
 *                this.items_ref = this.items;
 *              }
 *              if (!angular.equals(this.items_clone, this.items)) {
 *                this.log.push('doCheck: items mutated');
 *                this.items_clone = angular.copy(this.items);
 *              }
 *            };
 *          }
 *        });
 *   </file>
 * </example>
 *
 *
 * ### Directive Definition Object
 *
 * The directive definition object provides instructions to the {@link ng.$compile
 * compiler}. The attributes are:
 *
 * #### `multiElement`
 * When this property is set to true (default is `false`), the HTML compiler will collect DOM nodes between
 * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them
 * together as the directive elements. It is recommended that this feature be used on directives
 * which are not strictly behavioral (such as {@link ngClick}), and which
 * do not manipulate or replace child nodes (such as {@link ngInclude}).
 *
 * #### `priority`
 * When there are multiple directives defined on a single DOM element, sometimes it
 * is necessary to specify the order in which the directives are applied. The `priority` is used
 * to sort the directives before their `compile` functions get called. Priority is defined as a
 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions
 * are also run in priority order, but post-link functions are run in reverse order. The order
 * of directives with the same priority is undefined. The default priority is `0`.
 *
 * #### `terminal`
 * If set to true then the current `priority` will be the last set of directives
 * which will execute (any directives at the current priority will still execute
 * as the order of execution on same `priority` is undefined). Note that expressions
 * and other directives used in the directive's template will also be excluded from execution.
 *
 * #### `scope`
 * The scope property can be `false`, `true`, or an object:
 *
 * * **`false` (default):** No scope will be created for the directive. The directive will use its
 * parent's scope.
 *
 * * **`true`:** A new child scope that prototypically inherits from its parent will be created for
 * the directive's element. If multiple directives on the same element request a new scope,
 * only one new scope is created.
 *
 * * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's template.
 * The 'isolate' scope differs from normal scope in that it does not prototypically
 * inherit from its parent scope. This is useful when creating reusable components, which should not
 * accidentally read or modify data in the parent scope. Note that an isolate scope
 * directive without a `template` or `templateUrl` will not apply the isolate scope
 * to its children elements.
 *
 * The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the
 * directive's element. These local properties are useful for aliasing values for templates. The keys in
 * the object hash map to the name of the property on the isolate scope; the values define how the property
 * is bound to the parent scope, via matching attributes on the directive's element:
 *
 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
 *   always a string since DOM attributes are strings. If no `attr` name is specified then the
 *   attribute name is assumed to be the same as the local name. Given `<my-component
 *   my-attr="hello {{name}}">` and the isolate scope definition `scope: { localName:'@myAttr' }`,
 *   the directive's scope property `localName` will reflect the interpolated value of `hello
 *   {{name}}`. As the `name` attribute changes so will the `localName` property on the directive's
 *   scope. The `name` is read from the parent scope (not the directive's scope).
 *
 * * `=` or `=attr` - set up a bidirectional binding between a local scope property and an expression
 *   passed via the attribute `attr`. The expression is evaluated in the context of the parent scope.
 *   If no `attr` name is specified then the attribute name is assumed to be the same as the local
 *   name. Given `<my-component my-attr="parentModel">` and the isolate scope definition `scope: {
 *   localModel: '=myAttr' }`, the property `localModel` on the directive's scope will reflect the
 *   value of `parentModel` on the parent scope. Changes to `parentModel` will be reflected in
 *   `localModel` and vice versa. If the binding expression is non-assignable, or if the attribute
 *   isn't  optional and doesn't exist, an exception
 *   ({@link error/$compile/nonassign `$compile:nonassign`}) will be thrown upon discovering changes
 *   to the local value, since it will be impossible to sync them back to the parent scope.
 *
 *   By default, the {@link ng.$rootScope.Scope#$watch `$watch`}
 *   method is used for tracking changes, and the equality check is based on object identity.
 *   However, if an object literal or an array literal is passed as the binding expression, the
 *   equality check is done by value (using the {@link angular.equals} function). It's also possible
 *   to watch the evaluated value shallowly with {@link ng.$rootScope.Scope#$watchCollection
 *   `$watchCollection`}: use `=*` or `=*attr`
 *
  * * `<` or `<attr` - set up a one-way (one-directional) binding between a local scope property and an
 *   expression passed via the attribute `attr`. The expression is evaluated in the context of the
 *   parent scope. If no `attr` name is specified then the attribute name is assumed to be the same as the
 *   local name.
 *
 *   For example, given `<my-component my-attr="parentModel">` and directive definition of
 *   `scope: { localModel:'<myAttr' }`, then the isolated scope property `localModel` will reflect the
 *   value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected
 *   in `localModel`, but changes in `localModel` will not reflect in `parentModel`. There are however
 *   two caveats:
 *     1. one-way binding does not copy the value from the parent to the isolate scope, it simply
 *     sets the same value. That means if your bound value is an object, changes to its properties
 *     in the isolated scope will be reflected in the parent scope (because both reference the same object).
 *     2. one-way binding watches changes to the **identity** of the parent value. That means the
 *     {@link ng.$rootScope.Scope#$watch `$watch`} on the parent value only fires if the reference
 *     to the value has changed. In most cases, this should not be of concern, but can be important
 *     to know if you one-way bind to an object, and then replace that object in the isolated scope.
 *     If you now change a property of the object in your parent scope, the change will not be
 *     propagated to the isolated scope, because the identity of the object on the parent scope
 *     has not changed. Instead you must assign a new object.
 *
 *   One-way binding is useful if you do not plan to propagate changes to your isolated scope bindings
 *   back to the parent. However, it does not make this completely impossible.
 *
 *   By default, the {@link ng.$rootScope.Scope#$watch `$watch`}
 *   method is used for tracking changes, and the equality check is based on object identity.
 *   It's also possible to watch the evaluated value shallowly with
 *   {@link ng.$rootScope.Scope#$watchCollection `$watchCollection`}: use `<*` or `<*attr`
 *
 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. If
 *   no `attr` name is specified then the attribute name is assumed to be the same as the local name.
 *   Given `<my-component my-attr="count = count + value">` and the isolate scope definition `scope: {
 *   localFn:'&myAttr' }`, the isolate scope property `localFn` will point to a function wrapper for
 *   the `count = count + value` expression. Often it's desirable to pass data from the isolated scope
 *   via an expression to the parent scope. This can be done by passing a map of local variable names
 *   and values into the expression wrapper fn. For example, if the expression is `increment(amount)`
 *   then we can specify the amount value by calling the `localFn` as `localFn({amount: 22})`.
 *
 * All 4 kinds of bindings (`@`, `=`, `<`, and `&`) can be made optional by adding `?` to the expression.
 * The marker must come after the mode and before the attribute name.
 * See the {@link error/$compile/iscp Invalid Isolate Scope Definition error} for definition examples.
 * This is useful to refine the interface directives provide.
 * One subtle difference between optional and non-optional happens **when the binding attribute is not
 * set**:
 * - the binding is optional: the property will not be defined
 * - the binding is not optional: the property is defined
 *
 * ```js
 *app.directive('testDir', function() {
    return {
      scope: {
        notoptional: '=',
        optional: '=?',
      },
      bindToController: true,
      controller: function() {
        this.$onInit = function() {
          console.log(this.hasOwnProperty('notoptional')) // true
          console.log(this.hasOwnProperty('optional')) // false
        }
      }
    }
  })
 *```
 *
 *
 * ##### Combining directives with different scope defintions
 *
 * In general it's possible to apply more than one directive to one element, but there might be limitations
 * depending on the type of scope required by the directives. The following points will help explain these limitations.
 * For simplicity only two directives are taken into account, but it is also applicable for several directives:
 *
 * * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope
 * * **child scope** + **no scope** =>  Both directives will share one single child scope
 * * **child scope** + **child scope** =>  Both directives will share one single child scope
 * * **isolated scope** + **no scope** =>  The isolated directive will use it's own created isolated scope. The other directive will use
 * its parent's scope
 * * **isolated scope** + **child scope** =>  **Won't work!** Only one scope can be related to one element. Therefore these directives cannot
 * be applied to the same element.
 * * **isolated scope** + **isolated scope**  =>  **Won't work!** Only one scope can be related to one element. Therefore these directives
 * cannot be applied to the same element.
 *
 *
 * #### `bindToController`
 * This property is used to bind scope properties directly to the controller. It can be either
 * `true` or an object hash with the same format as the `scope` property.
 *
 * When an isolate scope is used for a directive (see above), `bindToController: true` will
 * allow a component to have its properties bound to the controller, rather than to scope.
 *
 * After the controller is instantiated, the initial values of the isolate scope bindings will be bound to the controller
 * properties. You can access these bindings once they have been initialized by providing a controller method called
 * `$onInit`, which is called after all the controllers on an element have been constructed and had their bindings
 * initialized.
 *
 * It is also possible to set `bindToController` to an object hash with the same format as the `scope` property.
 * This will set up the scope bindings to the controller directly. Note that `scope` can still be used
 * to define which kind of scope is created. By default, no scope is created. Use `scope: {}` to create an isolate
 * scope (useful for component directives).
 *
 * If both `bindToController` and `scope` are defined and have object hashes, `bindToController` overrides `scope`.
 *
 *
 * #### `controller`
 * Controller constructor function. The controller is instantiated before the
 * pre-linking phase and can be accessed by other directives (see
 * `require` attribute). This allows the directives to communicate with each other and augment
 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals:
 *
 * * `$scope` - Current scope associated with the element
 * * `$element` - Current element
 * * `$attrs` - Current attributes object for the element
 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope:
 *   `function([scope], cloneLinkingFn, futureParentElement, slotName)`:
 *    * `scope`: (optional) override the scope.
 *    * `cloneLinkingFn`: (optional) argument to create clones of the original transcluded content.
 *    * `futureParentElement` (optional):
 *        * defines the parent to which the `cloneLinkingFn` will add the cloned elements.
 *        * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`.
 *        * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements)
 *          and when the `cloneLinkingFn` is passed,
 *          as those elements need to created and cloned in a special way when they are defined outside their
 *          usual containers (e.g. like `<svg>`).
 *        * See also the `directive.templateNamespace` property.
 *    * `slotName`: (optional) the name of the slot to transclude. If falsy (e.g. `null`, `undefined` or `''`)
 *      then the default transclusion is provided.
 *    The `$transclude` function also has a method on it, `$transclude.isSlotFilled(slotName)`, which returns
 *    `true` if the specified slot contains content (i.e. one or more DOM nodes).
 *
 * #### `require`
 * Require another directive and inject its controller as the fourth argument to the linking function. The
 * `require` property can be a string, an array or an object:
 * * a **string** containing the name of the directive to pass to the linking function
 * * an **array** containing the names of directives to pass to the linking function. The argument passed to the
 * linking function will be an array of controllers in the same order as the names in the `require` property
 * * an **object** whose property values are the names of the directives to pass to the linking function. The argument
 * passed to the linking function will also be an object with matching keys, whose values will hold the corresponding
 * controllers.
 *
 * If the `require` property is an object and `bindToController` is truthy, then the required controllers are
 * bound to the controller using the keys of the `require` property. This binding occurs after all the controllers
 * have been constructed but before `$onInit` is called.
 * If the name of the required controller is the same as the local name (the key), the name can be
 * omitted. For example, `{parentDir: '^^'}` is equivalent to `{parentDir: '^^parentDir'}`.
 * See the {@link $compileProvider#component} helper for an example of how this can be used.
 * If no such required directive(s) can be found, or if the directive does not have a controller, then an error is
 * raised (unless no link function is specified and the required controllers are not being bound to the directive
 * controller, in which case error checking is skipped). The name can be prefixed with:
 *
 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
 * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found.
 * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found.
 * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass
 *   `null` to the `link` fn if not found.
 * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass
 *   `null` to the `link` fn if not found.
 *
 *
 * #### `controllerAs`
 * Identifier name for a reference to the controller in the directive's scope.
 * This allows the controller to be referenced from the directive template. This is especially
 * useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible
 * to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the
 * `controllerAs` reference might overwrite a property that already exists on the parent scope.
 *
 *
 * #### `restrict`
 * String of subset of `EACM` which restricts the directive to a specific directive
 * declaration style. If omitted, the defaults (elements and attributes) are used.
 *
 * * `E` - Element name (default): `<my-directive></my-directive>`
 * * `A` - Attribute (default): `<div my-directive="exp"></div>`
 * * `C` - Class: `<div class="my-directive: exp;"></div>`
 * * `M` - Comment: `<!-- directive: my-directive exp -->`
 *
 *
 * #### `templateNamespace`
 * String representing the document type used by the markup in the template.
 * AngularJS needs this information as those elements need to be created and cloned
 * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`.
 *
 * * `html` - All root nodes in the template are HTML. Root nodes may also be
 *   top-level elements such as `<svg>` or `<math>`.
 * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`).
 * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`).
 *
 * If no `templateNamespace` is specified, then the namespace is considered to be `html`.
 *
 * #### `template`
 * HTML markup that may:
 * * Replace the contents of the directive's element (default).
 * * Replace the directive's element itself (if `replace` is true - DEPRECATED).
 * * Wrap the contents of the directive's element (if `transclude` is true).
 *
 * Value may be:
 *
 * * A string. For example `<div red-on-hover>{{delete_str}}</div>`.
 * * A function which takes two arguments `tElement` and `tAttrs` (described in the `compile`
 *   function api below) and returns a string value.
 *
 *
 * #### `templateUrl`
 * This is similar to `template` but the template is loaded from the specified URL, asynchronously.
 *
 * Because template loading is asynchronous the compiler will suspend compilation of directives on that element
 * for later when the template has been resolved.  In the meantime it will continue to compile and link
 * sibling and parent elements as though this element had not contained any directives.
 *
 * The compiler does not suspend the entire compilation to wait for templates to be loaded because this
 * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the
 * case when only one deeply nested directive has `templateUrl`.
 *
 * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache}.
 *
 * You can specify `templateUrl` as a string representing the URL or as a function which takes two
 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns
 * a string value representing the url.  In either case, the template URL is passed through {@link
 * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}.
 *
 *
 * #### `replace`
 * <div class="alert alert-danger">
 * **Note:** `replace` is deprecated in AngularJS and has been removed in the new Angular (v2+).
 * </div>
 *
 * Specifies what the template should replace. Defaults to `false`.
 *
 * * `true` - the template will replace the directive's element.
 * * `false` - the template will replace the contents of the directive's element.
 *
 * The replacement process migrates all of the attributes / classes from the old element to the new
 * one. See the {@link guide/directive#template-expanding-directive
 * Directives Guide} for an example.
 *
 * There are very few scenarios where element replacement is required for the application function,
 * the main one being reusable custom components that are used within SVG contexts
 * (because SVG doesn't work with custom elements in the DOM tree).
 *
 * #### `transclude`
 * Extract the contents of the element where the directive appears and make it available to the directive.
 * The contents are compiled and provided to the directive as a **transclusion function**. See the
 * {@link $compile#transclusion Transclusion} section below.
 *
 *
 * #### `compile`
 *
 * ```js
 *   function compile(tElement, tAttrs, transclude) { ... }
 * ```
 *
 * The compile function deals with transforming the template DOM. Since most directives do not do
 * template transformation, it is not used often. The compile function takes the following arguments:
 *
 *   * `tElement` - template element - The element where the directive has been declared. It is
 *     safe to do template transformation on the element and child elements only.
 *
 *   * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared
 *     between all directive compile functions.
 *
 *   * `transclude` -  [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)`
 *
 * <div class="alert alert-warning">
 * **Note:** The template instance and the link instance may be different objects if the template has
 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that
 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration
 * should be done in a linking function rather than in a compile function.
 * </div>

 * <div class="alert alert-warning">
 * **Note:** The compile function cannot handle directives that recursively use themselves in their
 * own templates or compile functions. Compiling these directives results in an infinite loop and
 * stack overflow errors.
 *
 * This can be avoided by manually using `$compile` in the postLink function to imperatively compile
 * a directive's template instead of relying on automatic template compilation via `template` or
 * `templateUrl` declaration or manual compilation inside the compile function.
 * </div>
 *
 * <div class="alert alert-danger">
 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it
 *   e.g. does not know about the right outer scope. Please use the transclude function that is passed
 *   to the link function instead.
 * </div>

 * A compile function can have a return value which can be either a function or an object.
 *
 * * returning a (post-link) function - is equivalent to registering the linking function via the
 *   `link` property of the config object when the compile function is empty.
 *
 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to
 *   control when a linking function should be called during the linking phase. See info about
 *   pre-linking and post-linking functions below.
 *
 *
 * #### `link`
 * This property is used only if the `compile` property is not defined.
 *
 * ```js
 *   function link(scope, iElement, iAttrs, controller, transcludeFn) { ... }
 * ```
 *
 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is
 * executed after the template has been cloned. This is where most of the directive logic will be
 * put.
 *
 *   * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the
 *     directive for registering {@link ng.$rootScope.Scope#$watch watches}.
 *
 *   * `iElement` - instance element - The element where the directive is to be used. It is safe to
 *     manipulate the children of the element only in `postLink` function since the children have
 *     already been linked.
 *
 *   * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared
 *     between all directive linking functions.
 *
 *   * `controller` - the directive's required controller instance(s) - Instances are shared
 *     among all directives, which allows the directives to use the controllers as a communication
 *     channel. The exact value depends on the directive's `require` property:
 *       * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one
 *       * `string`: the controller instance
 *       * `array`: array of controller instances
 *
 *     If a required controller cannot be found, and it is optional, the instance is `null`,
 *     otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown.
 *
 *     Note that you can also require the directive's own controller - it will be made available like
 *     any other controller.
 *
 *   * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope.
 *     This is the same as the `$transclude` parameter of directive controllers,
 *     see {@link ng.$compile#-controller- the controller section for details}.
 *     `function([scope], cloneLinkingFn, futureParentElement)`.
 *
 * #### Pre-linking function
 *
 * Executed before the child elements are linked. Not safe to do DOM transformation since the
 * compiler linking function will fail to locate the correct elements for linking.
 *
 * #### Post-linking function
 *
 * Executed after the child elements are linked.
 *
 * Note that child elements that contain `templateUrl` directives will not have been compiled
 * and linked since they are waiting for their template to load asynchronously and their own
 * compilation and linking has been suspended until that occurs.
 *
 * It is safe to do DOM transformation in the post-linking function on elements that are not waiting
 * for their async templates to be resolved.
 *
 *
 * ### Transclusion
 *
 * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and
 * copying them to another part of the DOM, while maintaining their connection to the original AngularJS
 * scope from where they were taken.
 *
 * Transclusion is used (often with {@link ngTransclude}) to insert the
 * original contents of a directive's element into a specified place in the template of the directive.
 * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded
 * content has access to the properties on the scope from which it was taken, even if the directive
 * has isolated scope.
 * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}.
 *
 * This makes it possible for the widget to have private state for its template, while the transcluded
 * content has access to its originating scope.
 *
 * <div class="alert alert-warning">
 * **Note:** When testing an element transclude directive you must not place the directive at the root of the
 * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives
 * Testing Transclusion Directives}.
 * </div>
 *
 * There are three kinds of transclusion depending upon whether you want to transclude just the contents of the
 * directive's element, the entire element or multiple parts of the element contents:
 *
 * * `true` - transclude the content (i.e. the child nodes) of the directive's element.
 * * `'element'` - transclude the whole of the directive's element including any directives on this
 *   element that are defined at a lower priority than this directive. When used, the `template`
 *   property is ignored.
 * * **`{...}` (an object hash):** - map elements of the content onto transclusion "slots" in the template.
 *
 * **Multi-slot transclusion** is declared by providing an object for the `transclude` property.
 *
 * This object is a map where the keys are the name of the slot to fill and the value is an element selector
 * used to match the HTML to the slot. The element selector should be in normalized form (e.g. `myElement`)
 * and will match the standard element variants (e.g. `my-element`, `my:element`, `data-my-element`, etc).
 *
 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}.
 *
 * If the element selector is prefixed with a `?` then that slot is optional.
 *
 * For example, the transclude object `{ slotA: '?myCustomElement' }` maps `<my-custom-element>` elements to
 * the `slotA` slot, which can be accessed via the `$transclude` function or via the {@link ngTransclude} directive.
 *
 * Slots that are not marked as optional (`?`) will trigger a compile time error if there are no matching elements
 * in the transclude content. If you wish to know if an optional slot was filled with content, then you can call
 * `$transclude.isSlotFilled(slotName)` on the transclude function passed to the directive's link function and
 * injectable into the directive's controller.
 *
 *
 * #### Transclusion Functions
 *
 * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion
 * function** to the directive's `link` function and `controller`. This transclusion function is a special
 * **linking function** that will return the compiled contents linked to a new transclusion scope.
 *
 * <div class="alert alert-info">
 * If you are just using {@link ngTransclude} then you don't need to worry about this function, since
 * ngTransclude will deal with it for us.
 * </div>
 *
 * If you want to manually control the insertion and removal of the transcluded content in your directive
 * then you must use this transclude function. When you call a transclude function it returns a jqLite/JQuery
 * object that contains the compiled DOM, which is linked to the correct transclusion scope.
 *
 * When you call a transclusion function you can pass in a **clone attach function**. This function accepts
 * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded
 * content and the `scope` is the newly created transclusion scope, which the clone will be linked to.
 *
 * <div class="alert alert-info">
 * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a transclude function
 * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope.
 * </div>
 *
 * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone
 * attach function**:
 *
 * ```js
 * var transcludedContent, transclusionScope;
 *
 * $transclude(function(clone, scope) {
 *   element.append(clone);
 *   transcludedContent = clone;
 *   transclusionScope = scope;
 * });
 * ```
 *
 * Later, if you want to remove the transcluded content from your DOM then you should also destroy the
 * associated transclusion scope:
 *
 * ```js
 * transcludedContent.remove();
 * transclusionScope.$destroy();
 * ```
 *
 * <div class="alert alert-info">
 * **Best Practice**: if you intend to add and remove transcluded content manually in your directive
 * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it),
 * then you are also responsible for calling `$destroy` on the transclusion scope.
 * </div>
 *
 * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat}
 * automatically destroy their transcluded clones as necessary so you do not need to worry about this if
 * you are simply using {@link ngTransclude} to inject the transclusion into your directive.
 *
 *
 * #### Transclusion Scopes
 *
 * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion
 * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed
 * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it
 * was taken.
 *
 * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look
 * like this:
 *
 * ```html
 * <div ng-app>
 *   <div isolate>
 *     <div transclusion>
 *     </div>
 *   </div>
 * </div>
 * ```
 *
 * The `$parent` scope hierarchy will look like this:
 *
   ```
   - $rootScope
     - isolate
       - transclusion
   ```
 *
 * but the scopes will inherit prototypically from different scopes to their `$parent`.
 *
   ```
   - $rootScope
     - transclusion
   - isolate
   ```
 *
 *
 * ### Attributes
 *
 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the
 * `link()` or `compile()` functions. It has a variety of uses.
 *
 * * *Accessing normalized attribute names:* Directives like `ngBind` can be expressed in many ways:
 *   `ng:bind`, `data-ng-bind`, or `x-ng-bind`. The attributes object allows for normalized access
 *   to the attributes.
 *
 * * *Directive inter-communication:* All directives share the same instance of the attributes
 *   object which allows the directives to use the attributes object as inter directive
 *   communication.
 *
 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object
 *   allowing other directives to read the interpolated value.
 *
 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes
 *   that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also
 *   the only way to easily get the actual value because during the linking phase the interpolation
 *   hasn't been evaluated yet and so the value is at this time set to `undefined`.
 *
 * ```js
 * function linkingFn(scope, elm, attrs, ctrl) {
 *   // get the attribute value
 *   console.log(attrs.ngModel);
 *
 *   // change the attribute
 *   attrs.$set('ngModel', 'new value');
 *
 *   // observe changes to interpolated attribute
 *   attrs.$observe('ngModel', function(value) {
 *     console.log('ngModel has changed value to ' + value);
 *   });
 * }
 * ```
 *
 * ## Example
 *
 * <div class="alert alert-warning">
 * **Note**: Typically directives are registered with `module.directive`. The example below is
 * to illustrate how `$compile` works.
 * </div>
 *
 <example module="compileExample" name="compile">
   <file name="index.html">
    <script>
      angular.module('compileExample', [], function($compileProvider) {
        // Configure new 'compile' directive by passing a directive
        // factory function. The factory function injects '$compile'.
        $compileProvider.directive('compile', function($compile) {
          // The directive factory creates a link function.
          return function(scope, element, attrs) {
            scope.$watch(
              function(scope) {
                // Watch the 'compile' expression for changes.
                return scope.$eval(attrs.compile);
              },
              function(value) {
                // When the 'compile' expression changes
                // assign it into the current DOM.
                element.html(value);

                // Compile the new DOM and link it to the current scope.
                // NOTE: we only compile '.childNodes' so that we
                // don't get into an infinite loop compiling ourselves.
                $compile(element.contents())(scope);
              }
            );
          };
        });
      })
      .controller('GreeterController', ['$scope', function($scope) {
        $scope.name = 'AngularJS';
        $scope.html = 'Hello {{name}}';
      }]);
    </script>
    <div ng-controller="GreeterController">
      <input ng-model="name"> <br/>
      <textarea ng-model="html"></textarea> <br/>
      <div compile="html"></div>
    </div>
   </file>
   <file name="protractor.js" type="protractor">
     it('should auto compile', function() {
       var textarea = $('textarea');
       var output = $('div[compile]');
       // The initial state reads 'Hello AngularJS'.
       expect(output.getText()).toBe('Hello AngularJS');
       textarea.clear();
       textarea.sendKeys('{{name}}!');
       expect(output.getText()).toBe('AngularJS!');
     });
   </file>
 </example>

 *
 *
 * @param {string|DOMElement} element Element or HTML string to compile into a template function.
 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED.
 *
 * <div class="alert alert-danger">
 * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it
 *   e.g. will not use the right outer scope. Please pass the transclude function as a
 *   `parentBoundTranscludeFn` to the link function instead.
 * </div>
 *
 * @param {number} maxPriority only apply directives lower than given priority (Only effects the
 *                 root element(s), not their children)
 * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template
 * (a DOM element/tree) to a scope. Where:
 *
 *  * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
 *  * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
 *  `template` and call the `cloneAttachFn` function allowing the caller to attach the
 *  cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
 *  called as: <br/> `cloneAttachFn(clonedElement, scope)` where:
 *
 *      * `clonedElement` - is a clone of the original `element` passed into the compiler.
 *      * `scope` - is the current scope with which the linking function is working with.
 *
 *  * `options` - An optional object hash with linking options. If `options` is provided, then the following
 *  keys may be used to control linking behavior:
 *
 *      * `parentBoundTranscludeFn` - the transclude function made available to
 *        directives; if given, it will be passed through to the link functions of
 *        directives found in `element` during compilation.
 *      * `transcludeControllers` - an object hash with keys that map controller names
 *        to a hash with the key `instance`, which maps to the controller instance;
 *        if given, it will make the controllers available to directives on the compileNode:
 *        ```
 *        {
 *          parent: {
 *            instance: parentControllerInstance
 *          }
 *        }
 *        ```
 *      * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add
 *        the cloned elements; only needed for transcludes that are allowed to contain non HTML
 *        elements (e.g. SVG elements). See also the `directive.controller` property.
 *
 * Calling the linking function returns the element of the template. It is either the original
 * element passed in, or the clone of the element if the `cloneAttachFn` is provided.
 *
 * After linking the view is not updated until after a call to `$digest`, which typically is done by
 * AngularJS automatically.
 *
 * If you need access to the bound view, there are two ways to do it:
 *
 * - If you are not asking the linking function to clone the template, create the DOM element(s)
 *   before you send them to the compiler and keep this reference around.
 *   ```js
 *     var element = angular.element('<p>{{total}}</p>');
 *     $compile(element)(scope);
 *   ```
 *
 * - if on the other hand, you need the element to be cloned, the view reference from the original
 *   example would not point to the clone, but rather to the original template that was cloned. In
 *   this case, you can access the clone either via the `cloneAttachFn` or the value returned by the
 *   linking function:
 *   ```js
 *     var templateElement = angular.element('<p>{{total}}</p>');
 *     var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) {
 *       // Attach the clone to DOM document at the right place.
 *     });
 *
 *     // Now we have reference to the cloned DOM via `clonedElement`.
 *     // NOTE: The `clonedElement` returned by the linking function is the same as the
 *     //       `clonedElement` passed to `cloneAttachFn`.
 *   ```
 *
 *
 * For information on how the compiler works, see the
 * {@link guide/compiler AngularJS HTML Compiler} section of the Developer Guide.
 *
 * @knownIssue
 *
 * ### Double Compilation
 *
   Double compilation occurs when an already compiled part of the DOM gets
   compiled again. This is an undesired effect and can lead to misbehaving directives, performance issues,
   and memory leaks. Refer to the Compiler Guide {@link guide/compiler#double-compilation-and-how-to-avoid-it
   section on double compilation} for an in-depth explanation and ways to avoid it.

 * @knownIssue

   ### Issues with `replace: true`
 *
 * <div class="alert alert-danger">
 *   **Note**: {@link $compile#-replace- `replace: true`} is deprecated and not recommended to use,
 *   mainly due to the issues listed here. It has been completely removed in the new Angular.
 * </div>
 *
 * #### Attribute values are not merged
 *
 * When a `replace` directive encounters the same attribute on the original and the replace node,
 * it will simply deduplicate the attribute and join the values with a space or with a `;` in case of
 * the `style` attribute.
 * ```html
 * Original Node: <span class="original" style="color: red;"></span>
 * Replace Template: <span class="replaced" style="background: blue;"></span>
 * Result: <span class="original replaced" style="color: red; background: blue;"></span>
 * ```
 *
 * That means attributes that contain AngularJS expressions will not be merged correctly, e.g.
 * {@link ngShow} or {@link ngClass} will cause a {@link $parse} error:
 *
 * ```html
 * Original Node: <span ng-class="{'something': something}" ng-show="!condition"></span>
 * Replace Template: <span ng-class="{'else': else}" ng-show="otherCondition"></span>
 * Result: <span ng-class="{'something': something} {'else': else}" ng-show="!condition otherCondition"></span>
 * ```
 *
 * See issue [#5695](https://github.com/angular/angular.js/issues/5695).
 *
 * #### Directives are not deduplicated before compilation
 *
 * When the original node and the replace template declare the same directive(s), they will be
 * {@link guide/compiler#double-compilation-and-how-to-avoid-it compiled twice} because the compiler
 * does not deduplicate them. In many cases, this is not noticeable, but e.g. {@link ngModel} will
 * attach `$formatters` and `$parsers` twice.
 *
 * See issue [#2573](https://github.com/angular/angular.js/issues/2573).
 *
 * #### `transclude: element` in the replace template root can have unexpected effects
 *
 * When the replace template has a directive at the root node that uses
 * {@link $compile#-transclude- `transclude: element`}, e.g.
 * {@link ngIf} or {@link ngRepeat}, the DOM structure or scope inheritance can be incorrect.
 * See the following issues:
 *
 * - Incorrect scope on replaced element:
 * [#9837](https://github.com/angular/angular.js/issues/9837)
 * - Different DOM between `template` and `templateUrl`:
 * [#10612](https://github.com/angular/angular.js/issues/14326)
 *
 */

/**
 * @ngdoc directive
 * @name ngProp
 * @restrict A
 * @element ANY
 *
 * @usage
 *
 * ```html
 * <ANY ng-prop-propname="expression">
 * </ANY>
 * ```
 *
 * or with uppercase letters in property (e.g. "propName"):
 *
 *
 * ```html
 * <ANY ng-prop-prop_name="expression">
 * </ANY>
 * ```
 *
 *
 * @description
 * The `ngProp` directive binds an expression to a DOM element property.
 * `ngProp` allows writing to arbitrary properties by including
 * the property name in the attribute, e.g. `ng-prop-value="'my value'"` binds 'my value' to
 * the `value` property.
 *
 * Usually, it's not necessary to write to properties in AngularJS, as the built-in directives
 * handle the most common use cases (instead of the above example, you would use {@link ngValue}).
 *
 * However, [custom elements](https://developer.mozilla.org/docs/Web/Web_Components/Using_custom_elements)
 * often use custom properties to hold data, and `ngProp` can be used to provide input to these
 * custom elements.
 *
 * ## Binding to camelCase properties
 *
 * Since HTML attributes are case-insensitive, camelCase properties like `innerHTML` must be escaped.
 * AngularJS uses the underscore (_) in front of a character to indicate that it is uppercase, so
 * `innerHTML`  must be written as `ng-prop-inner_h_t_m_l="expression"` (Note that this is just an
 * example, and for binding HTML {@link ngBindHtml} should be used.
 *
 * ## Security
 *
 * Binding expressions to arbitrary properties poses a security risk, as  properties like `innerHTML`
 * can insert potentially dangerous HTML into the application, e.g. script tags that execute
 * malicious code.
 * For this reason, `ngProp` applies Strict Contextual Escaping with the {@link ng.$sce $sce service}.
 * This means vulnerable properties require their content to be "trusted", based on the
 * context of the property. For example, the `innerHTML` is in the `HTML` context, and the
 * `iframe.src` property is in the `RESOURCE_URL` context, which requires that values written to
 * this property are trusted as a `RESOURCE_URL`.
 *
 * This can be set explicitly by calling $sce.trustAs(type, value) on the value that is
 * trusted before passing it to the `ng-prop-*` directive. There are exist shorthand methods for
 * each context type in the form of {@link ng.$sce#trustAsResourceUrl $sce.trustAsResourceUrl()} et al.
 *
 * In some cases you can also rely upon automatic sanitization of untrusted values - see below.
 *
 * Based on the context, other options may exist to mark a value as trusted / configure the behavior
 * of {@link ng.$sce}. For example, to restrict the `RESOURCE_URL` context to specific origins, use
 * the {@link $sceDelegateProvider#trustedResourceUrlList trustedResourceUrlList()}
 * and {@link $sceDelegateProvider#bannedResourceUrlList bannedResourceUrlList()}.
 *
 * {@link ng.$sce#what-trusted-context-types-are-supported- Find out more about the different context types}.
 *
 * ### HTML Sanitization
 *
 * By default, `$sce` will throw an error if it detects untrusted HTML content, and will not bind the
 * content.
 * However, if you include the {@link ngSanitize ngSanitize module}, it will try to sanitize the
 * potentially dangerous HTML, e.g. strip non-trusted tags and attributes when binding to
 * `innerHTML`.
 *
 * @example
 * ### Binding to different contexts
 *
 * <example name="ngProp" module="exampleNgProp">
 *   <file name="app.js">
 *     angular.module('exampleNgProp', [])
 *       .component('main', {
 *         templateUrl: 'main.html',
 *         controller: function($sce) {
 *           this.safeContent = '<strong>Safe content</strong>';
 *           this.unsafeContent = '<button onclick="alert(\'Hello XSS!\')">Click for XSS</button>';
 *           this.trustedUnsafeContent = $sce.trustAsHtml(this.unsafeContent);
 *         }
 *       });
 *   </file>
 *   <file name="main.html">
 *     <div>
 *       <div class="prop-unit">
 *         Binding to a property without security context:
 *         <div class="prop-binding" ng-prop-inner_text="$ctrl.safeContent"></div>
 *         <span class="prop-note">innerText</span> (safeContent)
 *       </div>
 *
 *       <div class="prop-unit">
 *         "Safe" content that requires a security context will throw because the contents could potentially be dangerous ...
 *         <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.safeContent"></div>
 *         <span class="prop-note">innerHTML</span> (safeContent)
 *       </div>
 *
 *       <div class="prop-unit">
 *         ... so that actually dangerous content cannot be executed:
 *         <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.unsafeContent"></div>
 *         <span class="prop-note">innerHTML</span> (unsafeContent)
 *       </div>
 *
 *       <div class="prop-unit">
 *         ... but unsafe Content that has been trusted explicitly works - only do this if you are 100% sure!
 *         <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.trustedUnsafeContent"></div>
 *         <span class="prop-note">innerHTML</span> (trustedUnsafeContent)
 *       </div>
 *     </div>
 *   </file>
 *   <file name="index.html">
 *     <main></main>
 *   </file>
 *   <file name="styles.css">
 *     .prop-unit {
 *       margin-bottom: 10px;
 *     }
 *
 *     .prop-binding {
 *       min-height: 30px;
 *       border: 1px solid blue;
 *     }
 *
 *     .prop-note {
 *       font-family: Monospace;
 *     }
 *   </file>
 * </example>
 *
 *
 * @example
 * ### Binding to innerHTML with ngSanitize
 *
 * <example name="ngProp" module="exampleNgProp" deps="angular-sanitize.js">
 *   <file name="app.js">
 *     angular.module('exampleNgProp', ['ngSanitize'])
 *       .component('main', {
 *         templateUrl: 'main.html',
 *         controller: function($sce) {
 *           this.safeContent = '<strong>Safe content</strong>';
 *           this.unsafeContent = '<button onclick="alert(\'Hello XSS!\')">Click for XSS</button>';
 *           this.trustedUnsafeContent = $sce.trustAsHtml(this.unsafeContent);
 *         }
 *       });
 *   </file>
 *   <file name="main.html">
 *     <div>
 *       <div class="prop-unit">
 *         "Safe" content will be sanitized ...
 *         <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.safeContent"></div>
 *         <span class="prop-note">innerHTML</span> (safeContent)
 *       </div>
 *
 *       <div class="prop-unit">
 *         ... as will dangerous content:
 *         <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.unsafeContent"></div>
 *         <span class="prop-note">innerHTML</span> (unsafeContent)
 *       </div>
 *
 *       <div class="prop-unit">
 *         ... and content that has been trusted explicitly works the same as without ngSanitize:
 *         <div class="prop-binding" ng-prop-inner_h_t_m_l="$ctrl.trustedUnsafeContent"></div>
 *         <span class="prop-note">innerHTML</span> (trustedUnsafeContent)
 *       </div>
 *     </div>
 *   </file>
 *   <file name="index.html">
 *     <main></main>
 *   </file>
 *   <file name="styles.css">
 *     .prop-unit {
 *       margin-bottom: 10px;
 *     }
 *
 *     .prop-binding {
 *       min-height: 30px;
 *       border: 1px solid blue;
 *     }
 *
 *     .prop-note {
 *       font-family: Monospace;
 *     }
 *   </file>
 * </example>
 *
 */

/** @ngdoc directive
 * @name ngOn
 * @restrict A
 * @element ANY
 *
 * @usage
 *
 * ```html
 * <ANY ng-on-eventname="expression">
 * </ANY>
 * ```
 *
 * or with uppercase letters in property (e.g. "eventName"):
 *
 *
 * ```html
 * <ANY ng-on-event_name="expression">
 * </ANY>
 * ```
 *
 * @description
 * The `ngOn` directive adds an event listener to a DOM element via
 * {@link angular.element angular.element().on()}, and evaluates an expression when the event is
 * fired.
 * `ngOn` allows adding listeners for arbitrary events by including
 * the event name in the attribute, e.g. `ng-on-drop="onDrop()"` executes the 'onDrop()' expression
 * when the `drop` event is fired.
 *
 * AngularJS provides specific directives for many events, such as {@link ngClick}, so in most
 * cases it is not necessary to use `ngOn`. However, AngularJS does not support all events
 * (e.g. the `drop` event in the example above), and new events might be introduced in later DOM
 * standards.
 *
 * Another use-case for `ngOn` is listening to
 * [custom events](https://developer.mozilla.org/docs/Web/Guide/Events/Creating_and_triggering_events)
 * fired by
 * [custom elements](https://developer.mozilla.org/docs/Web/Web_Components/Using_custom_elements).
 *
 * ## Binding to camelCase properties
 *
 * Since HTML attributes are case-insensitive, camelCase properties like `myEvent` must be escaped.
 * AngularJS uses the underscore (_) in front of a character to indicate that it is uppercase, so
 * `myEvent` must be written as `ng-on-my_event="expression"`.
 *
 * @example
 * ### Bind to built-in DOM events
 *
 * <example name="ngOn" module="exampleNgOn">
 *   <file name="app.js">
 *     angular.module('exampleNgOn', [])
 *       .component('main', {
 *         templateUrl: 'main.html',
 *         controller: function() {
 *           this.clickCount = 0;
 *           this.mouseoverCount = 0;
 *
 *           this.loadingState = 0;
 *         }
 *       });
 *   </file>
 *   <file name="main.html">
 *     <div>
 *       This is equivalent to `ngClick` and `ngMouseover`:<br>
 *       <button
 *         ng-on-click="$ctrl.clickCount = $ctrl.clickCount + 1"
 *         ng-on-mouseover="$ctrl.mouseoverCount = $ctrl.mouseoverCount + 1">Click or mouseover</button><br>
 *       clickCount: {{$ctrl.clickCount}}<br>
 *       mouseover: {{$ctrl.mouseoverCount}}
 *
 *       <hr>
 *
 *       For the `error` and `load` event on images no built-in AngularJS directives exist:<br>
 *       <img src="thisimagedoesnotexist.png" ng-on-error="$ctrl.loadingState = -1" ng-on-load="$ctrl.loadingState = 1"><br>
 *       <div ng-switch="$ctrl.loadingState">
 *         <span ng-switch-when="0">Image is loading</span>
 *         <span ng-switch-when="-1">Image load error</span>
 *         <span ng-switch-when="1">Image loaded successfully</span>
 *       </div>
 *     </div>
 *   </file>
 *   <file name="index.html">
 *     <main></main>
 *   </file>
 * </example>
 *
 *
 * @example
 * ### Bind to custom DOM events
 *
 * <example name="ngOnCustom" module="exampleNgOn">
 *   <file name="app.js">
 *     angular.module('exampleNgOn', [])
 *       .component('main', {
 *         templateUrl: 'main.html',
 *         controller: function() {
 *           this.eventLog = '';
 *
 *           this.listener = function($event) {
 *             this.eventLog = 'Event with type "' + $event.type + '" fired at ' + $event.detail;
 *           };
 *         }
 *       })
 *       .component('childComponent', {
 *         templateUrl: 'child.html',
 *         controller: function($element) {
 *           this.fireEvent = function() {
 *             var event = new CustomEvent('customtype', { detail: new Date()});
 *
 *             $element[0].dispatchEvent(event);
 *           };
 *         }
 *       });
 *   </file>
 *   <file name="main.html">
 *     <child-component ng-on-customtype="$ctrl.listener($event)"></child-component><br>
 *     <span>Event log: {{$ctrl.eventLog}}</span>
 *   </file>
 *   <file name="child.html">
      <button ng-click="$ctrl.fireEvent()">Fire custom event</button>
 *   </file>
 *   <file name="index.html">
 *     <main></main>
 *   </file>
 * </example>
 */

var $compileMinErr = minErr('$compile');

function UNINITIALIZED_VALUE() {}
var _UNINITIALIZED_VALUE = new UNINITIALIZED_VALUE();

/**
 * @ngdoc provider
 * @name $compileProvider
 *
 * @description
 */
$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider'];
/** @this */
function $CompileProvider($provide, $$sanitizeUriProvider) {
  var hasDirectives = {},
      Suffix = 'Directive',
      COMMENT_DIRECTIVE_REGEXP = /^\s*directive:\s*([\w-]+)\s+(.*)$/,
      CLASS_DIRECTIVE_REGEXP = /(([\w-]+)(?::([^;]+))?;?)/,
      ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'),
      REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/;

  // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
  // The assumption is that future DOM event attribute names will begin with
  // 'on' and be composed of only English letters.
  var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
  var bindingCache = createMap();

  function parseIsolateBindings(scope, directiveName, isController) {
    var LOCAL_REGEXP = /^([@&]|[=<](\*?))(\??)\s*([\w$]*)$/;

    var bindings = createMap();

    forEach(scope, function(definition, scopeName) {
      definition = definition.trim();

      if (definition in bindingCache) {
        bindings[scopeName] = bindingCache[definition];
        return;
      }
      var match = definition.match(LOCAL_REGEXP);

      if (!match) {
        throw $compileMinErr('iscp',
            'Invalid {3} for directive \'{0}\'.' +
            ' Definition: {... {1}: \'{2}\' ...}',
            directiveName, scopeName, definition,
            (isController ? 'controller bindings definition' :
            'isolate scope definition'));
      }

      bindings[scopeName] = {
        mode: match[1][0],
        collection: match[2] === '*',
        optional: match[3] === '?',
        attrName: match[4] || scopeName
      };
      if (match[4]) {
        bindingCache[definition] = bindings[scopeName];
      }
    });

    return bindings;
  }

  function parseDirectiveBindings(directive, directiveName) {
    var bindings = {
      isolateScope: null,
      bindToController: null
    };
    if (isObject(directive.scope)) {
      if (directive.bindToController === true) {
        bindings.bindToController = parseIsolateBindings(directive.scope,
                                                         directiveName, true);
        bindings.isolateScope = {};
      } else {
        bindings.isolateScope = parseIsolateBindings(directive.scope,
                                                     directiveName, false);
      }
    }
    if (isObject(directive.bindToController)) {
      bindings.bindToController =
          parseIsolateBindings(directive.bindToController, directiveName, true);
    }
    if (bindings.bindToController && !directive.controller) {
      // There is no controller
      throw $compileMinErr('noctrl',
            'Cannot bind to controller without directive \'{0}\'s controller.',
            directiveName);
    }
    return bindings;
  }

  function assertValidDirectiveName(name) {
    var letter = name.charAt(0);
    if (!letter || letter !== lowercase(letter)) {
      throw $compileMinErr('baddir', 'Directive/Component name \'{0}\' is invalid. The first character must be a lowercase letter', name);
    }
    if (name !== name.trim()) {
      throw $compileMinErr('baddir',
            'Directive/Component name \'{0}\' is invalid. The name should not contain leading or trailing whitespaces',
            name);
    }
  }

  function getDirectiveRequire(directive) {
    var require = directive.require || (directive.controller && directive.name);

    if (!isArray(require) && isObject(require)) {
      forEach(require, function(value, key) {
        var match = value.match(REQUIRE_PREFIX_REGEXP);
        var name = value.substring(match[0].length);
        if (!name) require[key] = match[0] + key;
      });
    }

    return require;
  }

  function getDirectiveRestrict(restrict, name) {
    if (restrict && !(isString(restrict) && /[EACM]/.test(restrict))) {
      throw $compileMinErr('badrestrict',
          'Restrict property \'{0}\' of directive \'{1}\' is invalid',
          restrict,
          name);
    }

    return restrict || 'EA';
  }

  /**
   * @ngdoc method
   * @name $compileProvider#directive
   * @kind function
   *
   * @description
   * Register a new directive with the compiler.
   *
   * @param {string|Object} name Name of the directive in camel-case (i.e. `ngBind` which will match
   *    as `ng-bind`), or an object map of directives where the keys are the names and the values
   *    are the factories.
   * @param {Function|Array} directiveFactory An injectable directive factory function. See the
   *    {@link guide/directive directive guide} and the {@link $compile compile API} for more info.
   * @returns {ng.$compileProvider} Self for chaining.
   */
  this.directive = function registerDirective(name, directiveFactory) {
    assertArg(name, 'name');
    assertNotHasOwnProperty(name, 'directive');
    if (isString(name)) {
      assertValidDirectiveName(name);
      assertArg(directiveFactory, 'directiveFactory');
      if (!hasDirectives.hasOwnProperty(name)) {
        hasDirectives[name] = [];
        $provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
          function($injector, $exceptionHandler) {
            var directives = [];
            forEach(hasDirectives[name], function(directiveFactory, index) {
              try {
                var directive = $injector.invoke(directiveFactory);
                if (isFunction(directive)) {
                  directive = { compile: valueFn(directive) };
                } else if (!directive.compile && directive.link) {
                  directive.compile = valueFn(directive.link);
                }
                directive.priority = directive.priority || 0;
                directive.index = index;
                directive.name = directive.name || name;
                directive.require = getDirectiveRequire(directive);
                directive.restrict = getDirectiveRestrict(directive.restrict, name);
                directive.$$moduleName = directiveFactory.$$moduleName;
                directives.push(directive);
              } catch (e) {
                $exceptionHandler(e);
              }
            });
            return directives;
          }]);
      }
      hasDirectives[name].push(directiveFactory);
    } else {
      forEach(name, reverseParams(registerDirective));
    }
    return this;
  };

  /**
   * @ngdoc method
   * @name $compileProvider#component
   * @module ng
   * @param {string|Object} name Name of the component in camelCase (i.e. `myComp` which will match `<my-comp>`),
   *    or an object map of components where the keys are the names and the values are the component definition objects.
   * @param {Object} options Component definition object (a simplified
   *    {@link ng.$compile#directive-definition-object directive definition object}),
   *    with the following properties (all optional):
   *
   *    - `controller` – `{(string|function()=}` – controller constructor function that should be
   *      associated with newly created scope or the name of a {@link ng.$compile#-controller-
   *      registered controller} if passed as a string. An empty `noop` function by default.
   *    - `controllerAs` – `{string=}` – identifier name for to reference the controller in the component's scope.
   *      If present, the controller will be published to scope under the `controllerAs` name.
   *      If not present, this will default to be `$ctrl`.
   *    - `template` – `{string=|function()=}` – html template as a string or a function that
   *      returns an html template as a string which should be used as the contents of this component.
   *      Empty string by default.
   *
   *      If `template` is a function, then it is {@link auto.$injector#invoke injected} with
   *      the following locals:
   *
   *      - `$element` - Current element
   *      - `$attrs` - Current attributes object for the element
   *
   *    - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html
   *      template that should be used  as the contents of this component.
   *
   *      If `templateUrl` is a function, then it is {@link auto.$injector#invoke injected} with
   *      the following locals:
   *
   *      - `$element` - Current element
   *      - `$attrs` - Current attributes object for the element
   *
   *    - `bindings` – `{object=}` – defines bindings between DOM attributes and component properties.
   *      Component properties are always bound to the component controller and not to the scope.
   *      See {@link ng.$compile#-bindtocontroller- `bindToController`}.
   *    - `transclude` – `{boolean=}` – whether {@link $compile#transclusion content transclusion} is enabled.
   *      Disabled by default.
   *    - `require` - `{Object<string, string>=}` - requires the controllers of other directives and binds them to
   *      this component's controller. The object keys specify the property names under which the required
   *      controllers (object values) will be bound. See {@link ng.$compile#-require- `require`}.
   *    - `$...` – additional properties to attach to the directive factory function and the controller
   *      constructor function. (This is used by the component router to annotate)
   *
   * @returns {ng.$compileProvider} the compile provider itself, for chaining of function calls.
   * @description
   * Register a **component definition** with the compiler. This is a shorthand for registering a special
   * type of directive, which represents a self-contained UI component in your application. Such components
   * are always isolated (i.e. `scope: {}`) and are always restricted to elements (i.e. `restrict: 'E'`).
   *
   * Component definitions are very simple and do not require as much configuration as defining general
   * directives. Component definitions usually consist only of a template and a controller backing it.
   *
   * In order to make the definition easier, components enforce best practices like use of `controllerAs`,
   * `bindToController`. They always have **isolate scope** and are restricted to elements.
   *
   * Here are a few examples of how you would usually define components:
   *
   * ```js
   *   var myMod = angular.module(...);
   *   myMod.component('myComp', {
   *     template: '<div>My name is {{$ctrl.name}}</div>',
   *     controller: function() {
   *       this.name = 'shahar';
   *     }
   *   });
   *
   *   myMod.component('myComp', {
   *     template: '<div>My name is {{$ctrl.name}}</div>',
   *     bindings: {name: '@'}
   *   });
   *
   *   myMod.component('myComp', {
   *     templateUrl: 'views/my-comp.html',
   *     controller: 'MyCtrl',
   *     controllerAs: 'ctrl',
   *     bindings: {name: '@'}
   *   });
   *
   * ```
   * For more examples, and an in-depth guide, see the {@link guide/component component guide}.
   *
   * <br />
   * See also {@link ng.$compileProvider#directive $compileProvider.directive()}.
   */
  this.component = function registerComponent(name, options) {
    if (!isString(name)) {
      forEach(name, reverseParams(bind(this, registerComponent)));
      return this;
    }

    var controller = options.controller || function() {};

    function factory($injector) {
      function makeInjectable(fn) {
        if (isFunction(fn) || isArray(fn)) {
          return /** @this */ function(tElement, tAttrs) {
            return $injector.invoke(fn, this, {$element: tElement, $attrs: tAttrs});
          };
        } else {
          return fn;
        }
      }

      var template = (!options.template && !options.templateUrl ? '' : options.template);
      var ddo = {
        controller: controller,
        controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl',
        template: makeInjectable(template),
        templateUrl: makeInjectable(options.templateUrl),
        transclude: options.transclude,
        scope: {},
        bindToController: options.bindings || {},
        restrict: 'E',
        require: options.require
      };

      // Copy annotations (starting with $) over to the DDO
      forEach(options, function(val, key) {
        if (key.charAt(0) === '$') ddo[key] = val;
      });

      return ddo;
    }

    // TODO(pete) remove the following `forEach` before we release 1.6.0
    // The component-router@0.2.0 looks for the annotations on the controller constructor
    // Nothing in AngularJS looks for annotations on the factory function but we can't remove
    // it from 1.5.x yet.

    // Copy any annotation properties (starting with $) over to the factory and controller constructor functions
    // These could be used by libraries such as the new component router
    forEach(options, function(val, key) {
      if (key.charAt(0) === '$') {
        factory[key] = val;
        // Don't try to copy over annotations to named controller
        if (isFunction(controller)) controller[key] = val;
      }
    });

    factory.$inject = ['$injector'];

    return this.directive(name, factory);
  };


  /**
   * @ngdoc method
   * @name $compileProvider#aHrefSanitizationTrustedUrlList
   * @kind function
   *
   * @description
   * Retrieves or overrides the default regular expression that is used for determining trusted safe
   * urls during a[href] sanitization.
   *
   * The sanitization is a security measure aimed at preventing XSS attacks via html links.
   *
   * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
   * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationTrustedUrlList`
   * regular expression. If a match is found, the original url is written into the dom. Otherwise,
   * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
   *
   * @param {RegExp=} regexp New regexp to trust urls with.
   * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
   *    chaining otherwise.
   */
  this.aHrefSanitizationTrustedUrlList = function(regexp) {
    if (isDefined(regexp)) {
      $$sanitizeUriProvider.aHrefSanitizationTrustedUrlList(regexp);
      return this;
    } else {
      return $$sanitizeUriProvider.aHrefSanitizationTrustedUrlList();
    }
  };


  /**
   * @ngdoc method
   * @name $compileProvider#aHrefSanitizationWhitelist
   * @kind function
   *
   * @deprecated
   * sinceVersion="1.8.1"
   *
   * This method is deprecated. Use {@link $compileProvider#aHrefSanitizationTrustedUrlList
   * aHrefSanitizationTrustedUrlList} instead.
   */
  Object.defineProperty(this, 'aHrefSanitizationWhitelist', {
    get: function() {
      return this.aHrefSanitizationTrustedUrlList;
    },
    set: function(value) {
      this.aHrefSanitizationTrustedUrlList = value;
    }
  });


  /**
   * @ngdoc method
   * @name $compileProvider#imgSrcSanitizationTrustedUrlList
   * @kind function
   *
   * @description
   * Retrieves or overrides the default regular expression that is used for determining trusted safe
   * urls during img[src] sanitization.
   *
   * The sanitization is a security measure aimed at prevent XSS attacks via html links.
   *
   * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
   * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationTrustedUrlList`
   * regular expression. If a match is found, the original url is written into the dom. Otherwise,
   * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
   *
   * @param {RegExp=} regexp New regexp to trust urls with.
   * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
   *    chaining otherwise.
   */
  this.imgSrcSanitizationTrustedUrlList = function(regexp) {
    if (isDefined(regexp)) {
      $$sanitizeUriProvider.imgSrcSanitizationTrustedUrlList(regexp);
      return this;
    } else {
      return $$sanitizeUriProvider.imgSrcSanitizationTrustedUrlList();
    }
  };


  /**
   * @ngdoc method
   * @name $compileProvider#imgSrcSanitizationWhitelist
   * @kind function
   *
   * @deprecated
   * sinceVersion="1.8.1"
   *
   * This method is deprecated. Use {@link $compileProvider#imgSrcSanitizationTrustedUrlList
   * imgSrcSanitizationTrustedUrlList} instead.
   */
  Object.defineProperty(this, 'imgSrcSanitizationWhitelist', {
    get: function() {
      return this.imgSrcSanitizationTrustedUrlList;
    },
    set: function(value) {
      this.imgSrcSanitizationTrustedUrlList = value;
    }
  });

  /**
   * @ngdoc method
   * @name  $compileProvider#debugInfoEnabled
   *
   * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the
   * current debugInfoEnabled state
   * @returns {*} current value if used as getter or itself (chaining) if used as setter
   *
   * @kind function
   *
   * @description
   * Call this method to enable/disable various debug runtime information in the compiler such as adding
   * binding information and a reference to the current scope on to DOM elements.
   * If enabled, the compiler will add the following to DOM elements that have been bound to the scope
   * * `ng-binding` CSS class
   * * `ng-scope` and `ng-isolated-scope` CSS classes
   * * `$binding` data property containing an array of the binding expressions
   * * Data properties used by the {@link angular.element#methods `scope()`/`isolateScope()` methods} to return
   *   the element's scope.
   * * Placeholder comments will contain information about what directive and binding caused the placeholder.
   *   E.g. `<!-- ngIf: shouldShow() -->`.
   *
   * You may want to disable this in production for a significant performance boost. See
   * {@link guide/production#disabling-debug-data Disabling Debug Data} for more.
   *
   * The default value is true.
   */
  var debugInfoEnabled = true;
  this.debugInfoEnabled = function(enabled) {
    if (isDefined(enabled)) {
      debugInfoEnabled = enabled;
      return this;
    }
    return debugInfoEnabled;
  };

  /**
   * @ngdoc method
   * @name  $compileProvider#strictComponentBindingsEnabled
   *
   * @param {boolean=} enabled update the strictComponentBindingsEnabled state if provided,
   * otherwise return the current strictComponentBindingsEnabled state.
   * @returns {*} current value if used as getter or itself (chaining) if used as setter
   *
   * @kind function
   *
   * @description
   * Call this method to enable / disable the strict component bindings check. If enabled, the
   * compiler will enforce that all scope / controller bindings of a
   * {@link $compileProvider#directive directive} / {@link $compileProvider#component component}
   * that are not set as optional with `?`, must be provided when the directive is instantiated.
   * If not provided, the compiler will throw the
   * {@link error/$compile/missingattr $compile:missingattr error}.
   *
   * The default value is false.
   */
  var strictComponentBindingsEnabled = false;
  this.strictComponentBindingsEnabled = function(enabled) {
    if (isDefined(enabled)) {
      strictComponentBindingsEnabled = enabled;
      return this;
    }
    return strictComponentBindingsEnabled;
  };

  var TTL = 10;
  /**
   * @ngdoc method
   * @name $compileProvider#onChangesTtl
   * @description
   *
   * Sets the number of times `$onChanges` hooks can trigger new changes before giving up and
   * assuming that the model is unstable.
   *
   * The current default is 10 iterations.
   *
   * In complex applications it's possible that dependencies between `$onChanges` hooks and bindings will result
   * in several iterations of calls to these hooks. However if an application needs more than the default 10
   * iterations to stabilize then you should investigate what is causing the model to continuously change during
   * the `$onChanges` hook execution.
   *
   * Increasing the TTL could have performance implications, so you should not change it without proper justification.
   *
   * @param {number} limit The number of `$onChanges` hook iterations.
   * @returns {number|object} the current limit (or `this` if called as a setter for chaining)
   */
  this.onChangesTtl = function(value) {
    if (arguments.length) {
      TTL = value;
      return this;
    }
    return TTL;
  };

  var commentDirectivesEnabledConfig = true;
  /**
   * @ngdoc method
   * @name $compileProvider#commentDirectivesEnabled
   * @description
   *
   * It indicates to the compiler
   * whether or not directives on comments should be compiled.
   * Defaults to `true`.
   *
   * Calling this function with false disables the compilation of directives
   * on comments for the whole application.
   * This results in a compilation performance gain,
   * as the compiler doesn't have to check comments when looking for directives.
   * This should however only be used if you are sure that no comment directives are used in
   * the application (including any 3rd party directives).
   *
   * @param {boolean} enabled `false` if the compiler may ignore directives on comments
   * @returns {boolean|object} the current value (or `this` if called as a setter for chaining)
   */
  this.commentDirectivesEnabled = function(value) {
    if (arguments.length) {
      commentDirectivesEnabledConfig = value;
      return this;
    }
    return commentDirectivesEnabledConfig;
  };


  var cssClassDirectivesEnabledConfig = true;
  /**
   * @ngdoc method
   * @name $compileProvider#cssClassDirectivesEnabled
   * @description
   *
   * It indicates to the compiler
   * whether or not directives on element classes should be compiled.
   * Defaults to `true`.
   *
   * Calling this function with false disables the compilation of directives
   * on element classes for the whole application.
   * This results in a compilation performance gain,
   * as the compiler doesn't have to check element classes when looking for directives.
   * This should however only be used if you are sure that no class directives are used in
   * the application (including any 3rd party directives).
   *
   * @param {boolean} enabled `false` if the compiler may ignore directives on element classes
   * @returns {boolean|object} the current value (or `this` if called as a setter for chaining)
   */
  this.cssClassDirectivesEnabled = function(value) {
    if (arguments.length) {
      cssClassDirectivesEnabledConfig = value;
      return this;
    }
    return cssClassDirectivesEnabledConfig;
  };


  /**
   * The security context of DOM Properties.
   * @private
   */
  var PROP_CONTEXTS = createMap();

  /**
   * @ngdoc method
   * @name $compileProvider#addPropertySecurityContext
   * @description
   *
   * Defines the security context for DOM properties bound by ng-prop-*.
   *
   * @param {string} elementName The element name or '*' to match any element.
   * @param {string} propertyName The DOM property name.
   * @param {string} ctx The {@link $sce} security context in which this value is safe for use, e.g. `$sce.URL`
   * @returns {object} `this` for chaining
   */
  this.addPropertySecurityContext = function(elementName, propertyName, ctx) {
    var key = (elementName.toLowerCase() + '|' + propertyName.toLowerCase());

    if (key in PROP_CONTEXTS && PROP_CONTEXTS[key] !== ctx) {
      throw $compileMinErr('ctxoverride', 'Property context \'{0}.{1}\' already set to \'{2}\', cannot override to \'{3}\'.', elementName, propertyName, PROP_CONTEXTS[key], ctx);
    }

    PROP_CONTEXTS[key] = ctx;
    return this;
  };

  /* Default property contexts.
   *
   * Copy of https://github.com/angular/angular/blob/6.0.6/packages/compiler/src/schema/dom_security_schema.ts#L31-L58
   * Changing:
   * - SecurityContext.* => SCE_CONTEXTS/$sce.*
   * - STYLE => CSS
   * - various URL => MEDIA_URL
   * - *|formAction, form|action URL => RESOURCE_URL (like the attribute)
   */
  (function registerNativePropertyContexts() {
    function registerContext(ctx, values) {
      forEach(values, function(v) { PROP_CONTEXTS[v.toLowerCase()] = ctx; });
    }

    registerContext(SCE_CONTEXTS.HTML, [
      'iframe|srcdoc',
      '*|innerHTML',
      '*|outerHTML'
    ]);
    registerContext(SCE_CONTEXTS.CSS, ['*|style']);
    registerContext(SCE_CONTEXTS.URL, [
      'area|href',       'area|ping',
      'a|href',          'a|ping',
      'blockquote|cite',
      'body|background',
      'del|cite',
      'input|src',
      'ins|cite',
      'q|cite'
    ]);
    registerContext(SCE_CONTEXTS.MEDIA_URL, [
      'audio|src',
      'img|src',    'img|srcset',
      'source|src', 'source|srcset',
      'track|src',
      'video|src',  'video|poster'
    ]);
    registerContext(SCE_CONTEXTS.RESOURCE_URL, [
      '*|formAction',
      'applet|code',      'applet|codebase',
      'base|href',
      'embed|src',
      'frame|src',
      'form|action',
      'head|profile',
      'html|manifest',
      'iframe|src',
      'link|href',
      'media|src',
      'object|codebase',  'object|data',
      'script|src'
    ]);
  })();


  this.$get = [
            '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse',
            '$controller', '$rootScope', '$sce', '$animate',
    function($injector,   $interpolate,   $exceptionHandler,   $templateRequest,   $parse,
             $controller,   $rootScope,   $sce,   $animate) {

    var SIMPLE_ATTR_NAME = /^\w/;
    var specialAttrHolder = window.document.createElement('div');


    var commentDirectivesEnabled = commentDirectivesEnabledConfig;
    var cssClassDirectivesEnabled = cssClassDirectivesEnabledConfig;


    var onChangesTtl = TTL;
    // The onChanges hooks should all be run together in a single digest
    // When changes occur, the call to trigger their hooks will be added to this queue
    var onChangesQueue;

    // This function is called in a $$postDigest to trigger all the onChanges hooks in a single digest
    function flushOnChangesQueue() {
      try {
        if (!(--onChangesTtl)) {
          // We have hit the TTL limit so reset everything
          onChangesQueue = undefined;
          throw $compileMinErr('infchng', '{0} $onChanges() iterations reached. Aborting!\n', TTL);
        }
        // We must run this hook in an apply since the $$postDigest runs outside apply
        $rootScope.$apply(function() {
          for (var i = 0, ii = onChangesQueue.length; i < ii; ++i) {
            try {
              onChangesQueue[i]();
            } catch (e) {
              $exceptionHandler(e);
            }
          }
          // Reset the queue to trigger a new schedule next time there is a change
          onChangesQueue = undefined;
        });
      } finally {
        onChangesTtl++;
      }
    }


    function sanitizeSrcset(value, invokeType) {
      if (!value) {
        return value;
      }
      if (!isString(value)) {
        throw $compileMinErr('srcset', 'Can\'t pass trusted values to `{0}`: "{1}"', invokeType, value.toString());
      }

      // Such values are a bit too complex to handle automatically inside $sce.
      // Instead, we sanitize each of the URIs individually, which works, even dynamically.

      // It's not possible to work around this using `$sce.trustAsMediaUrl`.
      // If you want to programmatically set explicitly trusted unsafe URLs, you should use
      // `$sce.trustAsHtml` on the whole `img` tag and inject it into the DOM using the
      // `ng-bind-html` directive.

      var result = '';

      //                (   999x   ,|   999w   ,|   ,|,   )
      var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/;

      // only numbers and descriptors allowed
      var descriptorPattern = /^[a-zA-Z,\.\d\s]{0,100}$/;

      /**
       * `/(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/` - The purpose of this regular expression is to split the
       * srcset into a tuple of URI and descriptor, which are then sanitized accordingly. It includes spaces,
       * although they don't serve a clear purpose and may decrease the overall performance of the expression.
       * To optimize this, we replace multiple spaces with a single space before processing with the `srcPattern`.
       * First, we check for spaces because it's not part of the same pattern.
       * References:
       * - https://html.spec.whatwg.org/multipage/images.html#srcset-attribute
       * - https://html.spec.whatwg.org/multipage/images.html#image-candidate-string
       */
      var trimmedSrcset = trim(value).replace(/\s+/g, ' ');

      var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/;

      // split srcset into tuple of uri and descriptor except for the last item
      var rawUris = trimmedSrcset.split(pattern);

      function validDescriptor(descrpt) {
        if (descrpt && !descriptorPattern.test(descrpt)) return ', ' + $sce.getTrustedMediaUrl(descrpt);
        return descrpt;
      }

      // for each tuples
      var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
      for (var i = 0; i < nbrUrisWith2parts; i++) {
        var innerIdx = i * 2;
        // sanitize the uri
        result += $sce.getTrustedMediaUrl(trim(rawUris[innerIdx]));
        // add the descriptor
        var descriptor = trim(rawUris[innerIdx + 1]);
        // var descriptor = trim(rawUris[innerIdx + 1]);

        result += ' ' + validDescriptor(descriptor);
      }

      // split the last item into uri and descriptor
      var lastTuple = trim(rawUris[i * 2]).split(/\s/);

      // sanitize the last uri
      result += $sce.getTrustedMediaUrl(trim(lastTuple[0]));

      // and add the last descriptor if any
      if (lastTuple.length === 2) {
        var descriptor = trim(lastTuple[1]);
        result += (' ' + validDescriptor(descriptor));
      }
      return result;
    }


    function Attributes(element, attributesToCopy) {
      if (attributesToCopy) {
        var keys = Object.keys(attributesToCopy);
        var i, l, key;

        for (i = 0, l = keys.length; i < l; i++) {
          key = keys[i];
          this[key] = attributesToCopy[key];
        }
      } else {
        this.$attr = {};
      }

      this.$$element = element;
    }

    Attributes.prototype = {
      /**
       * @ngdoc method
       * @name $compile.directive.Attributes#$normalize
       * @kind function
       *
       * @description
       * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or
       * `data-`) to its normalized, camelCase form.
       *
       * Also there is special case for Moz prefix starting with upper case letter.
       *
       * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
       *
       * @param {string} name Name to normalize
       */
      $normalize: directiveNormalize,


      /**
       * @ngdoc method
       * @name $compile.directive.Attributes#$addClass
       * @kind function
       *
       * @description
       * Adds the CSS class value specified by the classVal parameter to the element. If animations
       * are enabled then an animation will be triggered for the class addition.
       *
       * @param {string} classVal The className value that will be added to the element
       */
      $addClass: function(classVal) {
        if (classVal && classVal.length > 0) {
          $animate.addClass(this.$$element, classVal);
        }
      },

      /**
       * @ngdoc method
       * @name $compile.directive.Attributes#$removeClass
       * @kind function
       *
       * @description
       * Removes the CSS class value specified by the classVal parameter from the element. If
       * animations are enabled then an animation will be triggered for the class removal.
       *
       * @param {string} classVal The className value that will be removed from the element
       */
      $removeClass: function(classVal) {
        if (classVal && classVal.length > 0) {
          $animate.removeClass(this.$$element, classVal);
        }
      },

      /**
       * @ngdoc method
       * @name $compile.directive.Attributes#$updateClass
       * @kind function
       *
       * @description
       * Adds and removes the appropriate CSS class values to the element based on the difference
       * between the new and old CSS class values (specified as newClasses and oldClasses).
       *
       * @param {string} newClasses The current CSS className value
       * @param {string} oldClasses The former CSS className value
       */
      $updateClass: function(newClasses, oldClasses) {
        var toAdd = tokenDifference(newClasses, oldClasses);
        if (toAdd && toAdd.length) {
          $animate.addClass(this.$$element, toAdd);
        }

        var toRemove = tokenDifference(oldClasses, newClasses);
        if (toRemove && toRemove.length) {
          $animate.removeClass(this.$$element, toRemove);
        }
      },

      /**
       * Set a normalized attribute on the element in a way such that all directives
       * can share the attribute. This function properly handles boolean attributes.
       * @param {string} key Normalized key. (ie ngAttribute)
       * @param {string|boolean} value The value to set. If `null` attribute will be deleted.
       * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute.
       *     Defaults to true.
       * @param {string=} attrName Optional none normalized name. Defaults to key.
       */
      $set: function(key, value, writeAttr, attrName) {
        // TODO: decide whether or not to throw an error if "class"
        // is set through this function since it may cause $updateClass to
        // become unstable.

        var node = this.$$element[0],
            booleanKey = getBooleanAttrName(node, key),
            aliasedKey = getAliasedAttrName(key),
            observer = key,
            nodeName;

        if (booleanKey) {
          this.$$element.prop(key, value);
          attrName = booleanKey;
        } else if (aliasedKey) {
          this[aliasedKey] = value;
          observer = aliasedKey;
        }

        this[key] = value;

        // translate normalized key to actual key
        if (attrName) {
          this.$attr[key] = attrName;
        } else {
          attrName = this.$attr[key];
          if (!attrName) {
            this.$attr[key] = attrName = snake_case(key, '-');
          }
        }

        nodeName = nodeName_(this.$$element);

        // Sanitize img[srcset] values.
        var isImg = (nodeName === 'source' && nodeName_(node.parentNode) === 'picture' || nodeName === 'img');
        if (isImg && key === 'srcset') {
          this[key] = value = sanitizeSrcset(value, '$set(\'srcset\', value)');
        }
        
        /**
         * svg's image element contains lots of attributes, out of which href, xlink:href and ngHref
         * are the only ones that are allowed to be sanitized. The rest of the attributes are not
         * allowed to be sanitized as they are not URLs.
         * see https://www.w3.org/TR/SVG/embedded.html#ImageElement
         * see https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/image#attributes
         */
        var isSvgImage = (nodeName === 'image' && nodeName_(node.parentNode) === 'svg' && (key === 'href' || key === 'xlinkHref' || key === 'ngHref'));
        if (isSvgImage) {
          this[key] = value = $sce.getTrustedMediaUrl(value);
        }

        if (writeAttr !== false) {
          if (value === null || isUndefined(value)) {
            this.$$element.removeAttr(attrName);
          } else {
            if (SIMPLE_ATTR_NAME.test(attrName)) {
              // jQuery skips special boolean attrs treatment in XML nodes for
              // historical reasons and hence AngularJS cannot freely call
              // `.attr(attrName, false) with such attributes. To avoid issues
              // in XHTML, call `removeAttr` in such cases instead.
              // See https://github.com/jquery/jquery/issues/4249
              if (booleanKey && value === false) {
                this.$$element.removeAttr(attrName);
              } else {
                this.$$element.attr(attrName, value);
              }
            } else {
              setSpecialAttr(this.$$element[0], attrName, value);
            }
          }
        }

        // fire observers
        var $$observers = this.$$observers;
        if ($$observers) {
          forEach($$observers[observer], function(fn) {
            try {
              fn(value);
            } catch (e) {
              $exceptionHandler(e);
            }
          });
        }
      },


      /**
       * @ngdoc method
       * @name $compile.directive.Attributes#$observe
       * @kind function
       *
       * @description
       * Observes an interpolated attribute.
       *
       * The observer function will be invoked once during the next `$digest` following
       * compilation. The observer is then invoked whenever the interpolated value
       * changes.
       *
       * @param {string} key Normalized key. (ie ngAttribute) .
       * @param {function(interpolatedValue)} fn Function that will be called whenever
                the interpolated value of the attribute changes.
       *        See the {@link guide/interpolation#how-text-and-attribute-bindings-work Interpolation
       *        guide} for more info.
       * @returns {function()} Returns a deregistration function for this observer.
       */
      $observe: function(key, fn) {
        var attrs = this,
            $$observers = (attrs.$$observers || (attrs.$$observers = createMap())),
            listeners = ($$observers[key] || ($$observers[key] = []));

        listeners.push(fn);
        $rootScope.$evalAsync(function() {
          if (!listeners.$$inter && attrs.hasOwnProperty(key) && !isUndefined(attrs[key])) {
            // no one registered attribute interpolation function, so lets call it manually
            fn(attrs[key]);
          }
        });

        return function() {
          arrayRemove(listeners, fn);
        };
      }
    };

    function setSpecialAttr(element, attrName, value) {
      // Attributes names that do not start with letters (such as `(click)`) cannot be set using `setAttribute`
      // so we have to jump through some hoops to get such an attribute
      // https://github.com/angular/angular.js/pull/13318
      specialAttrHolder.innerHTML = '<span ' + attrName + '>';
      var attributes = specialAttrHolder.firstChild.attributes;
      var attribute = attributes[0];
      // We have to remove the attribute from its container element before we can add it to the destination element
      attributes.removeNamedItem(attribute.name);
      attribute.value = value;
      element.attributes.setNamedItem(attribute);
    }

    function safeAddClass($element, className) {
      try {
        $element.addClass(className);
      } catch (e) {
        // ignore, since it means that we are trying to set class on
        // SVG element, where class name is read-only.
      }
    }


    var startSymbol = $interpolate.startSymbol(),
        endSymbol = $interpolate.endSymbol(),
        denormalizeTemplate = (startSymbol === '{{' && endSymbol  === '}}')
            ? identity
            : function denormalizeTemplate(template) {
              return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
        },
        NG_PREFIX_BINDING = /^ng(Attr|Prop|On)([A-Z].*)$/;
    var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/;

    compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) {
      var bindings = $element.data('$binding') || [];

      if (isArray(binding)) {
        bindings = bindings.concat(binding);
      } else {
        bindings.push(binding);
      }

      $element.data('$binding', bindings);
    } : noop;

    compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) {
      safeAddClass($element, 'ng-binding');
    } : noop;

    compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) {
      var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope';
      $element.data(dataName, scope);
    } : noop;

    compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) {
      safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope');
    } : noop;

    compile.$$createComment = function(directiveName, comment) {
      var content = '';
      if (debugInfoEnabled) {
        content = ' ' + (directiveName || '') + ': ';
        if (comment) content += comment + ' ';
      }
      return window.document.createComment(content);
    };

    return compile;

    //================================

    function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective,
                        previousCompileContext) {
      if (!($compileNodes instanceof jqLite)) {
        // jquery always rewraps, whereas we need to preserve the original selector so that we can
        // modify it.
        $compileNodes = jqLite($compileNodes);
      }
      var compositeLinkFn =
              compileNodes($compileNodes, transcludeFn, $compileNodes,
                           maxPriority, ignoreDirective, previousCompileContext);
      compile.$$addScopeClass($compileNodes);
      var namespace = null;
      return function publicLinkFn(scope, cloneConnectFn, options) {
        if (!$compileNodes) {
          throw $compileMinErr('multilink', 'This element has already been linked.');
        }
        assertArg(scope, 'scope');

        if (previousCompileContext && previousCompileContext.needsNewScope) {
          // A parent directive did a replace and a directive on this element asked
          // for transclusion, which caused us to lose a layer of element on which
          // we could hold the new transclusion scope, so we will create it manually
          // here.
          scope = scope.$parent.$new();
        }

        options = options || {};
        var parentBoundTranscludeFn = options.parentBoundTranscludeFn,
          transcludeControllers = options.transcludeControllers,
          futureParentElement = options.futureParentElement;

        // When `parentBoundTranscludeFn` is passed, it is a
        // `controllersBoundTransclude` function (it was previously passed
        // as `transclude` to directive.link) so we must unwrap it to get
        // its `boundTranscludeFn`
        if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) {
          parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude;
        }

        if (!namespace) {
          namespace = detectNamespaceForChildElements(futureParentElement);
        }
        var $linkNode;
        if (namespace !== 'html') {
          // When using a directive with replace:true and templateUrl the $compileNodes
          // (or a child element inside of them)
          // might change, so we need to recreate the namespace adapted compileNodes
          // for call to the link function.
          // Note: This will already clone the nodes...
          $linkNode = jqLite(
            wrapTemplate(namespace, jqLite('<div></div>').append($compileNodes).html())
          );
        } else if (cloneConnectFn) {
          // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
          // and sometimes changes the structure of the DOM.
          $linkNode = JQLitePrototype.clone.call($compileNodes);
        } else {
          $linkNode = $compileNodes;
        }

        if (transcludeControllers) {
          for (var controllerName in transcludeControllers) {
            $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance);
          }
        }

        compile.$$addScopeInfo($linkNode, scope);

        if (cloneConnectFn) cloneConnectFn($linkNode, scope);
        if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn);

        if (!cloneConnectFn) {
          $compileNodes = compositeLinkFn = null;
        }
        return $linkNode;
      };
    }

    function detectNamespaceForChildElements(parentElement) {
      // TODO: Make this detect MathML as well...
      var node = parentElement && parentElement[0];
      if (!node) {
        return 'html';
      } else {
        return nodeName_(node) !== 'foreignobject' && toString.call(node).match(/SVG/) ? 'svg' : 'html';
      }
    }

    /**
     * Compile function matches each node in nodeList against the directives. Once all directives
     * for a particular node are collected their compile functions are executed. The compile
     * functions return values - the linking functions - are combined into a composite linking
     * function, which is the a linking function for the node.
     *
     * @param {NodeList} nodeList an array of nodes or NodeList to compile
     * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
     *        scope argument is auto-generated to the new child of the transcluded parent scope.
     * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then
     *        the rootElement must be set the jqLite collection of the compile root. This is
     *        needed so that the jqLite collection items can be replaced with widgets.
     * @param {number=} maxPriority Max directive priority.
     * @returns {Function} A composite linking function of all of the matched directives or null.
     */
    function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective,
                            previousCompileContext) {
      var linkFns = [],
          // `nodeList` can be either an element's `.childNodes` (live NodeList)
          // or a jqLite/jQuery collection or an array
          notLiveList = isArray(nodeList) || (nodeList instanceof jqLite),
          attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound;


      for (var i = 0; i < nodeList.length; i++) {
        attrs = new Attributes();

        // Support: IE 11 only
        // Workaround for #11781 and #14924
        if (msie === 11) {
          mergeConsecutiveTextNodes(nodeList, i, notLiveList);
        }

        // We must always refer to `nodeList[i]` hereafter,
        // since the nodes can be replaced underneath us.
        directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined,
                                        ignoreDirective);

        nodeLinkFn = (directives.length)
            ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement,
                                      null, [], [], previousCompileContext)
            : null;

        if (nodeLinkFn && nodeLinkFn.scope) {
          compile.$$addScopeClass(attrs.$$element);
        }

        childLinkFn = (nodeLinkFn && nodeLinkFn.terminal ||
                      !(childNodes = nodeList[i].childNodes) ||
                      !childNodes.length)
            ? null
            : compileNodes(childNodes,
                 nodeLinkFn ? (
                  (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement)
                     && nodeLinkFn.transclude) : transcludeFn);

        if (nodeLinkFn || childLinkFn) {
          linkFns.push(i, nodeLinkFn, childLinkFn);
          linkFnFound = true;
          nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn;
        }

        //use the previous context only for the first element in the virtual group
        previousCompileContext = null;
      }

      // return a linking function if we have found anything, null otherwise
      return linkFnFound ? compositeLinkFn : null;

      function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) {
        var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn;
        var stableNodeList;


        if (nodeLinkFnFound) {
          // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our
          // offsets don't get screwed up
          var nodeListLength = nodeList.length;
          stableNodeList = new Array(nodeListLength);

          // create a sparse array by only copying the elements which have a linkFn
          for (i = 0; i < linkFns.length; i += 3) {
            idx = linkFns[i];
            stableNodeList[idx] = nodeList[idx];
          }
        } else {
          stableNodeList = nodeList;
        }

        for (i = 0, ii = linkFns.length; i < ii;) {
          node = stableNodeList[linkFns[i++]];
          nodeLinkFn = linkFns[i++];
          childLinkFn = linkFns[i++];

          if (nodeLinkFn) {
            if (nodeLinkFn.scope) {
              childScope = scope.$new();
              compile.$$addScopeInfo(jqLite(node), childScope);
            } else {
              childScope = scope;
            }

            if (nodeLinkFn.transcludeOnThisElement) {
              childBoundTranscludeFn = createBoundTranscludeFn(
                  scope, nodeLinkFn.transclude, parentBoundTranscludeFn);

            } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) {
              childBoundTranscludeFn = parentBoundTranscludeFn;

            } else if (!parentBoundTranscludeFn && transcludeFn) {
              childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn);

            } else {
              childBoundTranscludeFn = null;
            }

            nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);

          } else if (childLinkFn) {
            childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
          }
        }
      }
    }

    function mergeConsecutiveTextNodes(nodeList, idx, notLiveList) {
      var node = nodeList[idx];
      var parent = node.parentNode;
      var sibling;

      if (node.nodeType !== NODE_TYPE_TEXT) {
        return;
      }

      while (true) {
        sibling = parent ? node.nextSibling : nodeList[idx + 1];
        if (!sibling || sibling.nodeType !== NODE_TYPE_TEXT) {
          break;
        }

        node.nodeValue = node.nodeValue + sibling.nodeValue;

        if (sibling.parentNode) {
          sibling.parentNode.removeChild(sibling);
        }
        if (notLiveList && sibling === nodeList[idx + 1]) {
          nodeList.splice(idx + 1, 1);
        }
      }
    }

    function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) {
      function boundTranscludeFn(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) {

        if (!transcludedScope) {
          transcludedScope = scope.$new(false, containingScope);
          transcludedScope.$$transcluded = true;
        }

        return transcludeFn(transcludedScope, cloneFn, {
          parentBoundTranscludeFn: previousBoundTranscludeFn,
          transcludeControllers: controllers,
          futureParentElement: futureParentElement
        });
      }

      // We need  to attach the transclusion slots onto the `boundTranscludeFn`
      // so that they are available inside the `controllersBoundTransclude` function
      var boundSlots = boundTranscludeFn.$$slots = createMap();
      for (var slotName in transcludeFn.$$slots) {
        if (transcludeFn.$$slots[slotName]) {
          boundSlots[slotName] = createBoundTranscludeFn(scope, transcludeFn.$$slots[slotName], previousBoundTranscludeFn);
        } else {
          boundSlots[slotName] = null;
        }
      }

      return boundTranscludeFn;
    }

    /**
     * Looks for directives on the given node and adds them to the directive collection which is
     * sorted.
     *
     * @param node Node to search.
     * @param directives An array to which the directives are added to. This array is sorted before
     *        the function returns.
     * @param attrs The shared attrs object which is used to populate the normalized attributes.
     * @param {number=} maxPriority Max directive priority.
     */
    function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
      var nodeType = node.nodeType,
          attrsMap = attrs.$attr,
          match,
          nodeName,
          className;

      switch (nodeType) {
        case NODE_TYPE_ELEMENT: /* Element */

          nodeName = nodeName_(node);

          // use the node name: <directive>
          addDirective(directives,
              directiveNormalize(nodeName), 'E', maxPriority, ignoreDirective);

          // iterate over the attributes
          for (var attr, name, nName, value, ngPrefixMatch, nAttrs = node.attributes,
                   j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
            var attrStartName = false;
            var attrEndName = false;

            var isNgAttr = false, isNgProp = false, isNgEvent = false;
            var multiElementMatch;

            attr = nAttrs[j];
            name = attr.name;
            value = attr.value;

            nName = directiveNormalize(name.toLowerCase());

            // Support ng-attr-*, ng-prop-* and ng-on-*
            if ((ngPrefixMatch = nName.match(NG_PREFIX_BINDING))) {
              isNgAttr = ngPrefixMatch[1] === 'Attr';
              isNgProp = ngPrefixMatch[1] === 'Prop';
              isNgEvent = ngPrefixMatch[1] === 'On';

              // Normalize the non-prefixed name
              name = name.replace(PREFIX_REGEXP, '')
                .toLowerCase()
                .substr(4 + ngPrefixMatch[1].length).replace(/_(.)/g, function(match, letter) {
                  return letter.toUpperCase();
                });

            // Support *-start / *-end multi element directives
            } else if ((multiElementMatch = nName.match(MULTI_ELEMENT_DIR_RE)) && directiveIsMultiElement(multiElementMatch[1])) {
              attrStartName = name;
              attrEndName = name.substr(0, name.length - 5) + 'end';
              name = name.substr(0, name.length - 6);
            }

            if (isNgProp || isNgEvent) {
              attrs[nName] = value;
              attrsMap[nName] = attr.name;

              if (isNgProp) {
                addPropertyDirective(node, directives, nName, name);
              } else {
                addEventDirective(directives, nName, name);
              }
            } else {
              // Update nName for cases where a prefix was removed
              // NOTE: the .toLowerCase() is unnecessary and causes https://github.com/angular/angular.js/issues/16624 for ng-attr-*
              nName = directiveNormalize(name.toLowerCase());
              attrsMap[nName] = name;

              if (isNgAttr || !attrs.hasOwnProperty(nName)) {
                attrs[nName] = value;
                if (getBooleanAttrName(node, nName)) {
                  attrs[nName] = true; // presence means true
                }
              }

              addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
              addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
                            attrEndName);
            }
          }

          if (nodeName === 'input' && node.getAttribute('type') === 'hidden') {
            // Hidden input elements can have strange behaviour when navigating back to the page
            // This tells the browser not to try to cache and reinstate previous values
            node.setAttribute('autocomplete', 'off');
          }

          // use class as directive
          if (!cssClassDirectivesEnabled) break;
          className = node.className;
          if (isObject(className)) {
              // Maybe SVGAnimatedString
              className = className.animVal;
          }
          if (isString(className) && className !== '') {
            while ((match = CLASS_DIRECTIVE_REGEXP.exec(className))) {
              nName = directiveNormalize(match[2]);
              if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) {
                attrs[nName] = trim(match[3]);
              }
              className = className.substr(match.index + match[0].length);
            }
          }
          break;
        case NODE_TYPE_TEXT: /* Text Node */
          var parentTagName = node.parentNode && nodeName_(node.parentNode);
          if (
            msie && parentTagName === 'textarea' && node.nodeValue !== node.parentNode.defaultValue
          ) {
            // @see: https://security.snyk.io/vuln/SNYK-JS-ANGULAR-2949781
            // Resolve back to history XSS string interpolation of textarea, vulnerability
            break;
          }

          addTextInterpolateDirective(directives, node.nodeValue);
          break;
        case NODE_TYPE_COMMENT: /* Comment */
          if (!commentDirectivesEnabled) break;
          collectCommentDirectives(node, directives, attrs, maxPriority, ignoreDirective);
          break;
      }

      directives.sort(byPriority);
      return directives;
    }

    function collectCommentDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
      // function created because of performance, try/catch disables
      // the optimization of the whole function #14848
      try {
        var match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
        if (match) {
          var nName = directiveNormalize(match[1]);
          if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) {
            attrs[nName] = trim(match[2]);
          }
        }
      } catch (e) {
        // turns out that under some circumstances IE9 throws errors when one attempts to read
        // comment's node value.
        // Just ignore it and continue. (Can't seem to reproduce in test case.)
      }
    }

    /**
     * Given a node with a directive-start it collects all of the siblings until it finds
     * directive-end.
     * @param node
     * @param attrStart
     * @param attrEnd
     * @returns {*}
     */
    function groupScan(node, attrStart, attrEnd) {
      var nodes = [];
      var depth = 0;
      if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) {
        do {
          if (!node) {
            throw $compileMinErr('uterdir',
                      'Unterminated attribute, found \'{0}\' but no matching \'{1}\' found.',
                      attrStart, attrEnd);
          }
          if (node.nodeType === NODE_TYPE_ELEMENT) {
            if (node.hasAttribute(attrStart)) depth++;
            if (node.hasAttribute(attrEnd)) depth--;
          }
          nodes.push(node);
          node = node.nextSibling;
        } while (depth > 0);
      } else {
        nodes.push(node);
      }

      return jqLite(nodes);
    }

    /**
     * Wrapper for linking function which converts normal linking function into a grouped
     * linking function.
     * @param linkFn
     * @param attrStart
     * @param attrEnd
     * @returns {Function}
     */
    function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) {
      return function groupedElementsLink(scope, element, attrs, controllers, transcludeFn) {
        element = groupScan(element[0], attrStart, attrEnd);
        return linkFn(scope, element, attrs, controllers, transcludeFn);
      };
    }

    /**
     * A function generator that is used to support both eager and lazy compilation
     * linking function.
     * @param eager
     * @param $compileNodes
     * @param transcludeFn
     * @param maxPriority
     * @param ignoreDirective
     * @param previousCompileContext
     * @returns {Function}
     */
    function compilationGenerator(eager, $compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) {
      var compiled;

      if (eager) {
        return compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext);
      }
      return /** @this */ function lazyCompilation() {
        if (!compiled) {
          compiled = compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext);

          // Null out all of these references in order to make them eligible for garbage collection
          // since this is a potentially long lived closure
          $compileNodes = transcludeFn = previousCompileContext = null;
        }
        return compiled.apply(this, arguments);
      };
    }

    /**
     * Once the directives have been collected, their compile functions are executed. This method
     * is responsible for inlining directive templates as well as terminating the application
     * of the directives if the terminal directive has been reached.
     *
     * @param {Array} directives Array of collected directives to execute their compile function.
     *        this needs to be pre-sorted by priority order.
     * @param {Node} compileNode The raw DOM node to apply the compile functions to
     * @param {Object} templateAttrs The shared attribute function
     * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
     *                                                  scope argument is auto-generated to the new
     *                                                  child of the transcluded parent scope.
     * @param {JQLite} jqCollection If we are working on the root of the compile tree then this
     *                              argument has the root jqLite array so that we can replace nodes
     *                              on it.
     * @param {Object=} originalReplaceDirective An optional directive that will be ignored when
     *                                           compiling the transclusion.
     * @param {Array.<Function>} preLinkFns
     * @param {Array.<Function>} postLinkFns
     * @param {Object} previousCompileContext Context used for previous compilation of the current
     *                                        node
     * @returns {Function} linkFn
     */
    function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn,
                                   jqCollection, originalReplaceDirective, preLinkFns, postLinkFns,
                                   previousCompileContext) {
      previousCompileContext = previousCompileContext || {};

      var terminalPriority = -Number.MAX_VALUE,
          newScopeDirective = previousCompileContext.newScopeDirective,
          controllerDirectives = previousCompileContext.controllerDirectives,
          newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective,
          templateDirective = previousCompileContext.templateDirective,
          nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective,
          hasTranscludeDirective = false,
          hasTemplate = false,
          hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective,
          $compileNode = templateAttrs.$$element = jqLite(compileNode),
          directive,
          directiveName,
          $template,
          replaceDirective = originalReplaceDirective,
          childTranscludeFn = transcludeFn,
          linkFn,
          didScanForMultipleTransclusion = false,
          mightHaveMultipleTransclusionError = false,
          directiveValue;

      // executes all directives on the current element
      for (var i = 0, ii = directives.length; i < ii; i++) {
        directive = directives[i];
        var attrStart = directive.$$start;
        var attrEnd = directive.$$end;

        // collect multiblock sections
        if (attrStart) {
          $compileNode = groupScan(compileNode, attrStart, attrEnd);
        }
        $template = undefined;

        if (terminalPriority > directive.priority) {
          break; // prevent further processing of directives
        }

        directiveValue = directive.scope;

        if (directiveValue) {

          // skip the check for directives with async templates, we'll check the derived sync
          // directive when the template arrives
          if (!directive.templateUrl) {
            if (isObject(directiveValue)) {
              // This directive is trying to add an isolated scope.
              // Check that there is no scope of any kind already
              assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective,
                                directive, $compileNode);
              newIsolateScopeDirective = directive;
            } else {
              // This directive is trying to add a child scope.
              // Check that there is no isolated scope already
              assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive,
                                $compileNode);
            }
          }

          newScopeDirective = newScopeDirective || directive;
        }

        directiveName = directive.name;

        // If we encounter a condition that can result in transclusion on the directive,
        // then scan ahead in the remaining directives for others that may cause a multiple
        // transclusion error to be thrown during the compilation process.  If a matching directive
        // is found, then we know that when we encounter a transcluded directive, we need to eagerly
        // compile the `transclude` function rather than doing it lazily in order to throw
        // exceptions at the correct time
        if (!didScanForMultipleTransclusion && ((directive.replace && (directive.templateUrl || directive.template))
            || (directive.transclude && !directive.$$tlb))) {
                var candidateDirective;

                for (var scanningIndex = i + 1; (candidateDirective = directives[scanningIndex++]);) {
                    if ((candidateDirective.transclude && !candidateDirective.$$tlb)
                        || (candidateDirective.replace && (candidateDirective.templateUrl || candidateDirective.template))) {
                        mightHaveMultipleTransclusionError = true;
                        break;
                    }
                }

                didScanForMultipleTransclusion = true;
        }

        if (!directive.templateUrl && directive.controller) {
          controllerDirectives = controllerDirectives || createMap();
          assertNoDuplicate('\'' + directiveName + '\' controller',
              controllerDirectives[directiveName], directive, $compileNode);
          controllerDirectives[directiveName] = directive;
        }

        directiveValue = directive.transclude;

        if (directiveValue) {
          hasTranscludeDirective = true;

          // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion.
          // This option should only be used by directives that know how to safely handle element transclusion,
          // where the transcluded nodes are added or replaced after linking.
          if (!directive.$$tlb) {
            assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode);
            nonTlbTranscludeDirective = directive;
          }

          if (directiveValue === 'element') {
            hasElementTranscludeDirective = true;
            terminalPriority = directive.priority;
            $template = $compileNode;
            $compileNode = templateAttrs.$$element =
                jqLite(compile.$$createComment(directiveName, templateAttrs[directiveName]));
            compileNode = $compileNode[0];
            replaceWith(jqCollection, sliceArgs($template), compileNode);

            childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority,
                                        replaceDirective && replaceDirective.name, {
                                          // Don't pass in:
                                          // - controllerDirectives - otherwise we'll create duplicates controllers
                                          // - newIsolateScopeDirective or templateDirective - combining templates with
                                          //   element transclusion doesn't make sense.
                                          //
                                          // We need only nonTlbTranscludeDirective so that we prevent putting transclusion
                                          // on the same element more than once.
                                          nonTlbTranscludeDirective: nonTlbTranscludeDirective
                                        });
          } else {

            var slots = createMap();

            if (!isObject(directiveValue)) {
              $template = jqLite(jqLiteClone(compileNode)).contents();
            } else {

              // We have transclusion slots,
              // collect them up, compile them and store their transclusion functions
              $template = window.document.createDocumentFragment();

              var slotMap = createMap();
              var filledSlots = createMap();

              // Parse the element selectors
              forEach(directiveValue, function(elementSelector, slotName) {
                // If an element selector starts with a ? then it is optional
                var optional = (elementSelector.charAt(0) === '?');
                elementSelector = optional ? elementSelector.substring(1) : elementSelector;

                slotMap[elementSelector] = slotName;

                // We explicitly assign `null` since this implies that a slot was defined but not filled.
                // Later when calling boundTransclusion functions with a slot name we only error if the
                // slot is `undefined`
                slots[slotName] = null;

                // filledSlots contains `true` for all slots that are either optional or have been
                // filled. This is used to check that we have not missed any required slots
                filledSlots[slotName] = optional;
              });

              // Add the matching elements into their slot
              forEach($compileNode.contents(), function(node) {
                var slotName = slotMap[directiveNormalize(nodeName_(node))];
                if (slotName) {
                  filledSlots[slotName] = true;
                  slots[slotName] = slots[slotName] || window.document.createDocumentFragment();
                  slots[slotName].appendChild(node);
                } else {
                  $template.appendChild(node);
                }
              });

              // Check for required slots that were not filled
              forEach(filledSlots, function(filled, slotName) {
                if (!filled) {
                  throw $compileMinErr('reqslot', 'Required transclusion slot `{0}` was not filled.', slotName);
                }
              });

              for (var slotName in slots) {
                if (slots[slotName]) {
                  // Only define a transclusion function if the slot was filled
                  var slotCompileNodes = jqLite(slots[slotName].childNodes);
                  slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slotCompileNodes, transcludeFn);
                }
              }

              $template = jqLite($template.childNodes);
            }

            $compileNode.empty(); // clear contents
            childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, undefined,
                undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope});
            childTranscludeFn.$$slots = slots;
          }
        }

        if (directive.template) {
          hasTemplate = true;
          assertNoDuplicate('template', templateDirective, directive, $compileNode);
          templateDirective = directive;

          directiveValue = (isFunction(directive.template))
              ? directive.template($compileNode, templateAttrs)
              : directive.template;

          directiveValue = denormalizeTemplate(directiveValue);

          if (directive.replace) {
            replaceDirective = directive;
            if (jqLiteIsTextNode(directiveValue)) {
              $template = [];
            } else {
              $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue)));
            }
            compileNode = $template[0];

            if ($template.length !== 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
              throw $compileMinErr('tplrt',
                  'Template for directive \'{0}\' must have exactly one root element. {1}',
                  directiveName, '');
            }

            replaceWith(jqCollection, $compileNode, compileNode);

            var newTemplateAttrs = {$attr: {}};

            // combine directives from the original node and from the template:
            // - take the array of directives for this element
            // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed)
            // - collect directives from the template and sort them by priority
            // - combine directives as: processed + template + unprocessed
            var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs);
            var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1));

            if (newIsolateScopeDirective || newScopeDirective) {
              // The original directive caused the current element to be replaced but this element
              // also needs to have a new scope, so we need to tell the template directives
              // that they would need to get their scope from further up, if they require transclusion
              markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective);
            }
            directives = directives.concat(templateDirectives).concat(unprocessedDirectives);
            mergeTemplateAttributes(templateAttrs, newTemplateAttrs);

            ii = directives.length;
          } else {
            $compileNode.html(directiveValue);
          }
        }

        if (directive.templateUrl) {
          hasTemplate = true;
          assertNoDuplicate('template', templateDirective, directive, $compileNode);
          templateDirective = directive;

          if (directive.replace) {
            replaceDirective = directive;
          }

          // eslint-disable-next-line no-func-assign
          nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode,
              templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, {
                controllerDirectives: controllerDirectives,
                newScopeDirective: (newScopeDirective !== directive) && newScopeDirective,
                newIsolateScopeDirective: newIsolateScopeDirective,
                templateDirective: templateDirective,
                nonTlbTranscludeDirective: nonTlbTranscludeDirective
              });
          ii = directives.length;
        } else if (directive.compile) {
          try {
            linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);
            var context = directive.$$originalDirective || directive;
            if (isFunction(linkFn)) {
              addLinkFns(null, bind(context, linkFn), attrStart, attrEnd);
            } else if (linkFn) {
              addLinkFns(bind(context, linkFn.pre), bind(context, linkFn.post), attrStart, attrEnd);
            }
          } catch (e) {
            $exceptionHandler(e, startingTag($compileNode));
          }
        }

        if (directive.terminal) {
          nodeLinkFn.terminal = true;
          terminalPriority = Math.max(terminalPriority, directive.priority);
        }

      }

      nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true;
      nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective;
      nodeLinkFn.templateOnThisElement = hasTemplate;
      nodeLinkFn.transclude = childTranscludeFn;

      previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective;

      // might be normal or delayed nodeLinkFn depending on if templateUrl is present
      return nodeLinkFn;

      ////////////////////

      function addLinkFns(pre, post, attrStart, attrEnd) {
        if (pre) {
          if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd);
          pre.require = directive.require;
          pre.directiveName = directiveName;
          if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
            pre = cloneAndAnnotateFn(pre, {isolateScope: true});
          }
          preLinkFns.push(pre);
        }
        if (post) {
          if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd);
          post.require = directive.require;
          post.directiveName = directiveName;
          if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
            post = cloneAndAnnotateFn(post, {isolateScope: true});
          }
          postLinkFns.push(post);
        }
      }

      function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
        var i, ii, linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element,
            attrs, scopeBindingInfo;

        if (compileNode === linkNode) {
          attrs = templateAttrs;
          $element = templateAttrs.$$element;
        } else {
          $element = jqLite(linkNode);
          attrs = new Attributes($element, templateAttrs);
        }

        controllerScope = scope;
        if (newIsolateScopeDirective) {
          isolateScope = scope.$new(true);
        } else if (newScopeDirective) {
          controllerScope = scope.$parent;
        }

        if (boundTranscludeFn) {
          // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn`
          // is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
          transcludeFn = controllersBoundTransclude;
          transcludeFn.$$boundTransclude = boundTranscludeFn;
          // expose the slots on the `$transclude` function
          transcludeFn.isSlotFilled = function(slotName) {
            return !!boundTranscludeFn.$$slots[slotName];
          };
        }

        if (controllerDirectives) {
          elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective);
        }

        if (newIsolateScopeDirective) {
          // Initialize isolate scope bindings for new isolate scope directive.
          compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective ||
              templateDirective === newIsolateScopeDirective.$$originalDirective)));
          compile.$$addScopeClass($element, true);
          isolateScope.$$isolateBindings =
              newIsolateScopeDirective.$$isolateBindings;
          scopeBindingInfo = initializeDirectiveBindings(scope, attrs, isolateScope,
                                        isolateScope.$$isolateBindings,
                                        newIsolateScopeDirective);
          if (scopeBindingInfo.removeWatches) {
            isolateScope.$on('$destroy', scopeBindingInfo.removeWatches);
          }
        }

        // Initialize bindToController bindings
        for (var name in elementControllers) {
          var controllerDirective = controllerDirectives[name];
          var controller = elementControllers[name];
          var bindings = controllerDirective.$$bindings.bindToController;

          controller.instance = controller();
          $element.data('$' + controllerDirective.name + 'Controller', controller.instance);
          controller.bindingInfo =
            initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
          }

        // Bind the required controllers to the controller, if `require` is an object and `bindToController` is truthy
        forEach(controllerDirectives, function(controllerDirective, name) {
          var require = controllerDirective.require;
          if (controllerDirective.bindToController && !isArray(require) && isObject(require)) {
            extend(elementControllers[name].instance, getControllers(name, require, $element, elementControllers));
          }
        });

        // Handle the init and destroy lifecycle hooks on all controllers that have them
        forEach(elementControllers, function(controller) {
          var controllerInstance = controller.instance;
          if (isFunction(controllerInstance.$onChanges)) {
            try {
              controllerInstance.$onChanges(controller.bindingInfo.initialChanges);
            } catch (e) {
              $exceptionHandler(e);
            }
          }
          if (isFunction(controllerInstance.$onInit)) {
            try {
              controllerInstance.$onInit();
            } catch (e) {
              $exceptionHandler(e);
            }
          }
          if (isFunction(controllerInstance.$doCheck)) {
            controllerScope.$watch(function() { controllerInstance.$doCheck(); });
            controllerInstance.$doCheck();
          }
          if (isFunction(controllerInstance.$onDestroy)) {
            controllerScope.$on('$destroy', function callOnDestroyHook() {
              controllerInstance.$onDestroy();
            });
          }
        });

        // PRELINKING
        for (i = 0, ii = preLinkFns.length; i < ii; i++) {
          linkFn = preLinkFns[i];
          invokeLinkFn(linkFn,
              linkFn.isolateScope ? isolateScope : scope,
              $element,
              attrs,
              linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
              transcludeFn
          );
        }

        // RECURSION
        // We only pass the isolate scope, if the isolate directive has a template,
        // otherwise the child elements do not belong to the isolate directive.
        var scopeToChild = scope;
        if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) {
          scopeToChild = isolateScope;
        }
        if (childLinkFn) {
          childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);
        }

        // POSTLINKING
        for (i = postLinkFns.length - 1; i >= 0; i--) {
          linkFn = postLinkFns[i];
          invokeLinkFn(linkFn,
              linkFn.isolateScope ? isolateScope : scope,
              $element,
              attrs,
              linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
              transcludeFn
          );
        }

        // Trigger $postLink lifecycle hooks
        forEach(elementControllers, function(controller) {
          var controllerInstance = controller.instance;
          if (isFunction(controllerInstance.$postLink)) {
            controllerInstance.$postLink();
          }
        });

        // This is the function that is injected as `$transclude`.
        // Note: all arguments are optional!
        function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement, slotName) {
          var transcludeControllers;
          // No scope passed in:
          if (!isScope(scope)) {
            slotName = futureParentElement;
            futureParentElement = cloneAttachFn;
            cloneAttachFn = scope;
            scope = undefined;
          }

          if (hasElementTranscludeDirective) {
            transcludeControllers = elementControllers;
          }
          if (!futureParentElement) {
            futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element;
          }
          if (slotName) {
            // slotTranscludeFn can be one of three things:
            //  * a transclude function - a filled slot
            //  * `null` - an optional slot that was not filled
            //  * `undefined` - a slot that was not declared (i.e. invalid)
            var slotTranscludeFn = boundTranscludeFn.$$slots[slotName];
            if (slotTranscludeFn) {
              return slotTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
            } else if (isUndefined(slotTranscludeFn)) {
              throw $compileMinErr('noslot',
               'No parent directive that requires a transclusion with slot name "{0}". ' +
               'Element: {1}',
               slotName, startingTag($element));
            }
          } else {
            return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
          }
        }
      }
    }

    function getControllers(directiveName, require, $element, elementControllers) {
      var value;

      if (isString(require)) {
        var match = require.match(REQUIRE_PREFIX_REGEXP);
        var name = require.substring(match[0].length);
        var inheritType = match[1] || match[3];
        var optional = match[2] === '?';

        //If only parents then start at the parent element
        if (inheritType === '^^') {
          $element = $element.parent();
        //Otherwise attempt getting the controller from elementControllers in case
        //the element is transcluded (and has no data) and to avoid .data if possible
        } else {
          value = elementControllers && elementControllers[name];
          value = value && value.instance;
        }

        if (!value) {
          var dataName = '$' + name + 'Controller';

          if (inheritType === '^^' && $element[0] && $element[0].nodeType === NODE_TYPE_DOCUMENT) {
            // inheritedData() uses the documentElement when it finds the document, so we would
            // require from the element itself.
            value = null;
          } else {
            value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName);
          }
        }

        if (!value && !optional) {
          throw $compileMinErr('ctreq',
              'Controller \'{0}\', required by directive \'{1}\', can\'t be found!',
              name, directiveName);
        }
      } else if (isArray(require)) {
        value = [];
        for (var i = 0, ii = require.length; i < ii; i++) {
          value[i] = getControllers(directiveName, require[i], $element, elementControllers);
        }
      } else if (isObject(require)) {
        value = {};
        forEach(require, function(controller, property) {
          value[property] = getControllers(directiveName, controller, $element, elementControllers);
        });
      }

      return value || null;
    }

    function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective) {
      var elementControllers = createMap();
      for (var controllerKey in controllerDirectives) {
        var directive = controllerDirectives[controllerKey];
        var locals = {
          $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
          $element: $element,
          $attrs: attrs,
          $transclude: transcludeFn
        };

        var controller = directive.controller;
        if (controller === '@') {
          controller = attrs[directive.name];
        }

        var controllerInstance = $controller(controller, locals, true, directive.controllerAs);

        // For directives with element transclusion the element is a comment.
        // In this case .data will not attach any data.
        // Instead, we save the controllers for the element in a local hash and attach to .data
        // later, once we have the actual element.
        elementControllers[directive.name] = controllerInstance;
        $element.data('$' + directive.name + 'Controller', controllerInstance.instance);
      }
      return elementControllers;
    }

    // Depending upon the context in which a directive finds itself it might need to have a new isolated
    // or child scope created. For instance:
    // * if the directive has been pulled into a template because another directive with a higher priority
    // asked for element transclusion
    // * if the directive itself asks for transclusion but it is at the root of a template and the original
    // element was replaced. See https://github.com/angular/angular.js/issues/12936
    function markDirectiveScope(directives, isolateScope, newScope) {
      for (var j = 0, jj = directives.length; j < jj; j++) {
        directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope});
      }
    }

    /**
     * looks up the directive and decorates it with exception handling and proper parameters. We
     * call this the boundDirective.
     *
     * @param {string} name name of the directive to look up.
     * @param {string} location The directive must be found in specific format.
     *   String containing any of theses characters:
     *
     *   * `E`: element name
     *   * `A': attribute
     *   * `C`: class
     *   * `M`: comment
     * @returns {boolean} true if directive was added.
     */
    function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName,
                          endAttrName) {
      if (name === ignoreDirective) return null;
      var match = null;
      if (hasDirectives.hasOwnProperty(name)) {
        for (var directive, directives = $injector.get(name + Suffix),
            i = 0, ii = directives.length; i < ii; i++) {
          directive = directives[i];
          if ((isUndefined(maxPriority) || maxPriority > directive.priority) &&
               directive.restrict.indexOf(location) !== -1) {
            if (startAttrName) {
              directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
            }
            if (!directive.$$bindings) {
              var bindings = directive.$$bindings =
                  parseDirectiveBindings(directive, directive.name);
              if (isObject(bindings.isolateScope)) {
                directive.$$isolateBindings = bindings.isolateScope;
              }
            }
            tDirectives.push(directive);
            match = directive;
          }
        }
      }
      return match;
    }


    /**
     * looks up the directive and returns true if it is a multi-element directive,
     * and therefore requires DOM nodes between -start and -end markers to be grouped
     * together.
     *
     * @param {string} name name of the directive to look up.
     * @returns true if directive was registered as multi-element.
     */
    function directiveIsMultiElement(name) {
      if (hasDirectives.hasOwnProperty(name)) {
        for (var directive, directives = $injector.get(name + Suffix),
            i = 0, ii = directives.length; i < ii; i++) {
          directive = directives[i];
          if (directive.multiElement) {
            return true;
          }
        }
      }
      return false;
    }

    /**
     * When the element is replaced with HTML template then the new attributes
     * on the template need to be merged with the existing attributes in the DOM.
     * The desired effect is to have both of the attributes present.
     *
     * @param {object} dst destination attributes (original DOM)
     * @param {object} src source attributes (from the directive template)
     */
    function mergeTemplateAttributes(dst, src) {
      var srcAttr = src.$attr,
          dstAttr = dst.$attr;

      // reapply the old attributes to the new element
      forEach(dst, function(value, key) {
        if (key.charAt(0) !== '$') {
          if (src[key] && src[key] !== value) {
            if (value.length) {
              value += (key === 'style' ? ';' : ' ') + src[key];
            } else {
              value = src[key];
            }
          }
          dst.$set(key, value, true, srcAttr[key]);
        }
      });

      // copy the new attributes on the old attrs object
      forEach(src, function(value, key) {
        // Check if we already set this attribute in the loop above.
        // `dst` will never contain hasOwnProperty as DOM parser won't let it.
        // You will get an "InvalidCharacterError: DOM Exception 5" error if you
        // have an attribute like "has-own-property" or "data-has-own-property", etc.
        if (!dst.hasOwnProperty(key) && key.charAt(0) !== '$') {
          dst[key] = value;

          if (key !== 'class' && key !== 'style') {
            dstAttr[key] = srcAttr[key];
          }
        }
      });
    }


    function compileTemplateUrl(directives, $compileNode, tAttrs,
        $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) {
      var linkQueue = [],
          afterTemplateNodeLinkFn,
          afterTemplateChildLinkFn,
          beforeTemplateCompileNode = $compileNode[0],
          origAsyncDirective = directives.shift(),
          derivedSyncDirective = inherit(origAsyncDirective, {
            templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective
          }),
          templateUrl = (isFunction(origAsyncDirective.templateUrl))
              ? origAsyncDirective.templateUrl($compileNode, tAttrs)
              : origAsyncDirective.templateUrl,
          templateNamespace = origAsyncDirective.templateNamespace;

      $compileNode.empty();

      $templateRequest(templateUrl)
        .then(function(content) {
          var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn;

          content = denormalizeTemplate(content);

          if (origAsyncDirective.replace) {
            if (jqLiteIsTextNode(content)) {
              $template = [];
            } else {
              $template = removeComments(wrapTemplate(templateNamespace, trim(content)));
            }
            compileNode = $template[0];

            if ($template.length !== 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
              throw $compileMinErr('tplrt',
                  'Template for directive \'{0}\' must have exactly one root element. {1}',
                  origAsyncDirective.name, templateUrl);
            }

            tempTemplateAttrs = {$attr: {}};
            replaceWith($rootElement, $compileNode, compileNode);
            var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs);

            if (isObject(origAsyncDirective.scope)) {
              // the original directive that caused the template to be loaded async required
              // an isolate scope
              markDirectiveScope(templateDirectives, true);
            }
            directives = templateDirectives.concat(directives);
            mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
          } else {
            compileNode = beforeTemplateCompileNode;
            $compileNode.html(content);
          }

          directives.unshift(derivedSyncDirective);

          afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs,
              childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns,
              previousCompileContext);
          forEach($rootElement, function(node, i) {
            if (node === compileNode) {
              $rootElement[i] = $compileNode[0];
            }
          });
          afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn);

          while (linkQueue.length) {
            var scope = linkQueue.shift(),
                beforeTemplateLinkNode = linkQueue.shift(),
                linkRootElement = linkQueue.shift(),
                boundTranscludeFn = linkQueue.shift(),
                linkNode = $compileNode[0];

            if (scope.$$destroyed) continue;

            if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
              var oldClasses = beforeTemplateLinkNode.className;

              if (!(previousCompileContext.hasElementTranscludeDirective &&
                  origAsyncDirective.replace)) {
                // it was cloned therefore we have to clone as well.
                linkNode = jqLiteClone(compileNode);
              }
              replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);

              // Copy in CSS classes from original node
              safeAddClass(jqLite(linkNode), oldClasses);
            }
            if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
              childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
            } else {
              childBoundTranscludeFn = boundTranscludeFn;
            }
            afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement,
              childBoundTranscludeFn);
          }
          linkQueue = null;
        }).catch(function(error) {
          if (isError(error)) {
            $exceptionHandler(error);
          }
        });

      return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
        var childBoundTranscludeFn = boundTranscludeFn;
        if (scope.$$destroyed) return;
        if (linkQueue) {
          linkQueue.push(scope,
                         node,
                         rootElement,
                         childBoundTranscludeFn);
        } else {
          if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
            childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
          }
          afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn);
        }
      };
    }


    /**
     * Sorting function for bound directives.
     */
    function byPriority(a, b) {
      var diff = b.priority - a.priority;
      if (diff !== 0) return diff;
      if (a.name !== b.name) return (a.name < b.name) ? -1 : 1;
      return a.index - b.index;
    }

    function assertNoDuplicate(what, previousDirective, directive, element) {

      function wrapModuleNameIfDefined(moduleName) {
        return moduleName ?
          (' (module: ' + moduleName + ')') :
          '';
      }

      if (previousDirective) {
        throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}',
            previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName),
            directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element));
      }
    }


    function addTextInterpolateDirective(directives, text) {
      var interpolateFn = $interpolate(text, true);
      if (interpolateFn) {
        directives.push({
          priority: 0,
          compile: function textInterpolateCompileFn(templateNode) {
            var templateNodeParent = templateNode.parent(),
                hasCompileParent = !!templateNodeParent.length;

            // When transcluding a template that has bindings in the root
            // we don't have a parent and thus need to add the class during linking fn.
            if (hasCompileParent) compile.$$addBindingClass(templateNodeParent);

            return function textInterpolateLinkFn(scope, node) {
              var parent = node.parent();
              if (!hasCompileParent) compile.$$addBindingClass(parent);
              compile.$$addBindingInfo(parent, interpolateFn.expressions);
              scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
                node[0].nodeValue = value;
              });
            };
          }
        });
      }
    }


    function wrapTemplate(type, template) {
      type = lowercase(type || 'html');
      switch (type) {
      case 'svg':
      case 'math':
        var wrapper = window.document.createElement('div');
        wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>';
        return wrapper.childNodes[0].childNodes;
      default:
        return template;
      }
    }


    function getTrustedAttrContext(nodeName, attrNormalizedName) {
      if (attrNormalizedName === 'srcdoc') {
        return $sce.HTML;
      }
      // All nodes with src attributes require a RESOURCE_URL value, except for
      // img and various html5 media nodes, which require the MEDIA_URL context.
      if (attrNormalizedName === 'src' || attrNormalizedName === 'ngSrc') {
        if (['img', 'video', 'audio', 'source', 'track'].indexOf(nodeName) === -1) {
          return $sce.RESOURCE_URL;
        }
        return $sce.MEDIA_URL;
      } else if (attrNormalizedName === 'xlinkHref') {
        // Some xlink:href are okay, most aren't
        if (nodeName === 'image') return $sce.MEDIA_URL;
        if (nodeName === 'a') return $sce.URL;
        return $sce.RESOURCE_URL;
      } else if (
          // Formaction
          (nodeName === 'form' && attrNormalizedName === 'action') ||
          // If relative URLs can go where they are not expected to, then
          // all sorts of trust issues can arise.
          (nodeName === 'base' && attrNormalizedName === 'href') ||
          // links can be stylesheets or imports, which can run script in the current origin
          (nodeName === 'link' && attrNormalizedName === 'href')
      ) {
        return $sce.RESOURCE_URL;
      } else if (nodeName === 'a' && (attrNormalizedName === 'href' ||
                                 attrNormalizedName === 'ngHref')) {
        return $sce.URL;
      }
    }

    function getTrustedPropContext(nodeName, propNormalizedName) {
      var prop = propNormalizedName.toLowerCase();
      return PROP_CONTEXTS[nodeName + '|' + prop] || PROP_CONTEXTS['*|' + prop];
    }

    function sanitizeSrcsetPropertyValue(value) {
      return sanitizeSrcset($sce.valueOf(value), 'ng-prop-srcset');
    }
    function addPropertyDirective(node, directives, attrName, propName) {
      if (EVENT_HANDLER_ATTR_REGEXP.test(propName)) {
        throw $compileMinErr('nodomevents', 'Property bindings for HTML DOM event properties are disallowed');
      }

      var nodeName = nodeName_(node);
      var trustedContext = getTrustedPropContext(nodeName, propName);

      var sanitizer = identity;
      // Sanitize img[srcset] + source[srcset] values.
      if (propName === 'srcset' && (nodeName === 'img' || nodeName === 'source')) {
        sanitizer = sanitizeSrcsetPropertyValue;
      } else if (trustedContext) {
        sanitizer = $sce.getTrusted.bind($sce, trustedContext);
      }

      directives.push({
        priority: 100,
        compile: function ngPropCompileFn(_, attr) {
          var ngPropGetter = $parse(attr[attrName]);
          var ngPropWatch = $parse(attr[attrName], function sceValueOf(val) {
            // Unwrap the value to compare the actual inner safe value, not the wrapper object.
            return $sce.valueOf(val);
          });

          return {
            pre: function ngPropPreLinkFn(scope, $element) {
              function applyPropValue() {
                var propValue = ngPropGetter(scope);
                $element[0][propName] = sanitizer(propValue);
              }

              applyPropValue();
              scope.$watch(ngPropWatch, applyPropValue);
            }
          };
        }
      });
    }

    function addEventDirective(directives, attrName, eventName) {
      directives.push(
        createEventDirective($parse, $rootScope, $exceptionHandler, attrName, eventName, /*forceAsync=*/false)
      );
    }

    function addAttrInterpolateDirective(node, directives, value, name, isNgAttr) {
      var nodeName = nodeName_(node);
      var trustedContext = getTrustedAttrContext(nodeName, name);
      var mustHaveExpression = !isNgAttr;
      var allOrNothing = ALL_OR_NOTHING_ATTRS[name] || isNgAttr;

      var interpolateFn = $interpolate(value, mustHaveExpression, trustedContext, allOrNothing);

      // no interpolation found -> ignore
      if (!interpolateFn) return;

      if (name === 'multiple' && nodeName === 'select') {
        throw $compileMinErr('selmulti',
            'Binding to the \'multiple\' attribute is not supported. Element: {0}',
            startingTag(node));
      }

      if (EVENT_HANDLER_ATTR_REGEXP.test(name)) {
        throw $compileMinErr('nodomevents', 'Interpolations for HTML DOM event attributes are disallowed');
      }

      directives.push({
        priority: 100,
        compile: function() {
            return {
              pre: function attrInterpolatePreLinkFn(scope, element, attr) {
                var $$observers = (attr.$$observers || (attr.$$observers = createMap()));

                // If the attribute has changed since last $interpolate()ed
                var newValue = attr[name];
                if (newValue !== value) {
                  // we need to interpolate again since the attribute value has been updated
                  // (e.g. by another directive's compile function)
                  // ensure unset/empty values make interpolateFn falsy
                  interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing);
                  value = newValue;
                }

                // if attribute was updated so that there is no interpolation going on we don't want to
                // register any observers
                if (!interpolateFn) return;

                // initialize attr object so that it's ready in case we need the value for isolate
                // scope initialization, otherwise the value would not be available from isolate
                // directive's linking fn during linking phase
                attr[name] = interpolateFn(scope);

                ($$observers[name] || ($$observers[name] = [])).$$inter = true;
                (attr.$$observers && attr.$$observers[name].$$scope || scope).
                  $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) {
                    //special case for class attribute addition + removal
                    //so that class changes can tap into the animation
                    //hooks provided by the $animate service. Be sure to
                    //skip animations when the first digest occurs (when
                    //both the new and the old values are the same) since
                    //the CSS classes are the non-interpolated values
                    if (name === 'class' && newValue !== oldValue) {
                      attr.$updateClass(newValue, oldValue);
                    } else {
                      attr.$set(name, newValue);
                    }
                  });
              }
            };
          }
      });
    }


    /**
     * This is a special jqLite.replaceWith, which can replace items which
     * have no parents, provided that the containing jqLite collection is provided.
     *
     * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes
     *                               in the root of the tree.
     * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep
     *                                  the shell, but replace its DOM node reference.
     * @param {Node} newNode The new DOM node.
     */
    function replaceWith($rootElement, elementsToRemove, newNode) {
      var firstElementToRemove = elementsToRemove[0],
          removeCount = elementsToRemove.length,
          parent = firstElementToRemove.parentNode,
          i, ii;

      if ($rootElement) {
        for (i = 0, ii = $rootElement.length; i < ii; i++) {
          if ($rootElement[i] === firstElementToRemove) {
            $rootElement[i++] = newNode;
            for (var j = i, j2 = j + removeCount - 1,
                     jj = $rootElement.length;
                 j < jj; j++, j2++) {
              if (j2 < jj) {
                $rootElement[j] = $rootElement[j2];
              } else {
                delete $rootElement[j];
              }
            }
            $rootElement.length -= removeCount - 1;

            // If the replaced element is also the jQuery .context then replace it
            // .context is a deprecated jQuery api, so we should set it only when jQuery set it
            // http://api.jquery.com/context/
            if ($rootElement.context === firstElementToRemove) {
              $rootElement.context = newNode;
            }
            break;
          }
        }
      }

      if (parent) {
        parent.replaceChild(newNode, firstElementToRemove);
      }

      // Append all the `elementsToRemove` to a fragment. This will...
      // - remove them from the DOM
      // - allow them to still be traversed with .nextSibling
      // - allow a single fragment.qSA to fetch all elements being removed
      var fragment = window.document.createDocumentFragment();
      for (i = 0; i < removeCount; i++) {
        fragment.appendChild(elementsToRemove[i]);
      }

      if (jqLite.hasData(firstElementToRemove)) {
        // Copy over user data (that includes AngularJS's $scope etc.). Don't copy private
        // data here because there's no public interface in jQuery to do that and copying over
        // event listeners (which is the main use of private data) wouldn't work anyway.
        jqLite.data(newNode, jqLite.data(firstElementToRemove));

        // Remove $destroy event listeners from `firstElementToRemove`
        jqLite(firstElementToRemove).off('$destroy');
      }

      // Cleanup any data/listeners on the elements and children.
      // This includes invoking the $destroy event on any elements with listeners.
      jqLite.cleanData(fragment.querySelectorAll('*'));

      // Update the jqLite collection to only contain the `newNode`
      for (i = 1; i < removeCount; i++) {
        delete elementsToRemove[i];
      }
      elementsToRemove[0] = newNode;
      elementsToRemove.length = 1;
    }


    function cloneAndAnnotateFn(fn, annotation) {
      return extend(function() { return fn.apply(null, arguments); }, fn, annotation);
    }


    function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) {
      try {
        linkFn(scope, $element, attrs, controllers, transcludeFn);
      } catch (e) {
        $exceptionHandler(e, startingTag($element));
      }
    }

    function strictBindingsCheck(attrName, directiveName) {
      if (strictComponentBindingsEnabled) {
        throw $compileMinErr('missingattr',
          'Attribute \'{0}\' of \'{1}\' is non-optional and must be set!',
          attrName, directiveName);
      }
    }

    // Set up $watches for isolate scope and controller bindings.
    function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) {
      var removeWatchCollection = [];
      var initialChanges = {};
      var changes;

      forEach(bindings, function initializeBinding(definition, scopeName) {
        var attrName = definition.attrName,
        optional = definition.optional,
        mode = definition.mode, // @, =, <, or &
        lastValue,
        parentGet, parentSet, compare, removeWatch;

        switch (mode) {

          case '@':
            if (!optional && !hasOwnProperty.call(attrs, attrName)) {
              strictBindingsCheck(attrName, directive.name);
              destination[scopeName] = attrs[attrName] = undefined;

            }
            removeWatch = attrs.$observe(attrName, function(value) {
              if (isString(value) || isBoolean(value)) {
                var oldValue = destination[scopeName];
                recordChanges(scopeName, value, oldValue);
                destination[scopeName] = value;
              }
            });
            attrs.$$observers[attrName].$$scope = scope;
            lastValue = attrs[attrName];
            if (isString(lastValue)) {
              // If the attribute has been provided then we trigger an interpolation to ensure
              // the value is there for use in the link fn
              destination[scopeName] = $interpolate(lastValue)(scope);
            } else if (isBoolean(lastValue)) {
              // If the attributes is one of the BOOLEAN_ATTR then AngularJS will have converted
              // the value to boolean rather than a string, so we special case this situation
              destination[scopeName] = lastValue;
            }
            initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]);
            removeWatchCollection.push(removeWatch);
            break;

          case '=':
            if (!hasOwnProperty.call(attrs, attrName)) {
              if (optional) break;
              strictBindingsCheck(attrName, directive.name);
              attrs[attrName] = undefined;
            }
            if (optional && !attrs[attrName]) break;

            parentGet = $parse(attrs[attrName]);
            if (parentGet.literal) {
              compare = equals;
            } else {
              compare = simpleCompare;
            }
            parentSet = parentGet.assign || function() {
              // reset the change, or we will throw this exception on every $digest
              lastValue = destination[scopeName] = parentGet(scope);
              throw $compileMinErr('nonassign',
                  'Expression \'{0}\' in attribute \'{1}\' used with directive \'{2}\' is non-assignable!',
                  attrs[attrName], attrName, directive.name);
            };
            lastValue = destination[scopeName] = parentGet(scope);
            var parentValueWatch = function parentValueWatch(parentValue) {
              if (!compare(parentValue, destination[scopeName])) {
                // we are out of sync and need to copy
                if (!compare(parentValue, lastValue)) {
                  // parent changed and it has precedence
                  destination[scopeName] = parentValue;
                } else {
                  // if the parent can be assigned then do so
                  parentSet(scope, parentValue = destination[scopeName]);
                }
              }
              lastValue = parentValue;
              return lastValue;
            };
            parentValueWatch.$stateful = true;
            if (definition.collection) {
              removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
            } else {
              removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
            }
            removeWatchCollection.push(removeWatch);
            break;

          case '<':
            if (!hasOwnProperty.call(attrs, attrName)) {
              if (optional) break;
              strictBindingsCheck(attrName, directive.name);
              attrs[attrName] = undefined;
            }
            if (optional && !attrs[attrName]) break;

            parentGet = $parse(attrs[attrName]);
            var isLiteral = parentGet.literal;

            var initialValue = destination[scopeName] = parentGet(scope);
            initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]);

            removeWatch = scope[definition.collection ? '$watchCollection' : '$watch'](parentGet, function parentValueWatchAction(newValue, oldValue) {
              if (oldValue === newValue) {
                if (oldValue === initialValue || (isLiteral && equals(oldValue, initialValue))) {
                  return;
                }
                oldValue = initialValue;
              }
              recordChanges(scopeName, newValue, oldValue);
              destination[scopeName] = newValue;
            });

            removeWatchCollection.push(removeWatch);
            break;

          case '&':
            if (!optional && !hasOwnProperty.call(attrs, attrName)) {
              strictBindingsCheck(attrName, directive.name);
            }
            // Don't assign Object.prototype method to scope
            parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop;

            // Don't assign noop to destination if expression is not valid
            if (parentGet === noop && optional) break;

            destination[scopeName] = function(locals) {
              return parentGet(scope, locals);
            };
            break;
        }
      });

      function recordChanges(key, currentValue, previousValue) {
        if (isFunction(destination.$onChanges) && !simpleCompare(currentValue, previousValue)) {
          // If we have not already scheduled the top level onChangesQueue handler then do so now
          if (!onChangesQueue) {
            scope.$$postDigest(flushOnChangesQueue);
            onChangesQueue = [];
          }
          // If we have not already queued a trigger of onChanges for this controller then do so now
          if (!changes) {
            changes = {};
            onChangesQueue.push(triggerOnChangesHook);
          }
          // If the has been a change on this property already then we need to reuse the previous value
          if (changes[key]) {
            previousValue = changes[key].previousValue;
          }
          // Store this change
          changes[key] = new SimpleChange(previousValue, currentValue);
        }
      }

      function triggerOnChangesHook() {
        destination.$onChanges(changes);
        // Now clear the changes so that we schedule onChanges when more changes arrive
        changes = undefined;
      }

      return {
        initialChanges: initialChanges,
        removeWatches: removeWatchCollection.length && function removeWatches() {
          for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) {
            removeWatchCollection[i]();
          }
        }
      };
    }
  }];
}

function SimpleChange(previous, current) {
  this.previousValue = previous;
  this.currentValue = current;
}
SimpleChange.prototype.isFirstChange = function() { return this.previousValue === _UNINITIALIZED_VALUE; };


var PREFIX_REGEXP = /^((?:x|data)[:\-_])/i;
var SPECIAL_CHARS_REGEXP = /[:\-_]+(.)/g;

/**
 * Converts all accepted directives format into proper directive name.
 * @param name Name to normalize
 */
function directiveNormalize(name) {
  return name
    .replace(PREFIX_REGEXP, '')
    .replace(SPECIAL_CHARS_REGEXP, function(_, letter, offset) {
      return offset ? letter.toUpperCase() : letter;
    });
}

/**
 * @ngdoc type
 * @name $compile.directive.Attributes
 *
 * @description
 * A shared object between directive compile / linking functions which contains normalized DOM
 * element attributes. The values reflect current binding state `{{ }}`. The normalization is
 * needed since all of these are treated as equivalent in AngularJS:
 *
 * ```
 *    <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a">
 * ```
 */

/**
 * @ngdoc property
 * @name $compile.directive.Attributes#$attr
 *
 * @description
 * A map of DOM element attribute names to the normalized name. This is
 * needed to do reverse lookup from normalized name back to actual name.
 */


/**
 * @ngdoc method
 * @name $compile.directive.Attributes#$set
 * @kind function
 *
 * @description
 * Set DOM element attribute value.
 *
 *
 * @param {string} name Normalized element attribute name of the property to modify. The name is
 *          reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
 *          property to the original name.
 * @param {string} value Value to set the attribute to. The value can be an interpolated string.
 */



/**
 * Closure compiler type information
 */

function nodesetLinkingFn(
  /* angular.Scope */ scope,
  /* NodeList */ nodeList,
  /* Element */ rootElement,
  /* function(Function) */ boundTranscludeFn
) {}

function directiveLinkingFn(
  /* nodesetLinkingFn */ nodesetLinkingFn,
  /* angular.Scope */ scope,
  /* Node */ node,
  /* Element */ rootElement,
  /* function(Function) */ boundTranscludeFn
) {}

function tokenDifference(str1, str2) {
  var values = '',
      tokens1 = str1.split(/\s+/),
      tokens2 = str2.split(/\s+/);

  outer:
  for (var i = 0; i < tokens1.length; i++) {
    var token = tokens1[i];
    for (var j = 0; j < tokens2.length; j++) {
      if (token === tokens2[j]) continue outer;
    }
    values += (values.length > 0 ? ' ' : '') + token;
  }
  return values;
}

function removeComments(jqNodes) {
  jqNodes = jqLite(jqNodes);
  var i = jqNodes.length;

  if (i <= 1) {
    return jqNodes;
  }

  while (i--) {
    var node = jqNodes[i];
    if (node.nodeType === NODE_TYPE_COMMENT ||
       (node.nodeType === NODE_TYPE_TEXT && node.nodeValue.trim() === '')) {
         splice.call(jqNodes, i, 1);
    }
  }
  return jqNodes;
}

var $controllerMinErr = minErr('$controller');


var CNTRL_REG = /^(\S+)(\s+as\s+([\w$]+))?$/;
function identifierForController(controller, ident) {
  if (ident && isString(ident)) return ident;
  if (isString(controller)) {
    var match = CNTRL_REG.exec(controller);
    if (match) return match[3];
  }
}


/**
 * @ngdoc provider
 * @name $controllerProvider
 * @this
 *
 * @description
 * The {@link ng.$controller $controller service} is used by AngularJS to create new
 * controllers.
 *
 * This provider allows controller registration via the
 * {@link ng.$controllerProvider#register register} method.
 */
function $ControllerProvider() {
  var controllers = {};

  /**
   * @ngdoc method
   * @name $controllerProvider#has
   * @param {string} name Controller name to check.
   */
  this.has = function(name) {
    return controllers.hasOwnProperty(name);
  };

  /**
   * @ngdoc method
   * @name $controllerProvider#register
   * @param {string|Object} name Controller name, or an object map of controllers where the keys are
   *    the names and the values are the constructors.
   * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI
   *    annotations in the array notation).
   */
  this.register = function(name, constructor) {
    assertNotHasOwnProperty(name, 'controller');
    if (isObject(name)) {
      extend(controllers, name);
    } else {
      controllers[name] = constructor;
    }
  };

  this.$get = ['$injector', function($injector) {

    /**
     * @ngdoc service
     * @name $controller
     * @requires $injector
     *
     * @param {Function|string} constructor If called with a function then it's considered to be the
     *    controller constructor function. Otherwise it's considered to be a string which is used
     *    to retrieve the controller constructor using the following steps:
     *
     *    * check if a controller with given name is registered via `$controllerProvider`
     *    * check if evaluating the string on the current scope returns a constructor
     *
     *    The string can use the `controller as property` syntax, where the controller instance is published
     *    as the specified property on the `scope`; the `scope` must be injected into `locals` param for this
     *    to work correctly.
     *
     * @param {Object} locals Injection locals for Controller.
     * @return {Object} Instance of given controller.
     *
     * @description
     * `$controller` service is responsible for instantiating controllers.
     *
     * It's just a simple call to {@link auto.$injector $injector}, but extracted into
     * a service, so that one can override this service with [BC version](https://gist.github.com/1649788).
     */
    return function $controller(expression, locals, later, ident) {
      // PRIVATE API:
      //   param `later` --- indicates that the controller's constructor is invoked at a later time.
      //                     If true, $controller will allocate the object with the correct
      //                     prototype chain, but will not invoke the controller until a returned
      //                     callback is invoked.
      //   param `ident` --- An optional label which overrides the label parsed from the controller
      //                     expression, if any.
      var instance, match, constructor, identifier;
      later = later === true;
      if (ident && isString(ident)) {
        identifier = ident;
      }

      if (isString(expression)) {
        match = expression.match(CNTRL_REG);
        if (!match) {
          throw $controllerMinErr('ctrlfmt',
            'Badly formed controller string \'{0}\'. ' +
            'Must match `__name__ as __id__` or `__name__`.', expression);
        }
        constructor = match[1];
        identifier = identifier || match[3];
        expression = controllers.hasOwnProperty(constructor)
            ? controllers[constructor]
            : getter(locals.$scope, constructor, true);

        if (!expression) {
          throw $controllerMinErr('ctrlreg',
            'The controller with the name \'{0}\' is not registered.', constructor);
        }

        assertArgFn(expression, constructor, true);
      }

      if (later) {
        // Instantiate controller later:
        // This machinery is used to create an instance of the object before calling the
        // controller's constructor itself.
        //
        // This allows properties to be added to the controller before the constructor is
        // invoked. Primarily, this is used for isolate scope bindings in $compile.
        //
        // This feature is not intended for use by applications, and is thus not documented
        // publicly.
        // Object creation: http://jsperf.com/create-constructor/2
        var controllerPrototype = (isArray(expression) ?
          expression[expression.length - 1] : expression).prototype;
        instance = Object.create(controllerPrototype || null);

        if (identifier) {
          addIdentifier(locals, identifier, instance, constructor || expression.name);
        }

        return extend(function $controllerInit() {
          var result = $injector.invoke(expression, instance, locals, constructor);
          if (result !== instance && (isObject(result) || isFunction(result))) {
            instance = result;
            if (identifier) {
              // If result changed, re-assign controllerAs value to scope.
              addIdentifier(locals, identifier, instance, constructor || expression.name);
            }
          }
          return instance;
        }, {
          instance: instance,
          identifier: identifier
        });
      }

      instance = $injector.instantiate(expression, locals, constructor);

      if (identifier) {
        addIdentifier(locals, identifier, instance, constructor || expression.name);
      }

      return instance;
    };

    function addIdentifier(locals, identifier, instance, name) {
      if (!(locals && isObject(locals.$scope))) {
        throw minErr('$controller')('noscp',
          'Cannot export controller \'{0}\' as \'{1}\'! No $scope object provided via `locals`.',
          name, identifier);
      }

      locals.$scope[identifier] = instance;
    }
  }];
}

/**
 * @ngdoc service
 * @name $document
 * @requires $window
 * @this
 *
 * @description
 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object.
 *
 * @example
   <example module="documentExample" name="document">
     <file name="index.html">
       <div ng-controller="ExampleController">
         <p>$document title: <b ng-bind="title"></b></p>
         <p>window.document title: <b ng-bind="windowTitle"></b></p>
       </div>
     </file>
     <file name="script.js">
       angular.module('documentExample', [])
         .controller('ExampleController', ['$scope', '$document', function($scope, $document) {
           $scope.title = $document[0].title;
           $scope.windowTitle = angular.element(window.document)[0].title;
         }]);
     </file>
   </example>
 */
function $DocumentProvider() {
  this.$get = ['$window', function(window) {
    return jqLite(window.document);
  }];
}


/**
 * @private
 * @this
 * Listens for document visibility change and makes the current status accessible.
 */
function $$IsDocumentHiddenProvider() {
  this.$get = ['$document', '$rootScope', function($document, $rootScope) {
    var doc = $document[0];
    var hidden = doc && doc.hidden;

    $document.on('visibilitychange', changeListener);

    $rootScope.$on('$destroy', function() {
      $document.off('visibilitychange', changeListener);
    });

    function changeListener() {
      hidden = doc.hidden;
    }

    return function() {
      return hidden;
    };
  }];
}

/**
 * @ngdoc service
 * @name $exceptionHandler
 * @requires ng.$log
 * @this
 *
 * @description
 * Any uncaught exception in AngularJS expressions is delegated to this service.
 * The default implementation simply delegates to `$log.error` which logs it into
 * the browser console.
 *
 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by
 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing.
 *
 * ## Example:
 *
 * The example below will overwrite the default `$exceptionHandler` in order to (a) log uncaught
 * errors to the backend for later inspection by the developers and (b) to use `$log.warn()` instead
 * of `$log.error()`.
 *
 * ```js
 *   angular.
 *     module('exceptionOverwrite', []).
 *     factory('$exceptionHandler', ['$log', 'logErrorsToBackend', function($log, logErrorsToBackend) {
 *       return function myExceptionHandler(exception, cause) {
 *         logErrorsToBackend(exception, cause);
 *         $log.warn(exception, cause);
 *       };
 *     }]);
 * ```
 *
 * <hr />
 * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind`
 * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler}
 * (unless executed during a digest).
 *
 * If you wish, you can manually delegate exceptions, e.g.
 * `try { ... } catch(e) { $exceptionHandler(e); }`
 *
 * @param {Error} exception Exception associated with the error.
 * @param {string=} cause Optional information about the context in which
 *       the error was thrown.
 *
 */
function $ExceptionHandlerProvider() {
  this.$get = ['$log', function($log) {
    return function(exception, cause) {
      $log.error.apply($log, arguments);
    };
  }];
}

var $$ForceReflowProvider = /** @this */ function() {
  this.$get = ['$document', function($document) {
    return function(domNode) {
      //the line below will force the browser to perform a repaint so
      //that all the animated elements within the animation frame will
      //be properly updated and drawn on screen. This is required to
      //ensure that the preparation animation is properly flushed so that
      //the active state picks up from there. DO NOT REMOVE THIS LINE.
      //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH
      //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND
      //WILL TAKE YEARS AWAY FROM YOUR LIFE.
      if (domNode) {
        if (!domNode.nodeType && domNode instanceof jqLite) {
          domNode = domNode[0];
        }
      } else {
        domNode = $document[0].body;
      }
      return domNode.offsetWidth + 1;
    };
  }];
};

var APPLICATION_JSON = 'application/json';
var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};
var JSON_START = /^\[|^\{(?!\{)/;
var JSON_ENDS = {
  '[': /]$/,
  '{': /}$/
};
var JSON_PROTECTION_PREFIX = /^\)]\}',?\n/;
var $httpMinErr = minErr('$http');

function serializeValue(v) {
  if (isObject(v)) {
    return isDate(v) ? v.toISOString() : toJson(v);
  }
  return v;
}


/** @this */
function $HttpParamSerializerProvider() {
  /**
   * @ngdoc service
   * @name $httpParamSerializer
   * @description
   *
   * Default {@link $http `$http`} params serializer that converts objects to strings
   * according to the following rules:
   *
   * * `{'foo': 'bar'}` results in `foo=bar`
   * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object)
   * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element)
   * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D` (stringified and encoded representation of an object)
   *
   * Note that serializer will sort the request parameters alphabetically.
   */

  this.$get = function() {
    return function ngParamSerializer(params) {
      if (!params) return '';
      var parts = [];
      forEachSorted(params, function(value, key) {
        if (value === null || isUndefined(value) || isFunction(value)) return;
        if (isArray(value)) {
          forEach(value, function(v) {
            parts.push(encodeUriQuery(key)  + '=' + encodeUriQuery(serializeValue(v)));
          });
        } else {
          parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value)));
        }
      });

      return parts.join('&');
    };
  };
}

/** @this */
function $HttpParamSerializerJQLikeProvider() {
  /**
   * @ngdoc service
   * @name $httpParamSerializerJQLike
   *
   * @description
   *
   * Alternative {@link $http `$http`} params serializer that follows
   * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic.
   * The serializer will also sort the params alphabetically.
   *
   * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property:
   *
   * ```js
   * $http({
   *   url: myUrl,
   *   method: 'GET',
   *   params: myParams,
   *   paramSerializer: '$httpParamSerializerJQLike'
   * });
   * ```
   *
   * It is also possible to set it as the default `paramSerializer` in the
   * {@link $httpProvider#defaults `$httpProvider`}.
   *
   * Additionally, you can inject the serializer and use it explicitly, for example to serialize
   * form data for submission:
   *
   * ```js
   * .controller(function($http, $httpParamSerializerJQLike) {
   *   //...
   *
   *   $http({
   *     url: myUrl,
   *     method: 'POST',
   *     data: $httpParamSerializerJQLike(myData),
   *     headers: {
   *       'Content-Type': 'application/x-www-form-urlencoded'
   *     }
   *   });
   *
   * });
   * ```
   *
   */
  this.$get = function() {
    return function jQueryLikeParamSerializer(params) {
      if (!params) return '';
      var parts = [];
      serialize(params, '', true);
      return parts.join('&');

      function serialize(toSerialize, prefix, topLevel) {
        if (isArray(toSerialize)) {
          forEach(toSerialize, function(value, index) {
            serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']');
          });
        } else if (isObject(toSerialize) && !isDate(toSerialize)) {
          forEachSorted(toSerialize, function(value, key) {
            serialize(value, prefix +
                (topLevel ? '' : '[') +
                key +
                (topLevel ? '' : ']'));
          });
        } else {
          if (isFunction(toSerialize)) {
            toSerialize = toSerialize();
          }
          parts.push(encodeUriQuery(prefix) + '=' +
              (toSerialize == null ? '' : encodeUriQuery(serializeValue(toSerialize))));
        }
      }
    };
  };
}

function defaultHttpResponseTransform(data, headers) {
  if (isString(data)) {
    // Strip json vulnerability protection prefix and trim whitespace
    var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim();

    if (tempData) {
      var contentType = headers('Content-Type');
      var hasJsonContentType = contentType && (contentType.indexOf(APPLICATION_JSON) === 0);

      if (hasJsonContentType || isJsonLike(tempData)) {
        try {
          data = fromJson(tempData);
        } catch (e) {
          if (!hasJsonContentType) {
            return data;
          }
          throw $httpMinErr('baddata', 'Data must be a valid JSON object. Received: "{0}". ' +
          'Parse error: "{1}"', data, e);
        }
      }
    }
  }

  return data;
}

function isJsonLike(str) {
    var jsonStart = str.match(JSON_START);
    return jsonStart && JSON_ENDS[jsonStart[0]].test(str);
}

/**
 * Parse headers into key value object
 *
 * @param {string} headers Raw headers as a string
 * @returns {Object} Parsed headers as key value object
 */
function parseHeaders(headers) {
  var parsed = createMap(), i;

  function fillInParsed(key, val) {
    if (key) {
      parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
    }
  }

  if (isString(headers)) {
    forEach(headers.split('\n'), function(line) {
      i = line.indexOf(':');
      fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1)));
    });
  } else if (isObject(headers)) {
    forEach(headers, function(headerVal, headerKey) {
      fillInParsed(lowercase(headerKey), trim(headerVal));
    });
  }

  return parsed;
}


/**
 * Returns a function that provides access to parsed headers.
 *
 * Headers are lazy parsed when first requested.
 * @see parseHeaders
 *
 * @param {(string|Object)} headers Headers to provide access to.
 * @returns {function(string=)} Returns a getter function which if called with:
 *
 *   - if called with an argument returns a single header value or null
 *   - if called with no arguments returns an object containing all headers.
 */
function headersGetter(headers) {
  var headersObj;

  return function(name) {
    if (!headersObj) headersObj =  parseHeaders(headers);

    if (name) {
      var value = headersObj[lowercase(name)];
      if (value === undefined) {
        value = null;
      }
      return value;
    }

    return headersObj;
  };
}


/**
 * Chain all given functions
 *
 * This function is used for both request and response transforming
 *
 * @param {*} data Data to transform.
 * @param {function(string=)} headers HTTP headers getter fn.
 * @param {number} status HTTP status code of the response.
 * @param {(Function|Array.<Function>)} fns Function or an array of functions.
 * @returns {*} Transformed data.
 */
function transformData(data, headers, status, fns) {
  if (isFunction(fns)) {
    return fns(data, headers, status);
  }

  forEach(fns, function(fn) {
    data = fn(data, headers, status);
  });

  return data;
}


function isSuccess(status) {
  return 200 <= status && status < 300;
}


/**
 * @ngdoc provider
 * @name $httpProvider
 * @this
 *
 * @description
 * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service.
 */
function $HttpProvider() {
  /**
   * @ngdoc property
   * @name $httpProvider#defaults
   * @description
   *
   * Object containing default values for all {@link ng.$http $http} requests.
   *
   * - **`defaults.cache`** - {boolean|Object} - A boolean value or object created with
   * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of HTTP responses
   * by default. See {@link $http#caching $http Caching} for more information.
   *
   * - **`defaults.headers`** - {Object} - Default headers for all $http requests.
   * Refer to {@link ng.$http#setting-http-headers $http} for documentation on
   * setting default headers.
   *     - **`defaults.headers.common`**
   *     - **`defaults.headers.post`**
   *     - **`defaults.headers.put`**
   *     - **`defaults.headers.patch`**
   *
   * - **`defaults.jsonpCallbackParam`** - `{string}` - the name of the query parameter that passes the name of the
   * callback in a JSONP request. The value of this parameter will be replaced with the expression generated by the
   * {@link $jsonpCallbacks} service. Defaults to `'callback'`.
   *
   * - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function
   *  used to the prepare string representation of request parameters (specified as an object).
   *  If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}.
   *  Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}.
   *
   * - **`defaults.transformRequest`** -
   * `{Array<function(data, headersGetter)>|function(data, headersGetter)}` -
   * An array of functions (or a single function) which are applied to the request data.
   * By default, this is an array with one request transformation function:
   *
   *   - If the `data` property of the request configuration object contains an object, serialize it
   *     into JSON format.
   *
   * - **`defaults.transformResponse`** -
   * `{Array<function(data, headersGetter, status)>|function(data, headersGetter, status)}` -
   * An array of functions (or a single function) which are applied to the response data. By default,
   * this is an array which applies one response transformation function that does two things:
   *
   *  - If XSRF prefix is detected, strip it
   *    (see {@link ng.$http#security-considerations Security Considerations in the $http docs}).
   *  - If the `Content-Type` is `application/json` or the response looks like JSON,
   *    deserialize it using a JSON parser.
   *
   * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token.
   * Defaults value is `'XSRF-TOKEN'`.
   *
   * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the
   * XSRF token. Defaults value is `'X-XSRF-TOKEN'`.
   *
   */
  var defaults = this.defaults = {
    // transform incoming response data
    transformResponse: [defaultHttpResponseTransform],

    // transform outgoing request data
    transformRequest: [function(d) {
      return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d;
    }],

    // default headers
    headers: {
      common: {
        'Accept': 'application/json, text/plain, */*'
      },
      post:   shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
      put:    shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
      patch:  shallowCopy(CONTENT_TYPE_APPLICATION_JSON)
    },

    xsrfCookieName: 'XSRF-TOKEN',
    xsrfHeaderName: 'X-XSRF-TOKEN',

    paramSerializer: '$httpParamSerializer',

    jsonpCallbackParam: 'callback'
  };

  var useApplyAsync = false;
  /**
   * @ngdoc method
   * @name $httpProvider#useApplyAsync
   * @description
   *
   * Configure $http service to combine processing of multiple http responses received at around
   * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in
   * significant performance improvement for bigger applications that make many HTTP requests
   * concurrently (common during application bootstrap).
   *
   * Defaults to false. If no value is specified, returns the current configured value.
   *
   * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred
   *    "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window
   *    to load and share the same digest cycle.
   *
   * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
   *    otherwise, returns the current configured value.
   */
  this.useApplyAsync = function(value) {
    if (isDefined(value)) {
      useApplyAsync = !!value;
      return this;
    }
    return useApplyAsync;
  };

  /**
   * @ngdoc property
   * @name $httpProvider#interceptors
   * @description
   *
   * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http}
   * pre-processing of request or postprocessing of responses.
   *
   * These service factories are ordered by request, i.e. they are applied in the same order as the
   * array, on request, but reverse order, on response.
   *
   * {@link ng.$http#interceptors Interceptors detailed info}
   */
  var interceptorFactories = this.interceptors = [];

  /**
   * @ngdoc property
   * @name $httpProvider#xsrfTrustedOrigins
   * @description
   *
   * Array containing URLs whose origins are trusted to receive the XSRF token. See the
   * {@link ng.$http#security-considerations Security Considerations} sections for more details on
   * XSRF.
   *
   * **Note:** An "origin" consists of the [URI scheme](https://en.wikipedia.org/wiki/URI_scheme),
   * the [hostname](https://en.wikipedia.org/wiki/Hostname) and the
   * [port number](https://en.wikipedia.org/wiki/Port_(computer_networking). For `http:` and
   * `https:`, the port number can be omitted if using th default ports (80 and 443 respectively).
   * Examples: `http://example.com`, `https://api.example.com:9876`
   *
   * <div class="alert alert-warning">
   *   It is not possible to trust specific URLs/paths. The `path`, `query` and `fragment` parts
   *   of a URL will be ignored. For example, `https://foo.com/path/bar?query=baz#fragment` will be
   *   treated as `https://foo.com`, meaning that **all** requests to URLs starting with
   *   `https://foo.com/` will include the XSRF token.
   * </div>
   *
   * @example
   *
   * ```js
   * // App served from `https://example.com/`.
   * angular.
   *   module('xsrfTrustedOriginsExample', []).
   *   config(['$httpProvider', function($httpProvider) {
   *     $httpProvider.xsrfTrustedOrigins.push('https://api.example.com');
   *   }]).
   *   run(['$http', function($http) {
   *     // The XSRF token will be sent.
   *     $http.get('https://api.example.com/preferences').then(...);
   *
   *     // The XSRF token will NOT be sent.
   *     $http.get('https://stats.example.com/activity').then(...);
   *   }]);
   * ```
   */
  var xsrfTrustedOrigins = this.xsrfTrustedOrigins = [];

  /**
   * @ngdoc property
   * @name $httpProvider#xsrfWhitelistedOrigins
   * @description
   *
   * @deprecated
   * sinceVersion="1.8.1"
   *
   * This property is deprecated. Use {@link $httpProvider#xsrfTrustedOrigins xsrfTrustedOrigins}
   * instead.
   */
  Object.defineProperty(this, 'xsrfWhitelistedOrigins', {
    get: function() {
      return this.xsrfTrustedOrigins;
    },
    set: function(origins) {
      this.xsrfTrustedOrigins = origins;
    }
  });

  this.$get = ['$browser', '$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector', '$sce',
      function($browser, $httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector, $sce) {

    var defaultCache = $cacheFactory('$http');

    /**
     * Make sure that default param serializer is exposed as a function
     */
    defaults.paramSerializer = isString(defaults.paramSerializer) ?
      $injector.get(defaults.paramSerializer) : defaults.paramSerializer;

    /**
     * Interceptors stored in reverse order. Inner interceptors before outer interceptors.
     * The reversal is needed so that we can build up the interception chain around the
     * server request.
     */
    var reversedInterceptors = [];

    forEach(interceptorFactories, function(interceptorFactory) {
      reversedInterceptors.unshift(isString(interceptorFactory)
          ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory));
    });

    /**
     * A function to check request URLs against a list of allowed origins.
     */
    var urlIsAllowedOrigin = urlIsAllowedOriginFactory(xsrfTrustedOrigins);

    /**
     * @ngdoc service
     * @kind function
     * @name $http
     * @requires ng.$httpBackend
     * @requires $cacheFactory
     * @requires $rootScope
     * @requires $q
     * @requires $injector
     *
     * @description
     * The `$http` service is a core AngularJS service that facilitates communication with the remote
     * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest)
     * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP).
     *
     * For unit testing applications that use `$http` service, see
     * {@link ngMock.$httpBackend $httpBackend mock}.
     *
     * For a higher level of abstraction, please check out the {@link ngResource.$resource
     * $resource} service.
     *
     * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by
     * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage
     * it is important to familiarize yourself with these APIs and the guarantees they provide.
     *
     *
     * ## General usage
     * The `$http` service is a function which takes a single argument — a {@link $http#usage configuration object} —
     * that is used to generate an HTTP request and returns  a {@link ng.$q promise} that is
     * resolved (request success) or rejected (request failure) with a
     * {@link ng.$http#$http-returns response} object.
     *
     * ```js
     *   // Simple GET request example:
     *   $http({
     *     method: 'GET',
     *     url: '/someUrl'
     *   }).then(function successCallback(response) {
     *       // this callback will be called asynchronously
     *       // when the response is available
     *     }, function errorCallback(response) {
     *       // called asynchronously if an error occurs
     *       // or server returns response with an error status.
     *     });
     * ```
     *
     *
     * ## Shortcut methods
     *
     * Shortcut methods are also available. All shortcut methods require passing in the URL, and
     * request data must be passed in for POST/PUT requests. An optional config can be passed as the
     * last argument.
     *
     * ```js
     *   $http.get('/someUrl', config).then(successCallback, errorCallback);
     *   $http.post('/someUrl', data, config).then(successCallback, errorCallback);
     * ```
     *
     * Complete list of shortcut methods:
     *
     * - {@link ng.$http#get $http.get}
     * - {@link ng.$http#head $http.head}
     * - {@link ng.$http#post $http.post}
     * - {@link ng.$http#put $http.put}
     * - {@link ng.$http#delete $http.delete}
     * - {@link ng.$http#jsonp $http.jsonp}
     * - {@link ng.$http#patch $http.patch}
     *
     *
     * ## Writing Unit Tests that use $http
     * When unit testing (using {@link ngMock ngMock}), it is necessary to call
     * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending
     * request using trained responses.
     *
     * ```
     * $httpBackend.expectGET(...);
     * $http.get(...);
     * $httpBackend.flush();
     * ```
     *
     * ## Setting HTTP Headers
     *
     * The $http service will automatically add certain HTTP headers to all requests. These defaults
     * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration
     * object, which currently contains this default configuration:
     *
     * - `$httpProvider.defaults.headers.common` (headers that are common for all requests):
     *   - <code>Accept: application/json, text/plain, \*&#65279;/&#65279;\*</code>
     * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests)
     *   - `Content-Type: application/json`
     * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests)
     *   - `Content-Type: application/json`
     *
     * To add or overwrite these defaults, simply add or remove a property from these configuration
     * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object
     * with the lowercased HTTP method name as the key, e.g.
     * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`.
     *
     * The defaults can also be set at runtime via the `$http.defaults` object in the same
     * fashion. For example:
     *
     * ```
     * module.run(function($http) {
     *   $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w';
     * });
     * ```
     *
     * In addition, you can supply a `headers` property in the config object passed when
     * calling `$http(config)`, which overrides the defaults without changing them globally.
     *
     * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis,
     * Use the `headers` property, setting the desired header to `undefined`. For example:
     *
     * ```js
     * var req = {
     *  method: 'POST',
     *  url: 'http://example.com',
     *  headers: {
     *    'Content-Type': undefined
     *  },
     *  data: { test: 'test' }
     * }
     *
     * $http(req).then(function(){...}, function(){...});
     * ```
     *
     * ## Transforming Requests and Responses
     *
     * Both requests and responses can be transformed using transformation functions: `transformRequest`
     * and `transformResponse`. These properties can be a single function that returns
     * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions,
     * which allows you to `push` or `unshift` a new transformation function into the transformation chain.
     *
     * <div class="alert alert-warning">
     * **Note:** AngularJS does not make a copy of the `data` parameter before it is passed into the `transformRequest` pipeline.
     * That means changes to the properties of `data` are not local to the transform function (since Javascript passes objects by reference).
     * For example, when calling `$http.get(url, $scope.myObject)`, modifications to the object's properties in a transformRequest
     * function will be reflected on the scope and in any templates where the object is data-bound.
     * To prevent this, transform functions should have no side-effects.
     * If you need to modify properties, it is recommended to make a copy of the data, or create new object to return.
     * </div>
     *
     * ### Default Transformations
     *
     * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and
     * `defaults.transformResponse` properties. If a request does not provide its own transformations
     * then these will be applied.
     *
     * You can augment or replace the default transformations by modifying these properties by adding to or
     * replacing the array.
     *
     * AngularJS provides the following default transformations:
     *
     * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`) is
     * an array with one function that does the following:
     *
     * - If the `data` property of the request configuration object contains an object, serialize it
     *   into JSON format.
     *
     * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`) is
     * an array with one function that does the following:
     *
     *  - If XSRF prefix is detected, strip it (see Security Considerations section below).
     *  - If the `Content-Type` is `application/json` or the response looks like JSON,
   *      deserialize it using a JSON parser.
     *
     *
     * ### Overriding the Default Transformations Per Request
     *
     * If you wish to override the request/response transformations only for a single request then provide
     * `transformRequest` and/or `transformResponse` properties on the configuration object passed
     * into `$http`.
     *
     * Note that if you provide these properties on the config object the default transformations will be
     * overwritten. If you wish to augment the default transformations then you must include them in your
     * local transformation array.
     *
     * The following code demonstrates adding a new response transformation to be run after the default response
     * transformations have been run.
     *
     * ```js
     * function appendTransform(defaults, transform) {
     *
     *   // We can't guarantee that the default transformation is an array
     *   defaults = angular.isArray(defaults) ? defaults : [defaults];
     *
     *   // Append the new transformation to the defaults
     *   return defaults.concat(transform);
     * }
     *
     * $http({
     *   url: '...',
     *   method: 'GET',
     *   transformResponse: appendTransform($http.defaults.transformResponse, function(value) {
     *     return doTransform(value);
     *   })
     * });
     * ```
     *
     *
     * ## Caching
     *
     * {@link ng.$http `$http`} responses are not cached by default. To enable caching, you must
     * set the config.cache value or the default cache value to TRUE or to a cache object (created
     * with {@link ng.$cacheFactory `$cacheFactory`}). If defined, the value of config.cache takes
     * precedence over the default cache value.
     *
     * In order to:
     *   * cache all responses - set the default cache value to TRUE or to a cache object
     *   * cache a specific response - set config.cache value to TRUE or to a cache object
     *
     * If caching is enabled, but neither the default cache nor config.cache are set to a cache object,
     * then the default `$cacheFactory("$http")` object is used.
     *
     * The default cache value can be set by updating the
     * {@link ng.$http#defaults `$http.defaults.cache`} property or the
     * {@link $httpProvider#defaults `$httpProvider.defaults.cache`} property.
     *
     * When caching is enabled, {@link ng.$http `$http`} stores the response from the server using
     * the relevant cache object. The next time the same request is made, the response is returned
     * from the cache without sending a request to the server.
     *
     * Take note that:
     *
     *   * Only GET and JSONP requests are cached.
     *   * The cache key is the request URL including search parameters; headers are not considered.
     *   * Cached responses are returned asynchronously, in the same way as responses from the server.
     *   * If multiple identical requests are made using the same cache, which is not yet populated,
     *     one request will be made to the server and remaining requests will return the same response.
     *   * A cache-control header on the response does not affect if or how responses are cached.
     *
     *
     * ## Interceptors
     *
     * Before you start creating interceptors, be sure to understand the
     * {@link ng.$q $q and deferred/promise APIs}.
     *
     * For purposes of global error handling, authentication, or any kind of synchronous or
     * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be
     * able to intercept requests before they are handed to the server and
     * responses before they are handed over to the application code that
     * initiated these requests. The interceptors leverage the {@link ng.$q
     * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing.
     *
     * The interceptors are service factories that are registered with the `$httpProvider` by
     * adding them to the `$httpProvider.interceptors` array. The factory is called and
     * injected with dependencies (if specified) and returns the interceptor.
     *
     * There are two kinds of interceptors (and two kinds of rejection interceptors):
     *
     *   * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to
     *     modify the `config` object or create a new one. The function needs to return the `config`
     *     object directly, or a promise containing the `config` or a new `config` object.
     *   * `requestError`: interceptor gets called when a previous interceptor threw an error or
     *     resolved with a rejection.
     *   * `response`: interceptors get called with http `response` object. The function is free to
     *     modify the `response` object or create a new one. The function needs to return the `response`
     *     object directly, or as a promise containing the `response` or a new `response` object.
     *   * `responseError`: interceptor gets called when a previous interceptor threw an error or
     *     resolved with a rejection.
     *
     *
     * ```js
     *   // register the interceptor as a service
     *   $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
     *     return {
     *       // optional method
     *       'request': function(config) {
     *         // do something on success
     *         return config;
     *       },
     *
     *       // optional method
     *      'requestError': function(rejection) {
     *         // do something on error
     *         if (canRecover(rejection)) {
     *           return responseOrNewPromise
     *         }
     *         return $q.reject(rejection);
     *       },
     *
     *
     *
     *       // optional method
     *       'response': function(response) {
     *         // do something on success
     *         return response;
     *       },
     *
     *       // optional method
     *      'responseError': function(rejection) {
     *         // do something on error
     *         if (canRecover(rejection)) {
     *           return responseOrNewPromise
     *         }
     *         return $q.reject(rejection);
     *       }
     *     };
     *   });
     *
     *   $httpProvider.interceptors.push('myHttpInterceptor');
     *
     *
     *   // alternatively, register the interceptor via an anonymous factory
     *   $httpProvider.interceptors.push(function($q, dependency1, dependency2) {
     *     return {
     *      'request': function(config) {
     *          // same as above
     *       },
     *
     *       'response': function(response) {
     *          // same as above
     *       }
     *     };
     *   });
     * ```
     *
     * ## Security Considerations
     *
     * When designing web applications, consider security threats from:
     *
     * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
     * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery)
     *
     * Both server and the client must cooperate in order to eliminate these threats. AngularJS comes
     * pre-configured with strategies that address these issues, but for this to work backend server
     * cooperation is required.
     *
     * ### JSON Vulnerability Protection
     *
     * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
     * allows third party website to turn your JSON resource URL into
     * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To
     * counter this your server can prefix all JSON requests with following string `")]}',\n"`.
     * AngularJS will automatically strip the prefix before processing it as JSON.
     *
     * For example if your server needs to return:
     * ```js
     * ['one','two']
     * ```
     *
     * which is vulnerable to attack, your server can return:
     * ```js
     * )]}',
     * ['one','two']
     * ```
     *
     * AngularJS will strip the prefix, before processing the JSON.
     *
     *
     * ### Cross Site Request Forgery (XSRF) Protection
     *
     * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is an attack technique by
     * which the attacker can trick an authenticated user into unknowingly executing actions on your
     * website. AngularJS provides a mechanism to counter XSRF. When performing XHR requests, the
     * $http service reads a token from a cookie (by default, `XSRF-TOKEN`) and sets it as an HTTP
     * header (by default `X-XSRF-TOKEN`). Since only JavaScript that runs on your domain could read
     * the cookie, your server can be assured that the XHR came from JavaScript running on your
     * domain.
     *
     * To take advantage of this, your server needs to set a token in a JavaScript readable session
     * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the
     * server can verify that the cookie matches the `X-XSRF-TOKEN` HTTP header, and therefore be
     * sure that only JavaScript running on your domain could have sent the request. The token must
     * be unique for each user and must be verifiable by the server (to prevent the JavaScript from
     * making up its own tokens). We recommend that the token is a digest of your site's
     * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography&#41;)
     * for added security.
     *
     * The header will &mdash; by default &mdash; **not** be set for cross-domain requests. This
     * prevents unauthorized servers (e.g. malicious or compromised 3rd-party APIs) from gaining
     * access to your users' XSRF tokens and exposing them to Cross Site Request Forgery. If you
     * want to, you can trust additional origins to also receive the XSRF token, by adding them
     * to {@link ng.$httpProvider#xsrfTrustedOrigins xsrfTrustedOrigins}. This might be
     * useful, for example, if your application, served from `example.com`, needs to access your API
     * at `api.example.com`.
     * See {@link ng.$httpProvider#xsrfTrustedOrigins $httpProvider.xsrfTrustedOrigins} for
     * more details.
     *
     * <div class="alert alert-danger">
     *   **Warning**<br />
     *   Only trusted origins that you have control over and make sure you understand the
     *   implications of doing so.
     * </div>
     *
     * The name of the cookie and the header can be specified using the `xsrfCookieName` and
     * `xsrfHeaderName` properties of either `$httpProvider.defaults` at config-time,
     * `$http.defaults` at run-time, or the per-request config object.
     *
     * In order to prevent collisions in environments where multiple AngularJS apps share the
     * same domain or subdomain, we recommend that each application uses a unique cookie name.
     *
     *
     * @param {object} config Object describing the request to be made and how it should be
     *    processed. The object has following properties:
     *
     *    - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc)
     *    - **url** – `{string|TrustedObject}` – Absolute or relative URL of the resource that is being requested;
     *      or an object created by a call to `$sce.trustAsResourceUrl(url)`.
     *    - **params** – `{Object.<string|Object>}` – Map of strings or objects which will be serialized
     *      with the `paramSerializer` and appended as GET parameters.
     *    - **data** – `{string|Object}` – Data to be sent as the request message data.
     *    - **headers** – `{Object}` – Map of strings or functions which return strings representing
     *      HTTP headers to send to the server. If the return value of a function is null, the
     *      header will not be sent. Functions accept a config object as an argument.
     *    - **eventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest object.
     *      To bind events to the XMLHttpRequest upload object, use `uploadEventHandlers`.
     *      The handler will be called in the context of a `$apply` block.
     *    - **uploadEventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest upload
     *      object. To bind events to the XMLHttpRequest object, use `eventHandlers`.
     *      The handler will be called in the context of a `$apply` block.
     *    - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token.
     *    - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token.
     *    - **transformRequest** –
     *      `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
     *      transform function or an array of such functions. The transform function takes the http
     *      request body and headers and returns its transformed (typically serialized) version.
     *      See {@link ng.$http#overriding-the-default-transformations-per-request
     *      Overriding the Default Transformations}
     *    - **transformResponse** –
     *      `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` –
     *      transform function or an array of such functions. The transform function takes the http
     *      response body, headers and status and returns its transformed (typically deserialized) version.
     *      See {@link ng.$http#overriding-the-default-transformations-per-request
     *      Overriding the Default Transformations}
     *    - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to
     *      prepare the string representation of request parameters (specified as an object).
     *      If specified as string, it is interpreted as function registered with the
     *      {@link $injector $injector}, which means you can create your own serializer
     *      by registering it as a {@link auto.$provide#service service}.
     *      The default serializer is the {@link $httpParamSerializer $httpParamSerializer};
     *      alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike}
     *    - **cache** – `{boolean|Object}` – A boolean value or object created with
     *      {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of the HTTP response.
     *      See {@link $http#caching $http Caching} for more information.
     *    - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise}
     *      that should abort the request when resolved.
     *
     *      A numerical timeout or a promise returned from {@link ng.$timeout $timeout}, will set
     *      the `xhrStatus` in the {@link $http#$http-returns response} to "timeout", and any other
     *      resolved promise will set it to "abort", following standard XMLHttpRequest behavior.
     *
     *    - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the
     *      XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials)
     *      for more information.
     *    - **responseType** - `{string}` - see
     *      [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype).
     *
     * @returns {HttpPromise} A {@link ng.$q `Promise}` that will be resolved (request success)
     *   or rejected (request failure) with a response object.
     *
     *   The response object has these properties:
     *
     *   - **data** – `{string|Object}` – The response body transformed with
     *     the transform functions.
     *   - **status** – `{number}` – HTTP status code of the response.
     *   - **headers** – `{function([headerName])}` – Header getter function.
     *   - **config** – `{Object}` – The configuration object that was used
     *     to generate the request.
     *   - **statusText** – `{string}` – HTTP status text of the response.
     *   - **xhrStatus** – `{string}` – Status of the XMLHttpRequest
     *     (`complete`, `error`, `timeout` or `abort`).
     *
     *
     *   A response status code between 200 and 299 is considered a success status
     *   and will result in the success callback being called. Any response status
     *   code outside of that range is considered an error status and will result
     *   in the error callback being called.
     *   Also, status codes less than -1 are normalized to zero. -1 usually means
     *   the request was aborted, e.g. using a `config.timeout`. More information
     *   about the status might be available in the `xhrStatus` property.
     *
     *   Note that if the response is a redirect, XMLHttpRequest will transparently
     *   follow it, meaning that the outcome (success or error) will be determined
     *   by the final response status code.
     *
     *
     * @property {Array.<Object>} pendingRequests Array of config objects for currently pending
     *   requests. This is primarily meant to be used for debugging purposes.
     *
     *
     * @example
<example module="httpExample" name="http-service">
<file name="index.html">
  <div ng-controller="FetchController">
    <select ng-model="method" aria-label="Request method">
      <option>GET</option>
      <option>JSONP</option>
    </select>
    <input type="text" ng-model="url" size="80" aria-label="URL" />
    <button id="fetchbtn" ng-click="fetch()">fetch</button><br>
    <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button>
    <button id="samplejsonpbtn"
      ng-click="updateModel('JSONP',
                    'https://angularjs.org/greet.php?name=Super%20Hero')">
      Sample JSONP
    </button>
    <button id="invalidjsonpbtn"
      ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist')">
        Invalid JSONP
      </button>
    <pre>http status code: {{status}}</pre>
    <pre>http response data: {{data}}</pre>
  </div>
</file>
<file name="script.js">
  angular.module('httpExample', [])
    .config(['$sceDelegateProvider', function($sceDelegateProvider) {
      // We must add the JSONP endpoint that we are using to the trusted list to show that we trust it
      $sceDelegateProvider.trustedResourceUrlList([
        'self',
        'https://angularjs.org/**'
      ]);
    }])
    .controller('FetchController', ['$scope', '$http', '$templateCache',
      function($scope, $http, $templateCache) {
        $scope.method = 'GET';
        $scope.url = 'http-hello.html';

        $scope.fetch = function() {
          $scope.code = null;
          $scope.response = null;

          $http({method: $scope.method, url: $scope.url, cache: $templateCache}).
            then(function(response) {
              $scope.status = response.status;
              $scope.data = response.data;
            }, function(response) {
              $scope.data = response.data || 'Request failed';
              $scope.status = response.status;
          });
        };

        $scope.updateModel = function(method, url) {
          $scope.method = method;
          $scope.url = url;
        };
      }]);
</file>
<file name="http-hello.html">
  Hello, $http!
</file>
<file name="protractor.js" type="protractor">
  var status = element(by.binding('status'));
  var data = element(by.binding('data'));
  var fetchBtn = element(by.id('fetchbtn'));
  var sampleGetBtn = element(by.id('samplegetbtn'));
  var invalidJsonpBtn = element(by.id('invalidjsonpbtn'));

  it('should make an xhr GET request', function() {
    sampleGetBtn.click();
    fetchBtn.click();
    expect(status.getText()).toMatch('200');
    expect(data.getText()).toMatch(/Hello, \$http!/);
  });

// Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185
// it('should make a JSONP request to angularjs.org', function() {
//   var sampleJsonpBtn = element(by.id('samplejsonpbtn'));
//   sampleJsonpBtn.click();
//   fetchBtn.click();
//   expect(status.getText()).toMatch('200');
//   expect(data.getText()).toMatch(/Super Hero!/);
// });

  it('should make JSONP request to invalid URL and invoke the error handler',
      function() {
    invalidJsonpBtn.click();
    fetchBtn.click();
    expect(status.getText()).toMatch('0');
    expect(data.getText()).toMatch('Request failed');
  });
</file>
</example>
     */
    function $http(requestConfig) {

      if (!isObject(requestConfig)) {
        throw minErr('$http')('badreq', 'Http request configuration must be an object.  Received: {0}', requestConfig);
      }

      if (!isString($sce.valueOf(requestConfig.url))) {
        throw minErr('$http')('badreq', 'Http request configuration url must be a string or a $sce trusted object.  Received: {0}', requestConfig.url);
      }

      var config = extend({
        method: 'get',
        transformRequest: defaults.transformRequest,
        transformResponse: defaults.transformResponse,
        paramSerializer: defaults.paramSerializer,
        jsonpCallbackParam: defaults.jsonpCallbackParam
      }, requestConfig);

      config.headers = mergeHeaders(requestConfig);
      config.method = uppercase(config.method);
      config.paramSerializer = isString(config.paramSerializer) ?
          $injector.get(config.paramSerializer) : config.paramSerializer;

      $browser.$$incOutstandingRequestCount('$http');

      var requestInterceptors = [];
      var responseInterceptors = [];
      var promise = $q.resolve(config);

      // apply interceptors
      forEach(reversedInterceptors, function(interceptor) {
        if (interceptor.request || interceptor.requestError) {
          requestInterceptors.unshift(interceptor.request, interceptor.requestError);
        }
        if (interceptor.response || interceptor.responseError) {
          responseInterceptors.push(interceptor.response, interceptor.responseError);
        }
      });

      promise = chainInterceptors(promise, requestInterceptors);
      promise = promise.then(serverRequest);
      promise = chainInterceptors(promise, responseInterceptors);
      promise = promise.finally(completeOutstandingRequest);

      return promise;


      function chainInterceptors(promise, interceptors) {
        for (var i = 0, ii = interceptors.length; i < ii;) {
          var thenFn = interceptors[i++];
          var rejectFn = interceptors[i++];

          promise = promise.then(thenFn, rejectFn);
        }

        interceptors.length = 0;

        return promise;
      }

      function completeOutstandingRequest() {
        $browser.$$completeOutstandingRequest(noop, '$http');
      }

      function executeHeaderFns(headers, config) {
        var headerContent, processedHeaders = {};

        forEach(headers, function(headerFn, header) {
          if (isFunction(headerFn)) {
            headerContent = headerFn(config);
            if (headerContent != null) {
              processedHeaders[header] = headerContent;
            }
          } else {
            processedHeaders[header] = headerFn;
          }
        });

        return processedHeaders;
      }

      function mergeHeaders(config) {
        var defHeaders = defaults.headers,
            reqHeaders = extend({}, config.headers),
            defHeaderName, lowercaseDefHeaderName, reqHeaderName;

        defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]);

        // using for-in instead of forEach to avoid unnecessary iteration after header has been found
        defaultHeadersIteration:
        for (defHeaderName in defHeaders) {
          lowercaseDefHeaderName = lowercase(defHeaderName);

          for (reqHeaderName in reqHeaders) {
            if (lowercase(reqHeaderName) === lowercaseDefHeaderName) {
              continue defaultHeadersIteration;
            }
          }

          reqHeaders[defHeaderName] = defHeaders[defHeaderName];
        }

        // execute if header value is a function for merged headers
        return executeHeaderFns(reqHeaders, shallowCopy(config));
      }

      function serverRequest(config) {
        var headers = config.headers;
        var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest);

        // strip content-type if data is undefined
        if (isUndefined(reqData)) {
          forEach(headers, function(value, header) {
            if (lowercase(header) === 'content-type') {
              delete headers[header];
            }
          });
        }

        if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) {
          config.withCredentials = defaults.withCredentials;
        }

        // send request
        return sendReq(config, reqData).then(transformResponse, transformResponse);
      }

      function transformResponse(response) {
        // make a copy since the response must be cacheable
        var resp = extend({}, response);
        resp.data = transformData(response.data, response.headers, response.status,
                                  config.transformResponse);
        return (isSuccess(response.status))
          ? resp
          : $q.reject(resp);
      }
    }

    $http.pendingRequests = [];

    /**
     * @ngdoc method
     * @name $http#get
     *
     * @description
     * Shortcut method to perform `GET` request.
     *
     * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested;
     *                                   or an object created by a call to `$sce.trustAsResourceUrl(url)`.
     * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}.
     * @returns {HttpPromise}  A Promise that will be resolved or rejected with a response object.
     * See {@link ng.$http#$http-returns `$http()` return value}.
     */

    /**
     * @ngdoc method
     * @name $http#delete
     *
     * @description
     * Shortcut method to perform `DELETE` request.
     *
     * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested;
     *                                   or an object created by a call to `$sce.trustAsResourceUrl(url)`.
     * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}.
     * @returns {HttpPromise}  A Promise that will be resolved or rejected with a response object.
     * See {@link ng.$http#$http-returns `$http()` return value}.
     */

    /**
     * @ngdoc method
     * @name $http#head
     *
     * @description
     * Shortcut method to perform `HEAD` request.
     *
     * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested;
     *                                   or an object created by a call to `$sce.trustAsResourceUrl(url)`.
     * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}.
     * @returns {HttpPromise}  A Promise that will be resolved or rejected with a response object.
     * See {@link ng.$http#$http-returns `$http()` return value}.
     */

    /**
     * @ngdoc method
     * @name $http#jsonp
     *
     * @description
     * Shortcut method to perform `JSONP` request.
     *
     * Note that, since JSONP requests are sensitive because the response is given full access to the browser,
     * the url must be declared, via {@link $sce} as a trusted resource URL.
     * You can trust a URL by adding it to the trusted resource URL list via
     * {@link $sceDelegateProvider#trustedResourceUrlList  `$sceDelegateProvider.trustedResourceUrlList`} or
     * by explicitly trusting the URL via {@link $sce#trustAsResourceUrl `$sce.trustAsResourceUrl(url)`}.
     *
     * You should avoid generating the URL for the JSONP request from user provided data.
     * Provide additional query parameters via `params` property of the `config` parameter, rather than
     * modifying the URL itself.
     *
     * JSONP requests must specify a callback to be used in the response from the server. This callback
     * is passed as a query parameter in the request. You must specify the name of this parameter by
     * setting the `jsonpCallbackParam` property on the request config object.
     *
     * ```
     * $http.jsonp('some/trusted/url', {jsonpCallbackParam: 'callback'})
     * ```
     *
     * You can also specify a default callback parameter name in `$http.defaults.jsonpCallbackParam`.
     * Initially this is set to `'callback'`.
     *
     * <div class="alert alert-danger">
     * You can no longer use the `JSON_CALLBACK` string as a placeholder for specifying where the callback
     * parameter value should go.
     * </div>
     *
     * If you would like to customise where and how the callbacks are stored then try overriding
     * or decorating the {@link $jsonpCallbacks} service.
     *
     * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested;
     *                                   or an object created by a call to `$sce.trustAsResourceUrl(url)`.
     * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}.
     * @returns {HttpPromise}  A Promise that will be resolved or rejected with a response object.
     * See {@link ng.$http#$http-returns `$http()` return value}.
     */
    createShortMethods('get', 'delete', 'head', 'jsonp');

    /**
     * @ngdoc method
     * @name $http#post
     *
     * @description
     * Shortcut method to perform `POST` request.
     *
     * @param {string} url Relative or absolute URL specifying the destination of the request
     * @param {*} data Request content
     * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}.
     * @returns {HttpPromise}  A Promise that will be resolved or rejected with a response object.
     * See {@link ng.$http#$http-returns `$http()` return value}.
     */

    /**
     * @ngdoc method
     * @name $http#put
     *
     * @description
     * Shortcut method to perform `PUT` request.
     *
     * @param {string} url Relative or absolute URL specifying the destination of the request
     * @param {*} data Request content
     * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}.
     * @returns {HttpPromise}  A Promise that will be resolved or rejected with a response object.
     * See {@link ng.$http#$http-returns `$http()` return value}.
     */

     /**
      * @ngdoc method
      * @name $http#patch
      *
      * @description
      * Shortcut method to perform `PATCH` request.
      *
      * @param {string} url Relative or absolute URL specifying the destination of the request
      * @param {*} data Request content
      * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}.
      * @returns {HttpPromise}  A Promise that will be resolved or rejected with a response object.
      * See {@link ng.$http#$http-returns `$http()` return value}.
      */
    createShortMethodsWithData('post', 'put', 'patch');

        /**
         * @ngdoc property
         * @name $http#defaults
         *
         * @description
         * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
         * default headers, withCredentials as well as request and response transformations.
         *
         * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
         */
    $http.defaults = defaults;


    return $http;


    function createShortMethods(names) {
      forEach(arguments, function(name) {
        $http[name] = function(url, config) {
          return $http(extend({}, config || {}, {
            method: name,
            url: url
          }));
        };
      });
    }


    function createShortMethodsWithData(name) {
      forEach(arguments, function(name) {
        $http[name] = function(url, data, config) {
          return $http(extend({}, config || {}, {
            method: name,
            url: url,
            data: data
          }));
        };
      });
    }


    /**
     * Makes the request.
     *
     * !!! ACCESSES CLOSURE VARS:
     * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests
     */
    function sendReq(config, reqData) {
      var deferred = $q.defer(),
          promise = deferred.promise,
          cache,
          cachedResp,
          reqHeaders = config.headers,
          isJsonp = lowercase(config.method) === 'jsonp',
          url = config.url;

      if (isJsonp) {
        // JSONP is a pretty sensitive operation where we're allowing a script to have full access to
        // our DOM and JS space.  So we require that the URL satisfies SCE.RESOURCE_URL.
        url = $sce.getTrustedResourceUrl(url);
      } else if (!isString(url)) {
        // If it is not a string then the URL must be a $sce trusted object
        url = $sce.valueOf(url);
      }

      url = buildUrl(url, config.paramSerializer(config.params));

      if (isJsonp) {
        // Check the url and add the JSONP callback placeholder
        url = sanitizeJsonpCallbackParam(url, config.jsonpCallbackParam);
      }

      $http.pendingRequests.push(config);
      promise.then(removePendingReq, removePendingReq);

      if ((config.cache || defaults.cache) && config.cache !== false &&
          (config.method === 'GET' || config.method === 'JSONP')) {
        cache = isObject(config.cache) ? config.cache
            : isObject(/** @type {?} */ (defaults).cache)
              ? /** @type {?} */ (defaults).cache
              : defaultCache;
      }

      if (cache) {
        cachedResp = cache.get(url);
        if (isDefined(cachedResp)) {
          if (isPromiseLike(cachedResp)) {
            // cached request has already been sent, but there is no response yet
            cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult);
          } else {
            // serving from cache
            if (isArray(cachedResp)) {
              resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3], cachedResp[4]);
            } else {
              resolvePromise(cachedResp, 200, {}, 'OK', 'complete');
            }
          }
        } else {
          // put the promise for the non-transformed response into cache as a placeholder
          cache.put(url, promise);
        }
      }


      // if we won't have the response in cache, set the xsrf headers and
      // send the request to the backend
      if (isUndefined(cachedResp)) {
        var xsrfValue = urlIsAllowedOrigin(config.url)
            ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName]
            : undefined;
        if (xsrfValue) {
          reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue;
        }

        $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout,
            config.withCredentials, config.responseType,
            createApplyHandlers(config.eventHandlers),
            createApplyHandlers(config.uploadEventHandlers));
      }

      return promise;

      function createApplyHandlers(eventHandlers) {
        if (eventHandlers) {
          var applyHandlers = {};
          forEach(eventHandlers, function(eventHandler, key) {
            applyHandlers[key] = function(event) {
              if (useApplyAsync) {
                $rootScope.$applyAsync(callEventHandler);
              } else if ($rootScope.$$phase) {
                callEventHandler();
              } else {
                $rootScope.$apply(callEventHandler);
              }

              function callEventHandler() {
                eventHandler(event);
              }
            };
          });
          return applyHandlers;
        }
      }


      /**
       * Callback registered to $httpBackend():
       *  - caches the response if desired
       *  - resolves the raw $http promise
       *  - calls $apply
       */
      function done(status, response, headersString, statusText, xhrStatus) {
        if (cache) {
          if (isSuccess(status)) {
            cache.put(url, [status, response, parseHeaders(headersString), statusText, xhrStatus]);
          } else {
            // remove promise from the cache
            cache.remove(url);
          }
        }

        function resolveHttpPromise() {
          resolvePromise(response, status, headersString, statusText, xhrStatus);
        }

        if (useApplyAsync) {
          $rootScope.$applyAsync(resolveHttpPromise);
        } else {
          resolveHttpPromise();
          if (!$rootScope.$$phase) $rootScope.$apply();
        }
      }


      /**
       * Resolves the raw $http promise.
       */
      function resolvePromise(response, status, headers, statusText, xhrStatus) {
        //status: HTTP response status code, 0, -1 (aborted by timeout / promise)
        status = status >= -1 ? status : 0;

        (isSuccess(status) ? deferred.resolve : deferred.reject)({
          data: response,
          status: status,
          headers: headersGetter(headers),
          config: config,
          statusText: statusText,
          xhrStatus: xhrStatus
        });
      }

      function resolvePromiseWithResult(result) {
        resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText, result.xhrStatus);
      }

      function removePendingReq() {
        var idx = $http.pendingRequests.indexOf(config);
        if (idx !== -1) $http.pendingRequests.splice(idx, 1);
      }
    }


    function buildUrl(url, serializedParams) {
      if (serializedParams.length > 0) {
        url += ((url.indexOf('?') === -1) ? '?' : '&') + serializedParams;
      }
      return url;
    }

    function sanitizeJsonpCallbackParam(url, cbKey) {
      var parts = url.split('?');
      if (parts.length > 2) {
        // Throw if the url contains more than one `?` query indicator
        throw $httpMinErr('badjsonp', 'Illegal use more than one "?", in url, "{1}"', url);
      }
      var params = parseKeyValue(parts[1]);
      forEach(params, function(value, key) {
        if (value === 'JSON_CALLBACK') {
          // Throw if the url already contains a reference to JSON_CALLBACK
          throw $httpMinErr('badjsonp', 'Illegal use of JSON_CALLBACK in url, "{0}"', url);
        }
        if (key === cbKey) {
          // Throw if the callback param was already provided
          throw $httpMinErr('badjsonp', 'Illegal use of callback param, "{0}", in url, "{1}"', cbKey, url);
        }
      });

      // Add in the JSON_CALLBACK callback param value
      url += ((url.indexOf('?') === -1) ? '?' : '&') + cbKey + '=JSON_CALLBACK';

      return url;
    }
  }];
}

/**
 * @ngdoc service
 * @name $xhrFactory
 * @this
 *
 * @description
 * Factory function used to create XMLHttpRequest objects.
 *
 * Replace or decorate this service to create your own custom XMLHttpRequest objects.
 *
 * ```
 * angular.module('myApp', [])
 * .factory('$xhrFactory', function() {
 *   return function createXhr(method, url) {
 *     return new window.XMLHttpRequest({mozSystem: true});
 *   };
 * });
 * ```
 *
 * @param {string} method HTTP method of the request (GET, POST, PUT, ..)
 * @param {string} url URL of the request.
 */
function $xhrFactoryProvider() {
  this.$get = function() {
    return function createXhr() {
      return new window.XMLHttpRequest();
    };
  };
}

/**
 * @ngdoc service
 * @name $httpBackend
 * @requires $jsonpCallbacks
 * @requires $document
 * @requires $xhrFactory
 * @this
 *
 * @description
 * HTTP backend used by the {@link ng.$http service} that delegates to
 * XMLHttpRequest object or JSONP and deals with browser incompatibilities.
 *
 * You should never need to use this service directly, instead use the higher-level abstractions:
 * {@link ng.$http $http} or {@link ngResource.$resource $resource}.
 *
 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock
 * $httpBackend} which can be trained with responses.
 */
function $HttpBackendProvider() {
  this.$get = ['$browser', '$jsonpCallbacks', '$document', '$xhrFactory', function($browser, $jsonpCallbacks, $document, $xhrFactory) {
    return createHttpBackend($browser, $xhrFactory, $browser.defer, $jsonpCallbacks, $document[0]);
  }];
}

function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) {
  // TODO(vojta): fix the signature
  return function(method, url, post, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers) {
    url = url || $browser.url();

    if (lowercase(method) === 'jsonp') {
      var callbackPath = callbacks.createCallback(url);
      var jsonpDone = jsonpReq(url, callbackPath, function(status, text) {
        // jsonpReq only ever sets status to 200 (OK), 404 (ERROR) or -1 (WAITING)
        var response = (status === 200) && callbacks.getResponse(callbackPath);
        completeRequest(callback, status, response, '', text, 'complete');
        callbacks.removeCallback(callbackPath);
      });
    } else {

      var xhr = createXhr(method, url);
      var abortedByTimeout = false;

      xhr.open(method, url, true);
      forEach(headers, function(value, key) {
        if (isDefined(value)) {
            xhr.setRequestHeader(key, value);
        }
      });

      xhr.onload = function requestLoaded() {
        var statusText = xhr.statusText || '';

        // responseText is the old-school way of retrieving response (supported by IE9)
        // response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
        var response = ('response' in xhr) ? xhr.response : xhr.responseText;

        // normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
        var status = xhr.status === 1223 ? 204 : xhr.status;

        // fix status code when it is 0 (0 status is undocumented).
        // Occurs when accessing file resources or on Android 4.1 stock browser
        // while retrieving files from application cache.
        if (status === 0) {
          status = response ? 200 : urlResolve(url).protocol === 'file' ? 404 : 0;
        }

        completeRequest(callback,
            status,
            response,
            xhr.getAllResponseHeaders(),
            statusText,
            'complete');
      };

      var requestError = function() {
        // The response is always empty
        // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error
        completeRequest(callback, -1, null, null, '', 'error');
      };

      var requestAborted = function() {
        completeRequest(callback, -1, null, null, '', abortedByTimeout ? 'timeout' : 'abort');
      };

      var requestTimeout = function() {
        // The response is always empty
        // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error
        completeRequest(callback, -1, null, null, '', 'timeout');
      };

      xhr.onerror = requestError;
      xhr.ontimeout = requestTimeout;
      xhr.onabort = requestAborted;

      forEach(eventHandlers, function(value, key) {
        xhr.addEventListener(key, value);
      });

      forEach(uploadEventHandlers, function(value, key) {
        xhr.upload.addEventListener(key, value);
      });

      if (withCredentials) {
        xhr.withCredentials = true;
      }

      if (responseType) {
        try {
          xhr.responseType = responseType;
        } catch (e) {
          // WebKit added support for the json responseType value on 09/03/2013
          // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are
          // known to throw when setting the value "json" as the response type. Other older
          // browsers implementing the responseType
          //
          // The json response type can be ignored if not supported, because JSON payloads are
          // parsed on the client-side regardless.
          if (responseType !== 'json') {
            throw e;
          }
        }
      }

      xhr.send(isUndefined(post) ? null : post);
    }

    // Since we are using xhr.abort() when a request times out, we have to set a flag that
    // indicates to requestAborted if the request timed out or was aborted.
    //
    // http.timeout = numerical timeout   timeout
    // http.timeout = $timeout            timeout
    // http.timeout = promise             abort
    // xhr.abort()                        abort (The xhr object is normally inaccessible, but
    //                                    can be exposed with the xhrFactory)
    if (timeout > 0) {
      var timeoutId = $browserDefer(function() {
        timeoutRequest('timeout');
      }, timeout);
    } else if (isPromiseLike(timeout)) {
      timeout.then(function() {
        timeoutRequest(isDefined(timeout.$$timeoutId) ? 'timeout' : 'abort');
      });
    }

    function timeoutRequest(reason) {
      abortedByTimeout = reason === 'timeout';
      if (jsonpDone) {
        jsonpDone();
      }
      if (xhr) {
        xhr.abort();
      }
    }

    function completeRequest(callback, status, response, headersString, statusText, xhrStatus) {
      // cancel timeout and subsequent timeout promise resolution
      if (isDefined(timeoutId)) {
        $browserDefer.cancel(timeoutId);
      }
      jsonpDone = xhr = null;

      callback(status, response, headersString, statusText, xhrStatus);
    }
  };

  function jsonpReq(url, callbackPath, done) {
    url = url.replace('JSON_CALLBACK', callbackPath);
    // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.:
    // - fetches local scripts via XHR and evals them
    // - adds and immediately removes script elements from the document
    var script = rawDocument.createElement('script'), callback = null;
    script.type = 'text/javascript';
    script.src = url;
    script.async = true;

    callback = function(event) {
      script.removeEventListener('load', callback);
      script.removeEventListener('error', callback);
      rawDocument.body.removeChild(script);
      script = null;
      var status = -1;
      var text = 'unknown';

      if (event) {
        if (event.type === 'load' && !callbacks.wasCalled(callbackPath)) {
          event = { type: 'error' };
        }
        text = event.type;
        status = event.type === 'error' ? 404 : 200;
      }

      if (done) {
        done(status, text);
      }
    };

    script.addEventListener('load', callback);
    script.addEventListener('error', callback);
    rawDocument.body.appendChild(script);
    return callback;
  }
}

var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate');
$interpolateMinErr.throwNoconcat = function(text) {
  throw $interpolateMinErr('noconcat',
      'Error while interpolating: {0}\nStrict Contextual Escaping disallows ' +
      'interpolations that concatenate multiple expressions when a trusted value is ' +
      'required.  See http://docs.angularjs.org/api/ng.$sce', text);
};

$interpolateMinErr.interr = function(text, err) {
  return $interpolateMinErr('interr', 'Can\'t interpolate: {0}\n{1}', text, err.toString());
};

/**
 * @ngdoc provider
 * @name $interpolateProvider
 * @this
 *
 * @description
 *
 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
 *
 * <div class="alert alert-danger">
 * This feature is sometimes used to mix different markup languages, e.g. to wrap an AngularJS
 * template within a Python Jinja template (or any other template language). Mixing templating
 * languages is **very dangerous**. The embedding template language will not safely escape AngularJS
 * expressions, so any user-controlled values in the template will cause Cross Site Scripting (XSS)
 * security bugs!
 * </div>
 *
 * @example
<example name="custom-interpolation-markup" module="customInterpolationApp">
<file name="index.html">
<script>
  var customInterpolationApp = angular.module('customInterpolationApp', []);

  customInterpolationApp.config(function($interpolateProvider) {
    $interpolateProvider.startSymbol('//');
    $interpolateProvider.endSymbol('//');
  });


  customInterpolationApp.controller('DemoController', function() {
      this.label = "This binding is brought you by // interpolation symbols.";
  });
</script>
<div ng-controller="DemoController as demo">
    //demo.label//
</div>
</file>
<file name="protractor.js" type="protractor">
  it('should interpolate binding with custom symbols', function() {
    expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.');
  });
</file>
</example>
 */
function $InterpolateProvider() {
  var startSymbol = '{{';
  var endSymbol = '}}';

  /**
   * @ngdoc method
   * @name $interpolateProvider#startSymbol
   * @description
   * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
   *
   * @param {string=} value new value to set the starting symbol to.
   * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
   */
  this.startSymbol = function(value) {
    if (value) {
      startSymbol = value;
      return this;
    }
    return startSymbol;
  };

  /**
   * @ngdoc method
   * @name $interpolateProvider#endSymbol
   * @description
   * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
   *
   * @param {string=} value new value to set the ending symbol to.
   * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
   */
  this.endSymbol = function(value) {
    if (value) {
      endSymbol = value;
      return this;
    }
    return endSymbol;
  };


  this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) {
    var startSymbolLength = startSymbol.length,
        endSymbolLength = endSymbol.length,
        escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'),
        escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g');

    function escape(ch) {
      return '\\\\\\' + ch;
    }

    function unescapeText(text) {
      return text.replace(escapedStartRegexp, startSymbol).
        replace(escapedEndRegexp, endSymbol);
    }

    // TODO: this is the same as the constantWatchDelegate in parse.js
    function constantWatchDelegate(scope, listener, objectEquality, constantInterp) {
      var unwatch = scope.$watch(function constantInterpolateWatch(scope) {
        unwatch();
        return constantInterp(scope);
      }, listener, objectEquality);
      return unwatch;
    }

    /**
     * @ngdoc service
     * @name $interpolate
     * @kind function
     *
     * @requires $parse
     * @requires $sce
     *
     * @description
     *
     * Compiles a string with markup into an interpolation function. This service is used by the
     * HTML {@link ng.$compile $compile} service for data binding. See
     * {@link ng.$interpolateProvider $interpolateProvider} for configuring the
     * interpolation markup.
     *
     *
     * ```js
     *   var $interpolate = ...; // injected
     *   var exp = $interpolate('Hello {{name | uppercase}}!');
     *   expect(exp({name:'AngularJS'})).toEqual('Hello ANGULARJS!');
     * ```
     *
     * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is
     * `true`, the interpolation function will return `undefined` unless all embedded expressions
     * evaluate to a value other than `undefined`.
     *
     * ```js
     *   var $interpolate = ...; // injected
     *   var context = {greeting: 'Hello', name: undefined };
     *
     *   // default "forgiving" mode
     *   var exp = $interpolate('{{greeting}} {{name}}!');
     *   expect(exp(context)).toEqual('Hello !');
     *
     *   // "allOrNothing" mode
     *   exp = $interpolate('{{greeting}} {{name}}!', false, null, true);
     *   expect(exp(context)).toBeUndefined();
     *   context.name = 'AngularJS';
     *   expect(exp(context)).toEqual('Hello AngularJS!');
     * ```
     *
     * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior.
     *
     * #### Escaped Interpolation
     * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers
     * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash).
     * It will be rendered as a regular start/end marker, and will not be interpreted as an expression
     * or binding.
     *
     * This enables web-servers to prevent script injection attacks and defacing attacks, to some
     * degree, while also enabling code examples to work without relying on the
     * {@link ng.directive:ngNonBindable ngNonBindable} directive.
     *
     * **For security purposes, it is strongly encouraged that web servers escape user-supplied data,
     * replacing angle brackets (&lt;, &gt;) with &amp;lt; and &amp;gt; respectively, and replacing all
     * interpolation start/end markers with their escaped counterparts.**
     *
     * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered
     * output when the $interpolate service processes the text. So, for HTML elements interpolated
     * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter
     * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such,
     * this is typically useful only when user-data is used in rendering a template from the server, or
     * when otherwise untrusted data is used by a directive.
     *
     * <example name="interpolation">
     *  <file name="index.html">
     *    <div ng-init="username='A user'">
     *      <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\}
     *        </p>
     *      <p><strong>{{username}}</strong> attempts to inject code which will deface the
     *        application, but fails to accomplish their task, because the server has correctly
     *        escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash)
     *        characters.</p>
     *      <p>Instead, the result of the attempted script injection is visible, and can be removed
     *        from the database by an administrator.</p>
     *    </div>
     *  </file>
     * </example>
     *
     * @knownIssue
     * It is currently not possible for an interpolated expression to contain the interpolation end
     * symbol. For example, `{{ '}}' }}` will be incorrectly interpreted as `{{ ' }}` + `' }}`, i.e.
     * an interpolated expression consisting of a single-quote (`'`) and the `' }}` string.
     *
     * @knownIssue
     * All directives and components must use the standard `{{` `}}` interpolation symbols
     * in their templates. If you change the application interpolation symbols the {@link $compile}
     * service will attempt to denormalize the standard symbols to the custom symbols.
     * The denormalization process is not clever enough to know not to replace instances of the standard
     * symbols where they would not normally be treated as interpolation symbols. For example in the following
     * code snippet the closing braces of the literal object will get incorrectly denormalized:
     *
     * ```
     * <div data-context='{"context":{"id":3,"type":"page"}}">
     * ```
     *
     * The workaround is to ensure that such instances are separated by whitespace:
     * ```
     * <div data-context='{"context":{"id":3,"type":"page"} }">
     * ```
     *
     * See https://github.com/angular/angular.js/pull/14610#issuecomment-219401099 for more information.
     *
     * @param {string} text The text with markup to interpolate.
     * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
     *    embedded expression in order to return an interpolation function. Strings with no
     *    embedded expression will return null for the interpolation function.
     * @param {string=} trustedContext when provided, the returned function passes the interpolated
     *    result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult,
     *    trustedContext)} before returning it.  Refer to the {@link ng.$sce $sce} service that
     *    provides Strict Contextual Escaping for details.
     * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined
     *    unless all embedded expressions evaluate to a value other than `undefined`.
     * @returns {function(context)} an interpolation function which is used to compute the
     *    interpolated string. The function has these parameters:
     *
     * - `context`: evaluation context for all expressions embedded in the interpolated text
     */
    function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
      var contextAllowsConcatenation = trustedContext === $sce.URL || trustedContext === $sce.MEDIA_URL;

      // Provide a quick exit and simplified result function for text with no interpolation
      if (!text.length || text.indexOf(startSymbol) === -1) {
        if (mustHaveExpression) return;

        var unescapedText = unescapeText(text);
        if (contextAllowsConcatenation) {
          unescapedText = $sce.getTrusted(trustedContext, unescapedText);
        }
        var constantInterp = valueFn(unescapedText);
        constantInterp.exp = text;
        constantInterp.expressions = [];
        constantInterp.$$watchDelegate = constantWatchDelegate;

        return constantInterp;
      }

      allOrNothing = !!allOrNothing;
      var startIndex,
          endIndex,
          index = 0,
          expressions = [],
          parseFns,
          textLength = text.length,
          exp,
          concat = [],
          expressionPositions = [],
          singleExpression;


      while (index < textLength) {
        if (((startIndex = text.indexOf(startSymbol, index)) !== -1) &&
             ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) !== -1)) {
          if (index !== startIndex) {
            concat.push(unescapeText(text.substring(index, startIndex)));
          }
          exp = text.substring(startIndex + startSymbolLength, endIndex);
          expressions.push(exp);
          index = endIndex + endSymbolLength;
          expressionPositions.push(concat.length);
          concat.push(''); // Placeholder that will get replaced with the evaluated expression.
        } else {
          // we did not find an interpolation, so we have to add the remainder to the separators array
          if (index !== textLength) {
            concat.push(unescapeText(text.substring(index)));
          }
          break;
        }
      }

      singleExpression = concat.length === 1 && expressionPositions.length === 1;
      // Intercept expression if we need to stringify concatenated inputs, which may be SCE trusted
      // objects rather than simple strings
      // (we don't modify the expression if the input consists of only a single trusted input)
      var interceptor = contextAllowsConcatenation && singleExpression ? undefined : parseStringifyInterceptor;
      parseFns = expressions.map(function(exp) { return $parse(exp, interceptor); });

      // Concatenating expressions makes it hard to reason about whether some combination of
      // concatenated values are unsafe to use and could easily lead to XSS.  By requiring that a
      // single expression be used for some $sce-managed secure contexts (RESOURCE_URLs mostly),
      // we ensure that the value that's used is assigned or constructed by some JS code somewhere
      // that is more testable or make it obvious that you bound the value to some user controlled
      // value.  This helps reduce the load when auditing for XSS issues.

      // Note that URL and MEDIA_URL $sce contexts do not need this, since `$sce` can sanitize the values
      // passed to it. In that case, `$sce.getTrusted` will be called on either the single expression
      // or on the overall concatenated string (losing trusted types used in the mix, by design).
      // Both these methods will sanitize plain strings. Also, HTML could be included, but since it's
      // only used in srcdoc attributes, this would not be very useful.

      if (!mustHaveExpression || expressions.length) {
        var compute = function(values) {
          for (var i = 0, ii = expressions.length; i < ii; i++) {
            if (allOrNothing && isUndefined(values[i])) return;
            concat[expressionPositions[i]] = values[i];
          }

          if (contextAllowsConcatenation) {
            // If `singleExpression` then `concat[0]` might be a "trusted" value or `null`, rather than a string
            return $sce.getTrusted(trustedContext, singleExpression ? concat[0] : concat.join(''));
          } else if (trustedContext && concat.length > 1) {
            // This context does not allow more than one part, e.g. expr + string or exp + exp.
            $interpolateMinErr.throwNoconcat(text);
          }
          // In an unprivileged context or only one part: just concatenate and return.
          return concat.join('');
        };

        return extend(function interpolationFn(context) {
            var i = 0;
            var ii = expressions.length;
            var values = new Array(ii);

            try {
              for (; i < ii; i++) {
                values[i] = parseFns[i](context);
              }

              return compute(values);
            } catch (err) {
              $exceptionHandler($interpolateMinErr.interr(text, err));
            }

          }, {
          // all of these properties are undocumented for now
          exp: text, //just for compatibility with regular watchers created via $watch
          expressions: expressions,
          $$watchDelegate: function(scope, listener) {
            var lastValue;
            return scope.$watchGroup(parseFns, /** @this */ function interpolateFnWatcher(values, oldValues) {
              var currValue = compute(values);
              listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope);
              lastValue = currValue;
            });
          }
        });
      }

      function parseStringifyInterceptor(value) {
        try {
          // In concatenable contexts, getTrusted comes at the end, to avoid sanitizing individual
          // parts of a full URL. We don't care about losing the trustedness here.
          // In non-concatenable contexts, where there is only one expression, this interceptor is
          // not applied to the expression.
          value = (trustedContext && !contextAllowsConcatenation) ?
                    $sce.getTrusted(trustedContext, value) :
                    $sce.valueOf(value);
          return allOrNothing && !isDefined(value) ? value : stringify(value);
        } catch (err) {
          $exceptionHandler($interpolateMinErr.interr(text, err));
        }
      }
    }


    /**
     * @ngdoc method
     * @name $interpolate#startSymbol
     * @description
     * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`.
     *
     * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change
     * the symbol.
     *
     * @returns {string} start symbol.
     */
    $interpolate.startSymbol = function() {
      return startSymbol;
    };


    /**
     * @ngdoc method
     * @name $interpolate#endSymbol
     * @description
     * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
     *
     * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change
     * the symbol.
     *
     * @returns {string} end symbol.
     */
    $interpolate.endSymbol = function() {
      return endSymbol;
    };

    return $interpolate;
  }];
}

var $intervalMinErr = minErr('$interval');

/** @this */
function $IntervalProvider() {
  this.$get = ['$$intervalFactory', '$window',
       function($$intervalFactory,   $window) {
    var intervals = {};
    var setIntervalFn = function(tick, delay, deferred) {
      var id = $window.setInterval(tick, delay);
      intervals[id] = deferred;
      return id;
    };
    var clearIntervalFn = function(id) {
      $window.clearInterval(id);
      delete intervals[id];
    };

    /**
     * @ngdoc service
     * @name $interval
     *
     * @description
     * AngularJS's wrapper for `window.setInterval`. The `fn` function is executed every `delay`
     * milliseconds.
     *
     * The return value of registering an interval function is a promise. This promise will be
     * notified upon each tick of the interval, and will be resolved after `count` iterations, or
     * run indefinitely if `count` is not defined. The value of the notification will be the
     * number of iterations that have run.
     * To cancel an interval, call `$interval.cancel(promise)`.
     *
     * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to
     * move forward by `millis` milliseconds and trigger any functions scheduled to run in that
     * time.
     *
     * <div class="alert alert-warning">
     * **Note**: Intervals created by this service must be explicitly destroyed when you are finished
     * with them.  In particular they are not automatically destroyed when a controller's scope or a
     * directive's element are destroyed.
     * You should take this into consideration and make sure to always cancel the interval at the
     * appropriate moment.  See the example below for more details on how and when to do this.
     * </div>
     *
     * @param {function()} fn A function that should be called repeatedly. If no additional arguments
     *   are passed (see below), the function is called with the current iteration count.
     * @param {number} delay Number of milliseconds between each function call.
     * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat
     *   indefinitely.
     * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
     *   will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
     * @param {...*=} Pass additional parameters to the executed function.
     * @returns {promise} A promise which will be notified on each iteration. It will resolve once all iterations of the interval complete.
     *
     * @example
     * <example module="intervalExample" name="interval-service">
     * <file name="index.html">
     *   <script>
     *     angular.module('intervalExample', [])
     *       .controller('ExampleController', ['$scope', '$interval',
     *         function($scope, $interval) {
     *           $scope.format = 'M/d/yy h:mm:ss a';
     *           $scope.blood_1 = 100;
     *           $scope.blood_2 = 120;
     *
     *           var stop;
     *           $scope.fight = function() {
     *             // Don't start a new fight if we are already fighting
     *             if ( angular.isDefined(stop) ) return;
     *
     *             stop = $interval(function() {
     *               if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
     *                 $scope.blood_1 = $scope.blood_1 - 3;
     *                 $scope.blood_2 = $scope.blood_2 - 4;
     *               } else {
     *                 $scope.stopFight();
     *               }
     *             }, 100);
     *           };
     *
     *           $scope.stopFight = function() {
     *             if (angular.isDefined(stop)) {
     *               $interval.cancel(stop);
     *               stop = undefined;
     *             }
     *           };
     *
     *           $scope.resetFight = function() {
     *             $scope.blood_1 = 100;
     *             $scope.blood_2 = 120;
     *           };
     *
     *           $scope.$on('$destroy', function() {
     *             // Make sure that the interval is destroyed too
     *             $scope.stopFight();
     *           });
     *         }])
     *       // Register the 'myCurrentTime' directive factory method.
     *       // We inject $interval and dateFilter service since the factory method is DI.
     *       .directive('myCurrentTime', ['$interval', 'dateFilter',
     *         function($interval, dateFilter) {
     *           // return the directive link function. (compile function not needed)
     *           return function(scope, element, attrs) {
     *             var format,  // date format
     *                 stopTime; // so that we can cancel the time updates
     *
     *             // used to update the UI
     *             function updateTime() {
     *               element.text(dateFilter(new Date(), format));
     *             }
     *
     *             // watch the expression, and update the UI on change.
     *             scope.$watch(attrs.myCurrentTime, function(value) {
     *               format = value;
     *               updateTime();
     *             });
     *
     *             stopTime = $interval(updateTime, 1000);
     *
     *             // listen on DOM destroy (removal) event, and cancel the next UI update
     *             // to prevent updating time after the DOM element was removed.
     *             element.on('$destroy', function() {
     *               $interval.cancel(stopTime);
     *             });
     *           }
     *         }]);
     *   </script>
     *
     *   <div>
     *     <div ng-controller="ExampleController">
     *       <label>Date format: <input ng-model="format"></label> <hr/>
     *       Current time is: <span my-current-time="format"></span>
     *       <hr/>
     *       Blood 1 : <font color='red'>{{blood_1}}</font>
     *       Blood 2 : <font color='red'>{{blood_2}}</font>
     *       <button type="button" data-ng-click="fight()">Fight</button>
     *       <button type="button" data-ng-click="stopFight()">StopFight</button>
     *       <button type="button" data-ng-click="resetFight()">resetFight</button>
     *     </div>
     *   </div>
     *
     * </file>
     * </example>
     */
    var interval = $$intervalFactory(setIntervalFn, clearIntervalFn);

    /**
     * @ngdoc method
     * @name $interval#cancel
     *
     * @description
     * Cancels a task associated with the `promise`.
     *
     * @param {Promise=} promise returned by the `$interval` function.
     * @returns {boolean} Returns `true` if the task was successfully canceled.
     */
    interval.cancel = function(promise) {
      if (!promise) return false;

      if (!promise.hasOwnProperty('$$intervalId')) {
        throw $intervalMinErr('badprom',
            '`$interval.cancel()` called with a promise that was not generated by `$interval()`.');
      }

      if (!intervals.hasOwnProperty(promise.$$intervalId)) return false;

      var id = promise.$$intervalId;
      var deferred = intervals[id];

      // Interval cancels should not report an unhandled promise.
      markQExceptionHandled(deferred.promise);
      deferred.reject('canceled');
      clearIntervalFn(id);

      return true;
    };

    return interval;
  }];
}

/** @this */
function $$IntervalFactoryProvider() {
  this.$get = ['$browser', '$q', '$$q', '$rootScope',
       function($browser,   $q,   $$q,   $rootScope) {
    return function intervalFactory(setIntervalFn, clearIntervalFn) {
      return function intervalFn(fn, delay, count, invokeApply) {
        var hasParams = arguments.length > 4,
            args = hasParams ? sliceArgs(arguments, 4) : [],
            iteration = 0,
            skipApply = isDefined(invokeApply) && !invokeApply,
            deferred = (skipApply ? $$q : $q).defer(),
            promise = deferred.promise;

        count = isDefined(count) ? count : 0;

        function callback() {
          if (!hasParams) {
            fn(iteration);
          } else {
            fn.apply(null, args);
          }
        }

        function tick() {
          if (skipApply) {
            $browser.defer(callback);
          } else {
            $rootScope.$evalAsync(callback);
          }
          deferred.notify(iteration++);

          if (count > 0 && iteration >= count) {
            deferred.resolve(iteration);
            clearIntervalFn(promise.$$intervalId);
          }

          if (!skipApply) $rootScope.$apply();
        }

        promise.$$intervalId = setIntervalFn(tick, delay, deferred, skipApply);

        return promise;
      };
    };
  }];
}

/**
 * @ngdoc service
 * @name $jsonpCallbacks
 * @requires $window
 * @description
 * This service handles the lifecycle of callbacks to handle JSONP requests.
 * Override this service if you wish to customise where the callbacks are stored and
 * how they vary compared to the requested url.
 */
var $jsonpCallbacksProvider = /** @this */ function() {
  this.$get = function() {
    var callbacks = angular.callbacks;
    var callbackMap = {};

    function createCallback(callbackId) {
      var callback = function(data) {
        callback.data = data;
        callback.called = true;
      };
      callback.id = callbackId;
      return callback;
    }

    return {
      /**
       * @ngdoc method
       * @name $jsonpCallbacks#createCallback
       * @param {string} url the url of the JSONP request
       * @returns {string} the callback path to send to the server as part of the JSONP request
       * @description
       * {@link $httpBackend} calls this method to create a callback and get hold of the path to the callback
       * to pass to the server, which will be used to call the callback with its payload in the JSONP response.
       */
      createCallback: function(url) {
        var callbackId = '_' + (callbacks.$$counter++).toString(36);
        var callbackPath = 'angular.callbacks.' + callbackId;
        var callback = createCallback(callbackId);
        callbackMap[callbackPath] = callbacks[callbackId] = callback;
        return callbackPath;
      },
      /**
       * @ngdoc method
       * @name $jsonpCallbacks#wasCalled
       * @param {string} callbackPath the path to the callback that was sent in the JSONP request
       * @returns {boolean} whether the callback has been called, as a result of the JSONP response
       * @description
       * {@link $httpBackend} calls this method to find out whether the JSONP response actually called the
       * callback that was passed in the request.
       */
      wasCalled: function(callbackPath) {
        return callbackMap[callbackPath].called;
      },
      /**
       * @ngdoc method
       * @name $jsonpCallbacks#getResponse
       * @param {string} callbackPath the path to the callback that was sent in the JSONP request
       * @returns {*} the data received from the response via the registered callback
       * @description
       * {@link $httpBackend} calls this method to get hold of the data that was provided to the callback
       * in the JSONP response.
       */
      getResponse: function(callbackPath) {
        return callbackMap[callbackPath].data;
      },
      /**
       * @ngdoc method
       * @name $jsonpCallbacks#removeCallback
       * @param {string} callbackPath the path to the callback that was sent in the JSONP request
       * @description
       * {@link $httpBackend} calls this method to remove the callback after the JSONP request has
       * completed or timed-out.
       */
      removeCallback: function(callbackPath) {
        var callback = callbackMap[callbackPath];
        delete callbacks[callback.id];
        delete callbackMap[callbackPath];
      }
    };
  };
};

/**
 * @ngdoc service
 * @name $locale
 *
 * @description
 * $locale service provides localization rules for various AngularJS components. As of right now the
 * only public api is:
 *
 * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`)
 */

/* global stripHash: true */

var PATH_MATCH = /^([^?#]*)(\?([^#]*))?(#(.*))?$/,
    DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21};
var $locationMinErr = minErr('$location');


/**
 * Encode path using encodeUriSegment, ignoring forward slashes
 *
 * @param {string} path Path to encode
 * @returns {string}
 */
function encodePath(path) {
  var segments = path.split('/'),
      i = segments.length;

  while (i--) {
    // decode forward slashes to prevent them from being double encoded
    segments[i] = encodeUriSegment(segments[i].replace(/%2F/g, '/'));
  }

  return segments.join('/');
}

function decodePath(path, html5Mode) {
  var segments = path.split('/'),
      i = segments.length;

  while (i--) {
    segments[i] = decodeURIComponent(segments[i]);
    if (html5Mode) {
      // encode forward slashes to prevent them from being mistaken for path separators
      segments[i] = segments[i].replace(/\//g, '%2F');
    }
  }

  return segments.join('/');
}

function normalizePath(pathValue, searchValue, hashValue) {
  var search = toKeyValue(searchValue),
    hash = hashValue ? '#' + encodeUriSegment(hashValue) : '',
    path = encodePath(pathValue);

  return path + (search ? '?' + search : '') + hash;
}

function parseAbsoluteUrl(absoluteUrl, locationObj) {
  var parsedUrl = urlResolve(absoluteUrl);

  locationObj.$$protocol = parsedUrl.protocol;
  locationObj.$$host = parsedUrl.hostname;
  locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null;
}

var DOUBLE_SLASH_REGEX = /^\s*[\\/]{2,}/;
function parseAppUrl(url, locationObj, html5Mode) {

  if (DOUBLE_SLASH_REGEX.test(url)) {
    throw $locationMinErr('badpath', 'Invalid url "{0}".', url);
  }

  var prefixed = (url.charAt(0) !== '/');
  if (prefixed) {
    url = '/' + url;
  }
  var match = urlResolve(url);
  var path = prefixed && match.pathname.charAt(0) === '/' ? match.pathname.substring(1) : match.pathname;
  locationObj.$$path = decodePath(path, html5Mode);
  locationObj.$$search = parseKeyValue(match.search);
  locationObj.$$hash = decodeURIComponent(match.hash);

  // make sure path starts with '/';
  if (locationObj.$$path && locationObj.$$path.charAt(0) !== '/') {
    locationObj.$$path = '/' + locationObj.$$path;
  }
}

function startsWith(str, search) {
  return str.slice(0, search.length) === search;
}

/**
 *
 * @param {string} base
 * @param {string} url
 * @returns {string} returns text from `url` after `base` or `undefined` if it does not begin with
 *                   the expected string.
 */
function stripBaseUrl(base, url) {
  if (startsWith(url, base)) {
    return url.substr(base.length);
  }
}

function stripHash(url) {
  var index = url.indexOf('#');
  return index === -1 ? url : url.substr(0, index);
}

function stripFile(url) {
  return url.substr(0, stripHash(url).lastIndexOf('/') + 1);
}

/* return the server only (scheme://host:port) */
function serverBase(url) {
  return url.substring(0, url.indexOf('/', url.indexOf('//') + 2));
}


/**
 * LocationHtml5Url represents a URL
 * This object is exposed as $location service when HTML5 mode is enabled and supported
 *
 * @constructor
 * @param {string} appBase application base URL
 * @param {string} appBaseNoFile application base URL stripped of any filename
 * @param {string} basePrefix URL path prefix
 */
function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
  this.$$html5 = true;
  basePrefix = basePrefix || '';
  parseAbsoluteUrl(appBase, this);


  /**
   * Parse given HTML5 (regular) URL string into properties
   * @param {string} url HTML5 URL
   * @private
   */
  this.$$parse = function(url) {
    var pathUrl = stripBaseUrl(appBaseNoFile, url);
    if (!isString(pathUrl)) {
      throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url,
          appBaseNoFile);
    }

    parseAppUrl(pathUrl, this, true);

    if (!this.$$path) {
      this.$$path = '/';
    }

    this.$$compose();
  };

  this.$$normalizeUrl = function(url) {
    return appBaseNoFile + url.substr(1); // first char is always '/'
  };

  this.$$parseLinkUrl = function(url, relHref) {
    if (relHref && relHref[0] === '#') {
      // special case for links to hash fragments:
      // keep the old url and only replace the hash fragment
      this.hash(relHref.slice(1));
      return true;
    }
    var appUrl, prevAppUrl;
    var rewrittenUrl;


    if (isDefined(appUrl = stripBaseUrl(appBase, url))) {
      prevAppUrl = appUrl;
      if (basePrefix && isDefined(appUrl = stripBaseUrl(basePrefix, appUrl))) {
        rewrittenUrl = appBaseNoFile + (stripBaseUrl('/', appUrl) || appUrl);
      } else {
        rewrittenUrl = appBase + prevAppUrl;
      }
    } else if (isDefined(appUrl = stripBaseUrl(appBaseNoFile, url))) {
      rewrittenUrl = appBaseNoFile + appUrl;
    } else if (appBaseNoFile === url + '/') {
      rewrittenUrl = appBaseNoFile;
    }
    if (rewrittenUrl) {
      this.$$parse(rewrittenUrl);
    }
    return !!rewrittenUrl;
  };
}


/**
 * LocationHashbangUrl represents URL
 * This object is exposed as $location service when developer doesn't opt into html5 mode.
 * It also serves as the base class for html5 mode fallback on legacy browsers.
 *
 * @constructor
 * @param {string} appBase application base URL
 * @param {string} appBaseNoFile application base URL stripped of any filename
 * @param {string} hashPrefix hashbang prefix
 */
function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {

  parseAbsoluteUrl(appBase, this);


  /**
   * Parse given hashbang URL into properties
   * @param {string} url Hashbang URL
   * @private
   */
  this.$$parse = function(url) {
    var withoutBaseUrl = stripBaseUrl(appBase, url) || stripBaseUrl(appBaseNoFile, url);
    var withoutHashUrl;

    if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') {

      // The rest of the URL starts with a hash so we have
      // got either a hashbang path or a plain hash fragment
      withoutHashUrl = stripBaseUrl(hashPrefix, withoutBaseUrl);
      if (isUndefined(withoutHashUrl)) {
        // There was no hashbang prefix so we just have a hash fragment
        withoutHashUrl = withoutBaseUrl;
      }

    } else {
      // There was no hashbang path nor hash fragment:
      // If we are in HTML5 mode we use what is left as the path;
      // Otherwise we ignore what is left
      if (this.$$html5) {
        withoutHashUrl = withoutBaseUrl;
      } else {
        withoutHashUrl = '';
        if (isUndefined(withoutBaseUrl)) {
          appBase = url;
          /** @type {?} */ (this).replace();
        }
      }
    }

    parseAppUrl(withoutHashUrl, this, false);

    this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase);

    this.$$compose();

    /*
     * In Windows, on an anchor node on documents loaded from
     * the filesystem, the browser will return a pathname
     * prefixed with the drive name ('/C:/path') when a
     * pathname without a drive is set:
     *  * a.setAttribute('href', '/foo')
     *   * a.pathname === '/C:/foo' //true
     *
     * Inside of AngularJS, we're always using pathnames that
     * do not include drive names for routing.
     */
    function removeWindowsDriveName(path, url, base) {
      /*
      Matches paths for file protocol on windows,
      such as /C:/foo/bar, and captures only /foo/bar.
      */
      var windowsFilePathExp = /^\/[A-Z]:(\/.*)/;

      var firstPathSegmentMatch;

      //Get the relative path from the input URL.
      if (startsWith(url, base)) {
        url = url.replace(base, '');
      }

      // The input URL intentionally contains a first path segment that ends with a colon.
      if (windowsFilePathExp.exec(url)) {
        return path;
      }

      firstPathSegmentMatch = windowsFilePathExp.exec(path);
      return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path;
    }
  };

  this.$$normalizeUrl = function(url) {
    return appBase + (url ? hashPrefix + url : '');
  };

  this.$$parseLinkUrl = function(url, relHref) {
    if (stripHash(appBase) === stripHash(url)) {
      this.$$parse(url);
      return true;
    }
    return false;
  };
}


/**
 * LocationHashbangUrl represents URL
 * This object is exposed as $location service when html5 history api is enabled but the browser
 * does not support it.
 *
 * @constructor
 * @param {string} appBase application base URL
 * @param {string} appBaseNoFile application base URL stripped of any filename
 * @param {string} hashPrefix hashbang prefix
 */
function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) {
  this.$$html5 = true;
  LocationHashbangUrl.apply(this, arguments);

  this.$$parseLinkUrl = function(url, relHref) {
    if (relHref && relHref[0] === '#') {
      // special case for links to hash fragments:
      // keep the old url and only replace the hash fragment
      this.hash(relHref.slice(1));
      return true;
    }

    var rewrittenUrl;
    var appUrl;

    if (appBase === stripHash(url)) {
      rewrittenUrl = url;
    } else if ((appUrl = stripBaseUrl(appBaseNoFile, url))) {
      rewrittenUrl = appBase + hashPrefix + appUrl;
    } else if (appBaseNoFile === url + '/') {
      rewrittenUrl = appBaseNoFile;
    }
    if (rewrittenUrl) {
      this.$$parse(rewrittenUrl);
    }
    return !!rewrittenUrl;
  };

  this.$$normalizeUrl = function(url) {
    // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#'
    return appBase + hashPrefix + url;
  };
}


var locationPrototype = {

  /**
   * Ensure absolute URL is initialized.
   * @private
   */
  $$absUrl:'',

  /**
   * Are we in html5 mode?
   * @private
   */
  $$html5: false,

  /**
   * Has any change been replacing?
   * @private
   */
  $$replace: false,

  /**
   * Compose url and update `url` and `absUrl` property
   * @private
   */
  $$compose: function() {
    this.$$url = normalizePath(this.$$path, this.$$search, this.$$hash);
    this.$$absUrl = this.$$normalizeUrl(this.$$url);
    this.$$urlUpdatedByLocation = true;
  },

  /**
   * @ngdoc method
   * @name $location#absUrl
   *
   * @description
   * This method is getter only.
   *
   * Return full URL representation with all segments encoded according to rules specified in
   * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt).
   *
   *
   * ```js
   * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
   * var absUrl = $location.absUrl();
   * // => "http://example.com/#/some/path?foo=bar&baz=xoxo"
   * ```
   *
   * @return {string} full URL
   */
  absUrl: locationGetter('$$absUrl'),

  /**
   * @ngdoc method
   * @name $location#url
   *
   * @description
   * This method is getter / setter.
   *
   * Return URL (e.g. `/path?a=b#hash`) when called without any parameter.
   *
   * Change path, search and hash, when called with parameter and return `$location`.
   *
   *
   * ```js
   * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
   * var url = $location.url();
   * // => "/some/path?foo=bar&baz=xoxo"
   * ```
   *
   * @param {string=} url New URL without base prefix (e.g. `/path?a=b#hash`)
   * @return {string} url
   */
  url: function(url) {
    if (isUndefined(url)) {
      return this.$$url;
    }

    var match = PATH_MATCH.exec(url);
    if (match[1] || url === '') this.path(decodeURIComponent(match[1]));
    if (match[2] || match[1] || url === '') this.search(match[3] || '');
    this.hash(match[5] || '');

    return this;
  },

  /**
   * @ngdoc method
   * @name $location#protocol
   *
   * @description
   * This method is getter only.
   *
   * Return protocol of current URL.
   *
   *
   * ```js
   * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
   * var protocol = $location.protocol();
   * // => "http"
   * ```
   *
   * @return {string} protocol of current URL
   */
  protocol: locationGetter('$$protocol'),

  /**
   * @ngdoc method
   * @name $location#host
   *
   * @description
   * This method is getter only.
   *
   * Return host of current URL.
   *
   * Note: compared to the non-AngularJS version `location.host` which returns `hostname:port`, this returns the `hostname` portion only.
   *
   *
   * ```js
   * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
   * var host = $location.host();
   * // => "example.com"
   *
   * // given URL http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo
   * host = $location.host();
   * // => "example.com"
   * host = location.host;
   * // => "example.com:8080"
   * ```
   *
   * @return {string} host of current URL.
   */
  host: locationGetter('$$host'),

  /**
   * @ngdoc method
   * @name $location#port
   *
   * @description
   * This method is getter only.
   *
   * Return port of current URL.
   *
   *
   * ```js
   * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
   * var port = $location.port();
   * // => 80
   * ```
   *
   * @return {Number} port
   */
  port: locationGetter('$$port'),

  /**
   * @ngdoc method
   * @name $location#path
   *
   * @description
   * This method is getter / setter.
   *
   * Return path of current URL when called without any parameter.
   *
   * Change path when called with parameter and return `$location`.
   *
   * Note: Path should always begin with forward slash (/), this method will add the forward slash
   * if it is missing.
   *
   *
   * ```js
   * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
   * var path = $location.path();
   * // => "/some/path"
   * ```
   *
   * @param {(string|number)=} path New path
   * @return {(string|object)} path if called with no parameters, or `$location` if called with a parameter
   */
  path: locationGetterSetter('$$path', function(path) {
    path = path !== null ? path.toString() : '';
    return path.charAt(0) === '/' ? path : '/' + path;
  }),

  /**
   * @ngdoc method
   * @name $location#search
   *
   * @description
   * This method is getter / setter.
   *
   * Return search part (as object) of current URL when called without any parameter.
   *
   * Change search part when called with parameter and return `$location`.
   *
   *
   * ```js
   * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
   * var searchObject = $location.search();
   * // => {foo: 'bar', baz: 'xoxo'}
   *
   * // set foo to 'yipee'
   * $location.search('foo', 'yipee');
   * // $location.search() => {foo: 'yipee', baz: 'xoxo'}
   * ```
   *
   * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or
   * hash object.
   *
   * When called with a single argument the method acts as a setter, setting the `search` component
   * of `$location` to the specified value.
   *
   * If the argument is a hash object containing an array of values, these values will be encoded
   * as duplicate search parameters in the URL.
   *
   * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue`
   * will override only a single search property.
   *
   * If `paramValue` is an array, it will override the property of the `search` component of
   * `$location` specified via the first argument.
   *
   * If `paramValue` is `null`, the property specified via the first argument will be deleted.
   *
   * If `paramValue` is `true`, the property specified via the first argument will be added with no
   * value nor trailing equal sign.
   *
   * @return {Object} If called with no arguments returns the parsed `search` object. If called with
   * one or more arguments returns `$location` object itself.
   */
  search: function(search, paramValue) {
    switch (arguments.length) {
      case 0:
        return this.$$search;
      case 1:
        if (isString(search) || isNumber(search)) {
          search = search.toString();
          this.$$search = parseKeyValue(search);
        } else if (isObject(search)) {
          search = copy(search, {});
          // remove object undefined or null properties
          forEach(search, function(value, key) {
            if (value == null) delete search[key];
          });

          this.$$search = search;
        } else {
          throw $locationMinErr('isrcharg',
              'The first argument of the `$location#search()` call must be a string or an object.');
        }
        break;
      default:
        if (isUndefined(paramValue) || paramValue === null) {
          delete this.$$search[search];
        } else {
          this.$$search[search] = paramValue;
        }
    }

    this.$$compose();
    return this;
  },

  /**
   * @ngdoc method
   * @name $location#hash
   *
   * @description
   * This method is getter / setter.
   *
   * Returns the hash fragment when called without any parameters.
   *
   * Changes the hash fragment when called with a parameter and returns `$location`.
   *
   *
   * ```js
   * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
   * var hash = $location.hash();
   * // => "hashValue"
   * ```
   *
   * @param {(string|number)=} hash New hash fragment
   * @return {string} hash
   */
  hash: locationGetterSetter('$$hash', function(hash) {
    return hash !== null ? hash.toString() : '';
  }),

  /**
   * @ngdoc method
   * @name $location#replace
   *
   * @description
   * If called, all changes to $location during the current `$digest` will replace the current history
   * record, instead of adding a new one.
   */
  replace: function() {
    this.$$replace = true;
    return this;
  }
};

forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) {
  Location.prototype = Object.create(locationPrototype);

  /**
   * @ngdoc method
   * @name $location#state
   *
   * @description
   * This method is getter / setter.
   *
   * Return the history state object when called without any parameter.
   *
   * Change the history state object when called with one parameter and return `$location`.
   * The state object is later passed to `pushState` or `replaceState`.
   *
   * NOTE: This method is supported only in HTML5 mode and only in browsers supporting
   * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support
   * older browsers (like IE9 or Android < 4.0), don't use this method.
   *
   * @param {object=} state State object for pushState or replaceState
   * @return {object} state
   */
  Location.prototype.state = function(state) {
    if (!arguments.length) {
      return this.$$state;
    }

    if (Location !== LocationHtml5Url || !this.$$html5) {
      throw $locationMinErr('nostate', 'History API state support is available only ' +
        'in HTML5 mode and only in browsers supporting HTML5 History API');
    }
    // The user might modify `stateObject` after invoking `$location.state(stateObject)`
    // but we're changing the $$state reference to $browser.state() during the $digest
    // so the modification window is narrow.
    this.$$state = isUndefined(state) ? null : state;
    this.$$urlUpdatedByLocation = true;

    return this;
  };
});


function locationGetter(property) {
  return /** @this */ function() {
    return this[property];
  };
}


function locationGetterSetter(property, preprocess) {
  return /** @this */ function(value) {
    if (isUndefined(value)) {
      return this[property];
    }

    this[property] = preprocess(value);
    this.$$compose();

    return this;
  };
}


/**
 * @ngdoc service
 * @name $location
 *
 * @requires $rootElement
 *
 * @description
 * The $location service parses the URL in the browser address bar (based on the
 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL
 * available to your application. Changes to the URL in the address bar are reflected into
 * $location service and changes to $location are reflected into the browser address bar.
 *
 * **The $location service:**
 *
 * - Exposes the current URL in the browser address bar, so you can
 *   - Watch and observe the URL.
 *   - Change the URL.
 * - Synchronizes the URL with the browser when the user
 *   - Changes the address bar.
 *   - Clicks the back or forward button (or clicks a History link).
 *   - Clicks on a link.
 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
 *
 * For more information see {@link guide/$location Developer Guide: Using $location}
 */

/**
 * @ngdoc provider
 * @name $locationProvider
 * @this
 *
 * @description
 * Use the `$locationProvider` to configure how the application deep linking paths are stored.
 */
function $LocationProvider() {
  var hashPrefix = '!',
      html5Mode = {
        enabled: false,
        requireBase: true,
        rewriteLinks: true
      };

  /**
   * @ngdoc method
   * @name $locationProvider#hashPrefix
   * @description
   * The default value for the prefix is `'!'`.
   * @param {string=} prefix Prefix for hash part (containing path and search)
   * @returns {*} current value if used as getter or itself (chaining) if used as setter
   */
  this.hashPrefix = function(prefix) {
    if (isDefined(prefix)) {
      hashPrefix = prefix;
      return this;
    } else {
      return hashPrefix;
    }
  };

  /**
   * @ngdoc method
   * @name $locationProvider#html5Mode
   * @description
   * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value.
   *   If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported
   *   properties:
   *   - **enabled** – `{boolean}` – (default: false) If true, will rely on `history.pushState` to
   *     change urls where supported. Will fall back to hash-prefixed paths in browsers that do not
   *     support `pushState`.
   *   - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies
   *     whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are
   *     true, and a base tag is not present, an error will be thrown when `$location` is injected.
   *     See the {@link guide/$location $location guide for more information}
   *   - **rewriteLinks** - `{boolean|string}` - (default: `true`) When html5Mode is enabled,
   *     enables/disables URL rewriting for relative links. If set to a string, URL rewriting will
   *     only happen on links with an attribute that matches the given string. For example, if set
   *     to `'internal-link'`, then the URL will only be rewritten for `<a internal-link>` links.
   *     Note that [attribute name normalization](guide/directive#normalization) does not apply
   *     here, so `'internalLink'` will **not** match `'internal-link'`.
   *
   * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter
   */
  this.html5Mode = function(mode) {
    if (isBoolean(mode)) {
      html5Mode.enabled = mode;
      return this;
    } else if (isObject(mode)) {

      if (isBoolean(mode.enabled)) {
        html5Mode.enabled = mode.enabled;
      }

      if (isBoolean(mode.requireBase)) {
        html5Mode.requireBase = mode.requireBase;
      }

      if (isBoolean(mode.rewriteLinks) || isString(mode.rewriteLinks)) {
        html5Mode.rewriteLinks = mode.rewriteLinks;
      }

      return this;
    } else {
      return html5Mode;
    }
  };

  /**
   * @ngdoc event
   * @name $location#$locationChangeStart
   * @eventType broadcast on root scope
   * @description
   * Broadcasted before a URL will change.
   *
   * This change can be prevented by calling
   * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more
   * details about event object. Upon successful change
   * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired.
   *
   * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
   * the browser supports the HTML5 History API.
   *
   * @param {Object} angularEvent Synthetic event object.
   * @param {string} newUrl New URL
   * @param {string=} oldUrl URL that was before it was changed.
   * @param {string=} newState New history state object
   * @param {string=} oldState History state object that was before it was changed.
   */

  /**
   * @ngdoc event
   * @name $location#$locationChangeSuccess
   * @eventType broadcast on root scope
   * @description
   * Broadcasted after a URL was changed.
   *
   * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
   * the browser supports the HTML5 History API.
   *
   * @param {Object} angularEvent Synthetic event object.
   * @param {string} newUrl New URL
   * @param {string=} oldUrl URL that was before it was changed.
   * @param {string=} newState New history state object
   * @param {string=} oldState History state object that was before it was changed.
   */

  this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window',
      function($rootScope, $browser, $sniffer, $rootElement, $window) {
    var $location,
        LocationMode,
        baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to ''
        initialUrl = $browser.url(),
        appBase;

    if (html5Mode.enabled) {
      if (!baseHref && html5Mode.requireBase) {
        throw $locationMinErr('nobase',
          '$location in HTML5 mode requires a <base> tag to be present!');
      }
      appBase = serverBase(initialUrl) + (baseHref || '/');
      LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url;
    } else {
      appBase = stripHash(initialUrl);
      LocationMode = LocationHashbangUrl;
    }
    var appBaseNoFile = stripFile(appBase);

    $location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix);
    $location.$$parseLinkUrl(initialUrl, initialUrl);

    $location.$$state = $browser.state();

    var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;

    // Determine if two URLs are equal despite potentially having different encoding/normalizing
    //  such as $location.absUrl() vs $browser.url()
    // See https://github.com/angular/angular.js/issues/16592
    function urlsEqual(a, b) {
      return a === b || urlResolve(a).href === urlResolve(b).href;
    }

    function setBrowserUrlWithFallback(url, replace, state) {
      var oldUrl = $location.url();
      var oldState = $location.$$state;
      try {
        $browser.url(url, replace, state);

        // Make sure $location.state() returns referentially identical (not just deeply equal)
        // state object; this makes possible quick checking if the state changed in the digest
        // loop. Checking deep equality would be too expensive.
        $location.$$state = $browser.state();
      } catch (e) {
        // Restore old values if pushState fails
        $location.url(oldUrl);
        $location.$$state = oldState;

        throw e;
      }
    }

    $rootElement.on('click', function(event) {
      var rewriteLinks = html5Mode.rewriteLinks;
      // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
      // currently we open nice url link and redirect then

      if (!rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which === 2 || event.button === 2) return;

      var elm = jqLite(event.target);

      // traverse the DOM up to find first A tag
      while (nodeName_(elm[0]) !== 'a') {
        // ignore rewriting if no A tag (reached root element, or no parent - removed from document)
        if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return;
      }

      if (isString(rewriteLinks) && isUndefined(elm.attr(rewriteLinks))) return;

      var absHref = elm.prop('href');
      // get the actual href attribute - see
      // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx
      var relHref = elm.attr('href') || elm.attr('xlink:href');

      if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') {
        // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during
        // an animation.
        absHref = urlResolve(absHref.animVal).href;
      }

      // Ignore when url is started with javascript: or mailto:
      if (IGNORE_URI_REGEXP.test(absHref)) return;

      if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) {
        if ($location.$$parseLinkUrl(absHref, relHref)) {
          // We do a preventDefault for all urls that are part of the AngularJS application,
          // in html5mode and also without, so that we are able to abort navigation without
          // getting double entries in the location history.
          event.preventDefault();
          // update location manually
          if ($location.absUrl() !== $browser.url()) {
            $rootScope.$apply();
          }
        }
      }
    });


    // rewrite hashbang url <> html5 url
    if ($location.absUrl() !== initialUrl) {
      $browser.url($location.absUrl(), true);
    }

    var initializing = true;

    // update $location when $browser url changes
    $browser.onUrlChange(function(newUrl, newState) {

      if (!startsWith(newUrl, appBaseNoFile)) {
        // If we are navigating outside of the app then force a reload
        $window.location.href = newUrl;
        return;
      }

      $rootScope.$evalAsync(function() {
        var oldUrl = $location.absUrl();
        var oldState = $location.$$state;
        var defaultPrevented;
        $location.$$parse(newUrl);
        $location.$$state = newState;

        defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
            newState, oldState).defaultPrevented;

        // if the location was changed by a `$locationChangeStart` handler then stop
        // processing this location change
        if ($location.absUrl() !== newUrl) return;

        if (defaultPrevented) {
          $location.$$parse(oldUrl);
          $location.$$state = oldState;
          setBrowserUrlWithFallback(oldUrl, false, oldState);
        } else {
          initializing = false;
          afterLocationChange(oldUrl, oldState);
        }
      });
      if (!$rootScope.$$phase) $rootScope.$digest();
    });

    // update browser
    $rootScope.$watch(function $locationWatch() {
      if (initializing || $location.$$urlUpdatedByLocation) {
        $location.$$urlUpdatedByLocation = false;

        var oldUrl = $browser.url();
        var newUrl = $location.absUrl();
        var oldState = $browser.state();
        var currentReplace = $location.$$replace;
        var urlOrStateChanged = !urlsEqual(oldUrl, newUrl) ||
          ($location.$$html5 && $sniffer.history && oldState !== $location.$$state);

        if (initializing || urlOrStateChanged) {
          initializing = false;

          $rootScope.$evalAsync(function() {
            var newUrl = $location.absUrl();
            var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
                $location.$$state, oldState).defaultPrevented;

            // if the location was changed by a `$locationChangeStart` handler then stop
            // processing this location change
            if ($location.absUrl() !== newUrl) return;

            if (defaultPrevented) {
              $location.$$parse(oldUrl);
              $location.$$state = oldState;
            } else {
              if (urlOrStateChanged) {
                setBrowserUrlWithFallback(newUrl, currentReplace,
                                          oldState === $location.$$state ? null : $location.$$state);
              }
              afterLocationChange(oldUrl, oldState);
            }
          });
        }
      }

      $location.$$replace = false;

      // we don't need to return anything because $evalAsync will make the digest loop dirty when
      // there is a change
    });

    return $location;

    function afterLocationChange(oldUrl, oldState) {
      $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl,
        $location.$$state, oldState);
    }
}];
}

/**
 * @ngdoc service
 * @name $log
 * @requires $window
 *
 * @description
 * Simple service for logging. Default implementation safely writes the message
 * into the browser's console (if present).
 *
 * The main purpose of this service is to simplify debugging and troubleshooting.
 *
 * To reveal the location of the calls to `$log` in the JavaScript console,
 * you can "blackbox" the AngularJS source in your browser:
 *
 * [Mozilla description of blackboxing](https://developer.mozilla.org/en-US/docs/Tools/Debugger/How_to/Black_box_a_source).
 * [Chrome description of blackboxing](https://developer.chrome.com/devtools/docs/blackboxing).
 *
 * Note: Not all browsers support blackboxing.
 *
 * The default is to log `debug` messages. You can use
 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this.
 *
 * @example
   <example module="logExample" name="log-service">
     <file name="script.js">
       angular.module('logExample', [])
         .controller('LogController', ['$scope', '$log', function($scope, $log) {
           $scope.$log = $log;
           $scope.message = 'Hello World!';
         }]);
     </file>
     <file name="index.html">
       <div ng-controller="LogController">
         <p>Reload this page with open console, enter text and hit the log button...</p>
         <label>Message:
         <input type="text" ng-model="message" /></label>
         <button ng-click="$log.log(message)">log</button>
         <button ng-click="$log.warn(message)">warn</button>
         <button ng-click="$log.info(message)">info</button>
         <button ng-click="$log.error(message)">error</button>
         <button ng-click="$log.debug(message)">debug</button>
       </div>
     </file>
   </example>
 */

/**
 * @ngdoc provider
 * @name $logProvider
 * @this
 *
 * @description
 * Use the `$logProvider` to configure how the application logs messages
 */
function $LogProvider() {
  var debug = true,
      self = this;

  /**
   * @ngdoc method
   * @name $logProvider#debugEnabled
   * @description
   * @param {boolean=} flag enable or disable debug level messages
   * @returns {*} current value if used as getter or itself (chaining) if used as setter
   */
  this.debugEnabled = function(flag) {
    if (isDefined(flag)) {
      debug = flag;
      return this;
    } else {
      return debug;
    }
  };

  this.$get = ['$window', function($window) {
    // Support: IE 9-11, Edge 12-14+
    // IE/Edge display errors in such a way that it requires the user to click in 4 places
    // to see the stack trace. There is no way to feature-detect it so there's a chance
    // of the user agent sniffing to go wrong but since it's only about logging, this shouldn't
    // break apps. Other browsers display errors in a sensible way and some of them map stack
    // traces along source maps if available so it makes sense to let browsers display it
    // as they want.
    var formatStackTrace = msie || /\bEdge\//.test($window.navigator && $window.navigator.userAgent);

    return {
      /**
       * @ngdoc method
       * @name $log#log
       *
       * @description
       * Write a log message
       */
      log: consoleLog('log'),

      /**
       * @ngdoc method
       * @name $log#info
       *
       * @description
       * Write an information message
       */
      info: consoleLog('info'),

      /**
       * @ngdoc method
       * @name $log#warn
       *
       * @description
       * Write a warning message
       */
      warn: consoleLog('warn'),

      /**
       * @ngdoc method
       * @name $log#error
       *
       * @description
       * Write an error message
       */
      error: consoleLog('error'),

      /**
       * @ngdoc method
       * @name $log#debug
       *
       * @description
       * Write a debug message
       */
      debug: (function() {
        var fn = consoleLog('debug');

        return function() {
          if (debug) {
            fn.apply(self, arguments);
          }
        };
      })()
    };

    function formatError(arg) {
      if (isError(arg)) {
        if (arg.stack && formatStackTrace) {
          arg = (arg.message && arg.stack.indexOf(arg.message) === -1)
              ? 'Error: ' + arg.message + '\n' + arg.stack
              : arg.stack;
        } else if (arg.sourceURL) {
          arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line;
        }
      }
      return arg;
    }

    function consoleLog(type) {
      var console = $window.console || {},
          logFn = console[type] || console.log || noop;

      return function() {
        var args = [];
        forEach(arguments, function(arg) {
          args.push(formatError(arg));
        });
        // Support: IE 9 only
        // console methods don't inherit from Function.prototype in IE 9 so we can't
        // call `logFn.apply(console, args)` directly.
        return Function.prototype.apply.call(logFn, console, args);
      };
    }
  }];
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *     Any commits to this file should be reviewed with security in mind.  *
 *   Changes to this file can potentially create security vulnerabilities. *
 *          An approval from 2 Core members with history of modifying      *
 *                         this file is required.                          *
 *                                                                         *
 *  Does the change somehow allow for arbitrary javascript to be executed? *
 *    Or allows for someone to change the prototype of built-in objects?   *
 *     Or gives undesired access to variables likes document or window?    *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

var $parseMinErr = minErr('$parse');

var objectValueOf = {}.constructor.prototype.valueOf;

// Sandboxing AngularJS Expressions
// ------------------------------
// AngularJS expressions are no longer sandboxed. So it is now even easier to access arbitrary JS code by
// various means such as obtaining a reference to native JS functions like the Function constructor.
//
// As an example, consider the following AngularJS expression:
//
//   {}.toString.constructor('alert("evil JS code")')
//
// It is important to realize that if you create an expression from a string that contains user provided
// content then it is possible that your application contains a security vulnerability to an XSS style attack.
//
// See https://docs.angularjs.org/guide/security


function getStringValue(name) {
  // Property names must be strings. This means that non-string objects cannot be used
  // as keys in an object. Any non-string object, including a number, is typecasted
  // into a string via the toString method.
  // -- MDN, https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Property_accessors#Property_names
  //
  // So, to ensure that we are checking the same `name` that JavaScript would use, we cast it
  // to a string. It's not always possible. If `name` is an object and its `toString` method is
  // 'broken' (doesn't return a string, isn't a function, etc.), an error will be thrown:
  //
  // TypeError: Cannot convert object to primitive value
  //
  // For performance reasons, we don't catch this error here and allow it to propagate up the call
  // stack. Note that you'll get the same error in JavaScript if you try to access a property using
  // such a 'broken' object as a key.
  return name + '';
}


var OPERATORS = createMap();
forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; });
var ESCAPE = {'n':'\n', 'f':'\f', 'r':'\r', 't':'\t', 'v':'\v', '\'':'\'', '"':'"'};


/////////////////////////////////////////


/**
 * @constructor
 */
var Lexer = function Lexer(options) {
  this.options = options;
};

Lexer.prototype = {
  constructor: Lexer,

  lex: function(text) {
    this.text = text;
    this.index = 0;
    this.tokens = [];

    while (this.index < this.text.length) {
      var ch = this.text.charAt(this.index);
      if (ch === '"' || ch === '\'') {
        this.readString(ch);
      } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) {
        this.readNumber();
      } else if (this.isIdentifierStart(this.peekMultichar())) {
        this.readIdent();
      } else if (this.is(ch, '(){}[].,;:?')) {
        this.tokens.push({index: this.index, text: ch});
        this.index++;
      } else if (this.isWhitespace(ch)) {
        this.index++;
      } else {
        var ch2 = ch + this.peek();
        var ch3 = ch2 + this.peek(2);
        var op1 = OPERATORS[ch];
        var op2 = OPERATORS[ch2];
        var op3 = OPERATORS[ch3];
        if (op1 || op2 || op3) {
          var token = op3 ? ch3 : (op2 ? ch2 : ch);
          this.tokens.push({index: this.index, text: token, operator: true});
          this.index += token.length;
        } else {
          this.throwError('Unexpected next character ', this.index, this.index + 1);
        }
      }
    }
    return this.tokens;
  },

  is: function(ch, chars) {
    return chars.indexOf(ch) !== -1;
  },

  peek: function(i) {
    var num = i || 1;
    return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false;
  },

  isNumber: function(ch) {
    return ('0' <= ch && ch <= '9') && typeof ch === 'string';
  },

  isWhitespace: function(ch) {
    // IE treats non-breaking space as \u00A0
    return (ch === ' ' || ch === '\r' || ch === '\t' ||
            ch === '\n' || ch === '\v' || ch === '\u00A0');
  },

  isIdentifierStart: function(ch) {
    return this.options.isIdentifierStart ?
        this.options.isIdentifierStart(ch, this.codePointAt(ch)) :
        this.isValidIdentifierStart(ch);
  },

  isValidIdentifierStart: function(ch) {
    return ('a' <= ch && ch <= 'z' ||
            'A' <= ch && ch <= 'Z' ||
            '_' === ch || ch === '$');
  },

  isIdentifierContinue: function(ch) {
    return this.options.isIdentifierContinue ?
        this.options.isIdentifierContinue(ch, this.codePointAt(ch)) :
        this.isValidIdentifierContinue(ch);
  },

  isValidIdentifierContinue: function(ch, cp) {
    return this.isValidIdentifierStart(ch, cp) || this.isNumber(ch);
  },

  codePointAt: function(ch) {
    if (ch.length === 1) return ch.charCodeAt(0);
    // eslint-disable-next-line no-bitwise
    return (ch.charCodeAt(0) << 10) + ch.charCodeAt(1) - 0x35FDC00;
  },

  peekMultichar: function() {
    var ch = this.text.charAt(this.index);
    var peek = this.peek();
    if (!peek) {
      return ch;
    }
    var cp1 = ch.charCodeAt(0);
    var cp2 = peek.charCodeAt(0);
    if (cp1 >= 0xD800 && cp1 <= 0xDBFF && cp2 >= 0xDC00 && cp2 <= 0xDFFF) {
      return ch + peek;
    }
    return ch;
  },

  isExpOperator: function(ch) {
    return (ch === '-' || ch === '+' || this.isNumber(ch));
  },

  throwError: function(error, start, end) {
    end = end || this.index;
    var colStr = (isDefined(start)
            ? 's ' + start +  '-' + this.index + ' [' + this.text.substring(start, end) + ']'
            : ' ' + end);
    throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].',
        error, colStr, this.text);
  },

  readNumber: function() {
    var number = '';
    var start = this.index;
    while (this.index < this.text.length) {
      var ch = lowercase(this.text.charAt(this.index));
      if (ch === '.' || this.isNumber(ch)) {
        number += ch;
      } else {
        var peekCh = this.peek();
        if (ch === 'e' && this.isExpOperator(peekCh)) {
          number += ch;
        } else if (this.isExpOperator(ch) &&
            peekCh && this.isNumber(peekCh) &&
            number.charAt(number.length - 1) === 'e') {
          number += ch;
        } else if (this.isExpOperator(ch) &&
            (!peekCh || !this.isNumber(peekCh)) &&
            number.charAt(number.length - 1) === 'e') {
          this.throwError('Invalid exponent');
        } else {
          break;
        }
      }
      this.index++;
    }
    this.tokens.push({
      index: start,
      text: number,
      constant: true,
      value: Number(number)
    });
  },

  readIdent: function() {
    var start = this.index;
    this.index += this.peekMultichar().length;
    while (this.index < this.text.length) {
      var ch = this.peekMultichar();
      if (!this.isIdentifierContinue(ch)) {
        break;
      }
      this.index += ch.length;
    }
    this.tokens.push({
      index: start,
      text: this.text.slice(start, this.index),
      identifier: true
    });
  },

  readString: function(quote) {
    var start = this.index;
    this.index++;
    var string = '';
    var rawString = quote;
    var escape = false;
    while (this.index < this.text.length) {
      var ch = this.text.charAt(this.index);
      rawString += ch;
      if (escape) {
        if (ch === 'u') {
          var hex = this.text.substring(this.index + 1, this.index + 5);
          if (!hex.match(/[\da-f]{4}/i)) {
            this.throwError('Invalid unicode escape [\\u' + hex + ']');
          }
          this.index += 4;
          string += String.fromCharCode(parseInt(hex, 16));
        } else {
          var rep = ESCAPE[ch];
          string = string + (rep || ch);
        }
        escape = false;
      } else if (ch === '\\') {
        escape = true;
      } else if (ch === quote) {
        this.index++;
        this.tokens.push({
          index: start,
          text: rawString,
          constant: true,
          value: string
        });
        return;
      } else {
        string += ch;
      }
      this.index++;
    }
    this.throwError('Unterminated quote', start);
  }
};

var AST = function AST(lexer, options) {
  this.lexer = lexer;
  this.options = options;
};

AST.Program = 'Program';
AST.ExpressionStatement = 'ExpressionStatement';
AST.AssignmentExpression = 'AssignmentExpression';
AST.ConditionalExpression = 'ConditionalExpression';
AST.LogicalExpression = 'LogicalExpression';
AST.BinaryExpression = 'BinaryExpression';
AST.UnaryExpression = 'UnaryExpression';
AST.CallExpression = 'CallExpression';
AST.MemberExpression = 'MemberExpression';
AST.Identifier = 'Identifier';
AST.Literal = 'Literal';
AST.ArrayExpression = 'ArrayExpression';
AST.Property = 'Property';
AST.ObjectExpression = 'ObjectExpression';
AST.ThisExpression = 'ThisExpression';
AST.LocalsExpression = 'LocalsExpression';

// Internal use only
AST.NGValueParameter = 'NGValueParameter';

AST.prototype = {
  ast: function(text) {
    this.text = text;
    this.tokens = this.lexer.lex(text);

    var value = this.program();

    if (this.tokens.length !== 0) {
      this.throwError('is an unexpected token', this.tokens[0]);
    }

    return value;
  },

  program: function() {
    var body = [];
    while (true) {
      if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']'))
        body.push(this.expressionStatement());
      if (!this.expect(';')) {
        return { type: AST.Program, body: body};
      }
    }
  },

  expressionStatement: function() {
    return { type: AST.ExpressionStatement, expression: this.filterChain() };
  },

  filterChain: function() {
    var left = this.expression();
    while (this.expect('|')) {
      left = this.filter(left);
    }
    return left;
  },

  expression: function() {
    return this.assignment();
  },

  assignment: function() {
    var result = this.ternary();
    if (this.expect('=')) {
      if (!isAssignable(result)) {
        throw $parseMinErr('lval', 'Trying to assign a value to a non l-value');
      }

      result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='};
    }
    return result;
  },

  ternary: function() {
    var test = this.logicalOR();
    var alternate;
    var consequent;
    if (this.expect('?')) {
      alternate = this.expression();
      if (this.consume(':')) {
        consequent = this.expression();
        return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent};
      }
    }
    return test;
  },

  logicalOR: function() {
    var left = this.logicalAND();
    while (this.expect('||')) {
      left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() };
    }
    return left;
  },

  logicalAND: function() {
    var left = this.equality();
    while (this.expect('&&')) {
      left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()};
    }
    return left;
  },

  equality: function() {
    var left = this.relational();
    var token;
    while ((token = this.expect('==','!=','===','!=='))) {
      left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() };
    }
    return left;
  },

  relational: function() {
    var left = this.additive();
    var token;
    while ((token = this.expect('<', '>', '<=', '>='))) {
      left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() };
    }
    return left;
  },

  additive: function() {
    var left = this.multiplicative();
    var token;
    while ((token = this.expect('+','-'))) {
      left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() };
    }
    return left;
  },

  multiplicative: function() {
    var left = this.unary();
    var token;
    while ((token = this.expect('*','/','%'))) {
      left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() };
    }
    return left;
  },

  unary: function() {
    var token;
    if ((token = this.expect('+', '-', '!'))) {
      return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() };
    } else {
      return this.primary();
    }
  },

  primary: function() {
    var primary;
    if (this.expect('(')) {
      primary = this.filterChain();
      this.consume(')');
    } else if (this.expect('[')) {
      primary = this.arrayDeclaration();
    } else if (this.expect('{')) {
      primary = this.object();
    } else if (this.selfReferential.hasOwnProperty(this.peek().text)) {
      primary = copy(this.selfReferential[this.consume().text]);
    } else if (this.options.literals.hasOwnProperty(this.peek().text)) {
      primary = { type: AST.Literal, value: this.options.literals[this.consume().text]};
    } else if (this.peek().identifier) {
      primary = this.identifier();
    } else if (this.peek().constant) {
      primary = this.constant();
    } else {
      this.throwError('not a primary expression', this.peek());
    }

    var next;
    while ((next = this.expect('(', '[', '.'))) {
      if (next.text === '(') {
        primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() };
        this.consume(')');
      } else if (next.text === '[') {
        primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true };
        this.consume(']');
      } else if (next.text === '.') {
        primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false };
      } else {
        this.throwError('IMPOSSIBLE');
      }
    }
    return primary;
  },

  filter: function(baseExpression) {
    var args = [baseExpression];
    var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true};

    while (this.expect(':')) {
      args.push(this.expression());
    }

    return result;
  },

  parseArguments: function() {
    var args = [];
    if (this.peekToken().text !== ')') {
      do {
        args.push(this.filterChain());
      } while (this.expect(','));
    }
    return args;
  },

  identifier: function() {
    var token = this.consume();
    if (!token.identifier) {
      this.throwError('is not a valid identifier', token);
    }
    return { type: AST.Identifier, name: token.text };
  },

  constant: function() {
    // TODO check that it is a constant
    return { type: AST.Literal, value: this.consume().value };
  },

  arrayDeclaration: function() {
    var elements = [];
    if (this.peekToken().text !== ']') {
      do {
        if (this.peek(']')) {
          // Support trailing commas per ES5.1.
          break;
        }
        elements.push(this.expression());
      } while (this.expect(','));
    }
    this.consume(']');

    return { type: AST.ArrayExpression, elements: elements };
  },

  object: function() {
    var properties = [], property;
    if (this.peekToken().text !== '}') {
      do {
        if (this.peek('}')) {
          // Support trailing commas per ES5.1.
          break;
        }
        property = {type: AST.Property, kind: 'init'};
        if (this.peek().constant) {
          property.key = this.constant();
          property.computed = false;
          this.consume(':');
          property.value = this.expression();
        } else if (this.peek().identifier) {
          property.key = this.identifier();
          property.computed = false;
          if (this.peek(':')) {
            this.consume(':');
            property.value = this.expression();
          } else {
            property.value = property.key;
          }
        } else if (this.peek('[')) {
          this.consume('[');
          property.key = this.expression();
          this.consume(']');
          property.computed = true;
          this.consume(':');
          property.value = this.expression();
        } else {
          this.throwError('invalid key', this.peek());
        }
        properties.push(property);
      } while (this.expect(','));
    }
    this.consume('}');

    return {type: AST.ObjectExpression, properties: properties };
  },

  throwError: function(msg, token) {
    throw $parseMinErr('syntax',
        'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].',
          token.text, msg, (token.index + 1), this.text, this.text.substring(token.index));
  },

  consume: function(e1) {
    if (this.tokens.length === 0) {
      throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
    }

    var token = this.expect(e1);
    if (!token) {
      this.throwError('is unexpected, expecting [' + e1 + ']', this.peek());
    }
    return token;
  },

  peekToken: function() {
    if (this.tokens.length === 0) {
      throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
    }
    return this.tokens[0];
  },

  peek: function(e1, e2, e3, e4) {
    return this.peekAhead(0, e1, e2, e3, e4);
  },

  peekAhead: function(i, e1, e2, e3, e4) {
    if (this.tokens.length > i) {
      var token = this.tokens[i];
      var t = token.text;
      if (t === e1 || t === e2 || t === e3 || t === e4 ||
          (!e1 && !e2 && !e3 && !e4)) {
        return token;
      }
    }
    return false;
  },

  expect: function(e1, e2, e3, e4) {
    var token = this.peek(e1, e2, e3, e4);
    if (token) {
      this.tokens.shift();
      return token;
    }
    return false;
  },

  selfReferential: {
    'this': {type: AST.ThisExpression },
    '$locals': {type: AST.LocalsExpression }
  }
};

function ifDefined(v, d) {
  return typeof v !== 'undefined' ? v : d;
}

function plusFn(l, r) {
  if (typeof l === 'undefined') return r;
  if (typeof r === 'undefined') return l;
  return l + r;
}

function isStateless($filter, filterName) {
  var fn = $filter(filterName);
  return !fn.$stateful;
}

var PURITY_ABSOLUTE = 1;
var PURITY_RELATIVE = 2;

// Detect nodes which could depend on non-shallow state of objects
function isPure(node, parentIsPure) {
  switch (node.type) {
    // Computed members might invoke a stateful toString()
    case AST.MemberExpression:
      if (node.computed) {
        return false;
      }
      break;

    // Unary always convert to primative
    case AST.UnaryExpression:
      return PURITY_ABSOLUTE;

    // The binary + operator can invoke a stateful toString().
    case AST.BinaryExpression:
      return node.operator !== '+' ? PURITY_ABSOLUTE : false;

    // Functions / filters probably read state from within objects
    case AST.CallExpression:
      return false;
  }

  return (undefined === parentIsPure) ? PURITY_RELATIVE : parentIsPure;
}

function findConstantAndWatchExpressions(ast, $filter, parentIsPure) {
  var allConstants;
  var argsToWatch;
  var isStatelessFilter;

  var astIsPure = ast.isPure = isPure(ast, parentIsPure);

  switch (ast.type) {
  case AST.Program:
    allConstants = true;
    forEach(ast.body, function(expr) {
      findConstantAndWatchExpressions(expr.expression, $filter, astIsPure);
      allConstants = allConstants && expr.expression.constant;
    });
    ast.constant = allConstants;
    break;
  case AST.Literal:
    ast.constant = true;
    ast.toWatch = [];
    break;
  case AST.UnaryExpression:
    findConstantAndWatchExpressions(ast.argument, $filter, astIsPure);
    ast.constant = ast.argument.constant;
    ast.toWatch = ast.argument.toWatch;
    break;
  case AST.BinaryExpression:
    findConstantAndWatchExpressions(ast.left, $filter, astIsPure);
    findConstantAndWatchExpressions(ast.right, $filter, astIsPure);
    ast.constant = ast.left.constant && ast.right.constant;
    ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch);
    break;
  case AST.LogicalExpression:
    findConstantAndWatchExpressions(ast.left, $filter, astIsPure);
    findConstantAndWatchExpressions(ast.right, $filter, astIsPure);
    ast.constant = ast.left.constant && ast.right.constant;
    ast.toWatch = ast.constant ? [] : [ast];
    break;
  case AST.ConditionalExpression:
    findConstantAndWatchExpressions(ast.test, $filter, astIsPure);
    findConstantAndWatchExpressions(ast.alternate, $filter, astIsPure);
    findConstantAndWatchExpressions(ast.consequent, $filter, astIsPure);
    ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant;
    ast.toWatch = ast.constant ? [] : [ast];
    break;
  case AST.Identifier:
    ast.constant = false;
    ast.toWatch = [ast];
    break;
  case AST.MemberExpression:
    findConstantAndWatchExpressions(ast.object, $filter, astIsPure);
    if (ast.computed) {
      findConstantAndWatchExpressions(ast.property, $filter, astIsPure);
    }
    ast.constant = ast.object.constant && (!ast.computed || ast.property.constant);
    ast.toWatch = ast.constant ? [] : [ast];
    break;
  case AST.CallExpression:
    isStatelessFilter = ast.filter ? isStateless($filter, ast.callee.name) : false;
    allConstants = isStatelessFilter;
    argsToWatch = [];
    forEach(ast.arguments, function(expr) {
      findConstantAndWatchExpressions(expr, $filter, astIsPure);
      allConstants = allConstants && expr.constant;
      argsToWatch.push.apply(argsToWatch, expr.toWatch);
    });
    ast.constant = allConstants;
    ast.toWatch = isStatelessFilter ? argsToWatch : [ast];
    break;
  case AST.AssignmentExpression:
    findConstantAndWatchExpressions(ast.left, $filter, astIsPure);
    findConstantAndWatchExpressions(ast.right, $filter, astIsPure);
    ast.constant = ast.left.constant && ast.right.constant;
    ast.toWatch = [ast];
    break;
  case AST.ArrayExpression:
    allConstants = true;
    argsToWatch = [];
    forEach(ast.elements, function(expr) {
      findConstantAndWatchExpressions(expr, $filter, astIsPure);
      allConstants = allConstants && expr.constant;
      argsToWatch.push.apply(argsToWatch, expr.toWatch);
    });
    ast.constant = allConstants;
    ast.toWatch = argsToWatch;
    break;
  case AST.ObjectExpression:
    allConstants = true;
    argsToWatch = [];
    forEach(ast.properties, function(property) {
      findConstantAndWatchExpressions(property.value, $filter, astIsPure);
      allConstants = allConstants && property.value.constant;
      argsToWatch.push.apply(argsToWatch, property.value.toWatch);
      if (property.computed) {
        //`{[key]: value}` implicitly does `key.toString()` which may be non-pure
        findConstantAndWatchExpressions(property.key, $filter, /*parentIsPure=*/false);
        allConstants = allConstants && property.key.constant;
        argsToWatch.push.apply(argsToWatch, property.key.toWatch);
      }
    });
    ast.constant = allConstants;
    ast.toWatch = argsToWatch;
    break;
  case AST.ThisExpression:
    ast.constant = false;
    ast.toWatch = [];
    break;
  case AST.LocalsExpression:
    ast.constant = false;
    ast.toWatch = [];
    break;
  }
}

function getInputs(body) {
  if (body.length !== 1) return;
  var lastExpression = body[0].expression;
  var candidate = lastExpression.toWatch;
  if (candidate.length !== 1) return candidate;
  return candidate[0] !== lastExpression ? candidate : undefined;
}

function isAssignable(ast) {
  return ast.type === AST.Identifier || ast.type === AST.MemberExpression;
}

function assignableAST(ast) {
  if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) {
    return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='};
  }
}

function isLiteral(ast) {
  return ast.body.length === 0 ||
      ast.body.length === 1 && (
      ast.body[0].expression.type === AST.Literal ||
      ast.body[0].expression.type === AST.ArrayExpression ||
      ast.body[0].expression.type === AST.ObjectExpression);
}

function isConstant(ast) {
  return ast.constant;
}

function ASTCompiler($filter) {
  this.$filter = $filter;
}

ASTCompiler.prototype = {
  compile: function(ast) {
    var self = this;
    this.state = {
      nextId: 0,
      filters: {},
      fn: {vars: [], body: [], own: {}},
      assign: {vars: [], body: [], own: {}},
      inputs: []
    };
    findConstantAndWatchExpressions(ast, self.$filter);
    var extra = '';
    var assignable;
    this.stage = 'assign';
    if ((assignable = assignableAST(ast))) {
      this.state.computing = 'assign';
      var result = this.nextId();
      this.recurse(assignable, result);
      this.return_(result);
      extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l');
    }
    var toWatch = getInputs(ast.body);
    self.stage = 'inputs';
    forEach(toWatch, function(watch, key) {
      var fnKey = 'fn' + key;
      self.state[fnKey] = {vars: [], body: [], own: {}};
      self.state.computing = fnKey;
      var intoId = self.nextId();
      self.recurse(watch, intoId);
      self.return_(intoId);
      self.state.inputs.push({name: fnKey, isPure: watch.isPure});
      watch.watchId = key;
    });
    this.state.computing = 'fn';
    this.stage = 'main';
    this.recurse(ast);
    var fnString =
      // The build and minification steps remove the string "use strict" from the code, but this is done using a regex.
      // This is a workaround for this until we do a better job at only removing the prefix only when we should.
      '"' + this.USE + ' ' + this.STRICT + '";\n' +
      this.filterPrefix() +
      'var fn=' + this.generateFunction('fn', 's,l,a,i') +
      extra +
      this.watchFns() +
      'return fn;';

    // eslint-disable-next-line no-new-func
    var fn = (new Function('$filter',
        'getStringValue',
        'ifDefined',
        'plus',
        fnString))(
          this.$filter,
          getStringValue,
          ifDefined,
          plusFn);
    this.state = this.stage = undefined;
    return fn;
  },

  USE: 'use',

  STRICT: 'strict',

  watchFns: function() {
    var result = [];
    var inputs = this.state.inputs;
    var self = this;
    forEach(inputs, function(input) {
      result.push('var ' + input.name + '=' + self.generateFunction(input.name, 's'));
      if (input.isPure) {
        result.push(input.name, '.isPure=' + JSON.stringify(input.isPure) + ';');
      }
    });
    if (inputs.length) {
      result.push('fn.inputs=[' + inputs.map(function(i) { return i.name; }).join(',') + '];');
    }
    return result.join('');
  },

  generateFunction: function(name, params) {
    return 'function(' + params + '){' +
        this.varsPrefix(name) +
        this.body(name) +
        '};';
  },

  filterPrefix: function() {
    var parts = [];
    var self = this;
    forEach(this.state.filters, function(id, filter) {
      parts.push(id + '=$filter(' + self.escape(filter) + ')');
    });
    if (parts.length) return 'var ' + parts.join(',') + ';';
    return '';
  },

  varsPrefix: function(section) {
    return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : '';
  },

  body: function(section) {
    return this.state[section].body.join('');
  },

  recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
    var left, right, self = this, args, expression, computed;
    recursionFn = recursionFn || noop;
    if (!skipWatchIdCheck && isDefined(ast.watchId)) {
      intoId = intoId || this.nextId();
      this.if_('i',
        this.lazyAssign(intoId, this.computedMember('i', ast.watchId)),
        this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true)
      );
      return;
    }
    switch (ast.type) {
    case AST.Program:
      forEach(ast.body, function(expression, pos) {
        self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; });
        if (pos !== ast.body.length - 1) {
          self.current().body.push(right, ';');
        } else {
          self.return_(right);
        }
      });
      break;
    case AST.Literal:
      expression = this.escape(ast.value);
      this.assign(intoId, expression);
      recursionFn(intoId || expression);
      break;
    case AST.UnaryExpression:
      this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; });
      expression = ast.operator + '(' + this.ifDefined(right, 0) + ')';
      this.assign(intoId, expression);
      recursionFn(expression);
      break;
    case AST.BinaryExpression:
      this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; });
      this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; });
      if (ast.operator === '+') {
        expression = this.plus(left, right);
      } else if (ast.operator === '-') {
        expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0);
      } else {
        expression = '(' + left + ')' + ast.operator + '(' + right + ')';
      }
      this.assign(intoId, expression);
      recursionFn(expression);
      break;
    case AST.LogicalExpression:
      intoId = intoId || this.nextId();
      self.recurse(ast.left, intoId);
      self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId));
      recursionFn(intoId);
      break;
    case AST.ConditionalExpression:
      intoId = intoId || this.nextId();
      self.recurse(ast.test, intoId);
      self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId));
      recursionFn(intoId);
      break;
    case AST.Identifier:
      intoId = intoId || this.nextId();
      if (nameId) {
        nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s');
        nameId.computed = false;
        nameId.name = ast.name;
      }
      self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)),
        function() {
          self.if_(self.stage === 'inputs' || 's', function() {
            if (create && create !== 1) {
              self.if_(
                self.isNull(self.nonComputedMember('s', ast.name)),
                self.lazyAssign(self.nonComputedMember('s', ast.name), '{}'));
            }
            self.assign(intoId, self.nonComputedMember('s', ast.name));
          });
        }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name))
        );
      recursionFn(intoId);
      break;
    case AST.MemberExpression:
      left = nameId && (nameId.context = this.nextId()) || this.nextId();
      intoId = intoId || this.nextId();
      self.recurse(ast.object, left, undefined, function() {
        self.if_(self.notNull(left), function() {
          if (ast.computed) {
            right = self.nextId();
            self.recurse(ast.property, right);
            self.getStringValue(right);
            if (create && create !== 1) {
              self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}'));
            }
            expression = self.computedMember(left, right);
            self.assign(intoId, expression);
            if (nameId) {
              nameId.computed = true;
              nameId.name = right;
            }
          } else {
            if (create && create !== 1) {
              self.if_(self.isNull(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}'));
            }
            expression = self.nonComputedMember(left, ast.property.name);
            self.assign(intoId, expression);
            if (nameId) {
              nameId.computed = false;
              nameId.name = ast.property.name;
            }
          }
        }, function() {
          self.assign(intoId, 'undefined');
        });
        recursionFn(intoId);
      }, !!create);
      break;
    case AST.CallExpression:
      intoId = intoId || this.nextId();
      if (ast.filter) {
        right = self.filter(ast.callee.name);
        args = [];
        forEach(ast.arguments, function(expr) {
          var argument = self.nextId();
          self.recurse(expr, argument);
          args.push(argument);
        });
        expression = right + '(' + args.join(',') + ')';
        self.assign(intoId, expression);
        recursionFn(intoId);
      } else {
        right = self.nextId();
        left = {};
        args = [];
        self.recurse(ast.callee, right, left, function() {
          self.if_(self.notNull(right), function() {
            forEach(ast.arguments, function(expr) {
              self.recurse(expr, ast.constant ? undefined : self.nextId(), undefined, function(argument) {
                args.push(argument);
              });
            });
            if (left.name) {
              expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')';
            } else {
              expression = right + '(' + args.join(',') + ')';
            }
            self.assign(intoId, expression);
          }, function() {
            self.assign(intoId, 'undefined');
          });
          recursionFn(intoId);
        });
      }
      break;
    case AST.AssignmentExpression:
      right = this.nextId();
      left = {};
      this.recurse(ast.left, undefined, left, function() {
        self.if_(self.notNull(left.context), function() {
          self.recurse(ast.right, right);
          expression = self.member(left.context, left.name, left.computed) + ast.operator + right;
          self.assign(intoId, expression);
          recursionFn(intoId || expression);
        });
      }, 1);
      break;
    case AST.ArrayExpression:
      args = [];
      forEach(ast.elements, function(expr) {
        self.recurse(expr, ast.constant ? undefined : self.nextId(), undefined, function(argument) {
          args.push(argument);
        });
      });
      expression = '[' + args.join(',') + ']';
      this.assign(intoId, expression);
      recursionFn(intoId || expression);
      break;
    case AST.ObjectExpression:
      args = [];
      computed = false;
      forEach(ast.properties, function(property) {
        if (property.computed) {
          computed = true;
        }
      });
      if (computed) {
        intoId = intoId || this.nextId();
        this.assign(intoId, '{}');
        forEach(ast.properties, function(property) {
          if (property.computed) {
            left = self.nextId();
            self.recurse(property.key, left);
          } else {
            left = property.key.type === AST.Identifier ?
                       property.key.name :
                       ('' + property.key.value);
          }
          right = self.nextId();
          self.recurse(property.value, right);
          self.assign(self.member(intoId, left, property.computed), right);
        });
      } else {
        forEach(ast.properties, function(property) {
          self.recurse(property.value, ast.constant ? undefined : self.nextId(), undefined, function(expr) {
            args.push(self.escape(
                property.key.type === AST.Identifier ? property.key.name :
                  ('' + property.key.value)) +
                ':' + expr);
          });
        });
        expression = '{' + args.join(',') + '}';
        this.assign(intoId, expression);
      }
      recursionFn(intoId || expression);
      break;
    case AST.ThisExpression:
      this.assign(intoId, 's');
      recursionFn(intoId || 's');
      break;
    case AST.LocalsExpression:
      this.assign(intoId, 'l');
      recursionFn(intoId || 'l');
      break;
    case AST.NGValueParameter:
      this.assign(intoId, 'v');
      recursionFn(intoId || 'v');
      break;
    }
  },

  getHasOwnProperty: function(element, property) {
    var key = element + '.' + property;
    var own = this.current().own;
    if (!own.hasOwnProperty(key)) {
      own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')');
    }
    return own[key];
  },

  assign: function(id, value) {
    if (!id) return;
    this.current().body.push(id, '=', value, ';');
    return id;
  },

  filter: function(filterName) {
    if (!this.state.filters.hasOwnProperty(filterName)) {
      this.state.filters[filterName] = this.nextId(true);
    }
    return this.state.filters[filterName];
  },

  ifDefined: function(id, defaultValue) {
    return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')';
  },

  plus: function(left, right) {
    return 'plus(' + left + ',' + right + ')';
  },

  return_: function(id) {
    this.current().body.push('return ', id, ';');
  },

  if_: function(test, alternate, consequent) {
    if (test === true) {
      alternate();
    } else {
      var body = this.current().body;
      body.push('if(', test, '){');
      alternate();
      body.push('}');
      if (consequent) {
        body.push('else{');
        consequent();
        body.push('}');
      }
    }
  },

  not: function(expression) {
    return '!(' + expression + ')';
  },

  isNull: function(expression) {
    return expression + '==null';
  },

  notNull: function(expression) {
    return expression + '!=null';
  },

  nonComputedMember: function(left, right) {
    var SAFE_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/;
    var UNSAFE_CHARACTERS = /[^$_a-zA-Z0-9]/g;
    if (SAFE_IDENTIFIER.test(right)) {
      return left + '.' + right;
    } else {
      return left  + '["' + right.replace(UNSAFE_CHARACTERS, this.stringEscapeFn) + '"]';
    }
  },

  computedMember: function(left, right) {
    return left + '[' + right + ']';
  },

  member: function(left, right, computed) {
    if (computed) return this.computedMember(left, right);
    return this.nonComputedMember(left, right);
  },

  getStringValue: function(item) {
    this.assign(item, 'getStringValue(' + item + ')');
  },

  lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
    var self = this;
    return function() {
      self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck);
    };
  },

  lazyAssign: function(id, value) {
    var self = this;
    return function() {
      self.assign(id, value);
    };
  },

  stringEscapeRegex: /[^ a-zA-Z0-9]/g,

  stringEscapeFn: function(c) {
    return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4);
  },

  escape: function(value) {
    if (isString(value)) return '\'' + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + '\'';
    if (isNumber(value)) return value.toString();
    if (value === true) return 'true';
    if (value === false) return 'false';
    if (value === null) return 'null';
    if (typeof value === 'undefined') return 'undefined';

    throw $parseMinErr('esc', 'IMPOSSIBLE');
  },

  nextId: function(skip, init) {
    var id = 'v' + (this.state.nextId++);
    if (!skip) {
      this.current().vars.push(id + (init ? '=' + init : ''));
    }
    return id;
  },

  current: function() {
    return this.state[this.state.computing];
  }
};


function ASTInterpreter($filter) {
  this.$filter = $filter;
}

ASTInterpreter.prototype = {
  compile: function(ast) {
    var self = this;
    findConstantAndWatchExpressions(ast, self.$filter);
    var assignable;
    var assign;
    if ((assignable = assignableAST(ast))) {
      assign = this.recurse(assignable);
    }
    var toWatch = getInputs(ast.body);
    var inputs;
    if (toWatch) {
      inputs = [];
      forEach(toWatch, function(watch, key) {
        var input = self.recurse(watch);
        input.isPure = watch.isPure;
        watch.input = input;
        inputs.push(input);
        watch.watchId = key;
      });
    }
    var expressions = [];
    forEach(ast.body, function(expression) {
      expressions.push(self.recurse(expression.expression));
    });
    var fn = ast.body.length === 0 ? noop :
             ast.body.length === 1 ? expressions[0] :
             function(scope, locals) {
               var lastValue;
               forEach(expressions, function(exp) {
                 lastValue = exp(scope, locals);
               });
               return lastValue;
             };
    if (assign) {
      fn.assign = function(scope, value, locals) {
        return assign(scope, locals, value);
      };
    }
    if (inputs) {
      fn.inputs = inputs;
    }
    return fn;
  },

  recurse: function(ast, context, create) {
    var left, right, self = this, args;
    if (ast.input) {
      return this.inputs(ast.input, ast.watchId);
    }
    switch (ast.type) {
    case AST.Literal:
      return this.value(ast.value, context);
    case AST.UnaryExpression:
      right = this.recurse(ast.argument);
      return this['unary' + ast.operator](right, context);
    case AST.BinaryExpression:
      left = this.recurse(ast.left);
      right = this.recurse(ast.right);
      return this['binary' + ast.operator](left, right, context);
    case AST.LogicalExpression:
      left = this.recurse(ast.left);
      right = this.recurse(ast.right);
      return this['binary' + ast.operator](left, right, context);
    case AST.ConditionalExpression:
      return this['ternary?:'](
        this.recurse(ast.test),
        this.recurse(ast.alternate),
        this.recurse(ast.consequent),
        context
      );
    case AST.Identifier:
      return self.identifier(ast.name, context, create);
    case AST.MemberExpression:
      left = this.recurse(ast.object, false, !!create);
      if (!ast.computed) {
        right = ast.property.name;
      }
      if (ast.computed) right = this.recurse(ast.property);
      return ast.computed ?
        this.computedMember(left, right, context, create) :
        this.nonComputedMember(left, right, context, create);
    case AST.CallExpression:
      args = [];
      forEach(ast.arguments, function(expr) {
        args.push(self.recurse(expr));
      });
      if (ast.filter) right = this.$filter(ast.callee.name);
      if (!ast.filter) right = this.recurse(ast.callee, true);
      return ast.filter ?
        function(scope, locals, assign, inputs) {
          var values = [];
          for (var i = 0; i < args.length; ++i) {
            values.push(args[i](scope, locals, assign, inputs));
          }
          var value = right.apply(undefined, values, inputs);
          return context ? {context: undefined, name: undefined, value: value} : value;
        } :
        function(scope, locals, assign, inputs) {
          var rhs = right(scope, locals, assign, inputs);
          var value;
          if (rhs.value != null) {
            var values = [];
            for (var i = 0; i < args.length; ++i) {
              values.push(args[i](scope, locals, assign, inputs));
            }
            value = rhs.value.apply(rhs.context, values);
          }
          return context ? {value: value} : value;
        };
    case AST.AssignmentExpression:
      left = this.recurse(ast.left, true, 1);
      right = this.recurse(ast.right);
      return function(scope, locals, assign, inputs) {
        var lhs = left(scope, locals, assign, inputs);
        var rhs = right(scope, locals, assign, inputs);
        lhs.context[lhs.name] = rhs;
        return context ? {value: rhs} : rhs;
      };
    case AST.ArrayExpression:
      args = [];
      forEach(ast.elements, function(expr) {
        args.push(self.recurse(expr));
      });
      return function(scope, locals, assign, inputs) {
        var value = [];
        for (var i = 0; i < args.length; ++i) {
          value.push(args[i](scope, locals, assign, inputs));
        }
        return context ? {value: value} : value;
      };
    case AST.ObjectExpression:
      args = [];
      forEach(ast.properties, function(property) {
        if (property.computed) {
          args.push({key: self.recurse(property.key),
                     computed: true,
                     value: self.recurse(property.value)
          });
        } else {
          args.push({key: property.key.type === AST.Identifier ?
                          property.key.name :
                          ('' + property.key.value),
                     computed: false,
                     value: self.recurse(property.value)
          });
        }
      });
      return function(scope, locals, assign, inputs) {
        var value = {};
        for (var i = 0; i < args.length; ++i) {
          if (args[i].computed) {
            value[args[i].key(scope, locals, assign, inputs)] = args[i].value(scope, locals, assign, inputs);
          } else {
            value[args[i].key] = args[i].value(scope, locals, assign, inputs);
          }
        }
        return context ? {value: value} : value;
      };
    case AST.ThisExpression:
      return function(scope) {
        return context ? {value: scope} : scope;
      };
    case AST.LocalsExpression:
      return function(scope, locals) {
        return context ? {value: locals} : locals;
      };
    case AST.NGValueParameter:
      return function(scope, locals, assign) {
        return context ? {value: assign} : assign;
      };
    }
  },

  'unary+': function(argument, context) {
    return function(scope, locals, assign, inputs) {
      var arg = argument(scope, locals, assign, inputs);
      if (isDefined(arg)) {
        arg = +arg;
      } else {
        arg = 0;
      }
      return context ? {value: arg} : arg;
    };
  },
  'unary-': function(argument, context) {
    return function(scope, locals, assign, inputs) {
      var arg = argument(scope, locals, assign, inputs);
      if (isDefined(arg)) {
        arg = -arg;
      } else {
        arg = -0;
      }
      return context ? {value: arg} : arg;
    };
  },
  'unary!': function(argument, context) {
    return function(scope, locals, assign, inputs) {
      var arg = !argument(scope, locals, assign, inputs);
      return context ? {value: arg} : arg;
    };
  },
  'binary+': function(left, right, context) {
    return function(scope, locals, assign, inputs) {
      var lhs = left(scope, locals, assign, inputs);
      var rhs = right(scope, locals, assign, inputs);
      var arg = plusFn(lhs, rhs);
      return context ? {value: arg} : arg;
    };
  },
  'binary-': function(left, right, context) {
    return function(scope, locals, assign, inputs) {
      var lhs = left(scope, locals, assign, inputs);
      var rhs = right(scope, locals, assign, inputs);
      var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0);
      return context ? {value: arg} : arg;
    };
  },
  'binary*': function(left, right, context) {
    return function(scope, locals, assign, inputs) {
      var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs);
      return context ? {value: arg} : arg;
    };
  },
  'binary/': function(left, right, context) {
    return function(scope, locals, assign, inputs) {
      var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs);
      return context ? {value: arg} : arg;
    };
  },
  'binary%': function(left, right, context) {
    return function(scope, locals, assign, inputs) {
      var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs);
      return context ? {value: arg} : arg;
    };
  },
  'binary===': function(left, right, context) {
    return function(scope, locals, assign, inputs) {
      var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs);
      return context ? {value: arg} : arg;
    };
  },
  'binary!==': function(left, right, context) {
    return function(scope, locals, assign, inputs) {
      var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs);
      return context ? {value: arg} : arg;
    };
  },
  'binary==': function(left, right, context) {
    return function(scope, locals, assign, inputs) {
      // eslint-disable-next-line eqeqeq
      var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs);
      return context ? {value: arg} : arg;
    };
  },
  'binary!=': function(left, right, context) {
    return function(scope, locals, assign, inputs) {
      // eslint-disable-next-line eqeqeq
      var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs);
      return context ? {value: arg} : arg;
    };
  },
  'binary<': function(left, right, context) {
    return function(scope, locals, assign, inputs) {
      var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs);
      return context ? {value: arg} : arg;
    };
  },
  'binary>': function(left, right, context) {
    return function(scope, locals, assign, inputs) {
      var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs);
      return context ? {value: arg} : arg;
    };
  },
  'binary<=': function(left, right, context) {
    return function(scope, locals, assign, inputs) {
      var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs);
      return context ? {value: arg} : arg;
    };
  },
  'binary>=': function(left, right, context) {
    return function(scope, locals, assign, inputs) {
      var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs);
      return context ? {value: arg} : arg;
    };
  },
  'binary&&': function(left, right, context) {
    return function(scope, locals, assign, inputs) {
      var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs);
      return context ? {value: arg} : arg;
    };
  },
  'binary||': function(left, right, context) {
    return function(scope, locals, assign, inputs) {
      var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs);
      return context ? {value: arg} : arg;
    };
  },
  'ternary?:': function(test, alternate, consequent, context) {
    return function(scope, locals, assign, inputs) {
      var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs);
      return context ? {value: arg} : arg;
    };
  },
  value: function(value, context) {
    return function() { return context ? {context: undefined, name: undefined, value: value} : value; };
  },
  identifier: function(name, context, create) {
    return function(scope, locals, assign, inputs) {
      var base = locals && (name in locals) ? locals : scope;
      if (create && create !== 1 && base && base[name] == null) {
        base[name] = {};
      }
      var value = base ? base[name] : undefined;
      if (context) {
        return {context: base, name: name, value: value};
      } else {
        return value;
      }
    };
  },
  computedMember: function(left, right, context, create) {
    return function(scope, locals, assign, inputs) {
      var lhs = left(scope, locals, assign, inputs);
      var rhs;
      var value;
      if (lhs != null) {
        rhs = right(scope, locals, assign, inputs);
        rhs = getStringValue(rhs);
        if (create && create !== 1) {
          if (lhs && !(lhs[rhs])) {
            lhs[rhs] = {};
          }
        }
        value = lhs[rhs];
      }
      if (context) {
        return {context: lhs, name: rhs, value: value};
      } else {
        return value;
      }
    };
  },
  nonComputedMember: function(left, right, context, create) {
    return function(scope, locals, assign, inputs) {
      var lhs = left(scope, locals, assign, inputs);
      if (create && create !== 1) {
        if (lhs && lhs[right] == null) {
          lhs[right] = {};
        }
      }
      var value = lhs != null ? lhs[right] : undefined;
      if (context) {
        return {context: lhs, name: right, value: value};
      } else {
        return value;
      }
    };
  },
  inputs: function(input, watchId) {
    return function(scope, value, locals, inputs) {
      if (inputs) return inputs[watchId];
      return input(scope, value, locals);
    };
  }
};

/**
 * @constructor
 */
function Parser(lexer, $filter, options) {
  this.ast = new AST(lexer, options);
  this.astCompiler = options.csp ? new ASTInterpreter($filter) :
                                   new ASTCompiler($filter);
}

Parser.prototype = {
  constructor: Parser,

  parse: function(text) {
    var ast = this.getAst(text);
    var fn = this.astCompiler.compile(ast.ast);
    fn.literal = isLiteral(ast.ast);
    fn.constant = isConstant(ast.ast);
    fn.oneTime = ast.oneTime;
    return fn;
  },

  getAst: function(exp) {
    var oneTime = false;
    exp = exp.trim();

    if (exp.charAt(0) === ':' && exp.charAt(1) === ':') {
      oneTime = true;
      exp = exp.substring(2);
    }
    return {
      ast: this.ast.ast(exp),
      oneTime: oneTime
    };
  }
};

function getValueOf(value) {
  return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value);
}

///////////////////////////////////

/**
 * @ngdoc service
 * @name $parse
 * @kind function
 *
 * @description
 *
 * Converts AngularJS {@link guide/expression expression} into a function.
 *
 * ```js
 *   var getter = $parse('user.name');
 *   var setter = getter.assign;
 *   var context = {user:{name:'AngularJS'}};
 *   var locals = {user:{name:'local'}};
 *
 *   expect(getter(context)).toEqual('AngularJS');
 *   setter(context, 'newValue');
 *   expect(context.user.name).toEqual('newValue');
 *   expect(getter(context, locals)).toEqual('local');
 * ```
 *
 *
 * @param {string} expression String expression to compile.
 * @returns {function(context, locals)} a function which represents the compiled expression:
 *
 *    * `context` – `{object}` – an object against which any expressions embedded in the strings
 *      are evaluated against (typically a scope object).
 *    * `locals` – `{object=}` – local variables context object, useful for overriding values in
 *      `context`.
 *
 *    The returned function also has the following properties:
 *      * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript
 *        literal.
 *      * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript
 *        constant literals.
 *      * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be
 *        set to a function to change its value on the given context.
 *
 */


/**
 * @ngdoc provider
 * @name $parseProvider
 * @this
 *
 * @description
 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse}
 *  service.
 */
function $ParseProvider() {
  var cache = createMap();
  var literals = {
    'true': true,
    'false': false,
    'null': null,
    'undefined': undefined
  };
  var identStart, identContinue;

  /**
   * @ngdoc method
   * @name $parseProvider#addLiteral
   * @description
   *
   * Configure $parse service to add literal values that will be present as literal at expressions.
   *
   * @param {string} literalName Token for the literal value. The literal name value must be a valid literal name.
   * @param {*} literalValue Value for this literal. All literal values must be primitives or `undefined`.
   *
   **/
  this.addLiteral = function(literalName, literalValue) {
    literals[literalName] = literalValue;
  };

 /**
  * @ngdoc method
  * @name $parseProvider#setIdentifierFns
  *
  * @description
  *
  * Allows defining the set of characters that are allowed in AngularJS expressions. The function
  * `identifierStart` will get called to know if a given character is a valid character to be the
  * first character for an identifier. The function `identifierContinue` will get called to know if
  * a given character is a valid character to be a follow-up identifier character. The functions
  * `identifierStart` and `identifierContinue` will receive as arguments the single character to be
  * identifier and the character code point. These arguments will be `string` and `numeric`. Keep in
  * mind that the `string` parameter can be two characters long depending on the character
  * representation. It is expected for the function to return `true` or `false`, whether that
  * character is allowed or not.
  *
  * Since this function will be called extensively, keep the implementation of these functions fast,
  * as the performance of these functions have a direct impact on the expressions parsing speed.
  *
  * @param {function=} identifierStart The function that will decide whether the given character is
  *   a valid identifier start character.
  * @param {function=} identifierContinue The function that will decide whether the given character is
  *   a valid identifier continue character.
  */
  this.setIdentifierFns = function(identifierStart, identifierContinue) {
    identStart = identifierStart;
    identContinue = identifierContinue;
    return this;
  };

  this.$get = ['$filter', function($filter) {
    var noUnsafeEval = csp().noUnsafeEval;
    var $parseOptions = {
          csp: noUnsafeEval,
          literals: copy(literals),
          isIdentifierStart: isFunction(identStart) && identStart,
          isIdentifierContinue: isFunction(identContinue) && identContinue
        };
    $parse.$$getAst = $$getAst;
    return $parse;

    function $parse(exp, interceptorFn) {
      var parsedExpression, cacheKey;

      switch (typeof exp) {
        case 'string':
          exp = exp.trim();
          cacheKey = exp;

          parsedExpression = cache[cacheKey];

          if (!parsedExpression) {
            var lexer = new Lexer($parseOptions);
            var parser = new Parser(lexer, $filter, $parseOptions);
            parsedExpression = parser.parse(exp);

            cache[cacheKey] = addWatchDelegate(parsedExpression);
          }
          return addInterceptor(parsedExpression, interceptorFn);

        case 'function':
          return addInterceptor(exp, interceptorFn);

        default:
          return addInterceptor(noop, interceptorFn);
      }
    }

    function $$getAst(exp) {
      var lexer = new Lexer($parseOptions);
      var parser = new Parser(lexer, $filter, $parseOptions);
      return parser.getAst(exp).ast;
    }

    function expressionInputDirtyCheck(newValue, oldValueOfValue, compareObjectIdentity) {

      if (newValue == null || oldValueOfValue == null) { // null/undefined
        return newValue === oldValueOfValue;
      }

      if (typeof newValue === 'object') {

        // attempt to convert the value to a primitive type
        // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can
        //             be cheaply dirty-checked
        newValue = getValueOf(newValue);

        if (typeof newValue === 'object' && !compareObjectIdentity) {
          // objects/arrays are not supported - deep-watching them would be too expensive
          return false;
        }

        // fall-through to the primitive equality check
      }

      //Primitive or NaN
      // eslint-disable-next-line no-self-compare
      return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue);
    }

    function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) {
      var inputExpressions = parsedExpression.inputs;
      var lastResult;

      if (inputExpressions.length === 1) {
        var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails
        inputExpressions = inputExpressions[0];
        return scope.$watch(function expressionInputWatch(scope) {
          var newInputValue = inputExpressions(scope);
          if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf, inputExpressions.isPure)) {
            lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]);
            oldInputValueOf = newInputValue && getValueOf(newInputValue);
          }
          return lastResult;
        }, listener, objectEquality, prettyPrintExpression);
      }

      var oldInputValueOfValues = [];
      var oldInputValues = [];
      for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
        oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails
        oldInputValues[i] = null;
      }

      return scope.$watch(function expressionInputsWatch(scope) {
        var changed = false;

        for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
          var newInputValue = inputExpressions[i](scope);
          if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i], inputExpressions[i].isPure))) {
            oldInputValues[i] = newInputValue;
            oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue);
          }
        }

        if (changed) {
          lastResult = parsedExpression(scope, undefined, undefined, oldInputValues);
        }

        return lastResult;
      }, listener, objectEquality, prettyPrintExpression);
    }

    function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) {
      var isDone = parsedExpression.literal ? isAllDefined : isDefined;
      var unwatch, lastValue;

      var exp = parsedExpression.$$intercepted || parsedExpression;
      var post = parsedExpression.$$interceptor || identity;

      var useInputs = parsedExpression.inputs && !exp.inputs;

      // Propagate the literal/inputs/constant attributes
      // ... but not oneTime since we are handling it
      oneTimeWatch.literal = parsedExpression.literal;
      oneTimeWatch.constant = parsedExpression.constant;
      oneTimeWatch.inputs = parsedExpression.inputs;

      // Allow other delegates to run on this wrapped expression
      addWatchDelegate(oneTimeWatch);

      unwatch = scope.$watch(oneTimeWatch, listener, objectEquality, prettyPrintExpression);

      return unwatch;

      function unwatchIfDone() {
        if (isDone(lastValue)) {
          unwatch();
        }
      }

      function oneTimeWatch(scope, locals, assign, inputs) {
        lastValue = useInputs && inputs ? inputs[0] : exp(scope, locals, assign, inputs);
        if (isDone(lastValue)) {
          scope.$$postDigest(unwatchIfDone);
        }
        return post(lastValue);
      }
    }

    function isAllDefined(value) {
      var allDefined = true;
      forEach(value, function(val) {
        if (!isDefined(val)) allDefined = false;
      });
      return allDefined;
    }

    function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) {
      var unwatch = scope.$watch(function constantWatch(scope) {
        unwatch();
        return parsedExpression(scope);
      }, listener, objectEquality);
      return unwatch;
    }

    function addWatchDelegate(parsedExpression) {
      if (parsedExpression.constant) {
        parsedExpression.$$watchDelegate = constantWatchDelegate;
      } else if (parsedExpression.oneTime) {
        parsedExpression.$$watchDelegate = oneTimeWatchDelegate;
      } else if (parsedExpression.inputs) {
        parsedExpression.$$watchDelegate = inputsWatchDelegate;
      }

      return parsedExpression;
    }

    function chainInterceptors(first, second) {
      function chainedInterceptor(value) {
        return second(first(value));
      }
      chainedInterceptor.$stateful = first.$stateful || second.$stateful;
      chainedInterceptor.$$pure = first.$$pure && second.$$pure;

      return chainedInterceptor;
    }

    function addInterceptor(parsedExpression, interceptorFn) {
      if (!interceptorFn) return parsedExpression;

      // Extract any existing interceptors out of the parsedExpression
      // to ensure the original parsedExpression is always the $$intercepted
      if (parsedExpression.$$interceptor) {
        interceptorFn = chainInterceptors(parsedExpression.$$interceptor, interceptorFn);
        parsedExpression = parsedExpression.$$intercepted;
      }

      var useInputs = false;

      var fn = function interceptedExpression(scope, locals, assign, inputs) {
        var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs);
        return interceptorFn(value);
      };

      // Maintain references to the interceptor/intercepted
      fn.$$intercepted = parsedExpression;
      fn.$$interceptor = interceptorFn;

      // Propagate the literal/oneTime/constant attributes
      fn.literal = parsedExpression.literal;
      fn.oneTime = parsedExpression.oneTime;
      fn.constant = parsedExpression.constant;

      // Treat the interceptor like filters.
      // If it is not $stateful then only watch its inputs.
      // If the expression itself has no inputs then use the full expression as an input.
      if (!interceptorFn.$stateful) {
        useInputs = !parsedExpression.inputs;
        fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression];

        if (!interceptorFn.$$pure) {
          fn.inputs = fn.inputs.map(function(e) {
              // Remove the isPure flag of inputs when it is not absolute because they are now wrapped in a
              // non-pure interceptor function.
              if (e.isPure === PURITY_RELATIVE) {
                return function depurifier(s) { return e(s); };
              }
              return e;
            });
        }
      }

      return addWatchDelegate(fn);
    }
  }];
}

/**
 * @ngdoc service
 * @name $q
 * @requires $rootScope
 *
 * @description
 * A service that helps you run functions asynchronously, and use their return values (or exceptions)
 * when they are done processing.
 *
 * This is a [Promises/A+](https://promisesaplus.com/)-compliant implementation of promises/deferred
 * objects inspired by [Kris Kowal's Q](https://github.com/kriskowal/q).
 *
 * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred
 * implementations, and the other which resembles ES6 (ES2015) promises to some degree.
 *
 * ## $q constructor
 *
 * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver`
 * function as the first argument. This is similar to the native Promise implementation from ES6,
 * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
 *
 * While the constructor-style use is supported, not all of the supporting methods from ES6 promises are
 * available yet.
 *
 * It can be used like so:
 *
 * ```js
 *   // for the purpose of this example let's assume that variables `$q` and `okToGreet`
 *   // are available in the current lexical scope (they could have been injected or passed in).
 *
 *   function asyncGreet(name) {
 *     // perform some asynchronous operation, resolve or reject the promise when appropriate.
 *     return $q(function(resolve, reject) {
 *       setTimeout(function() {
 *         if (okToGreet(name)) {
 *           resolve('Hello, ' + name + '!');
 *         } else {
 *           reject('Greeting ' + name + ' is not allowed.');
 *         }
 *       }, 1000);
 *     });
 *   }
 *
 *   var promise = asyncGreet('Robin Hood');
 *   promise.then(function(greeting) {
 *     alert('Success: ' + greeting);
 *   }, function(reason) {
 *     alert('Failed: ' + reason);
 *   });
 * ```
 *
 * Note: progress/notify callbacks are not currently supported via the ES6-style interface.
 *
 * Note: unlike ES6 behavior, an exception thrown in the constructor function will NOT implicitly reject the promise.
 *
 * However, the more traditional CommonJS-style usage is still available, and documented below.
 *
 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an
 * interface for interacting with an object that represents the result of an action that is
 * performed asynchronously, and may or may not be finished at any given point in time.
 *
 * From the perspective of dealing with error handling, deferred and promise APIs are to
 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming.
 *
 * ```js
 *   // for the purpose of this example let's assume that variables `$q` and `okToGreet`
 *   // are available in the current lexical scope (they could have been injected or passed in).
 *
 *   function asyncGreet(name) {
 *     var deferred = $q.defer();
 *
 *     setTimeout(function() {
 *       deferred.notify('About to greet ' + name + '.');
 *
 *       if (okToGreet(name)) {
 *         deferred.resolve('Hello, ' + name + '!');
 *       } else {
 *         deferred.reject('Greeting ' + name + ' is not allowed.');
 *       }
 *     }, 1000);
 *
 *     return deferred.promise;
 *   }
 *
 *   var promise = asyncGreet('Robin Hood');
 *   promise.then(function(greeting) {
 *     alert('Success: ' + greeting);
 *   }, function(reason) {
 *     alert('Failed: ' + reason);
 *   }, function(update) {
 *     alert('Got notification: ' + update);
 *   });
 * ```
 *
 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff
 * comes in the way of guarantees that promise and deferred APIs make, see
 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md.
 *
 * Additionally the promise api allows for composition that is very hard to do with the
 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach.
 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the
 * section on serial or parallel joining of promises.
 *
 * ## The Deferred API
 *
 * A new instance of deferred is constructed by calling `$q.defer()`.
 *
 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs
 * that can be used for signaling the successful or unsuccessful completion, as well as the status
 * of the task.
 *
 * **Methods**
 *
 * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection
 *   constructed via `$q.reject`, the promise will be rejected instead.
 * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to
 *   resolving it with a rejection constructed via `$q.reject`.
 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called
 *   multiple times before the promise is either resolved or rejected.
 *
 * **Properties**
 *
 * - promise – `{Promise}` – promise object associated with this deferred.
 *
 *
 * ## The Promise API
 *
 * A new promise instance is created when a deferred instance is created and can be retrieved by
 * calling `deferred.promise`.
 *
 * The purpose of the promise object is to allow for interested parties to get access to the result
 * of the deferred task when it completes.
 *
 * **Methods**
 *
 * - `then(successCallback, [errorCallback], [notifyCallback])` – regardless of when the promise was or
 *   will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously
 *   as soon as the result is available. The callbacks are called with a single argument: the result
 *   or rejection reason. Additionally, the notify callback may be called zero or more times to
 *   provide a progress indication, before the promise is resolved or rejected.
 *
 *   This method *returns a new promise* which is resolved or rejected via the return value of the
 *   `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved
 *   with the value which is resolved in that promise using
 *   [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)).
 *   It also notifies via the return value of the `notifyCallback` method. The promise cannot be
 *   resolved or rejected from the notifyCallback method. The errorCallback and notifyCallback
 *   arguments are optional.
 *
 * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)`
 *
 * - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise,
 *   but to do so without modifying the final value. This is useful to release resources or do some
 *   clean-up that needs to be done whether the promise was rejected or resolved. See the [full
 *   specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for
 *   more information.
 *
 * ## Chaining promises
 *
 * Because calling the `then` method of a promise returns a new derived promise, it is easily
 * possible to create a chain of promises:
 *
 * ```js
 *   promiseB = promiseA.then(function(result) {
 *     return result + 1;
 *   });
 *
 *   // promiseB will be resolved immediately after promiseA is resolved and its value
 *   // will be the result of promiseA incremented by 1
 * ```
 *
 * It is possible to create chains of any length and since a promise can be resolved with another
 * promise (which will defer its resolution further), it is possible to pause/defer resolution of
 * the promises at any point in the chain. This makes it possible to implement powerful APIs like
 * $http's response interceptors.
 *
 *
 * ## Differences between Kris Kowal's Q and $q
 *
 *  There are two main differences:
 *
 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation
 *   mechanism in AngularJS, which means faster propagation of resolution or rejection into your
 *   models and avoiding unnecessary browser repaints, which would result in flickering UI.
 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
 *   all the important functionality needed for common async tasks.
 *
 * ## Testing
 *
 *  ```js
 *    it('should simulate promise', inject(function($q, $rootScope) {
 *      var deferred = $q.defer();
 *      var promise = deferred.promise;
 *      var resolvedValue;
 *
 *      promise.then(function(value) { resolvedValue = value; });
 *      expect(resolvedValue).toBeUndefined();
 *
 *      // Simulate resolving of promise
 *      deferred.resolve(123);
 *      // Note that the 'then' function does not get called synchronously.
 *      // This is because we want the promise API to always be async, whether or not
 *      // it got called synchronously or asynchronously.
 *      expect(resolvedValue).toBeUndefined();
 *
 *      // Propagate promise resolution to 'then' functions using $apply().
 *      $rootScope.$apply();
 *      expect(resolvedValue).toEqual(123);
 *    }));
 *  ```
 *
 * @param {function(function, function)} resolver Function which is responsible for resolving or
 *   rejecting the newly created promise. The first parameter is a function which resolves the
 *   promise, the second parameter is a function which rejects the promise.
 *
 * @returns {Promise} The newly created promise.
 */
/**
 * @ngdoc provider
 * @name $qProvider
 * @this
 *
 * @description
 */
function $QProvider() {
  var errorOnUnhandledRejections = true;
  this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
    return qFactory(function(callback) {
      $rootScope.$evalAsync(callback);
    }, $exceptionHandler, errorOnUnhandledRejections);
  }];

  /**
   * @ngdoc method
   * @name $qProvider#errorOnUnhandledRejections
   * @kind function
   *
   * @description
   * Retrieves or overrides whether to generate an error when a rejected promise is not handled.
   * This feature is enabled by default.
   *
   * @param {boolean=} value Whether to generate an error when a rejected promise is not handled.
   * @returns {boolean|ng.$qProvider} Current value when called without a new value or self for
   *    chaining otherwise.
   */
  this.errorOnUnhandledRejections = function(value) {
    if (isDefined(value)) {
      errorOnUnhandledRejections = value;
      return this;
    } else {
      return errorOnUnhandledRejections;
    }
  };
}

/** @this */
function $$QProvider() {
  var errorOnUnhandledRejections = true;
  this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) {
    return qFactory(function(callback) {
      $browser.defer(callback);
    }, $exceptionHandler, errorOnUnhandledRejections);
  }];

  this.errorOnUnhandledRejections = function(value) {
    if (isDefined(value)) {
      errorOnUnhandledRejections = value;
      return this;
    } else {
      return errorOnUnhandledRejections;
    }
  };
}

/**
 * Constructs a promise manager.
 *
 * @param {function(function)} nextTick Function for executing functions in the next turn.
 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for
 *     debugging purposes.
 * @param {boolean=} errorOnUnhandledRejections Whether an error should be generated on unhandled
 *     promises rejections.
 * @returns {object} Promise manager.
 */
function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) {
  var $qMinErr = minErr('$q', TypeError);
  var queueSize = 0;
  var checkQueue = [];

  /**
   * @ngdoc method
   * @name ng.$q#defer
   * @kind function
   *
   * @description
   * Creates a `Deferred` object which represents a task which will finish in the future.
   *
   * @returns {Deferred} Returns a new instance of deferred.
   */
  function defer() {
    return new Deferred();
  }

  function Deferred() {
    var promise = this.promise = new Promise();
    //Non prototype methods necessary to support unbound execution :/
    this.resolve = function(val) { resolvePromise(promise, val); };
    this.reject = function(reason) { rejectPromise(promise, reason); };
    this.notify = function(progress) { notifyPromise(promise, progress); };
  }


  function Promise() {
    this.$$state = { status: 0 };
  }

  extend(Promise.prototype, {
    then: function(onFulfilled, onRejected, progressBack) {
      if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) {
        return this;
      }
      var result = new Promise();

      this.$$state.pending = this.$$state.pending || [];
      this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]);
      if (this.$$state.status > 0) scheduleProcessQueue(this.$$state);

      return result;
    },

    'catch': function(callback) {
      return this.then(null, callback);
    },

    'finally': function(callback, progressBack) {
      return this.then(function(value) {
        return handleCallback(value, resolve, callback);
      }, function(error) {
        return handleCallback(error, reject, callback);
      }, progressBack);
    }
  });

  function processQueue(state) {
    var fn, promise, pending;

    pending = state.pending;
    state.processScheduled = false;
    state.pending = undefined;
    try {
      for (var i = 0, ii = pending.length; i < ii; ++i) {
        markQStateExceptionHandled(state);
        promise = pending[i][0];
        fn = pending[i][state.status];
        try {
          if (isFunction(fn)) {
            resolvePromise(promise, fn(state.value));
          } else if (state.status === 1) {
            resolvePromise(promise, state.value);
          } else {
            rejectPromise(promise, state.value);
          }
        } catch (e) {
          rejectPromise(promise, e);
          // This error is explicitly marked for being passed to the $exceptionHandler
          if (e && e.$$passToExceptionHandler === true) {
            exceptionHandler(e);
          }
        }
      }
    } finally {
      --queueSize;
      if (errorOnUnhandledRejections && queueSize === 0) {
        nextTick(processChecks);
      }
    }
  }

  function processChecks() {
    // eslint-disable-next-line no-unmodified-loop-condition
    while (!queueSize && checkQueue.length) {
      var toCheck = checkQueue.shift();
      if (!isStateExceptionHandled(toCheck)) {
        markQStateExceptionHandled(toCheck);
        var errorMessage = 'Possibly unhandled rejection: ' + toDebugString(toCheck.value);
        if (isError(toCheck.value)) {
          exceptionHandler(toCheck.value, errorMessage);
        } else {
          exceptionHandler(errorMessage);
        }
      }
    }
  }

  function scheduleProcessQueue(state) {
    if (errorOnUnhandledRejections && !state.pending && state.status === 2 && !isStateExceptionHandled(state)) {
      if (queueSize === 0 && checkQueue.length === 0) {
        nextTick(processChecks);
      }
      checkQueue.push(state);
    }
    if (state.processScheduled || !state.pending) return;
    state.processScheduled = true;
    ++queueSize;
    nextTick(function() { processQueue(state); });
  }

  function resolvePromise(promise, val) {
    if (promise.$$state.status) return;
    if (val === promise) {
      $$reject(promise, $qMinErr(
        'qcycle',
        'Expected promise to be resolved with value other than itself \'{0}\'',
        val));
    } else {
      $$resolve(promise, val);
    }

  }

  function $$resolve(promise, val) {
    var then;
    var done = false;
    try {
      if (isObject(val) || isFunction(val)) then = val.then;
      if (isFunction(then)) {
        promise.$$state.status = -1;
        then.call(val, doResolve, doReject, doNotify);
      } else {
        promise.$$state.value = val;
        promise.$$state.status = 1;
        scheduleProcessQueue(promise.$$state);
      }
    } catch (e) {
      doReject(e);
    }

    function doResolve(val) {
      if (done) return;
      done = true;
      $$resolve(promise, val);
    }
    function doReject(val) {
      if (done) return;
      done = true;
      $$reject(promise, val);
    }
    function doNotify(progress) {
      notifyPromise(promise, progress);
    }
  }

  function rejectPromise(promise, reason) {
    if (promise.$$state.status) return;
    $$reject(promise, reason);
  }

  function $$reject(promise, reason) {
    promise.$$state.value = reason;
    promise.$$state.status = 2;
    scheduleProcessQueue(promise.$$state);
  }

  function notifyPromise(promise, progress) {
    var callbacks = promise.$$state.pending;

    if ((promise.$$state.status <= 0) && callbacks && callbacks.length) {
      nextTick(function() {
        var callback, result;
        for (var i = 0, ii = callbacks.length; i < ii; i++) {
          result = callbacks[i][0];
          callback = callbacks[i][3];
          try {
            notifyPromise(result, isFunction(callback) ? callback(progress) : progress);
          } catch (e) {
            exceptionHandler(e);
          }
        }
      });
    }
  }

  /**
   * @ngdoc method
   * @name $q#reject
   * @kind function
   *
   * @description
   * Creates a promise that is resolved as rejected with the specified `reason`. This api should be
   * used to forward rejection in a chain of promises. If you are dealing with the last promise in
   * a promise chain, you don't need to worry about it.
   *
   * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of
   * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via
   * a promise error callback and you want to forward the error to the promise derived from the
   * current promise, you have to "rethrow" the error by returning a rejection constructed via
   * `reject`.
   *
   * ```js
   *   promiseB = promiseA.then(function(result) {
   *     // success: do something and resolve promiseB
   *     //          with the old or a new result
   *     return result;
   *   }, function(reason) {
   *     // error: handle the error if possible and
   *     //        resolve promiseB with newPromiseOrValue,
   *     //        otherwise forward the rejection to promiseB
   *     if (canHandle(reason)) {
   *      // handle the error and recover
   *      return newPromiseOrValue;
   *     }
   *     return $q.reject(reason);
   *   });
   * ```
   *
   * @param {*} reason Constant, message, exception or an object representing the rejection reason.
   * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`.
   */
  function reject(reason) {
    var result = new Promise();
    rejectPromise(result, reason);
    return result;
  }

  function handleCallback(value, resolver, callback) {
    var callbackOutput = null;
    try {
      if (isFunction(callback)) callbackOutput = callback();
    } catch (e) {
      return reject(e);
    }
    if (isPromiseLike(callbackOutput)) {
      return callbackOutput.then(function() {
        return resolver(value);
      }, reject);
    } else {
      return resolver(value);
    }
  }

  /**
   * @ngdoc method
   * @name $q#when
   * @kind function
   *
   * @description
   * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise.
   * This is useful when you are dealing with an object that might or might not be a promise, or if
   * the promise comes from a source that can't be trusted.
   *
   * @param {*} value Value or a promise
   * @param {Function=} successCallback
   * @param {Function=} errorCallback
   * @param {Function=} progressCallback
   * @returns {Promise} Returns a promise of the passed value or promise
   */


  function when(value, callback, errback, progressBack) {
    var result = new Promise();
    resolvePromise(result, value);
    return result.then(callback, errback, progressBack);
  }

  /**
   * @ngdoc method
   * @name $q#resolve
   * @kind function
   *
   * @description
   * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6.
   *
   * @param {*} value Value or a promise
   * @param {Function=} successCallback
   * @param {Function=} errorCallback
   * @param {Function=} progressCallback
   * @returns {Promise} Returns a promise of the passed value or promise
   */
  var resolve = when;

  /**
   * @ngdoc method
   * @name $q#all
   * @kind function
   *
   * @description
   * Combines multiple promises into a single promise that is resolved when all of the input
   * promises are resolved.
   *
   * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises.
   * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values,
   *   each value corresponding to the promise at the same index/key in the `promises` array/hash.
   *   If any of the promises is resolved with a rejection, this resulting promise will be rejected
   *   with the same rejection value.
   */

  function all(promises) {
    var result = new Promise(),
        counter = 0,
        results = isArray(promises) ? [] : {};

    forEach(promises, function(promise, key) {
      counter++;
      when(promise).then(function(value) {
        results[key] = value;
        if (!(--counter)) resolvePromise(result, results);
      }, function(reason) {
        rejectPromise(result, reason);
      });
    });

    if (counter === 0) {
      resolvePromise(result, results);
    }

    return result;
  }

  /**
   * @ngdoc method
   * @name $q#race
   * @kind function
   *
   * @description
   * Returns a promise that resolves or rejects as soon as one of those promises
   * resolves or rejects, with the value or reason from that promise.
   *
   * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises.
   * @returns {Promise} a promise that resolves or rejects as soon as one of the `promises`
   * resolves or rejects, with the value or reason from that promise.
   */

  function race(promises) {
    var deferred = defer();

    forEach(promises, function(promise) {
      when(promise).then(deferred.resolve, deferred.reject);
    });

    return deferred.promise;
  }

  function $Q(resolver) {
    if (!isFunction(resolver)) {
      throw $qMinErr('norslvr', 'Expected resolverFn, got \'{0}\'', resolver);
    }

    var promise = new Promise();

    function resolveFn(value) {
      resolvePromise(promise, value);
    }

    function rejectFn(reason) {
      rejectPromise(promise, reason);
    }

    resolver(resolveFn, rejectFn);

    return promise;
  }

  // Let's make the instanceof operator work for promises, so that
  // `new $q(fn) instanceof $q` would evaluate to true.
  $Q.prototype = Promise.prototype;

  $Q.defer = defer;
  $Q.reject = reject;
  $Q.when = when;
  $Q.resolve = resolve;
  $Q.all = all;
  $Q.race = race;

  return $Q;
}

function isStateExceptionHandled(state) {
  return !!state.pur;
}
function markQStateExceptionHandled(state) {
  state.pur = true;
}
function markQExceptionHandled(q) {
  // Built-in `$q` promises will always have a `$$state` property. This check is to allow
  // overwriting `$q` with a different promise library (e.g. Bluebird + angular-bluebird-promises).
  // (Currently, this is the only method that might be called with a promise, even if it is not
  // created by the built-in `$q`.)
  if (q.$$state) {
    markQStateExceptionHandled(q.$$state);
  }
}

/** @this */
function $$RAFProvider() { //rAF
  this.$get = ['$window', '$timeout', function($window, $timeout) {
    var requestAnimationFrame = $window.requestAnimationFrame ||
                                $window.webkitRequestAnimationFrame;

    var cancelAnimationFrame = $window.cancelAnimationFrame ||
                               $window.webkitCancelAnimationFrame ||
                               $window.webkitCancelRequestAnimationFrame;

    var rafSupported = !!requestAnimationFrame;
    var raf = rafSupported
      ? function(fn) {
          var id = requestAnimationFrame(fn);
          return function() {
            cancelAnimationFrame(id);
          };
        }
      : function(fn) {
          var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666
          return function() {
            $timeout.cancel(timer);
          };
        };

    raf.supported = rafSupported;

    return raf;
  }];
}

/**
 * DESIGN NOTES
 *
 * The design decisions behind the scope are heavily favored for speed and memory consumption.
 *
 * The typical use of scope is to watch the expressions, which most of the time return the same
 * value as last time so we optimize the operation.
 *
 * Closures construction is expensive in terms of speed as well as memory:
 *   - No closures, instead use prototypical inheritance for API
 *   - Internal state needs to be stored on scope directly, which means that private state is
 *     exposed as $$____ properties
 *
 * Loop operations are optimized by using while(count--) { ... }
 *   - This means that in order to keep the same order of execution as addition we have to add
 *     items to the array at the beginning (unshift) instead of at the end (push)
 *
 * Child scopes are created and removed often
 *   - Using an array would be slow since inserts in the middle are expensive; so we use linked lists
 *
 * There are fewer watches than observers. This is why you don't want the observer to be implemented
 * in the same way as watch. Watch requires return of the initialization function which is expensive
 * to construct.
 */


/**
 * @ngdoc provider
 * @name $rootScopeProvider
 * @description
 *
 * Provider for the $rootScope service.
 */

/**
 * @ngdoc method
 * @name $rootScopeProvider#digestTtl
 * @description
 *
 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and
 * assuming that the model is unstable.
 *
 * The current default is 10 iterations.
 *
 * In complex applications it's possible that the dependencies between `$watch`s will result in
 * several digest iterations. However if an application needs more than the default 10 digest
 * iterations for its model to stabilize then you should investigate what is causing the model to
 * continuously change during the digest.
 *
 * Increasing the TTL could have performance implications, so you should not change it without
 * proper justification.
 *
 * @param {number} limit The number of digest iterations.
 */


/**
 * @ngdoc service
 * @name $rootScope
 * @this
 *
 * @description
 *
 * Every application has a single root {@link ng.$rootScope.Scope scope}.
 * All other scopes are descendant scopes of the root scope. Scopes provide separation
 * between the model and the view, via a mechanism for watching the model for changes.
 * They also provide event emission/broadcast and subscription facility. See the
 * {@link guide/scope developer guide on scopes}.
 */
function $RootScopeProvider() {
  var TTL = 10;
  var $rootScopeMinErr = minErr('$rootScope');
  var lastDirtyWatch = null;
  var applyAsyncId = null;

  this.digestTtl = function(value) {
    if (arguments.length) {
      TTL = value;
    }
    return TTL;
  };

  function createChildScopeClass(parent) {
    function ChildScope() {
      this.$$watchers = this.$$nextSibling =
          this.$$childHead = this.$$childTail = null;
      this.$$listeners = {};
      this.$$listenerCount = {};
      this.$$watchersCount = 0;
      this.$id = nextUid();
      this.$$ChildScope = null;
      this.$$suspended = false;
    }
    ChildScope.prototype = parent;
    return ChildScope;
  }

  this.$get = ['$exceptionHandler', '$parse', '$browser',
      function($exceptionHandler, $parse, $browser) {

    function destroyChildScope($event) {
        $event.currentScope.$$destroyed = true;
    }

    function cleanUpScope($scope) {

      // Support: IE 9 only
      if (msie === 9) {
        // There is a memory leak in IE9 if all child scopes are not disconnected
        // completely when a scope is destroyed. So this code will recurse up through
        // all this scopes children
        //
        // See issue https://github.com/angular/angular.js/issues/10706
        if ($scope.$$childHead) {
          cleanUpScope($scope.$$childHead);
        }
        if ($scope.$$nextSibling) {
          cleanUpScope($scope.$$nextSibling);
        }
      }

      // The code below works around IE9 and V8's memory leaks
      //
      // See:
      // - https://code.google.com/p/v8/issues/detail?id=2073#c26
      // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909
      // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451

      $scope.$parent = $scope.$$nextSibling = $scope.$$prevSibling = $scope.$$childHead =
          $scope.$$childTail = $scope.$root = $scope.$$watchers = null;
    }

    /**
     * @ngdoc type
     * @name $rootScope.Scope
     *
     * @description
     * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the
     * {@link auto.$injector $injector}. Child scopes are created using the
     * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when
     * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for
     * an in-depth introduction and usage examples.
     *
     *
     * ## Inheritance
     * A scope can inherit from a parent scope, as in this example:
     * ```js
         var parent = $rootScope;
         var child = parent.$new();

         parent.salutation = "Hello";
         expect(child.salutation).toEqual('Hello');

         child.salutation = "Welcome";
         expect(child.salutation).toEqual('Welcome');
         expect(parent.salutation).toEqual('Hello');
     * ```
     *
     * When interacting with `Scope` in tests, additional helper methods are available on the
     * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional
     * details.
     *
     *
     * @param {Object.<string, function()>=} providers Map of service factory which need to be
     *                                       provided for the current scope. Defaults to {@link ng}.
     * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should
     *                              append/override services provided by `providers`. This is handy
     *                              when unit-testing and having the need to override a default
     *                              service.
     * @returns {Object} Newly created scope.
     *
     */
    function Scope() {
      this.$id = nextUid();
      this.$$phase = this.$parent = this.$$watchers =
                     this.$$nextSibling = this.$$prevSibling =
                     this.$$childHead = this.$$childTail = null;
      this.$root = this;
      this.$$destroyed = false;
      this.$$suspended = false;
      this.$$listeners = {};
      this.$$listenerCount = {};
      this.$$watchersCount = 0;
      this.$$isolateBindings = null;
    }

    /**
     * @ngdoc property
     * @name $rootScope.Scope#$id
     *
     * @description
     * Unique scope ID (monotonically increasing) useful for debugging.
     */

     /**
      * @ngdoc property
      * @name $rootScope.Scope#$parent
      *
      * @description
      * Reference to the parent scope.
      */

      /**
       * @ngdoc property
       * @name $rootScope.Scope#$root
       *
       * @description
       * Reference to the root scope.
       */

    Scope.prototype = {
      constructor: Scope,
      /**
       * @ngdoc method
       * @name $rootScope.Scope#$new
       * @kind function
       *
       * @description
       * Creates a new child {@link ng.$rootScope.Scope scope}.
       *
       * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event.
       * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}.
       *
       * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is
       * desired for the scope and its child scopes to be permanently detached from the parent and
       * thus stop participating in model change detection and listener notification by invoking.
       *
       * @param {boolean} isolate If true, then the scope does not prototypically inherit from the
       *         parent scope. The scope is isolated, as it can not see parent scope properties.
       *         When creating widgets, it is useful for the widget to not accidentally read parent
       *         state.
       *
       * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent`
       *                              of the newly created scope. Defaults to `this` scope if not provided.
       *                              This is used when creating a transclude scope to correctly place it
       *                              in the scope hierarchy while maintaining the correct prototypical
       *                              inheritance.
       *
       * @returns {Object} The newly created child scope.
       *
       */
      $new: function(isolate, parent) {
        var child;

        parent = parent || this;

        if (isolate) {
          child = new Scope();
          child.$root = this.$root;
        } else {
          // Only create a child scope class if somebody asks for one,
          // but cache it to allow the VM to optimize lookups.
          if (!this.$$ChildScope) {
            this.$$ChildScope = createChildScopeClass(this);
          }
          child = new this.$$ChildScope();
        }
        child.$parent = parent;
        child.$$prevSibling = parent.$$childTail;
        if (parent.$$childHead) {
          parent.$$childTail.$$nextSibling = child;
          parent.$$childTail = child;
        } else {
          parent.$$childHead = parent.$$childTail = child;
        }

        // When the new scope is not isolated or we inherit from `this`, and
        // the parent scope is destroyed, the property `$$destroyed` is inherited
        // prototypically. In all other cases, this property needs to be set
        // when the parent scope is destroyed.
        // The listener needs to be added after the parent is set
        if (isolate || parent !== this) child.$on('$destroy', destroyChildScope);

        return child;
      },

      /**
       * @ngdoc method
       * @name $rootScope.Scope#$watch
       * @kind function
       *
       * @description
       * Registers a `listener` callback to be executed whenever the `watchExpression` changes.
       *
       * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest
       *   $digest()} and should return the value that will be watched. (`watchExpression` should not change
       *   its value when executed multiple times with the same input because it may be executed multiple
       *   times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be
       *   [idempotent](http://en.wikipedia.org/wiki/Idempotence).)
       * - The `listener` is called only when the value from the current `watchExpression` and the
       *   previous call to `watchExpression` are not equal (with the exception of the initial run,
       *   see below). Inequality is determined according to reference inequality,
       *   [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators)
       *    via the `!==` Javascript operator, unless `objectEquality == true`
       *   (see next point)
       * - When `objectEquality == true`, inequality of the `watchExpression` is determined
       *   according to the {@link angular.equals} function. To save the value of the object for
       *   later comparison, the {@link angular.copy} function is used. This therefore means that
       *   watching complex objects will have adverse memory and performance implications.
       * - This should not be used to watch for changes in objects that are (or contain)
       *   [File](https://developer.mozilla.org/docs/Web/API/File) objects due to limitations with {@link angular.copy `angular.copy`}.
       * - The watch `listener` may change the model, which may trigger other `listener`s to fire.
       *   This is achieved by rerunning the watchers until no changes are detected. The rerun
       *   iteration limit is 10 to prevent an infinite loop deadlock.
       *
       *
       * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called,
       * you can register a `watchExpression` function with no `listener`. (Be prepared for
       * multiple calls to your `watchExpression` because it will execute multiple times in a
       * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.)
       *
       * After a watcher is registered with the scope, the `listener` fn is called asynchronously
       * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the
       * watcher. In rare cases, this is undesirable because the listener is called when the result
       * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you
       * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the
       * listener was called due to initialization.
       *
       *
       *
       * @example
       * ```js
           // let's assume that scope was dependency injected as the $rootScope
           var scope = $rootScope;
           scope.name = 'misko';
           scope.counter = 0;

           expect(scope.counter).toEqual(0);
           scope.$watch('name', function(newValue, oldValue) {
             scope.counter = scope.counter + 1;
           });
           expect(scope.counter).toEqual(0);

           scope.$digest();
           // the listener is always called during the first $digest loop after it was registered
           expect(scope.counter).toEqual(1);

           scope.$digest();
           // but now it will not be called unless the value changes
           expect(scope.counter).toEqual(1);

           scope.name = 'adam';
           scope.$digest();
           expect(scope.counter).toEqual(2);



           // Using a function as a watchExpression
           var food;
           scope.foodCounter = 0;
           expect(scope.foodCounter).toEqual(0);
           scope.$watch(
             // This function returns the value being watched. It is called for each turn of the $digest loop
             function() { return food; },
             // This is the change listener, called when the value returned from the above function changes
             function(newValue, oldValue) {
               if ( newValue !== oldValue ) {
                 // Only increment the counter if the value changed
                 scope.foodCounter = scope.foodCounter + 1;
               }
             }
           );
           // No digest has been run so the counter will be zero
           expect(scope.foodCounter).toEqual(0);

           // Run the digest but since food has not changed count will still be zero
           scope.$digest();
           expect(scope.foodCounter).toEqual(0);

           // Update food and run digest.  Now the counter will increment
           food = 'cheeseburger';
           scope.$digest();
           expect(scope.foodCounter).toEqual(1);

       * ```
       *
       *
       *
       * @param {(function()|string)} watchExpression Expression that is evaluated on each
       *    {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers
       *    a call to the `listener`.
       *
       *    - `string`: Evaluated as {@link guide/expression expression}
       *    - `function(scope)`: called with current `scope` as a parameter.
       * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value
       *    of `watchExpression` changes.
       *
       *    - `newVal` contains the current value of the `watchExpression`
       *    - `oldVal` contains the previous value of the `watchExpression`
       *    - `scope` refers to the current scope
       * @param {boolean=} [objectEquality=false] Compare for object equality using {@link angular.equals} instead of
       *     comparing for reference equality.
       * @returns {function()} Returns a deregistration function for this listener.
       */
      $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) {
        var get = $parse(watchExp);
        var fn = isFunction(listener) ? listener : noop;

        if (get.$$watchDelegate) {
          return get.$$watchDelegate(this, fn, objectEquality, get, watchExp);
        }
        var scope = this,
            array = scope.$$watchers,
            watcher = {
              fn: fn,
              last: initWatchVal,
              get: get,
              exp: prettyPrintExpression || watchExp,
              eq: !!objectEquality
            };

        lastDirtyWatch = null;

        if (!array) {
          array = scope.$$watchers = [];
          array.$$digestWatchIndex = -1;
        }
        // we use unshift since we use a while loop in $digest for speed.
        // the while loop reads in reverse order.
        array.unshift(watcher);
        array.$$digestWatchIndex++;
        incrementWatchersCount(this, 1);

        return function deregisterWatch() {
          var index = arrayRemove(array, watcher);
          if (index >= 0) {
            incrementWatchersCount(scope, -1);
            if (index < array.$$digestWatchIndex) {
              array.$$digestWatchIndex--;
            }
          }
          lastDirtyWatch = null;
        };
      },

      /**
       * @ngdoc method
       * @name $rootScope.Scope#$watchGroup
       * @kind function
       *
       * @description
       * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`.
       * If any one expression in the collection changes the `listener` is executed.
       *
       * - The items in the `watchExpressions` array are observed via the standard `$watch` operation. Their return
       *   values are examined for changes on every call to `$digest`.
       * - The `listener` is called whenever any expression in the `watchExpressions` array changes.
       *
       * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually
       * watched using {@link ng.$rootScope.Scope#$watch $watch()}
       *
       * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any
       *    expression in `watchExpressions` changes
       *    The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching
       *    those of `watchExpression`
       *    and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching
       *    those of `watchExpression`
       *    The `scope` refers to the current scope.
       * @returns {function()} Returns a de-registration function for all listeners.
       */
      $watchGroup: function(watchExpressions, listener) {
        var oldValues = new Array(watchExpressions.length);
        var newValues = new Array(watchExpressions.length);
        var deregisterFns = [];
        var self = this;
        var changeReactionScheduled = false;
        var firstRun = true;

        if (!watchExpressions.length) {
          // No expressions means we call the listener ASAP
          var shouldCall = true;
          self.$evalAsync(function() {
            if (shouldCall) listener(newValues, newValues, self);
          });
          return function deregisterWatchGroup() {
            shouldCall = false;
          };
        }

        if (watchExpressions.length === 1) {
          // Special case size of one
          return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) {
            newValues[0] = value;
            oldValues[0] = oldValue;
            listener(newValues, (value === oldValue) ? newValues : oldValues, scope);
          });
        }

        forEach(watchExpressions, function(expr, i) {
          var unwatchFn = self.$watch(expr, function watchGroupSubAction(value) {
            newValues[i] = value;
            if (!changeReactionScheduled) {
              changeReactionScheduled = true;
              self.$evalAsync(watchGroupAction);
            }
          });
          deregisterFns.push(unwatchFn);
        });

        function watchGroupAction() {
          changeReactionScheduled = false;

          try {
            if (firstRun) {
              firstRun = false;
              listener(newValues, newValues, self);
            } else {
              listener(newValues, oldValues, self);
            }
          } finally {
            for (var i = 0; i < watchExpressions.length; i++) {
              oldValues[i] = newValues[i];
            }
          }
        }

        return function deregisterWatchGroup() {
          while (deregisterFns.length) {
            deregisterFns.shift()();
          }
        };
      },


      /**
       * @ngdoc method
       * @name $rootScope.Scope#$watchCollection
       * @kind function
       *
       * @description
       * Shallow watches the properties of an object and fires whenever any of the properties change
       * (for arrays, this implies watching the array items; for object maps, this implies watching
       * the properties). If a change is detected, the `listener` callback is fired.
       *
       * - The `obj` collection is observed via standard $watch operation and is examined on every
       *   call to $digest() to see if any items have been added, removed, or moved.
       * - The `listener` is called whenever anything within the `obj` has changed. Examples include
       *   adding, removing, and moving items belonging to an object or array.
       *
       *
       * @example
       * ```js
          $scope.names = ['igor', 'matias', 'misko', 'james'];
          $scope.dataCount = 4;

          $scope.$watchCollection('names', function(newNames, oldNames) {
            $scope.dataCount = newNames.length;
          });

          expect($scope.dataCount).toEqual(4);
          $scope.$digest();

          //still at 4 ... no changes
          expect($scope.dataCount).toEqual(4);

          $scope.names.pop();
          $scope.$digest();

          //now there's been a change
          expect($scope.dataCount).toEqual(3);
       * ```
       *
       *
       * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The
       *    expression value should evaluate to an object or an array which is observed on each
       *    {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the
       *    collection will trigger a call to the `listener`.
       *
       * @param {function(newCollection, oldCollection, scope)} listener a callback function called
       *    when a change is detected.
       *    - The `newCollection` object is the newly modified data obtained from the `obj` expression
       *    - The `oldCollection` object is a copy of the former collection data.
       *      Due to performance considerations, the`oldCollection` value is computed only if the
       *      `listener` function declares two or more arguments.
       *    - The `scope` argument refers to the current scope.
       *
       * @returns {function()} Returns a de-registration function for this listener. When the
       *    de-registration function is executed, the internal watch operation is terminated.
       */
      $watchCollection: function(obj, listener) {
        // Mark the interceptor as
        // ... $$pure when literal since the instance will change when any input changes
        $watchCollectionInterceptor.$$pure = $parse(obj).literal;
        // ... $stateful when non-literal since we must read the state of the collection
        $watchCollectionInterceptor.$stateful = !$watchCollectionInterceptor.$$pure;

        var self = this;
        // the current value, updated on each dirty-check run
        var newValue;
        // a shallow copy of the newValue from the last dirty-check run,
        // updated to match newValue during dirty-check run
        var oldValue;
        // a shallow copy of the newValue from when the last change happened
        var veryOldValue;
        // only track veryOldValue if the listener is asking for it
        var trackVeryOldValue = (listener.length > 1);
        var changeDetected = 0;
        var changeDetector = $parse(obj, $watchCollectionInterceptor);
        var internalArray = [];
        var internalObject = {};
        var initRun = true;
        var oldLength = 0;

        function $watchCollectionInterceptor(_value) {
          newValue = _value;
          var newLength, key, bothNaN, newItem, oldItem;

          // If the new value is undefined, then return undefined as the watch may be a one-time watch
          if (isUndefined(newValue)) return;

          if (!isObject(newValue)) { // if primitive
            if (oldValue !== newValue) {
              oldValue = newValue;
              changeDetected++;
            }
          } else if (isArrayLike(newValue)) {
            if (oldValue !== internalArray) {
              // we are transitioning from something which was not an array into array.
              oldValue = internalArray;
              oldLength = oldValue.length = 0;
              changeDetected++;
            }

            newLength = newValue.length;

            if (oldLength !== newLength) {
              // if lengths do not match we need to trigger change notification
              changeDetected++;
              oldValue.length = oldLength = newLength;
            }
            // copy the items to oldValue and look for changes.
            for (var i = 0; i < newLength; i++) {
              oldItem = oldValue[i];
              newItem = newValue[i];

              // eslint-disable-next-line no-self-compare
              bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
              if (!bothNaN && (oldItem !== newItem)) {
                changeDetected++;
                oldValue[i] = newItem;
              }
            }
          } else {
            if (oldValue !== internalObject) {
              // we are transitioning from something which was not an object into object.
              oldValue = internalObject = {};
              oldLength = 0;
              changeDetected++;
            }
            // copy the items to oldValue and look for changes.
            newLength = 0;
            for (key in newValue) {
              if (hasOwnProperty.call(newValue, key)) {
                newLength++;
                newItem = newValue[key];
                oldItem = oldValue[key];

                if (key in oldValue) {
                  // eslint-disable-next-line no-self-compare
                  bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
                  if (!bothNaN && (oldItem !== newItem)) {
                    changeDetected++;
                    oldValue[key] = newItem;
                  }
                } else {
                  oldLength++;
                  oldValue[key] = newItem;
                  changeDetected++;
                }
              }
            }
            if (oldLength > newLength) {
              // we used to have more keys, need to find them and destroy them.
              changeDetected++;
              for (key in oldValue) {
                if (!hasOwnProperty.call(newValue, key)) {
                  oldLength--;
                  delete oldValue[key];
                }
              }
            }
          }
          return changeDetected;
        }

        function $watchCollectionAction() {
          if (initRun) {
            initRun = false;
            listener(newValue, newValue, self);
          } else {
            listener(newValue, veryOldValue, self);
          }

          // make a copy for the next time a collection is changed
          if (trackVeryOldValue) {
            if (!isObject(newValue)) {
              //primitive
              veryOldValue = newValue;
            } else if (isArrayLike(newValue)) {
              veryOldValue = new Array(newValue.length);
              for (var i = 0; i < newValue.length; i++) {
                veryOldValue[i] = newValue[i];
              }
            } else { // if object
              veryOldValue = {};
              for (var key in newValue) {
                if (hasOwnProperty.call(newValue, key)) {
                  veryOldValue[key] = newValue[key];
                }
              }
            }
          }
        }

        return this.$watch(changeDetector, $watchCollectionAction);
      },

      /**
       * @ngdoc method
       * @name $rootScope.Scope#$digest
       * @kind function
       *
       * @description
       * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and
       * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change
       * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers}
       * until no more listeners are firing. This means that it is possible to get into an infinite
       * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of
       * iterations exceeds 10.
       *
       * Usually, you don't call `$digest()` directly in
       * {@link ng.directive:ngController controllers} or in
       * {@link ng.$compileProvider#directive directives}.
       * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within
       * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`.
       *
       * If you want to be notified whenever `$digest()` is called,
       * you can register a `watchExpression` function with
       * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`.
       *
       * In unit tests, you may need to call `$digest()` to simulate the scope life cycle.
       *
       * @example
       * ```js
           var scope = ...;
           scope.name = 'misko';
           scope.counter = 0;

           expect(scope.counter).toEqual(0);
           scope.$watch('name', function(newValue, oldValue) {
             scope.counter = scope.counter + 1;
           });
           expect(scope.counter).toEqual(0);

           scope.$digest();
           // the listener is always called during the first $digest loop after it was registered
           expect(scope.counter).toEqual(1);

           scope.$digest();
           // but now it will not be called unless the value changes
           expect(scope.counter).toEqual(1);

           scope.name = 'adam';
           scope.$digest();
           expect(scope.counter).toEqual(2);
       * ```
       *
       */
      $digest: function() {
        var watch, value, last, fn, get,
            watchers,
            dirty, ttl = TTL,
            next, current, target = asyncQueue.length ? $rootScope : this,
            watchLog = [],
            logIdx, asyncTask;

        beginPhase('$digest');
        // Check for changes to browser url that happened in sync before the call to $digest
        $browser.$$checkUrlChange();

        if (this === $rootScope && applyAsyncId !== null) {
          // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then
          // cancel the scheduled $apply and flush the queue of expressions to be evaluated.
          $browser.defer.cancel(applyAsyncId);
          flushApplyAsync();
        }

        lastDirtyWatch = null;

        do { // "while dirty" loop
          dirty = false;
          current = target;

          // It's safe for asyncQueuePosition to be a local variable here because this loop can't
          // be reentered recursively. Calling $digest from a function passed to $evalAsync would
          // lead to a '$digest already in progress' error.
          for (var asyncQueuePosition = 0; asyncQueuePosition < asyncQueue.length; asyncQueuePosition++) {
            try {
              asyncTask = asyncQueue[asyncQueuePosition];
              fn = asyncTask.fn;
              fn(asyncTask.scope, asyncTask.locals);
            } catch (e) {
              $exceptionHandler(e);
            }
            lastDirtyWatch = null;
          }
          asyncQueue.length = 0;

          traverseScopesLoop:
          do { // "traverse the scopes" loop
            if ((watchers = !current.$$suspended && current.$$watchers)) {
              // process our watches
              watchers.$$digestWatchIndex = watchers.length;
              while (watchers.$$digestWatchIndex--) {
                try {
                  watch = watchers[watchers.$$digestWatchIndex];
                  // Most common watches are on primitives, in which case we can short
                  // circuit it with === operator, only when === fails do we use .equals
                  if (watch) {
                    get = watch.get;
                    if ((value = get(current)) !== (last = watch.last) &&
                        !(watch.eq
                            ? equals(value, last)
                            : (isNumberNaN(value) && isNumberNaN(last)))) {
                      dirty = true;
                      lastDirtyWatch = watch;
                      watch.last = watch.eq ? copy(value, null) : value;
                      fn = watch.fn;
                      fn(value, ((last === initWatchVal) ? value : last), current);
                      if (ttl < 5) {
                        logIdx = 4 - ttl;
                        if (!watchLog[logIdx]) watchLog[logIdx] = [];
                        watchLog[logIdx].push({
                          msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp,
                          newVal: value,
                          oldVal: last
                        });
                      }
                    } else if (watch === lastDirtyWatch) {
                      // If the most recently dirty watcher is now clean, short circuit since the remaining watchers
                      // have already been tested.
                      dirty = false;
                      break traverseScopesLoop;
                    }
                  }
                } catch (e) {
                  $exceptionHandler(e);
                }
              }
            }

            // Insanity Warning: scope depth-first traversal
            // yes, this code is a bit crazy, but it works and we have tests to prove it!
            // this piece should be kept in sync with the traversal in $broadcast
            // (though it differs due to having the extra check for $$suspended and does not
            // check $$listenerCount)
            if (!(next = ((!current.$$suspended && current.$$watchersCount && current.$$childHead) ||
                (current !== target && current.$$nextSibling)))) {
              while (current !== target && !(next = current.$$nextSibling)) {
                current = current.$parent;
              }
            }
          } while ((current = next));

          // `break traverseScopesLoop;` takes us to here

          if ((dirty || asyncQueue.length) && !(ttl--)) {
            clearPhase();
            throw $rootScopeMinErr('infdig',
                '{0} $digest() iterations reached. Aborting!\n' +
                'Watchers fired in the last 5 iterations: {1}',
                TTL, watchLog);
          }

        } while (dirty || asyncQueue.length);

        clearPhase();

        // postDigestQueuePosition isn't local here because this loop can be reentered recursively.
        while (postDigestQueuePosition < postDigestQueue.length) {
          try {
            postDigestQueue[postDigestQueuePosition++]();
          } catch (e) {
            $exceptionHandler(e);
          }
        }
        postDigestQueue.length = postDigestQueuePosition = 0;

        // Check for changes to browser url that happened during the $digest
        // (for which no event is fired; e.g. via `history.pushState()`)
        $browser.$$checkUrlChange();
      },

      /**
       * @ngdoc method
       * @name $rootScope.Scope#$suspend
       * @kind function
       *
       * @description
       * Suspend watchers of this scope subtree so that they will not be invoked during digest.
       *
       * This can be used to optimize your application when you know that running those watchers
       * is redundant.
       *
       * **Warning**
       *
       * Suspending scopes from the digest cycle can have unwanted and difficult to debug results.
       * Only use this approach if you are confident that you know what you are doing and have
       * ample tests to ensure that bindings get updated as you expect.
       *
       * Some of the things to consider are:
       *
       * * Any external event on a directive/component will not trigger a digest while the hosting
       *   scope is suspended - even if the event handler calls `$apply()` or `$rootScope.$digest()`.
       * * Transcluded content exists on a scope that inherits from outside a directive but exists
       *   as a child of the directive's containing scope. If the containing scope is suspended the
       *   transcluded scope will also be suspended, even if the scope from which the transcluded
       *   scope inherits is not suspended.
       * * Multiple directives trying to manage the suspended status of a scope can confuse each other:
       *    * A call to `$suspend()` on an already suspended scope is a no-op.
       *    * A call to `$resume()` on a non-suspended scope is a no-op.
       *    * If two directives suspend a scope, then one of them resumes the scope, the scope will no
       *      longer be suspended. This could result in the other directive believing a scope to be
       *      suspended when it is not.
       * * If a parent scope is suspended then all its descendants will be also excluded from future
       *   digests whether or not they have been suspended themselves. Note that this also applies to
       *   isolate child scopes.
       * * Calling `$digest()` directly on a descendant of a suspended scope will still run the watchers
       *   for that scope and its descendants. When digesting we only check whether the current scope is
       *   locally suspended, rather than checking whether it has a suspended ancestor.
       * * Calling `$resume()` on a scope that has a suspended ancestor will not cause the scope to be
       *   included in future digests until all its ancestors have been resumed.
       * * Resolved promises, e.g. from explicit `$q` deferreds and `$http` calls, trigger `$apply()`
       *   against the `$rootScope` and so will still trigger a global digest even if the promise was
       *   initiated by a component that lives on a suspended scope.
       */
      $suspend: function() {
        this.$$suspended = true;
      },

      /**
       * @ngdoc method
       * @name $rootScope.Scope#$isSuspended
       * @kind function
       *
       * @description
       * Call this method to determine if this scope has been explicitly suspended. It will not
       * tell you whether an ancestor has been suspended.
       * To determine if this scope will be excluded from a digest triggered at the $rootScope,
       * for example, you must check all its ancestors:
       *
       * ```
       * function isExcludedFromDigest(scope) {
       *   while(scope) {
       *     if (scope.$isSuspended()) return true;
       *     scope = scope.$parent;
       *   }
       *   return false;
       * ```
       *
       * Be aware that a scope may not be included in digests if it has a suspended ancestor,
       * even if `$isSuspended()` returns false.
       *
       * @returns true if the current scope has been suspended.
       */
      $isSuspended: function() {
        return this.$$suspended;
      },

      /**
       * @ngdoc method
       * @name $rootScope.Scope#$resume
       * @kind function
       *
       * @description
       * Resume watchers of this scope subtree in case it was suspended.
       *
       * See {@link $rootScope.Scope#$suspend} for information about the dangers of using this approach.
       */
      $resume: function() {
        this.$$suspended = false;
      },

      /**
       * @ngdoc event
       * @name $rootScope.Scope#$destroy
       * @eventType broadcast on scope being destroyed
       *
       * @description
       * Broadcasted when a scope and its children are being destroyed.
       *
       * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
       * clean up DOM bindings before an element is removed from the DOM.
       */

      /**
       * @ngdoc method
       * @name $rootScope.Scope#$destroy
       * @kind function
       *
       * @description
       * Removes the current scope (and all of its children) from the parent scope. Removal implies
       * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer
       * propagate to the current scope and its children. Removal also implies that the current
       * scope is eligible for garbage collection.
       *
       * The `$destroy()` is usually used by directives such as
       * {@link ng.directive:ngRepeat ngRepeat} for managing the
       * unrolling of the loop.
       *
       * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope.
       * Application code can register a `$destroy` event handler that will give it a chance to
       * perform any necessary cleanup.
       *
       * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
       * clean up DOM bindings before an element is removed from the DOM.
       */
      $destroy: function() {
        // We can't destroy a scope that has been already destroyed.
        if (this.$$destroyed) return;
        var parent = this.$parent;

        this.$broadcast('$destroy');
        this.$$destroyed = true;

        if (this === $rootScope) {
          //Remove handlers attached to window when $rootScope is removed
          $browser.$$applicationDestroyed();
        }

        incrementWatchersCount(this, -this.$$watchersCount);
        for (var eventName in this.$$listenerCount) {
          decrementListenerCount(this, this.$$listenerCount[eventName], eventName);
        }

        // sever all the references to parent scopes (after this cleanup, the current scope should
        // not be retained by any of our references and should be eligible for garbage collection)
        if (parent && parent.$$childHead === this) parent.$$childHead = this.$$nextSibling;
        if (parent && parent.$$childTail === this) parent.$$childTail = this.$$prevSibling;
        if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
        if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;

        // Disable listeners, watchers and apply/digest methods
        this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop;
        this.$on = this.$watch = this.$watchGroup = function() { return noop; };
        this.$$listeners = {};

        // Disconnect the next sibling to prevent `cleanUpScope` destroying those too
        this.$$nextSibling = null;
        cleanUpScope(this);
      },

      /**
       * @ngdoc method
       * @name $rootScope.Scope#$eval
       * @kind function
       *
       * @description
       * Executes the `expression` on the current scope and returns the result. Any exceptions in
       * the expression are propagated (uncaught). This is useful when evaluating AngularJS
       * expressions.
       *
       * @example
       * ```js
           var scope = ng.$rootScope.Scope();
           scope.a = 1;
           scope.b = 2;

           expect(scope.$eval('a+b')).toEqual(3);
           expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
       * ```
       *
       * @param {(string|function())=} expression An AngularJS expression to be executed.
       *
       *    - `string`: execute using the rules as defined in  {@link guide/expression expression}.
       *    - `function(scope)`: execute the function with the current `scope` parameter.
       *
       * @param {(object)=} locals Local variables object, useful for overriding values in scope.
       * @returns {*} The result of evaluating the expression.
       */
      $eval: function(expr, locals) {
        return $parse(expr)(this, locals);
      },

      /**
       * @ngdoc method
       * @name $rootScope.Scope#$evalAsync
       * @kind function
       *
       * @description
       * Executes the expression on the current scope at a later point in time.
       *
       * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only
       * that:
       *
       *   - it will execute after the function that scheduled the evaluation (preferably before DOM
       *     rendering).
       *   - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after
       *     `expression` execution.
       *
       * Any exceptions from the execution of the expression are forwarded to the
       * {@link ng.$exceptionHandler $exceptionHandler} service.
       *
       * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle
       * will be scheduled. However, it is encouraged to always call code that changes the model
       * from within an `$apply` call. That includes code evaluated via `$evalAsync`.
       *
       * @param {(string|function())=} expression An AngularJS expression to be executed.
       *
       *    - `string`: execute using the rules as defined in {@link guide/expression expression}.
       *    - `function(scope)`: execute the function with the current `scope` parameter.
       *
       * @param {(object)=} locals Local variables object, useful for overriding values in scope.
       */
      $evalAsync: function(expr, locals) {
        // if we are outside of an $digest loop and this is the first time we are scheduling async
        // task also schedule async auto-flush
        if (!$rootScope.$$phase && !asyncQueue.length) {
          $browser.defer(function() {
            if (asyncQueue.length) {
              $rootScope.$digest();
            }
          }, null, '$evalAsync');
        }

        asyncQueue.push({scope: this, fn: $parse(expr), locals: locals});
      },

      $$postDigest: function(fn) {
        postDigestQueue.push(fn);
      },

      /**
       * @ngdoc method
       * @name $rootScope.Scope#$apply
       * @kind function
       *
       * @description
       * `$apply()` is used to execute an expression in AngularJS from outside of the AngularJS
       * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries).
       * Because we are calling into the AngularJS framework we need to perform proper scope life
       * cycle of {@link ng.$exceptionHandler exception handling},
       * {@link ng.$rootScope.Scope#$digest executing watches}.
       *
       * **Life cycle: Pseudo-Code of `$apply()`**
       *
       * ```js
           function $apply(expr) {
             try {
               return $eval(expr);
             } catch (e) {
               $exceptionHandler(e);
             } finally {
               $root.$digest();
             }
           }
       * ```
       *
       *
       * Scope's `$apply()` method transitions through the following stages:
       *
       * 1. The {@link guide/expression expression} is executed using the
       *    {@link ng.$rootScope.Scope#$eval $eval()} method.
       * 2. Any exceptions from the execution of the expression are forwarded to the
       *    {@link ng.$exceptionHandler $exceptionHandler} service.
       * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the
       *    expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method.
       *
       *
       * @param {(string|function())=} exp An AngularJS expression to be executed.
       *
       *    - `string`: execute using the rules as defined in {@link guide/expression expression}.
       *    - `function(scope)`: execute the function with current `scope` parameter.
       *
       * @returns {*} The result of evaluating the expression.
       */
      $apply: function(expr) {
        try {
          beginPhase('$apply');
          try {
            return this.$eval(expr);
          } finally {
            clearPhase();
          }
        } catch (e) {
          $exceptionHandler(e);
        } finally {
          try {
            $rootScope.$digest();
          } catch (e) {
            $exceptionHandler(e);
            // eslint-disable-next-line no-unsafe-finally
            throw e;
          }
        }
      },

      /**
       * @ngdoc method
       * @name $rootScope.Scope#$applyAsync
       * @kind function
       *
       * @description
       * Schedule the invocation of $apply to occur at a later time. The actual time difference
       * varies across browsers, but is typically around ~10 milliseconds.
       *
       * This can be used to queue up multiple expressions which need to be evaluated in the same
       * digest.
       *
       * @param {(string|function())=} exp An AngularJS expression to be executed.
       *
       *    - `string`: execute using the rules as defined in {@link guide/expression expression}.
       *    - `function(scope)`: execute the function with current `scope` parameter.
       */
      $applyAsync: function(expr) {
        var scope = this;
        if (expr) {
          applyAsyncQueue.push($applyAsyncExpression);
        }
        expr = $parse(expr);
        scheduleApplyAsync();

        function $applyAsyncExpression() {
          scope.$eval(expr);
        }
      },

      /**
       * @ngdoc method
       * @name $rootScope.Scope#$on
       * @kind function
       *
       * @description
       * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for
       * discussion of event life cycle.
       *
       * The event listener function format is: `function(event, args...)`. The `event` object
       * passed into the listener has the following attributes:
       *
       *   - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or
       *     `$broadcast`-ed.
       *   - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the
       *     event propagates through the scope hierarchy, this property is set to null.
       *   - `name` - `{string}`: name of the event.
       *   - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel
       *     further event propagation (available only for events that were `$emit`-ed).
       *   - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag
       *     to true.
       *   - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called.
       *
       * @param {string} name Event name to listen on.
       * @param {function(event, ...args)} listener Function to call when the event is emitted.
       * @returns {function()} Returns a deregistration function for this listener.
       */
      $on: function(name, listener) {
        var namedListeners = this.$$listeners[name];
        if (!namedListeners) {
          this.$$listeners[name] = namedListeners = [];
        }
        namedListeners.push(listener);

        var current = this;
        do {
          if (!current.$$listenerCount[name]) {
            current.$$listenerCount[name] = 0;
          }
          current.$$listenerCount[name]++;
        } while ((current = current.$parent));

        var self = this;
        return function() {
          var indexOfListener = namedListeners.indexOf(listener);
          if (indexOfListener !== -1) {
            // Use delete in the hope of the browser deallocating the memory for the array entry,
            // while not shifting the array indexes of other listeners.
            // See issue https://github.com/angular/angular.js/issues/16135
            delete namedListeners[indexOfListener];
            decrementListenerCount(self, 1, name);
          }
        };
      },


      /**
       * @ngdoc method
       * @name $rootScope.Scope#$emit
       * @kind function
       *
       * @description
       * Dispatches an event `name` upwards through the scope hierarchy notifying the
       * registered {@link ng.$rootScope.Scope#$on} listeners.
       *
       * The event life cycle starts at the scope on which `$emit` was called. All
       * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
       * notified. Afterwards, the event traverses upwards toward the root scope and calls all
       * registered listeners along the way. The event will stop propagating if one of the listeners
       * cancels it.
       *
       * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
       * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
       *
       * @param {string} name Event name to emit.
       * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
       * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}).
       */
      $emit: function(name, args) {
        var empty = [],
            namedListeners,
            scope = this,
            stopPropagation = false,
            event = {
              name: name,
              targetScope: scope,
              stopPropagation: function() {stopPropagation = true;},
              preventDefault: function() {
                event.defaultPrevented = true;
              },
              defaultPrevented: false
            },
            listenerArgs = concat([event], arguments, 1),
            i, length;

        do {
          namedListeners = scope.$$listeners[name] || empty;
          event.currentScope = scope;
          for (i = 0, length = namedListeners.length; i < length; i++) {

            // if listeners were deregistered, defragment the array
            if (!namedListeners[i]) {
              namedListeners.splice(i, 1);
              i--;
              length--;
              continue;
            }
            try {
              //allow all listeners attached to the current scope to run
              namedListeners[i].apply(null, listenerArgs);
            } catch (e) {
              $exceptionHandler(e);
            }
          }
          //if any listener on the current scope stops propagation, prevent bubbling
          if (stopPropagation) {
            break;
          }
          //traverse upwards
          scope = scope.$parent;
        } while (scope);

        event.currentScope = null;

        return event;
      },


      /**
       * @ngdoc method
       * @name $rootScope.Scope#$broadcast
       * @kind function
       *
       * @description
       * Dispatches an event `name` downwards to all child scopes (and their children) notifying the
       * registered {@link ng.$rootScope.Scope#$on} listeners.
       *
       * The event life cycle starts at the scope on which `$broadcast` was called. All
       * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
       * notified. Afterwards, the event propagates to all direct and indirect scopes of the current
       * scope and calls all registered listeners along the way. The event cannot be canceled.
       *
       * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
       * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
       *
       * @param {string} name Event name to broadcast.
       * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
       * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on}
       */
      $broadcast: function(name, args) {
        var target = this,
            current = target,
            next = target,
            event = {
              name: name,
              targetScope: target,
              preventDefault: function() {
                event.defaultPrevented = true;
              },
              defaultPrevented: false
            };

        if (!target.$$listenerCount[name]) return event;

        var listenerArgs = concat([event], arguments, 1),
            listeners, i, length;

        //down while you can, then up and next sibling or up and next sibling until back at root
        while ((current = next)) {
          event.currentScope = current;
          listeners = current.$$listeners[name] || [];
          for (i = 0, length = listeners.length; i < length; i++) {
            // if listeners were deregistered, defragment the array
            if (!listeners[i]) {
              listeners.splice(i, 1);
              i--;
              length--;
              continue;
            }

            try {
              listeners[i].apply(null, listenerArgs);
            } catch (e) {
              $exceptionHandler(e);
            }
          }

          // Insanity Warning: scope depth-first traversal
          // yes, this code is a bit crazy, but it works and we have tests to prove it!
          // this piece should be kept in sync with the traversal in $digest
          // (though it differs due to having the extra check for $$listenerCount and
          // does not check $$suspended)
          if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||
              (current !== target && current.$$nextSibling)))) {
            while (current !== target && !(next = current.$$nextSibling)) {
              current = current.$parent;
            }
          }
        }

        event.currentScope = null;
        return event;
      }
    };

    var $rootScope = new Scope();

    //The internal queues. Expose them on the $rootScope for debugging/testing purposes.
    var asyncQueue = $rootScope.$$asyncQueue = [];
    var postDigestQueue = $rootScope.$$postDigestQueue = [];
    var applyAsyncQueue = $rootScope.$$applyAsyncQueue = [];

    var postDigestQueuePosition = 0;

    return $rootScope;


    function beginPhase(phase) {
      if ($rootScope.$$phase) {
        throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase);
      }

      $rootScope.$$phase = phase;
    }

    function clearPhase() {
      $rootScope.$$phase = null;
    }

    function incrementWatchersCount(current, count) {
      do {
        current.$$watchersCount += count;
      } while ((current = current.$parent));
    }

    function decrementListenerCount(current, count, name) {
      do {
        current.$$listenerCount[name] -= count;

        if (current.$$listenerCount[name] === 0) {
          delete current.$$listenerCount[name];
        }
      } while ((current = current.$parent));
    }

    /**
     * function used as an initial value for watchers.
     * because it's unique we can easily tell it apart from other values
     */
    function initWatchVal() {}

    function flushApplyAsync() {
      while (applyAsyncQueue.length) {
        try {
          applyAsyncQueue.shift()();
        } catch (e) {
          $exceptionHandler(e);
        }
      }
      applyAsyncId = null;
    }

    function scheduleApplyAsync() {
      if (applyAsyncId === null) {
        applyAsyncId = $browser.defer(function() {
          $rootScope.$apply(flushApplyAsync);
        }, null, '$applyAsync');
      }
    }
  }];
}

/**
 * @ngdoc service
 * @name $rootElement
 *
 * @description
 * The root element of AngularJS application. This is either the element where {@link
 * ng.directive:ngApp ngApp} was declared or the element passed into
 * {@link angular.bootstrap}. The element represents the root element of application. It is also the
 * location where the application's {@link auto.$injector $injector} service gets
 * published, and can be retrieved using `$rootElement.injector()`.
 */


// the implementation is in angular.bootstrap

/**
 * @this
 * @description
 * Private service to sanitize uris for links and images. Used by $compile and $sanitize.
 */
function $$SanitizeUriProvider() {

  var aHrefSanitizationTrustedUrlList = /^\s*(https?|s?ftp|mailto|tel|file):/,
    imgSrcSanitizationTrustedUrlList = /^\s*((https?|ftp|file|blob):|data:image\/)/;

  /**
   * @description
   * Retrieves or overrides the default regular expression that is used for determining trusted safe
   * urls during a[href] sanitization.
   *
   * The sanitization is a security measure aimed at prevent XSS attacks via HTML anchor links.
   *
   * Any url due to be assigned to an `a[href]` attribute via interpolation is marked as requiring
   * the $sce.URL security context. When interpolation occurs a call is made to `$sce.trustAsUrl(url)`
   * which in turn may call `$$sanitizeUri(url, isMedia)` to sanitize the potentially malicious URL.
   *
   * If the URL matches the `aHrefSanitizationTrustedUrlList` regular expression, it is returned unchanged.
   *
   * If there is no match the URL is returned prefixed with `'unsafe:'` to ensure that when it is written
   * to the DOM it is inactive and potentially malicious code will not be executed.
   *
   * @param {RegExp=} regexp New regexp to trust urls with.
   * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
   *    chaining otherwise.
   */
  this.aHrefSanitizationTrustedUrlList = function(regexp) {
    if (isDefined(regexp)) {
      aHrefSanitizationTrustedUrlList = regexp;
      return this;
    }
    return aHrefSanitizationTrustedUrlList;
  };


  /**
   * @description
   * Retrieves or overrides the default regular expression that is used for determining trusted safe
   * urls during img[src] sanitization.
   *
   * The sanitization is a security measure aimed at prevent XSS attacks via HTML image src links.
   *
   * Any URL due to be assigned to an `img[src]` attribute via interpolation is marked as requiring
   * the $sce.MEDIA_URL security context. When interpolation occurs a call is made to
   * `$sce.trustAsMediaUrl(url)` which in turn may call `$$sanitizeUri(url, isMedia)` to sanitize
   * the potentially malicious URL.
   *
   * If the URL matches the `imgSrcSanitizationTrustedUrlList` regular expression, it is returned
   * unchanged.
   *
   * If there is no match the URL is returned prefixed with `'unsafe:'` to ensure that when it is written
   * to the DOM it is inactive and potentially malicious code will not be executed.
   *
   * @param {RegExp=} regexp New regexp to trust urls with.
   * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
   *    chaining otherwise.
   */
  this.imgSrcSanitizationTrustedUrlList = function(regexp) {
    if (isDefined(regexp)) {
      imgSrcSanitizationTrustedUrlList = regexp;
      return this;
    }
    return imgSrcSanitizationTrustedUrlList;
  };

  this.$get = function() {
    return function sanitizeUri(uri, isMediaUrl) {
      if (uri.indexOf('unsafe:') === 0) return uri
      // if (!uri) return uri;
      var regex = isMediaUrl ? imgSrcSanitizationTrustedUrlList : aHrefSanitizationTrustedUrlList;
      var normalizedVal = urlResolve(uri && uri.trim()).href;
      if (normalizedVal !== '' && !normalizedVal.match(regex)) {
        return 'unsafe:' + normalizedVal;
      }
      return uri;
    };
  };
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *     Any commits to this file should be reviewed with security in mind.  *
 *   Changes to this file can potentially create security vulnerabilities. *
 *          An approval from 2 Core members with history of modifying      *
 *                         this file is required.                          *
 *                                                                         *
 *  Does the change somehow allow for arbitrary javascript to be executed? *
 *    Or allows for someone to change the prototype of built-in objects?   *
 *     Or gives undesired access to variables likes document or window?    *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* exported $SceProvider, $SceDelegateProvider */

var $sceMinErr = minErr('$sce');

var SCE_CONTEXTS = {
  // HTML is used when there's HTML rendered (e.g. ng-bind-html, iframe srcdoc binding).
  HTML: 'html',

  // Style statements or stylesheets. Currently unused in AngularJS.
  CSS: 'css',

  // An URL used in a context where it refers to the source of media, which are not expected to be run
  // as scripts, such as an image, audio, video, etc.
  MEDIA_URL: 'mediaUrl',

  // An URL used in a context where it does not refer to a resource that loads code.
  // A value that can be trusted as a URL can also trusted as a MEDIA_URL.
  URL: 'url',

  // RESOURCE_URL is a subtype of URL used where the referred-to resource could be interpreted as
  // code. (e.g. ng-include, script src binding, templateUrl)
  // A value that can be trusted as a RESOURCE_URL, can also trusted as a URL and a MEDIA_URL.
  RESOURCE_URL: 'resourceUrl',

  // Script. Currently unused in AngularJS.
  JS: 'js'
};

// Helper functions follow.

var UNDERSCORE_LOWERCASE_REGEXP = /_([a-z])/g;

function snakeToCamel(name) {
  return name
    .replace(UNDERSCORE_LOWERCASE_REGEXP, fnCamelCaseReplace);
}

function adjustMatcher(matcher) {
  if (matcher === 'self') {
    return matcher;
  } else if (isString(matcher)) {
    // Strings match exactly except for 2 wildcards - '*' and '**'.
    // '*' matches any character except those from the set ':/.?&'.
    // '**' matches any character (like .* in a RegExp).
    // More than 2 *'s raises an error as it's ill defined.
    if (matcher.indexOf('***') > -1) {
      throw $sceMinErr('iwcard',
          'Illegal sequence *** in string matcher.  String: {0}', matcher);
    }
    matcher = escapeForRegexp(matcher).
                  replace(/\\\*\\\*/g, '.*').
                  replace(/\\\*/g, '[^:/.?&;]*');
    return new RegExp('^' + matcher + '$');
  } else if (isRegExp(matcher)) {
    // The only other type of matcher allowed is a Regexp.
    // Match entire URL / disallow partial matches.
    // Flags are reset (i.e. no global, ignoreCase or multiline)
    return new RegExp('^' + matcher.source + '$');
  } else {
    throw $sceMinErr('imatcher',
        'Matchers may only be "self", string patterns or RegExp objects');
  }
}


function adjustMatchers(matchers) {
  var adjustedMatchers = [];
  if (isDefined(matchers)) {
    forEach(matchers, function(matcher) {
      adjustedMatchers.push(adjustMatcher(matcher));
    });
  }
  return adjustedMatchers;
}


/**
 * @ngdoc service
 * @name $sceDelegate
 * @kind function
 *
 * @description
 *
 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict
 * Contextual Escaping (SCE)} services to AngularJS.
 *
 * For an overview of this service and the functionnality it provides in AngularJS, see the main
 * page for {@link ng.$sce SCE}. The current page is targeted for developers who need to alter how
 * SCE works in their application, which shouldn't be needed in most cases.
 *
 * <div class="alert alert-danger">
 * AngularJS strongly relies on contextual escaping for the security of bindings: disabling or
 * modifying this might cause cross site scripting (XSS) vulnerabilities. For libraries owners,
 * changes to this service will also influence users, so be extra careful and document your changes.
 * </div>
 *
 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of
 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS.  This is
 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to
 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things
 * work because `$sce` delegates to `$sceDelegate` for these operations.
 *
 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service.
 *
 * The default instance of `$sceDelegate` should work out of the box with little pain.  While you
 * can override it completely to change the behavior of `$sce`, the common case would
 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting
 * your own trusted and banned resource lists for trusting URLs used for loading AngularJS resources
 * such as templates.  Refer {@link ng.$sceDelegateProvider#trustedResourceUrlList
 * $sceDelegateProvider.trustedResourceUrlList} and {@link
 * ng.$sceDelegateProvider#bannedResourceUrlList $sceDelegateProvider.bannedResourceUrlList}
 */

/**
 * @ngdoc provider
 * @name $sceDelegateProvider
 * @this
 *
 * @description
 *
 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate
 * $sceDelegate service}, used as a delegate for {@link ng.$sce Strict Contextual Escaping (SCE)}.
 *
 * The `$sceDelegateProvider` allows one to get/set the `trustedResourceUrlList` and
 * `bannedResourceUrlList` used to ensure that the URLs used for sourcing AngularJS templates and
 * other script-running URLs are safe (all places that use the `$sce.RESOURCE_URL` context). See
 * {@link ng.$sceDelegateProvider#trustedResourceUrlList
 * $sceDelegateProvider.trustedResourceUrlList} and
 * {@link ng.$sceDelegateProvider#bannedResourceUrlList $sceDelegateProvider.bannedResourceUrlList},
 *
 * For the general details about this service in AngularJS, read the main page for {@link ng.$sce
 * Strict Contextual Escaping (SCE)}.
 *
 * **Example**:  Consider the following case. <a name="example"></a>
 *
 * - your app is hosted at url `http://myapp.example.com/`
 * - but some of your templates are hosted on other domains you control such as
 *   `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc.
 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`.
 *
 * Here is what a secure configuration for this scenario might look like:
 *
 * ```
 *  angular.module('myApp', []).config(function($sceDelegateProvider) {
 *    $sceDelegateProvider.trustedResourceUrlList([
 *      // Allow same origin resource loads.
 *      'self',
 *      // Allow loading from our assets domain.  Notice the difference between * and **.
 *      'http://srv*.assets.example.com/**'
 *    ]);
 *
 *    // The banned resource URL list overrides the trusted resource URL list so the open redirect
 *    // here is blocked.
 *    $sceDelegateProvider.bannedResourceUrlList([
 *      'http://myapp.example.com/clickThru**'
 *    ]);
 *  });
 * ```
 * Note that an empty trusted resource URL list will block every resource URL from being loaded, and will require
 * you to manually mark each one as trusted with `$sce.trustAsResourceUrl`. However, templates
 * requested by {@link ng.$templateRequest $templateRequest} that are present in
 * {@link ng.$templateCache $templateCache} will not go through this check. If you have a mechanism
 * to populate your templates in that cache at config time, then it is a good idea to remove 'self'
 * from the trusted resource URL lsit. This helps to mitigate the security impact of certain types
 * of issues, like for instance attacker-controlled `ng-includes`.
 */

function $SceDelegateProvider() {
  this.SCE_CONTEXTS = SCE_CONTEXTS;

  // Resource URLs can also be trusted by policy.
  var trustedResourceUrlList = ['self'],
      bannedResourceUrlList = [];

  /**
   * @ngdoc method
   * @name $sceDelegateProvider#trustedResourceUrlList
   * @kind function
   *
   * @param {Array=} trustedResourceUrlList When provided, replaces the trustedResourceUrlList with
   *     the value provided.  This must be an array or null.  A snapshot of this array is used so
   *     further changes to the array are ignored.
   *     Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
   *     allowed in this array.
   *
   * @return {Array} The currently set trusted resource URL array.
   *
   * @description
   * Sets/Gets the list trusted of resource URLs.
   *
   * The **default value** when no `trustedResourceUrlList` has been explicitly set is `['self']`
   * allowing only same origin resource requests.
   *
   * <div class="alert alert-warning">
   * **Note:** the default `trustedResourceUrlList` of 'self' is not recommended if your app shares
   * its origin with other apps! It is a good idea to limit it to only your application's directory.
   * </div>
   */
  this.trustedResourceUrlList = function(value) {
    if (arguments.length) {
      trustedResourceUrlList = adjustMatchers(value);
    }
    return trustedResourceUrlList;
  };

  /**
   * @ngdoc method
   * @name $sceDelegateProvider#resourceUrlWhitelist
   * @kind function
   *
   * @deprecated
   * sinceVersion="1.8.1"
   *
   * This method is deprecated. Use {@link $sceDelegateProvider#trustedResourceUrlList
   * trustedResourceUrlList} instead.
   */
  Object.defineProperty(this, 'resourceUrlWhitelist', {
    get: function() {
      return this.trustedResourceUrlList;
    },
    set: function(value) {
      this.trustedResourceUrlList = value;
    }
  });

  /**
   * @ngdoc method
   * @name $sceDelegateProvider#bannedResourceUrlList
   * @kind function
   *
   * @param {Array=} bannedResourceUrlList When provided, replaces the `bannedResourceUrlList` with
   *     the value provided. This must be an array or null. A snapshot of this array is used so
   *     further changes to the array are ignored.</p><p>
   *     Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items
   *     allowed in this array.</p><p>
   *     The typical usage for the `bannedResourceUrlList` is to **block
   *     [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as
   *     these would otherwise be trusted but actually return content from the redirected domain.
   *     </p><p>
   *     Finally, **the banned resource URL list overrides the trusted resource URL list** and has
   *     the final say.
   *
   * @return {Array} The currently set `bannedResourceUrlList` array.
   *
   * @description
   * Sets/Gets the `bannedResourceUrlList` of trusted resource URLs.
   *
   * The **default value** when no trusted resource URL list has been explicitly set is the empty
   * array (i.e. there is no `bannedResourceUrlList`.)
   */
  this.bannedResourceUrlList = function(value) {
    if (arguments.length) {
      bannedResourceUrlList = adjustMatchers(value);
    }
    return bannedResourceUrlList;
  };

  /**
   * @ngdoc method
   * @name $sceDelegateProvider#resourceUrlBlacklist
   * @kind function
   *
   * @deprecated
   * sinceVersion="1.8.1"
   *
   * This method is deprecated. Use {@link $sceDelegateProvider#bannedResourceUrlList
   * bannedResourceUrlList} instead.
   */
  Object.defineProperty(this, 'resourceUrlBlacklist', {
    get: function() {
      return this.bannedResourceUrlList;
    },
    set: function(value) {
      this.bannedResourceUrlList = value;
    }
  });

  this.$get = ['$injector', '$$sanitizeUri', function($injector, $$sanitizeUri) {

    var htmlSanitizer = function htmlSanitizer(html) {
      throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
    };

    if ($injector.has('$sanitize')) {
      htmlSanitizer = $injector.get('$sanitize');
    }


    function matchUrl(matcher, parsedUrl) {
      if (matcher === 'self') {
        return urlIsSameOrigin(parsedUrl) || urlIsSameOriginAsBaseUrl(parsedUrl);
      } else {
        // definitely a regex.  See adjustMatchers()
        return !!matcher.exec(parsedUrl.href);
      }
    }

    function isResourceUrlAllowedByPolicy(url) {
      var parsedUrl = urlResolve(url.toString());
      var i, n, allowed = false;
      // Ensure that at least one item from the trusted resource URL list allows this url.
      for (i = 0, n = trustedResourceUrlList.length; i < n; i++) {
        if (matchUrl(trustedResourceUrlList[i], parsedUrl)) {
          allowed = true;
          break;
        }
      }
      if (allowed) {
        // Ensure that no item from the banned resource URL list has blocked this url.
        for (i = 0, n = bannedResourceUrlList.length; i < n; i++) {
          if (matchUrl(bannedResourceUrlList[i], parsedUrl)) {
            allowed = false;
            break;
          }
        }
      }
      return allowed;
    }

    function generateHolderType(Base) {
      var holderType = function TrustedValueHolderType(trustedValue) {
        this.$$unwrapTrustedValue = function() {
          return trustedValue;
        };
      };
      if (Base) {
        holderType.prototype = new Base();
      }
      holderType.prototype.valueOf = function sceValueOf() {
        return this.$$unwrapTrustedValue();
      };
      holderType.prototype.toString = function sceToString() {
        return this.$$unwrapTrustedValue().toString();
      };
      return holderType;
    }

    var trustedValueHolderBase = generateHolderType(),
        byType = {};

    byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase);
    byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase);
    byType[SCE_CONTEXTS.MEDIA_URL] = generateHolderType(trustedValueHolderBase);
    byType[SCE_CONTEXTS.URL] = generateHolderType(byType[SCE_CONTEXTS.MEDIA_URL]);
    byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase);
    byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]);

    /**
     * @ngdoc method
     * @name $sceDelegate#trustAs
     *
     * @description
     * Returns a trusted representation of the parameter for the specified context. This trusted
     * object will later on be used as-is, without any security check, by bindings or directives
     * that require this security context.
     * For instance, marking a string as trusted for the `$sce.HTML` context will entirely bypass
     * the potential `$sanitize` call in corresponding `$sce.HTML` bindings or directives, such as
     * `ng-bind-html`. Note that in most cases you won't need to call this function: if you have the
     * sanitizer loaded, passing the value itself will render all the HTML that does not pose a
     * security risk.
     *
     * See {@link ng.$sceDelegate#getTrusted getTrusted} for the function that will consume those
     * trusted values, and {@link ng.$sce $sce} for general documentation about strict contextual
     * escaping.
     *
     * @param {string} type The context in which this value is safe for use, e.g. `$sce.URL`,
     *     `$sce.RESOURCE_URL`, `$sce.HTML`, `$sce.JS` or `$sce.CSS`.
     *
     * @param {*} value The value that should be considered trusted.
     * @return {*} A trusted representation of value, that can be used in the given context.
     */
    function trustAs(type, trustedValue) {
      var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
      if (!Constructor) {
        throw $sceMinErr('icontext',
            'Attempted to trust a value in invalid context. Context: {0}; Value: {1}',
            type, trustedValue);
      }
      if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') {
        return trustedValue;
      }
      // All the current contexts in SCE_CONTEXTS happen to be strings.  In order to avoid trusting
      // mutable objects, we ensure here that the value passed in is actually a string.
      if (typeof trustedValue !== 'string') {
        throw $sceMinErr('itype',
            'Attempted to trust a non-string value in a content requiring a string: Context: {0}',
            type);
      }
      return new Constructor(trustedValue);
    }

    /**
     * @ngdoc method
     * @name $sceDelegate#valueOf
     *
     * @description
     * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs
     * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link
     * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}.
     *
     * If the passed parameter is not a value that had been returned by {@link
     * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, it must be returned as-is.
     *
     * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}
     *     call or anything else.
     * @return {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs
     *     `$sceDelegate.trustAs`} if `value` is the result of such a call.  Otherwise, returns
     *     `value` unchanged.
     */
    function valueOf(maybeTrusted) {
      if (maybeTrusted instanceof trustedValueHolderBase) {
        return maybeTrusted.$$unwrapTrustedValue();
      } else {
        return maybeTrusted;
      }
    }

    /**
     * @ngdoc method
     * @name $sceDelegate#getTrusted
     *
     * @description
     * Given an object and a security context in which to assign it, returns a value that's safe to
     * use in this context, which was represented by the parameter. To do so, this function either
     * unwraps the safe type it has been given (for instance, a {@link ng.$sceDelegate#trustAs
     * `$sceDelegate.trustAs`} result), or it might try to sanitize the value given, depending on
     * the context and sanitizer availablility.
     *
     * The contexts that can be sanitized are $sce.MEDIA_URL, $sce.URL and $sce.HTML. The first two are available
     * by default, and the third one relies on the `$sanitize` service (which may be loaded through
     * the `ngSanitize` module). Furthermore, for $sce.RESOURCE_URL context, a plain string may be
     * accepted if the resource url policy defined by {@link ng.$sceDelegateProvider#trustedResourceUrlList
     * `$sceDelegateProvider.trustedResourceUrlList`} and {@link ng.$sceDelegateProvider#bannedResourceUrlList
     * `$sceDelegateProvider.bannedResourceUrlList`} accepts that resource.
     *
     * This function will throw if the safe type isn't appropriate for this context, or if the
     * value given cannot be accepted in the context (which might be caused by sanitization not
     * being available, or the value not being recognized as safe).
     *
     * <div class="alert alert-danger">
     * Disabling auto-escaping is extremely dangerous, it usually creates a Cross Site Scripting
     * (XSS) vulnerability in your application.
     * </div>
     *
     * @param {string} type The context in which this value is to be used (such as `$sce.HTML`).
     * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs
     *     `$sceDelegate.trustAs`} call, or anything else (which will not be considered trusted.)
     * @return {*} A version of the value that's safe to use in the given context, or throws an
     *     exception if this is impossible.
     */
    function getTrusted(type, maybeTrusted) {
      if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') {
        return maybeTrusted;
      }
      var constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
      // If maybeTrusted is a trusted class instance or subclass instance, then unwrap and return
      // as-is.
      if (constructor && maybeTrusted instanceof constructor) {
        return maybeTrusted.$$unwrapTrustedValue();
      }

      // If maybeTrusted is a trusted class instance but not of the correct trusted type
      // then unwrap it and allow it to pass through to the rest of the checks
      if (isFunction(maybeTrusted.$$unwrapTrustedValue)) {
        maybeTrusted = maybeTrusted.$$unwrapTrustedValue();
      }

      // If we get here, then we will either sanitize the value or throw an exception.
      if (type === SCE_CONTEXTS.MEDIA_URL || type === SCE_CONTEXTS.URL) {
        // we attempt to sanitize non-resource URLs
        return $$sanitizeUri(maybeTrusted.toString(), type === SCE_CONTEXTS.MEDIA_URL);
      } else if (type === SCE_CONTEXTS.RESOURCE_URL) {
        if (isResourceUrlAllowedByPolicy(maybeTrusted)) {
          return maybeTrusted;
        } else {
          throw $sceMinErr('insecurl',
              'Blocked loading resource from url not allowed by $sceDelegate policy.  URL: {0}',
              maybeTrusted.toString());
        }
      } else if (type === SCE_CONTEXTS.HTML) {
        // htmlSanitizer throws its own error when no sanitizer is available.
        return htmlSanitizer(maybeTrusted);
      }
      // Default error when the $sce service has no way to make the input safe.
      throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
    }

    return { trustAs: trustAs,
             getTrusted: getTrusted,
             valueOf: valueOf };
  }];
}


/**
 * @ngdoc provider
 * @name $sceProvider
 * @this
 *
 * @description
 *
 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service.
 * -   enable/disable Strict Contextual Escaping (SCE) in a module
 * -   override the default implementation with a custom delegate
 *
 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}.
 */

/**
 * @ngdoc service
 * @name $sce
 * @kind function
 *
 * @description
 *
 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS.
 *
 * ## Strict Contextual Escaping
 *
 * Strict Contextual Escaping (SCE) is a mode in which AngularJS constrains bindings to only render
 * trusted values. Its goal is to assist in writing code in a way that (a) is secure by default, and
 * (b) makes auditing for security vulnerabilities such as XSS, clickjacking, etc. a lot easier.
 *
 * ### Overview
 *
 * To systematically block XSS security bugs, AngularJS treats all values as untrusted by default in
 * HTML or sensitive URL bindings. When binding untrusted values, AngularJS will automatically
 * run security checks on them (sanitizations, trusted URL resource, depending on context), or throw
 * when it cannot guarantee the security of the result. That behavior depends strongly on contexts:
 * HTML can be sanitized, but template URLs cannot, for instance.
 *
 * To illustrate this, consider the `ng-bind-html` directive. It renders its value directly as HTML:
 * we call that the *context*. When given an untrusted input, AngularJS will attempt to sanitize it
 * before rendering if a sanitizer is available, and throw otherwise. To bypass sanitization and
 * render the input as-is, you will need to mark it as trusted for that context before attempting
 * to bind it.
 *
 * As of version 1.2, AngularJS ships with SCE enabled by default.
 *
 * ### In practice
 *
 * Here's an example of a binding in a privileged context:
 *
 * ```
 * <input ng-model="userHtml" aria-label="User input">
 * <div ng-bind-html="userHtml"></div>
 * ```
 *
 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user.  With SCE
 * disabled, this application allows the user to render arbitrary HTML into the DIV, which would
 * be an XSS security bug. In a more realistic example, one may be rendering user comments, blog
 * articles, etc. via bindings. (HTML is just one example of a context where rendering user
 * controlled input creates security vulnerabilities.)
 *
 * For the case of HTML, you might use a library, either on the client side, or on the server side,
 * to sanitize unsafe HTML before binding to the value and rendering it in the document.
 *
 * How would you ensure that every place that used these types of bindings was bound to a value that
 * was sanitized by your library (or returned as safe for rendering by your server?)  How can you
 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some
 * properties/fields and forgot to update the binding to the sanitized value?
 *
 * To be secure by default, AngularJS makes sure bindings go through that sanitization, or
 * any similar validation process, unless there's a good reason to trust the given value in this
 * context.  That trust is formalized with a function call. This means that as a developer, you
 * can assume all untrusted bindings are safe. Then, to audit your code for binding security issues,
 * you just need to ensure the values you mark as trusted indeed are safe - because they were
 * received from your server, sanitized by your library, etc. You can organize your codebase to
 * help with this - perhaps allowing only the files in a specific directory to do this.
 * Ensuring that the internal API exposed by that code doesn't markup arbitrary values as safe then
 * becomes a more manageable task.
 *
 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs}
 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to
 * build the trusted versions of your values.
 *
 * ### How does it work?
 *
 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted
 * $sce.getTrusted(context, value)} rather than to the value directly.  Think of this function as
 * a way to enforce the required security context in your data sink. Directives use {@link
 * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs
 * the {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. Also,
 * when binding without directives, AngularJS will understand the context of your bindings
 * automatically.
 *
 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link
 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}.  Here's the actual code (slightly
 * simplified):
 *
 * ```
 * var ngBindHtmlDirective = ['$sce', function($sce) {
 *   return function(scope, element, attr) {
 *     scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) {
 *       element.html(value || '');
 *     });
 *   };
 * }];
 * ```
 *
 * ### Impact on loading templates
 *
 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as
 * `templateUrl`'s specified by {@link guide/directive directives}.
 *
 * By default, AngularJS only loads templates from the same domain and protocol as the application
 * document.  This is done by calling {@link ng.$sce#getTrustedResourceUrl
 * $sce.getTrustedResourceUrl} on the template URL.  To load templates from other domains and/or
 * protocols, you may either add them to the {@link ng.$sceDelegateProvider#trustedResourceUrlList
 * trustedResourceUrlList} or {@link ng.$sce#trustAsResourceUrl wrap them} into trusted values.
 *
 * *Please note*:
 * The browser's
 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/)
 * policy apply in addition to this and may further restrict whether the template is successfully
 * loaded.  This means that without the right CORS policy, loading templates from a different domain
 * won't work on all browsers.  Also, loading templates from `file://` URL does not work on some
 * browsers.
 *
 * ### This feels like too much overhead
 *
 * It's important to remember that SCE only applies to interpolation expressions.
 *
 * If your expressions are constant literals, they're automatically trusted and you don't need to
 * call `$sce.trustAs` on them (e.g.
 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works (remember to include the
 * `ngSanitize` module). The `$sceDelegate` will also use the `$sanitize` service if it is available
 * when binding untrusted values to `$sce.HTML` context.
 * AngularJS provides an implementation in `angular-sanitize.js`, and if you
 * wish to use it, you will also need to depend on the {@link ngSanitize `ngSanitize`} module in
 * your application.
 *
 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load
 * templates in `ng-include` from your application's domain without having to even know about SCE.
 * It blocks loading templates from other domains or loading templates over http from an https
 * served document.  You can change these by setting your own custom {@link
 * ng.$sceDelegateProvider#trustedResourceUrlList trusted resource URL list} and {@link
 * ng.$sceDelegateProvider#bannedResourceUrlList banned resource URL list} for matching such URLs.
 *
 * This significantly reduces the overhead.  It is far easier to pay the small overhead and have an
 * application that's secure and can be audited to verify that with much more ease than bolting
 * security onto an application later.
 *
 * <a name="contexts"></a>
 * ### What trusted context types are supported?
 *
 * | Context             | Notes          |
 * |---------------------|----------------|
 * | `$sce.HTML`         | For HTML that's safe to source into the application.  The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. If an unsafe value is encountered and the {@link ngSanitize $sanitize} module is present this will sanitize the value instead of throwing an error. |
 * | `$sce.CSS`          | For CSS that's safe to source into the application.  Currently unused.  Feel free to use it in your own directives. |
 * | `$sce.MEDIA_URL`    | For URLs that are safe to render as media. Is automatically converted from string by sanitizing when needed. |
 * | `$sce.URL`          | For URLs that are safe to follow as links. Is automatically converted from string by sanitizing when needed. Note that `$sce.URL` makes a stronger statement about the URL than `$sce.MEDIA_URL` does and therefore contexts requiring values trusted for `$sce.URL` can be used anywhere that values trusted for `$sce.MEDIA_URL` are required.|
 * | `$sce.RESOURCE_URL` | For URLs that are not only safe to follow as links, but whose contents are also safe to include in your application.  Examples include `ng-include`, `src` / `ngSrc` bindings for tags other than `IMG` (e.g. `IFRAME`, `OBJECT`, etc.)  <br><br>Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` or `$sce.MEDIA_URL` do and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` or `$sce.MEDIA_URL` are required. <br><br> The {@link $sceDelegateProvider#trustedResourceUrlList $sceDelegateProvider#trustedResourceUrlList()} and {@link $sceDelegateProvider#bannedResourceUrlList $sceDelegateProvider#bannedResourceUrlList()} can be used to restrict trusted origins for `RESOURCE_URL` |
 * | `$sce.JS`           | For JavaScript that is safe to execute in your application's context.  Currently unused.  Feel free to use it in your own directives. |
 *
 *
 * <div class="alert alert-warning">
 * Be aware that, before AngularJS 1.7.0, `a[href]` and `img[src]` used to sanitize their
 * interpolated values directly rather than rely upon {@link ng.$sce#getTrusted `$sce.getTrusted`}.
 *
 * **As of 1.7.0, this is no longer the case.**
 *
 * Now such interpolations are marked as requiring `$sce.URL` (for `a[href]`) or `$sce.MEDIA_URL`
 * (for `img[src]`), so that the sanitization happens (via `$sce.getTrusted...`) when the `$interpolate`
 * service evaluates the expressions.
 * </div>
 *
 * There are no CSS or JS context bindings in AngularJS currently, so their corresponding `$sce.trustAs`
 * functions aren't useful yet. This might evolve.
 *
 * ### Format of items in {@link ng.$sceDelegateProvider#trustedResourceUrlList trustedResourceUrlList}/{@link ng.$sceDelegateProvider#bannedResourceUrlList bannedResourceUrlList} <a name="resourceUrlPatternItem"></a>
 *
 *  Each element in these arrays must be one of the following:
 *
 *  - **'self'**
 *    - The special **string**, `'self'`, can be used to match against all URLs of the **same
 *      domain** as the application document using the **same protocol**.
 *  - **String** (except the special value `'self'`)
 *    - The string is matched against the full *normalized / absolute URL* of the resource
 *      being tested (substring matches are not good enough.)
 *    - There are exactly **two wildcard sequences** - `*` and `**`.  All other characters
 *      match themselves.
 *    - `*`: matches zero or more occurrences of any character other than one of the following 6
 *      characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'.  It's a useful wildcard for use
 *      for matching resource URL lists.
 *    - `**`: matches zero or more occurrences of *any* character.  As such, it's not
 *      appropriate for use in a scheme, domain, etc. as it would match too much.  (e.g.
 *      http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might
 *      not have been the intention.)  Its usage at the very end of the path is ok.  (e.g.
 *      http://foo.example.com/templates/**).
 *  - **RegExp** (*see caveat below*)
 *    - *Caveat*:  While regular expressions are powerful and offer great flexibility,  their syntax
 *      (and all the inevitable escaping) makes them *harder to maintain*.  It's easy to
 *      accidentally introduce a bug when one updates a complex expression (imho, all regexes should
 *      have good test coverage).  For instance, the use of `.` in the regex is correct only in a
 *      small number of cases.  A `.` character in the regex used when matching the scheme or a
 *      subdomain could be matched against a `:` or literal `.` that was likely not intended.   It
 *      is highly recommended to use the string patterns and only fall back to regular expressions
 *      as a last resort.
 *    - The regular expression must be an instance of RegExp (i.e. not a string.)  It is
 *      matched against the **entire** *normalized / absolute URL* of the resource being tested
 *      (even when the RegExp did not have the `^` and `$` codes.)  In addition, any flags
 *      present on the RegExp (such as multiline, global, ignoreCase) are ignored.
 *    - If you are generating your JavaScript from some other templating engine (not
 *      recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)),
 *      remember to escape your regular expression (and be aware that you might need more than
 *      one level of escaping depending on your templating engine and the way you interpolated
 *      the value.)  Do make use of your platform's escaping mechanism as it might be good
 *      enough before coding your own.  E.g. Ruby has
 *      [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape)
 *      and Python has [re.escape](http://docs.python.org/library/re.html#re.escape).
 *      Javascript lacks a similar built in function for escaping.  Take a look at Google
 *      Closure library's [goog.string.regExpEscape(s)](
 *      http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962).
 *
 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example.
 *
 * ### Show me an example using SCE.
 *
 * <example module="mySceApp" deps="angular-sanitize.js" name="sce-service">
 * <file name="index.html">
 *   <div ng-controller="AppController as myCtrl">
 *     <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br>
 *     <b>User comments</b><br>
 *     By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when
 *     $sanitize is available.  If $sanitize isn't available, this results in an error instead of an
 *     exploit.
 *     <div class="well">
 *       <div ng-repeat="userComment in myCtrl.userComments">
 *         <b>{{userComment.name}}</b>:
 *         <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span>
 *         <br>
 *       </div>
 *     </div>
 *   </div>
 * </file>
 *
 * <file name="script.js">
 *   angular.module('mySceApp', ['ngSanitize'])
 *     .controller('AppController', ['$http', '$templateCache', '$sce',
 *       function AppController($http, $templateCache, $sce) {
 *         var self = this;
 *         $http.get('test_data.json', {cache: $templateCache}).then(function(response) {
 *           self.userComments = response.data;
 *         });
 *         self.explicitlyTrustedHtml = $sce.trustAsHtml(
 *             '<span onmouseover="this.textContent=&quot;Explicitly trusted HTML bypasses ' +
 *             'sanitization.&quot;">Hover over this text.</span>');
 *       }]);
 * </file>
 *
 * <file name="test_data.json">
 * [
 *   { "name": "Alice",
 *     "htmlComment":
 *         "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>"
 *   },
 *   { "name": "Bob",
 *     "htmlComment": "<i>Yes!</i>  Am I the only other one?"
 *   }
 * ]
 * </file>
 *
 * <file name="protractor.js" type="protractor">
 *   describe('SCE doc demo', function() {
 *     it('should sanitize untrusted values', function() {
 *       expect(element.all(by.css('.htmlComment')).first().getAttribute('innerHTML'))
 *           .toBe('<span>Is <i>anyone</i> reading this?</span>');
 *     });
 *
 *     it('should NOT sanitize explicitly trusted values', function() {
 *       expect(element(by.id('explicitlyTrustedHtml')).getAttribute('innerHTML')).toBe(
 *           '<span onmouseover="this.textContent=&quot;Explicitly trusted HTML bypasses ' +
 *           'sanitization.&quot;">Hover over this text.</span>');
 *     });
 *   });
 * </file>
 * </example>
 *
 *
 *
 * ## Can I disable SCE completely?
 *
 * Yes, you can.  However, this is strongly discouraged.  SCE gives you a lot of security benefits
 * for little coding overhead.  It will be much harder to take an SCE disabled application and
 * either secure it on your own or enable SCE at a later stage.  It might make sense to disable SCE
 * for cases where you have a lot of existing code that was written before SCE was introduced and
 * you're migrating them a module at a time. Also do note that this is an app-wide setting, so if
 * you are writing a library, you will cause security bugs applications using it.
 *
 * That said, here's how you can completely disable SCE:
 *
 * ```
 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
 *   // Completely disable SCE.  For demonstration purposes only!
 *   // Do not use in new projects or libraries.
 *   $sceProvider.enabled(false);
 * });
 * ```
 *
 */

function $SceProvider() {
  var enabled = true;

  /**
   * @ngdoc method
   * @name $sceProvider#enabled
   * @kind function
   *
   * @param {boolean=} value If provided, then enables/disables SCE application-wide.
   * @return {boolean} True if SCE is enabled, false otherwise.
   *
   * @description
   * Enables/disables SCE and returns the current value.
   */
  this.enabled = function(value) {
    if (arguments.length) {
      enabled = !!value;
    }
    return enabled;
  };


  /* Design notes on the default implementation for SCE.
   *
   * The API contract for the SCE delegate
   * -------------------------------------
   * The SCE delegate object must provide the following 3 methods:
   *
   * - trustAs(contextEnum, value)
   *     This method is used to tell the SCE service that the provided value is OK to use in the
   *     contexts specified by contextEnum.  It must return an object that will be accepted by
   *     getTrusted() for a compatible contextEnum and return this value.
   *
   * - valueOf(value)
   *     For values that were not produced by trustAs(), return them as is.  For values that were
   *     produced by trustAs(), return the corresponding input value to trustAs.  Basically, if
   *     trustAs is wrapping the given values into some type, this operation unwraps it when given
   *     such a value.
   *
   * - getTrusted(contextEnum, value)
   *     This function should return the value that is safe to use in the context specified by
   *     contextEnum or throw and exception otherwise.
   *
   * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be
   * opaque or wrapped in some holder object.  That happens to be an implementation detail.  For
   * instance, an implementation could maintain a registry of all trusted objects by context.  In
   * such a case, trustAs() would return the same object that was passed in.  getTrusted() would
   * return the same object passed in if it was found in the registry under a compatible context or
   * throw an exception otherwise.  An implementation might only wrap values some of the time based
   * on some criteria.  getTrusted() might return a value and not throw an exception for special
   * constants or objects even if not wrapped.  All such implementations fulfill this contract.
   *
   *
   * A note on the inheritance model for SCE contexts
   * ------------------------------------------------
   * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types.  This
   * is purely an implementation details.
   *
   * The contract is simply this:
   *
   *     getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value)
   *     will also succeed.
   *
   * Inheritance happens to capture this in a natural way. In some future, we may not use
   * inheritance anymore. That is OK because no code outside of sce.js and sceSpecs.js would need to
   * be aware of this detail.
   */

  this.$get = ['$parse', '$sceDelegate', function(
                $parse,   $sceDelegate) {
    // Support: IE 9-11 only
    // Prereq: Ensure that we're not running in IE<11 quirks mode.  In that mode, IE < 11 allow
    // the "expression(javascript expression)" syntax which is insecure.
    if (enabled && msie < 8) {
      throw $sceMinErr('iequirks',
        'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' +
        'mode.  You can fix this by adding the text <!doctype html> to the top of your HTML ' +
        'document.  See http://docs.angularjs.org/api/ng.$sce for more information.');
    }

    var sce = shallowCopy(SCE_CONTEXTS);

    /**
     * @ngdoc method
     * @name $sce#isEnabled
     * @kind function
     *
     * @return {Boolean} True if SCE is enabled, false otherwise.  If you want to set the value, you
     *     have to do it at module config time on {@link ng.$sceProvider $sceProvider}.
     *
     * @description
     * Returns a boolean indicating if SCE is enabled.
     */
    sce.isEnabled = function() {
      return enabled;
    };
    sce.trustAs = $sceDelegate.trustAs;
    sce.getTrusted = $sceDelegate.getTrusted;
    sce.valueOf = $sceDelegate.valueOf;

    if (!enabled) {
      sce.trustAs = sce.getTrusted = function(type, value) { return value; };
      sce.valueOf = identity;
    }

    /**
     * @ngdoc method
     * @name $sce#parseAs
     *
     * @description
     * Converts AngularJS {@link guide/expression expression} into a function.  This is like {@link
     * ng.$parse $parse} and is identical when the expression is a literal constant.  Otherwise, it
     * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*,
     * *result*)}
     *
     * @param {string} type The SCE context in which this result will be used.
     * @param {string} expression String expression to compile.
     * @return {function(context, locals)} A function which represents the compiled expression:
     *
     *    * `context` – `{object}` – an object against which any expressions embedded in the
     *      strings are evaluated against (typically a scope object).
     *    * `locals` – `{object=}` – local variables context object, useful for overriding values
     *      in `context`.
     */
    sce.parseAs = function sceParseAs(type, expr) {
      var parsed = $parse(expr);
      if (parsed.literal && parsed.constant) {
        return parsed;
      } else {
        return $parse(expr, function(value) {
          return sce.getTrusted(type, value);
        });
      }
    };

    /**
     * @ngdoc method
     * @name $sce#trustAs
     *
     * @description
     * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, returns a
     * wrapped object that represents your value, and the trust you have in its safety for the given
     * context. AngularJS can then use that value as-is in bindings of the specified secure context.
     * This is used in bindings for `ng-bind-html`, `ng-include`, and most `src` attribute
     * interpolations. See {@link ng.$sce $sce} for strict contextual escaping.
     *
     * @param {string} type The context in which this value is safe for use, e.g. `$sce.URL`,
     *     `$sce.RESOURCE_URL`, `$sce.HTML`, `$sce.JS` or `$sce.CSS`.
     *
     * @param {*} value The value that that should be considered trusted.
     * @return {*} A wrapped version of value that can be used as a trusted variant of your `value`
     *     in the context you specified.
     */

    /**
     * @ngdoc method
     * @name $sce#trustAsHtml
     *
     * @description
     * Shorthand method.  `$sce.trustAsHtml(value)` →
     *     {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`}
     *
     * @param {*} value The value to mark as trusted for `$sce.HTML` context.
     * @return {*} A wrapped version of value that can be used as a trusted variant of your `value`
     *     in `$sce.HTML` context (like `ng-bind-html`).
     */

    /**
     * @ngdoc method
     * @name $sce#trustAsCss
     *
     * @description
     * Shorthand method.  `$sce.trustAsCss(value)` →
     *     {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.CSS, value)`}
     *
     * @param {*} value The value to mark as trusted for `$sce.CSS` context.
     * @return {*} A wrapped version of value that can be used as a trusted variant
     *     of your `value` in `$sce.CSS` context. This context is currently unused, so there are
     *     almost no reasons to use this function so far.
     */

    /**
     * @ngdoc method
     * @name $sce#trustAsUrl
     *
     * @description
     * Shorthand method.  `$sce.trustAsUrl(value)` →
     *     {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`}
     *
     * @param {*} value The value to mark as trusted for `$sce.URL` context.
     * @return {*} A wrapped version of value that can be used as a trusted variant of your `value`
     *     in `$sce.URL` context. That context is currently unused, so there are almost no reasons
     *     to use this function so far.
     */

    /**
     * @ngdoc method
     * @name $sce#trustAsResourceUrl
     *
     * @description
     * Shorthand method.  `$sce.trustAsResourceUrl(value)` →
     *     {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`}
     *
     * @param {*} value The value to mark as trusted for `$sce.RESOURCE_URL` context.
     * @return {*} A wrapped version of value that can be used as a trusted variant of your `value`
     *     in `$sce.RESOURCE_URL` context (template URLs in `ng-include`, most `src` attribute
     *     bindings, ...)
     */

    /**
     * @ngdoc method
     * @name $sce#trustAsJs
     *
     * @description
     * Shorthand method.  `$sce.trustAsJs(value)` →
     *     {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`}
     *
     * @param {*} value The value to mark as trusted for `$sce.JS` context.
     * @return {*} A wrapped version of value that can be used as a trusted variant of your `value`
     *     in `$sce.JS` context. That context is currently unused, so there are almost no reasons to
     *     use this function so far.
     */

    /**
     * @ngdoc method
     * @name $sce#getTrusted
     *
     * @description
     * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}.  As such,
     * takes any input, and either returns a value that's safe to use in the specified context,
     * or throws an exception. This function is aware of trusted values created by the `trustAs`
     * function and its shorthands, and when contexts are appropriate, returns the unwrapped value
     * as-is. Finally, this function can also throw when there is no way to turn `maybeTrusted` in a
     * safe value (e.g., no sanitization is available or possible.)
     *
     * @param {string} type The context in which this value is to be used.
     * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs
     *     `$sce.trustAs`} call, or anything else (which will not be considered trusted.)
     * @return {*} A version of the value that's safe to use in the given context, or throws an
     *     exception if this is impossible.
     */

    /**
     * @ngdoc method
     * @name $sce#getTrustedHtml
     *
     * @description
     * Shorthand method.  `$sce.getTrustedHtml(value)` →
     *     {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`}
     *
     * @param {*} value The value to pass to `$sce.getTrusted`.
     * @return {*} The return value of `$sce.getTrusted($sce.HTML, value)`
     */

    /**
     * @ngdoc method
     * @name $sce#getTrustedCss
     *
     * @description
     * Shorthand method.  `$sce.getTrustedCss(value)` →
     *     {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`}
     *
     * @param {*} value The value to pass to `$sce.getTrusted`.
     * @return {*} The return value of `$sce.getTrusted($sce.CSS, value)`
     */

    /**
     * @ngdoc method
     * @name $sce#getTrustedUrl
     *
     * @description
     * Shorthand method.  `$sce.getTrustedUrl(value)` →
     *     {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`}
     *
     * @param {*} value The value to pass to `$sce.getTrusted`.
     * @return {*} The return value of `$sce.getTrusted($sce.URL, value)`
     */

    /**
     * @ngdoc method
     * @name $sce#getTrustedResourceUrl
     *
     * @description
     * Shorthand method.  `$sce.getTrustedResourceUrl(value)` →
     *     {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`}
     *
     * @param {*} value The value to pass to `$sceDelegate.getTrusted`.
     * @return {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)`
     */

    /**
     * @ngdoc method
     * @name $sce#getTrustedJs
     *
     * @description
     * Shorthand method.  `$sce.getTrustedJs(value)` →
     *     {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`}
     *
     * @param {*} value The value to pass to `$sce.getTrusted`.
     * @return {*} The return value of `$sce.getTrusted($sce.JS, value)`
     */

    /**
     * @ngdoc method
     * @name $sce#parseAsHtml
     *
     * @description
     * Shorthand method.  `$sce.parseAsHtml(expression string)` →
     *     {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`}
     *
     * @param {string} expression String expression to compile.
     * @return {function(context, locals)} A function which represents the compiled expression:
     *
     *    * `context` – `{object}` – an object against which any expressions embedded in the
     *      strings are evaluated against (typically a scope object).
     *    * `locals` – `{object=}` – local variables context object, useful for overriding values
     *      in `context`.
     */

    /**
     * @ngdoc method
     * @name $sce#parseAsCss
     *
     * @description
     * Shorthand method.  `$sce.parseAsCss(value)` →
     *     {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`}
     *
     * @param {string} expression String expression to compile.
     * @return {function(context, locals)} A function which represents the compiled expression:
     *
     *    * `context` – `{object}` – an object against which any expressions embedded in the
     *      strings are evaluated against (typically a scope object).
     *    * `locals` – `{object=}` – local variables context object, useful for overriding values
     *      in `context`.
     */

    /**
     * @ngdoc method
     * @name $sce#parseAsUrl
     *
     * @description
     * Shorthand method.  `$sce.parseAsUrl(value)` →
     *     {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`}
     *
     * @param {string} expression String expression to compile.
     * @return {function(context, locals)} A function which represents the compiled expression:
     *
     *    * `context` – `{object}` – an object against which any expressions embedded in the
     *      strings are evaluated against (typically a scope object).
     *    * `locals` – `{object=}` – local variables context object, useful for overriding values
     *      in `context`.
     */

    /**
     * @ngdoc method
     * @name $sce#parseAsResourceUrl
     *
     * @description
     * Shorthand method.  `$sce.parseAsResourceUrl(value)` →
     *     {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`}
     *
     * @param {string} expression String expression to compile.
     * @return {function(context, locals)} A function which represents the compiled expression:
     *
     *    * `context` – `{object}` – an object against which any expressions embedded in the
     *      strings are evaluated against (typically a scope object).
     *    * `locals` – `{object=}` – local variables context object, useful for overriding values
     *      in `context`.
     */

    /**
     * @ngdoc method
     * @name $sce#parseAsJs
     *
     * @description
     * Shorthand method.  `$sce.parseAsJs(value)` →
     *     {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`}
     *
     * @param {string} expression String expression to compile.
     * @return {function(context, locals)} A function which represents the compiled expression:
     *
     *    * `context` – `{object}` – an object against which any expressions embedded in the
     *      strings are evaluated against (typically a scope object).
     *    * `locals` – `{object=}` – local variables context object, useful for overriding values
     *      in `context`.
     */

    // Shorthand delegations.
    var parse = sce.parseAs,
        getTrusted = sce.getTrusted,
        trustAs = sce.trustAs;

    forEach(SCE_CONTEXTS, function(enumValue, name) {
      var lName = lowercase(name);
      sce[snakeToCamel('parse_as_' + lName)] = function(expr) {
        return parse(enumValue, expr);
      };
      sce[snakeToCamel('get_trusted_' + lName)] = function(value) {
        return getTrusted(enumValue, value);
      };
      sce[snakeToCamel('trust_as_' + lName)] = function(value) {
        return trustAs(enumValue, value);
      };
    });

    return sce;
  }];
}

/* exported $SnifferProvider */

/**
 * !!! This is an undocumented "private" service !!!
 *
 * @name $sniffer
 * @requires $window
 * @requires $document
 * @this
 *
 * @property {boolean} history Does the browser support html5 history api ?
 * @property {boolean} transitions Does the browser support CSS transition events ?
 * @property {boolean} animations Does the browser support CSS animation events ?
 *
 * @description
 * This is very simple implementation of testing browser's features.
 */
function $SnifferProvider() {
  this.$get = ['$window', '$document', function($window, $document) {
    var eventSupport = {},
        // Chrome Packaged Apps are not allowed to access `history.pushState`.
        // If not sandboxed, they can be detected by the presence of `chrome.app.runtime`
        // (see https://developer.chrome.com/apps/api_index). If sandboxed, they can be detected by
        // the presence of an extension runtime ID and the absence of other Chrome runtime APIs
        // (see https://developer.chrome.com/apps/manifest/sandbox).
        // (NW.js apps have access to Chrome APIs, but do support `history`.)
        isNw = $window.nw && $window.nw.process,
        isChromePackagedApp =
            !isNw &&
            $window.chrome &&
            ($window.chrome.app && $window.chrome.app.runtime ||
                !$window.chrome.app && $window.chrome.runtime && $window.chrome.runtime.id),
        hasHistoryPushState = !isChromePackagedApp && $window.history && $window.history.pushState,
        android =
          toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]),
        boxee = /Boxee/i.test(($window.navigator || {}).userAgent),
        document = $document[0] || {},
        bodyStyle = document.body && document.body.style,
        transitions = false,
        animations = false;

    if (bodyStyle) {
      // Support: Android <5, Blackberry Browser 10, default Chrome in Android 4.4.x
      // Mentioned browsers need a -webkit- prefix for transitions & animations.
      transitions = !!('transition' in bodyStyle || 'webkitTransition' in bodyStyle);
      animations = !!('animation' in bodyStyle || 'webkitAnimation' in bodyStyle);
    }


    return {
      // Android has history.pushState, but it does not update location correctly
      // so let's not use the history API at all.
      // http://code.google.com/p/android/issues/detail?id=17471
      // https://github.com/angular/angular.js/issues/904

      // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has
      // so let's not use the history API also
      // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined
      history: !!(hasHistoryPushState && !(android < 4) && !boxee),
      hasEvent: function(event) {
        // Support: IE 9-11 only
        // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
        // it. In particular the event is not fired when backspace or delete key are pressed or
        // when cut operation is performed.
        // IE10+ implements 'input' event but it erroneously fires under various situations,
        // e.g. when placeholder changes, or a form is focused.
        if (event === 'input' && msie) return false;

        if (isUndefined(eventSupport[event])) {
          var divElm = document.createElement('div');
          eventSupport[event] = 'on' + event in divElm;
        }

        return eventSupport[event];
      },
      csp: csp(),
      transitions: transitions,
      animations: animations,
      android: android
    };
  }];
}

/**
 * ! This is a private undocumented service !
 *
 * @name $$taskTrackerFactory
 * @description
 * A function to create `TaskTracker` instances.
 *
 * A `TaskTracker` can keep track of pending tasks (grouped by type) and can notify interested
 * parties when all pending tasks (or tasks of a specific type) have been completed.
 *
 * @param {$log} log - A logger instance (such as `$log`). Used to log error during callback
 *     execution.
 *
 * @this
 */
function $$TaskTrackerFactoryProvider() {
  this.$get = valueFn(function(log) { return new TaskTracker(log); });
}

function TaskTracker(log) {
  var self = this;
  var taskCounts = {};
  var taskCallbacks = [];

  var ALL_TASKS_TYPE = self.ALL_TASKS_TYPE = '$$all$$';
  var DEFAULT_TASK_TYPE = self.DEFAULT_TASK_TYPE = '$$default$$';

  /**
   * Execute the specified function and decrement the appropriate `taskCounts` counter.
   * If the counter reaches 0, all corresponding `taskCallbacks` are executed.
   *
   * @param {Function} fn - The function to execute.
   * @param {string=} [taskType=DEFAULT_TASK_TYPE] - The type of task that is being completed.
   */
  self.completeTask = completeTask;

  /**
   * Increase the task count for the specified task type (or the default task type if non is
   * specified).
   *
   * @param {string=} [taskType=DEFAULT_TASK_TYPE] - The type of task whose count will be increased.
   */
  self.incTaskCount = incTaskCount;

  /**
   * Execute the specified callback when all pending tasks have been completed.
   *
   * If there are no pending tasks, the callback is executed immediately. You can optionally limit
   * the tasks that will be waited for to a specific type, by passing a `taskType`.
   *
   * @param {function} callback - The function to call when there are no pending tasks.
   * @param {string=} [taskType=ALL_TASKS_TYPE] - The type of tasks that will be waited for.
   */
  self.notifyWhenNoPendingTasks = notifyWhenNoPendingTasks;

  function completeTask(fn, taskType) {
    taskType = taskType || DEFAULT_TASK_TYPE;

    try {
      fn();
    } finally {
      decTaskCount(taskType);

      var countForType = taskCounts[taskType];
      var countForAll = taskCounts[ALL_TASKS_TYPE];

      // If at least one of the queues (`ALL_TASKS_TYPE` or `taskType`) is empty, run callbacks.
      if (!countForAll || !countForType) {
        var getNextCallback = !countForAll ? getLastCallback : getLastCallbackForType;
        var nextCb;

        while ((nextCb = getNextCallback(taskType))) {
          try {
            nextCb();
          } catch (e) {
            log.error(e);
          }
        }
      }
    }
  }

  function decTaskCount(taskType) {
    taskType = taskType || DEFAULT_TASK_TYPE;
    if (taskCounts[taskType]) {
      taskCounts[taskType]--;
      taskCounts[ALL_TASKS_TYPE]--;
    }
  }

  function getLastCallback() {
    var cbInfo = taskCallbacks.pop();
    return cbInfo && cbInfo.cb;
  }

  function getLastCallbackForType(taskType) {
    for (var i = taskCallbacks.length - 1; i >= 0; --i) {
      var cbInfo = taskCallbacks[i];
      if (cbInfo.type === taskType) {
        taskCallbacks.splice(i, 1);
        return cbInfo.cb;
      }
    }
  }

  function incTaskCount(taskType) {
    taskType = taskType || DEFAULT_TASK_TYPE;
    taskCounts[taskType] = (taskCounts[taskType] || 0) + 1;
    taskCounts[ALL_TASKS_TYPE] = (taskCounts[ALL_TASKS_TYPE] || 0) + 1;
  }

  function notifyWhenNoPendingTasks(callback, taskType) {
    taskType = taskType || ALL_TASKS_TYPE;
    if (!taskCounts[taskType]) {
      callback();
    } else {
      taskCallbacks.push({type: taskType, cb: callback});
    }
  }
}

var $templateRequestMinErr = minErr('$templateRequest');

/**
 * @ngdoc provider
 * @name $templateRequestProvider
 * @this
 *
 * @description
 * Used to configure the options passed to the {@link $http} service when making a template request.
 *
 * For example, it can be used for specifying the "Accept" header that is sent to the server, when
 * requesting a template.
 */
function $TemplateRequestProvider() {

  var httpOptions;

  /**
   * @ngdoc method
   * @name $templateRequestProvider#httpOptions
   * @description
   * The options to be passed to the {@link $http} service when making the request.
   * You can use this to override options such as the "Accept" header for template requests.
   *
   * The {@link $templateRequest} will set the `cache` and the `transformResponse` properties of the
   * options if not overridden here.
   *
   * @param {string=} value new value for the {@link $http} options.
   * @returns {string|self} Returns the {@link $http} options when used as getter and self if used as setter.
   */
  this.httpOptions = function(val) {
    if (val) {
      httpOptions = val;
      return this;
    }
    return httpOptions;
  };

  /**
   * @ngdoc service
   * @name $templateRequest
   *
   * @description
   * The `$templateRequest` service runs security checks then downloads the provided template using
   * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request
   * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the
   * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the
   * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted
   * when `tpl` is of type string and `$templateCache` has the matching entry.
   *
   * If you want to pass custom options to the `$http` service, such as setting the Accept header you
   * can configure this via {@link $templateRequestProvider#httpOptions}.
   *
   * `$templateRequest` is used internally by {@link $compile}, {@link ngRoute.$route}, and directives such
   * as {@link ngInclude} to download and cache templates.
   *
   * 3rd party modules should use `$templateRequest` if their services or directives are loading
   * templates.
   *
   * @param {string|TrustedResourceUrl} tpl The HTTP request template URL
   * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty
   *
   * @return {Promise} a promise for the HTTP response data of the given URL.
   *
   * @property {number} totalPendingRequests total amount of pending template requests being downloaded.
   */
  this.$get = ['$exceptionHandler', '$templateCache', '$http', '$q', '$sce',
    function($exceptionHandler, $templateCache, $http, $q, $sce) {

      function handleRequestFn(tpl, ignoreRequestError) {
        handleRequestFn.totalPendingRequests++;

        // We consider the template cache holds only trusted templates, so
        // there's no need to go through adding the template again to the trusted
        // resources for keys that already are included in there. This also makes
        // AngularJS accept any script directive, no matter its name. However, we
        // still need to unwrap trusted types.
        if (!isString(tpl) || isUndefined($templateCache.get(tpl))) {
          tpl = $sce.getTrustedResourceUrl(tpl);
        }

        var transformResponse = $http.defaults && $http.defaults.transformResponse;

        if (isArray(transformResponse)) {
          transformResponse = transformResponse.filter(function(transformer) {
            return transformer !== defaultHttpResponseTransform;
          });
        } else if (transformResponse === defaultHttpResponseTransform) {
          transformResponse = null;
        }

        return $http.get(tpl, extend({
            cache: $templateCache,
            transformResponse: transformResponse
          }, httpOptions))
          .finally(function() {
            handleRequestFn.totalPendingRequests--;
          })
          .then(function(response) {
            return $templateCache.put(tpl, response.data);
          }, handleError);

        function handleError(resp) {
          if (!ignoreRequestError) {
            resp = $templateRequestMinErr('tpload',
                'Failed to load template: {0} (HTTP status: {1} {2})',
                tpl, resp.status, resp.statusText);

            $exceptionHandler(resp);
          }

          return $q.reject(resp);
        }
      }

      handleRequestFn.totalPendingRequests = 0;

      return handleRequestFn;
    }
  ];
}

/** @this */
function $$TestabilityProvider() {
  this.$get = ['$rootScope', '$browser', '$location',
       function($rootScope,   $browser,   $location) {

    /**
     * @name $testability
     *
     * @description
     * The private $$testability service provides a collection of methods for use when debugging
     * or by automated test and debugging tools.
     */
    var testability = {};

    /**
     * @name $$testability#findBindings
     *
     * @description
     * Returns an array of elements that are bound (via ng-bind or {{}})
     * to expressions matching the input.
     *
     * @param {Element} element The element root to search from.
     * @param {string} expression The binding expression to match.
     * @param {boolean} opt_exactMatch If true, only returns exact matches
     *     for the expression. Filters and whitespace are ignored.
     */
    testability.findBindings = function(element, expression, opt_exactMatch) {
      var bindings = element.getElementsByClassName('ng-binding');
      var matches = [];
      forEach(bindings, function(binding) {
        var dataBinding = angular.element(binding).data('$binding');
        if (dataBinding) {
          forEach(dataBinding, function(bindingName) {
            if (opt_exactMatch) {
              var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)');
              if (matcher.test(bindingName)) {
                matches.push(binding);
              }
            } else {
              if (bindingName.indexOf(expression) !== -1) {
                matches.push(binding);
              }
            }
          });
        }
      });
      return matches;
    };

    /**
     * @name $$testability#findModels
     *
     * @description
     * Returns an array of elements that are two-way found via ng-model to
     * expressions matching the input.
     *
     * @param {Element} element The element root to search from.
     * @param {string} expression The model expression to match.
     * @param {boolean} opt_exactMatch If true, only returns exact matches
     *     for the expression.
     */
    testability.findModels = function(element, expression, opt_exactMatch) {
      var prefixes = ['ng-', 'data-ng-', 'ng\\:'];
      for (var p = 0; p < prefixes.length; ++p) {
        var attributeEquals = opt_exactMatch ? '=' : '*=';
        var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]';
        var elements = element.querySelectorAll(selector);
        if (elements.length) {
          return elements;
        }
      }
    };

    /**
     * @name $$testability#getLocation
     *
     * @description
     * Shortcut for getting the location in a browser agnostic way. Returns
     *     the path, search, and hash. (e.g. /path?a=b#hash)
     */
    testability.getLocation = function() {
      return $location.url();
    };

    /**
     * @name $$testability#setLocation
     *
     * @description
     * Shortcut for navigating to a location without doing a full page reload.
     *
     * @param {string} url The location url (path, search and hash,
     *     e.g. /path?a=b#hash) to go to.
     */
    testability.setLocation = function(url) {
      if (url !== $location.url()) {
        $location.url(url);
        $rootScope.$digest();
      }
    };

    /**
     * @name $$testability#whenStable
     *
     * @description
     * Calls the callback when all pending tasks are completed.
     *
     * Types of tasks waited for include:
     * - Pending timeouts (via {@link $timeout}).
     * - Pending HTTP requests (via {@link $http}).
     * - In-progress route transitions (via {@link $route}).
     * - Pending tasks scheduled via {@link $rootScope#$applyAsync}.
     * - Pending tasks scheduled via {@link $rootScope#$evalAsync}.
     *   These include tasks scheduled via `$evalAsync()` indirectly (such as {@link $q} promises).
     *
     * @param {function} callback
     */
    testability.whenStable = function(callback) {
      $browser.notifyWhenNoOutstandingRequests(callback);
    };

    return testability;
  }];
}

var $timeoutMinErr = minErr('$timeout');

/** @this */
function $TimeoutProvider() {
  this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler',
       function($rootScope,   $browser,   $q,   $$q,   $exceptionHandler) {

    var deferreds = {};


    /**
     * @ngdoc service
     * @name $timeout
     *
     * @description
     * AngularJS's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch
     * block and delegates any exceptions to
     * {@link ng.$exceptionHandler $exceptionHandler} service.
     *
     * The return value of calling `$timeout` is a promise, which will be resolved when
     * the delay has passed and the timeout function, if provided, is executed.
     *
     * To cancel a timeout request, call `$timeout.cancel(promise)`.
     *
     * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to
     * synchronously flush the queue of deferred functions.
     *
     * If you only want a promise that will be resolved after some specified delay
     * then you can call `$timeout` without the `fn` function.
     *
     * @param {function()=} fn A function, whose execution should be delayed.
     * @param {number=} [delay=0] Delay in milliseconds.
     * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
     *   will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
     * @param {...*=} Pass additional parameters to the executed function.
     * @returns {Promise} Promise that will be resolved when the timeout is reached. The promise
     *   will be resolved with the return value of the `fn` function.
     *
     */
    function timeout(fn, delay, invokeApply) {
      if (!isFunction(fn)) {
        invokeApply = delay;
        delay = fn;
        fn = noop;
      }

      var args = sliceArgs(arguments, 3),
          skipApply = (isDefined(invokeApply) && !invokeApply),
          deferred = (skipApply ? $$q : $q).defer(),
          promise = deferred.promise,
          timeoutId;

      timeoutId = $browser.defer(function() {
        try {
          deferred.resolve(fn.apply(null, args));
        } catch (e) {
          deferred.reject(e);
          $exceptionHandler(e);
        } finally {
          delete deferreds[promise.$$timeoutId];
        }

        if (!skipApply) $rootScope.$apply();
      }, delay, '$timeout');

      promise.$$timeoutId = timeoutId;
      deferreds[timeoutId] = deferred;

      return promise;
    }


    /**
     * @ngdoc method
     * @name $timeout#cancel
     *
     * @description
     * Cancels a task associated with the `promise`. As a result of this, the promise will be
     * resolved with a rejection.
     *
     * @param {Promise=} promise Promise returned by the `$timeout` function.
     * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
     *   canceled.
     */
    timeout.cancel = function(promise) {
      if (!promise) return false;

      if (!promise.hasOwnProperty('$$timeoutId')) {
        throw $timeoutMinErr('badprom',
            '`$timeout.cancel()` called with a promise that was not generated by `$timeout()`.');
      }

      if (!deferreds.hasOwnProperty(promise.$$timeoutId)) return false;

      var id = promise.$$timeoutId;
      var deferred = deferreds[id];

      // Timeout cancels should not report an unhandled promise.
      markQExceptionHandled(deferred.promise);
      deferred.reject('canceled');
      delete deferreds[id];

      return $browser.defer.cancel(id);
    };

    return timeout;
  }];
}

// NOTE:  The usage of window and document instead of $window and $document here is
// deliberate.  This service depends on the specific behavior of anchor nodes created by the
// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and
// cause us to break tests.  In addition, when the browser resolves a URL for XHR, it
// doesn't know about mocked locations and resolves URLs to the real document - which is
// exactly the behavior needed here.  There is little value is mocking these out for this
// service.
var urlParsingNode = window.document.createElement('a');
var originUrl = urlResolve(window.location.href);
var baseUrlParsingNode;

urlParsingNode.href = 'http://[::1]';

// Support: IE 9-11 only, Edge 16-17 only (fixed in 18 Preview)
// IE/Edge don't wrap IPv6 addresses' hostnames in square brackets
// when parsed out of an anchor element.
var ipv6InBrackets = urlParsingNode.hostname === '[::1]';

/**
 *
 * Implementation Notes for non-IE browsers
 * ----------------------------------------
 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM,
 * results both in the normalizing and parsing of the URL.  Normalizing means that a relative
 * URL will be resolved into an absolute URL in the context of the application document.
 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related
 * properties are all populated to reflect the normalized URL.  This approach has wide
 * compatibility - Safari 1+, Mozilla 1+ etc.  See
 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
 *
 * Implementation Notes for IE
 * ---------------------------
 * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other
 * browsers.  However, the parsed components will not be set if the URL assigned did not specify
 * them.  (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.)  We
 * work around that by performing the parsing in a 2nd step by taking a previously normalized
 * URL (e.g. by assigning to a.href) and assigning it a.href again.  This correctly populates the
 * properties such as protocol, hostname, port, etc.
 *
 * References:
 *   http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement
 *   http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
 *   http://url.spec.whatwg.org/#urlutils
 *   https://github.com/angular/angular.js/pull/2902
 *   http://james.padolsey.com/javascript/parsing-urls-with-the-dom/
 *
 * @kind function
 * @param {string|object} url The URL to be parsed. If `url` is not a string, it will be returned
 *     unchanged.
 * @description Normalizes and parses a URL.
 * @returns {object} Returns the normalized URL as a dictionary.
 *
 *   | member name   | Description                                                            |
 *   |---------------|------------------------------------------------------------------------|
 *   | href          | A normalized version of the provided URL if it was not an absolute URL |
 *   | protocol      | The protocol without the trailing colon                                |
 *   | host          | The host and port (if the port is non-default) of the normalizedUrl    |
 *   | search        | The search params, minus the question mark                             |
 *   | hash          | The hash string, minus the hash symbol                                 |
 *   | hostname      | The hostname                                                           |
 *   | port          | The port, without ":"                                                  |
 *   | pathname      | The pathname, beginning with "/"                                       |
 *
 */
function urlResolve(url) {
  if (!isString(url)) return url;

  var href = url;

  // Support: IE 9-11 only
  if (msie) {
    // Normalize before parse.  Refer Implementation Notes on why this is
    // done in two steps on IE.
    urlParsingNode.setAttribute('href', href);
    href = urlParsingNode.href;
  }

  urlParsingNode.setAttribute('href', href);

  var hostname = urlParsingNode.hostname;

  if (!ipv6InBrackets && hostname.indexOf(':') > -1) {
    hostname = '[' + hostname + ']';
  }

  return {
    href: urlParsingNode.href,
    protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '',
    host: urlParsingNode.host,
    search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '',
    hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '',
    hostname: hostname,
    port: urlParsingNode.port,
    pathname: (urlParsingNode.pathname.charAt(0) === '/')
      ? urlParsingNode.pathname
      : '/' + urlParsingNode.pathname
  };
}

/**
 * Parse a request URL and determine whether this is a same-origin request as the application
 * document.
 *
 * @param {string|object} requestUrl The url of the request as a string that will be resolved
 * or a parsed URL object.
 * @returns {boolean} Whether the request is for the same origin as the application document.
 */
function urlIsSameOrigin(requestUrl) {
  return urlsAreSameOrigin(requestUrl, originUrl);
}

/**
 * Parse a request URL and determine whether it is same-origin as the current document base URL.
 *
 * Note: The base URL is usually the same as the document location (`location.href`) but can
 * be overriden by using the `<base>` tag.
 *
 * @param {string|object} requestUrl The url of the request as a string that will be resolved
 * or a parsed URL object.
 * @returns {boolean} Whether the URL is same-origin as the document base URL.
 */
function urlIsSameOriginAsBaseUrl(requestUrl) {
  return urlsAreSameOrigin(requestUrl, getBaseUrl());
}

/**
 * Create a function that can check a URL's origin against a list of allowed/trusted origins.
 * The current location's origin is implicitly trusted.
 *
 * @param {string[]} trustedOriginUrls - A list of URLs (strings), whose origins are trusted.
 *
 * @returns {Function} - A function that receives a URL (string or parsed URL object) and returns
 *     whether it is of an allowed origin.
 */
function urlIsAllowedOriginFactory(trustedOriginUrls) {
  var parsedAllowedOriginUrls = [originUrl].concat(trustedOriginUrls.map(urlResolve));

  /**
   * Check whether the specified URL (string or parsed URL object) has an origin that is allowed
   * based on a list of trusted-origin URLs. The current location's origin is implicitly
   * trusted.
   *
   * @param {string|Object} requestUrl - The URL to be checked (provided as a string that will be
   *     resolved or a parsed URL object).
   *
   * @returns {boolean} - Whether the specified URL is of an allowed origin.
   */
  return function urlIsAllowedOrigin(requestUrl) {
    var parsedUrl = urlResolve(requestUrl);
    return parsedAllowedOriginUrls.some(urlsAreSameOrigin.bind(null, parsedUrl));
  };
}

/**
 * Determine if two URLs share the same origin.
 *
 * @param {string|Object} url1 - First URL to compare as a string or a normalized URL in the form of
 *     a dictionary object returned by `urlResolve()`.
 * @param {string|object} url2 - Second URL to compare as a string or a normalized URL in the form
 *     of a dictionary object returned by `urlResolve()`.
 *
 * @returns {boolean} - True if both URLs have the same origin, and false otherwise.
 */
function urlsAreSameOrigin(url1, url2) {
  url1 = urlResolve(url1);
  url2 = urlResolve(url2);

  return (url1.protocol === url2.protocol &&
          url1.host === url2.host);
}

/**
 * Returns the current document base URL.
 * @returns {string}
 */
function getBaseUrl() {
  if (window.document.baseURI) {
    return window.document.baseURI;
  }

  // `document.baseURI` is available everywhere except IE
  if (!baseUrlParsingNode) {
    baseUrlParsingNode = window.document.createElement('a');
    baseUrlParsingNode.href = '.';

    // Work-around for IE bug described in Implementation Notes. The fix in `urlResolve()` is not
    // suitable here because we need to track changes to the base URL.
    baseUrlParsingNode = baseUrlParsingNode.cloneNode(false);
  }
  return baseUrlParsingNode.href;
}

/**
 * @ngdoc service
 * @name $window
 * @this
 *
 * @description
 * A reference to the browser's `window` object. While `window`
 * is globally available in JavaScript, it causes testability problems, because
 * it is a global variable. In AngularJS we always refer to it through the
 * `$window` service, so it may be overridden, removed or mocked for testing.
 *
 * Expressions, like the one defined for the `ngClick` directive in the example
 * below, are evaluated with respect to the current scope.  Therefore, there is
 * no risk of inadvertently coding in a dependency on a global value in such an
 * expression.
 *
 * @example
   <example module="windowExample" name="window-service">
     <file name="index.html">
       <script>
         angular.module('windowExample', [])
           .controller('ExampleController', ['$scope', '$window', function($scope, $window) {
             $scope.greeting = 'Hello, World!';
             $scope.doGreeting = function(greeting) {
               $window.alert(greeting);
             };
           }]);
       </script>
       <div ng-controller="ExampleController">
         <input type="text" ng-model="greeting" aria-label="greeting" />
         <button ng-click="doGreeting(greeting)">ALERT</button>
       </div>
     </file>
     <file name="protractor.js" type="protractor">
      it('should display the greeting in the input box', function() {
       element(by.model('greeting')).sendKeys('Hello, E2E Tests');
       // If we click the button it will block the test runner
       // element(':button').click();
      });
     </file>
   </example>
 */
function $WindowProvider() {
  this.$get = valueFn(window);
}

/**
 * @name $$cookieReader
 * @requires $document
 *
 * @description
 * This is a private service for reading cookies used by $http and ngCookies
 *
 * @return {Object} a key/value map of the current cookies
 */
function $$CookieReader($document) {
  var rawDocument = $document[0] || {};
  var lastCookies = {};
  var lastCookieString = '';

  function safeGetCookie(rawDocument) {
    try {
      return rawDocument.cookie || '';
    } catch (e) {
      return '';
    }
  }

  function safeDecodeURIComponent(str) {
    try {
      return decodeURIComponent(str);
    } catch (e) {
      return str;
    }
  }

  return function() {
    var cookieArray, cookie, i, index, name;
    var currentCookieString = safeGetCookie(rawDocument);

    if (currentCookieString !== lastCookieString) {
      lastCookieString = currentCookieString;
      cookieArray = lastCookieString.split('; ');
      lastCookies = {};

      for (i = 0; i < cookieArray.length; i++) {
        cookie = cookieArray[i];
        index = cookie.indexOf('=');
        if (index > 0) { //ignore nameless cookies
          name = safeDecodeURIComponent(cookie.substring(0, index));
          // the first value that is seen for a cookie is the most
          // specific one.  values for the same cookie name that
          // follow are for less specific paths.
          if (isUndefined(lastCookies[name])) {
            lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1));
          }
        }
      }
    }
    return lastCookies;
  };
}

$$CookieReader.$inject = ['$document'];

/** @this */
function $$CookieReaderProvider() {
  this.$get = $$CookieReader;
}

/* global currencyFilter: true,
 dateFilter: true,
 filterFilter: true,
 jsonFilter: true,
 limitToFilter: true,
 lowercaseFilter: true,
 numberFilter: true,
 orderByFilter: true,
 uppercaseFilter: true,
 */

/**
 * @ngdoc provider
 * @name $filterProvider
 * @description
 *
 * Filters are just functions which transform input to an output. However filters need to be
 * Dependency Injected. To achieve this a filter definition consists of a factory function which is
 * annotated with dependencies and is responsible for creating a filter function.
 *
 * <div class="alert alert-warning">
 * **Note:** Filter names must be valid AngularJS {@link expression} identifiers, such as `uppercase` or `orderBy`.
 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
 * (`myapp_subsection_filterx`).
 * </div>
 *
 * ```js
 *   // Filter registration
 *   function MyModule($provide, $filterProvider) {
 *     // create a service to demonstrate injection (not always needed)
 *     $provide.value('greet', function(name){
 *       return 'Hello ' + name + '!';
 *     });
 *
 *     // register a filter factory which uses the
 *     // greet service to demonstrate DI.
 *     $filterProvider.register('greet', function(greet){
 *       // return the filter function which uses the greet service
 *       // to generate salutation
 *       return function(text) {
 *         // filters need to be forgiving so check input validity
 *         return text && greet(text) || text;
 *       };
 *     });
 *   }
 * ```
 *
 * The filter function is registered with the `$injector` under the filter name suffix with
 * `Filter`.
 *
 * ```js
 *   it('should be the same instance', inject(
 *     function($filterProvider) {
 *       $filterProvider.register('reverse', function(){
 *         return ...;
 *       });
 *     },
 *     function($filter, reverseFilter) {
 *       expect($filter('reverse')).toBe(reverseFilter);
 *     });
 * ```
 *
 *
 * For more information about how AngularJS filters work, and how to create your own filters, see
 * {@link guide/filter Filters} in the AngularJS Developer Guide.
 */

/**
 * @ngdoc service
 * @name $filter
 * @kind function
 * @description
 * Filters are used for formatting data displayed to the user.
 *
 * They can be used in view templates, controllers or services. AngularJS comes
 * with a collection of [built-in filters](api/ng/filter), but it is easy to
 * define your own as well.
 *
 * The general syntax in templates is as follows:
 *
 * ```html
 * {{ expression [| filter_name[:parameter_value] ... ] }}
 * ```
 *
 * @param {String} name Name of the filter function to retrieve
 * @return {Function} the filter function
 * @example
   <example name="$filter" module="filterExample">
     <file name="index.html">
       <div ng-controller="MainCtrl">
        <h3>{{ originalText }}</h3>
        <h3>{{ filteredText }}</h3>
       </div>
     </file>

     <file name="script.js">
      angular.module('filterExample', [])
      .controller('MainCtrl', function($scope, $filter) {
        $scope.originalText = 'hello';
        $scope.filteredText = $filter('uppercase')($scope.originalText);
      });
     </file>
   </example>
  */
$FilterProvider.$inject = ['$provide'];
/** @this */
function $FilterProvider($provide) {
  var suffix = 'Filter';

  /**
   * @ngdoc method
   * @name $filterProvider#register
   * @param {string|Object} name Name of the filter function, or an object map of filters where
   *    the keys are the filter names and the values are the filter factories.
   *
   *    <div class="alert alert-warning">
   *    **Note:** Filter names must be valid AngularJS {@link expression} identifiers, such as `uppercase` or `orderBy`.
   *    Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
   *    your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
   *    (`myapp_subsection_filterx`).
   *    </div>
    * @param {Function} factory If the first argument was a string, a factory function for the filter to be registered.
   * @returns {Object} Registered filter instance, or if a map of filters was provided then a map
   *    of the registered filter instances.
   */
  function register(name, factory) {
    if (isObject(name)) {
      var filters = {};
      forEach(name, function(filter, key) {
        filters[key] = register(key, filter);
      });
      return filters;
    } else {
      return $provide.factory(name + suffix, factory);
    }
  }
  this.register = register;

  this.$get = ['$injector', function($injector) {
    return function(name) {
      return $injector.get(name + suffix);
    };
  }];

  ////////////////////////////////////////

  /* global
    currencyFilter: false,
    dateFilter: false,
    filterFilter: false,
    jsonFilter: false,
    limitToFilter: false,
    lowercaseFilter: false,
    numberFilter: false,
    orderByFilter: false,
    uppercaseFilter: false
  */

  register('currency', currencyFilter);
  register('date', dateFilter);
  register('filter', filterFilter);
  register('json', jsonFilter);
  register('limitTo', limitToFilter);
  register('lowercase', lowercaseFilter);
  register('number', numberFilter);
  register('orderBy', orderByFilter);
  register('uppercase', uppercaseFilter);
}

/**
 * @ngdoc filter
 * @name filter
 * @kind function
 *
 * @description
 * Selects a subset of items from `array` and returns it as a new array.
 *
 * @param {Array} array The source array.
 * <div class="alert alert-info">
 *   **Note**: If the array contains objects that reference themselves, filtering is not possible.
 * </div>
 * @param {string|Object|function()} expression The predicate to be used for selecting items from
 *   `array`.
 *
 *   Can be one of:
 *
 *   - `string`: The string is used for matching against the contents of the `array`. All strings or
 *     objects with string properties in `array` that match this string will be returned. This also
 *     applies to nested object properties.
 *     The predicate can be negated by prefixing the string with `!`.
 *
 *   - `Object`: A pattern object can be used to filter specific properties on objects contained
 *     by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
 *     which have property `name` containing "M" and property `phone` containing "1". A special
 *     property name (`$` by default) can be used (e.g. as in `{$: "text"}`) to accept a match
 *     against any property of the object or its nested object properties. That's equivalent to the
 *     simple substring match with a `string` as described above. The special property name can be
 *     overwritten, using the `anyPropertyKey` parameter.
 *     The predicate can be negated by prefixing the string with `!`.
 *     For example `{name: "!M"}` predicate will return an array of items which have property `name`
 *     not containing "M".
 *
 *     Note that a named property will match properties on the same level only, while the special
 *     `$` property will match properties on the same level or deeper. E.g. an array item like
 *     `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but
 *     **will** be matched by `{$: 'John'}`.
 *
 *   - `function(value, index, array)`: A predicate function can be used to write arbitrary filters.
 *     The function is called for each element of the array, with the element, its index, and
 *     the entire array itself as arguments.
 *
 *     The final result is an array of those elements that the predicate returned true for.
 *
 * @param {function(actual, expected)|true|false} [comparator] Comparator which is used in
 *     determining if values retrieved using `expression` (when it is not a function) should be
 *     considered a match based on the expected value (from the filter expression) and actual
 *     value (from the object in the array).
 *
 *   Can be one of:
 *
 *   - `function(actual, expected)`:
 *     The function will be given the object value and the predicate value to compare and
 *     should return true if both values should be considered equal.
 *
 *   - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`.
 *     This is essentially strict comparison of expected and actual.
 *
 *   - `false`: A short hand for a function which will look for a substring match in a case
 *     insensitive way. Primitive values are converted to strings. Objects are not compared against
 *     primitives, unless they have a custom `toString` method (e.g. `Date` objects).
 *
 *
 *   Defaults to `false`.
 *
 * @param {string} [anyPropertyKey] The special property name that matches against any property.
 *     By default `$`.
 *
 * @example
   <example name="filter-filter">
     <file name="index.html">
       <div ng-init="friends = [{name:'John', phone:'555-1276'},
                                {name:'Mary', phone:'800-BIG-MARY'},
                                {name:'Mike', phone:'555-4321'},
                                {name:'Adam', phone:'555-5678'},
                                {name:'Julie', phone:'555-8765'},
                                {name:'Juliette', phone:'555-5678'}]"></div>

       <label>Search: <input ng-model="searchText"></label>
       <table id="searchTextResults">
         <tr><th>Name</th><th>Phone</th></tr>
         <tr ng-repeat="friend in friends | filter:searchText">
           <td>{{friend.name}}</td>
           <td>{{friend.phone}}</td>
         </tr>
       </table>
       <hr>
       <label>Any: <input ng-model="search.$"></label> <br>
       <label>Name only <input ng-model="search.name"></label><br>
       <label>Phone only <input ng-model="search.phone"></label><br>
       <label>Equality <input type="checkbox" ng-model="strict"></label><br>
       <table id="searchObjResults">
         <tr><th>Name</th><th>Phone</th></tr>
         <tr ng-repeat="friendObj in friends | filter:search:strict">
           <td>{{friendObj.name}}</td>
           <td>{{friendObj.phone}}</td>
         </tr>
       </table>
     </file>
     <file name="protractor.js" type="protractor">
       var expectFriendNames = function(expectedNames, key) {
         element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) {
           arr.forEach(function(wd, i) {
             expect(wd.getText()).toMatch(expectedNames[i]);
           });
         });
       };

       it('should search across all fields when filtering with a string', function() {
         var searchText = element(by.model('searchText'));
         searchText.clear();
         searchText.sendKeys('m');
         expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend');

         searchText.clear();
         searchText.sendKeys('76');
         expectFriendNames(['John', 'Julie'], 'friend');
       });

       it('should search in specific fields when filtering with a predicate object', function() {
         var searchAny = element(by.model('search.$'));
         searchAny.clear();
         searchAny.sendKeys('i');
         expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj');
       });
       it('should use a equal comparison when comparator is true', function() {
         var searchName = element(by.model('search.name'));
         var strict = element(by.model('strict'));
         searchName.clear();
         searchName.sendKeys('Julie');
         strict.click();
         expectFriendNames(['Julie'], 'friendObj');
       });
     </file>
   </example>
 */

function filterFilter() {
  return function(array, expression, comparator, anyPropertyKey) {
    if (!isArrayLike(array)) {
      if (array == null) {
        return array;
      } else {
        throw minErr('filter')('notarray', 'Expected array but received: {0}', array);
      }
    }

    anyPropertyKey = anyPropertyKey || '$';
    var expressionType = getTypeForFilter(expression);
    var predicateFn;
    var matchAgainstAnyProp;

    switch (expressionType) {
      case 'function':
        predicateFn = expression;
        break;
      case 'boolean':
      case 'null':
      case 'number':
      case 'string':
        matchAgainstAnyProp = true;
        // falls through
      case 'object':
        predicateFn = createPredicateFn(expression, comparator, anyPropertyKey, matchAgainstAnyProp);
        break;
      default:
        return array;
    }

    return Array.prototype.filter.call(array, predicateFn);
  };
}

// Helper functions for `filterFilter`
function createPredicateFn(expression, comparator, anyPropertyKey, matchAgainstAnyProp) {
  var shouldMatchPrimitives = isObject(expression) && (anyPropertyKey in expression);
  var predicateFn;

  if (comparator === true) {
    comparator = equals;
  } else if (!isFunction(comparator)) {
    comparator = function(actual, expected) {
      if (isUndefined(actual)) {
        // No substring matching against `undefined`
        return false;
      }
      if ((actual === null) || (expected === null)) {
        // No substring matching against `null`; only match against `null`
        return actual === expected;
      }
      if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) {
        // Should not compare primitives against objects, unless they have custom `toString` method
        return false;
      }

      actual = lowercase('' + actual);
      expected = lowercase('' + expected);
      return actual.indexOf(expected) !== -1;
    };
  }

  predicateFn = function(item) {
    if (shouldMatchPrimitives && !isObject(item)) {
      return deepCompare(item, expression[anyPropertyKey], comparator, anyPropertyKey, false);
    }
    return deepCompare(item, expression, comparator, anyPropertyKey, matchAgainstAnyProp);
  };

  return predicateFn;
}

function deepCompare(actual, expected, comparator, anyPropertyKey, matchAgainstAnyProp, dontMatchWholeObject) {
  var actualType = getTypeForFilter(actual);
  var expectedType = getTypeForFilter(expected);

  if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
    return !deepCompare(actual, expected.substring(1), comparator, anyPropertyKey, matchAgainstAnyProp);
  } else if (isArray(actual)) {
    // In case `actual` is an array, consider it a match
    // if ANY of it's items matches `expected`
    return actual.some(function(item) {
      return deepCompare(item, expected, comparator, anyPropertyKey, matchAgainstAnyProp);
    });
  }

  switch (actualType) {
    case 'object':
      var key;
      if (matchAgainstAnyProp) {
        for (key in actual) {
          // Under certain, rare, circumstances, key may not be a string and `charAt` will be undefined
          // See: https://github.com/angular/angular.js/issues/15644
          if (key.charAt && (key.charAt(0) !== '$') &&
              deepCompare(actual[key], expected, comparator, anyPropertyKey, true)) {
            return true;
          }
        }
        return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, anyPropertyKey, false);
      } else if (expectedType === 'object') {
        for (key in expected) {
          var expectedVal = expected[key];
          if (isFunction(expectedVal) || isUndefined(expectedVal)) {
            continue;
          }

          var matchAnyProperty = key === anyPropertyKey;
          var actualVal = matchAnyProperty ? actual : actual[key];
          if (!deepCompare(actualVal, expectedVal, comparator, anyPropertyKey, matchAnyProperty, matchAnyProperty)) {
            return false;
          }
        }
        return true;
      } else {
        return comparator(actual, expected);
      }
    case 'function':
      return false;
    default:
      return comparator(actual, expected);
  }
}

// Used for easily differentiating between `null` and actual `object`
function getTypeForFilter(val) {
  return (val === null) ? 'null' : typeof val;
}

var MAX_DIGITS = 22;
var DECIMAL_SEP = '.';
var ZERO_CHAR = '0';

/**
 * @ngdoc filter
 * @name currency
 * @kind function
 *
 * @description
 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default
 * symbol for current locale is used.
 *
 * @param {number} amount Input to filter.
 * @param {string=} symbol Currency symbol or identifier to be displayed.
 * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale
 * @returns {string} Formatted number.
 *
 *
 * @example
   <example module="currencyExample" name="currency-filter">
     <file name="index.html">
       <script>
         angular.module('currencyExample', [])
           .controller('ExampleController', ['$scope', function($scope) {
             $scope.amount = 1234.56;
           }]);
       </script>
       <div ng-controller="ExampleController">
         <input type="number" ng-model="amount" aria-label="amount"> <br>
         default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br>
         custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span><br>
         no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span>
       </div>
     </file>
     <file name="protractor.js" type="protractor">
       it('should init with 1234.56', function() {
         expect(element(by.id('currency-default')).getText()).toBe('$1,234.56');
         expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56');
         expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235');
       });
       it('should update', function() {
         if (browser.params.browser === 'safari') {
           // Safari does not understand the minus key. See
           // https://github.com/angular/protractor/issues/481
           return;
         }
         element(by.model('amount')).clear();
         element(by.model('amount')).sendKeys('-1234');
         expect(element(by.id('currency-default')).getText()).toBe('-$1,234.00');
         expect(element(by.id('currency-custom')).getText()).toBe('-USD$1,234.00');
         expect(element(by.id('currency-no-fractions')).getText()).toBe('-USD$1,234');
       });
     </file>
   </example>
 */
currencyFilter.$inject = ['$locale'];
function currencyFilter($locale) {
  var formats = $locale.NUMBER_FORMATS;
  return function(amount, currencySymbol, fractionSize) {
    if (isUndefined(currencySymbol)) {
      currencySymbol = formats.CURRENCY_SYM;
    }

    if (isUndefined(fractionSize)) {
      fractionSize = formats.PATTERNS[1].maxFrac;
    }

    function trimWhiteSpaceAroundChar(haystack, needlePosition) {
      var spaceSuffixStart = needlePosition + 1;
      var spacePrefixEnd = needlePosition - 1;
      var result = '';
      for (var needlePrefixStart = spacePrefixEnd; needlePrefixStart > -1; needlePrefixStart--) {
        if (' \t\n\r\v'.indexOf(haystack[needlePrefixStart]) === -1) {
          result += haystack.slice(0, needlePrefixStart + 1);
          break;
        }
      }
      result += currencySymbol;
      for (var needleSuffixEnd = spaceSuffixStart; needleSuffixEnd < haystack.length - 1; needleSuffixEnd++) {
        if (' \t\n\r\v'.indexOf(haystack[needleSuffixEnd]) === -1) {
          result += haystack.slice(needleSuffixEnd, haystack.length);
          break;
        }
      }
      return result;
    }

    // If the currency symbol is empty, trim whitespace around the symbol
    function insertCurrencySymbol(str, currencySymbol) {
      var trimWhiteSpace = !currencySymbol;
      var defaultCurrencySymbol = '\u00A4';
      var currencyLocation = str.indexOf(defaultCurrencySymbol);

      if (trimWhiteSpace && currencyLocation > -1) {
        var thing = str;
        while (thing.indexOf(defaultCurrencySymbol) > -1) {
          thing = trimWhiteSpaceAroundChar(thing, thing.indexOf(defaultCurrencySymbol));
        }
        return thing;
      }
      return str.replace(/\u00A4/g, currencySymbol);
    }

    // if null or undefined pass it through
    return (amount == null)
        ? amount
        : insertCurrencySymbol(
          formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize),
          currencySymbol
        );
  };
}

/**
 * @ngdoc filter
 * @name number
 * @kind function
 *
 * @description
 * Formats a number as text.
 *
 * If the input is null or undefined, it will just be returned.
 * If the input is infinite (Infinity or -Infinity), the Infinity symbol '∞' or '-∞' is returned, respectively.
 * If the input is not a number an empty string is returned.
 *
 *
 * @param {number|string} number Number to format.
 * @param {(number|string)=} fractionSize Number of decimal places to round the number to.
 * If this is not provided then the fraction size is computed from the current locale's number
 * formatting pattern. In the case of the default locale, it will be 3.
 * @returns {string} Number rounded to `fractionSize` appropriately formatted based on the current
 *                   locale (e.g., in the en_US locale it will have "." as the decimal separator and
 *                   include "," group separators after each third digit).
 *
 * @example
   <example module="numberFilterExample" name="number-filter">
     <file name="index.html">
       <script>
         angular.module('numberFilterExample', [])
           .controller('ExampleController', ['$scope', function($scope) {
             $scope.val = 1234.56789;
           }]);
       </script>
       <div ng-controller="ExampleController">
         <label>Enter number: <input ng-model='val'></label><br>
         Default formatting: <span id='number-default'>{{val | number}}</span><br>
         No fractions: <span>{{val | number:0}}</span><br>
         Negative number: <span>{{-val | number:4}}</span>
       </div>
     </file>
     <file name="protractor.js" type="protractor">
       it('should format numbers', function() {
         expect(element(by.id('number-default')).getText()).toBe('1,234.568');
         expect(element(by.binding('val | number:0')).getText()).toBe('1,235');
         expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679');
       });

       it('should update', function() {
         element(by.model('val')).clear();
         element(by.model('val')).sendKeys('3374.333');
         expect(element(by.id('number-default')).getText()).toBe('3,374.333');
         expect(element(by.binding('val | number:0')).getText()).toBe('3,374');
         expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330');
      });
     </file>
   </example>
 */
numberFilter.$inject = ['$locale'];
function numberFilter($locale) {
  var formats = $locale.NUMBER_FORMATS;
  return function(number, fractionSize) {

    // if null or undefined pass it through
    return (number == null)
        ? number
        : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP,
                       fractionSize);
  };
}

/**
 * Parse a number (as a string) into three components that can be used
 * for formatting the number.
 *
 * (Significant bits of this parse algorithm came from https://github.com/MikeMcl/big.js/)
 *
 * @param  {string} numStr The number to parse
 * @return {object} An object describing this number, containing the following keys:
 *  - d : an array of digits containing leading zeros as necessary
 *  - i : the number of the digits in `d` that are to the left of the decimal point
 *  - e : the exponent for numbers that would need more than `MAX_DIGITS` digits in `d`
 *
 */
function parse(numStr) {
  var exponent = 0, digits, numberOfIntegerDigits;
  var i, j, zeros;

  // Decimal point?
  if ((numberOfIntegerDigits = numStr.indexOf(DECIMAL_SEP)) > -1) {
    numStr = numStr.replace(DECIMAL_SEP, '');
  }

  // Exponential form?
  if ((i = numStr.search(/e/i)) > 0) {
    // Work out the exponent.
    if (numberOfIntegerDigits < 0) numberOfIntegerDigits = i;
    numberOfIntegerDigits += +numStr.slice(i + 1);
    numStr = numStr.substring(0, i);
  } else if (numberOfIntegerDigits < 0) {
    // There was no decimal point or exponent so it is an integer.
    numberOfIntegerDigits = numStr.length;
  }

  // Count the number of leading zeros.
  for (i = 0; numStr.charAt(i) === ZERO_CHAR; i++) { /* empty */ }

  if (i === (zeros = numStr.length)) {
    // The digits are all zero.
    digits = [0];
    numberOfIntegerDigits = 1;
  } else {
    // Count the number of trailing zeros
    zeros--;
    while (numStr.charAt(zeros) === ZERO_CHAR) zeros--;

    // Trailing zeros are insignificant so ignore them
    numberOfIntegerDigits -= i;
    digits = [];
    // Convert string to array of digits without leading/trailing zeros.
    for (j = 0; i <= zeros; i++, j++) {
      digits[j] = +numStr.charAt(i);
    }
  }

  // If the number overflows the maximum allowed digits then use an exponent.
  if (numberOfIntegerDigits > MAX_DIGITS) {
    digits = digits.splice(0, MAX_DIGITS - 1);
    exponent = numberOfIntegerDigits - 1;
    numberOfIntegerDigits = 1;
  }

  return { d: digits, e: exponent, i: numberOfIntegerDigits };
}

/**
 * Round the parsed number to the specified number of decimal places
 * This function changed the parsedNumber in-place
 */
function roundNumber(parsedNumber, fractionSize, minFrac, maxFrac) {
    var digits = parsedNumber.d;
    var fractionLen = digits.length - parsedNumber.i;

    // determine fractionSize if it is not specified; `+fractionSize` converts it to a number
    fractionSize = (isUndefined(fractionSize)) ? Math.min(Math.max(minFrac, fractionLen), maxFrac) : +fractionSize;

    // The index of the digit to where rounding is to occur
    var roundAt = fractionSize + parsedNumber.i;
    var digit = digits[roundAt];

    if (roundAt > 0) {
      // Drop fractional digits beyond `roundAt`
      digits.splice(Math.max(parsedNumber.i, roundAt));

      // Set non-fractional digits beyond `roundAt` to 0
      for (var j = roundAt; j < digits.length; j++) {
        digits[j] = 0;
      }
    } else {
      // We rounded to zero so reset the parsedNumber
      fractionLen = Math.max(0, fractionLen);
      parsedNumber.i = 1;
      digits.length = Math.max(1, roundAt = fractionSize + 1);
      digits[0] = 0;
      for (var i = 1; i < roundAt; i++) digits[i] = 0;
    }

    if (digit >= 5) {
      if (roundAt - 1 < 0) {
        for (var k = 0; k > roundAt; k--) {
          digits.unshift(0);
          parsedNumber.i++;
        }
        digits.unshift(1);
        parsedNumber.i++;
      } else {
        digits[roundAt - 1]++;
      }
    }

    // Pad out with zeros to get the required fraction length
    for (; fractionLen < Math.max(0, fractionSize); fractionLen++) digits.push(0);


    // Do any carrying, e.g. a digit was rounded up to 10
    var carry = digits.reduceRight(function(carry, d, i, digits) {
      d = d + carry;
      digits[i] = d % 10;
      return Math.floor(d / 10);
    }, 0);
    if (carry) {
      digits.unshift(carry);
      parsedNumber.i++;
    }
}

/**
 * Format a number into a string
 * @param  {number} number       The number to format
 * @param  {{
 *           minFrac, // the minimum number of digits required in the fraction part of the number
 *           maxFrac, // the maximum number of digits required in the fraction part of the number
 *           gSize,   // number of digits in each group of separated digits
 *           lgSize,  // number of digits in the last group of digits before the decimal separator
 *           negPre,  // the string to go in front of a negative number (e.g. `-` or `(`))
 *           posPre,  // the string to go in front of a positive number
 *           negSuf,  // the string to go after a negative number (e.g. `)`)
 *           posSuf   // the string to go after a positive number
 *         }} pattern
 * @param  {string} groupSep     The string to separate groups of number (e.g. `,`)
 * @param  {string} decimalSep   The string to act as the decimal separator (e.g. `.`)
 * @param  {[type]} fractionSize The size of the fractional part of the number
 * @return {string}              The number formatted as a string
 */
function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {

  if (!(isString(number) || isNumber(number)) || isNaN(number)) return '';

  var isInfinity = !isFinite(number);
  var isZero = false;
  var numStr = Math.abs(number) + '',
      formattedText = '',
      parsedNumber;

  if (isInfinity) {
    formattedText = '\u221e';
  } else {
    parsedNumber = parse(numStr);

    roundNumber(parsedNumber, fractionSize, pattern.minFrac, pattern.maxFrac);

    var digits = parsedNumber.d;
    var integerLen = parsedNumber.i;
    var exponent = parsedNumber.e;
    var decimals = [];
    isZero = digits.reduce(function(isZero, d) { return isZero && !d; }, true);

    // pad zeros for small numbers
    while (integerLen < 0) {
      digits.unshift(0);
      integerLen++;
    }

    // extract decimals digits
    if (integerLen > 0) {
      decimals = digits.splice(integerLen, digits.length);
    } else {
      decimals = digits;
      digits = [0];
    }

    // format the integer digits with grouping separators
    var groups = [];
    if (digits.length >= pattern.lgSize) {
      groups.unshift(digits.splice(-pattern.lgSize, digits.length).join(''));
    }
    while (digits.length > pattern.gSize) {
      groups.unshift(digits.splice(-pattern.gSize, digits.length).join(''));
    }
    if (digits.length) {
      groups.unshift(digits.join(''));
    }
    formattedText = groups.join(groupSep);

    // append the decimal digits
    if (decimals.length) {
      formattedText += decimalSep + decimals.join('');
    }

    if (exponent) {
      formattedText += 'e+' + exponent;
    }
  }
  if (number < 0 && !isZero) {
    return pattern.negPre + formattedText + pattern.negSuf;
  } else {
    return pattern.posPre + formattedText + pattern.posSuf;
  }
}

function padNumber(num, digits, trim, negWrap) {
  var neg = '';
  if (num < 0 || (negWrap && num <= 0)) {
    if (negWrap) {
      num = -num + 1;
    } else {
      num = -num;
      neg = '-';
    }
  }
  num = '' + num;
  while (num.length < digits) num = ZERO_CHAR + num;
  if (trim) {
    num = num.substr(num.length - digits);
  }
  return neg + num;
}


function dateGetter(name, size, offset, trim, negWrap) {
  offset = offset || 0;
  return function(date) {
    var value = date['get' + name]();
    if (offset > 0 || value > -offset) {
      value += offset;
    }
    if (value === 0 && offset === -12) value = 12;
    return padNumber(value, size, trim, negWrap);
  };
}

function dateStrGetter(name, shortForm, standAlone) {
  return function(date, formats) {
    var value = date['get' + name]();
    var propPrefix = (standAlone ? 'STANDALONE' : '') + (shortForm ? 'SHORT' : '');
    var get = uppercase(propPrefix + name);

    return formats[get][value];
  };
}

function timeZoneGetter(date, formats, offset) {
  var zone = -1 * offset;
  var paddedZone = (zone >= 0) ? '+' : '';

  paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) +
                padNumber(Math.abs(zone % 60), 2);

  return paddedZone;
}

function getFirstThursdayOfYear(year) {
    // 0 = index of January
    var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay();
    // 4 = index of Thursday (+1 to account for 1st = 5)
    // 11 = index of *next* Thursday (+1 account for 1st = 12)
    return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst);
}

function getThursdayThisWeek(datetime) {
    return new Date(datetime.getFullYear(), datetime.getMonth(),
      // 4 = index of Thursday
      datetime.getDate() + (4 - datetime.getDay()));
}

function weekGetter(size) {
   return function(date) {
      var firstThurs = getFirstThursdayOfYear(date.getFullYear()),
         thisThurs = getThursdayThisWeek(date);

      var diff = +thisThurs - +firstThurs,
         result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week

      return padNumber(result, size);
   };
}

function ampmGetter(date, formats) {
  return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1];
}

function eraGetter(date, formats) {
  return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1];
}

function longEraGetter(date, formats) {
  return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1];
}

var DATE_FORMATS = {
  yyyy: dateGetter('FullYear', 4, 0, false, true),
    yy: dateGetter('FullYear', 2, 0, true, true),
     y: dateGetter('FullYear', 1, 0, false, true),
  MMMM: dateStrGetter('Month'),
   MMM: dateStrGetter('Month', true),
    MM: dateGetter('Month', 2, 1),
     M: dateGetter('Month', 1, 1),
  LLLL: dateStrGetter('Month', false, true),
    dd: dateGetter('Date', 2),
     d: dateGetter('Date', 1),
    HH: dateGetter('Hours', 2),
     H: dateGetter('Hours', 1),
    hh: dateGetter('Hours', 2, -12),
     h: dateGetter('Hours', 1, -12),
    mm: dateGetter('Minutes', 2),
     m: dateGetter('Minutes', 1),
    ss: dateGetter('Seconds', 2),
     s: dateGetter('Seconds', 1),
     // while ISO 8601 requires fractions to be prefixed with `.` or `,`
     // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions
   sss: dateGetter('Milliseconds', 3),
  EEEE: dateStrGetter('Day'),
   EEE: dateStrGetter('Day', true),
     a: ampmGetter,
     Z: timeZoneGetter,
    ww: weekGetter(2),
     w: weekGetter(1),
     G: eraGetter,
     GG: eraGetter,
     GGG: eraGetter,
     GGGG: longEraGetter
};

var DATE_FORMATS_SPLIT = /((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))([\s\S]*)/,
    NUMBER_STRING = /^-?\d+$/;

/**
 * @ngdoc filter
 * @name date
 * @kind function
 *
 * @description
 *   Formats `date` to a string based on the requested `format`.
 *
 *   `format` string can be composed of the following elements:
 *
 *   * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
 *   * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
 *   * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
 *   * `'MMMM'`: Month in year (January-December)
 *   * `'MMM'`: Month in year (Jan-Dec)
 *   * `'MM'`: Month in year, padded (01-12)
 *   * `'M'`: Month in year (1-12)
 *   * `'LLLL'`: Stand-alone month in year (January-December)
 *   * `'dd'`: Day in month, padded (01-31)
 *   * `'d'`: Day in month (1-31)
 *   * `'EEEE'`: Day in Week,(Sunday-Saturday)
 *   * `'EEE'`: Day in Week, (Sun-Sat)
 *   * `'HH'`: Hour in day, padded (00-23)
 *   * `'H'`: Hour in day (0-23)
 *   * `'hh'`: Hour in AM/PM, padded (01-12)
 *   * `'h'`: Hour in AM/PM, (1-12)
 *   * `'mm'`: Minute in hour, padded (00-59)
 *   * `'m'`: Minute in hour (0-59)
 *   * `'ss'`: Second in minute, padded (00-59)
 *   * `'s'`: Second in minute (0-59)
 *   * `'sss'`: Millisecond in second, padded (000-999)
 *   * `'a'`: AM/PM marker
 *   * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
 *   * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year
 *   * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year
 *   * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD')
 *   * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini')
 *
 *   `format` string can also be one of the following predefined
 *   {@link guide/i18n localizable formats}:
 *
 *   * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale
 *     (e.g. Sep 3, 2010 12:05:08 PM)
 *   * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US  locale (e.g. 9/3/10 12:05 PM)
 *   * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US  locale
 *     (e.g. Friday, September 3, 2010)
 *   * `'longDate'`: equivalent to `'MMMM d, y'` for en_US  locale (e.g. September 3, 2010)
 *   * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US  locale (e.g. Sep 3, 2010)
 *   * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10)
 *   * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM)
 *   * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM)
 *
 *   `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g.
 *   `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence
 *   (e.g. `"h 'o''clock'"`).
 *
 *   Any other characters in the `format` string will be output as-is.
 *
 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
 *    number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its
 *    shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is
 *    specified in the string input, the time is considered to be in the local timezone.
 * @param {string=} format Formatting rules (see Description). If not specified,
 *    `mediumDate` is used.
 * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the
 *    continental US time zone abbreviations, but for general use, use a time zone offset, for
 *    example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
 *    If not specified, the timezone of the browser will be used.
 * @returns {string} Formatted string or the input if input is not recognized as date/millis.
 *
 * @example
   <example name="filter-date">
     <file name="index.html">
       <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>:
           <span>{{1288323623006 | date:'medium'}}</span><br>
       <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
          <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br>
       <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
          <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br>
       <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>:
          <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br>
     </file>
     <file name="protractor.js" type="protractor">
       it('should format date', function() {
         expect(element(by.binding("1288323623006 | date:'medium'")).getText()).
            toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
         expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()).
            toMatch(/2010-10-2\d \d{2}:\d{2}:\d{2} (-|\+)?\d{4}/);
         expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()).
            toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
         expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()).
            toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/);
       });
     </file>
   </example>
 */
dateFilter.$inject = ['$locale'];
function dateFilter($locale) {


  var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
                     // 1        2       3         4          5          6          7          8  9     10      11
  function jsonStringToDate(string) {
    var match;
    if ((match = string.match(R_ISO8601_STR))) {
      var date = new Date(0),
          tzHour = 0,
          tzMin  = 0,
          dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear,
          timeSetter = match[8] ? date.setUTCHours : date.setHours;

      if (match[9]) {
        tzHour = toInt(match[9] + match[10]);
        tzMin = toInt(match[9] + match[11]);
      }
      dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]));
      var h = toInt(match[4] || 0) - tzHour;
      var m = toInt(match[5] || 0) - tzMin;
      var s = toInt(match[6] || 0);
      var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
      timeSetter.call(date, h, m, s, ms);
      return date;
    }
    return string;
  }


  return function(date, format, timezone) {
    var text = '',
        parts = [],
        fn, match;

    format = format || 'mediumDate';
    format = $locale.DATETIME_FORMATS[format] || format;
    if (isString(date)) {
      date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date);
    }

    if (isNumber(date)) {
      date = new Date(date);
    }

    if (!isDate(date) || !isFinite(date.getTime())) {
      return date;
    }

    while (format) {
      match = DATE_FORMATS_SPLIT.exec(format);
      if (match) {
        parts = concat(parts, match, 1);
        format = parts.pop();
      } else {
        parts.push(format);
        format = null;
      }
    }

    var dateTimezoneOffset = date.getTimezoneOffset();
    if (timezone) {
      dateTimezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset);
      date = convertTimezoneToLocal(date, timezone, true);
    }
    forEach(parts, function(value) {
      fn = DATE_FORMATS[value];
      text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset)
                 : value === '\'\'' ? '\'' : value.replace(/(^'|'$)/g, '').replace(/''/g, '\'');
    });

    return text;
  };
}


/**
 * @ngdoc filter
 * @name json
 * @kind function
 *
 * @description
 *   Allows you to convert a JavaScript object into JSON string.
 *
 *   This filter is mostly useful for debugging. When using the double curly {{value}} notation
 *   the binding is automatically converted to JSON.
 *
 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
 * @param {number=} spacing The number of spaces to use per indentation, defaults to 2.
 * @returns {string} JSON string.
 *
 *
 * @example
   <example name="filter-json">
     <file name="index.html">
       <pre id="default-spacing">{{ {'name':'value'} | json }}</pre>
       <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre>
     </file>
     <file name="protractor.js" type="protractor">
       it('should jsonify filtered objects', function() {
         expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n {2}"name": ?"value"\n}/);
         expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n {4}"name": ?"value"\n}/);
       });
     </file>
   </example>
 *
 */
function jsonFilter() {
  return function(object, spacing) {
    if (isUndefined(spacing)) {
        spacing = 2;
    }
    return toJson(object, spacing);
  };
}


/**
 * @ngdoc filter
 * @name lowercase
 * @kind function
 * @description
 * Converts string to lowercase.
 *
 * See the {@link ng.uppercase uppercase filter documentation} for a functionally identical example.
 *
 * @see angular.lowercase
 */
var lowercaseFilter = valueFn(lowercase);


/**
 * @ngdoc filter
 * @name uppercase
 * @kind function
 * @description
 * Converts string to uppercase.
 * @example
   <example module="uppercaseFilterExample" name="filter-uppercase">
     <file name="index.html">
       <script>
         angular.module('uppercaseFilterExample', [])
           .controller('ExampleController', ['$scope', function($scope) {
             $scope.title = 'This is a title';
           }]);
       </script>
       <div ng-controller="ExampleController">
         <!-- This title should be formatted normally -->
         <h1>{{title}}</h1>
         <!-- This title should be capitalized -->
         <h1>{{title | uppercase}}</h1>
       </div>
     </file>
   </example>
 */
var uppercaseFilter = valueFn(uppercase);

/**
 * @ngdoc filter
 * @name limitTo
 * @kind function
 *
 * @description
 * Creates a new array or string containing only a specified number of elements. The elements are
 * taken from either the beginning or the end of the source array, string or number, as specified by
 * the value and sign (positive or negative) of `limit`. Other array-like objects are also supported
 * (e.g. array subclasses, NodeLists, jqLite/jQuery collections etc). If a number is used as input,
 * it is converted to a string.
 *
 * @param {Array|ArrayLike|string|number} input - Array/array-like, string or number to be limited.
 * @param {string|number} limit - The length of the returned array or string. If the `limit` number
 *     is positive, `limit` number of items from the beginning of the source array/string are copied.
 *     If the number is negative, `limit` number  of items from the end of the source array/string
 *     are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined,
 *     the input will be returned unchanged.
 * @param {(string|number)=} begin - Index at which to begin limitation. As a negative index,
 *     `begin` indicates an offset from the end of `input`. Defaults to `0`.
 * @returns {Array|string} A new sub-array or substring of length `limit` or less if the input had
 *     less than `limit` elements.
 *
 * @example
   <example module="limitToExample" name="limit-to-filter">
     <file name="index.html">
       <script>
         angular.module('limitToExample', [])
           .controller('ExampleController', ['$scope', function($scope) {
             $scope.numbers = [1,2,3,4,5,6,7,8,9];
             $scope.letters = "abcdefghi";
             $scope.longNumber = 2345432342;
             $scope.numLimit = 3;
             $scope.letterLimit = 3;
             $scope.longNumberLimit = 3;
           }]);
       </script>
       <div ng-controller="ExampleController">
         <label>
            Limit {{numbers}} to:
            <input type="number" step="1" ng-model="numLimit">
         </label>
         <p>Output numbers: {{ numbers | limitTo:numLimit }}</p>
         <label>
            Limit {{letters}} to:
            <input type="number" step="1" ng-model="letterLimit">
         </label>
         <p>Output letters: {{ letters | limitTo:letterLimit }}</p>
         <label>
            Limit {{longNumber}} to:
            <input type="number" step="1" ng-model="longNumberLimit">
         </label>
         <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p>
       </div>
     </file>
     <file name="protractor.js" type="protractor">
       var numLimitInput = element(by.model('numLimit'));
       var letterLimitInput = element(by.model('letterLimit'));
       var longNumberLimitInput = element(by.model('longNumberLimit'));
       var limitedNumbers = element(by.binding('numbers | limitTo:numLimit'));
       var limitedLetters = element(by.binding('letters | limitTo:letterLimit'));
       var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit'));

       it('should limit the number array to first three items', function() {
         expect(numLimitInput.getAttribute('value')).toBe('3');
         expect(letterLimitInput.getAttribute('value')).toBe('3');
         expect(longNumberLimitInput.getAttribute('value')).toBe('3');
         expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]');
         expect(limitedLetters.getText()).toEqual('Output letters: abc');
         expect(limitedLongNumber.getText()).toEqual('Output long number: 234');
       });

       // There is a bug in safari and protractor that doesn't like the minus key
       // it('should update the output when -3 is entered', function() {
       //   numLimitInput.clear();
       //   numLimitInput.sendKeys('-3');
       //   letterLimitInput.clear();
       //   letterLimitInput.sendKeys('-3');
       //   longNumberLimitInput.clear();
       //   longNumberLimitInput.sendKeys('-3');
       //   expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]');
       //   expect(limitedLetters.getText()).toEqual('Output letters: ghi');
       //   expect(limitedLongNumber.getText()).toEqual('Output long number: 342');
       // });

       it('should not exceed the maximum size of input array', function() {
         numLimitInput.clear();
         numLimitInput.sendKeys('100');
         letterLimitInput.clear();
         letterLimitInput.sendKeys('100');
         longNumberLimitInput.clear();
         longNumberLimitInput.sendKeys('100');
         expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]');
         expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi');
         expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342');
       });
     </file>
   </example>
*/
function limitToFilter() {
  return function(input, limit, begin) {
    if (Math.abs(Number(limit)) === Infinity) {
      limit = Number(limit);
    } else {
      limit = toInt(limit);
    }
    if (isNumberNaN(limit)) return input;

    if (isNumber(input)) input = input.toString();
    if (!isArrayLike(input)) return input;

    begin = (!begin || isNaN(begin)) ? 0 : toInt(begin);
    begin = (begin < 0) ? Math.max(0, input.length + begin) : begin;

    if (limit >= 0) {
      return sliceFn(input, begin, begin + limit);
    } else {
      if (begin === 0) {
        return sliceFn(input, limit, input.length);
      } else {
        return sliceFn(input, Math.max(0, begin + limit), begin);
      }
    }
  };
}

function sliceFn(input, begin, end) {
  if (isString(input)) return input.slice(begin, end);

  return slice.call(input, begin, end);
}

/**
 * @ngdoc filter
 * @name orderBy
 * @kind function
 *
 * @description
 * Returns an array containing the items from the specified `collection`, ordered by a `comparator`
 * function based on the values computed using the `expression` predicate.
 *
 * For example, `[{id: 'foo'}, {id: 'bar'}] | orderBy:'id'` would result in
 * `[{id: 'bar'}, {id: 'foo'}]`.
 *
 * The `collection` can be an Array or array-like object (e.g. NodeList, jQuery object, TypedArray,
 * String, etc).
 *
 * The `expression` can be a single predicate, or a list of predicates each serving as a tie-breaker
 * for the preceding one. The `expression` is evaluated against each item and the output is used
 * for comparing with other items.
 *
 * You can change the sorting order by setting `reverse` to `true`. By default, items are sorted in
 * ascending order.
 *
 * The comparison is done using the `comparator` function. If none is specified, a default, built-in
 * comparator is used (see below for details - in a nutshell, it compares numbers numerically and
 * strings alphabetically).
 *
 * ### Under the hood
 *
 * Ordering the specified `collection` happens in two phases:
 *
 * 1. All items are passed through the predicate (or predicates), and the returned values are saved
 *    along with their type (`string`, `number` etc). For example, an item `{label: 'foo'}`, passed
 *    through a predicate that extracts the value of the `label` property, would be transformed to:
 *    ```
 *    {
 *      value: 'foo',
 *      type: 'string',
 *      index: ...
 *    }
 *    ```
 *    **Note:** `null` values use `'null'` as their type.
 * 2. The comparator function is used to sort the items, based on the derived values, types and
 *    indices.
 *
 * If you use a custom comparator, it will be called with pairs of objects of the form
 * `{value: ..., type: '...', index: ...}` and is expected to return `0` if the objects are equal
 * (as far as the comparator is concerned), `-1` if the 1st one should be ranked higher than the
 * second, or `1` otherwise.
 *
 * In order to ensure that the sorting will be deterministic across platforms, if none of the
 * specified predicates can distinguish between two items, `orderBy` will automatically introduce a
 * dummy predicate that returns the item's index as `value`.
 * (If you are using a custom comparator, make sure it can handle this predicate as well.)
 *
 * If a custom comparator still can't distinguish between two items, then they will be sorted based
 * on their index using the built-in comparator.
 *
 * Finally, in an attempt to simplify things, if a predicate returns an object as the extracted
 * value for an item, `orderBy` will try to convert that object to a primitive value, before passing
 * it to the comparator. The following rules govern the conversion:
 *
 * 1. If the object has a `valueOf()` method that returns a primitive, its return value will be
 *    used instead.<br />
 *    (If the object has a `valueOf()` method that returns another object, then the returned object
 *    will be used in subsequent steps.)
 * 2. If the object has a custom `toString()` method (i.e. not the one inherited from `Object`) that
 *    returns a primitive, its return value will be used instead.<br />
 *    (If the object has a `toString()` method that returns another object, then the returned object
 *    will be used in subsequent steps.)
 * 3. No conversion; the object itself is used.
 *
 * ### The default comparator
 *
 * The default, built-in comparator should be sufficient for most usecases. In short, it compares
 * numbers numerically, strings alphabetically (and case-insensitively), for objects falls back to
 * using their index in the original collection, sorts values of different types by type and puts
 * `undefined` and `null` values at the end of the sorted list.
 *
 * More specifically, it follows these steps to determine the relative order of items:
 *
 * 1. If the compared values are of different types:
 *    - If one of the values is undefined, consider it "greater than" the other.
 *    - Else if one of the values is null, consider it "greater than" the other.
 *    - Else compare the types themselves alphabetically.
 * 2. If both values are of type `string`, compare them alphabetically in a case- and
 *    locale-insensitive way.
 * 3. If both values are objects, compare their indices instead.
 * 4. Otherwise, return:
 *    -  `0`, if the values are equal (by strict equality comparison, i.e. using `===`).
 *    - `-1`, if the 1st value is "less than" the 2nd value (compared using the `<` operator).
 *    -  `1`, otherwise.
 *
 * **Note:** If you notice numbers not being sorted as expected, make sure they are actually being
 *           saved as numbers and not strings.
 * **Note:** For the purpose of sorting, `null` and `undefined` are considered "greater than"
 *           any other value (with undefined "greater than" null). This effectively means that `null`
 *           and `undefined` values end up at the end of a list sorted in ascending order.
 * **Note:** `null` values use `'null'` as their type to be able to distinguish them from objects.
 *
 * @param {Array|ArrayLike} collection - The collection (array or array-like object) to sort.
 * @param {(Function|string|Array.<Function|string>)=} expression - A predicate (or list of
 *    predicates) to be used by the comparator to determine the order of elements.
 *
 *    Can be one of:
 *
 *    - `Function`: A getter function. This function will be called with each item as argument and
 *      the return value will be used for sorting.
 *    - `string`: An AngularJS expression. This expression will be evaluated against each item and the
 *      result will be used for sorting. For example, use `'label'` to sort by a property called
 *      `label` or `'label.substring(0, 3)'` to sort by the first 3 characters of the `label`
 *      property.<br />
 *      (The result of a constant expression is interpreted as a property name to be used for
 *      comparison. For example, use `'"special name"'` (note the extra pair of quotes) to sort by a
 *      property called `special name`.)<br />
 *      An expression can be optionally prefixed with `+` or `-` to control the sorting direction,
 *      ascending or descending. For example, `'+label'` or `'-label'`. If no property is provided,
 *      (e.g. `'+'` or `'-'`), the collection element itself is used in comparisons.
 *    - `Array`: An array of function and/or string predicates. If a predicate cannot determine the
 *      relative order of two items, the next predicate is used as a tie-breaker.
 *
 * **Note:** If the predicate is missing or empty then it defaults to `'+'`.
 *
 * @param {boolean=} reverse - If `true`, reverse the sorting order.
 * @param {(Function)=} comparator - The comparator function used to determine the relative order of
 *    value pairs. If omitted, the built-in comparator will be used.
 *
 * @returns {Array} - The sorted array.
 *
 *
 * @example
 * ### Ordering a table with `ngRepeat`
 *
 * The example below demonstrates a simple {@link ngRepeat ngRepeat}, where the data is sorted by
 * age in descending order (expression is set to `'-age'`). The `comparator` is not set, which means
 * it defaults to the built-in comparator.
 *
   <example name="orderBy-static" module="orderByExample1">
     <file name="index.html">
       <div ng-controller="ExampleController">
         <table class="friends">
           <tr>
             <th>Name</th>
             <th>Phone Number</th>
             <th>Age</th>
           </tr>
           <tr ng-repeat="friend in friends | orderBy:'-age'">
             <td>{{friend.name}}</td>
             <td>{{friend.phone}}</td>
             <td>{{friend.age}}</td>
           </tr>
         </table>
       </div>
     </file>
     <file name="script.js">
       angular.module('orderByExample1', [])
         .controller('ExampleController', ['$scope', function($scope) {
           $scope.friends = [
             {name: 'John',   phone: '555-1212',  age: 10},
             {name: 'Mary',   phone: '555-9876',  age: 19},
             {name: 'Mike',   phone: '555-4321',  age: 21},
             {name: 'Adam',   phone: '555-5678',  age: 35},
             {name: 'Julie',  phone: '555-8765',  age: 29}
           ];
         }]);
     </file>
     <file name="style.css">
       .friends {
         border-collapse: collapse;
       }

       .friends th {
         border-bottom: 1px solid;
       }
       .friends td, .friends th {
         border-left: 1px solid;
         padding: 5px 10px;
       }
       .friends td:first-child, .friends th:first-child {
         border-left: none;
       }
     </file>
     <file name="protractor.js" type="protractor">
       // Element locators
       var names = element.all(by.repeater('friends').column('friend.name'));

       it('should sort friends by age in reverse order', function() {
         expect(names.get(0).getText()).toBe('Adam');
         expect(names.get(1).getText()).toBe('Julie');
         expect(names.get(2).getText()).toBe('Mike');
         expect(names.get(3).getText()).toBe('Mary');
         expect(names.get(4).getText()).toBe('John');
       });
     </file>
   </example>
 * <hr />
 *
 * @example
 * ### Changing parameters dynamically
 *
 * All parameters can be changed dynamically. The next example shows how you can make the columns of
 * a table sortable, by binding the `expression` and `reverse` parameters to scope properties.
 *
   <example name="orderBy-dynamic" module="orderByExample2">
     <file name="index.html">
       <div ng-controller="ExampleController">
         <pre>Sort by = {{propertyName}}; reverse = {{reverse}}</pre>
         <hr/>
         <button ng-click="propertyName = null; reverse = false">Set to unsorted</button>
         <hr/>
         <table class="friends">
           <tr>
             <th>
               <button ng-click="sortBy('name')">Name</button>
               <span class="sortorder" ng-show="propertyName === 'name'" ng-class="{reverse: reverse}"></span>
             </th>
             <th>
               <button ng-click="sortBy('phone')">Phone Number</button>
               <span class="sortorder" ng-show="propertyName === 'phone'" ng-class="{reverse: reverse}"></span>
             </th>
             <th>
               <button ng-click="sortBy('age')">Age</button>
               <span class="sortorder" ng-show="propertyName === 'age'" ng-class="{reverse: reverse}"></span>
             </th>
           </tr>
           <tr ng-repeat="friend in friends | orderBy:propertyName:reverse">
             <td>{{friend.name}}</td>
             <td>{{friend.phone}}</td>
             <td>{{friend.age}}</td>
           </tr>
         </table>
       </div>
     </file>
     <file name="script.js">
       angular.module('orderByExample2', [])
         .controller('ExampleController', ['$scope', function($scope) {
           var friends = [
             {name: 'John',   phone: '555-1212',  age: 10},
             {name: 'Mary',   phone: '555-9876',  age: 19},
             {name: 'Mike',   phone: '555-4321',  age: 21},
             {name: 'Adam',   phone: '555-5678',  age: 35},
             {name: 'Julie',  phone: '555-8765',  age: 29}
           ];

           $scope.propertyName = 'age';
           $scope.reverse = true;
           $scope.friends = friends;

           $scope.sortBy = function(propertyName) {
             $scope.reverse = ($scope.propertyName === propertyName) ? !$scope.reverse : false;
             $scope.propertyName = propertyName;
           };
         }]);
     </file>
     <file name="style.css">
       .friends {
         border-collapse: collapse;
       }

       .friends th {
         border-bottom: 1px solid;
       }
       .friends td, .friends th {
         border-left: 1px solid;
         padding: 5px 10px;
       }
       .friends td:first-child, .friends th:first-child {
         border-left: none;
       }

       .sortorder:after {
         content: '\25b2';   // BLACK UP-POINTING TRIANGLE
       }
       .sortorder.reverse:after {
         content: '\25bc';   // BLACK DOWN-POINTING TRIANGLE
       }
     </file>
     <file name="protractor.js" type="protractor">
       // Element locators
       var unsortButton = element(by.partialButtonText('unsorted'));
       var nameHeader = element(by.partialButtonText('Name'));
       var phoneHeader = element(by.partialButtonText('Phone'));
       var ageHeader = element(by.partialButtonText('Age'));
       var firstName = element(by.repeater('friends').column('friend.name').row(0));
       var lastName = element(by.repeater('friends').column('friend.name').row(4));

       it('should sort friends by some property, when clicking on the column header', function() {
         expect(firstName.getText()).toBe('Adam');
         expect(lastName.getText()).toBe('John');

         phoneHeader.click();
         expect(firstName.getText()).toBe('John');
         expect(lastName.getText()).toBe('Mary');

         nameHeader.click();
         expect(firstName.getText()).toBe('Adam');
         expect(lastName.getText()).toBe('Mike');

         ageHeader.click();
         expect(firstName.getText()).toBe('John');
         expect(lastName.getText()).toBe('Adam');
       });

       it('should sort friends in reverse order, when clicking on the same column', function() {
         expect(firstName.getText()).toBe('Adam');
         expect(lastName.getText()).toBe('John');

         ageHeader.click();
         expect(firstName.getText()).toBe('John');
         expect(lastName.getText()).toBe('Adam');

         ageHeader.click();
         expect(firstName.getText()).toBe('Adam');
         expect(lastName.getText()).toBe('John');
       });

       it('should restore the original order, when clicking "Set to unsorted"', function() {
         expect(firstName.getText()).toBe('Adam');
         expect(lastName.getText()).toBe('John');

         unsortButton.click();
         expect(firstName.getText()).toBe('John');
         expect(lastName.getText()).toBe('Julie');
       });
     </file>
   </example>
 * <hr />
 *
 * @example
 * ### Using `orderBy` inside a controller
 *
 * It is also possible to call the `orderBy` filter manually, by injecting `orderByFilter`, and
 * calling it with the desired parameters. (Alternatively, you could inject the `$filter` factory
 * and retrieve the `orderBy` filter with `$filter('orderBy')`.)
 *
   <example name="orderBy-call-manually" module="orderByExample3">
     <file name="index.html">
       <div ng-controller="ExampleController">
         <pre>Sort by = {{propertyName}}; reverse = {{reverse}}</pre>
         <hr/>
         <button ng-click="sortBy(null)">Set to unsorted</button>
         <hr/>
         <table class="friends">
           <tr>
             <th>
               <button ng-click="sortBy('name')">Name</button>
               <span class="sortorder" ng-show="propertyName === 'name'" ng-class="{reverse: reverse}"></span>
             </th>
             <th>
               <button ng-click="sortBy('phone')">Phone Number</button>
               <span class="sortorder" ng-show="propertyName === 'phone'" ng-class="{reverse: reverse}"></span>
             </th>
             <th>
               <button ng-click="sortBy('age')">Age</button>
               <span class="sortorder" ng-show="propertyName === 'age'" ng-class="{reverse: reverse}"></span>
             </th>
           </tr>
           <tr ng-repeat="friend in friends">
             <td>{{friend.name}}</td>
             <td>{{friend.phone}}</td>
             <td>{{friend.age}}</td>
           </tr>
         </table>
       </div>
     </file>
     <file name="script.js">
       angular.module('orderByExample3', [])
         .controller('ExampleController', ['$scope', 'orderByFilter', function($scope, orderBy) {
           var friends = [
             {name: 'John',   phone: '555-1212',  age: 10},
             {name: 'Mary',   phone: '555-9876',  age: 19},
             {name: 'Mike',   phone: '555-4321',  age: 21},
             {name: 'Adam',   phone: '555-5678',  age: 35},
             {name: 'Julie',  phone: '555-8765',  age: 29}
           ];

           $scope.propertyName = 'age';
           $scope.reverse = true;
           $scope.friends = orderBy(friends, $scope.propertyName, $scope.reverse);

           $scope.sortBy = function(propertyName) {
             $scope.reverse = (propertyName !== null && $scope.propertyName === propertyName)
                 ? !$scope.reverse : false;
             $scope.propertyName = propertyName;
             $scope.friends = orderBy(friends, $scope.propertyName, $scope.reverse);
           };
         }]);
     </file>
     <file name="style.css">
       .friends {
         border-collapse: collapse;
       }

       .friends th {
         border-bottom: 1px solid;
       }
       .friends td, .friends th {
         border-left: 1px solid;
         padding: 5px 10px;
       }
       .friends td:first-child, .friends th:first-child {
         border-left: none;
       }

       .sortorder:after {
         content: '\25b2';   // BLACK UP-POINTING TRIANGLE
       }
       .sortorder.reverse:after {
         content: '\25bc';   // BLACK DOWN-POINTING TRIANGLE
       }
     </file>
     <file name="protractor.js" type="protractor">
       // Element locators
       var unsortButton = element(by.partialButtonText('unsorted'));
       var nameHeader = element(by.partialButtonText('Name'));
       var phoneHeader = element(by.partialButtonText('Phone'));
       var ageHeader = element(by.partialButtonText('Age'));
       var firstName = element(by.repeater('friends').column('friend.name').row(0));
       var lastName = element(by.repeater('friends').column('friend.name').row(4));

       it('should sort friends by some property, when clicking on the column header', function() {
         expect(firstName.getText()).toBe('Adam');
         expect(lastName.getText()).toBe('John');

         phoneHeader.click();
         expect(firstName.getText()).toBe('John');
         expect(lastName.getText()).toBe('Mary');

         nameHeader.click();
         expect(firstName.getText()).toBe('Adam');
         expect(lastName.getText()).toBe('Mike');

         ageHeader.click();
         expect(firstName.getText()).toBe('John');
         expect(lastName.getText()).toBe('Adam');
       });

       it('should sort friends in reverse order, when clicking on the same column', function() {
         expect(firstName.getText()).toBe('Adam');
         expect(lastName.getText()).toBe('John');

         ageHeader.click();
         expect(firstName.getText()).toBe('John');
         expect(lastName.getText()).toBe('Adam');

         ageHeader.click();
         expect(firstName.getText()).toBe('Adam');
         expect(lastName.getText()).toBe('John');
       });

       it('should restore the original order, when clicking "Set to unsorted"', function() {
         expect(firstName.getText()).toBe('Adam');
         expect(lastName.getText()).toBe('John');

         unsortButton.click();
         expect(firstName.getText()).toBe('John');
         expect(lastName.getText()).toBe('Julie');
       });
     </file>
   </example>
 * <hr />
 *
 * @example
 * ### Using a custom comparator
 *
 * If you have very specific requirements about the way items are sorted, you can pass your own
 * comparator function. For example, you might need to compare some strings in a locale-sensitive
 * way. (When specifying a custom comparator, you also need to pass a value for the `reverse`
 * argument - passing `false` retains the default sorting order, i.e. ascending.)
 *
   <example name="orderBy-custom-comparator" module="orderByExample4">
     <file name="index.html">
       <div ng-controller="ExampleController">
         <div class="friends-container custom-comparator">
           <h3>Locale-sensitive Comparator</h3>
           <table class="friends">
             <tr>
               <th>Name</th>
               <th>Favorite Letter</th>
             </tr>
             <tr ng-repeat="friend in friends | orderBy:'favoriteLetter':false:localeSensitiveComparator">
               <td>{{friend.name}}</td>
               <td>{{friend.favoriteLetter}}</td>
             </tr>
           </table>
         </div>
         <div class="friends-container default-comparator">
           <h3>Default Comparator</h3>
           <table class="friends">
             <tr>
               <th>Name</th>
               <th>Favorite Letter</th>
             </tr>
             <tr ng-repeat="friend in friends | orderBy:'favoriteLetter'">
               <td>{{friend.name}}</td>
               <td>{{friend.favoriteLetter}}</td>
             </tr>
           </table>
         </div>
       </div>
     </file>
     <file name="script.js">
       angular.module('orderByExample4', [])
         .controller('ExampleController', ['$scope', function($scope) {
           $scope.friends = [
             {name: 'John',   favoriteLetter: 'Ä'},
             {name: 'Mary',   favoriteLetter: 'Ü'},
             {name: 'Mike',   favoriteLetter: 'Ö'},
             {name: 'Adam',   favoriteLetter: 'H'},
             {name: 'Julie',  favoriteLetter: 'Z'}
           ];

           $scope.localeSensitiveComparator = function(v1, v2) {
             // If we don't get strings, just compare by index
             if (v1.type !== 'string' || v2.type !== 'string') {
               return (v1.index < v2.index) ? -1 : 1;
             }

             // Compare strings alphabetically, taking locale into account
             return v1.value.localeCompare(v2.value);
           };
         }]);
     </file>
     <file name="style.css">
       .friends-container {
         display: inline-block;
         margin: 0 30px;
       }

       .friends {
         border-collapse: collapse;
       }

       .friends th {
         border-bottom: 1px solid;
       }
       .friends td, .friends th {
         border-left: 1px solid;
         padding: 5px 10px;
       }
       .friends td:first-child, .friends th:first-child {
         border-left: none;
       }
     </file>
     <file name="protractor.js" type="protractor">
       // Element locators
       var container = element(by.css('.custom-comparator'));
       var names = container.all(by.repeater('friends').column('friend.name'));

       it('should sort friends by favorite letter (in correct alphabetical order)', function() {
         expect(names.get(0).getText()).toBe('John');
         expect(names.get(1).getText()).toBe('Adam');
         expect(names.get(2).getText()).toBe('Mike');
         expect(names.get(3).getText()).toBe('Mary');
         expect(names.get(4).getText()).toBe('Julie');
       });
     </file>
   </example>
 *
 */
orderByFilter.$inject = ['$parse'];
function orderByFilter($parse) {
  return function(array, sortPredicate, reverseOrder, compareFn) {

    if (array == null) return array;
    if (!isArrayLike(array)) {
      throw minErr('orderBy')('notarray', 'Expected array but received: {0}', array);
    }

    if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; }
    if (sortPredicate.length === 0) { sortPredicate = ['+']; }

    var predicates = processPredicates(sortPredicate);

    var descending = reverseOrder ? -1 : 1;

    // Define the `compare()` function. Use a default comparator if none is specified.
    var compare = isFunction(compareFn) ? compareFn : defaultCompare;

    // The next three lines are a version of a Swartzian Transform idiom from Perl
    // (sometimes called the Decorate-Sort-Undecorate idiom)
    // See https://en.wikipedia.org/wiki/Schwartzian_transform
    var compareValues = Array.prototype.map.call(array, getComparisonObject);
    compareValues.sort(doComparison);
    array = compareValues.map(function(item) { return item.value; });

    return array;

    function getComparisonObject(value, index) {
      // NOTE: We are adding an extra `tieBreaker` value based on the element's index.
      // This will be used to keep the sort stable when none of the input predicates can
      // distinguish between two elements.
      return {
        value: value,
        tieBreaker: {value: index, type: 'number', index: index},
        predicateValues: predicates.map(function(predicate) {
          return getPredicateValue(predicate.get(value), index);
        })
      };
    }

    function doComparison(v1, v2) {
      for (var i = 0, ii = predicates.length; i < ii; i++) {
        var result = compare(v1.predicateValues[i], v2.predicateValues[i]);
        if (result) {
          return result * predicates[i].descending * descending;
        }
      }

      return (compare(v1.tieBreaker, v2.tieBreaker) || defaultCompare(v1.tieBreaker, v2.tieBreaker)) * descending;
    }
  };

  function processPredicates(sortPredicates) {
    return sortPredicates.map(function(predicate) {
      var descending = 1, get = identity;

      if (isFunction(predicate)) {
        get = predicate;
      } else if (isString(predicate)) {
        if ((predicate.charAt(0) === '+' || predicate.charAt(0) === '-')) {
          descending = predicate.charAt(0) === '-' ? -1 : 1;
          predicate = predicate.substring(1);
        }
        if (predicate !== '') {
          get = $parse(predicate);
          if (get.constant) {
            var key = get();
            get = function(value) { return value[key]; };
          }
        }
      }
      return {get: get, descending: descending};
    });
  }

  function isPrimitive(value) {
    switch (typeof value) {
      case 'number': /* falls through */
      case 'boolean': /* falls through */
      case 'string':
        return true;
      default:
        return false;
    }
  }

  function objectValue(value) {
    // If `valueOf` is a valid function use that
    if (isFunction(value.valueOf)) {
      value = value.valueOf();
      if (isPrimitive(value)) return value;
    }
    // If `toString` is a valid function and not the one from `Object.prototype` use that
    if (hasCustomToString(value)) {
      value = value.toString();
      if (isPrimitive(value)) return value;
    }

    return value;
  }

  function getPredicateValue(value, index) {
    var type = typeof value;
    if (value === null) {
      type = 'null';
    } else if (type === 'object') {
      value = objectValue(value);
    }
    return {value: value, type: type, index: index};
  }

  function defaultCompare(v1, v2) {
    var result = 0;
    var type1 = v1.type;
    var type2 = v2.type;

    if (type1 === type2) {
      var value1 = v1.value;
      var value2 = v2.value;

      if (type1 === 'string') {
        // Compare strings case-insensitively
        value1 = value1.toLowerCase();
        value2 = value2.toLowerCase();
      } else if (type1 === 'object') {
        // For basic objects, use the position of the object
        // in the collection instead of the value
        if (isObject(value1)) value1 = v1.index;
        if (isObject(value2)) value2 = v2.index;
      }

      if (value1 !== value2) {
        result = value1 < value2 ? -1 : 1;
      }
    } else {
      result = (type1 === 'undefined') ? 1 :
        (type2 === 'undefined') ? -1 :
        (type1 === 'null') ? 1 :
        (type2 === 'null') ? -1 :
        (type1 < type2) ? -1 : 1;
    }

    return result;
  }
}

function ngDirective(directive) {
  if (isFunction(directive)) {
    directive = {
      link: directive
    };
  }
  directive.restrict = directive.restrict || 'AC';
  return valueFn(directive);
}

/**
 * @ngdoc directive
 * @name a
 * @restrict E
 *
 * @description
 * Modifies the default behavior of the html a tag so that the default action is prevented when
 * the href attribute is empty.
 *
 * For dynamically creating `href` attributes for a tags, see the {@link ng.ngHref `ngHref`} directive.
 */
var htmlAnchorDirective = valueFn({
  restrict: 'E',
  compile: function(element, attr) {
    if (!attr.href && !attr.xlinkHref) {
      return function(scope, element) {
        // If the linked element is not an anchor tag anymore, do nothing
        if (element[0].nodeName.toLowerCase() !== 'a') return;

        // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
        var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
                   'xlink:href' : 'href';
        element.on('click', function(event) {
          // if we have no href url, then don't navigate anywhere.
          if (!element.attr(href)) {
            event.preventDefault();
          }
        });
      };
    }
  }
});

/**
 * @ngdoc directive
 * @name ngHref
 * @restrict A
 * @priority 99
 *
 * @description
 * Using AngularJS markup like `{{hash}}` in an href attribute will
 * make the link go to the wrong URL if the user clicks it before
 * AngularJS has a chance to replace the `{{hash}}` markup with its
 * value. Until AngularJS replaces the markup the link will be broken
 * and will most likely return a 404 error. The `ngHref` directive
 * solves this problem.
 *
 * The wrong way to write it:
 * ```html
 * <a href="http://www.gravatar.com/avatar/{{hash}}">link1</a>
 * ```
 *
 * The correct way to write it:
 * ```html
 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}">link1</a>
 * ```
 *
 * @element A
 * @param {template} ngHref any string which can contain `{{}}` markup.
 *
 * @example
 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes
 * in links and their different behaviors:
    <example name="ng-href">
      <file name="index.html">
        <input ng-model="value" /><br />
        <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br />
        <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br />
        <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br />
        <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br />
        <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br />
        <a id="link-6" ng-href="{{value}}">link</a> (link, change location)
      </file>
      <file name="protractor.js" type="protractor">
        it('should execute ng-click but not reload when href without value', function() {
          element(by.id('link-1')).click();
          expect(element(by.model('value')).getAttribute('value')).toEqual('1');
          expect(element(by.id('link-1')).getAttribute('href')).toBe('');
        });

        it('should execute ng-click but not reload when href empty string', function() {
          element(by.id('link-2')).click();
          expect(element(by.model('value')).getAttribute('value')).toEqual('2');
          expect(element(by.id('link-2')).getAttribute('href')).toBe('');
        });

        it('should execute ng-click and change url when ng-href specified', function() {
          expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/);

          element(by.id('link-3')).click();

          // At this point, we navigate away from an AngularJS page, so we need
          // to use browser.driver to get the base webdriver.

          browser.wait(function() {
            return browser.driver.getCurrentUrl().then(function(url) {
              return url.match(/\/123$/);
            });
          }, 5000, 'page should navigate to /123');
        });

        it('should execute ng-click but not reload when href empty string and name specified', function() {
          element(by.id('link-4')).click();
          expect(element(by.model('value')).getAttribute('value')).toEqual('4');
          expect(element(by.id('link-4')).getAttribute('href')).toBe('');
        });

        it('should execute ng-click but not reload when no href but name specified', function() {
          element(by.id('link-5')).click();
          expect(element(by.model('value')).getAttribute('value')).toEqual('5');
          expect(element(by.id('link-5')).getAttribute('href')).toBe(null);
        });

        it('should only change url when only ng-href', function() {
          element(by.model('value')).clear();
          element(by.model('value')).sendKeys('6');
          expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/);

          element(by.id('link-6')).click();

          // At this point, we navigate away from an AngularJS page, so we need
          // to use browser.driver to get the base webdriver.
          browser.wait(function() {
            return browser.driver.getCurrentUrl().then(function(url) {
              return url.match(/\/6$/);
            });
          }, 5000, 'page should navigate to /6');
        });
      </file>
    </example>
 */

/**
 * @ngdoc directive
 * @name ngSrc
 * @restrict A
 * @priority 99
 *
 * @description
 * Using AngularJS markup like `{{hash}}` in a `src` attribute doesn't
 * work right: The browser will fetch from the URL with the literal
 * text `{{hash}}` until AngularJS replaces the expression inside
 * `{{hash}}`. The `ngSrc` directive solves this problem.
 *
 * The buggy way to write it:
 * ```html
 * <img src="http://www.gravatar.com/avatar/{{hash}}" alt="Description"/>
 * ```
 *
 * The correct way to write it:
 * ```html
 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}" alt="Description" />
 * ```
 *
 * @element IMG
 * @param {template} ngSrc any string which can contain `{{}}` markup.
 */

/**
 * @ngdoc directive
 * @name ngSrcset
 * @restrict A
 * @priority 99
 *
 * @description
 * Using AngularJS markup like `{{hash}}` in a `srcset` attribute doesn't
 * work right: The browser will fetch from the URL with the literal
 * text `{{hash}}` until AngularJS replaces the expression inside
 * `{{hash}}`. The `ngSrcset` directive solves this problem.
 *
 * The buggy way to write it:
 * ```html
 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description"/>
 * ```
 *
 * The correct way to write it:
 * ```html
 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description" />
 * ```
 *
 * @element IMG
 * @param {template} ngSrcset any string which can contain `{{}}` markup.
 */

/**
 * @ngdoc directive
 * @name ngDisabled
 * @restrict A
 * @priority 100
 *
 * @description
 *
 * This directive sets the `disabled` attribute on the element (typically a form control,
 * e.g. `input`, `button`, `select` etc.) if the
 * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy.
 *
 * A special directive is necessary because we cannot use interpolation inside the `disabled`
 * attribute. See the {@link guide/interpolation interpolation guide} for more info.
 *
 * @example
    <example name="ng-disabled">
      <file name="index.html">
        <label>Click me to toggle: <input type="checkbox" ng-model="checked"></label><br/>
        <button ng-model="button" ng-disabled="checked">Button</button>
      </file>
      <file name="protractor.js" type="protractor">
        it('should toggle button', function() {
          expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy();
          element(by.model('checked')).click();
          expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy();
        });
      </file>
    </example>
 *
 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy,
 *     then the `disabled` attribute will be set on the element
 */


/**
 * @ngdoc directive
 * @name ngChecked
 * @restrict A
 * @priority 100
 *
 * @description
 * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy.
 *
 * Note that this directive should not be used together with {@link ngModel `ngModel`},
 * as this can lead to unexpected behavior.
 *
 * A special directive is necessary because we cannot use interpolation inside the `checked`
 * attribute. See the {@link guide/interpolation interpolation guide} for more info.
 *
 * @example
    <example name="ng-checked">
      <file name="index.html">
        <label>Check me to check both: <input type="checkbox" ng-model="leader"></label><br/>
        <input id="checkFollower" type="checkbox" ng-checked="leader" aria-label="Follower input">
      </file>
      <file name="protractor.js" type="protractor">
        it('should check both checkBoxes', function() {
          expect(element(by.id('checkFollower')).getAttribute('checked')).toBeFalsy();
          element(by.model('leader')).click();
          expect(element(by.id('checkFollower')).getAttribute('checked')).toBeTruthy();
        });
      </file>
    </example>
 *
 * @element INPUT
 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy,
 *     then the `checked` attribute will be set on the element
 */


/**
 * @ngdoc directive
 * @name ngReadonly
 * @restrict A
 * @priority 100
 *
 * @description
 *
 * Sets the `readonly` attribute on the element, if the expression inside `ngReadonly` is truthy.
 * Note that `readonly` applies only to `input` elements with specific types. [See the input docs on
 * MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-readonly) for more information.
 *
 * A special directive is necessary because we cannot use interpolation inside the `readonly`
 * attribute. See the {@link guide/interpolation interpolation guide} for more info.
 *
 * @example
    <example name="ng-readonly">
      <file name="index.html">
        <label>Check me to make text readonly: <input type="checkbox" ng-model="checked"></label><br/>
        <input type="text" ng-readonly="checked" value="I'm AngularJS" aria-label="Readonly field" />
      </file>
      <file name="protractor.js" type="protractor">
        it('should toggle readonly attr', function() {
          expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy();
          element(by.model('checked')).click();
          expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy();
        });
      </file>
    </example>
 *
 * @element INPUT
 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy,
 *     then special attribute "readonly" will be set on the element
 */


/**
 * @ngdoc directive
 * @name ngSelected
 * @restrict A
 * @priority 100
 *
 * @description
 *
 * Sets the `selected` attribute on the element, if the expression inside `ngSelected` is truthy.
 *
 * A special directive is necessary because we cannot use interpolation inside the `selected`
 * attribute. See the {@link guide/interpolation interpolation guide} for more info.
 *
 * <div class="alert alert-warning">
 *   **Note:** `ngSelected` does not interact with the `select` and `ngModel` directives, it only
 *   sets the `selected` attribute on the element. If you are using `ngModel` on the select, you
 *   should not use `ngSelected` on the options, as `ngModel` will set the select value and
 *   selected options.
 * </div>
 *
 * @example
    <example name="ng-selected">
      <file name="index.html">
        <label>Check me to select: <input type="checkbox" ng-model="selected"></label><br/>
        <select aria-label="ngSelected demo">
          <option>Hello!</option>
          <option id="greet" ng-selected="selected">Greetings!</option>
        </select>
      </file>
      <file name="protractor.js" type="protractor">
        it('should select Greetings!', function() {
          expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy();
          element(by.model('selected')).click();
          expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy();
        });
      </file>
    </example>
 *
 * @element OPTION
 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy,
 *     then special attribute "selected" will be set on the element
 */

/**
 * @ngdoc directive
 * @name ngOpen
 * @restrict A
 * @priority 100
 *
 * @description
 *
 * Sets the `open` attribute on the element, if the expression inside `ngOpen` is truthy.
 *
 * A special directive is necessary because we cannot use interpolation inside the `open`
 * attribute. See the {@link guide/interpolation interpolation guide} for more info.
 *
 * ## A note about browser compatibility
 *
 * Internet Explorer and Edge do not support the `details` element, it is
 * recommended to use {@link ng.ngShow} and {@link ng.ngHide} instead.
 *
 * @example
     <example name="ng-open">
       <file name="index.html">
         <label>Toggle details: <input type="checkbox" ng-model="open"></label><br/>
         <details id="details" ng-open="open">
            <summary>List</summary>
            <ul>
              <li>Apple</li>
              <li>Orange</li>
              <li>Durian</li>
            </ul>
         </details>
       </file>
       <file name="protractor.js" type="protractor">
         it('should toggle open', function() {
           expect(element(by.id('details')).getAttribute('open')).toBeFalsy();
           element(by.model('open')).click();
           expect(element(by.id('details')).getAttribute('open')).toBeTruthy();
         });
       </file>
     </example>
 *
 * @element DETAILS
 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy,
 *     then special attribute "open" will be set on the element
 */

var ngAttributeAliasDirectives = {};

// boolean attrs are evaluated
forEach(BOOLEAN_ATTR, function(propName, attrName) {
  // binding to multiple is not supported
  if (propName === 'multiple') return;

  function defaultLinkFn(scope, element, attr) {
    scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
      attr.$set(attrName, !!value);
    });
  }

  var normalized = directiveNormalize('ng-' + attrName);
  var linkFn = defaultLinkFn;

  if (propName === 'checked') {
    linkFn = function(scope, element, attr) {
      // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input
      if (attr.ngModel !== attr[normalized]) {
        defaultLinkFn(scope, element, attr);
      }
    };
  }

  ngAttributeAliasDirectives[normalized] = function() {
    return {
      restrict: 'A',
      priority: 100,
      link: linkFn
    };
  };
});

// aliased input attrs are evaluated
forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) {
  ngAttributeAliasDirectives[ngAttr] = function() {
    return {
      priority: 100,
      link: function(scope, element, attr) {
        //special case ngPattern when a literal regular expression value
        //is used as the expression (this way we don't have to watch anything).
        if (ngAttr === 'ngPattern' && attr.ngPattern.charAt(0) === '/') {
          var match = attr.ngPattern.match(REGEX_STRING_REGEXP);
          if (match) {
            attr.$set('ngPattern', new RegExp(match[1], match[2]));
            return;
          }
        }

        scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) {
          attr.$set(ngAttr, value);
        });
      }
    };
  };
});

// ng-src, ng-srcset, ng-href are interpolated
forEach(['src', 'srcset', 'href'], function(attrName) {
  var normalized = directiveNormalize('ng-' + attrName);
  ngAttributeAliasDirectives[normalized] = ['$sce', function($sce) {
    return {
      priority: 99, // it needs to run after the attributes are interpolated
      link: function(scope, element, attr) {
        var propName = attrName,
            name = attrName;

        if (attrName === 'href' &&
            toString.call(element.prop('href')) === '[object SVGAnimatedString]') {
          name = 'xlinkHref';
          attr.$attr[name] = 'xlink:href';
          propName = null;
        }

        // We need to sanitize the url at least once, in case it is a constant
        // non-interpolated attribute.
        attr.$set(normalized, $sce.getTrustedMediaUrl(attr[normalized]));

        attr.$observe(normalized, function(value) {
          if (!value) {
            if (attrName === 'href') {
              attr.$set(name, null);
            }
            return;
          }

          attr.$set(name, value);

          // Support: IE 9-11 only
          // On IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
          // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
          // to set the property as well to achieve the desired effect.
          // We use attr[attrName] value since $set might have sanitized the url.
          if (msie && propName) element.prop(propName, attr[name]);
        });
      }
    };
  }];
});

/* global -nullFormCtrl, -PENDING_CLASS, -SUBMITTED_CLASS
 */
var nullFormCtrl = {
  $addControl: noop,
  $getControls: valueFn([]),
  $$renameControl: nullFormRenameControl,
  $removeControl: noop,
  $setValidity: noop,
  $setDirty: noop,
  $setPristine: noop,
  $setSubmitted: noop,
  $$setSubmitted: noop
},
PENDING_CLASS = 'ng-pending',
SUBMITTED_CLASS = 'ng-submitted';

function nullFormRenameControl(control, name) {
  control.$name = name;
}

/**
 * @ngdoc type
 * @name form.FormController
 *
 * @property {boolean} $pristine True if user has not interacted with the form yet.
 * @property {boolean} $dirty True if user has already interacted with the form.
 * @property {boolean} $valid True if all of the containing forms and controls are valid.
 * @property {boolean} $invalid True if at least one containing control or form is invalid.
 * @property {boolean} $submitted True if user has submitted the form even if its invalid.
 *
 * @property {Object} $pending An object hash, containing references to controls or forms with
 *  pending validators, where:
 *
 *  - keys are validations tokens (error names).
 *  - values are arrays of controls or forms that have a pending validator for the given error name.
 *
 * See {@link form.FormController#$error $error} for a list of built-in validation tokens.
 *
 * @property {Object} $error An object hash, containing references to controls or forms with failing
 *  validators, where:
 *
 *  - keys are validation tokens (error names),
 *  - values are arrays of controls or forms that have a failing validator for the given error name.
 *
 *  Built-in validation tokens:
 *  - `email`
 *  - `max`
 *  - `maxlength`
 *  - `min`
 *  - `minlength`
 *  - `number`
 *  - `pattern`
 *  - `required`
 *  - `url`
 *  - `date`
 *  - `datetimelocal`
 *  - `time`
 *  - `week`
 *  - `month`
 *
 * @description
 * `FormController` keeps track of all its controls and nested forms as well as the state of them,
 * such as being valid/invalid or dirty/pristine.
 *
 * Each {@link ng.directive:form form} directive creates an instance
 * of `FormController`.
 *
 */
//asks for $scope to fool the BC controller module
FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate'];
function FormController($element, $attrs, $scope, $animate, $interpolate) {
  this.$$controls = [];

  // init state
  this.$error = {};
  this.$$success = {};
  this.$pending = undefined;
  this.$name = $interpolate($attrs.name || $attrs.ngForm || '')($scope);
  this.$dirty = false;
  this.$pristine = true;
  this.$valid = true;
  this.$invalid = false;
  this.$submitted = false;
  this.$$parentForm = nullFormCtrl;

  this.$$element = $element;
  this.$$animate = $animate;

  setupValidity(this);
}

FormController.prototype = {
  /**
   * @ngdoc method
   * @name form.FormController#$rollbackViewValue
   *
   * @description
   * Rollback all form controls pending updates to the `$modelValue`.
   *
   * Updates may be pending by a debounced event or because the input is waiting for a some future
   * event defined in `ng-model-options`. This method is typically needed by the reset button of
   * a form that uses `ng-model-options` to pend updates.
   */
  $rollbackViewValue: function() {
    forEach(this.$$controls, function(control) {
      control.$rollbackViewValue();
    });
  },

  /**
   * @ngdoc method
   * @name form.FormController#$commitViewValue
   *
   * @description
   * Commit all form controls pending updates to the `$modelValue`.
   *
   * Updates may be pending by a debounced event or because the input is waiting for a some future
   * event defined in `ng-model-options`. This method is rarely needed as `NgModelController`
   * usually handles calling this in response to input events.
   */
  $commitViewValue: function() {
    forEach(this.$$controls, function(control) {
      control.$commitViewValue();
    });
  },

  /**
   * @ngdoc method
   * @name form.FormController#$addControl
   * @param {object} control control object, either a {@link form.FormController} or an
   * {@link ngModel.NgModelController}
   *
   * @description
   * Register a control with the form. Input elements using ngModelController do this automatically
   * when they are linked.
   *
   * Note that the current state of the control will not be reflected on the new parent form. This
   * is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine`
   * state.
   *
   * However, if the method is used programmatically, for example by adding dynamically created controls,
   * or controls that have been previously removed without destroying their corresponding DOM element,
   * it's the developers responsibility to make sure the current state propagates to the parent form.
   *
   * For example, if an input control is added that is already `$dirty` and has `$error` properties,
   * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form.
   */
  $addControl: function(control) {
    // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored
    // and not added to the scope.  Now we throw an error.
    assertNotHasOwnProperty(control.$name, 'input');
    this.$$controls.push(control);

    if (control.$name) {
      this[control.$name] = control;
    }

    control.$$parentForm = this;
  },

  /**
   * @ngdoc method
   * @name form.FormController#$getControls
   * @returns {Array} the controls that are currently part of this form
   *
   * @description
   * This method returns a **shallow copy** of the controls that are currently part of this form.
   * The controls can be instances of {@link form.FormController `FormController`}
   * ({@link ngForm "child-forms"}) and of {@link ngModel.NgModelController `NgModelController`}.
   * If you need access to the controls of child-forms, you have to call `$getControls()`
   * recursively on them.
   * This can be used for example to iterate over all controls to validate them.
   *
   * The controls can be accessed normally, but adding to, or removing controls from the array has
   * no effect on the form. Instead, use {@link form.FormController#$addControl `$addControl()`} and
   * {@link form.FormController#$removeControl `$removeControl()`} for this use-case.
   * Likewise, adding a control to, or removing a control from the form is not reflected
   * in the shallow copy. That means you should get a fresh copy from `$getControls()` every time
   * you need access to the controls.
   */
  $getControls: function() {
    return shallowCopy(this.$$controls);
  },

  // Private API: rename a form control
  $$renameControl: function(control, newName) {
    var oldName = control.$name;

    if (this[oldName] === control) {
      delete this[oldName];
    }
    this[newName] = control;
    control.$name = newName;
  },

  /**
   * @ngdoc method
   * @name form.FormController#$removeControl
   * @param {object} control control object, either a {@link form.FormController} or an
   * {@link ngModel.NgModelController}
   *
   * @description
   * Deregister a control from the form.
   *
   * Input elements using ngModelController do this automatically when they are destroyed.
   *
   * Note that only the removed control's validation state (`$errors`etc.) will be removed from the
   * form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be
   * different from case to case. For example, removing the only `$dirty` control from a form may or
   * may not mean that the form is still `$dirty`.
   */
  $removeControl: function(control) {
    if (control.$name && this[control.$name] === control) {
      delete this[control.$name];
    }
    forEach(this.$pending, function(value, name) {
      // eslint-disable-next-line no-invalid-this
      this.$setValidity(name, null, control);
    }, this);
    forEach(this.$error, function(value, name) {
      // eslint-disable-next-line no-invalid-this
      this.$setValidity(name, null, control);
    }, this);
    forEach(this.$$success, function(value, name) {
      // eslint-disable-next-line no-invalid-this
      this.$setValidity(name, null, control);
    }, this);

    arrayRemove(this.$$controls, control);
    control.$$parentForm = nullFormCtrl;
  },

  /**
   * @ngdoc method
   * @name form.FormController#$setDirty
   *
   * @description
   * Sets the form to a dirty state.
   *
   * This method can be called to add the 'ng-dirty' class and set the form to a dirty
   * state (ng-dirty class). This method will also propagate to parent forms.
   */
  $setDirty: function() {
    this.$$animate.removeClass(this.$$element, PRISTINE_CLASS);
    this.$$animate.addClass(this.$$element, DIRTY_CLASS);
    this.$dirty = true;
    this.$pristine = false;
    this.$$parentForm.$setDirty();
  },

  /**
   * @ngdoc method
   * @name form.FormController#$setPristine
   *
   * @description
   * Sets the form to its pristine state.
   *
   * This method sets the form's `$pristine` state to true, the `$dirty` state to false, removes
   * the `ng-dirty` class and adds the `ng-pristine` class. Additionally, it sets the `$submitted`
   * state to false.
   *
   * This method will also propagate to all the controls contained in this form.
   *
   * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after
   * saving or resetting it.
   */
  $setPristine: function() {
    this.$$animate.setClass(this.$$element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS);
    this.$dirty = false;
    this.$pristine = true;
    this.$submitted = false;
    forEach(this.$$controls, function(control) {
      control.$setPristine();
    });
  },

  /**
   * @ngdoc method
   * @name form.FormController#$setUntouched
   *
   * @description
   * Sets the form to its untouched state.
   *
   * This method can be called to remove the 'ng-touched' class and set the form controls to their
   * untouched state (ng-untouched class).
   *
   * Setting a form controls back to their untouched state is often useful when setting the form
   * back to its pristine state.
   */
  $setUntouched: function() {
    forEach(this.$$controls, function(control) {
      control.$setUntouched();
    });
  },

  /**
   * @ngdoc method
   * @name form.FormController#$setSubmitted
   *
   * @description
   * Sets the form to its `$submitted` state. This will also set `$submitted` on all child and
   * parent forms of the form.
   */
  $setSubmitted: function() {
    var rootForm = this;
    while (rootForm.$$parentForm && (rootForm.$$parentForm !== nullFormCtrl)) {
      rootForm = rootForm.$$parentForm;
    }
    rootForm.$$setSubmitted();
  },

  $$setSubmitted: function() {
    this.$$animate.addClass(this.$$element, SUBMITTED_CLASS);
    this.$submitted = true;
    forEach(this.$$controls, function(control) {
      if (control.$$setSubmitted) {
        control.$$setSubmitted();
      }
    });
  }
};

/**
 * @ngdoc method
 * @name form.FormController#$setValidity
 *
 * @description
 * Change the validity state of the form, and notify the parent form (if any).
 *
 * Application developers will rarely need to call this method directly. It is used internally, by
 * {@link ngModel.NgModelController#$setValidity NgModelController.$setValidity()}, to propagate a
 * control's validity state to the parent `FormController`.
 *
 * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be
 *        assigned to either `$error[validationErrorKey]` or `$pending[validationErrorKey]` (for
 *        unfulfilled `$asyncValidators`), so that it is available for data-binding. The
 *        `validationErrorKey` should be in camelCase and will get converted into dash-case for
 *        class name. Example: `myError` will result in `ng-valid-my-error` and
 *        `ng-invalid-my-error` classes and can be bound to as `{{ someForm.$error.myError }}`.
 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending
 *        (undefined),  or skipped (null). Pending is used for unfulfilled `$asyncValidators`.
 *        Skipped is used by AngularJS when validators do not run because of parse errors and when
 *        `$asyncValidators` do not run because any of the `$validators` failed.
 * @param {NgModelController | FormController} controller - The controller whose validity state is
 *        triggering the change.
 */
addSetValidityMethod({
  clazz: FormController,
  set: function(object, property, controller) {
    var list = object[property];
    if (!list) {
      object[property] = [controller];
    } else {
      var index = list.indexOf(controller);
      if (index === -1) {
        list.push(controller);
      }
    }
  },
  unset: function(object, property, controller) {
    var list = object[property];
    if (!list) {
      return;
    }
    arrayRemove(list, controller);
    if (list.length === 0) {
      delete object[property];
    }
  }
});

/**
 * @ngdoc directive
 * @name ngForm
 * @restrict EAC
 *
 * @description
 * Helper directive that makes it possible to create control groups inside a
 * {@link ng.directive:form `form`} directive.
 * These "child forms" can be used, for example, to determine the validity of a sub-group of
 * controls.
 *
 * <div class="alert alert-danger">
 * **Note**: `ngForm` cannot be used as a replacement for `<form>`, because it lacks its
 * [built-in HTML functionality](https://html.spec.whatwg.org/#the-form-element).
 * Specifically, you cannot submit `ngForm` like a `<form>` tag. That means,
 * you cannot send data to the server with `ngForm`, or integrate it with
 * {@link ng.directive:ngSubmit `ngSubmit`}.
 * </div>
 *
 * @param {string=} ngForm|name Name of the form. If specified, the form controller will
 *                              be published into the related scope, under this name.
 *
 */

 /**
 * @ngdoc directive
 * @name form
 * @restrict E
 *
 * @description
 * Directive that instantiates
 * {@link form.FormController FormController}.
 *
 * If the `name` attribute is specified, the form controller is published onto the current scope under
 * this name.
 *
 * ## Alias: {@link ng.directive:ngForm `ngForm`}
 *
 * In AngularJS, forms can be nested. This means that the outer form is valid when all of the child
 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so
 * AngularJS provides the {@link ng.directive:ngForm `ngForm`} directive, which behaves identically to
 * `form` but can be nested. Nested forms can be useful, for example, if the validity of a sub-group
 * of controls needs to be determined.
 *
 * ## CSS classes
 *  - `ng-valid` is set if the form is valid.
 *  - `ng-invalid` is set if the form is invalid.
 *  - `ng-pending` is set if the form is pending.
 *  - `ng-pristine` is set if the form is pristine.
 *  - `ng-dirty` is set if the form is dirty.
 *  - `ng-submitted` is set if the form was submitted.
 *
 * Keep in mind that ngAnimate can detect each of these classes when added and removed.
 *
 *
 * ## Submitting a form and preventing the default action
 *
 * Since the role of forms in client-side AngularJS applications is different than in classical
 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full
 * page reload that sends the data to the server. Instead some javascript logic should be triggered
 * to handle the form submission in an application-specific way.
 *
 * For this reason, AngularJS prevents the default action (form submission to the server) unless the
 * `<form>` element has an `action` attribute specified.
 *
 * You can use one of the following two ways to specify what javascript method should be called when
 * a form is submitted:
 *
 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element
 * - {@link ng.directive:ngClick ngClick} directive on the first
  *  button or input field of type submit (input[type=submit])
 *
 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit}
 * or {@link ng.directive:ngClick ngClick} directives.
 * This is because of the following form submission rules in the HTML specification:
 *
 * - If a form has only one input field then hitting enter in this field triggers form submit
 * (`ngSubmit`)
 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter
 * doesn't trigger submit
 * - if a form has one or more input fields and one or more buttons or input[type=submit] then
 * hitting enter in any of the input fields will trigger the click handler on the *first* button or
 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`)
 *
 * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is
 * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
 * to have access to the updated model.
 *
 * @animations
 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed.
 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any
 * other validations that are performed within the form. Animations in ngForm are similar to how
 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well
 * as JS animations.
 *
 * The following example shows a simple way to utilize CSS transitions to style a form element
 * that has been rendered as invalid after it has been validated:
 *
 * <pre>
 * //be sure to include ngAnimate as a module to hook into more
 * //advanced animations
 * .my-form {
 *   transition:0.5s linear all;
 *   background: white;
 * }
 * .my-form.ng-invalid {
 *   background: red;
 *   color:white;
 * }
 * </pre>
 *
 * @example
    <example name="ng-form" deps="angular-animate.js" animations="true" fixBase="true" module="formExample">
      <file name="index.html">
       <script>
         angular.module('formExample', [])
           .controller('FormController', ['$scope', function($scope) {
             $scope.userType = 'guest';
           }]);
       </script>
       <style>
        .my-form {
          transition:all linear 0.5s;
          background: transparent;
        }
        .my-form.ng-invalid {
          background: red;
        }
       </style>
       <form name="myForm" ng-controller="FormController" class="my-form">
         userType: <input name="input" ng-model="userType" required>
         <span class="error" ng-show="myForm.input.$error.required">Required!</span><br>
         <code>userType = {{userType}}</code><br>
         <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br>
         <code>myForm.input.$error = {{myForm.input.$error}}</code><br>
         <code>myForm.$valid = {{myForm.$valid}}</code><br>
         <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br>
        </form>
      </file>
      <file name="protractor.js" type="protractor">
        it('should initialize to model', function() {
          var userType = element(by.binding('userType'));
          var valid = element(by.binding('myForm.input.$valid'));

          expect(userType.getText()).toContain('guest');
          expect(valid.getText()).toContain('true');
        });

        it('should be invalid if empty', function() {
          var userType = element(by.binding('userType'));
          var valid = element(by.binding('myForm.input.$valid'));
          var userInput = element(by.model('userType'));

          userInput.clear();
          userInput.sendKeys('');

          expect(userType.getText()).toEqual('userType =');
          expect(valid.getText()).toContain('false');
        });
      </file>
    </example>
 *
 * @param {string=} name Name of the form. If specified, the form controller will be published into
 *                       related scope, under this name.
 */
var formDirectiveFactory = function(isNgForm) {
  return ['$timeout', '$parse', function($timeout, $parse) {
    var formDirective = {
      name: 'form',
      restrict: isNgForm ? 'EAC' : 'E',
      require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form
      controller: FormController,
      compile: function ngFormCompile(formElement, attr) {
        // Setup initial state of the control
        formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS);

        var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false);

        return {
          pre: function ngFormPreLink(scope, formElement, attr, ctrls) {
            var controller = ctrls[0];

            // if `action` attr is not present on the form, prevent the default action (submission)
            if (!('action' in attr)) {
              // we can't use jq events because if a form is destroyed during submission the default
              // action is not prevented. see #1238
              //
              // IE 9 is not affected because it doesn't fire a submit event and try to do a full
              // page reload if the form was destroyed by submission of the form via a click handler
              // on a button in the form. Looks like an IE9 specific bug.
              var handleFormSubmission = function(event) {
                scope.$apply(function() {
                  controller.$commitViewValue();
                  controller.$setSubmitted();
                });

                event.preventDefault();
              };

              formElement[0].addEventListener('submit', handleFormSubmission);

              // unregister the preventDefault listener so that we don't not leak memory but in a
              // way that will achieve the prevention of the default action.
              formElement.on('$destroy', function() {
                $timeout(function() {
                  formElement[0].removeEventListener('submit', handleFormSubmission);
                }, 0, false);
              });
            }

            var parentFormCtrl = ctrls[1] || controller.$$parentForm;
            parentFormCtrl.$addControl(controller);

            var setter = nameAttr ? getSetter(controller.$name) : noop;

            if (nameAttr) {
              setter(scope, controller);
              attr.$observe(nameAttr, function(newValue) {
                if (controller.$name === newValue) return;
                setter(scope, undefined);
                controller.$$parentForm.$$renameControl(controller, newValue);
                setter = getSetter(controller.$name);
                setter(scope, controller);
              });
            }
            formElement.on('$destroy', function() {
              controller.$$parentForm.$removeControl(controller);
              setter(scope, undefined);
              extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
            });
          }
        };
      }
    };

    return formDirective;

    function getSetter(expression) {
      if (expression === '') {
        //create an assignable expression, so forms with an empty name can be renamed later
        return $parse('this[""]').assign;
      }
      return $parse(expression).assign || noop;
    }
  }];
};

var formDirective = formDirectiveFactory();
var ngFormDirective = formDirectiveFactory(true);



// helper methods
function setupValidity(instance) {
  instance.$$classCache = {};
  instance.$$classCache[INVALID_CLASS] = !(instance.$$classCache[VALID_CLASS] = instance.$$element.hasClass(VALID_CLASS));
}
function addSetValidityMethod(context) {
  var clazz = context.clazz,
      set = context.set,
      unset = context.unset;

  clazz.prototype.$setValidity = function(validationErrorKey, state, controller) {
    if (isUndefined(state)) {
      createAndSet(this, '$pending', validationErrorKey, controller);
    } else {
      unsetAndCleanup(this, '$pending', validationErrorKey, controller);
    }
    if (!isBoolean(state)) {
      unset(this.$error, validationErrorKey, controller);
      unset(this.$$success, validationErrorKey, controller);
    } else {
      if (state) {
        unset(this.$error, validationErrorKey, controller);
        set(this.$$success, validationErrorKey, controller);
      } else {
        set(this.$error, validationErrorKey, controller);
        unset(this.$$success, validationErrorKey, controller);
      }
    }
    if (this.$pending) {
      cachedToggleClass(this, PENDING_CLASS, true);
      this.$valid = this.$invalid = undefined;
      toggleValidationCss(this, '', null);
    } else {
      cachedToggleClass(this, PENDING_CLASS, false);
      this.$valid = isObjectEmpty(this.$error);
      this.$invalid = !this.$valid;
      toggleValidationCss(this, '', this.$valid);
    }

    // re-read the state as the set/unset methods could have
    // combined state in this.$error[validationError] (used for forms),
    // where setting/unsetting only increments/decrements the value,
    // and does not replace it.
    var combinedState;
    if (this.$pending && this.$pending[validationErrorKey]) {
      combinedState = undefined;
    } else if (this.$error[validationErrorKey]) {
      combinedState = false;
    } else if (this.$$success[validationErrorKey]) {
      combinedState = true;
    } else {
      combinedState = null;
    }

    toggleValidationCss(this, validationErrorKey, combinedState);
    this.$$parentForm.$setValidity(validationErrorKey, combinedState, this);
  };

  function createAndSet(ctrl, name, value, controller) {
    if (!ctrl[name]) {
      ctrl[name] = {};
    }
    set(ctrl[name], value, controller);
  }

  function unsetAndCleanup(ctrl, name, value, controller) {
    if (ctrl[name]) {
      unset(ctrl[name], value, controller);
    }
    if (isObjectEmpty(ctrl[name])) {
      ctrl[name] = undefined;
    }
  }

  function cachedToggleClass(ctrl, className, switchValue) {
    if (switchValue && !ctrl.$$classCache[className]) {
      ctrl.$$animate.addClass(ctrl.$$element, className);
      ctrl.$$classCache[className] = true;
    } else if (!switchValue && ctrl.$$classCache[className]) {
      ctrl.$$animate.removeClass(ctrl.$$element, className);
      ctrl.$$classCache[className] = false;
    }
  }

  function toggleValidationCss(ctrl, validationErrorKey, isValid) {
    validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';

    cachedToggleClass(ctrl, VALID_CLASS + validationErrorKey, isValid === true);
    cachedToggleClass(ctrl, INVALID_CLASS + validationErrorKey, isValid === false);
  }
}

function isObjectEmpty(obj) {
  if (obj) {
    for (var prop in obj) {
      if (obj.hasOwnProperty(prop)) {
        return false;
      }
    }
  }
  return true;
}

/* global
  VALID_CLASS: false,
  INVALID_CLASS: false,
  PRISTINE_CLASS: false,
  DIRTY_CLASS: false,
  ngModelMinErr: false
*/

// Regex code was initially obtained from SO prior to modification: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231
var ISO_DATE_REGEXP = /^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)$/;
// See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987)
// Note: We are being more lenient, because browsers are too.
//   1. Scheme
//   2. Slashes
//   3. Username
//   4. Password
//   5. Hostname
//   6. Port
//   7. Path
//   8. Query
//   9. Fragment
//                 1111111111111111 222   333333    44444        55555555555555555555555     666     77777777     8888888     999
var URL_REGEXP = /^[a-z][a-z\d.+-]*:\/{0,10}(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i;
// eslint-disable-next-line max-len
var EMAIL_REGEXP = /^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/;
var NUMBER_REGEXP = /^\s*(-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/;
var DATE_REGEXP = /^(\d{4,})-(\d{2})-(\d{2})$/;
var DATETIMELOCAL_REGEXP = /^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
var WEEK_REGEXP = /^(\d{4,})-W(\d\d)$/;
var MONTH_REGEXP = /^(\d{4,})-(\d\d)$/;
var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;

var PARTIAL_VALIDATION_EVENTS = 'keydown wheel mousedown';
var PARTIAL_VALIDATION_TYPES = createMap();
forEach('date,datetime-local,month,time,week'.split(','), function(type) {
  PARTIAL_VALIDATION_TYPES[type] = true;
});

var inputType = {

  /**
   * @ngdoc input
   * @name input[text]
   *
   * @description
   * Standard HTML text input with AngularJS data binding, inherited by most of the `input` elements.
   *
   *
   * @param {string} ngModel Assignable AngularJS expression to data-bind to.
   * @param {string=} name Property name of the form under which the control is published.
   * @param {string=} required Adds `required` validation error key if the value is not entered.
   * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
   *    the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
   *    `required` when you want to data-bind to the `required` attribute.
   * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
   *    minlength.
   * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
   *    maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
   *    any length.
   * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
   *    that contains the regular expression body that will be converted to a regular expression
   *    as in the ngPattern directive.
   * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
   *    does not match a RegExp found by evaluating the AngularJS expression given in the attribute value.
   *    If the expression evaluates to a RegExp object, then this is used directly.
   *    If the expression evaluates to a string, then it will be converted to a RegExp
   *    after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
   *    `new RegExp('^abc$')`.<br />
   *    **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
   *    start at the index of the last search's match, thus not taking the whole input value into
   *    account.
   * @param {string=} ngChange AngularJS expression to be executed when input changes due to user
   *    interaction with the input element.
   * @param {boolean=} [ngTrim=true] If set to false AngularJS will not automatically trim the input.
   *    This parameter is ignored for input[type=password] controls, which will never trim the
   *    input.
   *
   * @example
      <example name="text-input-directive" module="textInputExample">
        <file name="index.html">
         <script>
           angular.module('textInputExample', [])
             .controller('ExampleController', ['$scope', function($scope) {
               $scope.example = {
                 text: 'guest',
                 word: /^\s*\w*\s*$/
               };
             }]);
         </script>
         <form name="myForm" ng-controller="ExampleController">
           <label>Single word:
             <input type="text" name="input" ng-model="example.text"
                    ng-pattern="example.word" required ng-trim="false">
           </label>
           <div role="alert">
             <span class="error" ng-show="myForm.input.$error.required">
               Required!</span>
             <span class="error" ng-show="myForm.input.$error.pattern">
               Single word only!</span>
           </div>
           <code>text = {{example.text}}</code><br/>
           <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br/>
           <code>myForm.input.$error = {{myForm.input.$error}}</code><br/>
           <code>myForm.$valid = {{myForm.$valid}}</code><br/>
           <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br/>
          </form>
        </file>
        <file name="protractor.js" type="protractor">
          var text = element(by.binding('example.text'));
          var valid = element(by.binding('myForm.input.$valid'));
          var input = element(by.model('example.text'));

          it('should initialize to model', function() {
            expect(text.getText()).toContain('guest');
            expect(valid.getText()).toContain('true');
          });

          it('should be invalid if empty', function() {
            input.clear();
            input.sendKeys('');

            expect(text.getText()).toEqual('text =');
            expect(valid.getText()).toContain('false');
          });

          it('should be invalid if multi word', function() {
            input.clear();
            input.sendKeys('hello world');

            expect(valid.getText()).toContain('false');
          });
        </file>
      </example>
   */
  'text': textInputType,

    /**
     * @ngdoc input
     * @name input[date]
     *
     * @description
     * Input with date validation and transformation. In browsers that do not yet support
     * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601
     * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many
     * modern browsers do not yet support this input type, it is important to provide cues to users on the
     * expected input format via a placeholder or label.
     *
     * The model must always be a Date object, otherwise AngularJS will throw an error.
     * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
     *
     * The timezone to be used to read/write the `Date` instance in the model can be defined using
     * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
     *
     * @param {string} ngModel Assignable AngularJS expression to data-bind to.
     * @param {string=} name Property name of the form under which the control is published.
     * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
     *   valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
     *   (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5
     *   constraint validation.
     * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be
     *   a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
     *   (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5
     *   constraint validation.
     * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string
     *   the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
     * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string
     *   the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
     * @param {string=} required Sets `required` validation error key if the value is not entered.
     * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
     *    the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
     *    `required` when you want to data-bind to the `required` attribute.
     * @param {string=} ngChange AngularJS expression to be executed when input changes due to user
     *    interaction with the input element.
     *
     * @example
     <example name="date-input-directive" module="dateInputExample">
     <file name="index.html">
       <script>
          angular.module('dateInputExample', [])
            .controller('DateController', ['$scope', function($scope) {
              $scope.example = {
                value: new Date(2013, 9, 22)
              };
            }]);
       </script>
       <form name="myForm" ng-controller="DateController as dateCtrl">
          <label for="exampleInput">Pick a date in 2013:</label>
          <input type="date" id="exampleInput" name="input" ng-model="example.value"
              placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required />
          <div role="alert">
            <span class="error" ng-show="myForm.input.$error.required">
                Required!</span>
            <span class="error" ng-show="myForm.input.$error.date">
                Not a valid date!</span>
           </div>
           <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/>
           <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
           <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
           <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
           <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
       </form>
     </file>
     <file name="protractor.js" type="protractor">
        var value = element(by.binding('example.value | date: "yyyy-MM-dd"'));
        var valid = element(by.binding('myForm.input.$valid'));

        // currently protractor/webdriver does not support
        // sending keys to all known HTML5 input controls
        // for various browsers (see https://github.com/angular/protractor/issues/562).
        function setInput(val) {
          // set the value of the element and force validation.
          var scr = "var ipt = document.getElementById('exampleInput'); " +
          "ipt.value = '" + val + "';" +
          "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
          browser.executeScript(scr);
        }

        it('should initialize to model', function() {
          expect(value.getText()).toContain('2013-10-22');
          expect(valid.getText()).toContain('myForm.input.$valid = true');
        });

        it('should be invalid if empty', function() {
          setInput('');
          expect(value.getText()).toEqual('value =');
          expect(valid.getText()).toContain('myForm.input.$valid = false');
        });

        it('should be invalid if over max', function() {
          setInput('2015-01-01');
          expect(value.getText()).toContain('');
          expect(valid.getText()).toContain('myForm.input.$valid = false');
        });
     </file>
     </example>
     */
  'date': createDateInputType('date', DATE_REGEXP,
         createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']),
         'yyyy-MM-dd'),

   /**
    * @ngdoc input
    * @name input[datetime-local]
    *
    * @description
    * Input with datetime validation and transformation. In browsers that do not yet support
    * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
    * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`.
    *
    * The model must always be a Date object, otherwise AngularJS will throw an error.
    * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
    *
    * The timezone to be used to read/write the `Date` instance in the model can be defined using
    * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
    *
    * The format of the displayed time can be adjusted with the
    * {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions} `timeSecondsFormat`
    * and `timeStripZeroSeconds`.
    *
    * @param {string} ngModel Assignable AngularJS expression to data-bind to.
    * @param {string=} name Property name of the form under which the control is published.
    * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
    *   This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
    *   inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
    *   Note that `min` will also add native HTML5 constraint validation.
    * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
    *   This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
    *   inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
    *   Note that `max` will also add native HTML5 constraint validation.
    * @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string
    *   the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
    * @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string
    *   the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
    * @param {string=} required Sets `required` validation error key if the value is not entered.
    * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
    *    the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
    *    `required` when you want to data-bind to the `required` attribute.
    * @param {string=} ngChange AngularJS expression to be executed when input changes due to user
    *    interaction with the input element.
    *
    * @example
    <example name="datetimelocal-input-directive" module="dateExample">
    <file name="index.html">
      <script>
        angular.module('dateExample', [])
          .controller('DateController', ['$scope', function($scope) {
            $scope.example = {
              value: new Date(2010, 11, 28, 14, 57)
            };
          }]);
      </script>
      <form name="myForm" ng-controller="DateController as dateCtrl">
        <label for="exampleInput">Pick a date between in 2013:</label>
        <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value"
            placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required />
        <div role="alert">
          <span class="error" ng-show="myForm.input.$error.required">
              Required!</span>
          <span class="error" ng-show="myForm.input.$error.datetimelocal">
              Not a valid date!</span>
        </div>
        <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/>
        <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
        <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
        <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
        <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
      </form>
    </file>
    <file name="protractor.js" type="protractor">
      var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"'));
      var valid = element(by.binding('myForm.input.$valid'));

      // currently protractor/webdriver does not support
      // sending keys to all known HTML5 input controls
      // for various browsers (https://github.com/angular/protractor/issues/562).
      function setInput(val) {
        // set the value of the element and force validation.
        var scr = "var ipt = document.getElementById('exampleInput'); " +
        "ipt.value = '" + val + "';" +
        "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
        browser.executeScript(scr);
      }

      it('should initialize to model', function() {
        expect(value.getText()).toContain('2010-12-28T14:57:00');
        expect(valid.getText()).toContain('myForm.input.$valid = true');
      });

      it('should be invalid if empty', function() {
        setInput('');
        expect(value.getText()).toEqual('value =');
        expect(valid.getText()).toContain('myForm.input.$valid = false');
      });

      it('should be invalid if over max', function() {
        setInput('2015-01-01T23:59:00');
        expect(value.getText()).toContain('');
        expect(valid.getText()).toContain('myForm.input.$valid = false');
      });
    </file>
    </example>
    */
  'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP,
      createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']),
      'yyyy-MM-ddTHH:mm:ss.sss'),

  /**
   * @ngdoc input
   * @name input[time]
   *
   * @description
   * Input with time validation and transformation. In browsers that do not yet support
   * the HTML5 time input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
   * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a
   * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`.
   *
   * The model must always be a Date object, otherwise AngularJS will throw an error.
   * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
   *
   * The timezone to be used to read/write the `Date` instance in the model can be defined using
   * {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions}. By default,
   * this is the timezone of the browser.
   *
   * The format of the displayed time can be adjusted with the
   * {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions} `timeSecondsFormat`
   * and `timeStripZeroSeconds`.
   *
   * @param {string} ngModel Assignable AngularJS expression to data-bind to.
   * @param {string=} name Property name of the form under which the control is published.
   * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
   *   This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
   *   attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add
   *   native HTML5 constraint validation.
   * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
   *   This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
   *   attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add
   *   native HTML5 constraint validation.
   * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the
   *   `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
   * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the
   *   `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
   * @param {string=} required Sets `required` validation error key if the value is not entered.
   * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
   *    the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
   *    `required` when you want to data-bind to the `required` attribute.
   * @param {string=} ngChange AngularJS expression to be executed when input changes due to user
   *    interaction with the input element.
   *
   * @example
   <example name="time-input-directive" module="timeExample">
   <file name="index.html">
     <script>
      angular.module('timeExample', [])
        .controller('DateController', ['$scope', function($scope) {
          $scope.example = {
            value: new Date(1970, 0, 1, 14, 57, 0)
          };
        }]);
     </script>
     <form name="myForm" ng-controller="DateController as dateCtrl">
        <label for="exampleInput">Pick a time between 8am and 5pm:</label>
        <input type="time" id="exampleInput" name="input" ng-model="example.value"
            placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required />
        <div role="alert">
          <span class="error" ng-show="myForm.input.$error.required">
              Required!</span>
          <span class="error" ng-show="myForm.input.$error.time">
              Not a valid date!</span>
        </div>
        <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/>
        <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
        <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
        <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
        <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
     </form>
   </file>
   <file name="protractor.js" type="protractor">
      var value = element(by.binding('example.value | date: "HH:mm:ss"'));
      var valid = element(by.binding('myForm.input.$valid'));

      // currently protractor/webdriver does not support
      // sending keys to all known HTML5 input controls
      // for various browsers (https://github.com/angular/protractor/issues/562).
      function setInput(val) {
        // set the value of the element and force validation.
        var scr = "var ipt = document.getElementById('exampleInput'); " +
        "ipt.value = '" + val + "';" +
        "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
        browser.executeScript(scr);
      }

      it('should initialize to model', function() {
        expect(value.getText()).toContain('14:57:00');
        expect(valid.getText()).toContain('myForm.input.$valid = true');
      });

      it('should be invalid if empty', function() {
        setInput('');
        expect(value.getText()).toEqual('value =');
        expect(valid.getText()).toContain('myForm.input.$valid = false');
      });

      it('should be invalid if over max', function() {
        setInput('23:59:00');
        expect(value.getText()).toContain('');
        expect(valid.getText()).toContain('myForm.input.$valid = false');
      });
   </file>
   </example>
   */
  'time': createDateInputType('time', TIME_REGEXP,
      createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']),
     'HH:mm:ss.sss'),

   /**
    * @ngdoc input
    * @name input[week]
    *
    * @description
    * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support
    * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
    * week format (yyyy-W##), for example: `2013-W02`.
    *
    * The model must always be a Date object, otherwise AngularJS will throw an error.
    * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
    *
    * The value of the resulting Date object will be set to Thursday at 00:00:00 of the requested week,
    * due to ISO-8601 week numbering standards. Information on ISO's system for numbering the weeks of the
    * year can be found at: https://en.wikipedia.org/wiki/ISO_8601#Week_dates
    *
    * The timezone to be used to read/write the `Date` instance in the model can be defined using
    * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
    *
    * @param {string} ngModel Assignable AngularJS expression to data-bind to.
    * @param {string=} name Property name of the form under which the control is published.
    * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
    *   This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
    *   attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add
    *   native HTML5 constraint validation.
    * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
    *   This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
    *   attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add
    *   native HTML5 constraint validation.
    * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
    *   the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
    * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
    *   the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
    * @param {string=} required Sets `required` validation error key if the value is not entered.
    * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
    *    the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
    *    `required` when you want to data-bind to the `required` attribute.
    * @param {string=} ngChange AngularJS expression to be executed when input changes due to user
    *    interaction with the input element.
    *
    * @example
    <example name="week-input-directive" module="weekExample">
    <file name="index.html">
      <script>
      angular.module('weekExample', [])
        .controller('DateController', ['$scope', function($scope) {
          $scope.example = {
            value: new Date(2013, 0, 3)
          };
        }]);
      </script>
      <form name="myForm" ng-controller="DateController as dateCtrl">
        <label>Pick a date between in 2013:
          <input id="exampleInput" type="week" name="input" ng-model="example.value"
                 placeholder="YYYY-W##" min="2012-W32"
                 max="2013-W52" required />
        </label>
        <div role="alert">
          <span class="error" ng-show="myForm.input.$error.required">
              Required!</span>
          <span class="error" ng-show="myForm.input.$error.week">
              Not a valid date!</span>
        </div>
        <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/>
        <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
        <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
        <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
        <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
      </form>
    </file>
    <file name="protractor.js" type="protractor">
      var value = element(by.binding('example.value | date: "yyyy-Www"'));
      var valid = element(by.binding('myForm.input.$valid'));

      // currently protractor/webdriver does not support
      // sending keys to all known HTML5 input controls
      // for various browsers (https://github.com/angular/protractor/issues/562).
      function setInput(val) {
        // set the value of the element and force validation.
        var scr = "var ipt = document.getElementById('exampleInput'); " +
        "ipt.value = '" + val + "';" +
        "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
        browser.executeScript(scr);
      }

      it('should initialize to model', function() {
        expect(value.getText()).toContain('2013-W01');
        expect(valid.getText()).toContain('myForm.input.$valid = true');
      });

      it('should be invalid if empty', function() {
        setInput('');
        expect(value.getText()).toEqual('value =');
        expect(valid.getText()).toContain('myForm.input.$valid = false');
      });

      it('should be invalid if over max', function() {
        setInput('2015-W01');
        expect(value.getText()).toContain('');
        expect(valid.getText()).toContain('myForm.input.$valid = false');
      });
    </file>
    </example>
    */
  'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'),

  /**
   * @ngdoc input
   * @name input[month]
   *
   * @description
   * Input with month validation and transformation. In browsers that do not yet support
   * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
   * month format (yyyy-MM), for example: `2009-01`.
   *
   * The model must always be a Date object, otherwise AngularJS will throw an error.
   * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string.
   * If the model is not set to the first of the month, the next view to model update will set it
   * to the first of the month.
   *
   * The timezone to be used to read/write the `Date` instance in the model can be defined using
   * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser.
   *
   * @param {string} ngModel Assignable AngularJS expression to data-bind to.
   * @param {string=} name Property name of the form under which the control is published.
   * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
   *   This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
   *   attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add
   *   native HTML5 constraint validation.
   * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
   *   This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
   *   attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add
   *   native HTML5 constraint validation.
   * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
   *   the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
   * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
   *   the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.

   * @param {string=} required Sets `required` validation error key if the value is not entered.
   * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
   *    the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
   *    `required` when you want to data-bind to the `required` attribute.
   * @param {string=} ngChange AngularJS expression to be executed when input changes due to user
   *    interaction with the input element.
   *
   * @example
   <example name="month-input-directive" module="monthExample">
   <file name="index.html">
     <script>
      angular.module('monthExample', [])
        .controller('DateController', ['$scope', function($scope) {
          $scope.example = {
            value: new Date(2013, 9, 1)
          };
        }]);
     </script>
     <form name="myForm" ng-controller="DateController as dateCtrl">
       <label for="exampleInput">Pick a month in 2013:</label>
       <input id="exampleInput" type="month" name="input" ng-model="example.value"
          placeholder="yyyy-MM" min="2013-01" max="2013-12" required />
       <div role="alert">
         <span class="error" ng-show="myForm.input.$error.required">
            Required!</span>
         <span class="error" ng-show="myForm.input.$error.month">
            Not a valid month!</span>
       </div>
       <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/>
       <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
       <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
       <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
       <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
     </form>
   </file>
   <file name="protractor.js" type="protractor">
      var value = element(by.binding('example.value | date: "yyyy-MM"'));
      var valid = element(by.binding('myForm.input.$valid'));

      // currently protractor/webdriver does not support
      // sending keys to all known HTML5 input controls
      // for various browsers (https://github.com/angular/protractor/issues/562).
      function setInput(val) {
        // set the value of the element and force validation.
        var scr = "var ipt = document.getElementById('exampleInput'); " +
        "ipt.value = '" + val + "';" +
        "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });";
        browser.executeScript(scr);
      }

      it('should initialize to model', function() {
        expect(value.getText()).toContain('2013-10');
        expect(valid.getText()).toContain('myForm.input.$valid = true');
      });

      it('should be invalid if empty', function() {
        setInput('');
        expect(value.getText()).toEqual('value =');
        expect(valid.getText()).toContain('myForm.input.$valid = false');
      });

      it('should be invalid if over max', function() {
        setInput('2015-01');
        expect(value.getText()).toContain('');
        expect(valid.getText()).toContain('myForm.input.$valid = false');
      });
   </file>
   </example>
   */
  'month': createDateInputType('month', MONTH_REGEXP,
     createDateParser(MONTH_REGEXP, ['yyyy', 'MM']),
     'yyyy-MM'),

  /**
   * @ngdoc input
   * @name input[number]
   *
   * @description
   * Text input with number validation and transformation. Sets the `number` validation
   * error if not a valid number.
   *
   * <div class="alert alert-warning">
   * The model must always be of type `number` otherwise AngularJS will throw an error.
   * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt}
   * error docs for more information and an example of how to convert your model if necessary.
   * </div>
   *
   *
   *
   * @knownIssue
   *
   * ### HTML5 constraint validation and `allowInvalid`
   *
   * In browsers that follow the
   * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29),
   * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}.
   * If a non-number is entered in the input, the browser will report the value as an empty string,
   * which means the view / model values in `ngModel` and subsequently the scope value
   * will also be an empty string.
   *
   * @knownIssue
   *
   * ### Large numbers and `step` validation
   *
   * The `step` validation will not work correctly for very large numbers (e.g. 9999999999) due to
   * Javascript's arithmetic limitations. If you need to handle large numbers, purpose-built
   * libraries (e.g. https://github.com/MikeMcl/big.js/), can be included into AngularJS by
   * {@link guide/forms#modifying-built-in-validators overwriting the validators}
   * for `number` and / or `step`, or by {@link guide/forms#custom-validation applying custom validators}
   * to an `input[text]` element. The source for `input[number]` type can be used as a starting
   * point for both implementations.
   *
   * @param {string} ngModel Assignable AngularJS expression to data-bind to.
   * @param {string=} name Property name of the form under which the control is published.
   * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
   *    Can be interpolated.
   * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
   *    Can be interpolated.
   * @param {string=} ngMin Like `min`, sets the `min` validation error key if the value entered is less than `ngMin`,
   *    but does not trigger HTML5 native validation. Takes an expression.
   * @param {string=} ngMax Like `max`, sets the `max` validation error key if the value entered is greater than `ngMax`,
   *    but does not trigger HTML5 native validation. Takes an expression.
   * @param {string=} step Sets the `step` validation error key if the value entered does not fit the `step` constraint.
   *    Can be interpolated.
   * @param {string=} ngStep Like `step`, sets the `step` validation error key if the value entered does not fit the `ngStep` constraint,
   *    but does not trigger HTML5 native validation. Takes an expression.
   * @param {string=} required Sets `required` validation error key if the value is not entered.
   * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
   *    the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
   *    `required` when you want to data-bind to the `required` attribute.
   * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
   *    minlength.
   * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
   *    maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
   *    any length.
   * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
   *    that contains the regular expression body that will be converted to a regular expression
   *    as in the ngPattern directive.
   * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
   *    does not match a RegExp found by evaluating the AngularJS expression given in the attribute value.
   *    If the expression evaluates to a RegExp object, then this is used directly.
   *    If the expression evaluates to a string, then it will be converted to a RegExp
   *    after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
   *    `new RegExp('^abc$')`.<br />
   *    **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
   *    start at the index of the last search's match, thus not taking the whole input value into
   *    account.
   * @param {string=} ngChange AngularJS expression to be executed when input changes due to user
   *    interaction with the input element.
   *
   * @example
      <example name="number-input-directive" module="numberExample">
        <file name="index.html">
         <script>
           angular.module('numberExample', [])
             .controller('ExampleController', ['$scope', function($scope) {
               $scope.example = {
                 value: 12
               };
             }]);
         </script>
         <form name="myForm" ng-controller="ExampleController">
           <label>Number:
             <input type="number" name="input" ng-model="example.value"
                    min="0" max="99" required>
          </label>
           <div role="alert">
             <span class="error" ng-show="myForm.input.$error.required">
               Required!</span>
             <span class="error" ng-show="myForm.input.$error.number">
               Not valid number!</span>
           </div>
           <tt>value = {{example.value}}</tt><br/>
           <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
           <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
           <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
           <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
          </form>
        </file>
        <file name="protractor.js" type="protractor">
          var value = element(by.binding('example.value'));
          var valid = element(by.binding('myForm.input.$valid'));
          var input = element(by.model('example.value'));

          it('should initialize to model', function() {
            expect(value.getText()).toContain('12');
            expect(valid.getText()).toContain('true');
          });

          it('should be invalid if empty', function() {
            input.clear();
            input.sendKeys('');
            expect(value.getText()).toEqual('value =');
            expect(valid.getText()).toContain('false');
          });

          it('should be invalid if over max', function() {
            input.clear();
            input.sendKeys('123');
            expect(value.getText()).toEqual('value =');
            expect(valid.getText()).toContain('false');
          });
        </file>
      </example>
   */
  'number': numberInputType,


  /**
   * @ngdoc input
   * @name input[url]
   *
   * @description
   * Text input with URL validation. Sets the `url` validation error key if the content is not a
   * valid URL.
   *
   * <div class="alert alert-warning">
   * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex
   * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify
   * the built-in validators (see the {@link guide/forms Forms guide})
   * </div>
   *
   * @param {string} ngModel Assignable AngularJS expression to data-bind to.
   * @param {string=} name Property name of the form under which the control is published.
   * @param {string=} required Sets `required` validation error key if the value is not entered.
   * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
   *    the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
   *    `required` when you want to data-bind to the `required` attribute.
   * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
   *    minlength.
   * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
   *    maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
   *    any length.
   * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
   *    that contains the regular expression body that will be converted to a regular expression
   *    as in the ngPattern directive.
   * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
   *    does not match a RegExp found by evaluating the AngularJS expression given in the attribute value.
   *    If the expression evaluates to a RegExp object, then this is used directly.
   *    If the expression evaluates to a string, then it will be converted to a RegExp
   *    after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
   *    `new RegExp('^abc$')`.<br />
   *    **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
   *    start at the index of the last search's match, thus not taking the whole input value into
   *    account.
   * @param {string=} ngChange AngularJS expression to be executed when input changes due to user
   *    interaction with the input element.
   *
   * @example
      <example name="url-input-directive" module="urlExample">
        <file name="index.html">
         <script>
           angular.module('urlExample', [])
             .controller('ExampleController', ['$scope', function($scope) {
               $scope.url = {
                 text: 'http://google.com'
               };
             }]);
         </script>
         <form name="myForm" ng-controller="ExampleController">
           <label>URL:
             <input type="url" name="input" ng-model="url.text" required>
           <label>
           <div role="alert">
             <span class="error" ng-show="myForm.input.$error.required">
               Required!</span>
             <span class="error" ng-show="myForm.input.$error.url">
               Not valid url!</span>
           </div>
           <tt>text = {{url.text}}</tt><br/>
           <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
           <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
           <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
           <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
           <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/>
          </form>
        </file>
        <file name="protractor.js" type="protractor">
          var text = element(by.binding('url.text'));
          var valid = element(by.binding('myForm.input.$valid'));
          var input = element(by.model('url.text'));

          it('should initialize to model', function() {
            expect(text.getText()).toContain('http://google.com');
            expect(valid.getText()).toContain('true');
          });

          it('should be invalid if empty', function() {
            input.clear();
            input.sendKeys('');

            expect(text.getText()).toEqual('text =');
            expect(valid.getText()).toContain('false');
          });

          it('should be invalid if not url', function() {
            input.clear();
            input.sendKeys('box');

            expect(valid.getText()).toContain('false');
          });
        </file>
      </example>
   */
  'url': urlInputType,


  /**
   * @ngdoc input
   * @name input[email]
   *
   * @description
   * Text input with email validation. Sets the `email` validation error key if not a valid email
   * address.
   *
   * <div class="alert alert-warning">
   * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex
   * used in Chromium, which may not fulfill your app's requirements.
   * If you need stricter (e.g. requiring a top-level domain), or more relaxed validation
   * (e.g. allowing IPv6 address literals) you can use `ng-pattern` or
   * modify the built-in validators (see the {@link guide/forms Forms guide}).
   * </div>
   *
   * @param {string} ngModel Assignable AngularJS expression to data-bind to.
   * @param {string=} name Property name of the form under which the control is published.
   * @param {string=} required Sets `required` validation error key if the value is not entered.
   * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
   *    the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
   *    `required` when you want to data-bind to the `required` attribute.
   * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
   *    minlength.
   * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
   *    maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
   *    any length.
   * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
   *    that contains the regular expression body that will be converted to a regular expression
   *    as in the ngPattern directive.
   * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
   *    does not match a RegExp found by evaluating the AngularJS expression given in the attribute value.
   *    If the expression evaluates to a RegExp object, then this is used directly.
   *    If the expression evaluates to a string, then it will be converted to a RegExp
   *    after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
   *    `new RegExp('^abc$')`.<br />
   *    **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
   *    start at the index of the last search's match, thus not taking the whole input value into
   *    account.
   * @param {string=} ngChange AngularJS expression to be executed when input changes due to user
   *    interaction with the input element.
   *
   * @example
      <example name="email-input-directive" module="emailExample">
        <file name="index.html">
         <script>
           angular.module('emailExample', [])
             .controller('ExampleController', ['$scope', function($scope) {
               $scope.email = {
                 text: 'me@example.com'
               };
             }]);
         </script>
           <form name="myForm" ng-controller="ExampleController">
             <label>Email:
               <input type="email" name="input" ng-model="email.text" required>
             </label>
             <div role="alert">
               <span class="error" ng-show="myForm.input.$error.required">
                 Required!</span>
               <span class="error" ng-show="myForm.input.$error.email">
                 Not valid email!</span>
             </div>
             <tt>text = {{email.text}}</tt><br/>
             <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
             <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
             <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
             <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
             <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/>
           </form>
         </file>
        <file name="protractor.js" type="protractor">
          var text = element(by.binding('email.text'));
          var valid = element(by.binding('myForm.input.$valid'));
          var input = element(by.model('email.text'));

          it('should initialize to model', function() {
            expect(text.getText()).toContain('me@example.com');
            expect(valid.getText()).toContain('true');
          });

          it('should be invalid if empty', function() {
            input.clear();
            input.sendKeys('');
            expect(text.getText()).toEqual('text =');
            expect(valid.getText()).toContain('false');
          });

          it('should be invalid if not email', function() {
            input.clear();
            input.sendKeys('xxx');

            expect(valid.getText()).toContain('false');
          });
        </file>
      </example>
   */
  'email': emailInputType,


  /**
   * @ngdoc input
   * @name input[radio]
   *
   * @description
   * HTML radio button.
   *
   * **Note:**<br>
   * All inputs controlled by {@link ngModel ngModel} (including those of type `radio`) will use the
   * value of their `name` attribute to determine the property under which their
   * {@link ngModel.NgModelController NgModelController} will be published on the parent
   * {@link form.FormController FormController}. Thus, if you use the same `name` for multiple
   * inputs of a form (e.g. a group of radio inputs), only _one_ `NgModelController` will be
   * published on the parent `FormController` under that name. The rest of the controllers will
   * continue to work as expected, but you won't be able to access them as properties on the parent
   * `FormController`.
   *
   * <div class="alert alert-info">
   *   <p>
   *     In plain HTML forms, the `name` attribute is used to identify groups of radio inputs, so
   *     that the browser can manage their state (checked/unchecked) based on the state of other
   *     inputs in the same group.
   *   </p>
   *   <p>
   *     In AngularJS forms, this is not necessary. The input's state will be updated based on the
   *     value of the underlying model data.
   *   </p>
   * </div>
   *
   * <div class="alert alert-success">
   *   If you omit the `name` attribute on a radio input, `ngModel` will automatically assign it a
   *   unique name.
   * </div>
   *
   * @param {string} ngModel Assignable AngularJS expression to data-bind to.
   * @param {string} value The value to which the `ngModel` expression should be set when selected.
   *    Note that `value` only supports `string` values, i.e. the scope model needs to be a string,
   *    too. Use `ngValue` if you need complex models (`number`, `object`, ...).
   * @param {string=} name Property name of the form under which the control is published.
   * @param {string=} ngChange AngularJS expression to be executed when input changes due to user
   *    interaction with the input element.
   * @param {string} ngValue AngularJS expression to which `ngModel` will be be set when the radio
   *    is selected. Should be used instead of the `value` attribute if you need
   *    a non-string `ngModel` (`boolean`, `array`, ...).
   *
   * @example
      <example name="radio-input-directive" module="radioExample">
        <file name="index.html">
         <script>
           angular.module('radioExample', [])
             .controller('ExampleController', ['$scope', function($scope) {
               $scope.color = {
                 name: 'blue'
               };
               $scope.specialValue = {
                 "id": "12345",
                 "value": "green"
               };
             }]);
         </script>
         <form name="myForm" ng-controller="ExampleController">
           <label>
             <input type="radio" ng-model="color.name" value="red">
             Red
           </label><br/>
           <label>
             <input type="radio" ng-model="color.name" ng-value="specialValue">
             Green
           </label><br/>
           <label>
             <input type="radio" ng-model="color.name" value="blue">
             Blue
           </label><br/>
           <tt>color = {{color.name | json}}</tt><br/>
          </form>
          Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`.
        </file>
        <file name="protractor.js" type="protractor">
          it('should change state', function() {
            var inputs = element.all(by.model('color.name'));
            var color = element(by.binding('color.name'));

            expect(color.getText()).toContain('blue');

            inputs.get(0).click();
            expect(color.getText()).toContain('red');

            inputs.get(1).click();
            expect(color.getText()).toContain('green');
          });
        </file>
      </example>
   */
  'radio': radioInputType,

  /**
   * @ngdoc input
   * @name input[range]
   *
   * @description
   * Native range input with validation and transformation.
   *
   * The model for the range input must always be a `Number`.
   *
   * IE9 and other browsers that do not support the `range` type fall back
   * to a text input without any default values for `min`, `max` and `step`. Model binding,
   * validation and number parsing are nevertheless supported.
   *
   * Browsers that support range (latest Chrome, Safari, Firefox, Edge) treat `input[range]`
   * in a way that never allows the input to hold an invalid value. That means:
   * - any non-numerical value is set to `(max + min) / 2`.
   * - any numerical value that is less than the current min val, or greater than the current max val
   * is set to the min / max val respectively.
   * - additionally, the current `step` is respected, so the nearest value that satisfies a step
   * is used.
   *
   * See the [HTML Spec on input[type=range]](https://www.w3.org/TR/html5/forms.html#range-state-(type=range))
   * for more info.
   *
   * This has the following consequences for AngularJS:
   *
   * Since the element value should always reflect the current model value, a range input
   * will set the bound ngModel expression to the value that the browser has set for the
   * input element. For example, in the following input `<input type="range" ng-model="model.value">`,
   * if the application sets `model.value = null`, the browser will set the input to `'50'`.
   * AngularJS will then set the model to `50`, to prevent input and model value being out of sync.
   *
   * That means the model for range will immediately be set to `50` after `ngModel` has been
   * initialized. It also means a range input can never have the required error.
   *
   * This does not only affect changes to the model value, but also to the values of the `min`,
   * `max`, and `step` attributes. When these change in a way that will cause the browser to modify
   * the input value, AngularJS will also update the model value.
   *
   * Automatic value adjustment also means that a range input element can never have the `required`,
   * `min`, or `max` errors.
   *
   * However, `step` is currently only fully implemented by Firefox. Other browsers have problems
   * when the step value changes dynamically - they do not adjust the element value correctly, but
   * instead may set the `stepMismatch` error. If that's the case, the AngularJS will set the `step`
   * error on the input, and set the model to `undefined`.
   *
   * Note that `input[range]` is not compatible with`ngMax`, `ngMin`, and `ngStep`, because they do
   * not set the `min` and `max` attributes, which means that the browser won't automatically adjust
   * the input value based on their values, and will always assume min = 0, max = 100, and step = 1.
   *
   * @param {string}  ngModel Assignable AngularJS expression to data-bind to.
   * @param {string=} name Property name of the form under which the control is published.
   * @param {string=} min Sets the `min` validation to ensure that the value entered is greater
   *                  than `min`. Can be interpolated.
   * @param {string=} max Sets the `max` validation to ensure that the value entered is less than `max`.
   *                  Can be interpolated.
   * @param {string=} step Sets the `step` validation to ensure that the value entered matches the `step`
   *                  Can be interpolated.
   * @param {expression=} ngChange AngularJS expression to be executed when the ngModel value changes due
   *                      to user interaction with the input element.
   * @param {expression=} ngChecked If the expression is truthy, then the `checked` attribute will be set on the
   *                      element. **Note** : `ngChecked` should not be used alongside `ngModel`.
   *                      Checkout {@link ng.directive:ngChecked ngChecked} for usage.
   *
   * @example
      <example name="range-input-directive" module="rangeExample">
        <file name="index.html">
          <script>
            angular.module('rangeExample', [])
              .controller('ExampleController', ['$scope', function($scope) {
                $scope.value = 75;
                $scope.min = 10;
                $scope.max = 90;
              }]);
          </script>
          <form name="myForm" ng-controller="ExampleController">

            Model as range: <input type="range" name="range" ng-model="value" min="{{min}}"  max="{{max}}">
            <hr>
            Model as number: <input type="number" ng-model="value"><br>
            Min: <input type="number" ng-model="min"><br>
            Max: <input type="number" ng-model="max"><br>
            value = <code>{{value}}</code><br/>
            myForm.range.$valid = <code>{{myForm.range.$valid}}</code><br/>
            myForm.range.$error = <code>{{myForm.range.$error}}</code>
          </form>
        </file>
      </example>

   * ## Range Input with ngMin & ngMax attributes

   * @example
      <example name="range-input-directive-ng" module="rangeExample">
        <file name="index.html">
          <script>
            angular.module('rangeExample', [])
              .controller('ExampleController', ['$scope', function($scope) {
                $scope.value = 75;
                $scope.min = 10;
                $scope.max = 90;
              }]);
          </script>
          <form name="myForm" ng-controller="ExampleController">
            Model as range: <input type="range" name="range" ng-model="value" ng-min="min" ng-max="max">
            <hr>
            Model as number: <input type="number" ng-model="value"><br>
            Min: <input type="number" ng-model="min"><br>
            Max: <input type="number" ng-model="max"><br>
            value = <code>{{value}}</code><br/>
            myForm.range.$valid = <code>{{myForm.range.$valid}}</code><br/>
            myForm.range.$error = <code>{{myForm.range.$error}}</code>
          </form>
        </file>
      </example>

   */
  'range': rangeInputType,

  /**
   * @ngdoc input
   * @name input[checkbox]
   *
   * @description
   * HTML checkbox.
   *
   * @param {string} ngModel Assignable AngularJS expression to data-bind to.
   * @param {string=} name Property name of the form under which the control is published.
   * @param {expression=} ngTrueValue The value to which the expression should be set when selected.
   * @param {expression=} ngFalseValue The value to which the expression should be set when not selected.
   * @param {string=} ngChange AngularJS expression to be executed when input changes due to user
   *    interaction with the input element.
   *
   * @example
      <example name="checkbox-input-directive" module="checkboxExample">
        <file name="index.html">
         <script>
           angular.module('checkboxExample', [])
             .controller('ExampleController', ['$scope', function($scope) {
               $scope.checkboxModel = {
                value1 : true,
                value2 : 'YES'
              };
             }]);
         </script>
         <form name="myForm" ng-controller="ExampleController">
           <label>Value1:
             <input type="checkbox" ng-model="checkboxModel.value1">
           </label><br/>
           <label>Value2:
             <input type="checkbox" ng-model="checkboxModel.value2"
                    ng-true-value="'YES'" ng-false-value="'NO'">
            </label><br/>
           <tt>value1 = {{checkboxModel.value1}}</tt><br/>
           <tt>value2 = {{checkboxModel.value2}}</tt><br/>
          </form>
        </file>
        <file name="protractor.js" type="protractor">
          it('should change state', function() {
            var value1 = element(by.binding('checkboxModel.value1'));
            var value2 = element(by.binding('checkboxModel.value2'));

            expect(value1.getText()).toContain('true');
            expect(value2.getText()).toContain('YES');

            element(by.model('checkboxModel.value1')).click();
            element(by.model('checkboxModel.value2')).click();

            expect(value1.getText()).toContain('false');
            expect(value2.getText()).toContain('NO');
          });
        </file>
      </example>
   */
  'checkbox': checkboxInputType,

  'hidden': noop,
  'button': noop,
  'submit': noop,
  'reset': noop,
  'file': noop
};

function stringBasedInputType(ctrl) {
  ctrl.$formatters.push(function(value) {
    return ctrl.$isEmpty(value) ? value : value.toString();
  });
}

function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
  baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
  stringBasedInputType(ctrl);
}

function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
  var type = lowercase(element[0].type);

  // In composition mode, users are still inputting intermediate text buffer,
  // hold the listener until composition is done.
  // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent
  if (!$sniffer.android) {
    var composing = false;

    element.on('compositionstart', function() {
      composing = true;
    });

    // Support: IE9+
    element.on('compositionupdate', function(ev) {
      // End composition when ev.data is empty string on 'compositionupdate' event.
      // When the input de-focusses (e.g. by clicking away), IE triggers 'compositionupdate'
      // instead of 'compositionend'.
      if (isUndefined(ev.data) || ev.data === '') {
        composing = false;
      }
    });

    element.on('compositionend', function() {
      composing = false;
      listener();
    });
  }

  var timeout;

  var listener = function(ev) {
    if (timeout) {
      $browser.defer.cancel(timeout);
      timeout = null;
    }
    if (composing) return;
    var value = element.val(),
        event = ev && ev.type;

    // By default we will trim the value
    // If the attribute ng-trim exists we will avoid trimming
    // If input type is 'password', the value is never trimmed
    if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) {
      value = trim(value);
    }

    // If a control is suffering from bad input (due to native validators), browsers discard its
    // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the
    // control's value is the same empty value twice in a row.
    if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) {
      ctrl.$setViewValue(value, event);
    }
  };

  // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the
  // input event on backspace, delete or cut
  if ($sniffer.hasEvent('input')) {
    element.on('input', listener);
  } else {
    var deferListener = function(ev, input, origValue) {
      if (!timeout) {
        timeout = $browser.defer(function() {
          timeout = null;
          if (!input || input.value !== origValue) {
            listener(ev);
          }
        });
      }
    };

    element.on('keydown', /** @this */ function(event) {
      var key = event.keyCode;

      // ignore
      //    command            modifiers                   arrows
      if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return;

      deferListener(event, this, this.value);
    });

    // if user modifies input value using context menu in IE, we need "paste", "cut" and "drop" events to catch it
    if ($sniffer.hasEvent('paste')) {
      element.on('paste cut drop', deferListener);
    }
  }

  // if user paste into input using mouse on older browser
  // or form autocomplete on newer browser, we need "change" event to catch it
  element.on('change', listener);

  // Some native input types (date-family) have the ability to change validity without
  // firing any input/change events.
  // For these event types, when native validators are present and the browser supports the type,
  // check for validity changes on various DOM events.
  if (PARTIAL_VALIDATION_TYPES[type] && ctrl.$$hasNativeValidators && type === attr.type) {
    element.on(PARTIAL_VALIDATION_EVENTS, /** @this */ function(ev) {
      if (!timeout) {
        var validity = this[VALIDITY_STATE_PROPERTY];
        var origBadInput = validity.badInput;
        var origTypeMismatch = validity.typeMismatch;
        timeout = $browser.defer(function() {
          timeout = null;
          if (validity.badInput !== origBadInput || validity.typeMismatch !== origTypeMismatch) {
            listener(ev);
          }
        });
      }
    });
  }

  ctrl.$render = function() {
    // Workaround for Firefox validation #12102.
    var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue;
    if (element.val() !== value) {
      element.val(value);
    }
  };
}

function weekParser(isoWeek, existingDate) {
  if (isDate(isoWeek)) {
    return isoWeek;
  }

  if (isString(isoWeek)) {
    WEEK_REGEXP.lastIndex = 0;
    var parts = WEEK_REGEXP.exec(isoWeek);
    if (parts) {
      var year = +parts[1],
          week = +parts[2],
          hours = 0,
          minutes = 0,
          seconds = 0,
          milliseconds = 0,
          firstThurs = getFirstThursdayOfYear(year),
          addDays = (week - 1) * 7;

      if (existingDate) {
        hours = existingDate.getHours();
        minutes = existingDate.getMinutes();
        seconds = existingDate.getSeconds();
        milliseconds = existingDate.getMilliseconds();
      }

      return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds);
    }
  }

  return NaN;
}

function createDateParser(regexp, mapping) {
  return function(iso, previousDate) {
    var parts, map;

    if (isDate(iso)) {
      return iso;
    }

    if (isString(iso)) {
      // When a date is JSON'ified to wraps itself inside of an extra
      // set of double quotes. This makes the date parsing code unable
      // to match the date string and parse it as a date.
      if (iso.charAt(0) === '"' && iso.charAt(iso.length - 1) === '"') {
        iso = iso.substring(1, iso.length - 1);
      }
      if (ISO_DATE_REGEXP.test(iso)) {
        return new Date(iso);
      }
      regexp.lastIndex = 0;
      parts = regexp.exec(iso);

      if (parts) {
        parts.shift();
        if (previousDate) {
          map = {
            yyyy: previousDate.getFullYear(),
            MM: previousDate.getMonth() + 1,
            dd: previousDate.getDate(),
            HH: previousDate.getHours(),
            mm: previousDate.getMinutes(),
            ss: previousDate.getSeconds(),
            sss: previousDate.getMilliseconds() / 1000
          };
        } else {
          map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 };
        }

        forEach(parts, function(part, index) {
          if (index < mapping.length) {
            map[mapping[index]] = +part;
          }
        });

        var date = new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0);
        if (map.yyyy < 100) {
          // In the constructor, 2-digit years map to 1900-1999.
          // Use `setFullYear()` to set the correct year.
          date.setFullYear(map.yyyy);
        }

        return date;
      }
    }

    return NaN;
  };
}

function createDateInputType(type, regexp, parseDate, format) {
  return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) {
    badInputChecker(scope, element, attr, ctrl, type);
    baseInputType(scope, element, attr, ctrl, $sniffer, $browser);

    var isTimeType = type === 'time' || type === 'datetimelocal';
    var previousDate;
    var previousTimezone;

    ctrl.$parsers.push(function(value) {
      if (ctrl.$isEmpty(value)) return null;

      if (regexp.test(value)) {
        // Note: We cannot read ctrl.$modelValue, as there might be a different
        // parser/formatter in the processing chain so that the model
        // contains some different data format!
        return parseDateAndConvertTimeZoneToLocal(value, previousDate);
      }
      ctrl.$$parserName = type;
      return undefined;
    });

    ctrl.$formatters.push(function(value) {
      if (value && !isDate(value)) {
        throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
      }
      if (isValidDate(value)) {
        previousDate = value;
        var timezone = ctrl.$options.getOption('timezone');

        if (timezone) {
          previousTimezone = timezone;
          previousDate = convertTimezoneToLocal(previousDate, timezone, true);
        }

        return formatter(value, timezone);
      } else {
        previousDate = null;
        previousTimezone = null;
        return '';
      }
    });

    if (isDefined(attr.min) || attr.ngMin) {
      var minVal = attr.min || $parse(attr.ngMin)(scope);
      var parsedMinVal = parseObservedDateValue(minVal);

      ctrl.$validators.min = function(value) {
        return !isValidDate(value) || isUndefined(parsedMinVal) || parseDate(value) >= parsedMinVal;
      };
      attr.$observe('min', function(val) {
        if (val !== minVal) {
          parsedMinVal = parseObservedDateValue(val);
          minVal = val;
          ctrl.$validate();
        }
      });
    }

    if (isDefined(attr.max) || attr.ngMax) {
      var maxVal = attr.max || $parse(attr.ngMax)(scope);
      var parsedMaxVal = parseObservedDateValue(maxVal);

      ctrl.$validators.max = function(value) {
        return !isValidDate(value) || isUndefined(parsedMaxVal) || parseDate(value) <= parsedMaxVal;
      };
      attr.$observe('max', function(val) {
        if (val !== maxVal) {
          parsedMaxVal = parseObservedDateValue(val);
          maxVal = val;
          ctrl.$validate();
        }
      });
    }

    function isValidDate(value) {
      // Invalid Date: getTime() returns NaN
      return value && !(value.getTime && value.getTime() !== value.getTime());
    }

    function parseObservedDateValue(val) {
      return isDefined(val) && !isDate(val) ? parseDateAndConvertTimeZoneToLocal(val) || undefined : val;
    }

    function parseDateAndConvertTimeZoneToLocal(value, previousDate) {
      var timezone = ctrl.$options.getOption('timezone');

      if (previousTimezone && previousTimezone !== timezone) {
        // If the timezone has changed, adjust the previousDate to the default timezone
        // so that the new date is converted with the correct timezone offset
        previousDate = addDateMinutes(previousDate, timezoneToOffset(previousTimezone));
      }

      var parsedDate = parseDate(value, previousDate);

      if (!isNaN(parsedDate) && timezone) {
        parsedDate = convertTimezoneToLocal(parsedDate, timezone);
      }
      return parsedDate;
    }

    function formatter(value, timezone) {
      var targetFormat = format;

      if (isTimeType && isString(ctrl.$options.getOption('timeSecondsFormat'))) {
        targetFormat = format
          .replace('ss.sss', ctrl.$options.getOption('timeSecondsFormat'))
          .replace(/:$/, '');
      }

      var formatted =  $filter('date')(value, targetFormat, timezone);

      if (isTimeType && ctrl.$options.getOption('timeStripZeroSeconds')) {
        formatted = formatted.replace(/(?::00)?(?:\.000)?$/, '');
      }

      return formatted;
    }
  };
}

function badInputChecker(scope, element, attr, ctrl, parserName) {
  var node = element[0];
  var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity);
  if (nativeValidation) {
    ctrl.$parsers.push(function(value) {
      var validity = element.prop(VALIDITY_STATE_PROPERTY) || {};
      if (validity.badInput || validity.typeMismatch) {
        ctrl.$$parserName = parserName;
        return undefined;
      }

      return value;
    });
  }
}

function numberFormatterParser(ctrl) {
  ctrl.$parsers.push(function(value) {
    if (ctrl.$isEmpty(value))      return null;
    if (NUMBER_REGEXP.test(value)) return parseFloat(value);

    ctrl.$$parserName = 'number';
    return undefined;
  });

  ctrl.$formatters.push(function(value) {
    if (!ctrl.$isEmpty(value)) {
      if (!isNumber(value)) {
        throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value);
      }
      value = value.toString();
    }
    return value;
  });
}

function parseNumberAttrVal(val) {
  if (isDefined(val) && !isNumber(val)) {
    val = parseFloat(val);
  }
  return !isNumberNaN(val) ? val : undefined;
}

function isNumberInteger(num) {
  // See http://stackoverflow.com/questions/14636536/how-to-check-if-a-variable-is-an-integer-in-javascript#14794066
  // (minus the assumption that `num` is a number)

  // eslint-disable-next-line no-bitwise
  return (num | 0) === num;
}

function countDecimals(num) {
  var numString = num.toString();
  var decimalSymbolIndex = numString.indexOf('.');

  if (decimalSymbolIndex === -1) {
    if (-1 < num && num < 1) {
      // It may be in the exponential notation format (`1e-X`)
      var match = /e-(\d+)$/.exec(numString);

      if (match) {
        return Number(match[1]);
      }
    }

    return 0;
  }

  return numString.length - decimalSymbolIndex - 1;
}

function isValidForStep(viewValue, stepBase, step) {
  // At this point `stepBase` and `step` are expected to be non-NaN values
  // and `viewValue` is expected to be a valid stringified number.
  var value = Number(viewValue);

  var isNonIntegerValue = !isNumberInteger(value);
  var isNonIntegerStepBase = !isNumberInteger(stepBase);
  var isNonIntegerStep = !isNumberInteger(step);

  // Due to limitations in Floating Point Arithmetic (e.g. `0.3 - 0.2 !== 0.1` or
  // `0.5 % 0.1 !== 0`), we need to convert all numbers to integers.
  if (isNonIntegerValue || isNonIntegerStepBase || isNonIntegerStep) {
    var valueDecimals = isNonIntegerValue ? countDecimals(value) : 0;
    var stepBaseDecimals = isNonIntegerStepBase ? countDecimals(stepBase) : 0;
    var stepDecimals = isNonIntegerStep ? countDecimals(step) : 0;

    var decimalCount = Math.max(valueDecimals, stepBaseDecimals, stepDecimals);
    var multiplier = Math.pow(10, decimalCount);

    value = value * multiplier;
    stepBase = stepBase * multiplier;
    step = step * multiplier;

    if (isNonIntegerValue) value = Math.round(value);
    if (isNonIntegerStepBase) stepBase = Math.round(stepBase);
    if (isNonIntegerStep) step = Math.round(step);
  }

  return (value - stepBase) % step === 0;
}

function numberInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) {
  badInputChecker(scope, element, attr, ctrl, 'number');
  numberFormatterParser(ctrl);
  baseInputType(scope, element, attr, ctrl, $sniffer, $browser);

  var parsedMinVal;

  if (isDefined(attr.min) || attr.ngMin) {
    var minVal = attr.min || $parse(attr.ngMin)(scope);
    parsedMinVal = parseNumberAttrVal(minVal);

    ctrl.$validators.min = function(modelValue, viewValue) {
      return ctrl.$isEmpty(viewValue) || isUndefined(parsedMinVal) || viewValue >= parsedMinVal;
    };

    attr.$observe('min', function(val) {
      if (val !== minVal) {
        parsedMinVal = parseNumberAttrVal(val);
        minVal = val;
        // TODO(matsko): implement validateLater to reduce number of validations
        ctrl.$validate();
      }
    });
  }

  if (isDefined(attr.max) || attr.ngMax) {
    var maxVal = attr.max || $parse(attr.ngMax)(scope);
    var parsedMaxVal = parseNumberAttrVal(maxVal);

    ctrl.$validators.max = function(modelValue, viewValue) {
      return ctrl.$isEmpty(viewValue) || isUndefined(parsedMaxVal) || viewValue <= parsedMaxVal;
    };

    attr.$observe('max', function(val) {
      if (val !== maxVal) {
        parsedMaxVal = parseNumberAttrVal(val);
        maxVal = val;
        // TODO(matsko): implement validateLater to reduce number of validations
        ctrl.$validate();
      }
    });
  }

  if (isDefined(attr.step) || attr.ngStep) {
    var stepVal = attr.step || $parse(attr.ngStep)(scope);
    var parsedStepVal = parseNumberAttrVal(stepVal);

    ctrl.$validators.step = function(modelValue, viewValue) {
      return ctrl.$isEmpty(viewValue) || isUndefined(parsedStepVal) ||
        isValidForStep(viewValue, parsedMinVal || 0, parsedStepVal);
    };

    attr.$observe('step', function(val) {
      // TODO(matsko): implement validateLater to reduce number of validations
      if (val !== stepVal) {
        parsedStepVal = parseNumberAttrVal(val);
        stepVal = val;
        ctrl.$validate();
      }

    });

  }
}

function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
  badInputChecker(scope, element, attr, ctrl, 'range');
  numberFormatterParser(ctrl);
  baseInputType(scope, element, attr, ctrl, $sniffer, $browser);

  var supportsRange = ctrl.$$hasNativeValidators && element[0].type === 'range',
      minVal = supportsRange ? 0 : undefined,
      maxVal = supportsRange ? 100 : undefined,
      stepVal = supportsRange ? 1 : undefined,
      validity = element[0].validity,
      hasMinAttr = isDefined(attr.min),
      hasMaxAttr = isDefined(attr.max),
      hasStepAttr = isDefined(attr.step);

  var originalRender = ctrl.$render;

  ctrl.$render = supportsRange && isDefined(validity.rangeUnderflow) && isDefined(validity.rangeOverflow) ?
    //Browsers that implement range will set these values automatically, but reading the adjusted values after
    //$render would cause the min / max validators to be applied with the wrong value
    function rangeRender() {
      originalRender();
      ctrl.$setViewValue(element.val());
    } :
    originalRender;

  if (hasMinAttr) {
    minVal = parseNumberAttrVal(attr.min);

    ctrl.$validators.min = supportsRange ?
      // Since all browsers set the input to a valid value, we don't need to check validity
      function noopMinValidator() { return true; } :
      // non-support browsers validate the min val
      function minValidator(modelValue, viewValue) {
        return ctrl.$isEmpty(viewValue) || isUndefined(minVal) || viewValue >= minVal;
      };

    setInitialValueAndObserver('min', minChange);
  }

  if (hasMaxAttr) {
    maxVal = parseNumberAttrVal(attr.max);

    ctrl.$validators.max = supportsRange ?
      // Since all browsers set the input to a valid value, we don't need to check validity
      function noopMaxValidator() { return true; } :
      // non-support browsers validate the max val
      function maxValidator(modelValue, viewValue) {
        return ctrl.$isEmpty(viewValue) || isUndefined(maxVal) || viewValue <= maxVal;
      };

    setInitialValueAndObserver('max', maxChange);
  }

  if (hasStepAttr) {
    stepVal = parseNumberAttrVal(attr.step);

    ctrl.$validators.step = supportsRange ?
      function nativeStepValidator() {
        // Currently, only FF implements the spec on step change correctly (i.e. adjusting the
        // input element value to a valid value). It's possible that other browsers set the stepMismatch
        // validity error instead, so we can at least report an error in that case.
        return !validity.stepMismatch;
      } :
      // ngStep doesn't set the setp attr, so the browser doesn't adjust the input value as setting step would
      function stepValidator(modelValue, viewValue) {
        return ctrl.$isEmpty(viewValue) || isUndefined(stepVal) ||
               isValidForStep(viewValue, minVal || 0, stepVal);
      };

    setInitialValueAndObserver('step', stepChange);
  }

  function setInitialValueAndObserver(htmlAttrName, changeFn) {
    // interpolated attributes set the attribute value only after a digest, but we need the
    // attribute value when the input is first rendered, so that the browser can adjust the
    // input value based on the min/max value
    element.attr(htmlAttrName, attr[htmlAttrName]);
    var oldVal = attr[htmlAttrName];
    attr.$observe(htmlAttrName, function wrappedObserver(val) {
      if (val !== oldVal) {
        oldVal = val;
        changeFn(val);
      }
    });
  }

  function minChange(val) {
    minVal = parseNumberAttrVal(val);
    // ignore changes before model is initialized
    if (isNumberNaN(ctrl.$modelValue)) {
      return;
    }

    if (supportsRange) {
      var elVal = element.val();
      // IE11 doesn't set the el val correctly if the minVal is greater than the element value
      if (minVal > elVal) {
        elVal = minVal;
        element.val(elVal);
      }
      ctrl.$setViewValue(elVal);
    } else {
      // TODO(matsko): implement validateLater to reduce number of validations
      ctrl.$validate();
    }
  }

  function maxChange(val) {
    maxVal = parseNumberAttrVal(val);
    // ignore changes before model is initialized
    if (isNumberNaN(ctrl.$modelValue)) {
      return;
    }

    if (supportsRange) {
      var elVal = element.val();
      // IE11 doesn't set the el val correctly if the maxVal is less than the element value
      if (maxVal < elVal) {
        element.val(maxVal);
        // IE11 and Chrome don't set the value to the minVal when max < min
        elVal = maxVal < minVal ? minVal : maxVal;
      }
      ctrl.$setViewValue(elVal);
    } else {
      // TODO(matsko): implement validateLater to reduce number of validations
      ctrl.$validate();
    }
  }

  function stepChange(val) {
    stepVal = parseNumberAttrVal(val);
    // ignore changes before model is initialized
    if (isNumberNaN(ctrl.$modelValue)) {
      return;
    }

    // Some browsers don't adjust the input value correctly, but set the stepMismatch error
    if (!supportsRange) {
      // TODO(matsko): implement validateLater to reduce number of validations
      ctrl.$validate();
    } else if (ctrl.$viewValue !== element.val()) {
      ctrl.$setViewValue(element.val());
    }
  }
}

function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
  // Note: no badInputChecker here by purpose as `url` is only a validation
  // in browsers, i.e. we can always read out input.value even if it is not valid!
  baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
  stringBasedInputType(ctrl);

  ctrl.$validators.url = function(modelValue, viewValue) {
    var value = modelValue || viewValue;
    return ctrl.$isEmpty(value) || URL_REGEXP.test(value);
  };
}

function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
  // Note: no badInputChecker here by purpose as `url` is only a validation
  // in browsers, i.e. we can always read out input.value even if it is not valid!
  baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
  stringBasedInputType(ctrl);

  ctrl.$validators.email = function(modelValue, viewValue) {
    var value = modelValue || viewValue;
    return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value);
  };
}

function radioInputType(scope, element, attr, ctrl) {
  var doTrim = !attr.ngTrim || trim(attr.ngTrim) !== 'false';
  // make the name unique, if not defined
  if (isUndefined(attr.name)) {
    element.attr('name', nextUid());
  }

  var listener = function(ev) {
    var value;
    if (element[0].checked) {
      value = attr.value;
      if (doTrim) {
        value = trim(value);
      }
      ctrl.$setViewValue(value, ev && ev.type);
    }
  };

  element.on('change', listener);

  ctrl.$render = function() {
    var value = attr.value;
    if (doTrim) {
      value = trim(value);
    }
    element[0].checked = (value === ctrl.$viewValue);
  };

  attr.$observe('value', ctrl.$render);
}

function parseConstantExpr($parse, context, name, expression, fallback) {
  var parseFn;
  if (isDefined(expression)) {
    parseFn = $parse(expression);
    if (!parseFn.constant) {
      throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' +
                                   '`{1}`.', name, expression);
    }
    return parseFn(context);
  }
  return fallback;
}

function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) {
  var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true);
  var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false);

  var listener = function(ev) {
    ctrl.$setViewValue(element[0].checked, ev && ev.type);
  };

  element.on('change', listener);

  ctrl.$render = function() {
    element[0].checked = ctrl.$viewValue;
  };

  // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false`
  // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert
  // it to a boolean.
  ctrl.$isEmpty = function(value) {
    return value === false;
  };

  ctrl.$formatters.push(function(value) {
    return equals(value, trueValue);
  });

  ctrl.$parsers.push(function(value) {
    return value ? trueValue : falseValue;
  });
}


/**
 * @ngdoc directive
 * @name textarea
 * @restrict E
 *
 * @description
 * HTML textarea element control with AngularJS data-binding. The data-binding and validation
 * properties of this element are exactly the same as those of the
 * {@link ng.directive:input input element}.
 *
 * @param {string} ngModel Assignable AngularJS expression to data-bind to.
 * @param {string=} name Property name of the form under which the control is published.
 * @param {string=} required Sets `required` validation error key if the value is not entered.
 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
 *    the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
 *    `required` when you want to data-bind to the `required` attribute.
 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
 *    minlength.
 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
 *    maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
 *    length.
 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
 *    does not match a RegExp found by evaluating the AngularJS expression given in the attribute value.
 *    If the expression evaluates to a RegExp object, then this is used directly.
 *    If the expression evaluates to a string, then it will be converted to a RegExp
 *    after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
 *    `new RegExp('^abc$')`.<br />
 *    **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
 *    start at the index of the last search's match, thus not taking the whole input value into
 *    account.
 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user
 *    interaction with the input element.
 * @param {boolean=} [ngTrim=true] If set to false AngularJS will not automatically trim the input.
 *
 * @knownIssue
 *
 * When specifying the `placeholder` attribute of `<textarea>`, Internet Explorer will temporarily
 * insert the placeholder value as the textarea's content. If the placeholder value contains
 * interpolation (`{{ ... }}`), an error will be logged in the console when AngularJS tries to update
 * the value of the by-then-removed text node. This doesn't affect the functionality of the
 * textarea, but can be undesirable.
 *
 * You can work around this Internet Explorer issue by using `ng-attr-placeholder` instead of
 * `placeholder` on textareas, whenever you need interpolation in the placeholder value. You can
 * find more details on `ngAttr` in the
 * [Interpolation](guide/interpolation#-ngattr-for-binding-to-arbitrary-attributes) section of the
 * Developer Guide.
 */


/**
 * @ngdoc directive
 * @name input
 * @restrict E
 *
 * @description
 * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding,
 * input state control, and validation.
 * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers.
 *
 * <div class="alert alert-warning">
 * **Note:** Not every feature offered is available for all input types.
 * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`.
 * </div>
 *
 * @param {string} ngModel Assignable AngularJS expression to data-bind to.
 * @param {string=} name Property name of the form under which the control is published.
 * @param {string=} required Sets `required` validation error key if the value is not entered.
 * @param {boolean=} ngRequired Sets `required` attribute if set to true
 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
 *    minlength.
 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
 *    maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
 *    length.
 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
 *    value does not match a RegExp found by evaluating the AngularJS expression given in the attribute value.
 *    If the expression evaluates to a RegExp object, then this is used directly.
 *    If the expression evaluates to a string, then it will be converted to a RegExp
 *    after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
 *    `new RegExp('^abc$')`.<br />
 *    **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
 *    start at the index of the last search's match, thus not taking the whole input value into
 *    account.
 * @param {string=} ngChange AngularJS expression to be executed when input changes due to user
 *    interaction with the input element.
 * @param {boolean=} [ngTrim=true] If set to false AngularJS will not automatically trim the input.
 *    This parameter is ignored for input[type=password] controls, which will never trim the
 *    input.
 *
 * @example
    <example name="input-directive" module="inputExample">
      <file name="index.html">
       <script>
          angular.module('inputExample', [])
            .controller('ExampleController', ['$scope', function($scope) {
              $scope.user = {name: 'guest', last: 'visitor'};
            }]);
       </script>
       <div ng-controller="ExampleController">
         <form name="myForm">
           <label>
              User name:
              <input type="text" name="userName" ng-model="user.name" required>
           </label>
           <div role="alert">
             <span class="error" ng-show="myForm.userName.$error.required">
              Required!</span>
           </div>
           <label>
              Last name:
              <input type="text" name="lastName" ng-model="user.last"
              ng-minlength="3" ng-maxlength="10">
           </label>
           <div role="alert">
             <span class="error" ng-show="myForm.lastName.$error.minlength">
               Too short!</span>
             <span class="error" ng-show="myForm.lastName.$error.maxlength">
               Too long!</span>
           </div>
         </form>
         <hr>
         <tt>user = {{user}}</tt><br/>
         <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br/>
         <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br/>
         <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br/>
         <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br/>
         <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
         <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
         <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br/>
         <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br/>
       </div>
      </file>
      <file name="protractor.js" type="protractor">
        var user = element(by.exactBinding('user'));
        var userNameValid = element(by.binding('myForm.userName.$valid'));
        var lastNameValid = element(by.binding('myForm.lastName.$valid'));
        var lastNameError = element(by.binding('myForm.lastName.$error'));
        var formValid = element(by.binding('myForm.$valid'));
        var userNameInput = element(by.model('user.name'));
        var userLastInput = element(by.model('user.last'));

        it('should initialize to model', function() {
          expect(user.getText()).toContain('{"name":"guest","last":"visitor"}');
          expect(userNameValid.getText()).toContain('true');
          expect(formValid.getText()).toContain('true');
        });

        it('should be invalid if empty when required', function() {
          userNameInput.clear();
          userNameInput.sendKeys('');

          expect(user.getText()).toContain('{"last":"visitor"}');
          expect(userNameValid.getText()).toContain('false');
          expect(formValid.getText()).toContain('false');
        });

        it('should be valid if empty when min length is set', function() {
          userLastInput.clear();
          userLastInput.sendKeys('');

          expect(user.getText()).toContain('{"name":"guest","last":""}');
          expect(lastNameValid.getText()).toContain('true');
          expect(formValid.getText()).toContain('true');
        });

        it('should be invalid if less than required min length', function() {
          userLastInput.clear();
          userLastInput.sendKeys('xx');

          expect(user.getText()).toContain('{"name":"guest"}');
          expect(lastNameValid.getText()).toContain('false');
          expect(lastNameError.getText()).toContain('minlength');
          expect(formValid.getText()).toContain('false');
        });

        it('should be invalid if longer than max length', function() {
          userLastInput.clear();
          userLastInput.sendKeys('some ridiculously long name');

          expect(user.getText()).toContain('{"name":"guest"}');
          expect(lastNameValid.getText()).toContain('false');
          expect(lastNameError.getText()).toContain('maxlength');
          expect(formValid.getText()).toContain('false');
        });
      </file>
    </example>
 */
var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
    function($browser, $sniffer, $filter, $parse) {
  return {
    restrict: 'E',
    require: ['?ngModel'],
    link: {
      pre: function(scope, element, attr, ctrls) {
        if (ctrls[0]) {
          (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
                                                              $browser, $filter, $parse);
        }
      }
    }
  };
}];


var hiddenInputBrowserCacheDirective = function() {
  var valueProperty = {
    configurable: true,
    enumerable: false,
    get: function() {
      return this.getAttribute('value') || '';
    },
    set: function(val) {
      this.setAttribute('value', val);
    }
  };

  return {
    restrict: 'E',
    priority: 200,
    compile: function(_, attr) {
      if (lowercase(attr.type) !== 'hidden') {
        return;
      }

      return {
        pre: function(scope, element, attr, ctrls) {
          var node = element[0];

          // Support: Edge
          // Moving the DOM around prevents autofillling
          if (node.parentNode) {
            node.parentNode.insertBefore(node, node.nextSibling);
          }

          // Support: FF, IE
          // Avoiding direct assignment to .value prevents autofillling
          if (Object.defineProperty) {
            Object.defineProperty(node, 'value', valueProperty);
          }
        }
      };
    }
  };
};



var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
/**
 * @ngdoc directive
 * @name ngValue
 * @restrict A
 * @priority 100
 *
 * @description
 * Binds the given expression to the value of the element.
 *
 * It is mainly used on {@link input[radio] `input[radio]`} and option elements,
 * so that when the element is selected, the {@link ngModel `ngModel`} of that element (or its
 * {@link select `select`} parent element) is set to the bound value. It is especially useful
 * for dynamically generated lists using {@link ngRepeat `ngRepeat`}, as shown below.
 *
 * It can also be used to achieve one-way binding of a given expression to an input element
 * such as an `input[text]` or a `textarea`, when that element does not use ngModel.
 *
 * @element ANY
 * @param {string=} ngValue AngularJS expression, whose value will be bound to the `value` attribute
 * and `value` property of the element.
 *
 * @example
    <example name="ngValue-directive" module="valueExample">
      <file name="index.html">
       <script>
          angular.module('valueExample', [])
            .controller('ExampleController', ['$scope', function($scope) {
              $scope.names = ['pizza', 'unicorns', 'robots'];
              $scope.my = { favorite: 'unicorns' };
            }]);
       </script>
        <form ng-controller="ExampleController">
          <h2>Which is your favorite?</h2>
            <label ng-repeat="name in names" for="{{name}}">
              {{name}}
              <input type="radio"
                     ng-model="my.favorite"
                     ng-value="name"
                     id="{{name}}"
                     name="favorite">
            </label>
          <div>You chose {{my.favorite}}</div>
        </form>
      </file>
      <file name="protractor.js" type="protractor">
        var favorite = element(by.binding('my.favorite'));

        it('should initialize to model', function() {
          expect(favorite.getText()).toContain('unicorns');
        });
        it('should bind the values to the inputs', function() {
          element.all(by.model('my.favorite')).get(0).click();
          expect(favorite.getText()).toContain('pizza');
        });
      </file>
    </example>
 */
var ngValueDirective = function() {
  /**
   *  inputs use the value attribute as their default value if the value property is not set.
   *  Once the value property has been set (by adding input), it will not react to changes to
   *  the value attribute anymore. Setting both attribute and property fixes this behavior, and
   *  makes it possible to use ngValue as a sort of one-way bind.
   */
  function updateElementValue(element, attr, value) {
    // Support: IE9 only
    // In IE9 values are converted to string (e.g. `input.value = null` results in `input.value === 'null'`).
    var propValue = isDefined(value) ? value : (msie === 9) ? '' : null;
    element.prop('value', propValue);
    attr.$set('value', value);
  }

  return {
    restrict: 'A',
    priority: 100,
    compile: function(tpl, tplAttr) {
      if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) {
        return function ngValueConstantLink(scope, elm, attr) {
          var value = scope.$eval(attr.ngValue);
          updateElementValue(elm, attr, value);
        };
      } else {
        return function ngValueLink(scope, elm, attr) {
          scope.$watch(attr.ngValue, function valueWatchAction(value) {
            updateElementValue(elm, attr, value);
          });
        };
      }
    }
  };
};

/**
 * @ngdoc directive
 * @name ngBind
 * @restrict AC
 *
 * @description
 * The `ngBind` attribute tells AngularJS to replace the text content of the specified HTML element
 * with the value of a given expression, and to update the text content when the value of that
 * expression changes.
 *
 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like
 * `{{ expression }}` which is similar but less verbose.
 *
 * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily
 * displayed by the browser in its raw state before AngularJS compiles it. Since `ngBind` is an
 * element attribute, it makes the bindings invisible to the user while the page is loading.
 *
 * An alternative solution to this problem would be using the
 * {@link ng.directive:ngCloak ngCloak} directive.
 *
 *
 * @element ANY
 * @param {expression} ngBind {@link guide/expression Expression} to evaluate.
 *
 * @example
 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly.
   <example module="bindExample" name="ng-bind">
     <file name="index.html">
       <script>
         angular.module('bindExample', [])
           .controller('ExampleController', ['$scope', function($scope) {
             $scope.name = 'Whirled';
           }]);
       </script>
       <div ng-controller="ExampleController">
         <label>Enter name: <input type="text" ng-model="name"></label><br>
         Hello <span ng-bind="name"></span>!
       </div>
     </file>
     <file name="protractor.js" type="protractor">
       it('should check ng-bind', function() {
         var nameInput = element(by.model('name'));

         expect(element(by.binding('name')).getText()).toBe('Whirled');
         nameInput.clear();
         nameInput.sendKeys('world');
         expect(element(by.binding('name')).getText()).toBe('world');
       });
     </file>
   </example>
 */
var ngBindDirective = ['$compile', function($compile) {
  return {
    restrict: 'AC',
    compile: function ngBindCompile(templateElement) {
      $compile.$$addBindingClass(templateElement);
      return function ngBindLink(scope, element, attr) {
        $compile.$$addBindingInfo(element, attr.ngBind);
        element = element[0];
        scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
          element.textContent = stringify(value);
        });
      };
    }
  };
}];


/**
 * @ngdoc directive
 * @name ngBindTemplate
 *
 * @description
 * The `ngBindTemplate` directive specifies that the element
 * text content should be replaced with the interpolation of the template
 * in the `ngBindTemplate` attribute.
 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}`
 * expressions. This directive is needed since some HTML elements
 * (such as TITLE and OPTION) cannot contain SPAN elements.
 *
 * @element ANY
 * @param {string} ngBindTemplate template of form
 *   <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval.
 *
 * @example
 * Try it here: enter text in text box and watch the greeting change.
   <example module="bindExample" name="ng-bind-template">
     <file name="index.html">
       <script>
         angular.module('bindExample', [])
           .controller('ExampleController', ['$scope', function($scope) {
             $scope.salutation = 'Hello';
             $scope.name = 'World';
           }]);
       </script>
       <div ng-controller="ExampleController">
        <label>Salutation: <input type="text" ng-model="salutation"></label><br>
        <label>Name: <input type="text" ng-model="name"></label><br>
        <pre ng-bind-template="{{salutation}} {{name}}!"></pre>
       </div>
     </file>
     <file name="protractor.js" type="protractor">
       it('should check ng-bind', function() {
         var salutationElem = element(by.binding('salutation'));
         var salutationInput = element(by.model('salutation'));
         var nameInput = element(by.model('name'));

         expect(salutationElem.getText()).toBe('Hello World!');

         salutationInput.clear();
         salutationInput.sendKeys('Greetings');
         nameInput.clear();
         nameInput.sendKeys('user');

         expect(salutationElem.getText()).toBe('Greetings user!');
       });
     </file>
   </example>
 */
var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) {
  return {
    compile: function ngBindTemplateCompile(templateElement) {
      $compile.$$addBindingClass(templateElement);
      return function ngBindTemplateLink(scope, element, attr) {
        var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate));
        $compile.$$addBindingInfo(element, interpolateFn.expressions);
        element = element[0];
        attr.$observe('ngBindTemplate', function(value) {
          element.textContent = isUndefined(value) ? '' : value;
        });
      };
    }
  };
}];


/**
 * @ngdoc directive
 * @name ngBindHtml
 *
 * @description
 * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default,
 * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service.
 * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link
 * ngSanitize} in your module's dependencies (not in core AngularJS). In order to use {@link ngSanitize}
 * in your module's dependencies, you need to include "angular-sanitize.js" in your application.
 *
 * You may also bypass sanitization for values you know are safe. To do so, bind to
 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}.  See the example
 * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}.
 *
 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you
 * will have an exception (instead of an exploit.)
 *
 * @element ANY
 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate.
 *
 * @example

   <example module="bindHtmlExample" deps="angular-sanitize.js" name="ng-bind-html">
     <file name="index.html">
       <div ng-controller="ExampleController">
        <p ng-bind-html="myHTML"></p>
       </div>
     </file>

     <file name="script.js">
       angular.module('bindHtmlExample', ['ngSanitize'])
         .controller('ExampleController', ['$scope', function($scope) {
           $scope.myHTML =
              'I am an <code>HTML</code>string with ' +
              '<a href="#">links!</a> and other <em>stuff</em>';
         }]);
     </file>

     <file name="protractor.js" type="protractor">
       it('should check ng-bind-html', function() {
         expect(element(by.binding('myHTML')).getText()).toBe(
             'I am an HTMLstring with links! and other stuff');
       });
     </file>
   </example>
 */
var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) {
  return {
    restrict: 'A',
    compile: function ngBindHtmlCompile(tElement, tAttrs) {
      var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml);
      var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function sceValueOf(val) {
        // Unwrap the value to compare the actual inner safe value, not the wrapper object.
        return $sce.valueOf(val);
      });
      $compile.$$addBindingClass(tElement);

      return function ngBindHtmlLink(scope, element, attr) {
        $compile.$$addBindingInfo(element, attr.ngBindHtml);

        scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() {
          // The watched value is the unwrapped value. To avoid re-escaping, use the direct getter.
          var value = ngBindHtmlGetter(scope);
          element.html($sce.getTrustedHtml(value) || '');
        });
      };
    }
  };
}];

/**
 * @ngdoc directive
 * @name ngChange
 * @restrict A
 *
 * @description
 * Evaluate the given expression when the user changes the input.
 * The expression is evaluated immediately, unlike the JavaScript onchange event
 * which only triggers at the end of a change (usually, when the user leaves the
 * form element or presses the return key).
 *
 * The `ngChange` expression is only evaluated when a change in the input value causes
 * a new value to be committed to the model.
 *
 * It will not be evaluated:
 * * if the value returned from the `$parsers` transformation pipeline has not changed
 * * if the input has continued to be invalid since the model will stay `null`
 * * if the model is changed programmatically and not by a change to the input value
 *
 *
 * Note, this directive requires `ngModel` to be present.
 *
 * @element ANY
 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change
 * in input value.
 *
 * @example
 * <example name="ngChange-directive" module="changeExample">
 *   <file name="index.html">
 *     <script>
 *       angular.module('changeExample', [])
 *         .controller('ExampleController', ['$scope', function($scope) {
 *           $scope.counter = 0;
 *           $scope.change = function() {
 *             $scope.counter++;
 *           };
 *         }]);
 *     </script>
 *     <div ng-controller="ExampleController">
 *       <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
 *       <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
 *       <label for="ng-change-example2">Confirmed</label><br />
 *       <tt>debug = {{confirmed}}</tt><br/>
 *       <tt>counter = {{counter}}</tt><br/>
 *     </div>
 *   </file>
 *   <file name="protractor.js" type="protractor">
 *     var counter = element(by.binding('counter'));
 *     var debug = element(by.binding('confirmed'));
 *
 *     it('should evaluate the expression if changing from view', function() {
 *       expect(counter.getText()).toContain('0');
 *
 *       element(by.id('ng-change-example1')).click();
 *
 *       expect(counter.getText()).toContain('1');
 *       expect(debug.getText()).toContain('true');
 *     });
 *
 *     it('should not evaluate the expression if changing from model', function() {
 *       element(by.id('ng-change-example2')).click();

 *       expect(counter.getText()).toContain('0');
 *       expect(debug.getText()).toContain('true');
 *     });
 *   </file>
 * </example>
 */
var ngChangeDirective = valueFn({
  restrict: 'A',
  require: 'ngModel',
  link: function(scope, element, attr, ctrl) {
    ctrl.$viewChangeListeners.push(function() {
      scope.$eval(attr.ngChange);
    });
  }
});

/* exported
  ngClassDirective,
  ngClassEvenDirective,
  ngClassOddDirective
*/

function classDirective(name, selector) {
  name = 'ngClass' + name;
  var indexWatchExpression;

  return ['$parse', function($parse) {
    return {
      restrict: 'AC',
      link: function(scope, element, attr) {
        var classCounts = element.data('$classCounts');
        var oldModulo = true;
        var oldClassString;

        if (!classCounts) {
          // Use createMap() to prevent class assumptions involving property
          // names in Object.prototype
          classCounts = createMap();
          element.data('$classCounts', classCounts);
        }

        if (name !== 'ngClass') {
          if (!indexWatchExpression) {
            indexWatchExpression = $parse('$index', function moduloTwo($index) {
              // eslint-disable-next-line no-bitwise
              return $index & 1;
            });
          }

          scope.$watch(indexWatchExpression, ngClassIndexWatchAction);
        }

        scope.$watch($parse(attr[name], toClassString), ngClassWatchAction);

        function addClasses(classString) {
          classString = digestClassCounts(split(classString), 1);
          attr.$addClass(classString);
        }

        function removeClasses(classString) {
          classString = digestClassCounts(split(classString), -1);
          attr.$removeClass(classString);
        }

        function updateClasses(oldClassString, newClassString) {
          var oldClassArray = split(oldClassString);
          var newClassArray = split(newClassString);

          var toRemoveArray = arrayDifference(oldClassArray, newClassArray);
          var toAddArray = arrayDifference(newClassArray, oldClassArray);

          var toRemoveString = digestClassCounts(toRemoveArray, -1);
          var toAddString = digestClassCounts(toAddArray, 1);

          attr.$addClass(toAddString);
          attr.$removeClass(toRemoveString);
        }

        function digestClassCounts(classArray, count) {
          var classesToUpdate = [];

          forEach(classArray, function(className) {
            if (count > 0 || classCounts[className]) {
              classCounts[className] = (classCounts[className] || 0) + count;
              if (classCounts[className] === +(count > 0)) {
                classesToUpdate.push(className);
              }
            }
          });

          return classesToUpdate.join(' ');
        }

        function ngClassIndexWatchAction(newModulo) {
          // This watch-action should run before the `ngClassWatchAction()`, thus it
          // adds/removes `oldClassString`. If the `ngClass` expression has changed as well, the
          // `ngClassWatchAction()` will update the classes.
          if (newModulo === selector) {
            addClasses(oldClassString);
          } else {
            removeClasses(oldClassString);
          }

          oldModulo = newModulo;
        }

        function ngClassWatchAction(newClassString) {
          if (oldModulo === selector) {
            updateClasses(oldClassString, newClassString);
          }

          oldClassString = newClassString;
        }
      }
    };
  }];

  // Helpers
  function arrayDifference(tokens1, tokens2) {
    if (!tokens1 || !tokens1.length) return [];
    if (!tokens2 || !tokens2.length) return tokens1;

    var values = [];

    outer:
    for (var i = 0; i < tokens1.length; i++) {
      var token = tokens1[i];
      for (var j = 0; j < tokens2.length; j++) {
        if (token === tokens2[j]) continue outer;
      }
      values.push(token);
    }

    return values;
  }

  function split(classString) {
    return classString && classString.split(' ');
  }

  function toClassString(classValue) {
    if (!classValue) return classValue;

    var classString = classValue;

    if (isArray(classValue)) {
      classString = classValue.map(toClassString).join(' ');
    } else if (isObject(classValue)) {
      classString = Object.keys(classValue).
        filter(function(key) { return classValue[key]; }).
        join(' ');
    } else if (!isString(classValue)) {
      classString = classValue + '';
    }

    return classString;
  }
}

/**
 * @ngdoc directive
 * @name ngClass
 * @restrict AC
 * @element ANY
 *
 * @description
 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding
 * an expression that represents all classes to be added.
 *
 * The directive operates in three different ways, depending on which of three types the expression
 * evaluates to:
 *
 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class
 * names.
 *
 * 2. If the expression evaluates to an object, then for each key-value pair of the
 * object with a truthy value the corresponding key is used as a class name.
 *
 * 3. If the expression evaluates to an array, each element of the array should either be a string as in
 * type 1 or an object as in type 2. This means that you can mix strings and objects together in an array
 * to give you more control over what CSS classes appear. See the code below for an example of this.
 *
 *
 * The directive won't add duplicate classes if a particular class was already set.
 *
 * When the expression changes, the previously added classes are removed and only then are the
 * new classes added.
 *
 * @knownIssue
 * You should not use {@link guide/interpolation interpolation} in the value of the `class`
 * attribute, when using the `ngClass` directive on the same element.
 * See {@link guide/interpolation#known-issues here} for more info.
 *
 * @animations
 * | Animation                        | Occurs                              |
 * |----------------------------------|-------------------------------------|
 * | {@link ng.$animate#addClass addClass}       | just before the class is applied to the element   |
 * | {@link ng.$animate#removeClass removeClass} | just before the class is removed from the element |
 * | {@link ng.$animate#setClass setClass} | just before classes are added and classes are removed from the element at the same time |
 *
 * ### ngClass and pre-existing CSS3 Transitions/Animations
   The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure.
   Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder
   any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure
   to view the step by step details of {@link $animate#addClass $animate.addClass} and
   {@link $animate#removeClass $animate.removeClass}.
 *
 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result
 *   of the evaluation can be a string representing space delimited class
 *   names, an array, or a map of class names to boolean values. In the case of a map, the
 *   names of the properties whose values are truthy will be added as css classes to the
 *   element.
 *
 * @example
 * ### Basic
   <example name="ng-class">
     <file name="index.html">
       <p ng-class="{strike: deleted, bold: important, 'has-error': error}">Map Syntax Example</p>
       <label>
          <input type="checkbox" ng-model="deleted">
          deleted (apply "strike" class)
       </label><br>
       <label>
          <input type="checkbox" ng-model="important">
          important (apply "bold" class)
       </label><br>
       <label>
          <input type="checkbox" ng-model="error">
          error (apply "has-error" class)
       </label>
       <hr>
       <p ng-class="style">Using String Syntax</p>
       <input type="text" ng-model="style"
              placeholder="Type: bold strike red" aria-label="Type: bold strike red">
       <hr>
       <p ng-class="[style1, style2, style3]">Using Array Syntax</p>
       <input ng-model="style1"
              placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red"><br>
       <input ng-model="style2"
              placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 2"><br>
       <input ng-model="style3"
              placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 3"><br>
       <hr>
       <p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p>
       <input ng-model="style4" placeholder="Type: bold, strike" aria-label="Type: bold, strike"><br>
       <label><input type="checkbox" ng-model="warning"> warning (apply "orange" class)</label>
     </file>
     <file name="style.css">
       .strike {
           text-decoration: line-through;
       }
       .bold {
           font-weight: bold;
       }
       .red {
           color: red;
       }
       .has-error {
           color: red;
           background-color: yellow;
       }
       .orange {
           color: orange;
       }
     </file>
     <file name="protractor.js" type="protractor">
       var ps = element.all(by.css('p'));

       it('should let you toggle the class', function() {

         expect(ps.first().getAttribute('class')).not.toMatch(/bold/);
         expect(ps.first().getAttribute('class')).not.toMatch(/has-error/);

         element(by.model('important')).click();
         expect(ps.first().getAttribute('class')).toMatch(/bold/);

         element(by.model('error')).click();
         expect(ps.first().getAttribute('class')).toMatch(/has-error/);
       });

       it('should let you toggle string example', function() {
         expect(ps.get(1).getAttribute('class')).toBe('');
         element(by.model('style')).clear();
         element(by.model('style')).sendKeys('red');
         expect(ps.get(1).getAttribute('class')).toBe('red');
       });

       it('array example should have 3 classes', function() {
         expect(ps.get(2).getAttribute('class')).toBe('');
         element(by.model('style1')).sendKeys('bold');
         element(by.model('style2')).sendKeys('strike');
         element(by.model('style3')).sendKeys('red');
         expect(ps.get(2).getAttribute('class')).toBe('bold strike red');
       });

       it('array with map example should have 2 classes', function() {
         expect(ps.last().getAttribute('class')).toBe('');
         element(by.model('style4')).sendKeys('bold');
         element(by.model('warning')).click();
         expect(ps.last().getAttribute('class')).toBe('bold orange');
       });
     </file>
   </example>

   @example
   ### Animations

   The example below demonstrates how to perform animations using ngClass.

   <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-class">
     <file name="index.html">
      <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'">
      <input id="clearbtn" type="button" value="clear" ng-click="myVar=''">
      <br>
      <span class="base-class" ng-class="myVar">Sample Text</span>
     </file>
     <file name="style.css">
       .base-class {
         transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
       }

       .base-class.my-class {
         color: red;
         font-size:3em;
       }
     </file>
     <file name="protractor.js" type="protractor">
       it('should check ng-class', function() {
         expect(element(by.css('.base-class')).getAttribute('class')).not.
           toMatch(/my-class/);

         element(by.id('setbtn')).click();

         expect(element(by.css('.base-class')).getAttribute('class')).
           toMatch(/my-class/);

         element(by.id('clearbtn')).click();

         expect(element(by.css('.base-class')).getAttribute('class')).not.
           toMatch(/my-class/);
       });
     </file>
   </example>
 */
var ngClassDirective = classDirective('', true);

/**
 * @ngdoc directive
 * @name ngClassOdd
 * @restrict AC
 *
 * @description
 * The `ngClassOdd` and `ngClassEven` directives work exactly as
 * {@link ng.directive:ngClass ngClass}, except they work in
 * conjunction with `ngRepeat` and take effect only on odd (even) rows.
 *
 * This directive can be applied only within the scope of an
 * {@link ng.directive:ngRepeat ngRepeat}.
 *
 * @animations
 * | Animation                        | Occurs                              |
 * |----------------------------------|-------------------------------------|
 * | {@link ng.$animate#addClass addClass}       | just before the class is applied to the element   |
 * | {@link ng.$animate#removeClass removeClass} | just before the class is removed from the element |
 *
 * @element ANY
 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result
 *   of the evaluation can be a string representing space delimited class names or an array.
 *
 * @example
   <example name="ng-class-odd">
     <file name="index.html">
        <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
          <li ng-repeat="name in names">
           <span ng-class-odd="'odd'" ng-class-even="'even'">
             {{name}}
           </span>
          </li>
        </ol>
     </file>
     <file name="style.css">
       .odd {
         color: red;
       }
       .even {
         color: blue;
       }
     </file>
     <file name="protractor.js" type="protractor">
       it('should check ng-class-odd and ng-class-even', function() {
         expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
           toMatch(/odd/);
         expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
           toMatch(/even/);
       });
     </file>
   </example>
 *
 * <hr />
 * @example
 * An example on how to implement animations using `ngClassOdd`:
 *
   <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-class-odd-animate">
     <file name="index.html">
       <div ng-init="items=['Item 3', 'Item 2', 'Item 1', 'Item 0']">
         <button ng-click="items.unshift('Item ' + items.length)">Add item</button>
         <hr />
         <table>
           <tr ng-repeat="item in items" ng-class-odd="'odd'">
             <td>{{ item }}</td>
           </tr>
         </table>
       </div>
     </file>
     <file name="style.css">
       .odd {
         background: rgba(255, 255, 0, 0.25);
       }

       .odd-add, .odd-remove {
         transition: 1.5s;
       }
     </file>
     <file name="protractor.js" type="protractor">
       it('should add new entries to the beginning of the list', function() {
         var button = element(by.buttonText('Add item'));
         var rows = element.all(by.repeater('item in items'));

         expect(rows.count()).toBe(4);
         expect(rows.get(0).getText()).toBe('Item 3');
         expect(rows.get(1).getText()).toBe('Item 2');

         button.click();

         expect(rows.count()).toBe(5);
         expect(rows.get(0).getText()).toBe('Item 4');
         expect(rows.get(1).getText()).toBe('Item 3');
       });

       it('should add odd class to odd entries', function() {
         var button = element(by.buttonText('Add item'));
         var rows = element.all(by.repeater('item in items'));

         expect(rows.get(0).getAttribute('class')).toMatch(/odd/);
         expect(rows.get(1).getAttribute('class')).not.toMatch(/odd/);

         button.click();

         expect(rows.get(0).getAttribute('class')).toMatch(/odd/);
         expect(rows.get(1).getAttribute('class')).not.toMatch(/odd/);
       });
     </file>
   </example>
 */
var ngClassOddDirective = classDirective('Odd', 0);

/**
 * @ngdoc directive
 * @name ngClassEven
 * @restrict AC
 *
 * @description
 * The `ngClassOdd` and `ngClassEven` directives work exactly as
 * {@link ng.directive:ngClass ngClass}, except they work in
 * conjunction with `ngRepeat` and take effect only on odd (even) rows.
 *
 * This directive can be applied only within the scope of an
 * {@link ng.directive:ngRepeat ngRepeat}.
 *
 * @animations
 * | Animation                        | Occurs                              |
 * |----------------------------------|-------------------------------------|
 * | {@link ng.$animate#addClass addClass}       | just before the class is applied to the element   |
 * | {@link ng.$animate#removeClass removeClass} | just before the class is removed from the element |
 *
 * @element ANY
 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The
 *   result of the evaluation can be a string representing space delimited class names or an array.
 *
 * @example
   <example name="ng-class-even">
     <file name="index.html">
        <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
          <li ng-repeat="name in names">
           <span ng-class-odd="'odd'" ng-class-even="'even'">
             {{name}} &nbsp; &nbsp; &nbsp;
           </span>
          </li>
        </ol>
     </file>
     <file name="style.css">
       .odd {
         color: red;
       }
       .even {
         color: blue;
       }
     </file>
     <file name="protractor.js" type="protractor">
       it('should check ng-class-odd and ng-class-even', function() {
         expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
           toMatch(/odd/);
         expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
           toMatch(/even/);
       });
     </file>
   </example>
 *
 * <hr />
 * @example
 * An example on how to implement animations using `ngClassEven`:
 *
   <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-class-even-animate">
     <file name="index.html">
       <div ng-init="items=['Item 3', 'Item 2', 'Item 1', 'Item 0']">
         <button ng-click="items.unshift('Item ' + items.length)">Add item</button>
         <hr />
         <table>
           <tr ng-repeat="item in items" ng-class-even="'even'">
             <td>{{ item }}</td>
           </tr>
         </table>
       </div>
     </file>
     <file name="style.css">
       .even {
         background: rgba(255, 255, 0, 0.25);
       }

       .even-add, .even-remove {
         transition: 1.5s;
       }
     </file>
     <file name="protractor.js" type="protractor">
       it('should add new entries to the beginning of the list', function() {
         var button = element(by.buttonText('Add item'));
         var rows = element.all(by.repeater('item in items'));

         expect(rows.count()).toBe(4);
         expect(rows.get(0).getText()).toBe('Item 3');
         expect(rows.get(1).getText()).toBe('Item 2');

         button.click();

         expect(rows.count()).toBe(5);
         expect(rows.get(0).getText()).toBe('Item 4');
         expect(rows.get(1).getText()).toBe('Item 3');
       });

       it('should add even class to even entries', function() {
         var button = element(by.buttonText('Add item'));
         var rows = element.all(by.repeater('item in items'));

         expect(rows.get(0).getAttribute('class')).not.toMatch(/even/);
         expect(rows.get(1).getAttribute('class')).toMatch(/even/);

         button.click();

         expect(rows.get(0).getAttribute('class')).not.toMatch(/even/);
         expect(rows.get(1).getAttribute('class')).toMatch(/even/);
       });
     </file>
   </example>
 */
var ngClassEvenDirective = classDirective('Even', 1);

/**
 * @ngdoc directive
 * @name ngCloak
 * @restrict AC
 *
 * @description
 * The `ngCloak` directive is used to prevent the AngularJS html template from being briefly
 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this
 * directive to avoid the undesirable flicker effect caused by the html template display.
 *
 * The directive can be applied to the `<body>` element, but the preferred usage is to apply
 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering
 * of the browser view.
 *
 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and
 * `angular.min.js`.
 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
 *
 * ```css
 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
 *   display: none !important;
 * }
 * ```
 *
 * When this css rule is loaded by the browser, all html elements (including their children) that
 * are tagged with the `ngCloak` directive are hidden. When AngularJS encounters this directive
 * during the compilation of the template it deletes the `ngCloak` element attribute, making
 * the compiled element visible.
 *
 * For the best result, the `angular.js` script must be loaded in the head section of the html
 * document; alternatively, the css rule above must be included in the external stylesheet of the
 * application.
 *
 * @element ANY
 *
 * @example
   <example name="ng-cloak">
     <file name="index.html">
        <div id="template1" ng-cloak>{{ 'hello' }}</div>
        <div id="template2" class="ng-cloak">{{ 'world' }}</div>
     </file>
     <file name="protractor.js" type="protractor">
       it('should remove the template directive and css class', function() {
         expect($('#template1').getAttribute('ng-cloak')).
           toBeNull();
         expect($('#template2').getAttribute('ng-cloak')).
           toBeNull();
       });
     </file>
   </example>
 *
 */
var ngCloakDirective = ngDirective({
  compile: function(element, attr) {
    attr.$set('ngCloak', undefined);
    element.removeClass('ng-cloak');
  }
});

/**
 * @ngdoc directive
 * @name ngController
 *
 * @description
 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular
 * supports the principles behind the Model-View-Controller design pattern.
 *
 * MVC components in angular:
 *
 * * Model — Models are the properties of a scope; scopes are attached to the DOM where scope properties
 *   are accessed through bindings.
 * * View — The template (HTML with data bindings) that is rendered into the View.
 * * Controller — The `ngController` directive specifies a Controller class; the class contains business
 *   logic behind the application to decorate the scope with functions and values
 *
 * Note that you can also attach controllers to the DOM by declaring it in a route definition
 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller
 * again using `ng-controller` in the template itself.  This will cause the controller to be attached
 * and executed twice.
 *
 * @element ANY
 * @scope
 * @priority 500
 * @param {expression} ngController Name of a constructor function registered with the current
 * {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression}
 * that on the current scope evaluates to a constructor function.
 *
 * The controller instance can be published into a scope property by specifying
 * `ng-controller="as propertyName"`.
 *
 * @example
 * Here is a simple form for editing user contact information. Adding, removing, clearing, and
 * greeting are methods declared on the controller (see source tab). These methods can
 * easily be called from the AngularJS markup. Any changes to the data are automatically reflected
 * in the View without the need for a manual update.
 *
 * Two different declaration styles are included below:
 *
 * * one binds methods and properties directly onto the controller using `this`:
 * `ng-controller="SettingsController1 as settings"`
 * * one injects `$scope` into the controller:
 * `ng-controller="SettingsController2"`
 *
 * The second option is more common in the AngularJS community, and is generally used in boilerplates
 * and in this guide. However, there are advantages to binding properties directly to the controller
 * and avoiding scope.
 *
 * * Using `controller as` makes it obvious which controller you are accessing in the template when
 * multiple controllers apply to an element.
 * * If you are writing your controllers as classes you have easier access to the properties and
 * methods, which will appear on the scope, from inside the controller code.
 * * Since there is always a `.` in the bindings, you don't have to worry about prototypal
 * inheritance masking primitives.
 *
 * This example demonstrates the `controller as` syntax.
 *
 * <example name="ngControllerAs" module="controllerAsExample">
 *   <file name="index.html">
 *    <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings">
 *      <label>Name: <input type="text" ng-model="settings.name"/></label>
 *      <button ng-click="settings.greet()">greet</button><br/>
 *      Contact:
 *      <ul>
 *        <li ng-repeat="contact in settings.contacts">
 *          <select ng-model="contact.type" aria-label="Contact method" id="select_{{$index}}">
 *             <option>phone</option>
 *             <option>email</option>
 *          </select>
 *          <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" />
 *          <button ng-click="settings.clearContact(contact)">clear</button>
 *          <button ng-click="settings.removeContact(contact)" aria-label="Remove">X</button>
 *        </li>
 *        <li><button ng-click="settings.addContact()">add</button></li>
 *     </ul>
 *    </div>
 *   </file>
 *   <file name="app.js">
 *    angular.module('controllerAsExample', [])
 *      .controller('SettingsController1', SettingsController1);
 *
 *    function SettingsController1() {
 *      this.name = 'John Smith';
 *      this.contacts = [
 *        {type: 'phone', value: '408 555 1212'},
 *        {type: 'email', value: 'john.smith@example.org'}
 *      ];
 *    }
 *
 *    SettingsController1.prototype.greet = function() {
 *      alert(this.name);
 *    };
 *
 *    SettingsController1.prototype.addContact = function() {
 *      this.contacts.push({type: 'email', value: 'yourname@example.org'});
 *    };
 *
 *    SettingsController1.prototype.removeContact = function(contactToRemove) {
 *     var index = this.contacts.indexOf(contactToRemove);
 *      this.contacts.splice(index, 1);
 *    };
 *
 *    SettingsController1.prototype.clearContact = function(contact) {
 *      contact.type = 'phone';
 *      contact.value = '';
 *    };
 *   </file>
 *   <file name="protractor.js" type="protractor">
 *     it('should check controller as', function() {
 *       var container = element(by.id('ctrl-as-exmpl'));
 *         expect(container.element(by.model('settings.name'))
 *           .getAttribute('value')).toBe('John Smith');
 *
 *       var firstRepeat =
 *           container.element(by.repeater('contact in settings.contacts').row(0));
 *       var secondRepeat =
 *           container.element(by.repeater('contact in settings.contacts').row(1));
 *
 *       expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
 *           .toBe('408 555 1212');
 *
 *       expect(secondRepeat.element(by.model('contact.value')).getAttribute('value'))
 *           .toBe('john.smith@example.org');
 *
 *       firstRepeat.element(by.buttonText('clear')).click();
 *
 *       expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
 *           .toBe('');
 *
 *       container.element(by.buttonText('add')).click();
 *
 *       expect(container.element(by.repeater('contact in settings.contacts').row(2))
 *           .element(by.model('contact.value'))
 *           .getAttribute('value'))
 *           .toBe('yourname@example.org');
 *     });
 *   </file>
 * </example>
 *
 * This example demonstrates the "attach to `$scope`" style of controller.
 *
 * <example name="ngController" module="controllerExample">
 *  <file name="index.html">
 *   <div id="ctrl-exmpl" ng-controller="SettingsController2">
 *     <label>Name: <input type="text" ng-model="name"/></label>
 *     <button ng-click="greet()">greet</button><br/>
 *     Contact:
 *     <ul>
 *       <li ng-repeat="contact in contacts">
 *         <select ng-model="contact.type" id="select_{{$index}}">
 *            <option>phone</option>
 *            <option>email</option>
 *         </select>
 *         <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" />
 *         <button ng-click="clearContact(contact)">clear</button>
 *         <button ng-click="removeContact(contact)">X</button>
 *       </li>
 *       <li>[ <button ng-click="addContact()">add</button> ]</li>
 *    </ul>
 *   </div>
 *  </file>
 *  <file name="app.js">
 *   angular.module('controllerExample', [])
 *     .controller('SettingsController2', ['$scope', SettingsController2]);
 *
 *   function SettingsController2($scope) {
 *     $scope.name = 'John Smith';
 *     $scope.contacts = [
 *       {type:'phone', value:'408 555 1212'},
 *       {type:'email', value:'john.smith@example.org'}
 *     ];
 *
 *     $scope.greet = function() {
 *       alert($scope.name);
 *     };
 *
 *     $scope.addContact = function() {
 *       $scope.contacts.push({type:'email', value:'yourname@example.org'});
 *     };
 *
 *     $scope.removeContact = function(contactToRemove) {
 *       var index = $scope.contacts.indexOf(contactToRemove);
 *       $scope.contacts.splice(index, 1);
 *     };
 *
 *     $scope.clearContact = function(contact) {
 *       contact.type = 'phone';
 *       contact.value = '';
 *     };
 *   }
 *  </file>
 *  <file name="protractor.js" type="protractor">
 *    it('should check controller', function() {
 *      var container = element(by.id('ctrl-exmpl'));
 *
 *      expect(container.element(by.model('name'))
 *          .getAttribute('value')).toBe('John Smith');
 *
 *      var firstRepeat =
 *          container.element(by.repeater('contact in contacts').row(0));
 *      var secondRepeat =
 *          container.element(by.repeater('contact in contacts').row(1));
 *
 *      expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
 *          .toBe('408 555 1212');
 *      expect(secondRepeat.element(by.model('contact.value')).getAttribute('value'))
 *          .toBe('john.smith@example.org');
 *
 *      firstRepeat.element(by.buttonText('clear')).click();
 *
 *      expect(firstRepeat.element(by.model('contact.value')).getAttribute('value'))
 *          .toBe('');
 *
 *      container.element(by.buttonText('add')).click();
 *
 *      expect(container.element(by.repeater('contact in contacts').row(2))
 *          .element(by.model('contact.value'))
 *          .getAttribute('value'))
 *          .toBe('yourname@example.org');
 *    });
 *  </file>
 *</example>

 */
var ngControllerDirective = [function() {
  return {
    restrict: 'A',
    scope: true,
    controller: '@',
    priority: 500
  };
}];

/**
 * @ngdoc directive
 * @name ngCsp
 *
 * @restrict A
 * @element ANY
 * @description
 *
 * AngularJS has some features that can conflict with certain restrictions that are applied when using
 * [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) rules.
 *
 * If you intend to implement CSP with these rules then you must tell AngularJS not to use these
 * features.
 *
 * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps.
 *
 *
 * The following default rules in CSP affect AngularJS:
 *
 * * The use of `eval()`, `Function(string)` and similar functions to dynamically create and execute
 * code from strings is forbidden. AngularJS makes use of this in the {@link $parse} service to
 * provide a 30% increase in the speed of evaluating AngularJS expressions. (This CSP rule can be
 * disabled with the CSP keyword `unsafe-eval`, but it is generally not recommended as it would
 * weaken the protections offered by CSP.)
 *
 * * The use of inline resources, such as inline `<script>` and `<style>` elements, are forbidden.
 * This prevents apps from injecting custom styles directly into the document. AngularJS makes use of
 * this to include some CSS rules (e.g. {@link ngCloak} and {@link ngHide}). To make these
 * directives work when a CSP rule is blocking inline styles, you must link to the `angular-csp.css`
 * in your HTML manually. (This CSP rule can be disabled with the CSP keyword `unsafe-inline`, but
 * it is generally not recommended as it would weaken the protections offered by CSP.)
 *
 * If you do not provide `ngCsp` then AngularJS tries to autodetect if CSP is blocking dynamic code
 * creation from strings (e.g., `unsafe-eval` not specified in CSP header) and automatically
 * deactivates this feature in the {@link $parse} service. This autodetection, however, triggers a
 * CSP error to be logged in the console:
 *
 * ```
 * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of
 * script in the following Content Security Policy directive: "default-src 'self'". Note that
 * 'script-src' was not explicitly set, so 'default-src' is used as a fallback.
 * ```
 *
 * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp`
 * directive on an element of the HTML document that appears before the `<script>` tag that loads
 * the `angular.js` file.
 *
 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.*
 *
 * You can specify which of the CSP related AngularJS features should be deactivated by providing
 * a value for the `ng-csp` attribute. The options are as follows:
 *
 * * no-inline-style: this stops AngularJS from injecting CSS styles into the DOM
 *
 * * no-unsafe-eval: this stops AngularJS from optimizing $parse with unsafe eval of strings
 *
 * You can use these values in the following combinations:
 *
 *
 * * No declaration means that AngularJS will assume that you can do inline styles, but it will do
 * a runtime check for unsafe-eval. E.g. `<body>`. This is backwardly compatible with previous
 * versions of AngularJS.
 *
 * * A simple `ng-csp` (or `data-ng-csp`) attribute will tell AngularJS to deactivate both inline
 * styles and unsafe eval. E.g. `<body ng-csp>`. This is backwardly compatible with previous
 * versions of AngularJS.
 *
 * * Specifying only `no-unsafe-eval` tells AngularJS that we must not use eval, but that we can
 * inject inline styles. E.g. `<body ng-csp="no-unsafe-eval">`.
 *
 * * Specifying only `no-inline-style` tells AngularJS that we must not inject styles, but that we can
 * run eval - no automatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">`
 *
 * * Specifying both `no-unsafe-eval` and `no-inline-style` tells AngularJS that we must not inject
 * styles nor use eval, which is the same as an empty: ng-csp.
 * E.g.`<body ng-csp="no-inline-style;no-unsafe-eval">`
 *
 * @example
 *
 * This example shows how to apply the `ngCsp` directive to the `html` tag.
   ```html
     <!doctype html>
     <html ng-app ng-csp>
     ...
     ...
     </html>
   ```

  <!-- Note: the `.csp` suffix in the example name triggers CSP mode in our http server! -->
  <example name="example.csp" module="cspExample" ng-csp="true">
    <file name="index.html">
      <div ng-controller="MainController as ctrl">
        <div>
          <button ng-click="ctrl.inc()" id="inc">Increment</button>
          <span id="counter">
            {{ctrl.counter}}
          </span>
        </div>

        <div>
          <button ng-click="ctrl.evil()" id="evil">Evil</button>
          <span id="evilError">
            {{ctrl.evilError}}
          </span>
        </div>
      </div>
    </file>
    <file name="script.js">
       angular.module('cspExample', [])
         .controller('MainController', function MainController() {
            this.counter = 0;
            this.inc = function() {
              this.counter++;
            };
            this.evil = function() {
              try {
                eval('1+2'); // eslint-disable-line no-eval
              } catch (e) {
                this.evilError = e.message;
              }
            };
          });
    </file>
    <file name="protractor.js" type="protractor">
      var util, webdriver;

      var incBtn = element(by.id('inc'));
      var counter = element(by.id('counter'));
      var evilBtn = element(by.id('evil'));
      var evilError = element(by.id('evilError'));

      function getAndClearSevereErrors() {
        // browser.manage().logs().get('browser') returns empty log 😱 - so it's falsy
        return browser.manage().logs().get('browser').then(function(browserLog) {
          return browserLog.filter(function(logEntry) {
            return logEntry.level.value > webdriver.logging.Level.WARNING.value;
          });
        });
      }

      function clearErrors() {
        getAndClearSevereErrors();
      }

      function expectNoErrors() {
        getAndClearSevereErrors().then(function(filteredLog) {
          expect(filteredLog.length).toEqual(0);
          if (filteredLog.length) {
            console.log('browser console errors: ' + util.inspect(filteredLog));
          }
        });
      }

      function expectError(regex) {
        getAndClearSevereErrors().then(function(filteredLog) {
          var found = false;
          filteredLog.forEach(function(log) {
            if (log.message.match(regex)) {
              found = true;
            }
          });
          if (!found) {
            // we cannot rely on browser logs from this api 😭 … so we comment this out
            // throw new Error('expected an error that matches ' + regex);
          }
        });
      }

      beforeEach(function() {
        util = require('util');
        webdriver = require('selenium-webdriver');
      });

      // For now, we only test on Chrome,
      // as Safari does not load the page with Protractor's injected scripts,
      // and Firefox webdriver always disables content security policy (#6358)
      if (browser.params.browser !== 'chrome') {
        return;
      }

      it('should not report errors when the page is loaded', function() {
        // clear errors so we are not dependent on previous tests
        clearErrors();
        // Need to reload the page as the page is already loaded when
        // we come here
        browser.driver.getCurrentUrl().then(function(url) {
          browser.get(url);
        });
        expectNoErrors();
      });

      it('should evaluate expressions', function() {
        expect(counter.getText()).toEqual('0');
        incBtn.click();
        expect(counter.getText()).toEqual('1');
        expectNoErrors();
      });

      it('should throw and report an error when using "eval"', function() {
        evilBtn.click();
        expect(evilError.getText()).toMatch(/Content Security Policy/);
        expectError(/Content Security Policy/);
      });
    </file>
  </example>
  */

// `ngCsp` is not implemented as a proper directive any more, because we need it be processed while
// we bootstrap the app (before `$parse` is instantiated). For this reason, we just have the `csp()`
// fn that looks for the `ng-csp` attribute anywhere in the current doc.

/**
 * @ngdoc directive
 * @name ngClick
 * @restrict A
 * @element ANY
 * @priority 0
 *
 * @description
 * The ngClick directive allows you to specify custom behavior when
 * an element is clicked.
 *
 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon
 * click. ({@link guide/expression#-event- Event object is available as `$event`})
 *
 * @example
   <example name="ng-click">
     <file name="index.html">
      <button ng-click="count = count + 1" ng-init="count=0">
        Increment
      </button>
      <span>
        count: {{count}}
      </span>
     </file>
     <file name="protractor.js" type="protractor">
       it('should check ng-click', function() {
         expect(element(by.binding('count')).getText()).toMatch('0');
         element(by.css('button')).click();
         expect(element(by.binding('count')).getText()).toMatch('1');
       });
     </file>
   </example>
 */
/*
 * A collection of directives that allows creation of custom event handlers that are defined as
 * AngularJS expressions and are compiled and executed within the current scope.
 */
var ngEventDirectives = {};

// For events that might fire synchronously during DOM manipulation
// we need to execute their event handlers asynchronously using $evalAsync,
// so that they are not executed in an inconsistent state.
var forceAsyncEvents = {
  'blur': true,
  'focus': true
};
forEach(
  'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
  function(eventName) {
    var directiveName = directiveNormalize('ng-' + eventName);
    ngEventDirectives[directiveName] = ['$parse', '$rootScope', '$exceptionHandler', function($parse, $rootScope, $exceptionHandler) {
      return createEventDirective($parse, $rootScope, $exceptionHandler, directiveName, eventName, forceAsyncEvents[eventName]);
    }];
  }
);

function createEventDirective($parse, $rootScope, $exceptionHandler, directiveName, eventName, forceAsync) {
  return {
    restrict: 'A',
    compile: function($element, attr) {
      // NOTE:
      // We expose the powerful `$event` object on the scope that provides access to the Window,
      // etc. This is OK, because expressions are not sandboxed any more (and the expression
      // sandbox was never meant to be a security feature anyway).
      var fn = $parse(attr[directiveName]);
      return function ngEventHandler(scope, element) {
        element.on(eventName, function(event) {
          var callback = function() {
            fn(scope, {$event: event});
          };

          if (!$rootScope.$$phase) {
            scope.$apply(callback);
          } else if (forceAsync) {
            scope.$evalAsync(callback);
          } else {
            try {
              callback();
            } catch (error) {
              $exceptionHandler(error);
            }
          }
        });
      };
    }
  };
}

/**
 * @ngdoc directive
 * @name ngDblclick
 * @restrict A
 * @element ANY
 * @priority 0
 *
 * @description
 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event.
 *
 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon
 * a dblclick. (The Event object is available as `$event`)
 *
 * @example
   <example name="ng-dblclick">
     <file name="index.html">
      <button ng-dblclick="count = count + 1" ng-init="count=0">
        Increment (on double click)
      </button>
      count: {{count}}
     </file>
   </example>
 */


/**
 * @ngdoc directive
 * @name ngMousedown
 * @restrict A
 * @element ANY
 * @priority 0
 *
 * @description
 * The ngMousedown directive allows you to specify custom behavior on mousedown event.
 *
 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon
 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`})
 *
 * @example
   <example name="ng-mousedown">
     <file name="index.html">
      <button ng-mousedown="count = count + 1" ng-init="count=0">
        Increment (on mouse down)
      </button>
      count: {{count}}
     </file>
   </example>
 */


/**
 * @ngdoc directive
 * @name ngMouseup
 * @restrict A
 * @element ANY
 * @priority 0
 *
 * @description
 * Specify custom behavior on mouseup event.
 *
 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon
 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`})
 *
 * @example
   <example name="ng-mouseup">
     <file name="index.html">
      <button ng-mouseup="count = count + 1" ng-init="count=0">
        Increment (on mouse up)
      </button>
      count: {{count}}
     </file>
   </example>
 */

/**
 * @ngdoc directive
 * @name ngMouseover
 * @restrict A
 * @element ANY
 * @priority 0
 *
 * @description
 * Specify custom behavior on mouseover event.
 *
 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon
 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`})
 *
 * @example
   <example name="ng-mouseover">
     <file name="index.html">
      <button ng-mouseover="count = count + 1" ng-init="count=0">
        Increment (when mouse is over)
      </button>
      count: {{count}}
     </file>
   </example>
 */


/**
 * @ngdoc directive
 * @name ngMouseenter
 * @restrict A
 * @element ANY
 * @priority 0
 *
 * @description
 * Specify custom behavior on mouseenter event.
 *
 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon
 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`})
 *
 * @example
   <example name="ng-mouseenter">
     <file name="index.html">
      <button ng-mouseenter="count = count + 1" ng-init="count=0">
        Increment (when mouse enters)
      </button>
      count: {{count}}
     </file>
   </example>
 */


/**
 * @ngdoc directive
 * @name ngMouseleave
 * @restrict A
 * @element ANY
 * @priority 0
 *
 * @description
 * Specify custom behavior on mouseleave event.
 *
 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon
 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`})
 *
 * @example
   <example name="ng-mouseleave">
     <file name="index.html">
      <button ng-mouseleave="count = count + 1" ng-init="count=0">
        Increment (when mouse leaves)
      </button>
      count: {{count}}
     </file>
   </example>
 */


/**
 * @ngdoc directive
 * @name ngMousemove
 * @restrict A
 * @element ANY
 * @priority 0
 *
 * @description
 * Specify custom behavior on mousemove event.
 *
 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon
 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`})
 *
 * @example
   <example name="ng-mousemove">
     <file name="index.html">
      <button ng-mousemove="count = count + 1" ng-init="count=0">
        Increment (when mouse moves)
      </button>
      count: {{count}}
     </file>
   </example>
 */


/**
 * @ngdoc directive
 * @name ngKeydown
 * @restrict A
 * @element ANY
 * @priority 0
 *
 * @description
 * Specify custom behavior on keydown event.
 *
 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon
 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
 *
 * @example
   <example name="ng-keydown">
     <file name="index.html">
      <input ng-keydown="count = count + 1" ng-init="count=0">
      key down count: {{count}}
     </file>
   </example>
 */


/**
 * @ngdoc directive
 * @name ngKeyup
 * @restrict A
 * @element ANY
 * @priority 0
 *
 * @description
 * Specify custom behavior on keyup event.
 *
 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon
 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
 *
 * @example
   <example name="ng-keyup">
     <file name="index.html">
       <p>Typing in the input box below updates the key count</p>
       <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}}

       <p>Typing in the input box below updates the keycode</p>
       <input ng-keyup="event=$event">
       <p>event keyCode: {{ event.keyCode }}</p>
       <p>event altKey: {{ event.altKey }}</p>
     </file>
   </example>
 */


/**
 * @ngdoc directive
 * @name ngKeypress
 * @restrict A
 * @element ANY
 *
 * @description
 * Specify custom behavior on keypress event.
 *
 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon
 * keypress. ({@link guide/expression#-event- Event object is available as `$event`}
 * and can be interrogated for keyCode, altKey, etc.)
 *
 * @example
   <example name="ng-keypress">
     <file name="index.html">
      <input ng-keypress="count = count + 1" ng-init="count=0">
      key press count: {{count}}
     </file>
   </example>
 */


/**
 * @ngdoc directive
 * @name ngSubmit
 * @restrict A
 * @element form
 * @priority 0
 *
 * @description
 * Enables binding AngularJS expressions to onsubmit events.
 *
 * Additionally it prevents the default action (which for form means sending the request to the
 * server and reloading the current page), but only if the form does not contain `action`,
 * `data-action`, or `x-action` attributes.
 *
 * <div class="alert alert-warning">
 * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and
 * `ngSubmit` handlers together. See the
 * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation}
 * for a detailed discussion of when `ngSubmit` may be triggered.
 * </div>
 *
 * @param {expression} ngSubmit {@link guide/expression Expression} to eval.
 * ({@link guide/expression#-event- Event object is available as `$event`})
 *
 * @example
   <example module="submitExample" name="ng-submit">
     <file name="index.html">
      <script>
        angular.module('submitExample', [])
          .controller('ExampleController', ['$scope', function($scope) {
            $scope.list = [];
            $scope.text = 'hello';
            $scope.submit = function() {
              if ($scope.text) {
                $scope.list.push(this.text);
                $scope.text = '';
              }
            };
          }]);
      </script>
      <form ng-submit="submit()" ng-controller="ExampleController">
        Enter text and hit enter:
        <input type="text" ng-model="text" name="text" />
        <input type="submit" id="submit" value="Submit" />
        <pre>list={{list}}</pre>
      </form>
     </file>
     <file name="protractor.js" type="protractor">
       it('should check ng-submit', function() {
         expect(element(by.binding('list')).getText()).toBe('list=[]');
         element(by.css('#submit')).click();
         expect(element(by.binding('list')).getText()).toContain('hello');
         expect(element(by.model('text')).getAttribute('value')).toBe('');
       });
       it('should ignore empty strings', function() {
         expect(element(by.binding('list')).getText()).toBe('list=[]');
         element(by.css('#submit')).click();
         element(by.css('#submit')).click();
         expect(element(by.binding('list')).getText()).toContain('hello');
        });
     </file>
   </example>
 */

/**
 * @ngdoc directive
 * @name ngFocus
 * @restrict A
 * @element window, input, select, textarea, a
 * @priority 0
 *
 * @description
 * Specify custom behavior on focus event.
 *
 * Note: As the `focus` event is executed synchronously when calling `input.focus()`
 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired
 * during an `$apply` to ensure a consistent state.
 *
 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon
 * focus. ({@link guide/expression#-event- Event object is available as `$event`})
 *
 * @example
 * See {@link ng.directive:ngClick ngClick}
 */

/**
 * @ngdoc directive
 * @name ngBlur
 * @restrict A
 * @element window, input, select, textarea, a
 * @priority 0
 *
 * @description
 * Specify custom behavior on blur event.
 *
 * A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when
 * an element has lost focus.
 *
 * Note: As the `blur` event is executed synchronously also during DOM manipulations
 * (e.g. removing a focussed input),
 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired
 * during an `$apply` to ensure a consistent state.
 *
 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon
 * blur. ({@link guide/expression#-event- Event object is available as `$event`})
 *
 * @example
 * See {@link ng.directive:ngClick ngClick}
 */

/**
 * @ngdoc directive
 * @name ngCopy
 * @restrict A
 * @element window, input, select, textarea, a
 * @priority 0
 *
 * @description
 * Specify custom behavior on copy event.
 *
 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon
 * copy. ({@link guide/expression#-event- Event object is available as `$event`})
 *
 * @example
   <example name="ng-copy">
     <file name="index.html">
      <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value">
      copied: {{copied}}
     </file>
   </example>
 */

/**
 * @ngdoc directive
 * @name ngCut
 * @restrict A
 * @element window, input, select, textarea, a
 * @priority 0
 *
 * @description
 * Specify custom behavior on cut event.
 *
 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon
 * cut. ({@link guide/expression#-event- Event object is available as `$event`})
 *
 * @example
   <example name="ng-cut">
     <file name="index.html">
      <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value">
      cut: {{cut}}
     </file>
   </example>
 */

/**
 * @ngdoc directive
 * @name ngPaste
 * @restrict A
 * @element window, input, select, textarea, a
 * @priority 0
 *
 * @description
 * Specify custom behavior on paste event.
 *
 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon
 * paste. ({@link guide/expression#-event- Event object is available as `$event`})
 *
 * @example
   <example name="ng-paste">
     <file name="index.html">
      <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'>
      pasted: {{paste}}
     </file>
   </example>
 */

/**
 * @ngdoc directive
 * @name ngIf
 * @restrict A
 * @multiElement
 *
 * @description
 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an
 * {expression}. If the expression assigned to `ngIf` evaluates to a false
 * value then the element is removed from the DOM, otherwise a clone of the
 * element is reinserted into the DOM.
 *
 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the
 * element in the DOM rather than changing its visibility via the `display` css property.  A common
 * case when this difference is significant is when using css selectors that rely on an element's
 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classes.
 *
 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope
 * is created when the element is restored.  The scope created within `ngIf` inherits from
 * its parent scope using
 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance).
 * An important implication of this is if `ngModel` is used within `ngIf` to bind to
 * a javascript primitive defined in the parent scope. In this case any modifications made to the
 * variable within the child scope will override (hide) the value in the parent scope.
 *
 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior
 * is if an element's class attribute is directly modified after it's compiled, using something like
 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element
 * the added class will be lost because the original compiled state is used to regenerate the element.
 *
 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter`
 * and `leave` effects.
 *
 * @animations
 * | Animation                        | Occurs                               |
 * |----------------------------------|-------------------------------------|
 * | {@link ng.$animate#enter enter}  | just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container |
 * | {@link ng.$animate#leave leave}  | just before the `ngIf` contents are removed from the DOM |
 *
 * @element ANY
 * @scope
 * @priority 600
 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then
 *     the element is removed from the DOM tree. If it is truthy a copy of the compiled
 *     element is added to the DOM tree.
 *
 * @example
  <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-if">
    <file name="index.html">
      <label>Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /></label><br/>
      Show when checked:
      <span ng-if="checked" class="animate-if">
        This is removed when the checkbox is unchecked.
      </span>
    </file>
    <file name="animations.css">
      .animate-if {
        background:white;
        border:1px solid black;
        padding:10px;
      }

      .animate-if.ng-enter, .animate-if.ng-leave {
        transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
      }

      .animate-if.ng-enter,
      .animate-if.ng-leave.ng-leave-active {
        opacity:0;
      }

      .animate-if.ng-leave,
      .animate-if.ng-enter.ng-enter-active {
        opacity:1;
      }
    </file>
  </example>
 */
var ngIfDirective = ['$animate', '$compile', function($animate, $compile) {
  return {
    multiElement: true,
    transclude: 'element',
    priority: 600,
    terminal: true,
    restrict: 'A',
    $$tlb: true,
    link: function($scope, $element, $attr, ctrl, $transclude) {
        var block, childScope, previousElements;
        $scope.$watch($attr.ngIf, function ngIfWatchAction(value) {

          if (value) {
            if (!childScope) {
              $transclude(function(clone, newScope) {
                childScope = newScope;
                clone[clone.length++] = $compile.$$createComment('end ngIf', $attr.ngIf);
                // Note: We only need the first/last node of the cloned nodes.
                // However, we need to keep the reference to the jqlite wrapper as it might be changed later
                // by a directive with templateUrl when its template arrives.
                block = {
                  clone: clone
                };
                $animate.enter(clone, $element.parent(), $element);
              });
            }
          } else {
            if (previousElements) {
              previousElements.remove();
              previousElements = null;
            }
            if (childScope) {
              childScope.$destroy();
              childScope = null;
            }
            if (block) {
              previousElements = getBlockNodes(block.clone);
              $animate.leave(previousElements).done(function(response) {
                if (response !== false) previousElements = null;
              });
              block = null;
            }
          }
        });
    }
  };
}];

/**
 * @ngdoc directive
 * @name ngInclude
 * @restrict ECA
 * @scope
 * @priority -400
 *
 * @description
 * Fetches, compiles and includes an external HTML fragment.
 *
 * By default, the template URL is restricted to the same domain and protocol as the
 * application document. This is done by calling {@link $sce#getTrustedResourceUrl
 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols
 * you may either add them to your {@link ng.$sceDelegateProvider#trustedResourceUrlList trusted
 * resource URL list} or {@link $sce#trustAsResourceUrl wrap them} as trusted values. Refer to
 * AngularJS's {@link ng.$sce Strict Contextual Escaping}.
 *
 * In addition, the browser's
 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/)
 * policy may further restrict whether the template is successfully loaded.
 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://`
 * access on some browsers.
 *
 * @animations
 * | Animation                        | Occurs                              |
 * |----------------------------------|-------------------------------------|
 * | {@link ng.$animate#enter enter}  | when the expression changes, on the new include |
 * | {@link ng.$animate#leave leave}  | when the expression changes, on the old include |
 *
 * The enter and leave animation occur concurrently.
 *
 * @param {string} ngInclude|src AngularJS expression evaluating to URL. If the source is a string constant,
 *                 make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`.
 * @param {string=} onload Expression to evaluate when a new partial is loaded.
 *                  <div class="alert alert-warning">
 *                  **Note:** When using onload on SVG elements in IE11, the browser will try to call
 *                  a function with the name on the window element, which will usually throw a
 *                  "function is undefined" error. To fix this, you can instead use `data-onload` or a
 *                  different form that {@link guide/directive#normalization matches} `onload`.
 *                  </div>
   *
 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll
 *                  $anchorScroll} to scroll the viewport after the content is loaded.
 *
 *                  - If the attribute is not set, disable scrolling.
 *                  - If the attribute is set without value, enable scrolling.
 *                  - Otherwise enable scrolling only if the expression evaluates to truthy value.
 *
 * @example
  <example module="includeExample" deps="angular-animate.js" animations="true" name="ng-include">
    <file name="index.html">
     <div ng-controller="ExampleController">
       <select ng-model="template" ng-options="t.name for t in templates">
        <option value="">(blank)</option>
       </select>
       url of the template: <code>{{template.url}}</code>
       <hr/>
       <div class="slide-animate-container">
         <div class="slide-animate" ng-include="template.url"></div>
       </div>
     </div>
    </file>
    <file name="script.js">
      angular.module('includeExample', ['ngAnimate'])
        .controller('ExampleController', ['$scope', function($scope) {
          $scope.templates =
            [{ name: 'template1.html', url: 'template1.html'},
             { name: 'template2.html', url: 'template2.html'}];
          $scope.template = $scope.templates[0];
        }]);
     </file>
    <file name="template1.html">
      Content of template1.html
    </file>
    <file name="template2.html">
      Content of template2.html
    </file>
    <file name="animations.css">
      .slide-animate-container {
        position:relative;
        background:white;
        border:1px solid black;
        height:40px;
        overflow:hidden;
      }

      .slide-animate {
        padding:10px;
      }

      .slide-animate.ng-enter, .slide-animate.ng-leave {
        transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;

        position:absolute;
        top:0;
        left:0;
        right:0;
        bottom:0;
        display:block;
        padding:10px;
      }

      .slide-animate.ng-enter {
        top:-50px;
      }
      .slide-animate.ng-enter.ng-enter-active {
        top:0;
      }

      .slide-animate.ng-leave {
        top:0;
      }
      .slide-animate.ng-leave.ng-leave-active {
        top:50px;
      }
    </file>
    <file name="protractor.js" type="protractor">
      var templateSelect = element(by.model('template'));
      var includeElem = element(by.css('[ng-include]'));

      it('should load template1.html', function() {
        expect(includeElem.getText()).toMatch(/Content of template1.html/);
      });

      it('should load template2.html', function() {
        if (browser.params.browser === 'firefox') {
          // Firefox can't handle using selects
          // See https://github.com/angular/protractor/issues/480
          return;
        }
        templateSelect.click();
        templateSelect.all(by.css('option')).get(2).click();
        expect(includeElem.getText()).toMatch(/Content of template2.html/);
      });

      it('should change to blank', function() {
        if (browser.params.browser === 'firefox') {
          // Firefox can't handle using selects
          return;
        }
        templateSelect.click();
        templateSelect.all(by.css('option')).get(0).click();
        expect(includeElem.isPresent()).toBe(false);
      });
    </file>
  </example>
 */


/**
 * @ngdoc event
 * @name ngInclude#$includeContentRequested
 * @eventType emit on the scope ngInclude was declared in
 * @description
 * Emitted every time the ngInclude content is requested.
 *
 * @param {Object} angularEvent Synthetic event object.
 * @param {String} src URL of content to load.
 */


/**
 * @ngdoc event
 * @name ngInclude#$includeContentLoaded
 * @eventType emit on the current ngInclude scope
 * @description
 * Emitted every time the ngInclude content is reloaded.
 *
 * @param {Object} angularEvent Synthetic event object.
 * @param {String} src URL of content to load.
 */


/**
 * @ngdoc event
 * @name ngInclude#$includeContentError
 * @eventType emit on the scope ngInclude was declared in
 * @description
 * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299)
 *
 * @param {Object} angularEvent Synthetic event object.
 * @param {String} src URL of content to load.
 */
var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate',
                  function($templateRequest,   $anchorScroll,   $animate) {
  return {
    restrict: 'ECA',
    priority: 400,
    terminal: true,
    transclude: 'element',
    controller: angular.noop,
    compile: function(element, attr) {
      var srcExp = attr.ngInclude || attr.src,
          onloadExp = attr.onload || '',
          autoScrollExp = attr.autoscroll;

      return function(scope, $element, $attr, ctrl, $transclude) {
        var changeCounter = 0,
            currentScope,
            previousElement,
            currentElement;

        var cleanupLastIncludeContent = function() {
          if (previousElement) {
            previousElement.remove();
            previousElement = null;
          }
          if (currentScope) {
            currentScope.$destroy();
            currentScope = null;
          }
          if (currentElement) {
            $animate.leave(currentElement).done(function(response) {
              if (response !== false) previousElement = null;
            });
            previousElement = currentElement;
            currentElement = null;
          }
        };

        scope.$watch(srcExp, function ngIncludeWatchAction(src) {
          var afterAnimation = function(response) {
            if (response !== false && isDefined(autoScrollExp) &&
              (!autoScrollExp || scope.$eval(autoScrollExp))) {
                $anchorScroll();
            }
          };
          var thisChangeId = ++changeCounter;

          if (src) {
            //set the 2nd param to true to ignore the template request error so that the inner
            //contents and scope can be cleaned up.
            $templateRequest(src, true).then(function(response) {
              if (scope.$$destroyed) return;

              if (thisChangeId !== changeCounter) return;
              var newScope = scope.$new();
              ctrl.template = response;

              // Note: This will also link all children of ng-include that were contained in the original
              // html. If that content contains controllers, ... they could pollute/change the scope.
              // However, using ng-include on an element with additional content does not make sense...
              // Note: We can't remove them in the cloneAttchFn of $transclude as that
              // function is called before linking the content, which would apply child
              // directives to non existing elements.
              var clone = $transclude(newScope, function(clone) {
                cleanupLastIncludeContent();
                $animate.enter(clone, null, $element).done(afterAnimation);
              });

              currentScope = newScope;
              currentElement = clone;

              currentScope.$emit('$includeContentLoaded', src);
              scope.$eval(onloadExp);
            }, function() {
              if (scope.$$destroyed) return;

              if (thisChangeId === changeCounter) {
                cleanupLastIncludeContent();
                scope.$emit('$includeContentError', src);
              }
            });
            scope.$emit('$includeContentRequested', src);
          } else {
            cleanupLastIncludeContent();
            ctrl.template = null;
          }
        });
      };
    }
  };
}];

// This directive is called during the $transclude call of the first `ngInclude` directive.
// It will replace and compile the content of the element with the loaded template.
// We need this directive so that the element content is already filled when
// the link function of another directive on the same element as ngInclude
// is called.
var ngIncludeFillContentDirective = ['$compile',
  function($compile) {
    return {
      restrict: 'ECA',
      priority: -400,
      require: 'ngInclude',
      link: function(scope, $element, $attr, ctrl) {
        if (toString.call($element[0]).match(/SVG/)) {
          // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not
          // support innerHTML, so detect this here and try to generate the contents
          // specially.
          $element.empty();
          $compile(jqLiteBuildFragment(ctrl.template, window.document).childNodes)(scope,
              function namespaceAdaptedClone(clone) {
            $element.append(clone);
          }, {futureParentElement: $element});
          return;
        }

        $element.html(ctrl.template);
        $compile($element.contents())(scope);
      }
    };
  }];

/**
 * @ngdoc directive
 * @name ngInit
 * @restrict AC
 * @priority 450
 * @element ANY
 *
 * @param {expression} ngInit {@link guide/expression Expression} to eval.
 *
 * @description
 * The `ngInit` directive allows you to evaluate an expression in the
 * current scope.
 *
 * <div class="alert alert-danger">
 * This directive can be abused to add unnecessary amounts of logic into your templates.
 * There are only a few appropriate uses of `ngInit`:
 * <ul>
 *   <li>aliasing special properties of {@link ng.directive:ngRepeat `ngRepeat`},
 *     as seen in the demo below.</li>
 *   <li>initializing data during development, or for examples, as seen throughout these docs.</li>
 *   <li>injecting data via server side scripting.</li>
 * </ul>
 *
 * Besides these few cases, you should use {@link guide/component Components} or
 * {@link guide/controller Controllers} rather than `ngInit` to initialize values on a scope.
 * </div>
 *
 * <div class="alert alert-warning">
 * **Note**: If you have assignment in `ngInit` along with a {@link ng.$filter `filter`}, make
 * sure you have parentheses to ensure correct operator precedence:
 * <pre class="prettyprint">
 * `<div ng-init="test1 = ($index | toString)"></div>`
 * </pre>
 * </div>
 *
 * @example
   <example module="initExample" name="ng-init">
     <file name="index.html">
   <script>
     angular.module('initExample', [])
       .controller('ExampleController', ['$scope', function($scope) {
         $scope.list = [['a', 'b'], ['c', 'd']];
       }]);
   </script>
   <div ng-controller="ExampleController">
     <div ng-repeat="innerList in list" ng-init="outerIndex = $index">
       <div ng-repeat="value in innerList" ng-init="innerIndex = $index">
          <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span>
       </div>
     </div>
   </div>
     </file>
     <file name="protractor.js" type="protractor">
       it('should alias index positions', function() {
         var elements = element.all(by.css('.example-init'));
         expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;');
         expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;');
         expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;');
         expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;');
       });
     </file>
   </example>
 */
var ngInitDirective = ngDirective({
  priority: 450,
  compile: function() {
    return {
      pre: function(scope, element, attrs) {
        scope.$eval(attrs.ngInit);
      }
    };
  }
});

/**
 * @ngdoc directive
 * @name ngList
 * @restrict A
 * @priority 100
 *
 * @param {string=} ngList optional delimiter that should be used to split the value.
 *
 * @description
 * Text input that converts between a delimited string and an array of strings. The default
 * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom
 * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`.
 *
 * The behaviour of the directive is affected by the use of the `ngTrim` attribute.
 * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each
 *   list item is respected. This implies that the user of the directive is responsible for
 *   dealing with whitespace but also allows you to use whitespace as a delimiter, such as a
 *   tab or newline character.
 * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected
 *   when joining the list items back together) and whitespace around each list item is stripped
 *   before it is added to the model.
 *
 * @example
 * ### Validation
 *
 * <example name="ngList-directive" module="listExample">
 *   <file name="app.js">
 *      angular.module('listExample', [])
 *        .controller('ExampleController', ['$scope', function($scope) {
 *          $scope.names = ['morpheus', 'neo', 'trinity'];
 *        }]);
 *   </file>
 *   <file name="index.html">
 *    <form name="myForm" ng-controller="ExampleController">
 *      <label>List: <input name="namesInput" ng-model="names" ng-list required></label>
 *      <span role="alert">
 *        <span class="error" ng-show="myForm.namesInput.$error.required">
 *        Required!</span>
 *      </span>
 *      <br>
 *      <tt>names = {{names}}</tt><br/>
 *      <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
 *      <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
 *      <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
 *      <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
 *     </form>
 *   </file>
 *   <file name="protractor.js" type="protractor">
 *     var listInput = element(by.model('names'));
 *     var names = element(by.exactBinding('names'));
 *     var valid = element(by.binding('myForm.namesInput.$valid'));
 *     var error = element(by.css('span.error'));
 *
 *     it('should initialize to model', function() {
 *       expect(names.getText()).toContain('["morpheus","neo","trinity"]');
 *       expect(valid.getText()).toContain('true');
 *       expect(error.getCssValue('display')).toBe('none');
 *     });
 *
 *     it('should be invalid if empty', function() {
 *       listInput.clear();
 *       listInput.sendKeys('');
 *
 *       expect(names.getText()).toContain('');
 *       expect(valid.getText()).toContain('false');
 *       expect(error.getCssValue('display')).not.toBe('none');
 *     });
 *   </file>
 * </example>
 *
 * @example
 * ### Splitting on newline
 *
 * <example name="ngList-directive-newlines">
 *   <file name="index.html">
 *    <textarea ng-model="list" ng-list="&#10;" ng-trim="false"></textarea>
 *    <pre>{{ list | json }}</pre>
 *   </file>
 *   <file name="protractor.js" type="protractor">
 *     it("should split the text by newlines", function() {
 *       var listInput = element(by.model('list'));
 *       var output = element(by.binding('list | json'));
 *       listInput.sendKeys('abc\ndef\nghi');
 *       expect(output.getText()).toContain('[\n  "abc",\n  "def",\n  "ghi"\n]');
 *     });
 *   </file>
 * </example>
 *
 */
var ngListDirective = function() {
  return {
    restrict: 'A',
    priority: 100,
    require: 'ngModel',
    link: function(scope, element, attr, ctrl) {
      var ngList = attr.ngList || ', ';
      var trimValues = attr.ngTrim !== 'false';
      var separator = trimValues ? trim(ngList) : ngList;

      var parse = function(viewValue) {
        // If the viewValue is invalid (say required but empty) it will be `undefined`
        if (isUndefined(viewValue)) return;

        var list = [];

        if (viewValue) {
          forEach(viewValue.split(separator), function(value) {
            if (value) list.push(trimValues ? trim(value) : value);
          });
        }

        return list;
      };

      ctrl.$parsers.push(parse);
      ctrl.$formatters.push(function(value) {
        if (isArray(value)) {
          return value.join(ngList);
        }

        return undefined;
      });

      // Override the standard $isEmpty because an empty array means the input is empty.
      ctrl.$isEmpty = function(value) {
        return !value || !value.length;
      };
    }
  };
};

/* global VALID_CLASS: true,
  INVALID_CLASS: true,
  PRISTINE_CLASS: true,
  DIRTY_CLASS: true,
  UNTOUCHED_CLASS: true,
  TOUCHED_CLASS: true,
  PENDING_CLASS: true,
  addSetValidityMethod: true,
  setupValidity: true,
  defaultModelOptions: false
*/


var VALID_CLASS = 'ng-valid',
    INVALID_CLASS = 'ng-invalid',
    PRISTINE_CLASS = 'ng-pristine',
    DIRTY_CLASS = 'ng-dirty',
    UNTOUCHED_CLASS = 'ng-untouched',
    TOUCHED_CLASS = 'ng-touched',
    EMPTY_CLASS = 'ng-empty',
    NOT_EMPTY_CLASS = 'ng-not-empty';

var ngModelMinErr = minErr('ngModel');

/**
 * @ngdoc type
 * @name ngModel.NgModelController
 * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a
 * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue
 * is set.
 *
 * @property {*} $modelValue The value in the model that the control is bound to.
 *
 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever
 *  the control updates the ngModelController with a new {@link ngModel.NgModelController#$viewValue
    `$viewValue`} from the DOM, usually via user input.
    See {@link ngModel.NgModelController#$setViewValue `$setViewValue()`} for a detailed lifecycle explanation.
    Note that the `$parsers` are not called when the bound ngModel expression changes programmatically.

  The functions are called in array order, each passing
    its return value through to the next. The last return value is forwarded to the
    {@link ngModel.NgModelController#$validators `$validators`} collection.

  Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue
    `$viewValue`}.

  Returning `undefined` from a parser means a parse error occurred. In that case,
    no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel`
    will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`}
    is set to `true`. The parse error is stored in `ngModel.$error.parse`.

  This simple example shows a parser that would convert text input value to lowercase:
 * ```js
 * function parse(value) {
 *   if (value) {
 *     return value.toLowerCase();
 *   }
 * }
 * ngModelController.$parsers.push(parse);
 * ```

 *
 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever
    the bound ngModel expression changes programmatically. The `$formatters` are not called when the
    value of the control is changed by user interaction.

  Formatters are used to format / convert the {@link ngModel.NgModelController#$modelValue
    `$modelValue`} for display in the control.

  The functions are called in reverse array order, each passing the value through to the
    next. The last return value is used as the actual DOM value.

  This simple example shows a formatter that would convert the model value to uppercase:

 * ```js
 * function format(value) {
 *   if (value) {
 *     return value.toUpperCase();
 *   }
 * }
 * ngModel.$formatters.push(format);
 * ```
 *
 * @property {Object.<string, function>} $validators A collection of validators that are applied
 *      whenever the model value changes. The key value within the object refers to the name of the
 *      validator while the function refers to the validation operation. The validation operation is
 *      provided with the model value as an argument and must return a true or false value depending
 *      on the response of that validation.
 *
 * ```js
 * ngModel.$validators.validCharacters = function(modelValue, viewValue) {
 *   var value = modelValue || viewValue;
 *   return /[0-9]+/.test(value) &&
 *          /[a-z]+/.test(value) &&
 *          /[A-Z]+/.test(value) &&
 *          /\W+/.test(value);
 * };
 * ```
 *
 * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to
 *      perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided
 *      is expected to return a promise when it is run during the model validation process. Once the promise
 *      is delivered then the validation status will be set to true when fulfilled and false when rejected.
 *      When the asynchronous validators are triggered, each of the validators will run in parallel and the model
 *      value will only be updated once all validators have been fulfilled. As long as an asynchronous validator
 *      is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators
 *      will only run once all synchronous validators have passed.
 *
 * Please note that if $http is used then it is important that the server returns a success HTTP response code
 * in order to fulfill the validation and a status level of `4xx` in order to reject the validation.
 *
 * ```js
 * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) {
 *   var value = modelValue || viewValue;
 *
 *   // Lookup user by username
 *   return $http.get('/api/users/' + value).
 *      then(function resolved() {
 *        //username exists, this means validation fails
 *        return $q.reject('exists');
 *      }, function rejected() {
 *        //username does not exist, therefore this validation passes
 *        return true;
 *      });
 * };
 * ```
 *
 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever
 *     a change to {@link ngModel.NgModelController#$viewValue `$viewValue`} has caused a change
 *     to {@link ngModel.NgModelController#$modelValue `$modelValue`}.
 *     It is called with no arguments, and its return value is ignored.
 *     This can be used in place of additional $watches against the model value.
 *
 * @property {Object} $error An object hash with all failing validator ids as keys.
 * @property {Object} $pending An object hash with all pending validator ids as keys.
 *
 * @property {boolean} $untouched True if control has not lost focus yet.
 * @property {boolean} $touched True if control has lost focus.
 * @property {boolean} $pristine True if user has not interacted with the control yet.
 * @property {boolean} $dirty True if user has already interacted with the control.
 * @property {boolean} $valid True if there is no error.
 * @property {boolean} $invalid True if at least one error on the control.
 * @property {string} $name The name attribute of the control.
 *
 * @description
 *
 * `NgModelController` provides API for the {@link ngModel `ngModel`} directive.
 * The controller contains services for data-binding, validation, CSS updates, and value formatting
 * and parsing. It purposefully does not contain any logic which deals with DOM rendering or
 * listening to DOM events.
 * Such DOM related logic should be provided by other directives which make use of
 * `NgModelController` for data-binding to control elements.
 * AngularJS provides this DOM logic for most {@link input `input`} elements.
 * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example
 * custom control example} that uses `ngModelController` to bind to `contenteditable` elements.
 *
 * @example
 * ### Custom Control Example
 * This example shows how to use `NgModelController` with a custom control to achieve
 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
 * collaborate together to achieve the desired result.
 *
 * `contenteditable` is an HTML5 attribute, which tells the browser to let the element
 * contents be edited in place by the user.
 *
 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize}
 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`).
 * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks
 * that content using the `$sce` service.
 *
 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js">
    <file name="style.css">
      [contenteditable] {
        border: 1px solid black;
        background-color: white;
        min-height: 20px;
      }

      .ng-invalid {
        border: 1px solid red;
      }

    </file>
    <file name="script.js">
      angular.module('customControl', ['ngSanitize']).
        directive('contenteditable', ['$sce', function($sce) {
          return {
            restrict: 'A', // only activate on element attribute
            require: '?ngModel', // get a hold of NgModelController
            link: function(scope, element, attrs, ngModel) {
              if (!ngModel) return; // do nothing if no ng-model

              // Specify how UI should be updated
              ngModel.$render = function() {
                element.html($sce.getTrustedHtml(ngModel.$viewValue || ''));
              };

              // Listen for change events to enable binding
              element.on('blur keyup change', function() {
                scope.$evalAsync(read);
              });
              read(); // initialize

              // Write data to the model
              function read() {
                var html = element.html();
                // When we clear the content editable the browser leaves a <br> behind
                // If strip-br attribute is provided then we strip this out
                if (attrs.stripBr && html === '<br>') {
                  html = '';
                }
                ngModel.$setViewValue(html);
              }
            }
          };
        }]);
    </file>
    <file name="index.html">
      <form name="myForm">
       <div contenteditable
            name="myWidget" ng-model="userContent"
            strip-br="true"
            required>Change me!</div>
        <span ng-show="myForm.myWidget.$error.required">Required!</span>
       <hr>
       <textarea ng-model="userContent" aria-label="Dynamic textarea"></textarea>
      </form>
    </file>
    <file name="protractor.js" type="protractor">
    it('should data-bind and become invalid', function() {
      if (browser.params.browser === 'safari' || browser.params.browser === 'firefox') {
        // SafariDriver can't handle contenteditable
        // and Firefox driver can't clear contenteditables very well
        return;
      }
      var contentEditable = element(by.css('[contenteditable]'));
      var content = 'Change me!';

      expect(contentEditable.getText()).toEqual(content);

      contentEditable.clear();
      contentEditable.sendKeys(protractor.Key.BACK_SPACE);
      expect(contentEditable.getText()).toEqual('');
      expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/);
    });
    </file>
 * </example>
 *
 *
 */
NgModelController.$inject = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$q', '$interpolate'];
function NgModelController($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $q, $interpolate) {
  this.$viewValue = Number.NaN;
  this.$modelValue = Number.NaN;
  this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity.
  this.$validators = {};
  this.$asyncValidators = {};
  this.$parsers = [];
  this.$formatters = [];
  this.$viewChangeListeners = [];
  this.$untouched = true;
  this.$touched = false;
  this.$pristine = true;
  this.$dirty = false;
  this.$valid = true;
  this.$invalid = false;
  this.$error = {}; // keep invalid keys here
  this.$$success = {}; // keep valid keys here
  this.$pending = undefined; // keep pending keys here
  this.$name = $interpolate($attr.name || '', false)($scope);
  this.$$parentForm = nullFormCtrl;
  this.$options = defaultModelOptions;
  this.$$updateEvents = '';
  // Attach the correct context to the event handler function for updateOn
  this.$$updateEventHandler = this.$$updateEventHandler.bind(this);

  this.$$parsedNgModel = $parse($attr.ngModel);
  this.$$parsedNgModelAssign = this.$$parsedNgModel.assign;
  this.$$ngModelGet = this.$$parsedNgModel;
  this.$$ngModelSet = this.$$parsedNgModelAssign;
  this.$$pendingDebounce = null;
  this.$$parserValid = undefined;
  this.$$parserName = 'parse';

  this.$$currentValidationRunId = 0;

  this.$$scope = $scope;
  this.$$rootScope = $scope.$root;
  this.$$attr = $attr;
  this.$$element = $element;
  this.$$animate = $animate;
  this.$$timeout = $timeout;
  this.$$parse = $parse;
  this.$$q = $q;
  this.$$exceptionHandler = $exceptionHandler;

  setupValidity(this);
  setupModelWatcher(this);
}

NgModelController.prototype = {
  $$initGetterSetters: function() {
    if (this.$options.getOption('getterSetter')) {
      var invokeModelGetter = this.$$parse(this.$$attr.ngModel + '()'),
          invokeModelSetter = this.$$parse(this.$$attr.ngModel + '($$$p)');

      this.$$ngModelGet = function($scope) {
        var modelValue = this.$$parsedNgModel($scope);
        if (isFunction(modelValue)) {
          modelValue = invokeModelGetter($scope);
        }
        return modelValue;
      };
      this.$$ngModelSet = function($scope, newValue) {
        if (isFunction(this.$$parsedNgModel($scope))) {
          invokeModelSetter($scope, {$$$p: newValue});
        } else {
          this.$$parsedNgModelAssign($scope, newValue);
        }
      };
    } else if (!this.$$parsedNgModel.assign) {
      throw ngModelMinErr('nonassign', 'Expression \'{0}\' is non-assignable. Element: {1}',
          this.$$attr.ngModel, startingTag(this.$$element));
    }
  },


  /**
   * @ngdoc method
   * @name ngModel.NgModelController#$render
   *
   * @description
   * Called when the view needs to be updated. It is expected that the user of the ng-model
   * directive will implement this method.
   *
   * The `$render()` method is invoked in the following situations:
   *
   * * `$rollbackViewValue()` is called.  If we are rolling back the view value to the last
   *   committed value then `$render()` is called to update the input control.
   * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and
   *   the `$viewValue` are different from last time.
   *
   * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of
   * `$modelValue` and `$viewValue` are actually different from their previous values. If `$modelValue`
   * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be
   * invoked if you only change a property on the objects.
   */
  $render: noop,

  /**
   * @ngdoc method
   * @name ngModel.NgModelController#$isEmpty
   *
   * @description
   * This is called when we need to determine if the value of an input is empty.
   *
   * For instance, the required directive does this to work out if the input has data or not.
   *
   * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`.
   *
   * You can override this for input directives whose concept of being empty is different from the
   * default. The `checkboxInputType` directive does this because in its case a value of `false`
   * implies empty.
   *
   * @param {*} value The value of the input to check for emptiness.
   * @returns {boolean} True if `value` is "empty".
   */
  $isEmpty: function(value) {
    // eslint-disable-next-line no-self-compare
    return isUndefined(value) || value === '' || value === null || value !== value;
  },

  $$updateEmptyClasses: function(value) {
    if (this.$isEmpty(value)) {
      this.$$animate.removeClass(this.$$element, NOT_EMPTY_CLASS);
      this.$$animate.addClass(this.$$element, EMPTY_CLASS);
    } else {
      this.$$animate.removeClass(this.$$element, EMPTY_CLASS);
      this.$$animate.addClass(this.$$element, NOT_EMPTY_CLASS);
    }
  },

  /**
   * @ngdoc method
   * @name ngModel.NgModelController#$setPristine
   *
   * @description
   * Sets the control to its pristine state.
   *
   * This method can be called to remove the `ng-dirty` class and set the control to its pristine
   * state (`ng-pristine` class). A model is considered to be pristine when the control
   * has not been changed from when first compiled.
   */
  $setPristine: function() {
    this.$dirty = false;
    this.$pristine = true;
    this.$$animate.removeClass(this.$$element, DIRTY_CLASS);
    this.$$animate.addClass(this.$$element, PRISTINE_CLASS);
  },

  /**
   * @ngdoc method
   * @name ngModel.NgModelController#$setDirty
   *
   * @description
   * Sets the control to its dirty state.
   *
   * This method can be called to remove the `ng-pristine` class and set the control to its dirty
   * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed
   * from when first compiled.
   */
  $setDirty: function() {
    this.$dirty = true;
    this.$pristine = false;
    this.$$animate.removeClass(this.$$element, PRISTINE_CLASS);
    this.$$animate.addClass(this.$$element, DIRTY_CLASS);
    this.$$parentForm.$setDirty();
  },

  /**
   * @ngdoc method
   * @name ngModel.NgModelController#$setUntouched
   *
   * @description
   * Sets the control to its untouched state.
   *
   * This method can be called to remove the `ng-touched` class and set the control to its
   * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched
   * by default, however this function can be used to restore that state if the model has
   * already been touched by the user.
   */
  $setUntouched: function() {
    this.$touched = false;
    this.$untouched = true;
    this.$$animate.setClass(this.$$element, UNTOUCHED_CLASS, TOUCHED_CLASS);
  },

  /**
   * @ngdoc method
   * @name ngModel.NgModelController#$setTouched
   *
   * @description
   * Sets the control to its touched state.
   *
   * This method can be called to remove the `ng-untouched` class and set the control to its
   * touched state (`ng-touched` class). A model is considered to be touched when the user has
   * first focused the control element and then shifted focus away from the control (blur event).
   */
  $setTouched: function() {
    this.$touched = true;
    this.$untouched = false;
    this.$$animate.setClass(this.$$element, TOUCHED_CLASS, UNTOUCHED_CLASS);
  },

  /**
   * @ngdoc method
   * @name ngModel.NgModelController#$rollbackViewValue
   *
   * @description
   * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`,
   * which may be caused by a pending debounced event or because the input is waiting for some
   * future event.
   *
   * If you have an input that uses `ng-model-options` to set up debounced updates or updates that
   * depend on special events such as `blur`, there can be a period when the `$viewValue` is out of
   * sync with the ngModel's `$modelValue`.
   *
   * In this case, you can use `$rollbackViewValue()` to manually cancel the debounced / future update
   * and reset the input to the last committed view value.
   *
   * It is also possible that you run into difficulties if you try to update the ngModel's `$modelValue`
   * programmatically before these debounced/future events have resolved/occurred, because AngularJS's
   * dirty checking mechanism is not able to tell whether the model has actually changed or not.
   *
   * The `$rollbackViewValue()` method should be called before programmatically changing the model of an
   * input which may have such events pending. This is important in order to make sure that the
   * input field will be updated with the new model value and any pending operations are cancelled.
   *
   * @example
   * <example name="ng-model-cancel-update" module="cancel-update-example">
   *   <file name="app.js">
   *     angular.module('cancel-update-example', [])
   *
   *     .controller('CancelUpdateController', ['$scope', function($scope) {
   *       $scope.model = {value1: '', value2: ''};
   *
   *       $scope.setEmpty = function(e, value, rollback) {
   *         if (e.keyCode === 27) {
   *           e.preventDefault();
   *           if (rollback) {
   *             $scope.myForm[value].$rollbackViewValue();
   *           }
   *           $scope.model[value] = '';
   *         }
   *       };
   *     }]);
   *   </file>
   *   <file name="index.html">
   *     <div ng-controller="CancelUpdateController">
   *       <p>Both of these inputs are only updated if they are blurred. Hitting escape should
   *       empty them. Follow these steps and observe the difference:</p>
   *       <ol>
   *         <li>Type something in the input. You will see that the model is not yet updated</li>
   *         <li>Press the Escape key.
   *           <ol>
   *             <li> In the first example, nothing happens, because the model is already '', and no
   *             update is detected. If you blur the input, the model will be set to the current view.
   *             </li>
   *             <li> In the second example, the pending update is cancelled, and the input is set back
   *             to the last committed view value (''). Blurring the input does nothing.
   *             </li>
   *           </ol>
   *         </li>
   *       </ol>
   *
   *       <form name="myForm" ng-model-options="{ updateOn: 'blur' }">
   *         <div>
   *           <p id="inputDescription1">Without $rollbackViewValue():</p>
   *           <input name="value1" aria-describedby="inputDescription1" ng-model="model.value1"
   *                  ng-keydown="setEmpty($event, 'value1')">
   *           value1: "{{ model.value1 }}"
   *         </div>
   *
   *         <div>
   *           <p id="inputDescription2">With $rollbackViewValue():</p>
   *           <input name="value2" aria-describedby="inputDescription2" ng-model="model.value2"
   *                  ng-keydown="setEmpty($event, 'value2', true)">
   *           value2: "{{ model.value2 }}"
   *         </div>
   *       </form>
   *     </div>
   *   </file>
       <file name="style.css">
          div {
            display: table-cell;
          }
          div:nth-child(1) {
            padding-right: 30px;
          }

        </file>
   * </example>
   */
  $rollbackViewValue: function() {
    this.$$timeout.cancel(this.$$pendingDebounce);
    this.$viewValue = this.$$lastCommittedViewValue;
    this.$render();
  },

  /**
   * @ngdoc method
   * @name ngModel.NgModelController#$validate
   *
   * @description
   * Runs each of the registered validators (first synchronous validators and then
   * asynchronous validators).
   * If the validity changes to invalid, the model will be set to `undefined`,
   * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`.
   * If the validity changes to valid, it will set the model to the last available valid
   * `$modelValue`, i.e. either the last parsed value or the last value set from the scope.
   */
  $validate: function() {

    // ignore $validate before model is initialized
    if (isNumberNaN(this.$modelValue)) {
      return;
    }

    var viewValue = this.$$lastCommittedViewValue;
    // Note: we use the $$rawModelValue as $modelValue might have been
    // set to undefined during a view -> model update that found validation
    // errors. We can't parse the view here, since that could change
    // the model although neither viewValue nor the model on the scope changed
    var modelValue = this.$$rawModelValue;

    var prevValid = this.$valid;
    var prevModelValue = this.$modelValue;

    var allowInvalid = this.$options.getOption('allowInvalid');

    var that = this;
    this.$$runValidators(modelValue, viewValue, function(allValid) {
      // If there was no change in validity, don't update the model
      // This prevents changing an invalid modelValue to undefined
      if (!allowInvalid && prevValid !== allValid) {
        // Note: Don't check this.$valid here, as we could have
        // external validators (e.g. calculated on the server),
        // that just call $setValidity and need the model value
        // to calculate their validity.
        that.$modelValue = allValid ? modelValue : undefined;

        if (that.$modelValue !== prevModelValue) {
          that.$$writeModelToScope();
        }
      }
    });
  },

  $$runValidators: function(modelValue, viewValue, doneCallback) {
    this.$$currentValidationRunId++;
    var localValidationRunId = this.$$currentValidationRunId;
    var that = this;

    // check parser error
    if (!processParseErrors()) {
      validationDone(false);
      return;
    }
    if (!processSyncValidators()) {
      validationDone(false);
      return;
    }
    processAsyncValidators();

    function processParseErrors() {
      var errorKey = that.$$parserName;

      if (isUndefined(that.$$parserValid)) {
        setValidity(errorKey, null);
      } else {
        if (!that.$$parserValid) {
          forEach(that.$validators, function(v, name) {
            setValidity(name, null);
          });
          forEach(that.$asyncValidators, function(v, name) {
            setValidity(name, null);
          });
        }

        // Set the parse error last, to prevent unsetting it, should a $validators key == parserName
        setValidity(errorKey, that.$$parserValid);
        return that.$$parserValid;
      }
      return true;
    }

    function processSyncValidators() {
      var syncValidatorsValid = true;
      forEach(that.$validators, function(validator, name) {
        var result = Boolean(validator(modelValue, viewValue));
        syncValidatorsValid = syncValidatorsValid && result;
        setValidity(name, result);
      });
      if (!syncValidatorsValid) {
        forEach(that.$asyncValidators, function(v, name) {
          setValidity(name, null);
        });
        return false;
      }
      return true;
    }

    function processAsyncValidators() {
      var validatorPromises = [];
      var allValid = true;
      forEach(that.$asyncValidators, function(validator, name) {
        var promise = validator(modelValue, viewValue);
        if (!isPromiseLike(promise)) {
          throw ngModelMinErr('nopromise',
            'Expected asynchronous validator to return a promise but got \'{0}\' instead.', promise);
        }
        setValidity(name, undefined);
        validatorPromises.push(promise.then(function() {
          setValidity(name, true);
        }, function() {
          allValid = false;
          setValidity(name, false);
        }));
      });
      if (!validatorPromises.length) {
        validationDone(true);
      } else {
        that.$$q.all(validatorPromises).then(function() {
          validationDone(allValid);
        }, noop);
      }
    }

    function setValidity(name, isValid) {
      if (localValidationRunId === that.$$currentValidationRunId) {
        that.$setValidity(name, isValid);
      }
    }

    function validationDone(allValid) {
      if (localValidationRunId === that.$$currentValidationRunId) {

        doneCallback(allValid);
      }
    }
  },

  /**
   * @ngdoc method
   * @name ngModel.NgModelController#$commitViewValue
   *
   * @description
   * Commit a pending update to the `$modelValue`.
   *
   * Updates may be pending by a debounced event or because the input is waiting for a some future
   * event defined in `ng-model-options`. this method is rarely needed as `NgModelController`
   * usually handles calling this in response to input events.
   */
  $commitViewValue: function() {
    var viewValue = this.$viewValue;

    this.$$timeout.cancel(this.$$pendingDebounce);

    // If the view value has not changed then we should just exit, except in the case where there is
    // a native validator on the element. In this case the validation state may have changed even though
    // the viewValue has stayed empty.
    if (this.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !this.$$hasNativeValidators)) {
      return;
    }
    this.$$updateEmptyClasses(viewValue);
    this.$$lastCommittedViewValue = viewValue;

    // change to dirty
    if (this.$pristine) {
      this.$setDirty();
    }
    this.$$parseAndValidate();
  },

  $$parseAndValidate: function() {
    var viewValue = this.$$lastCommittedViewValue;
    var modelValue = viewValue;
    var that = this;

    this.$$parserValid = isUndefined(modelValue) ? undefined : true;

    // Reset any previous parse error
    this.$setValidity(this.$$parserName, null);
    this.$$parserName = 'parse';

    if (this.$$parserValid) {
      for (var i = 0; i < this.$parsers.length; i++) {
        modelValue = this.$parsers[i](modelValue);
        if (isUndefined(modelValue)) {
          this.$$parserValid = false;
          break;
        }
      }
    }
    if (isNumberNaN(this.$modelValue)) {
      // this.$modelValue has not been touched yet...
      this.$modelValue = this.$$ngModelGet(this.$$scope);
    }
    var prevModelValue = this.$modelValue;
    var allowInvalid = this.$options.getOption('allowInvalid');
    this.$$rawModelValue = modelValue;

    if (allowInvalid) {
      this.$modelValue = modelValue;
      writeToModelIfNeeded();
    }

    // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date.
    // This can happen if e.g. $setViewValue is called from inside a parser
    this.$$runValidators(modelValue, this.$$lastCommittedViewValue, function(allValid) {
      if (!allowInvalid) {
        // Note: Don't check this.$valid here, as we could have
        // external validators (e.g. calculated on the server),
        // that just call $setValidity and need the model value
        // to calculate their validity.
        that.$modelValue = allValid ? modelValue : undefined;
        writeToModelIfNeeded();
      }
    });

    function writeToModelIfNeeded() {
      if (that.$modelValue !== prevModelValue) {
        that.$$writeModelToScope();
      }
    }
  },

  $$writeModelToScope: function() {
    this.$$ngModelSet(this.$$scope, this.$modelValue);
    forEach(this.$viewChangeListeners, function(listener) {
      try {
        listener();
      } catch (e) {
        // eslint-disable-next-line no-invalid-this
        this.$$exceptionHandler(e);
      }
    }, this);
  },

  /**
   * @ngdoc method
   * @name ngModel.NgModelController#$setViewValue
   *
   * @description
   * Update the view value.
   *
   * This method should be called when a control wants to change the view value; typically,
   * this is done from within a DOM event handler. For example, the {@link ng.directive:input input}
   * directive calls it when the value of the input changes and {@link ng.directive:select select}
   * calls it when an option is selected.
   *
   * When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers`
   * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged
   * value is sent directly for processing through the `$parsers` pipeline. After this, the `$validators` and
   * `$asyncValidators` are called and the value is applied to `$modelValue`.
   * Finally, the value is set to the **expression** specified in the `ng-model` attribute and
   * all the registered change listeners, in the `$viewChangeListeners` list are called.
   *
   * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn`
   * and the `default` trigger is not listed, all those actions will remain pending until one of the
   * `updateOn` events is triggered on the DOM element.
   * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions}
   * directive is used with a custom debounce for this particular event.
   * Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce`
   * is specified, once the timer runs out.
   *
   * When used with standard inputs, the view value will always be a string (which is in some cases
   * parsed into another type, such as a `Date` object for `input[date]`.)
   * However, custom controls might also pass objects to this method. In this case, we should make
   * a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not
   * perform a deep watch of objects, it only looks for a change of identity. If you only change
   * the property of the object then ngModel will not realize that the object has changed and
   * will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should
   * not change properties of the copy once it has been passed to `$setViewValue`.
   * Otherwise you may cause the model value on the scope to change incorrectly.
   *
   * <div class="alert alert-info">
   * In any case, the value passed to the method should always reflect the current value
   * of the control. For example, if you are calling `$setViewValue` for an input element,
   * you should pass the input DOM value. Otherwise, the control and the scope model become
   * out of sync. It's also important to note that `$setViewValue` does not call `$render` or change
   * the control's DOM value in any way. If we want to change the control's DOM value
   * programmatically, we should update the `ngModel` scope expression. Its new value will be
   * picked up by the model controller, which will run it through the `$formatters`, `$render` it
   * to update the DOM, and finally call `$validate` on it.
   * </div>
   *
   * @param {*} value value from the view.
   * @param {string} trigger Event that triggered the update.
   */
  $setViewValue: function(value, trigger) {
    this.$viewValue = value;
    if (this.$options.getOption('updateOnDefault')) {
      this.$$debounceViewValueCommit(trigger);
    }
  },

  $$debounceViewValueCommit: function(trigger) {
    var debounceDelay = this.$options.getOption('debounce');

    if (isNumber(debounceDelay[trigger])) {
      debounceDelay = debounceDelay[trigger];
    } else if (isNumber(debounceDelay['default']) &&
      this.$options.getOption('updateOn').indexOf(trigger) === -1
    ) {
      debounceDelay = debounceDelay['default'];
    } else if (isNumber(debounceDelay['*'])) {
      debounceDelay = debounceDelay['*'];
    }

    this.$$timeout.cancel(this.$$pendingDebounce);
    var that = this;
    if (debounceDelay > 0) { // this fails if debounceDelay is an object
      this.$$pendingDebounce = this.$$timeout(function() {
        that.$commitViewValue();
      }, debounceDelay);
    } else if (this.$$rootScope.$$phase) {
      this.$commitViewValue();
    } else {
      this.$$scope.$apply(function() {
        that.$commitViewValue();
      });
    }
  },

  /**
   * @ngdoc method
   *
   * @name ngModel.NgModelController#$overrideModelOptions
   *
   * @description
   *
   * Override the current model options settings programmatically.
   *
   * The previous `ModelOptions` value will not be modified. Instead, a
   * new `ModelOptions` object will inherit from the previous one overriding
   * or inheriting settings that are defined in the given parameter.
   *
   * See {@link ngModelOptions} for information about what options can be specified
   * and how model option inheritance works.
   *
   * <div class="alert alert-warning">
   * **Note:** this function only affects the options set on the `ngModelController`,
   * and not the options on the {@link ngModelOptions} directive from which they might have been
   * obtained initially.
   * </div>
   *
   * <div class="alert alert-danger">
   * **Note:** it is not possible to override the `getterSetter` option.
   * </div>
   *
   * @param {Object} options a hash of settings to override the previous options
   *
   */
  $overrideModelOptions: function(options) {
    this.$options = this.$options.createChild(options);
    this.$$setUpdateOnEvents();
  },

  /**
   * @ngdoc method
   *
   * @name  ngModel.NgModelController#$processModelValue

   * @description
   *
   * Runs the model -> view pipeline on the current
   * {@link ngModel.NgModelController#$modelValue $modelValue}.
   *
   * The following actions are performed by this method:
   *
   * - the `$modelValue` is run through the {@link ngModel.NgModelController#$formatters $formatters}
   * and the result is set to the {@link ngModel.NgModelController#$viewValue $viewValue}
   * - the `ng-empty` or `ng-not-empty` class is set on the element
   * - if the `$viewValue` has changed:
   *   - {@link ngModel.NgModelController#$render $render} is called on the control
   *   - the {@link ngModel.NgModelController#$validators $validators} are run and
   *   the validation status is set.
   *
   * This method is called by ngModel internally when the bound scope value changes.
   * Application developers usually do not have to call this function themselves.
   *
   * This function can be used when the `$viewValue` or the rendered DOM value are not correctly
   * formatted and the `$modelValue` must be run through the `$formatters` again.
   *
   * @example
   * Consider a text input with an autocomplete list (for fruit), where the items are
   * objects with a name and an id.
   * A user enters `ap` and then selects `Apricot` from the list.
   * Based on this, the autocomplete widget will call `$setViewValue({name: 'Apricot', id: 443})`,
   * but the rendered value will still be `ap`.
   * The widget can then call `ctrl.$processModelValue()` to run the model -> view
   * pipeline again, which formats the object to the string `Apricot`,
   * then updates the `$viewValue`, and finally renders it in the DOM.
   *
   * <example module="inputExample" name="ng-model-process">
     <file name="index.html">
      <div ng-controller="inputController" style="display: flex;">
        <div style="margin-right: 30px;">
          Search Fruit:
          <basic-autocomplete items="items" on-select="selectedFruit = item"></basic-autocomplete>
        </div>
        <div>
          Model:<br>
          <pre>{{selectedFruit | json}}</pre>
        </div>
      </div>
     </file>
     <file name="app.js">
      angular.module('inputExample', [])
        .controller('inputController', function($scope) {
          $scope.items = [
            {name: 'Apricot', id: 443},
            {name: 'Clementine', id: 972},
            {name: 'Durian', id: 169},
            {name: 'Jackfruit', id: 982},
            {name: 'Strawberry', id: 863}
          ];
        })
        .component('basicAutocomplete', {
          bindings: {
            items: '<',
            onSelect: '&'
          },
          templateUrl: 'autocomplete.html',
          controller: function($element, $scope) {
            var that = this;
            var ngModel;

            that.$postLink = function() {
              ngModel = $element.find('input').controller('ngModel');

              ngModel.$formatters.push(function(value) {
                return (value && value.name) || value;
              });

              ngModel.$parsers.push(function(value) {
                var match = value;
                for (var i = 0; i < that.items.length; i++) {
                  if (that.items[i].name === value) {
                    match = that.items[i];
                    break;
                  }
                }

                return match;
              });
            };

            that.selectItem = function(item) {
              ngModel.$setViewValue(item);
              ngModel.$processModelValue();
              that.onSelect({item: item});
            };
          }
        });
     </file>
     <file name="autocomplete.html">
       <div>
         <input type="search" ng-model="$ctrl.searchTerm" />
         <ul>
           <li ng-repeat="item in $ctrl.items | filter:$ctrl.searchTerm">
             <button ng-click="$ctrl.selectItem(item)">{{ item.name }}</button>
           </li>
         </ul>
       </div>
     </file>
   * </example>
   *
   */
  $processModelValue: function() {
    var viewValue = this.$$format();

    if (this.$viewValue !== viewValue) {
      this.$$updateEmptyClasses(viewValue);
      this.$viewValue = this.$$lastCommittedViewValue = viewValue;
      this.$render();
      // It is possible that model and view value have been updated during render
      this.$$runValidators(this.$modelValue, this.$viewValue, noop);
    }
  },

  /**
   * This method is called internally to run the $formatters on the $modelValue
   */
  $$format: function() {
    var formatters = this.$formatters,
        idx = formatters.length;

    var viewValue = this.$modelValue;
    while (idx--) {
      viewValue = formatters[idx](viewValue);
    }

    return viewValue;
  },

  /**
   * This method is called internally when the bound scope value changes.
   */
  $$setModelValue: function(modelValue) {
    this.$modelValue = this.$$rawModelValue = modelValue;
    this.$$parserValid = undefined;
    this.$processModelValue();
  },

  $$setUpdateOnEvents: function() {
    if (this.$$updateEvents) {
      this.$$element.off(this.$$updateEvents, this.$$updateEventHandler);
    }

    this.$$updateEvents = this.$options.getOption('updateOn');
    if (this.$$updateEvents) {
      this.$$element.on(this.$$updateEvents, this.$$updateEventHandler);
    }
  },

  $$updateEventHandler: function(ev) {
    this.$$debounceViewValueCommit(ev && ev.type);
  }
};

function setupModelWatcher(ctrl) {
  // model -> value
  // Note: we cannot use a normal scope.$watch as we want to detect the following:
  // 1. scope value is 'a'
  // 2. user enters 'b'
  // 3. ng-change kicks in and reverts scope value to 'a'
  //    -> scope value did not change since the last digest as
  //       ng-change executes in apply phase
  // 4. view should be changed back to 'a'
  ctrl.$$scope.$watch(function ngModelWatch(scope) {
    var modelValue = ctrl.$$ngModelGet(scope);

    // if scope model value and ngModel value are out of sync
    // This cannot be moved to the action function, because it would not catch the
    // case where the model is changed in the ngChange function or the model setter
    if (modelValue !== ctrl.$modelValue &&
      // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator
      // eslint-disable-next-line no-self-compare
      (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue)
    ) {
      ctrl.$$setModelValue(modelValue);
    }

    return modelValue;
  });
}

/**
 * @ngdoc method
 * @name ngModel.NgModelController#$setValidity
 *
 * @description
 * Change the validity state, and notify the form.
 *
 * This method can be called within $parsers/$formatters or a custom validation implementation.
 * However, in most cases it should be sufficient to use the `ngModel.$validators` and
 * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically.
 *
 * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned
 *        to either `$error[validationErrorKey]` or `$pending[validationErrorKey]`
 *        (for unfulfilled `$asyncValidators`), so that it is available for data-binding.
 *        The `validationErrorKey` should be in camelCase and will get converted into dash-case
 *        for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
 *        classes and can be bound to as `{{ someForm.someControl.$error.myError }}`.
 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined),
 *                          or skipped (null). Pending is used for unfulfilled `$asyncValidators`.
 *                          Skipped is used by AngularJS when validators do not run because of parse errors and
 *                          when `$asyncValidators` do not run because any of the `$validators` failed.
 */
addSetValidityMethod({
  clazz: NgModelController,
  set: function(object, property) {
    object[property] = true;
  },
  unset: function(object, property) {
    delete object[property];
  }
});


/**
 * @ngdoc directive
 * @name ngModel
 * @restrict A
 * @priority 1
 * @param {expression} ngModel assignable {@link guide/expression Expression} to bind to.
 *
 * @description
 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a
 * property on the scope using {@link ngModel.NgModelController NgModelController},
 * which is created and exposed by this directive.
 *
 * `ngModel` is responsible for:
 *
 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select`
 *   require.
 * - Providing validation behavior (i.e. required, number, email, url).
 * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors).
 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`,
 *   `ng-untouched`, `ng-empty`, `ng-not-empty`) including animations.
 * - Registering the control with its parent {@link ng.directive:form form}.
 *
 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the
 * current scope. If the property doesn't already exist on this scope, it will be created
 * implicitly and added to the scope.
 *
 * For best practices on using `ngModel`, see:
 *
 *  - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes)
 *
 * For basic examples, how to use `ngModel`, see:
 *
 *  - {@link ng.directive:input input}
 *    - {@link input[text] text}
 *    - {@link input[checkbox] checkbox}
 *    - {@link input[radio] radio}
 *    - {@link input[number] number}
 *    - {@link input[email] email}
 *    - {@link input[url] url}
 *    - {@link input[date] date}
 *    - {@link input[datetime-local] datetime-local}
 *    - {@link input[time] time}
 *    - {@link input[month] month}
 *    - {@link input[week] week}
 *  - {@link ng.directive:select select}
 *  - {@link ng.directive:textarea textarea}
 *
 * ## Complex Models (objects or collections)
 *
 * By default, `ngModel` watches the model by reference, not value. This is important to know when
 * binding inputs to models that are objects (e.g. `Date`) or collections (e.g. arrays). If only properties of the
 * object or collection change, `ngModel` will not be notified and so the input will not be  re-rendered.
 *
 * The model must be assigned an entirely new object or collection before a re-rendering will occur.
 *
 * Some directives have options that will cause them to use a custom `$watchCollection` on the model expression
 * - for example, `ngOptions` will do so when a `track by` clause is included in the comprehension expression or
 * if the select is given the `multiple` attribute.
 *
 * The `$watchCollection()` method only does a shallow comparison, meaning that changing properties deeper than the
 * first level of the object (or only changing the properties of an item in the collection if it's an array) will still
 * not trigger a re-rendering of the model.
 *
 * ## CSS classes
 * The following CSS classes are added and removed on the associated input/select/textarea element
 * depending on the validity of the model.
 *
 *  - `ng-valid`: the model is valid
 *  - `ng-invalid`: the model is invalid
 *  - `ng-valid-[key]`: for each valid key added by `$setValidity`
 *  - `ng-invalid-[key]`: for each invalid key added by `$setValidity`
 *  - `ng-pristine`: the control hasn't been interacted with yet
 *  - `ng-dirty`: the control has been interacted with
 *  - `ng-touched`: the control has been blurred
 *  - `ng-untouched`: the control hasn't been blurred
 *  - `ng-pending`: any `$asyncValidators` are unfulfilled
 *  - `ng-empty`: the view does not contain a value or the value is deemed "empty", as defined
 *     by the {@link ngModel.NgModelController#$isEmpty} method
 *  - `ng-not-empty`: the view contains a non-empty value
 *
 * Keep in mind that ngAnimate can detect each of these classes when added and removed.
 *
 * @animations
 * Animations within models are triggered when any of the associated CSS classes are added and removed
 * on the input element which is attached to the model. These classes include: `.ng-pristine`, `.ng-dirty`,
 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself.
 * The animations that are triggered within ngModel are similar to how they work in ngClass and
 * animations can be hooked into using CSS transitions, keyframes as well as JS animations.
 *
 * The following example shows a simple way to utilize CSS transitions to style an input element
 * that has been rendered as invalid after it has been validated:
 *
 * <pre>
 * //be sure to include ngAnimate as a module to hook into more
 * //advanced animations
 * .my-input {
 *   transition:0.5s linear all;
 *   background: white;
 * }
 * .my-input.ng-invalid {
 *   background: red;
 *   color:white;
 * }
 * </pre>
 *
 * @example
 * ### Basic Usage
 * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample" name="ng-model">
     <file name="index.html">
       <script>
        angular.module('inputExample', [])
          .controller('ExampleController', ['$scope', function($scope) {
            $scope.val = '1';
          }]);
       </script>
       <style>
         .my-input {
           transition:all linear 0.5s;
           background: transparent;
         }
         .my-input.ng-invalid {
           color:white;
           background: red;
         }
       </style>
       <p id="inputDescription">
        Update input to see transitions when valid/invalid.
        Integer is a valid value.
       </p>
       <form name="testForm" ng-controller="ExampleController">
         <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input"
                aria-describedby="inputDescription" />
       </form>
     </file>
 * </example>
 *
 * @example
 * ### Binding to a getter/setter
 *
 * Sometimes it's helpful to bind `ngModel` to a getter/setter function.  A getter/setter is a
 * function that returns a representation of the model when called with zero arguments, and sets
 * the internal state of a model when called with an argument. It's sometimes useful to use this
 * for models that have an internal representation that's different from what the model exposes
 * to the view.
 *
 * <div class="alert alert-success">
 * **Best Practice:** It's best to keep getters fast because AngularJS is likely to call them more
 * frequently than other parts of your code.
 * </div>
 *
 * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that
 * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to
 * a `<form>`, which will enable this behavior for all `<input>`s within it. See
 * {@link ng.directive:ngModelOptions `ngModelOptions`} for more.
 *
 * The following example shows how to use `ngModel` with a getter/setter:
 *
 * @example
 * <example name="ngModel-getter-setter" module="getterSetterExample">
     <file name="index.html">
       <div ng-controller="ExampleController">
         <form name="userForm">
           <label>Name:
             <input type="text" name="userName"
                    ng-model="user.name"
                    ng-model-options="{ getterSetter: true }" />
           </label>
         </form>
         <pre>user.name = <span ng-bind="user.name()"></span></pre>
       </div>
     </file>
     <file name="app.js">
       angular.module('getterSetterExample', [])
         .controller('ExampleController', ['$scope', function($scope) {
           var _name = 'Brian';
           $scope.user = {
             name: function(newName) {
              // Note that newName can be undefined for two reasons:
              // 1. Because it is called as a getter and thus called with no arguments
              // 2. Because the property should actually be set to undefined. This happens e.g. if the
              //    input is invalid
              return arguments.length ? (_name = newName) : _name;
             }
           };
         }]);
     </file>
 * </example>
 */
var ngModelDirective = ['$rootScope', function($rootScope) {
  return {
    restrict: 'A',
    require: ['ngModel', '^?form', '^?ngModelOptions'],
    controller: NgModelController,
    // Prelink needs to run before any input directive
    // so that we can set the NgModelOptions in NgModelController
    // before anyone else uses it.
    priority: 1,
    compile: function ngModelCompile(element) {
      // Setup initial state of the control
      element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS);

      return {
        pre: function ngModelPreLink(scope, element, attr, ctrls) {
          var modelCtrl = ctrls[0],
              formCtrl = ctrls[1] || modelCtrl.$$parentForm,
              optionsCtrl = ctrls[2];

          if (optionsCtrl) {
            modelCtrl.$options = optionsCtrl.$options;
          }

          modelCtrl.$$initGetterSetters();

          // notify others, especially parent forms
          formCtrl.$addControl(modelCtrl);

          attr.$observe('name', function(newValue) {
            if (modelCtrl.$name !== newValue) {
              modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue);
            }
          });

          scope.$on('$destroy', function() {
            modelCtrl.$$parentForm.$removeControl(modelCtrl);
          });
        },
        post: function ngModelPostLink(scope, element, attr, ctrls) {
          var modelCtrl = ctrls[0];
          modelCtrl.$$setUpdateOnEvents();

          function setTouched() {
            modelCtrl.$setTouched();
          }

          element.on('blur', function() {
            if (modelCtrl.$touched) return;

            if ($rootScope.$$phase) {
              scope.$evalAsync(setTouched);
            } else {
              scope.$apply(setTouched);
            }
          });
        }
      };
    }
  };
}];

/* exported defaultModelOptions */
var defaultModelOptions;
var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;

/**
 * @ngdoc type
 * @name ModelOptions
 * @description
 * A container for the options set by the {@link ngModelOptions} directive
 */
function ModelOptions(options) {
  this.$$options = options;
}

ModelOptions.prototype = {

  /**
   * @ngdoc method
   * @name ModelOptions#getOption
   * @param {string} name the name of the option to retrieve
   * @returns {*} the value of the option
   * @description
   * Returns the value of the given option
   */
  getOption: function(name) {
    return this.$$options[name];
  },

  /**
   * @ngdoc method
   * @name ModelOptions#createChild
   * @param {Object} options a hash of options for the new child that will override the parent's options
   * @return {ModelOptions} a new `ModelOptions` object initialized with the given options.
   */
  createChild: function(options) {
    var inheritAll = false;

    // make a shallow copy
    options = extend({}, options);

    // Inherit options from the parent if specified by the value `"$inherit"`
    forEach(options, /** @this */ function(option, key) {
      if (option === '$inherit') {
        if (key === '*') {
          inheritAll = true;
        } else {
          options[key] = this.$$options[key];
          // `updateOn` is special so we must also inherit the `updateOnDefault` option
          if (key === 'updateOn') {
            options.updateOnDefault = this.$$options.updateOnDefault;
          }
        }
      } else {
        if (key === 'updateOn') {
          // If the `updateOn` property contains the `default` event then we have to remove
          // it from the event list and set the `updateOnDefault` flag.
          options.updateOnDefault = false;
          options[key] = trim(option.replace(DEFAULT_REGEXP, function() {
            options.updateOnDefault = true;
            return ' ';
          }));
        }
      }
    }, this);

    if (inheritAll) {
      // We have a property of the form: `"*": "$inherit"`
      delete options['*'];
      defaults(options, this.$$options);
    }

    // Finally add in any missing defaults
    defaults(options, defaultModelOptions.$$options);

    return new ModelOptions(options);
  }
};


defaultModelOptions = new ModelOptions({
  updateOn: '',
  updateOnDefault: true,
  debounce: 0,
  getterSetter: false,
  allowInvalid: false,
  timezone: null
});


/**
 * @ngdoc directive
 * @name ngModelOptions
 * @restrict A
 * @priority 10
 *
 * @description
 * This directive allows you to modify the behaviour of {@link ngModel} directives within your
 * application. You can specify an `ngModelOptions` directive on any element. All {@link ngModel}
 * directives will use the options of their nearest `ngModelOptions` ancestor.
 *
 * The `ngModelOptions` settings are found by evaluating the value of the attribute directive as
 * an AngularJS expression. This expression should evaluate to an object, whose properties contain
 * the settings. For example: `<div ng-model-options="{ debounce: 100 }"`.
 *
 * ## Inheriting Options
 *
 * You can specify that an `ngModelOptions` setting should be inherited from a parent `ngModelOptions`
 * directive by giving it the value of `"$inherit"`.
 * Then it will inherit that setting from the first `ngModelOptions` directive found by traversing up the
 * DOM tree. If there is no ancestor element containing an `ngModelOptions` directive then default settings
 * will be used.
 *
 * For example given the following fragment of HTML
 *
 *
 * ```html
 * <div ng-model-options="{ allowInvalid: true, debounce: 200 }">
 *   <form ng-model-options="{ updateOn: 'blur', allowInvalid: '$inherit' }">
 *     <input ng-model-options="{ updateOn: 'default', allowInvalid: '$inherit' }" />
 *   </form>
 * </div>
 * ```
 *
 * the `input` element will have the following settings
 *
 * ```js
 * { allowInvalid: true, updateOn: 'default', debounce: 0 }
 * ```
 *
 * Notice that the `debounce` setting was not inherited and used the default value instead.
 *
 * You can specify that all undefined settings are automatically inherited from an ancestor by
 * including a property with key of `"*"` and value of `"$inherit"`.
 *
 * For example given the following fragment of HTML
 *
 *
 * ```html
 * <div ng-model-options="{ allowInvalid: true, debounce: 200 }">
 *   <form ng-model-options="{ updateOn: 'blur', "*": '$inherit' }">
 *     <input ng-model-options="{ updateOn: 'default', "*": '$inherit' }" />
 *   </form>
 * </div>
 * ```
 *
 * the `input` element will have the following settings
 *
 * ```js
 * { allowInvalid: true, updateOn: 'default', debounce: 200 }
 * ```
 *
 * Notice that the `debounce` setting now inherits the value from the outer `<div>` element.
 *
 * If you are creating a reusable component then you should be careful when using `"*": "$inherit"`
 * since you may inadvertently inherit a setting in the future that changes the behavior of your component.
 *
 *
 * ## Triggering and debouncing model updates
 *
 * The `updateOn` and `debounce` properties allow you to specify a custom list of events that will
 * trigger a model update and/or a debouncing delay so that the actual update only takes place when
 * a timer expires; this timer will be reset after another change takes place.
 *
 * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might
 * be different from the value in the actual model. This means that if you update the model you
 * should also invoke {@link ngModel.NgModelController#$rollbackViewValue} on the relevant input field in
 * order to make sure it is synchronized with the model and that any debounced action is canceled.
 *
 * The easiest way to reference the control's {@link ngModel.NgModelController#$rollbackViewValue}
 * method is by making sure the input is placed inside a form that has a `name` attribute. This is
 * important because `form` controllers are published to the related scope under the name in their
 * `name` attribute.
 *
 * Any pending changes will take place immediately when an enclosing form is submitted via the
 * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
 * to have access to the updated model.
 *
 * ### Overriding immediate updates
 *
 * The following example shows how to override immediate updates. Changes on the inputs within the
 * form will update the model only when the control loses focus (blur event). If `escape` key is
 * pressed while the input field is focused, the value is reset to the value in the current model.
 *
 * <example name="ngModelOptions-directive-blur" module="optionsExample">
 *   <file name="index.html">
 *     <div ng-controller="ExampleController">
 *       <form name="userForm">
 *         <label>
 *           Name:
 *           <input type="text" name="userName"
 *                  ng-model="user.name"
 *                  ng-model-options="{ updateOn: 'blur' }"
 *                  ng-keyup="cancel($event)" />
 *         </label><br />
 *         <label>
 *           Other data:
 *           <input type="text" ng-model="user.data" />
 *         </label><br />
 *       </form>
 *       <pre>user.name = <span ng-bind="user.name"></span></pre>
 *     </div>
 *   </file>
 *   <file name="app.js">
 *     angular.module('optionsExample', [])
 *       .controller('ExampleController', ['$scope', function($scope) {
 *         $scope.user = { name: 'say', data: '' };
 *
 *         $scope.cancel = function(e) {
 *           if (e.keyCode === 27) {
 *             $scope.userForm.userName.$rollbackViewValue();
 *           }
 *         };
 *       }]);
 *   </file>
 *   <file name="protractor.js" type="protractor">
 *     var model = element(by.binding('user.name'));
 *     var input = element(by.model('user.name'));
 *     var other = element(by.model('user.data'));
 *
 *     it('should allow custom events', function() {
 *       input.sendKeys(' hello');
 *       input.click();
 *       expect(model.getText()).toEqual('say');
 *       other.click();
 *       expect(model.getText()).toEqual('say hello');
 *     });
 *
 *     it('should $rollbackViewValue when model changes', function() {
 *       input.sendKeys(' hello');
 *       expect(input.getAttribute('value')).toEqual('say hello');
 *       input.sendKeys(protractor.Key.ESCAPE);
 *       expect(input.getAttribute('value')).toEqual('say');
 *       other.click();
 *       expect(model.getText()).toEqual('say');
 *     });
 *   </file>
 * </example>
 *
 * ### Debouncing updates
 *
 * The next example shows how to debounce model changes. Model will be updated only 1 sec after last change.
 * If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty.
 *
 * <example name="ngModelOptions-directive-debounce" module="optionsExample">
 *   <file name="index.html">
 *     <div ng-controller="ExampleController">
 *       <form name="userForm">
 *         Name:
 *         <input type="text" name="userName"
 *                ng-model="user.name"
 *                ng-model-options="{ debounce: 1000 }" />
 *         <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button><br />
 *       </form>
 *       <pre>user.name = <span ng-bind="user.name"></span></pre>
 *     </div>
 *   </file>
 *   <file name="app.js">
 *     angular.module('optionsExample', [])
 *       .controller('ExampleController', ['$scope', function($scope) {
 *         $scope.user = { name: 'say' };
 *       }]);
 *   </file>
 * </example>
 *
 * ### Default events, extra triggers, and catch-all debounce values
 *
 * This example shows the relationship between "default" update events and
 * additional `updateOn` triggers.
 *
 * `default` events are those that are bound to the control, and when fired, update the `$viewValue`
 * via {@link ngModel.NgModelController#$setViewValue $setViewValue}. Every event that is not listed
 * in `updateOn` is considered a "default" event, since different control types have different
 * default events.
 *
 * The control in this example updates by "default", "click", and "blur", with different `debounce`
 * values. You can see that "click" doesn't have an individual `debounce` value -
 * therefore it uses the `*` debounce value.
 *
 * There is also a button that calls {@link ngModel.NgModelController#$setViewValue $setViewValue}
 * directly with a "custom" event. Since "custom" is not defined in the `updateOn` list,
 * it is considered a "default" event and will update the
 * control if "default" is defined in `updateOn`, and will receive the "default" debounce value.
 * Note that this is just to illustrate how custom controls would possibly call `$setViewValue`.
 *
 * You can change the `updateOn` and `debounce` configuration to test different scenarios. This
 * is done with {@link ngModel.NgModelController#$overrideModelOptions $overrideModelOptions}.
 *
  <example name="ngModelOptions-advanced" module="optionsExample">
    <file name="index.html">
       <model-update-demo></model-update-demo>
    </file>
    <file name="app.js">
      angular.module('optionsExample', [])
        .component('modelUpdateDemo', {
          templateUrl: 'template.html',
          controller: function() {
            this.name = 'Chinua';

            this.options = {
              updateOn: 'default blur click',
              debounce: {
                default: 2000,
                blur: 0,
                '*': 1000
              }
            };

            this.updateEvents = function() {
              var eventList = this.options.updateOn.split(' ');
              eventList.push('*');
              var events = {};

              for (var i = 0; i < eventList.length; i++) {
                events[eventList[i]] = this.options.debounce[eventList[i]];
              }

              this.events = events;
            };

            this.updateOptions = function() {
              var options = angular.extend(this.options, {
                updateOn: Object.keys(this.events).join(' ').replace('*', ''),
                debounce: this.events
              });

              this.form.input.$overrideModelOptions(options);
            };

            // Initialize the event form
            this.updateEvents();
          }
        });
    </file>
    <file name="template.html">
      <form name="$ctrl.form">
        Input: <input type="text" name="input" ng-model="$ctrl.name" ng-model-options="$ctrl.options" />
      </form>
      Model: <tt>{{$ctrl.name}}</tt>
      <hr>
      <button ng-click="$ctrl.form.input.$setViewValue('some value', 'custom')">Trigger setViewValue with 'some value' and 'custom' event</button>

      <hr>
      <form ng-submit="$ctrl.updateOptions()">
        <b>updateOn</b><br>
        <input type="text" ng-model="$ctrl.options.updateOn" ng-change="$ctrl.updateEvents()" ng-model-options="{debounce: 500}">

        <table>
          <tr>
            <th>Option</th>
            <th>Debounce value</th>
          </tr>
          <tr ng-repeat="(key, value) in $ctrl.events">
            <td>{{key}}</td>
            <td><input type="number" ng-model="$ctrl.events[key]" /></td>
          </tr>
        </table>

        <br>
        <input type="submit" value="Update options">
      </form>
    </file>
  </example>
 *
 *
 * ## Model updates and validation
 *
 * The default behaviour in `ngModel` is that the model value is set to `undefined` when the
 * validation determines that the value is invalid. By setting the `allowInvalid` property to true,
 * the model will still be updated even if the value is invalid.
 *
 *
 * ## Connecting to the scope
 *
 * By setting the `getterSetter` property to true you are telling ngModel that the `ngModel` expression
 * on the scope refers to a "getter/setter" function rather than the value itself.
 *
 * The following example shows how to bind to getter/setters:
 *
 * <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample">
 *   <file name="index.html">
 *     <div ng-controller="ExampleController">
 *       <form name="userForm">
 *         <label>
 *           Name:
 *           <input type="text" name="userName"
 *                  ng-model="user.name"
 *                  ng-model-options="{ getterSetter: true }" />
 *         </label>
 *       </form>
 *       <pre>user.name = <span ng-bind="user.name()"></span></pre>
 *     </div>
 *   </file>
 *   <file name="app.js">
 *     angular.module('getterSetterExample', [])
 *       .controller('ExampleController', ['$scope', function($scope) {
 *         var _name = 'Brian';
 *         $scope.user = {
 *           name: function(newName) {
 *             return angular.isDefined(newName) ? (_name = newName) : _name;
 *           }
 *         };
 *       }]);
 *   </file>
 * </example>
 *
 *
 * ## Programmatically changing options
 *
 * The `ngModelOptions` expression is only evaluated once when the directive is linked; it is not
 * watched for changes. However, it is possible to override the options on a single
 * {@link ngModel.NgModelController} instance with
 * {@link ngModel.NgModelController#$overrideModelOptions `NgModelController#$overrideModelOptions()`}.
 * See also the example for
 * {@link ngModelOptions#default-events-extra-triggers-and-catch-all-debounce-values
 * Default events, extra triggers, and catch-all debounce values}.
 *
 *
 * ## Specifying timezones
 *
 * You can specify the timezone that date/time input directives expect by providing its name in the
 * `timezone` property.
 *
 *
 * ## Formatting the value of `time` and `datetime-local`
 *
 * With the options `timeSecondsFormat` and `timeStripZeroSeconds` it is possible to adjust the value
 * that is displayed in the control. Note that browsers may apply their own formatting
 * in the user interface.
 *
   <example name="ngModelOptions-time-format" module="timeExample">
     <file name="index.html">
       <time-example></time-example>
     </file>
     <file name="script.js">
        angular.module('timeExample', [])
          .component('timeExample', {
            templateUrl: 'timeExample.html',
            controller: function() {
              this.time = new Date(1970, 0, 1, 14, 57, 0);

              this.options = {
                timeSecondsFormat: 'ss',
                timeStripZeroSeconds: true
              };

              this.optionChange = function() {
                this.timeForm.timeFormatted.$overrideModelOptions(this.options);
                this.time = new Date(this.time);
              };
            }
          });
     </file>
     <file name="timeExample.html">
       <form name="$ctrl.timeForm">
         <strong>Default</strong>:
         <input type="time" ng-model="$ctrl.time" step="any" /><br>
         <strong>With options</strong>:
         <input type="time" name="timeFormatted" ng-model="$ctrl.time" step="any" ng-model-options="$ctrl.options" />
         <br>

         Options:<br>
         <code>timeSecondsFormat</code>:
         <input
           type="text"
           ng-model="$ctrl.options.timeSecondsFormat"
           ng-change="$ctrl.optionChange()">
         <br>
         <code>timeStripZeroSeconds</code>:
         <input
           type="checkbox"
           ng-model="$ctrl.options.timeStripZeroSeconds"
           ng-change="$ctrl.optionChange()">
        </form>
      </file>
 *  </example>
 *
 * @param {Object} ngModelOptions options to apply to {@link ngModel} directives on this element and
 *   and its descendents.
 *
 * **General options**:
 *
 *   - `updateOn`: string specifying which event should the input be bound to. You can set several
 *     events using an space delimited list. There is a special event called `default` that
 *     matches the default events belonging to the control. These are the events that are bound to
 *     the control, and when fired, update the `$viewValue` via `$setViewValue`.
 *
 *     `ngModelOptions` considers every event that is not listed in `updateOn` a "default" event,
 *     since different control types use different default events.
 *
 *     See also the section {@link ngModelOptions#triggering-and-debouncing-model-updates
 *     Triggering and debouncing model updates}.
 *
 *   - `debounce`: integer value which contains the debounce model update value in milliseconds. A
 *     value of 0 triggers an immediate update. If an object is supplied instead, you can specify a
 *     custom value for each event. For example:
 *     ```
 *     ng-model-options="{
 *       updateOn: 'default blur',
 *       debounce: { 'default': 500, 'blur': 0 }
 *     }"
 *     ```
 *     You can use the `*` key to specify a debounce value that applies to all events that are not
 *     specifically listed. In the following example, `mouseup` would have a debounce delay of 1000:
 *     ```
 *     ng-model-options="{
 *       updateOn: 'default blur mouseup',
 *       debounce: { 'default': 500, 'blur': 0, '*': 1000 }
 *     }"
 *     ```
 *   - `allowInvalid`: boolean value which indicates that the model can be set with values that did
 *     not validate correctly instead of the default behavior of setting the model to undefined.
 *   - `getterSetter`: boolean value which determines whether or not to treat functions bound to
 *     `ngModel` as getters/setters.
 *
 *
 *  **Input-type specific options**:
 *
 *   - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for
 *     `<input type="date" />`, `<input type="time" />`, ... . It understands UTC/GMT and the
 *     continental US time zone abbreviations, but for general use, use a time zone offset, for
 *     example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
 *     If not specified, the timezone of the browser will be used.
 *     Note that changing the timezone will have no effect on the current date, and is only applied after
 *     the next input / model change.
 *
 *   - `timeSecondsFormat`: Defines if the `time` and `datetime-local` types should show seconds and
 *     milliseconds. The option follows the format string of {@link date date filter}.
 *     By default, the options is `undefined` which is equal to `'ss.sss'` (seconds and milliseconds).
 *     The other options are `'ss'` (strips milliseconds), and `''` (empty string), which strips both
 *     seconds and milliseconds.
 *     Note that browsers that support `time` and `datetime-local` require the hour and minutes
 *     part of the time string, and may show the value differently in the user interface.
 *     {@link ngModelOptions#formatting-the-value-of-time-and-datetime-local- See the example}.
 *
 *   - `timeStripZeroSeconds`: Defines if the `time` and `datetime-local` types should strip the
 *     seconds and milliseconds from the formatted value if they are zero. This option is applied
 *     after `timeSecondsFormat`.
 *     This option can be used to make the formatting consistent over different browsers, as some
 *     browsers with support for `time` will natively hide the milliseconds and
 *     seconds if they are zero, but others won't, and browsers that don't implement these input
 *     types will always show the full string.
 *     {@link ngModelOptions#formatting-the-value-of-time-and-datetime-local- See the example}.
 *
 */
var ngModelOptionsDirective = function() {
  NgModelOptionsController.$inject = ['$attrs', '$scope'];
  function NgModelOptionsController($attrs, $scope) {
    this.$$attrs = $attrs;
    this.$$scope = $scope;
  }
  NgModelOptionsController.prototype = {
    $onInit: function() {
      var parentOptions = this.parentCtrl ? this.parentCtrl.$options : defaultModelOptions;
      var modelOptionsDefinition = this.$$scope.$eval(this.$$attrs.ngModelOptions);

      this.$options = parentOptions.createChild(modelOptionsDefinition);
    }
  };

  return {
    restrict: 'A',
    // ngModelOptions needs to run before ngModel and input directives
    priority: 10,
    require: {parentCtrl: '?^^ngModelOptions'},
    bindToController: true,
    controller: NgModelOptionsController
  };
};


// shallow copy over values from `src` that are not already specified on `dst`
function defaults(dst, src) {
  forEach(src, function(value, key) {
    if (!isDefined(dst[key])) {
      dst[key] = value;
    }
  });
}

/**
 * @ngdoc directive
 * @name ngNonBindable
 * @restrict AC
 * @priority 1000
 * @element ANY
 *
 * @description
 * The `ngNonBindable` directive tells AngularJS not to compile or bind the contents of the current
 * DOM element, including directives on the element itself that have a lower priority than
 * `ngNonBindable`. This is useful if the element contains what appears to be AngularJS directives
 * and bindings but which should be ignored by AngularJS. This could be the case if you have a site
 * that displays snippets of code, for instance.
 *
 * @example
 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present,
 * but the one wrapped in `ngNonBindable` is left alone.
 *
  <example name="ng-non-bindable">
    <file name="index.html">
      <div>Normal: {{1 + 2}}</div>
      <div ng-non-bindable>Ignored: {{1 + 2}}</div>
    </file>
    <file name="protractor.js" type="protractor">
     it('should check ng-non-bindable', function() {
       expect(element(by.binding('1 + 2')).getText()).toContain('3');
       expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/);
     });
    </file>
  </example>
 */
var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 });

/* exported ngOptionsDirective */

/* global jqLiteRemove */

var ngOptionsMinErr = minErr('ngOptions');

/**
 * @ngdoc directive
 * @name ngOptions
 * @restrict A
 *
 * @description
 *
 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>`
 * elements for the `<select>` element using the array or object obtained by evaluating the
 * `ngOptions` comprehension expression.
 *
 * In many cases, {@link ng.directive:ngRepeat ngRepeat} can be used on `<option>` elements instead of
 * `ngOptions` to achieve a similar result. However, `ngOptions` provides some benefits:
 * - more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
 * comprehension expression
 * - reduced memory consumption by not creating a new scope for each repeated instance
 * - increased render speed by creating the options in a documentFragment instead of individually
 *
 * When an item in the `<select>` menu is selected, the array element or object property
 * represented by the selected option will be bound to the model identified by the `ngModel`
 * directive.
 *
 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
 * be nested into the `<select>` element. This element will then represent the `null` or "not selected"
 * option. See example below for demonstration.
 *
 * ## Complex Models (objects or collections)
 *
 * By default, `ngModel` watches the model by reference, not value. This is important to know when
 * binding the select to a model that is an object or a collection.
 *
 * One issue occurs if you want to preselect an option. For example, if you set
 * the model to an object that is equal to an object in your collection, `ngOptions` won't be able to set the selection,
 * because the objects are not identical. So by default, you should always reference the item in your collection
 * for preselections, e.g.: `$scope.selected = $scope.collection[3]`.
 *
 * Another solution is to use a `track by` clause, because then `ngOptions` will track the identity
 * of the item not by reference, but by the result of the `track by` expression. For example, if your
 * collection items have an id property, you would `track by item.id`.
 *
 * A different issue with objects or collections is that ngModel won't detect if an object property or
 * a collection item changes. For that reason, `ngOptions` additionally watches the model using
 * `$watchCollection`, when the expression contains a `track by` clause or the the select has the `multiple` attribute.
 * This allows ngOptions to trigger a re-rendering of the options even if the actual object/collection
 * has not changed identity, but only a property on the object or an item in the collection changes.
 *
 * Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection
 * if the model is an array). This means that changing a property deeper than the first level inside the
 * object/collection will not trigger a re-rendering.
 *
 * ## `select` **`as`**
 *
 * Using `select` **`as`** will bind the result of the `select` expression to the model, but
 * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
 * or property name (for object data sources) of the value within the collection. If a **`track by`** expression
 * is used, the result of that expression will be set as the value of the `option` and `select` elements.
 *
 *
 * ### `select` **`as`** and **`track by`**
 *
 * <div class="alert alert-warning">
 * Be careful when using `select` **`as`** and **`track by`** in the same expression.
 * </div>
 *
 * Given this array of items on the $scope:
 *
 * ```js
 * $scope.items = [{
 *   id: 1,
 *   label: 'aLabel',
 *   subItem: { name: 'aSubItem' }
 * }, {
 *   id: 2,
 *   label: 'bLabel',
 *   subItem: { name: 'bSubItem' }
 * }];
 * ```
 *
 * This will work:
 *
 * ```html
 * <select ng-options="item as item.label for item in items track by item.id" ng-model="selected"></select>
 * ```
 * ```js
 * $scope.selected = $scope.items[0];
 * ```
 *
 * but this will not work:
 *
 * ```html
 * <select ng-options="item.subItem as item.label for item in items track by item.id" ng-model="selected"></select>
 * ```
 * ```js
 * $scope.selected = $scope.items[0].subItem;
 * ```
 *
 * In both examples, the **`track by`** expression is applied successfully to each `item` in the
 * `items` array. Because the selected option has been set programmatically in the controller, the
 * **`track by`** expression is also applied to the `ngModel` value. In the first example, the
 * `ngModel` value is `items[0]` and the **`track by`** expression evaluates to `items[0].id` with
 * no issue. In the second example, the `ngModel` value is `items[0].subItem` and the **`track by`**
 * expression evaluates to `items[0].subItem.id` (which is undefined). As a result, the model value
 * is not matched against any `<option>` and the `<select>` appears as having no selected value.
 *
 *
 * @param {string} ngModel Assignable AngularJS expression to data-bind to.
 * @param {comprehension_expression} ngOptions in one of the following forms:
 *
 *   * for array data sources:
 *     * `label` **`for`** `value` **`in`** `array`
 *     * `select` **`as`** `label` **`for`** `value` **`in`** `array`
 *     * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
 *     * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array`
 *     * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
 *     * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
 *     * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr`
 *        (for including a filter with `track by`)
 *   * for object data sources:
 *     * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
 *     * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
 *     * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
 *     * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object`
 *     * `select` **`as`** `label` **`group by`** `group`
 *         **`for` `(`**`key`**`,`** `value`**`) in`** `object`
 *     * `select` **`as`** `label` **`disable when`** `disable`
 *         **`for` `(`**`key`**`,`** `value`**`) in`** `object`
 *
 * Where:
 *
 *   * `array` / `object`: an expression which evaluates to an array / object to iterate over.
 *   * `value`: local variable which will refer to each item in the `array` or each property value
 *      of `object` during iteration.
 *   * `key`: local variable which will refer to a property name in `object` during iteration.
 *   * `label`: The result of this expression will be the label for `<option>` element. The
 *     `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`).
 *   * `select`: The result of this expression will be bound to the model of the parent `<select>`
 *      element. If not specified, `select` expression will default to `value`.
 *   * `group`: The result of this expression will be used to group options using the `<optgroup>`
 *      DOM element.
 *   * `disable`: The result of this expression will be used to disable the rendered `<option>`
 *      element. Return `true` to disable.
 *   * `trackexpr`: Used when working with an array of objects. The result of this expression will be
 *      used to identify the objects in the array. The `trackexpr` will most likely refer to the
 *     `value` variable (e.g. `value.propertyName`). With this the selection is preserved
 *      even when the options are recreated (e.g. reloaded from the server).
 * @param {string=} name Property name of the form under which the control is published.
 * @param {string=} required The control is considered valid only if value is entered.
 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
 *    the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
 *    `required` when you want to data-bind to the `required` attribute.
 * @param {string=} ngAttrSize sets the size of the select element dynamically. Uses the
 * {@link guide/interpolation#-ngattr-for-binding-to-arbitrary-attributes ngAttr} directive.
 *
 * @example
    <example module="selectExample" name="select">
      <file name="index.html">
        <script>
        angular.module('selectExample', [])
          .controller('ExampleController', ['$scope', function($scope) {
            $scope.colors = [
              {name:'black', shade:'dark'},
              {name:'white', shade:'light', notAnOption: true},
              {name:'red', shade:'dark'},
              {name:'blue', shade:'dark', notAnOption: true},
              {name:'yellow', shade:'light', notAnOption: false}
            ];
            $scope.myColor = $scope.colors[2]; // red
          }]);
        </script>
        <div ng-controller="ExampleController">
          <ul>
            <li ng-repeat="color in colors">
              <label>Name: <input ng-model="color.name"></label>
              <label><input type="checkbox" ng-model="color.notAnOption"> Disabled?</label>
              <button ng-click="colors.splice($index, 1)" aria-label="Remove">X</button>
            </li>
            <li>
              <button ng-click="colors.push({})">add</button>
            </li>
          </ul>
          <hr/>
          <label>Color (null not allowed):
            <select ng-model="myColor" ng-options="color.name for color in colors"></select>
          </label><br/>
          <label>Color (null allowed):
          <span  class="nullable">
            <select ng-model="myColor" ng-options="color.name for color in colors">
              <option value="">-- choose color --</option>
            </select>
          </span></label><br/>

          <label>Color grouped by shade:
            <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors">
            </select>
          </label><br/>

          <label>Color grouped by shade, with some disabled:
            <select ng-model="myColor"
                  ng-options="color.name group by color.shade disable when color.notAnOption for color in colors">
            </select>
          </label><br/>



          Select <button ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</button>.
          <br/>
          <hr/>
          Currently selected: {{ {selected_color:myColor} }}
          <div style="border:solid 1px black; height:20px"
               ng-style="{'background-color':myColor.name}">
          </div>
        </div>
      </file>
      <file name="protractor.js" type="protractor">
         it('should check ng-options', function() {
           expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red');
           element.all(by.model('myColor')).first().click();
           element.all(by.css('select[ng-model="myColor"] option')).first().click();
           expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black');
           element(by.css('.nullable select[ng-model="myColor"]')).click();
           element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click();
           expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null');
         });
      </file>
    </example>
 */

/* eslint-disable max-len */
//                     //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555000000000666666666666600000007777777777777000000000000000888888888800000000000000000009999999999
var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?(?:\s+disable\s+when\s+([\s\S]+?))?\s+for\s+(?:([$\w][$\w]*)|(?:\(\s*([$\w][$\w]*)\s*,\s*([$\w][$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/;
                        // 1: value expression (valueFn)
                        // 2: label expression (displayFn)
                        // 3: group by expression (groupByFn)
                        // 4: disable when expression (disableWhenFn)
                        // 5: array item variable name
                        // 6: object item key variable name
                        // 7: object item value variable name
                        // 8: collection expression
                        // 9: track by expression
/* eslint-enable */


var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile, $document, $parse) {

  function parseOptionsExpression(optionsExp, selectElement, scope) {

    var match = optionsExp.match(NG_OPTIONS_REGEXP);
    if (!(match)) {
      throw ngOptionsMinErr('iexp',
        'Expected expression in form of ' +
        '\'_select_ (as _label_)? for (_key_,)?_value_ in _collection_\'' +
        ' but got \'{0}\'. Element: {1}',
        optionsExp, startingTag(selectElement));
    }

    // Extract the parts from the ngOptions expression

    // The variable name for the value of the item in the collection
    var valueName = match[5] || match[7];
    // The variable name for the key of the item in the collection
    var keyName = match[6];

    // An expression that generates the viewValue for an option if there is a label expression
    var selectAs = / as /.test(match[0]) && match[1];
    // An expression that is used to track the id of each object in the options collection
    var trackBy = match[9];
    // An expression that generates the viewValue for an option if there is no label expression
    var valueFn = $parse(match[2] ? match[1] : valueName);
    var selectAsFn = selectAs && $parse(selectAs);
    var viewValueFn = selectAsFn || valueFn;
    var trackByFn = trackBy && $parse(trackBy);

    // Get the value by which we are going to track the option
    // if we have a trackFn then use that (passing scope and locals)
    // otherwise just hash the given viewValue
    var getTrackByValueFn = trackBy ?
                              function(value, locals) { return trackByFn(scope, locals); } :
                              function getHashOfValue(value) { return hashKey(value); };
    var getTrackByValue = function(value, key) {
      return getTrackByValueFn(value, getLocals(value, key));
    };

    var displayFn = $parse(match[2] || match[1]);
    var groupByFn = $parse(match[3] || '');
    var disableWhenFn = $parse(match[4] || '');
    var valuesFn = $parse(match[8]);

    var locals = {};
    var getLocals = keyName ? function(value, key) {
      locals[keyName] = key;
      locals[valueName] = value;
      return locals;
    } : function(value) {
      locals[valueName] = value;
      return locals;
    };


    function Option(selectValue, viewValue, label, group, disabled) {
      this.selectValue = selectValue;
      this.viewValue = viewValue;
      this.label = label;
      this.group = group;
      this.disabled = disabled;
    }

    function getOptionValuesKeys(optionValues) {
      var optionValuesKeys;

      if (!keyName && isArrayLike(optionValues)) {
        optionValuesKeys = optionValues;
      } else {
        // if object, extract keys, in enumeration order, unsorted
        optionValuesKeys = [];
        for (var itemKey in optionValues) {
          if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') {
            optionValuesKeys.push(itemKey);
          }
        }
      }
      return optionValuesKeys;
    }

    return {
      trackBy: trackBy,
      getTrackByValue: getTrackByValue,
      getWatchables: $parse(valuesFn, function(optionValues) {
        // Create a collection of things that we would like to watch (watchedArray)
        // so that they can all be watched using a single $watchCollection
        // that only runs the handler once if anything changes
        var watchedArray = [];
        optionValues = optionValues || [];

        var optionValuesKeys = getOptionValuesKeys(optionValues);
        var optionValuesLength = optionValuesKeys.length;
        for (var index = 0; index < optionValuesLength; index++) {
          var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
          var value = optionValues[key];

          var locals = getLocals(value, key);
          var selectValue = getTrackByValueFn(value, locals);
          watchedArray.push(selectValue);

          // Only need to watch the displayFn if there is a specific label expression
          if (match[2] || match[1]) {
            var label = displayFn(scope, locals);
            watchedArray.push(label);
          }

          // Only need to watch the disableWhenFn if there is a specific disable expression
          if (match[4]) {
            var disableWhen = disableWhenFn(scope, locals);
            watchedArray.push(disableWhen);
          }
        }
        return watchedArray;
      }),

      getOptions: function() {

        var optionItems = [];
        var selectValueMap = {};

        // The option values were already computed in the `getWatchables` fn,
        // which must have been called to trigger `getOptions`
        var optionValues = valuesFn(scope) || [];
        var optionValuesKeys = getOptionValuesKeys(optionValues);
        var optionValuesLength = optionValuesKeys.length;

        for (var index = 0; index < optionValuesLength; index++) {
          var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
          var value = optionValues[key];
          var locals = getLocals(value, key);
          var viewValue = viewValueFn(scope, locals);
          var selectValue = getTrackByValueFn(viewValue, locals);
          var label = displayFn(scope, locals);
          var group = groupByFn(scope, locals);
          var disabled = disableWhenFn(scope, locals);
          var optionItem = new Option(selectValue, viewValue, label, group, disabled);

          optionItems.push(optionItem);
          selectValueMap[selectValue] = optionItem;
        }

        return {
          items: optionItems,
          selectValueMap: selectValueMap,
          getOptionFromViewValue: function(value) {
            return selectValueMap[getTrackByValue(value)];
          },
          getViewValueFromOption: function(option) {
            // If the viewValue could be an object that may be mutated by the application,
            // we need to make a copy and not return the reference to the value on the option.
            return trackBy ? copy(option.viewValue) : option.viewValue;
          }
        };
      }
    };
  }


  // Support: IE 9 only
  // We can't just jqLite('<option>') since jqLite is not smart enough
  // to create it in <select> and IE barfs otherwise.
  var optionTemplate = window.document.createElement('option'),
      optGroupTemplate = window.document.createElement('optgroup');

    function ngOptionsPostLink(scope, selectElement, attr, ctrls) {

      var selectCtrl = ctrls[0];
      var ngModelCtrl = ctrls[1];
      var multiple = attr.multiple;

      // The emptyOption allows the application developer to provide their own custom "empty"
      // option when the viewValue does not match any of the option values.
      for (var i = 0, children = selectElement.children(), ii = children.length; i < ii; i++) {
        if (children[i].value === '') {
          selectCtrl.hasEmptyOption = true;
          selectCtrl.emptyOption = children.eq(i);
          break;
        }
      }

      // The empty option will be compiled and rendered before we first generate the options
      selectElement.empty();

      var providedEmptyOption = !!selectCtrl.emptyOption;

      var unknownOption = jqLite(optionTemplate.cloneNode(false));
      unknownOption.val('?');

      var options;
      var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope);
      // This stores the newly created options before they are appended to the select.
      // Since the contents are removed from the fragment when it is appended,
      // we only need to create it once.
      var listFragment = $document[0].createDocumentFragment();

      // Overwrite the implementation. ngOptions doesn't use hashes
      selectCtrl.generateUnknownOptionValue = function(val) {
        return '?';
      };

      // Update the controller methods for multiple selectable options
      if (!multiple) {

        selectCtrl.writeValue = function writeNgOptionsValue(value) {
          // The options might not be defined yet when ngModel tries to render
          if (!options) return;

          var selectedOption = selectElement[0].options[selectElement[0].selectedIndex];
          var option = options.getOptionFromViewValue(value);

          // Make sure to remove the selected attribute from the previously selected option
          // Otherwise, screen readers might get confused
          if (selectedOption) selectedOption.removeAttribute('selected');

          if (option) {
            // Don't update the option when it is already selected.
            // For example, the browser will select the first option by default. In that case,
            // most properties are set automatically - except the `selected` attribute, which we
            // set always

            if (selectElement[0].value !== option.selectValue) {
              selectCtrl.removeUnknownOption();

              selectElement[0].value = option.selectValue;
              option.element.selected = true;
            }

            option.element.setAttribute('selected', 'selected');
          } else {
            selectCtrl.selectUnknownOrEmptyOption(value);
          }
        };

        selectCtrl.readValue = function readNgOptionsValue() {

          var selectedOption = options.selectValueMap[selectElement.val()];

          if (selectedOption && !selectedOption.disabled) {
            selectCtrl.unselectEmptyOption();
            selectCtrl.removeUnknownOption();
            return options.getViewValueFromOption(selectedOption);
          }
          return null;
        };

        // If we are using `track by` then we must watch the tracked value on the model
        // since ngModel only watches for object identity change
        // FIXME: When a user selects an option, this watch will fire needlessly
        if (ngOptions.trackBy) {
          scope.$watch(
            function() { return ngOptions.getTrackByValue(ngModelCtrl.$viewValue); },
            function() { ngModelCtrl.$render(); }
          );
        }

      } else {

        selectCtrl.writeValue = function writeNgOptionsMultiple(values) {
          // The options might not be defined yet when ngModel tries to render
          if (!options) return;

          // Only set `<option>.selected` if necessary, in order to prevent some browsers from
          // scrolling to `<option>` elements that are outside the `<select>` element's viewport.
          var selectedOptions = values && values.map(getAndUpdateSelectedOption) || [];

          options.items.forEach(function(option) {
            if (option.element.selected && !includes(selectedOptions, option)) {
              option.element.selected = false;
            }
          });
        };


        selectCtrl.readValue = function readNgOptionsMultiple() {
          var selectedValues = selectElement.val() || [],
              selections = [];

          forEach(selectedValues, function(value) {
            var option = options.selectValueMap[value];
            if (option && !option.disabled) selections.push(options.getViewValueFromOption(option));
          });

          return selections;
        };

        // If we are using `track by` then we must watch these tracked values on the model
        // since ngModel only watches for object identity change
        if (ngOptions.trackBy) {

          scope.$watchCollection(function() {
            if (isArray(ngModelCtrl.$viewValue)) {
              return ngModelCtrl.$viewValue.map(function(value) {
                return ngOptions.getTrackByValue(value);
              });
            }
          }, function() {
            ngModelCtrl.$render();
          });

        }
      }

      if (providedEmptyOption) {

        // compile the element since there might be bindings in it
        $compile(selectCtrl.emptyOption)(scope);

        selectElement.prepend(selectCtrl.emptyOption);

        if (selectCtrl.emptyOption[0].nodeType === NODE_TYPE_COMMENT) {
          // This means the empty option has currently no actual DOM node, probably because
          // it has been modified by a transclusion directive.
          selectCtrl.hasEmptyOption = false;

          // Redefine the registerOption function, which will catch
          // options that are added by ngIf etc. (rendering of the node is async because of
          // lazy transclusion)
          selectCtrl.registerOption = function(optionScope, optionEl) {
            if (optionEl.val() === '') {
              selectCtrl.hasEmptyOption = true;
              selectCtrl.emptyOption = optionEl;
              selectCtrl.emptyOption.removeClass('ng-scope');
              // This ensures the new empty option is selected if previously no option was selected
              ngModelCtrl.$render();

              optionEl.on('$destroy', function() {
                var needsRerender = selectCtrl.$isEmptyOptionSelected();

                selectCtrl.hasEmptyOption = false;
                selectCtrl.emptyOption = undefined;

                if (needsRerender) ngModelCtrl.$render();
              });
            }
          };

        } else {
          // remove the class, which is added automatically because we recompile the element and it
          // becomes the compilation root
          selectCtrl.emptyOption.removeClass('ng-scope');
        }

      }

      // We will re-render the option elements if the option values or labels change
      scope.$watchCollection(ngOptions.getWatchables, updateOptions);

      // ------------------------------------------------------------------ //

      function addOptionElement(option, parent) {
        var optionElement = optionTemplate.cloneNode(false);
        parent.appendChild(optionElement);
        updateOptionElement(option, optionElement);
      }

      function getAndUpdateSelectedOption(viewValue) {
        var option = options.getOptionFromViewValue(viewValue);
        var element = option && option.element;

        if (element && !element.selected) element.selected = true;

        return option;
      }

      function updateOptionElement(option, element) {
        option.element = element;
        element.disabled = option.disabled;
        // Support: IE 11 only, Edge 12-13 only
        // NOTE: The label must be set before the value, otherwise IE 11 & Edge create unresponsive
        // selects in certain circumstances when multiple selects are next to each other and display
        // the option list in listbox style, i.e. the select is [multiple], or specifies a [size].
        // See https://github.com/angular/angular.js/issues/11314 for more info.
        // This is unfortunately untestable with unit / e2e tests
        if (option.label !== element.label) {
          element.label = option.label;
          element.textContent = option.label;
        }
        element.value = option.selectValue;
      }

      function updateOptions() {
        var previousValue = options && selectCtrl.readValue();

        // We must remove all current options, but cannot simply set innerHTML = null
        // since the providedEmptyOption might have an ngIf on it that inserts comments which we
        // must preserve.
        // Instead, iterate over the current option elements and remove them or their optgroup
        // parents
        if (options) {

          for (var i = options.items.length - 1; i >= 0; i--) {
            var option = options.items[i];
            if (isDefined(option.group)) {
              jqLiteRemove(option.element.parentNode);
            } else {
              jqLiteRemove(option.element);
            }
          }
        }

        options = ngOptions.getOptions();

        var groupElementMap = {};

        options.items.forEach(function addOption(option) {
          var groupElement;

          if (isDefined(option.group)) {

            // This option is to live in a group
            // See if we have already created this group
            groupElement = groupElementMap[option.group];

            if (!groupElement) {

              groupElement = optGroupTemplate.cloneNode(false);
              listFragment.appendChild(groupElement);

              // Update the label on the group element
              // "null" is special cased because of Safari
              groupElement.label = option.group === null ? 'null' : option.group;

              // Store it for use later
              groupElementMap[option.group] = groupElement;
            }

            addOptionElement(option, groupElement);

          } else {

            // This option is not in a group
            addOptionElement(option, listFragment);
          }
        });

        selectElement[0].appendChild(listFragment);

        ngModelCtrl.$render();

        // Check to see if the value has changed due to the update to the options
        if (!ngModelCtrl.$isEmpty(previousValue)) {
          var nextValue = selectCtrl.readValue();
          var isNotPrimitive = ngOptions.trackBy || multiple;
          if (isNotPrimitive ? !equals(previousValue, nextValue) : previousValue !== nextValue) {
            ngModelCtrl.$setViewValue(nextValue);
            ngModelCtrl.$render();
          }
        }
      }
  }

  return {
    restrict: 'A',
    terminal: true,
    require: ['select', 'ngModel'],
    link: {
      pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) {
        // Deactivate the SelectController.register method to prevent
        // option directives from accidentally registering themselves
        // (and unwanted $destroy handlers etc.)
        ctrls[0].registerOption = noop;
      },
      post: ngOptionsPostLink
    }
  };
}];

/**
 * @ngdoc directive
 * @name ngPluralize
 * @restrict EA
 *
 * @description
 * `ngPluralize` is a directive that displays messages according to en-US localization rules.
 * These rules are bundled with angular.js, but can be overridden
 * (see {@link guide/i18n AngularJS i18n} dev guide). You configure ngPluralize directive
 * by specifying the mappings between
 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html)
 * and the strings to be displayed.
 *
 * ## Plural categories and explicit number rules
 * There are two
 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html)
 * in AngularJS's default en-US locale: "one" and "other".
 *
 * While a plural category may match many numbers (for example, in en-US locale, "other" can match
 * any number that is not 1), an explicit number rule can only match one number. For example, the
 * explicit number rule for "3" matches the number 3. There are examples of plural categories
 * and explicit number rules throughout the rest of this documentation.
 *
 * ## Configuring ngPluralize
 * You configure ngPluralize by providing 2 attributes: `count` and `when`.
 * You can also provide an optional attribute, `offset`.
 *
 * The value of the `count` attribute can be either a string or an {@link guide/expression
 * AngularJS expression}; these are evaluated on the current scope for its bound value.
 *
 * The `when` attribute specifies the mappings between plural categories and the actual
 * string to be displayed. The value of the attribute should be a JSON object.
 *
 * The following example shows how to configure ngPluralize:
 *
 * ```html
 * <ng-pluralize count="personCount"
                 when="{'0': 'Nobody is viewing.',
 *                      'one': '1 person is viewing.',
 *                      'other': '{} people are viewing.'}">
 * </ng-pluralize>
 *```
 *
 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not
 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing"
 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for
 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can
 * show "a dozen people are viewing".
 *
 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted
 * into pluralized strings. In the previous example, AngularJS will replace `{}` with
 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder
 * for <span ng-non-bindable>{{numberExpression}}</span>.
 *
 * If no rule is defined for a category, then an empty string is displayed and a warning is generated.
 * Note that some locales define more categories than `one` and `other`. For example, fr-fr defines `few` and `many`.
 *
 * ## Configuring ngPluralize with offset
 * The `offset` attribute allows further customization of pluralized text, which can result in
 * a better user experience. For example, instead of the message "4 people are viewing this document",
 * you might display "John, Kate and 2 others are viewing this document".
 * The offset attribute allows you to offset a number by any desired value.
 * Let's take a look at an example:
 *
 * ```html
 * <ng-pluralize count="personCount" offset=2
 *               when="{'0': 'Nobody is viewing.',
 *                      '1': '{{person1}} is viewing.',
 *                      '2': '{{person1}} and {{person2}} are viewing.',
 *                      'one': '{{person1}}, {{person2}} and one other person are viewing.',
 *                      'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
 * </ng-pluralize>
 * ```
 *
 * Notice that we are still using two plural categories(one, other), but we added
 * three explicit number rules 0, 1 and 2.
 * When one person, perhaps John, views the document, "John is viewing" will be shown.
 * When three people view the document, no explicit number rule is found, so
 * an offset of 2 is taken off 3, and AngularJS uses 1 to decide the plural category.
 * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing"
 * is shown.
 *
 * Note that when you specify offsets, you must provide explicit number rules for
 * numbers from 0 up to and including the offset. If you use an offset of 3, for example,
 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for
 * plural categories "one" and "other".
 *
 * @param {string|expression} count The variable to be bound to.
 * @param {string} when The mapping between plural category to its corresponding strings.
 * @param {number=} offset Offset to deduct from the total number.
 *
 * @example
    <example module="pluralizeExample" name="ng-pluralize">
      <file name="index.html">
        <script>
          angular.module('pluralizeExample', [])
            .controller('ExampleController', ['$scope', function($scope) {
              $scope.person1 = 'Igor';
              $scope.person2 = 'Misko';
              $scope.personCount = 1;
            }]);
        </script>
        <div ng-controller="ExampleController">
          <label>Person 1:<input type="text" ng-model="person1" value="Igor" /></label><br/>
          <label>Person 2:<input type="text" ng-model="person2" value="Misko" /></label><br/>
          <label>Number of People:<input type="text" ng-model="personCount" value="1" /></label><br/>

          <!--- Example with simple pluralization rules for en locale --->
          Without Offset:
          <ng-pluralize count="personCount"
                        when="{'0': 'Nobody is viewing.',
                               'one': '1 person is viewing.',
                               'other': '{} people are viewing.'}">
          </ng-pluralize><br>

          <!--- Example with offset --->
          With Offset(2):
          <ng-pluralize count="personCount" offset=2
                        when="{'0': 'Nobody is viewing.',
                               '1': '{{person1}} is viewing.',
                               '2': '{{person1}} and {{person2}} are viewing.',
                               'one': '{{person1}}, {{person2}} and one other person are viewing.',
                               'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
          </ng-pluralize>
        </div>
      </file>
      <file name="protractor.js" type="protractor">
        it('should show correct pluralized string', function() {
          var withoutOffset = element.all(by.css('ng-pluralize')).get(0);
          var withOffset = element.all(by.css('ng-pluralize')).get(1);
          var countInput = element(by.model('personCount'));

          expect(withoutOffset.getText()).toEqual('1 person is viewing.');
          expect(withOffset.getText()).toEqual('Igor is viewing.');

          countInput.clear();
          countInput.sendKeys('0');

          expect(withoutOffset.getText()).toEqual('Nobody is viewing.');
          expect(withOffset.getText()).toEqual('Nobody is viewing.');

          countInput.clear();
          countInput.sendKeys('2');

          expect(withoutOffset.getText()).toEqual('2 people are viewing.');
          expect(withOffset.getText()).toEqual('Igor and Misko are viewing.');

          countInput.clear();
          countInput.sendKeys('3');

          expect(withoutOffset.getText()).toEqual('3 people are viewing.');
          expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.');

          countInput.clear();
          countInput.sendKeys('4');

          expect(withoutOffset.getText()).toEqual('4 people are viewing.');
          expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.');
        });
        it('should show data-bound names', function() {
          var withOffset = element.all(by.css('ng-pluralize')).get(1);
          var personCount = element(by.model('personCount'));
          var person1 = element(by.model('person1'));
          var person2 = element(by.model('person2'));
          personCount.clear();
          personCount.sendKeys('4');
          person1.clear();
          person1.sendKeys('Di');
          person2.clear();
          person2.sendKeys('Vojta');
          expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.');
        });
      </file>
    </example>
 */
var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) {
  var BRACE = /{}/g,
      IS_WHEN = /^when(Minus)?(.+)$/;

  return {
    link: function(scope, element, attr) {
      var numberExp = attr.count,
          whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs
          offset = attr.offset || 0,
          whens = scope.$eval(whenExp) || {},
          whensExpFns = {},
          startSymbol = $interpolate.startSymbol(),
          endSymbol = $interpolate.endSymbol(),
          braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol,
          watchRemover = angular.noop,
          lastCount;

      forEach(attr, function(expression, attributeName) {
        var tmpMatch = IS_WHEN.exec(attributeName);
        if (tmpMatch) {
          var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]);
          whens[whenKey] = element.attr(attr.$attr[attributeName]);
        }
      });
      forEach(whens, function(expression, key) {
        whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement));

      });

      scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) {
        var count = parseFloat(newVal);
        var countIsNaN = isNumberNaN(count);

        if (!countIsNaN && !(count in whens)) {
          // If an explicit number rule such as 1, 2, 3... is defined, just use it.
          // Otherwise, check it against pluralization rules in $locale service.
          count = $locale.pluralCat(count - offset);
        }

        // If both `count` and `lastCount` are NaN, we don't need to re-register a watch.
        // In JS `NaN !== NaN`, so we have to explicitly check.
        if ((count !== lastCount) && !(countIsNaN && isNumberNaN(lastCount))) {
          watchRemover();
          var whenExpFn = whensExpFns[count];
          if (isUndefined(whenExpFn)) {
            if (newVal != null) {
              $log.debug('ngPluralize: no rule defined for \'' + count + '\' in ' + whenExp);
            }
            watchRemover = noop;
            updateElementText();
          } else {
            watchRemover = scope.$watch(whenExpFn, updateElementText);
          }
          lastCount = count;
        }
      });

      function updateElementText(newText) {
        element.text(newText || '');
      }
    }
  };
}];

/**
 * @ngdoc directive
 * @name ngRef
 * @restrict A
 *
 * @description
 * The `ngRef` attribute tells AngularJS to assign the controller of a component (or a directive)
 * to the given property in the current scope. It is also possible to add the jqlite-wrapped DOM
 * element to the scope.
 *
 * If the element with `ngRef` is destroyed `null` is assigned to the property.
 *
 * Note that if you want to assign from a child into the parent scope, you must initialize the
 * target property on the parent scope, otherwise `ngRef` will assign on the child scope.
 * This commonly happens when assigning elements or components wrapped in {@link ngIf} or
 * {@link ngRepeat}. See the second example below.
 *
 *
 * @element ANY
 * @param {string} ngRef property name - A valid AngularJS expression identifier to which the
 *                       controller or jqlite-wrapped DOM element will be bound.
 * @param {string=} ngRefRead read value - The name of a directive (or component) on this element,
 *                            or the special string `$element`. If a name is provided, `ngRef` will
 *                            assign the matching controller. If `$element` is provided, the element
 *                            itself is assigned (even if a controller is available).
 *
 *
 * @example
 * ### Simple toggle
 * This example shows how the controller of the component toggle
 * is reused in the template through the scope to use its logic.
 * <example name="ng-ref-component" module="myApp">
 *   <file name="index.html">
 *     <my-toggle ng-ref="myToggle"></my-toggle>
 *     <button ng-click="myToggle.toggle()">Toggle</button>
 *     <div ng-show="myToggle.isOpen()">
 *       You are using a component in the same template to show it.
 *     </div>
 *   </file>
 *   <file name="index.js">
 *     angular.module('myApp', [])
 *     .component('myToggle', {
 *       controller: function ToggleController() {
 *         var opened = false;
 *         this.isOpen = function() { return opened; };
 *         this.toggle = function() { opened = !opened; };
 *       }
 *     });
 *   </file>
 *   <file name="protractor.js" type="protractor">
 *      it('should publish the toggle into the scope', function() {
 *        var toggle = element(by.buttonText('Toggle'));
 *        expect(toggle.evaluate('myToggle.isOpen()')).toEqual(false);
 *        toggle.click();
 *        expect(toggle.evaluate('myToggle.isOpen()')).toEqual(true);
 *      });
 *   </file>
 * </example>
 *
 * @example
 * ### ngRef inside scopes
 * This example shows how `ngRef` works with child scopes. The `ngRepeat`-ed `myWrapper` components
 * are assigned to the scope of `myRoot`, because the `toggles` property has been initialized.
 * The repeated `myToggle` components are published to the child scopes created by `ngRepeat`.
 * `ngIf` behaves similarly - the assignment of `myToggle` happens in the `ngIf` child scope,
 * because the target property has not been initialized on the `myRoot` component controller.
 *
 * <example name="ng-ref-scopes" module="myApp">
 *   <file name="index.html">
 *     <my-root></my-root>
 *   </file>
 *   <file name="index.js">
 *     angular.module('myApp', [])
 *     .component('myRoot', {
 *       templateUrl: 'root.html',
 *       controller: function() {
 *         this.wrappers = []; // initialize the array so that the wrappers are assigned into the parent scope
 *       }
 *     })
 *     .component('myToggle', {
 *       template: '<strong>myToggle</strong><button ng-click="$ctrl.toggle()" ng-transclude></button>',
 *       transclude: true,
 *       controller: function ToggleController() {
 *         var opened = false;
 *         this.isOpen = function() { return opened; };
 *         this.toggle = function() { opened = !opened; };
 *       }
 *     })
 *     .component('myWrapper', {
 *       transclude: true,
 *       template: '<strong>myWrapper</strong>' +
 *         '<div>ngRepeatToggle.isOpen(): {{$ctrl.ngRepeatToggle.isOpen() | json}}</div>' +
 *         '<my-toggle ng-ref="$ctrl.ngRepeatToggle"><ng-transclude></ng-transclude></my-toggle>'
 *     });
 *   </file>
 *   <file name="root.html">
 *     <strong>myRoot</strong>
 *     <my-toggle ng-ref="$ctrl.outerToggle">Outer Toggle</my-toggle>
 *     <div>outerToggle.isOpen(): {{$ctrl.outerToggle.isOpen() | json}}</div>
 *     <div><em>wrappers assigned to root</em><br>
 *     <div ng-repeat="wrapper in $ctrl.wrappers">
 *       wrapper.ngRepeatToggle.isOpen(): {{wrapper.ngRepeatToggle.isOpen() | json}}
 *     </div>
 *
 *     <ul>
 *       <li ng-repeat="(index, value) in [1,2,3]">
 *         <strong>ngRepeat</strong>
 *         <div>outerToggle.isOpen(): {{$ctrl.outerToggle.isOpen() | json}}</div>
 *         <my-wrapper ng-ref="$ctrl.wrappers[index]">ngRepeat Toggle {{$index + 1}}</my-wrapper>
 *       </li>
 *     </ul>
 *
 *     <div>ngIfToggle.isOpen(): {{ngIfToggle.isOpen()}} // This is always undefined because it's
 *       assigned to the child scope created by ngIf.
 *     </div>
 *     <div ng-if="true">
          <strong>ngIf</strong>
 *        <my-toggle ng-ref="ngIfToggle">ngIf Toggle</my-toggle>
 *        <div>ngIfToggle.isOpen(): {{ngIfToggle.isOpen() | json}}</div>
 *        <div>outerToggle.isOpen(): {{$ctrl.outerToggle.isOpen() | json}}</div>
 *     </div>
 *   </file>
 *   <file name="styles.css">
 *     ul {
 *       list-style: none;
 *       padding-left: 0;
 *     }
 *
 *     li[ng-repeat] {
 *       background: lightgreen;
 *       padding: 8px;
 *       margin: 8px;
 *     }
 *
 *     [ng-if] {
 *       background: lightgrey;
 *       padding: 8px;
 *     }
 *
 *     my-root {
 *       background: lightgoldenrodyellow;
 *       padding: 8px;
 *       display: block;
 *     }
 *
 *     my-wrapper {
 *       background: lightsalmon;
 *       padding: 8px;
 *       display: block;
 *     }
 *
 *     my-toggle {
 *       background: lightblue;
 *       padding: 8px;
 *       display: block;
 *     }
 *   </file>
 *   <file name="protractor.js" type="protractor">
 *      var OuterToggle = function() {
 *        this.toggle = function() {
 *          element(by.buttonText('Outer Toggle')).click();
 *        };
 *        this.isOpen = function() {
 *          return element.all(by.binding('outerToggle.isOpen()')).first().getText();
 *        };
 *      };
 *      var NgRepeatToggle = function(i) {
 *        var parent = element.all(by.repeater('(index, value) in [1,2,3]')).get(i - 1);
 *        this.toggle = function() {
 *          element(by.buttonText('ngRepeat Toggle ' + i)).click();
 *        };
 *        this.isOpen = function() {
 *          return parent.element(by.binding('ngRepeatToggle.isOpen() | json')).getText();
 *        };
 *        this.isOuterOpen = function() {
 *          return parent.element(by.binding('outerToggle.isOpen() | json')).getText();
 *        };
 *      };
 *      var NgRepeatToggles = function() {
 *        var toggles = [1,2,3].map(function(i) { return new NgRepeatToggle(i); });
 *        this.forEach = function(fn) {
 *          toggles.forEach(fn);
 *        };
 *        this.isOuterOpen = function(i) {
 *          return toggles[i - 1].isOuterOpen();
 *        };
 *      };
 *      var NgIfToggle = function() {
 *        var parent = element(by.css('[ng-if]'));
 *        this.toggle = function() {
 *          element(by.buttonText('ngIf Toggle')).click();
 *        };
 *        this.isOpen = function() {
 *          return by.binding('ngIfToggle.isOpen() | json').getText();
 *        };
 *        this.isOuterOpen = function() {
 *          return parent.element(by.binding('outerToggle.isOpen() | json')).getText();
 *        };
 *      };
 *
 *      it('should toggle the outer toggle', function() {
 *        var outerToggle = new OuterToggle();
 *        expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): false');
 *        outerToggle.toggle();
 *        expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): true');
 *      });
 *
 *      it('should toggle all outer toggles', function() {
 *        var outerToggle = new OuterToggle();
 *        var repeatToggles = new NgRepeatToggles();
 *        var ifToggle = new NgIfToggle();
 *        expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): false');
 *        expect(repeatToggles.isOuterOpen(1)).toEqual('outerToggle.isOpen(): false');
 *        expect(repeatToggles.isOuterOpen(2)).toEqual('outerToggle.isOpen(): false');
 *        expect(repeatToggles.isOuterOpen(3)).toEqual('outerToggle.isOpen(): false');
 *        expect(ifToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): false');
 *        outerToggle.toggle();
 *        expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): true');
 *        expect(repeatToggles.isOuterOpen(1)).toEqual('outerToggle.isOpen(): true');
 *        expect(repeatToggles.isOuterOpen(2)).toEqual('outerToggle.isOpen(): true');
 *        expect(repeatToggles.isOuterOpen(3)).toEqual('outerToggle.isOpen(): true');
 *        expect(ifToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): true');
 *      });
 *
 *      it('should toggle each repeat iteration separately', function() {
 *        var repeatToggles = new NgRepeatToggles();
 *
 *        repeatToggles.forEach(function(repeatToggle) {
 *          expect(repeatToggle.isOpen()).toEqual('ngRepeatToggle.isOpen(): false');
 *          expect(repeatToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): false');
 *          repeatToggle.toggle();
 *          expect(repeatToggle.isOpen()).toEqual('ngRepeatToggle.isOpen(): true');
 *          expect(repeatToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): false');
 *        });
 *      });
 *   </file>
 * </example>
 *
 */

var ngRefMinErr = minErr('ngRef');

var ngRefDirective = ['$parse', function($parse) {
  return {
    priority: -1, // Needed for compatibility with element transclusion on the same element
    restrict: 'A',
    compile: function(tElement, tAttrs) {
      // Get the expected controller name, converts <data-some-thing> into "someThing"
      var controllerName = directiveNormalize(nodeName_(tElement));

      // Get the expression for value binding
      var getter = $parse(tAttrs.ngRef);
      var setter = getter.assign || function() {
        throw ngRefMinErr('nonassign', 'Expression in ngRef="{0}" is non-assignable!', tAttrs.ngRef);
      };

      return function(scope, element, attrs) {
        var refValue;

        if (attrs.hasOwnProperty('ngRefRead')) {
          if (attrs.ngRefRead === '$element') {
            refValue = element;
          } else {
            refValue = element.data('$' + attrs.ngRefRead + 'Controller');

            if (!refValue) {
              throw ngRefMinErr(
                'noctrl',
                'The controller for ngRefRead="{0}" could not be found on ngRef="{1}"',
                attrs.ngRefRead,
                tAttrs.ngRef
              );
            }
          }
        } else {
          refValue = element.data('$' + controllerName + 'Controller');
        }

        refValue = refValue || element;

        setter(scope, refValue);

        // when the element is removed, remove it (nullify it)
        element.on('$destroy', function() {
          // only remove it if value has not changed,
          // because animations (and other procedures) may duplicate elements
          if (getter(scope) === refValue) {
            setter(scope, null);
          }
        });
      };
    }
  };
}];

/* exported ngRepeatDirective */

/**
 * @ngdoc directive
 * @name ngRepeat
 * @multiElement
 * @restrict A
 *
 * @description
 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template
 * instance gets its own scope, where the given loop variable is set to the current collection item,
 * and `$index` is set to the item index or key.
 *
 * Special properties are exposed on the local scope of each template instance, including:
 *
 * | Variable  | Type            | Details                                                                     |
 * |-----------|-----------------|-----------------------------------------------------------------------------|
 * | `$index`  | {@type number}  | iterator offset of the repeated element (0..length-1)                       |
 * | `$first`  | {@type boolean} | true if the repeated element is first in the iterator.                      |
 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. |
 * | `$last`   | {@type boolean} | true if the repeated element is last in the iterator.                       |
 * | `$even`   | {@type boolean} | true if the iterator position `$index` is even (otherwise false).           |
 * | `$odd`    | {@type boolean} | true if the iterator position `$index` is odd (otherwise false).            |
 *
 * <div class="alert alert-info">
 *   Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}.
 *   This may be useful when, for instance, nesting ngRepeats.
 * </div>
 *
 *
 * ## Iterating over object properties
 *
 * It is possible to get `ngRepeat` to iterate over the properties of an object using the following
 * syntax:
 *
 * ```js
 * <div ng-repeat="(key, value) in myObj"> ... </div>
 * ```
 *
 * However, there are a few limitations compared to array iteration:
 *
 * - The JavaScript specification does not define the order of keys
 *   returned for an object, so AngularJS relies on the order returned by the browser
 *   when running `for key in myObj`. Browsers generally follow the strategy of providing
 *   keys in the order in which they were defined, although there are exceptions when keys are deleted
 *   and reinstated. See the
 *   [MDN page on `delete` for more info](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_notes).
 *
 * - `ngRepeat` will silently *ignore* object keys starting with `$`, because
 *   it's a prefix used by AngularJS for public (`$`) and private (`$$`) properties.
 *
 * - The built-in filters {@link ng.orderBy orderBy} and {@link ng.filter filter} do not work with
 *   objects, and will throw an error if used with one.
 *
 * If you are hitting any of these limitations, the recommended workaround is to convert your object into an array
 * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could
 * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter)
 * or implement a `$watch` on the object yourself.
 *
 *
 * ## Tracking and Duplicates
 *
 * `ngRepeat` uses {@link $rootScope.Scope#$watchCollection $watchCollection} to detect changes in
 * the collection. When a change happens, `ngRepeat` then makes the corresponding changes to the DOM:
 *
 * * When an item is added, a new instance of the template is added to the DOM.
 * * When an item is removed, its template instance is removed from the DOM.
 * * When items are reordered, their respective templates are reordered in the DOM.
 *
 * To minimize creation of DOM elements, `ngRepeat` uses a function
 * to "keep track" of all items in the collection and their corresponding DOM elements.
 * For example, if an item is added to the collection, `ngRepeat` will know that all other items
 * already have DOM elements, and will not re-render them.
 *
 * All different types of tracking functions, their syntax, and their support for duplicate
 * items in collections can be found in the
 * {@link ngRepeat#ngRepeat-arguments ngRepeat expression description}.
 *
 * <div class="alert alert-success">
 * **Best Practice:** If you are working with objects that have a unique identifier property, you
 * should track by this identifier instead of the object instance,
 * e.g. `item in items track by item.id`.
 * Should you reload your data later, `ngRepeat` will not have to rebuild the DOM elements for items
 * it has already rendered, even if the JavaScript objects in the collection have been substituted
 * for new ones. For large collections, this significantly improves rendering performance.
 * </div>
 *
 * ### Effects of DOM Element re-use
 *
 * When DOM elements are re-used, ngRepeat updates the scope for the element, which will
 * automatically update any active bindings on the template. However, other
 * functionality will not be updated, because the element is not re-created:
 *
 * - Directives are not re-compiled
 * - {@link guide/expression#one-time-binding one-time expressions} on the repeated template are not
 * updated if they have stabilized.
 *
 * The above affects all kinds of element re-use due to tracking, but may be especially visible
 * when tracking by `$index` due to the way ngRepeat re-uses elements.
 *
 * The following example shows the effects of different actions with tracking:

  <example module="ngRepeat" name="ngRepeat-tracking" deps="angular-animate.js" animations="true">
    <file name="script.js">
      angular.module('ngRepeat', ['ngAnimate']).controller('repeatController', function($scope) {
        var friends = [
          {name:'John', age:25},
          {name:'Mary', age:40},
          {name:'Peter', age:85}
        ];

        $scope.removeFirst = function() {
          $scope.friends.shift();
        };

        $scope.updateAge = function() {
          $scope.friends.forEach(function(el) {
            el.age = el.age + 5;
          });
        };

        $scope.copy = function() {
          $scope.friends = angular.copy($scope.friends);
        };

        $scope.reset = function() {
          $scope.friends = angular.copy(friends);
        };

        $scope.reset();
      });
    </file>
    <file name="index.html">
      <div ng-controller="repeatController">
        <ol>
          <li>When you click "Update Age", only the first list updates the age, because all others have
          a one-time binding on the age property. If you then click "Copy", the current friend list
          is copied, and now the second list updates the age, because the identity of the collection items
          has changed and the list must be re-rendered. The 3rd and 4th list stay the same, because all the
          items are already known according to their tracking functions.
          </li>
          <li>When you click "Remove First", the 4th list has the wrong age on both remaining items. This is
          due to tracking by $index: when the first collection item is removed, ngRepeat reuses the first
          DOM element for the new first collection item, and so on. Since the age property is one-time
          bound, the value remains from the collection item which was previously at this index.
          </li>
        </ol>

        <button ng-click="removeFirst()">Remove First</button>
        <button ng-click="updateAge()">Update Age</button>
        <button ng-click="copy()">Copy</button>
        <br><button ng-click="reset()">Reset List</button>
        <br>
        <code>track by $id(friend)</code> (default):
        <ul class="example-animate-container">
          <li class="animate-repeat" ng-repeat="friend in friends">
            {{friend.name}} is {{friend.age}} years old.
          </li>
        </ul>
        <code>track by $id(friend)</code> (default), with age one-time binding:
        <ul class="example-animate-container">
          <li class="animate-repeat" ng-repeat="friend in friends">
            {{friend.name}} is {{::friend.age}} years old.
          </li>
        </ul>
        <code>track by friend.name</code>, with age one-time binding:
        <ul class="example-animate-container">
          <li class="animate-repeat" ng-repeat="friend in friends track by friend.name">
            {{friend.name}}  is {{::friend.age}} years old.
          </li>
        </ul>
        <code>track by $index</code>, with age one-time binding:
        <ul class="example-animate-container">
          <li class="animate-repeat" ng-repeat="friend in friends track by $index">
            {{friend.name}} is {{::friend.age}} years old.
          </li>
        </ul>
      </div>
    </file>
    <file name="animations.css">
      .example-animate-container {
        background:white;
        border:1px solid black;
        list-style:none;
        margin:0;
        padding:0 10px;
      }

      .animate-repeat {
        line-height:30px;
        list-style:none;
        box-sizing:border-box;
      }

      .animate-repeat.ng-move,
      .animate-repeat.ng-enter,
      .animate-repeat.ng-leave {
        transition:all linear 0.5s;
      }

      .animate-repeat.ng-leave.ng-leave-active,
      .animate-repeat.ng-move,
      .animate-repeat.ng-enter {
        opacity:0;
        max-height:0;
      }

      .animate-repeat.ng-leave,
      .animate-repeat.ng-move.ng-move-active,
      .animate-repeat.ng-enter.ng-enter-active {
        opacity:1;
        max-height:30px;
      }
    </file>
  </example>

 *
 * ## Special repeat start and end points
 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending
 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively.
 * The **ng-repeat-start** directive works the same as **ng-repeat**, but will repeat all the HTML code (including the tag it's defined on)
 * up to and including the ending HTML tag where **ng-repeat-end** is placed.
 *
 * The example below makes use of this feature:
 * ```html
 *   <header ng-repeat-start="item in items">
 *     Header {{ item }}
 *   </header>
 *   <div class="body">
 *     Body {{ item }}
 *   </div>
 *   <footer ng-repeat-end>
 *     Footer {{ item }}
 *   </footer>
 * ```
 *
 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to:
 * ```html
 *   <header>
 *     Header A
 *   </header>
 *   <div class="body">
 *     Body A
 *   </div>
 *   <footer>
 *     Footer A
 *   </footer>
 *   <header>
 *     Header B
 *   </header>
 *   <div class="body">
 *     Body B
 *   </div>
 *   <footer>
 *     Footer B
 *   </footer>
 * ```
 *
 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such
 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**).
 *
 * @animations
 * | Animation                        | Occurs                              |
 * |----------------------------------|-------------------------------------|
 * | {@link ng.$animate#enter enter} | when a new item is added to the list or when an item is revealed after a filter |
 * | {@link ng.$animate#leave leave} | when an item is removed from the list or when an item is filtered out |
 * | {@link ng.$animate#move move } | when an adjacent item is filtered out causing a reorder or when the item contents are reordered |
 *
 * See the example below for defining CSS animations with ngRepeat.
 *
 * @element ANY
 * @scope
 * @priority 1000
 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These
 *   formats are currently supported:
 *
 *   * `variable in expression` – where variable is the user defined loop variable and `expression`
 *     is a scope expression giving the collection to enumerate.
 *
 *     For example: `album in artist.albums`.
 *
 *   * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers,
 *     and `expression` is the scope expression giving the collection to enumerate.
 *
 *     For example: `(name, age) in {'adam':10, 'amalie':12}`.
 *
 *   * `variable in expression track by tracking_expression` – You can also provide an optional tracking expression
 *     which can be used to associate the objects in the collection with the DOM elements. If no tracking expression
 *     is specified, ng-repeat associates elements by identity. It is an error to have
 *     more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are
 *     mapped to the same DOM element, which is not possible.)
 *
 *     *Default tracking: $id()*: `item in items` is equivalent to `item in items track by $id(item)`.
 *     This implies that the DOM elements will be associated by item identity in the collection.
 *
 *     The built-in `$id()` function can be used to assign a unique
 *     `$$hashKey` property to each item in the collection. This property is then used as a key to associated DOM elements
 *     with the corresponding item in the collection by identity. Moving the same object would move
 *     the DOM element in the same way in the DOM.
 *     Note that the default id function does not support duplicate primitive values (`number`, `string`),
 *     but supports duplictae non-primitive values (`object`) that are *equal* in shape.
 *
 *     *Custom Expression*: It is possible to use any AngularJS expression to compute the tracking
 *     id, for example with a function, or using a property on the collection items.
 *     `item in items track by item.id` is a typical pattern when the items have a unique identifier,
 *     e.g. database id. In this case the object identity does not matter. Two objects are considered
 *     equivalent as long as their `id` property is same.
 *     Tracking by unique identifier is the most performant way and should be used whenever possible.
 *
 *     *$index*: This special property tracks the collection items by their index, and
 *     re-uses the DOM elements that match that index, e.g. `item in items track by $index`. This can
 *     be used for a performance improvement if no unique identfier is available and the identity of
 *     the collection items cannot be easily computed. It also allows duplicates.
 *
 *     <div class="alert alert-warning">
 *       <strong>Note:</strong> Re-using DOM elements can have unforeseen effects. Read the
 *       {@link ngRepeat#tracking-and-duplicates section on tracking and duplicates} for
 *       more info.
 *     </div>
 *
 *     <div class="alert alert-warning">
 *       <strong>Note:</strong> the `track by` expression must come last - after any filters, and the alias expression:
 *       `item in items | filter:searchText as results  track by item.id`
 *     </div>
 *
 *   * `variable in expression as alias_expression` – You can also provide an optional alias expression which will then store the
 *     intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message
 *     when a filter is active on the repeater, but the filtered result set is empty.
 *
 *     For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after
 *     the items have been processed through the filter.
 *
 *     Please note that `as [variable name]` is not an operator but rather a part of ngRepeat
 *     micro-syntax so it can be used only after all filters (and not as operator, inside an expression).
 *
 *     For example: `item in items | filter : x | orderBy : order | limitTo : limit as results track by item.id` .
 *
 * @example
 * This example uses `ngRepeat` to display a list of people. A filter is used to restrict the displayed
 * results by name or by age. New (entering) and removed (leaving) items are animated.
  <example module="ngRepeat" name="ngRepeat" deps="angular-animate.js" animations="true">
    <file name="index.html">
      <div ng-controller="repeatController">
        I have {{friends.length}} friends. They are:
        <input type="search" ng-model="q" placeholder="filter friends..." aria-label="filter friends" />
        <ul class="example-animate-container">
          <li class="animate-repeat" ng-repeat="friend in friends | filter:q as results track by friend.name">
            [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
          </li>
          <li class="animate-repeat" ng-if="results.length === 0">
            <strong>No results found...</strong>
          </li>
        </ul>
      </div>
    </file>
    <file name="script.js">
      angular.module('ngRepeat', ['ngAnimate']).controller('repeatController', function($scope) {
        $scope.friends = [
          {name:'John', age:25, gender:'boy'},
          {name:'Jessie', age:30, gender:'girl'},
          {name:'Johanna', age:28, gender:'girl'},
          {name:'Joy', age:15, gender:'girl'},
          {name:'Mary', age:28, gender:'girl'},
          {name:'Peter', age:95, gender:'boy'},
          {name:'Sebastian', age:50, gender:'boy'},
          {name:'Erika', age:27, gender:'girl'},
          {name:'Patrick', age:40, gender:'boy'},
          {name:'Samantha', age:60, gender:'girl'}
        ];
      });
    </file>
    <file name="animations.css">
      .example-animate-container {
        background:white;
        border:1px solid black;
        list-style:none;
        margin:0;
        padding:0 10px;
      }

      .animate-repeat {
        line-height:30px;
        list-style:none;
        box-sizing:border-box;
      }

      .animate-repeat.ng-move,
      .animate-repeat.ng-enter,
      .animate-repeat.ng-leave {
        transition:all linear 0.5s;
      }

      .animate-repeat.ng-leave.ng-leave-active,
      .animate-repeat.ng-move,
      .animate-repeat.ng-enter {
        opacity:0;
        max-height:0;
      }

      .animate-repeat.ng-leave,
      .animate-repeat.ng-move.ng-move-active,
      .animate-repeat.ng-enter.ng-enter-active {
        opacity:1;
        max-height:30px;
      }
    </file>
    <file name="protractor.js" type="protractor">
      var friends = element.all(by.repeater('friend in friends'));

      it('should render initial data set', function() {
        expect(friends.count()).toBe(10);
        expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.');
        expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.');
        expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.');
        expect(element(by.binding('friends.length')).getText())
            .toMatch("I have 10 friends. They are:");
      });

       it('should update repeater when filter predicate changes', function() {
         expect(friends.count()).toBe(10);

         element(by.model('q')).sendKeys('ma');

         expect(friends.count()).toBe(2);
         expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.');
         expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.');
       });
      </file>
    </example>
 */
var ngRepeatDirective = ['$parse', '$animate', '$compile', function($parse, $animate, $compile) {
  var NG_REMOVED = '$$NG_REMOVED';
  var ngRepeatMinErr = minErr('ngRepeat');

  var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) {
    // TODO(perf): generate setters to shave off ~40ms or 1-1.5%
    scope[valueIdentifier] = value;
    if (keyIdentifier) scope[keyIdentifier] = key;
    scope.$index = index;
    scope.$first = (index === 0);
    scope.$last = (index === (arrayLength - 1));
    scope.$middle = !(scope.$first || scope.$last);
    // eslint-disable-next-line no-bitwise
    scope.$odd = !(scope.$even = (index & 1) === 0);
  };

  var getBlockStart = function(block) {
    return block.clone[0];
  };

  var getBlockEnd = function(block) {
    return block.clone[block.clone.length - 1];
  };

  var trackByIdArrayFn = function($scope, key, value) {
    return hashKey(value);
  };

  var trackByIdObjFn = function($scope, key) {
    return key;
  };

  return {
    restrict: 'A',
    multiElement: true,
    transclude: 'element',
    priority: 1000,
    terminal: true,
    $$tlb: true,
    compile: function ngRepeatCompile($element, $attr) {
      var expression = $attr.ngRepeat;
      var ngRepeatEndComment = $compile.$$createComment('end ngRepeat', expression);

      var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);

      if (!match) {
        throw ngRepeatMinErr('iexp', 'Expected expression in form of \'_item_ in _collection_[ track by _id_]\' but got \'{0}\'.',
            expression);
      }

      var lhs = match[1];
      var rhs = match[2];
      var aliasAs = match[3];
      var trackByExp = match[4];

      match = lhs.match(/^(?:(\s*[$\w]+)|\(\s*([$\w]+)\s*,\s*([$\w]+)\s*\))$/);

      if (!match) {
        throw ngRepeatMinErr('iidexp', '\'_item_\' in \'_item_ in _collection_\' should be an identifier or \'(_key_, _value_)\' expression, but got \'{0}\'.',
            lhs);
      }
      var valueIdentifier = match[3] || match[1];
      var keyIdentifier = match[2];

      if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) ||
          /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) {
        throw ngRepeatMinErr('badident', 'alias \'{0}\' is invalid --- must be a valid JS identifier which is not a reserved name.',
          aliasAs);
      }

      var trackByIdExpFn;

      if (trackByExp) {
        var hashFnLocals = {$id: hashKey};
        var trackByExpGetter = $parse(trackByExp);

        trackByIdExpFn = function($scope, key, value, index) {
          // assign key, value, and $index to the locals so that they can be used in hash functions
          if (keyIdentifier) hashFnLocals[keyIdentifier] = key;
          hashFnLocals[valueIdentifier] = value;
          hashFnLocals.$index = index;
          return trackByExpGetter($scope, hashFnLocals);
        };
      }

      return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) {

        // Store a list of elements from previous run. This is a hash where key is the item from the
        // iterator, and the value is objects with following properties.
        //   - scope: bound scope
        //   - clone: previous element.
        //   - index: position
        //
        // We are using no-proto object so that we don't need to guard against inherited props via
        // hasOwnProperty.
        var lastBlockMap = createMap();

        //watch props
        $scope.$watchCollection(rhs, function ngRepeatAction(collection) {
          var index, length,
              previousNode = $element[0],     // node that cloned nodes should be inserted after
                                              // initialized to the comment node anchor
              nextNode,
              // Same as lastBlockMap but it has the current state. It will become the
              // lastBlockMap on the next iteration.
              nextBlockMap = createMap(),
              collectionLength,
              key, value, // key/value of iteration
              trackById,
              trackByIdFn,
              collectionKeys,
              block,       // last object information {scope, element, id}
              nextBlockOrder,
              elementsToRemove;

          if (aliasAs) {
            $scope[aliasAs] = collection;
          }

          if (isArrayLike(collection)) {
            collectionKeys = collection;
            trackByIdFn = trackByIdExpFn || trackByIdArrayFn;
          } else {
            trackByIdFn = trackByIdExpFn || trackByIdObjFn;
            // if object, extract keys, in enumeration order, unsorted
            collectionKeys = [];
            for (var itemKey in collection) {
              if (hasOwnProperty.call(collection, itemKey) && itemKey.charAt(0) !== '$') {
                collectionKeys.push(itemKey);
              }
            }
          }

          collectionLength = collectionKeys.length;
          nextBlockOrder = new Array(collectionLength);

          // locate existing items
          for (index = 0; index < collectionLength; index++) {
            key = (collection === collectionKeys) ? index : collectionKeys[index];
            value = collection[key];
            trackById = trackByIdFn($scope, key, value, index);
            if (lastBlockMap[trackById]) {
              // found previously seen block
              block = lastBlockMap[trackById];
              delete lastBlockMap[trackById];
              nextBlockMap[trackById] = block;
              nextBlockOrder[index] = block;
            } else if (nextBlockMap[trackById]) {
              // if collision detected. restore lastBlockMap and throw an error
              forEach(nextBlockOrder, function(block) {
                if (block && block.scope) lastBlockMap[block.id] = block;
              });
              throw ngRepeatMinErr('dupes',
                  'Duplicates in a repeater are not allowed. Use \'track by\' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}',
                  expression, trackById, value);
            } else {
              // new never before seen block
              nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined};
              nextBlockMap[trackById] = true;
            }
          }

          // Clear the value property from the hashFnLocals object to prevent a reference to the last value
          // being leaked into the ngRepeatCompile function scope
          if (hashFnLocals) {
            hashFnLocals[valueIdentifier] = undefined;
          }

          // remove leftover items
          for (var blockKey in lastBlockMap) {
            block = lastBlockMap[blockKey];
            elementsToRemove = getBlockNodes(block.clone);
            $animate.leave(elementsToRemove);
            if (elementsToRemove[0].parentNode) {
              // if the element was not removed yet because of pending animation, mark it as deleted
              // so that we can ignore it later
              for (index = 0, length = elementsToRemove.length; index < length; index++) {
                elementsToRemove[index][NG_REMOVED] = true;
              }
            }
            block.scope.$destroy();
          }

          // we are not using forEach for perf reasons (trying to avoid #call)
          for (index = 0; index < collectionLength; index++) {
            key = (collection === collectionKeys) ? index : collectionKeys[index];
            value = collection[key];
            block = nextBlockOrder[index];

            if (block.scope) {
              // if we have already seen this object, then we need to reuse the
              // associated scope/element

              nextNode = previousNode;

              // skip nodes that are already pending removal via leave animation
              do {
                nextNode = nextNode.nextSibling;
              } while (nextNode && nextNode[NG_REMOVED]);

              if (getBlockStart(block) !== nextNode) {
                // existing item which got moved
                $animate.move(getBlockNodes(block.clone), null, previousNode);
              }
              previousNode = getBlockEnd(block);
              updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
            } else {
              // new item which we don't know about
              $transclude(function ngRepeatTransclude(clone, scope) {
                block.scope = scope;
                // http://jsperf.com/clone-vs-createcomment
                var endNode = ngRepeatEndComment.cloneNode(false);
                clone[clone.length++] = endNode;

                $animate.enter(clone, null, previousNode);
                previousNode = endNode;
                // Note: We only need the first/last node of the cloned nodes.
                // However, we need to keep the reference to the jqlite wrapper as it might be changed later
                // by a directive with templateUrl when its template arrives.
                block.clone = clone;
                nextBlockMap[block.id] = block;
                updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength);
              });
            }
          }
          lastBlockMap = nextBlockMap;
        });
      };
    }
  };
}];

var NG_HIDE_CLASS = 'ng-hide';
var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
/**
 * @ngdoc directive
 * @name ngShow
 * @multiElement
 *
 * @description
 * The `ngShow` directive shows or hides the given HTML element based on the expression provided to
 * the `ngShow` attribute.
 *
 * The element is shown or hidden by removing or adding the `.ng-hide` CSS class onto the element.
 * The `.ng-hide` CSS class is predefined in AngularJS and sets the display style to none (using an
 * `!important` flag). For CSP mode please add `angular-csp.css` to your HTML file (see
 * {@link ng.directive:ngCsp ngCsp}).
 *
 * ```html
 * <!-- when $scope.myValue is truthy (element is visible) -->
 * <div ng-show="myValue"></div>
 *
 * <!-- when $scope.myValue is falsy (element is hidden) -->
 * <div ng-show="myValue" class="ng-hide"></div>
 * ```
 *
 * When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added
 * to the class attribute on the element causing it to become hidden. When truthy, the `.ng-hide`
 * CSS class is removed from the element causing the element not to appear hidden.
 *
 * ## Why is `!important` used?
 *
 * You may be wondering why `!important` is used for the `.ng-hide` CSS class. This is because the
 * `.ng-hide` selector can be easily overridden by heavier selectors. For example, something as
 * simple as changing the display style on a HTML list item would make hidden elements appear
 * visible. This also becomes a bigger issue when dealing with CSS frameworks.
 *
 * By using `!important`, the show and hide behavior will work as expected despite any clash between
 * CSS selector specificity (when `!important` isn't used with any conflicting styles). If a
 * developer chooses to override the styling to change how to hide an element then it is just a
 * matter of using `!important` in their own CSS code.
 *
 * ### Overriding `.ng-hide`
 *
 * By default, the `.ng-hide` class will style the element with `display: none !important`. If you
 * wish to change the hide behavior with `ngShow`/`ngHide`, you can simply overwrite the styles for
 * the `.ng-hide` CSS class. Note that the selector that needs to be used is actually
 * `.ng-hide:not(.ng-hide-animate)` to cope with extra animation classes that can be added.
 *
 * ```css
 * .ng-hide:not(.ng-hide-animate) {
 *   /&#42; These are just alternative ways of hiding an element &#42;/
 *   display: block!important;
 *   position: absolute;
 *   top: -9999px;
 *   left: -9999px;
 * }
 * ```
 *
 * By default you don't need to override anything in CSS and the animations will work around the
 * display style.
 *
 * @animations
 * | Animation                                           | Occurs                                                                                                        |
 * |-----------------------------------------------------|---------------------------------------------------------------------------------------------------------------|
 * | {@link $animate#addClass addClass} `.ng-hide`       | After the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden. |
 * | {@link $animate#removeClass removeClass} `.ng-hide` | After the `ngShow` expression evaluates to a truthy value and just before contents are set to visible.        |
 *
 * Animations in `ngShow`/`ngHide` work with the show and hide events that are triggered when the
 * directive expression is true and false. This system works like the animation system present with
 * `ngClass` except that you must also include the `!important` flag to override the display
 * property so that the elements are not actually hidden during the animation.
 *
 * ```css
 * /&#42; A working example can be found at the bottom of this page. &#42;/
 * .my-element.ng-hide-add, .my-element.ng-hide-remove {
 *   transition: all 0.5s linear;
 * }
 *
 * .my-element.ng-hide-add { ... }
 * .my-element.ng-hide-add.ng-hide-add-active { ... }
 * .my-element.ng-hide-remove { ... }
 * .my-element.ng-hide-remove.ng-hide-remove-active { ... }
 * ```
 *
 * Keep in mind that, as of AngularJS version 1.3, there is no need to change the display property
 * to block during animation states - ngAnimate will automatically handle the style toggling for you.
 *
 * @element ANY
 * @param {expression} ngShow If the {@link guide/expression expression} is truthy/falsy then the
 *                            element is shown/hidden respectively.
 *
 * @example
 * A simple example, animating the element's opacity:
 *
  <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-show-simple">
    <file name="index.html">
      Show: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br />
      <div class="check-element animate-show-hide" ng-show="checked">
        I show up when your checkbox is checked.
      </div>
    </file>
    <file name="animations.css">
      .animate-show-hide.ng-hide {
        opacity: 0;
      }

      .animate-show-hide.ng-hide-add,
      .animate-show-hide.ng-hide-remove {
        transition: all linear 0.5s;
      }

      .check-element {
        border: 1px solid black;
        opacity: 1;
        padding: 10px;
      }
    </file>
    <file name="protractor.js" type="protractor">
      it('should check ngShow', function() {
        var checkbox = element(by.model('checked'));
        var checkElem = element(by.css('.check-element'));

        expect(checkElem.isDisplayed()).toBe(false);
        checkbox.click();
        expect(checkElem.isDisplayed()).toBe(true);
      });
    </file>
  </example>
 *
 * <hr />
 * @example
 * A more complex example, featuring different show/hide animations:
 *
  <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-show-complex">
    <file name="index.html">
      Show: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br />
      <div class="check-element funky-show-hide" ng-show="checked">
        I show up when your checkbox is checked.
      </div>
    </file>
    <file name="animations.css">
      body {
        overflow: hidden;
        perspective: 1000px;
      }

      .funky-show-hide.ng-hide-add {
        transform: rotateZ(0);
        transform-origin: right;
        transition: all 0.5s ease-in-out;
      }

      .funky-show-hide.ng-hide-add.ng-hide-add-active {
        transform: rotateZ(-135deg);
      }

      .funky-show-hide.ng-hide-remove {
        transform: rotateY(90deg);
        transform-origin: left;
        transition: all 0.5s ease;
      }

      .funky-show-hide.ng-hide-remove.ng-hide-remove-active {
        transform: rotateY(0);
      }

      .check-element {
        border: 1px solid black;
        opacity: 1;
        padding: 10px;
      }
    </file>
    <file name="protractor.js" type="protractor">
      it('should check ngShow', function() {
        var checkbox = element(by.model('checked'));
        var checkElem = element(by.css('.check-element'));

        expect(checkElem.isDisplayed()).toBe(false);
        checkbox.click();
        expect(checkElem.isDisplayed()).toBe(true);
      });
    </file>
  </example>
 *
 * @knownIssue
 *
 * ### Flickering when using ngShow to toggle between elements
 *
 * When using {@link ngShow} and / or {@link ngHide} to toggle between elements, it can
 * happen that both the element to show and the element to hide are visible for a very short time.
 *
 * This usually happens when the {@link ngAnimate ngAnimate module} is included, but no actual animations
 * are defined for {@link ngShow} / {@link ngHide}. Internet Explorer is affected more often than
 * other browsers.
 *
 * There are several way to mitigate this problem:
 *
 * - {@link guide/animations#how-to-selectively-enable-disable-and-skip-animations Disable animations on the affected elements}.
 * - Use {@link ngIf} or {@link ngSwitch} instead of {@link ngShow} / {@link ngHide}.
 * - Use the special CSS selector `ng-hide.ng-hide-animate` to set `{display: none}` or similar on the affected elements.
 * - Use `ng-class="{'ng-hide': expression}` instead of instead of {@link ngShow} / {@link ngHide}.
 * - Define an animation on the affected elements.
 */
var ngShowDirective = ['$animate', function($animate) {
  return {
    restrict: 'A',
    multiElement: true,
    link: function(scope, element, attr) {
      scope.$watch(attr.ngShow, function ngShowWatchAction(value) {
        // we're adding a temporary, animation-specific class for ng-hide since this way
        // we can control when the element is actually displayed on screen without having
        // to have a global/greedy CSS selector that breaks when other animations are run.
        // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845
        $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, {
          tempClasses: NG_HIDE_IN_PROGRESS_CLASS
        });
      });
    }
  };
}];


/**
 * @ngdoc directive
 * @name ngHide
 * @multiElement
 *
 * @description
 * The `ngHide` directive shows or hides the given HTML element based on the expression provided to
 * the `ngHide` attribute.
 *
 * The element is shown or hidden by removing or adding the `.ng-hide` CSS class onto the element.
 * The `.ng-hide` CSS class is predefined in AngularJS and sets the display style to none (using an
 * `!important` flag). For CSP mode please add `angular-csp.css` to your HTML file (see
 * {@link ng.directive:ngCsp ngCsp}).
 *
 * ```html
 * <!-- when $scope.myValue is truthy (element is hidden) -->
 * <div ng-hide="myValue" class="ng-hide"></div>
 *
 * <!-- when $scope.myValue is falsy (element is visible) -->
 * <div ng-hide="myValue"></div>
 * ```
 *
 * When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added
 * to the class attribute on the element causing it to become hidden. When falsy, the `.ng-hide`
 * CSS class is removed from the element causing the element not to appear hidden.
 *
 * ## Why is `!important` used?
 *
 * You may be wondering why `!important` is used for the `.ng-hide` CSS class. This is because the
 * `.ng-hide` selector can be easily overridden by heavier selectors. For example, something as
 * simple as changing the display style on a HTML list item would make hidden elements appear
 * visible. This also becomes a bigger issue when dealing with CSS frameworks.
 *
 * By using `!important`, the show and hide behavior will work as expected despite any clash between
 * CSS selector specificity (when `!important` isn't used with any conflicting styles). If a
 * developer chooses to override the styling to change how to hide an element then it is just a
 * matter of using `!important` in their own CSS code.
 *
 * ### Overriding `.ng-hide`
 *
 * By default, the `.ng-hide` class will style the element with `display: none !important`. If you
 * wish to change the hide behavior with `ngShow`/`ngHide`, you can simply overwrite the styles for
 * the `.ng-hide` CSS class. Note that the selector that needs to be used is actually
 * `.ng-hide:not(.ng-hide-animate)` to cope with extra animation classes that can be added.
 *
 * ```css
 * .ng-hide:not(.ng-hide-animate) {
 *   /&#42; These are just alternative ways of hiding an element &#42;/
 *   display: block!important;
 *   position: absolute;
 *   top: -9999px;
 *   left: -9999px;
 * }
 * ```
 *
 * By default you don't need to override in CSS anything and the animations will work around the
 * display style.
 *
 * @animations
 * | Animation                                           | Occurs                                                                                                     |
 * |-----------------------------------------------------|------------------------------------------------------------------------------------------------------------|
 * | {@link $animate#addClass addClass} `.ng-hide`       | After the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden.  |
 * | {@link $animate#removeClass removeClass} `.ng-hide` | After the `ngHide` expression evaluates to a non truthy value and just before contents are set to visible. |
 *
 * Animations in `ngShow`/`ngHide` work with the show and hide events that are triggered when the
 * directive expression is true and false. This system works like the animation system present with
 * `ngClass` except that you must also include the `!important` flag to override the display
 * property so that the elements are not actually hidden during the animation.
 *
 * ```css
 * /&#42; A working example can be found at the bottom of this page. &#42;/
 * .my-element.ng-hide-add, .my-element.ng-hide-remove {
 *   transition: all 0.5s linear;
 * }
 *
 * .my-element.ng-hide-add { ... }
 * .my-element.ng-hide-add.ng-hide-add-active { ... }
 * .my-element.ng-hide-remove { ... }
 * .my-element.ng-hide-remove.ng-hide-remove-active { ... }
 * ```
 *
 * Keep in mind that, as of AngularJS version 1.3, there is no need to change the display property
 * to block during animation states - ngAnimate will automatically handle the style toggling for you.
 *
 * @element ANY
 * @param {expression} ngHide If the {@link guide/expression expression} is truthy/falsy then the
 *                            element is hidden/shown respectively.
 *
 * @example
 * A simple example, animating the element's opacity:
 *
  <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-hide-simple">
    <file name="index.html">
      Hide: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br />
      <div class="check-element animate-show-hide" ng-hide="checked">
        I hide when your checkbox is checked.
      </div>
    </file>
    <file name="animations.css">
      .animate-show-hide.ng-hide {
        opacity: 0;
      }

      .animate-show-hide.ng-hide-add,
      .animate-show-hide.ng-hide-remove {
        transition: all linear 0.5s;
      }

      .check-element {
        border: 1px solid black;
        opacity: 1;
        padding: 10px;
      }
    </file>
    <file name="protractor.js" type="protractor">
      it('should check ngHide', function() {
        var checkbox = element(by.model('checked'));
        var checkElem = element(by.css('.check-element'));

        expect(checkElem.isDisplayed()).toBe(true);
        checkbox.click();
        expect(checkElem.isDisplayed()).toBe(false);
      });
    </file>
  </example>
 *
 * <hr />
 * @example
 * A more complex example, featuring different show/hide animations:
 *
  <example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-hide-complex">
    <file name="index.html">
      Hide: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br />
      <div class="check-element funky-show-hide" ng-hide="checked">
        I hide when your checkbox is checked.
      </div>
    </file>
    <file name="animations.css">
      body {
        overflow: hidden;
        perspective: 1000px;
      }

      .funky-show-hide.ng-hide-add {
        transform: rotateZ(0);
        transform-origin: right;
        transition: all 0.5s ease-in-out;
      }

      .funky-show-hide.ng-hide-add.ng-hide-add-active {
        transform: rotateZ(-135deg);
      }

      .funky-show-hide.ng-hide-remove {
        transform: rotateY(90deg);
        transform-origin: left;
        transition: all 0.5s ease;
      }

      .funky-show-hide.ng-hide-remove.ng-hide-remove-active {
        transform: rotateY(0);
      }

      .check-element {
        border: 1px solid black;
        opacity: 1;
        padding: 10px;
      }
    </file>
    <file name="protractor.js" type="protractor">
      it('should check ngHide', function() {
        var checkbox = element(by.model('checked'));
        var checkElem = element(by.css('.check-element'));

        expect(checkElem.isDisplayed()).toBe(true);
        checkbox.click();
        expect(checkElem.isDisplayed()).toBe(false);
      });
    </file>
  </example>
 *
 * @knownIssue
 *
 * ### Flickering when using ngHide to toggle between elements
 *
 * When using {@link ngShow} and / or {@link ngHide} to toggle between elements, it can
 * happen that both the element to show and the element to hide are visible for a very short time.
 *
 * This usually happens when the {@link ngAnimate ngAnimate module} is included, but no actual animations
 * are defined for {@link ngShow} / {@link ngHide}. Internet Explorer is affected more often than
 * other browsers.
 *
 * There are several way to mitigate this problem:
 *
 * - {@link guide/animations#how-to-selectively-enable-disable-and-skip-animations Disable animations on the affected elements}.
 * - Use {@link ngIf} or {@link ngSwitch} instead of {@link ngShow} / {@link ngHide}.
 * - Use the special CSS selector `ng-hide.ng-hide-animate` to set `{display: none}` or similar on the affected elements.
 * - Use `ng-class="{'ng-hide': expression}` instead of instead of {@link ngShow} / {@link ngHide}.
 * - Define an animation on the affected elements.
 */
var ngHideDirective = ['$animate', function($animate) {
  return {
    restrict: 'A',
    multiElement: true,
    link: function(scope, element, attr) {
      scope.$watch(attr.ngHide, function ngHideWatchAction(value) {
        // The comment inside of the ngShowDirective explains why we add and
        // remove a temporary class for the show/hide animation
        $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, {
          tempClasses: NG_HIDE_IN_PROGRESS_CLASS
        });
      });
    }
  };
}];

/**
 * @ngdoc directive
 * @name ngStyle
 * @restrict AC
 *
 * @description
 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally.
 *
 * @knownIssue
 * You should not use {@link guide/interpolation interpolation} in the value of the `style`
 * attribute, when using the `ngStyle` directive on the same element.
 * See {@link guide/interpolation#known-issues here} for more info.
 *
 * @element ANY
 * @param {expression} ngStyle
 *
 * {@link guide/expression Expression} which evals to an
 * object whose keys are CSS style names and values are corresponding values for those CSS
 * keys.
 *
 * Since some CSS style names are not valid keys for an object, they must be quoted.
 * See the 'background-color' style in the example below.
 *
 * @example
   <example name="ng-style">
     <file name="index.html">
        <input type="button" value="set color" ng-click="myStyle={color:'red'}">
        <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}">
        <input type="button" value="clear" ng-click="myStyle={}">
        <br/>
        <span ng-style="myStyle">Sample Text</span>
        <pre>myStyle={{myStyle}}</pre>
     </file>
     <file name="style.css">
       span {
         color: black;
       }
     </file>
     <file name="protractor.js" type="protractor">
       var colorSpan = element(by.css('span'));

       it('should check ng-style', function() {
         expect(colorSpan.getCssValue('color')).toMatch(/rgba\(0, 0, 0, 1\)|rgb\(0, 0, 0\)/);
         element(by.css('input[value=\'set color\']')).click();
         expect(colorSpan.getCssValue('color')).toMatch(/rgba\(255, 0, 0, 1\)|rgb\(255, 0, 0\)/);
         element(by.css('input[value=clear]')).click();
         expect(colorSpan.getCssValue('color')).toMatch(/rgba\(0, 0, 0, 1\)|rgb\(0, 0, 0\)/);
       });
     </file>
   </example>
 */
var ngStyleDirective = ngDirective(function(scope, element, attr) {
  scope.$watchCollection(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
    if (oldStyles && (newStyles !== oldStyles)) {
      forEach(oldStyles, function(val, style) { element.css(style, ''); });
    }
    if (newStyles) element.css(newStyles);
  });
});

/**
 * @ngdoc directive
 * @name ngSwitch
 * @restrict EA
 *
 * @description
 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression.
 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location
 * as specified in the template.
 *
 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it
 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element
 * matches the value obtained from the evaluated expression. In other words, you define a container element
 * (where you place the directive), place an expression on the **`on="..."` attribute**
 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place
 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on
 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default
 * attribute is displayed.
 *
 * <div class="alert alert-info">
 * Be aware that the attribute values to match against cannot be expressions. They are interpreted
 * as literal string values to match against.
 * For example, **`ng-switch-when="someVal"`** will match against the string `"someVal"` not against the
 * value of the expression `$scope.someVal`.
 * </div>

 * @animations
 * | Animation                        | Occurs                              |
 * |----------------------------------|-------------------------------------|
 * | {@link ng.$animate#enter enter}  | after the ngSwitch contents change and the matched child element is placed inside the container |
 * | {@link ng.$animate#leave leave}  | after the ngSwitch contents change and just before the former contents are removed from the DOM |
 *
 * @usage
 *
 * ```
 * <ANY ng-switch="expression">
 *   <ANY ng-switch-when="matchValue1">...</ANY>
 *   <ANY ng-switch-when="matchValue2">...</ANY>
 *   <ANY ng-switch-default>...</ANY>
 * </ANY>
 * ```
 *
 *
 * @scope
 * @priority 1200
 * @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>.
 * On child elements add:
 *
 * * `ngSwitchWhen`: the case statement to match against. If match then this
 *   case will be displayed. If the same match appears multiple times, all the
 *   elements will be displayed. It is possible to associate multiple values to
 *   the same `ngSwitchWhen` by defining the optional attribute
 *   `ngSwitchWhenSeparator`. The separator will be used to split the value of
 *   the `ngSwitchWhen` attribute into multiple tokens, and the element will show
 *   if any of the `ngSwitch` evaluates to any of these tokens.
 * * `ngSwitchDefault`: the default case when no other case match. If there
 *   are multiple default cases, all of them will be displayed when no other
 *   case match.
 *
 *
 * @example
  <example module="switchExample" deps="angular-animate.js" animations="true" name="ng-switch">
    <file name="index.html">
      <div ng-controller="ExampleController">
        <select ng-model="selection" ng-options="item for item in items">
        </select>
        <code>selection={{selection}}</code>
        <hr/>
        <div class="animate-switch-container"
          ng-switch on="selection">
            <div class="animate-switch" ng-switch-when="settings|options" ng-switch-when-separator="|">Settings Div</div>
            <div class="animate-switch" ng-switch-when="home">Home Span</div>
            <div class="animate-switch" ng-switch-default>default</div>
        </div>
      </div>
    </file>
    <file name="script.js">
      angular.module('switchExample', ['ngAnimate'])
        .controller('ExampleController', ['$scope', function($scope) {
          $scope.items = ['settings', 'home', 'options', 'other'];
          $scope.selection = $scope.items[0];
        }]);
    </file>
    <file name="animations.css">
      .animate-switch-container {
        position:relative;
        background:white;
        border:1px solid black;
        height:40px;
        overflow:hidden;
      }

      .animate-switch {
        padding:10px;
      }

      .animate-switch.ng-animate {
        transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;

        position:absolute;
        top:0;
        left:0;
        right:0;
        bottom:0;
      }

      .animate-switch.ng-leave.ng-leave-active,
      .animate-switch.ng-enter {
        top:-50px;
      }
      .animate-switch.ng-leave,
      .animate-switch.ng-enter.ng-enter-active {
        top:0;
      }
    </file>
    <file name="protractor.js" type="protractor">
      var switchElem = element(by.css('[ng-switch]'));
      var select = element(by.model('selection'));

      it('should start in settings', function() {
        expect(switchElem.getText()).toMatch(/Settings Div/);
      });
      it('should change to home', function() {
        select.all(by.css('option')).get(1).click();
        expect(switchElem.getText()).toMatch(/Home Span/);
      });
      it('should change to settings via "options"', function() {
        select.all(by.css('option')).get(2).click();
        expect(switchElem.getText()).toMatch(/Settings Div/);
      });
      it('should select default', function() {
        select.all(by.css('option')).get(3).click();
        expect(switchElem.getText()).toMatch(/default/);
      });
    </file>
  </example>
 */
var ngSwitchDirective = ['$animate', '$compile', function($animate, $compile) {
  return {
    require: 'ngSwitch',

    // asks for $scope to fool the BC controller module
    controller: ['$scope', function NgSwitchController() {
     this.cases = {};
    }],
    link: function(scope, element, attr, ngSwitchController) {
      var watchExpr = attr.ngSwitch || attr.on,
          selectedTranscludes = [],
          selectedElements = [],
          previousLeaveAnimations = [],
          selectedScopes = [];

      var spliceFactory = function(array, index) {
          return function(response) {
            if (response !== false) array.splice(index, 1);
          };
      };

      scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
        var i, ii;

        // Start with the last, in case the array is modified during the loop
        while (previousLeaveAnimations.length) {
          $animate.cancel(previousLeaveAnimations.pop());
        }

        for (i = 0, ii = selectedScopes.length; i < ii; ++i) {
          var selected = getBlockNodes(selectedElements[i].clone);
          selectedScopes[i].$destroy();
          var runner = previousLeaveAnimations[i] = $animate.leave(selected);
          runner.done(spliceFactory(previousLeaveAnimations, i));
        }

        selectedElements.length = 0;
        selectedScopes.length = 0;

        if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) {
          forEach(selectedTranscludes, function(selectedTransclude) {
            selectedTransclude.transclude(function(caseElement, selectedScope) {
              selectedScopes.push(selectedScope);
              var anchor = selectedTransclude.element;
              caseElement[caseElement.length++] = $compile.$$createComment('end ngSwitchWhen');
              var block = { clone: caseElement };

              selectedElements.push(block);
              $animate.enter(caseElement, anchor.parent(), anchor);
            });
          });
        }
      });
    }
  };
}];

var ngSwitchWhenDirective = ngDirective({
  transclude: 'element',
  priority: 1200,
  require: '^ngSwitch',
  multiElement: true,
  link: function(scope, element, attrs, ctrl, $transclude) {

    var cases = attrs.ngSwitchWhen.split(attrs.ngSwitchWhenSeparator).sort().filter(
      // Filter duplicate cases
      function(element, index, array) { return array[index - 1] !== element; }
    );

    forEach(cases, function(whenCase) {
      ctrl.cases['!' + whenCase] = (ctrl.cases['!' + whenCase] || []);
      ctrl.cases['!' + whenCase].push({ transclude: $transclude, element: element });
    });
  }
});

var ngSwitchDefaultDirective = ngDirective({
  transclude: 'element',
  priority: 1200,
  require: '^ngSwitch',
  multiElement: true,
  link: function(scope, element, attr, ctrl, $transclude) {
    ctrl.cases['?'] = (ctrl.cases['?'] || []);
    ctrl.cases['?'].push({ transclude: $transclude, element: element });
   }
});

/**
 * @ngdoc directive
 * @name ngTransclude
 * @restrict EAC
 *
 * @description
 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion.
 *
 * You can specify that you want to insert a named transclusion slot, instead of the default slot, by providing the slot name
 * as the value of the `ng-transclude` or `ng-transclude-slot` attribute.
 *
 * If the transcluded content is not empty (i.e. contains one or more DOM nodes, including whitespace text nodes), any existing
 * content of this element will be removed before the transcluded content is inserted.
 * If the transcluded content is empty (or only whitespace), the existing content is left intact. This lets you provide fallback
 * content in the case that no transcluded content is provided.
 *
 * @element ANY
 *
 * @param {string} ngTransclude|ngTranscludeSlot the name of the slot to insert at this point. If this is not provided, is empty
 *                                               or its value is the same as the name of the attribute then the default slot is used.
 *
 * @example
 * ### Basic transclusion
 * This example demonstrates basic transclusion of content into a component directive.
 * <example name="simpleTranscludeExample" module="transcludeExample">
 *   <file name="index.html">
 *     <script>
 *       angular.module('transcludeExample', [])
 *        .directive('pane', function(){
 *           return {
 *             restrict: 'E',
 *             transclude: true,
 *             scope: { title:'@' },
 *             template: '<div style="border: 1px solid black;">' +
 *                         '<div style="background-color: gray">{{title}}</div>' +
 *                         '<ng-transclude></ng-transclude>' +
 *                       '</div>'
 *           };
 *       })
 *       .controller('ExampleController', ['$scope', function($scope) {
 *         $scope.title = 'Lorem Ipsum';
 *         $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
 *       }]);
 *     </script>
 *     <div ng-controller="ExampleController">
 *       <input ng-model="title" aria-label="title"> <br/>
 *       <textarea ng-model="text" aria-label="text"></textarea> <br/>
 *       <pane title="{{title}}"><span>{{text}}</span></pane>
 *     </div>
 *   </file>
 *   <file name="protractor.js" type="protractor">
 *      it('should have transcluded', function() {
 *        var titleElement = element(by.model('title'));
 *        titleElement.clear();
 *        titleElement.sendKeys('TITLE');
 *        var textElement = element(by.model('text'));
 *        textElement.clear();
 *        textElement.sendKeys('TEXT');
 *        expect(element(by.binding('title')).getText()).toEqual('TITLE');
 *        expect(element(by.binding('text')).getText()).toEqual('TEXT');
 *      });
 *   </file>
 * </example>
 *
 * @example
 * ### Transclude fallback content
 * This example shows how to use `NgTransclude` with fallback content, that
 * is displayed if no transcluded content is provided.
 *
 * <example module="transcludeFallbackContentExample" name="ng-transclude">
 * <file name="index.html">
 * <script>
 * angular.module('transcludeFallbackContentExample', [])
 * .directive('myButton', function(){
 *             return {
 *               restrict: 'E',
 *               transclude: true,
 *               scope: true,
 *               template: '<button style="cursor: pointer;">' +
 *                           '<ng-transclude>' +
 *                             '<b style="color: red;">Button1</b>' +
 *                           '</ng-transclude>' +
 *                         '</button>'
 *             };
 *         });
 * </script>
 * <!-- fallback button content -->
 * <my-button id="fallback"></my-button>
 * <!-- modified button content -->
 * <my-button id="modified">
 *   <i style="color: green;">Button2</i>
 * </my-button>
 * </file>
 * <file name="protractor.js" type="protractor">
 * it('should have different transclude element content', function() {
 *          expect(element(by.id('fallback')).getText()).toBe('Button1');
 *          expect(element(by.id('modified')).getText()).toBe('Button2');
 *        });
 * </file>
 * </example>
 *
 * @example
 * ### Multi-slot transclusion
 * This example demonstrates using multi-slot transclusion in a component directive.
 * <example name="multiSlotTranscludeExample" module="multiSlotTranscludeExample">
 *   <file name="index.html">
 *    <style>
 *      .title, .footer {
 *        background-color: gray
 *      }
 *    </style>
 *    <div ng-controller="ExampleController">
 *      <input ng-model="title" aria-label="title"> <br/>
 *      <textarea ng-model="text" aria-label="text"></textarea> <br/>
 *      <pane>
 *        <pane-title><a ng-href="{{link}}">{{title}}</a></pane-title>
 *        <pane-body><p>{{text}}</p></pane-body>
 *      </pane>
 *    </div>
 *   </file>
 *   <file name="app.js">
 *    angular.module('multiSlotTranscludeExample', [])
 *     .directive('pane', function() {
 *        return {
 *          restrict: 'E',
 *          transclude: {
 *            'title': '?paneTitle',
 *            'body': 'paneBody',
 *            'footer': '?paneFooter'
 *          },
 *          template: '<div style="border: 1px solid black;">' +
 *                      '<div class="title" ng-transclude="title">Fallback Title</div>' +
 *                      '<div ng-transclude="body"></div>' +
 *                      '<div class="footer" ng-transclude="footer">Fallback Footer</div>' +
 *                    '</div>'
 *        };
 *    })
 *    .controller('ExampleController', ['$scope', function($scope) {
 *      $scope.title = 'Lorem Ipsum';
 *      $scope.link = 'https://google.com';
 *      $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
 *    }]);
 *   </file>
 *   <file name="protractor.js" type="protractor">
 *      it('should have transcluded the title and the body', function() {
 *        var titleElement = element(by.model('title'));
 *        titleElement.clear();
 *        titleElement.sendKeys('TITLE');
 *        var textElement = element(by.model('text'));
 *        textElement.clear();
 *        textElement.sendKeys('TEXT');
 *        expect(element(by.css('.title')).getText()).toEqual('TITLE');
 *        expect(element(by.binding('text')).getText()).toEqual('TEXT');
 *        expect(element(by.css('.footer')).getText()).toEqual('Fallback Footer');
 *      });
 *   </file>
 * </example>
 */
var ngTranscludeMinErr = minErr('ngTransclude');
var ngTranscludeDirective = ['$compile', function($compile) {
  return {
    restrict: 'EAC',
    compile: function ngTranscludeCompile(tElement) {

      // Remove and cache any original content to act as a fallback
      var fallbackLinkFn = $compile(tElement.contents());
      tElement.empty();

      return function ngTranscludePostLink($scope, $element, $attrs, controller, $transclude) {

        if (!$transclude) {
          throw ngTranscludeMinErr('orphan',
          'Illegal use of ngTransclude directive in the template! ' +
          'No parent directive that requires a transclusion found. ' +
          'Element: {0}',
          startingTag($element));
        }


        // If the attribute is of the form: `ng-transclude="ng-transclude"` then treat it like the default
        if ($attrs.ngTransclude === $attrs.$attr.ngTransclude) {
          $attrs.ngTransclude = '';
        }
        var slotName = $attrs.ngTransclude || $attrs.ngTranscludeSlot;

        // If the slot is required and no transclusion content is provided then this call will throw an error
        $transclude(ngTranscludeCloneAttachFn, null, slotName);

        // If the slot is optional and no transclusion content is provided then use the fallback content
        if (slotName && !$transclude.isSlotFilled(slotName)) {
          useFallbackContent();
        }

        function ngTranscludeCloneAttachFn(clone, transcludedScope) {
          if (clone.length && notWhitespace(clone)) {
            $element.append(clone);
          } else {
            useFallbackContent();
            // There is nothing linked against the transcluded scope since no content was available,
            // so it should be safe to clean up the generated scope.
            transcludedScope.$destroy();
          }
        }

        function useFallbackContent() {
          // Since this is the fallback content rather than the transcluded content,
          // we link against the scope of this directive rather than the transcluded scope
          fallbackLinkFn($scope, function(clone) {
            $element.append(clone);
          });
        }

        function notWhitespace(nodes) {
          for (var i = 0, ii = nodes.length; i < ii; i++) {
            var node = nodes[i];
            if (node.nodeType !== NODE_TYPE_TEXT || node.nodeValue.trim()) {
              return true;
            }
          }
        }
      };
    }
  };
}];

/**
 * @ngdoc directive
 * @name script
 * @restrict E
 *
 * @description
 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the
 * template can be used by {@link ng.directive:ngInclude `ngInclude`},
 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the
 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be
 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`.
 *
 * @param {string} type Must be set to `'text/ng-template'`.
 * @param {string} id Cache name of the template.
 *
 * @example
  <example  name="script-tag">
    <file name="index.html">
      <script type="text/ng-template" id="/tpl.html">
        Content of the template.
      </script>

      <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a>
      <div id="tpl-content" ng-include src="currentTpl"></div>
    </file>
    <file name="protractor.js" type="protractor">
      it('should load template defined inside script tag', function() {
        element(by.css('#tpl-link')).click();
        expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/);
      });
    </file>
  </example>
 */
var scriptDirective = ['$templateCache', function($templateCache) {
  return {
    restrict: 'E',
    terminal: true,
    compile: function(element, attr) {
      if (attr.type === 'text/ng-template') {
        var templateUrl = attr.id,
            text = element[0].text;

        $templateCache.put(templateUrl, text);
      }
    }
  };
}];

/* exported selectDirective, optionDirective */

var noopNgModelController = { $setViewValue: noop, $render: noop };

function setOptionSelectedStatus(optionEl, value) {
  optionEl.prop('selected', value);
  /**
   * When unselecting an option, setting the property to null / false should be enough
   * However, screenreaders might react to the selected attribute instead, see
   * https://github.com/angular/angular.js/issues/14419
   * Note: "selected" is a boolean attr and will be removed when the "value" arg in attr() is false
   * or null
   */
  optionEl.attr('selected', value);
}

/**
 * @ngdoc type
 * @name  select.SelectController
 *
 * @description
 * The controller for the {@link ng.select select} directive. The controller exposes
 * a few utility methods that can be used to augment the behavior of a regular or an
 * {@link ng.ngOptions ngOptions} select element.
 *
 * @example
 * ### Set a custom error when the unknown option is selected
 *
 * This example sets a custom error "unknownValue" on the ngModelController
 * when the select element's unknown option is selected, i.e. when the model is set to a value
 * that is not matched by any option.
 *
 * <example name="select-unknown-value-error" module="staticSelect">
 * <file name="index.html">
 * <div ng-controller="ExampleController">
 *   <form name="myForm">
 *     <label for="testSelect"> Single select: </label><br>
 *     <select name="testSelect" ng-model="selected" unknown-value-error>
 *       <option value="option-1">Option 1</option>
 *       <option value="option-2">Option 2</option>
 *     </select><br>
 *     <span class="error" ng-if="myForm.testSelect.$error.unknownValue">
 *       Error: The current model doesn't match any option</span><br>
 *
 *     <button ng-click="forceUnknownOption()">Force unknown option</button><br>
 *   </form>
 * </div>
 * </file>
 * <file name="app.js">
 *  angular.module('staticSelect', [])
 *    .controller('ExampleController', ['$scope', function($scope) {
 *      $scope.selected = null;
 *
 *      $scope.forceUnknownOption = function() {
 *        $scope.selected = 'nonsense';
 *      };
 *   }])
 *   .directive('unknownValueError', function() {
 *     return {
 *       require: ['ngModel', 'select'],
 *       link: function(scope, element, attrs, ctrls) {
 *         var ngModelCtrl = ctrls[0];
 *         var selectCtrl = ctrls[1];
 *
 *         ngModelCtrl.$validators.unknownValue = function(modelValue, viewValue) {
 *           if (selectCtrl.$isUnknownOptionSelected()) {
 *             return false;
 *           }
 *
 *           return true;
 *         };
 *       }
 *
 *     };
 *   });
 * </file>
 *</example>
 *
 *
 * @example
 * ### Set the "required" error when the unknown option is selected.
 *
 * By default, the "required" error on the ngModelController is only set on a required select
 * when the empty option is selected. This example adds a custom directive that also sets the
 * error when the unknown option is selected.
 *
 * <example name="select-unknown-value-required" module="staticSelect">
 * <file name="index.html">
 * <div ng-controller="ExampleController">
 *   <form name="myForm">
 *     <label for="testSelect"> Select: </label><br>
 *     <select name="testSelect" ng-model="selected" required unknown-value-required>
 *       <option value="option-1">Option 1</option>
 *       <option value="option-2">Option 2</option>
 *     </select><br>
 *     <span class="error" ng-if="myForm.testSelect.$error.required">Error: Please select a value</span><br>
 *
 *     <button ng-click="forceUnknownOption()">Force unknown option</button><br>
 *   </form>
 * </div>
 * </file>
 * <file name="app.js">
 *  angular.module('staticSelect', [])
 *    .controller('ExampleController', ['$scope', function($scope) {
 *      $scope.selected = null;
 *
 *      $scope.forceUnknownOption = function() {
 *        $scope.selected = 'nonsense';
 *      };
 *   }])
 *   .directive('unknownValueRequired', function() {
 *     return {
 *       priority: 1, // This directive must run after the required directive has added its validator
 *       require: ['ngModel', 'select'],
 *       link: function(scope, element, attrs, ctrls) {
 *         var ngModelCtrl = ctrls[0];
 *         var selectCtrl = ctrls[1];
 *
 *         var originalRequiredValidator = ngModelCtrl.$validators.required;
 *
 *         ngModelCtrl.$validators.required = function() {
 *           if (attrs.required && selectCtrl.$isUnknownOptionSelected()) {
 *             return false;
 *           }
 *
 *           return originalRequiredValidator.apply(this, arguments);
 *         };
 *       }
 *     };
 *   });
 * </file>
 * <file name="protractor.js" type="protractor">
 *  it('should show the error message when the unknown option is selected', function() {

      var error = element(by.className('error'));

      expect(error.getText()).toBe('Error: Please select a value');

      element(by.cssContainingText('option', 'Option 1')).click();

      expect(error.isPresent()).toBe(false);

      element(by.tagName('button')).click();

      expect(error.getText()).toBe('Error: Please select a value');
    });
 * </file>
 *</example>
 *
 *
 */
var SelectController =
        ['$element', '$scope', /** @this */ function($element, $scope) {

  var self = this,
      optionsMap = new NgMap();

  self.selectValueMap = {}; // Keys are the hashed values, values the original values

  // If the ngModel doesn't get provided then provide a dummy noop version to prevent errors
  self.ngModelCtrl = noopNgModelController;
  self.multiple = false;

  // The "unknown" option is one that is prepended to the list if the viewValue
  // does not match any of the options. When it is rendered the value of the unknown
  // option is '? XXX ?' where XXX is the hashKey of the value that is not known.
  //
  // Support: IE 9 only
  // We can't just jqLite('<option>') since jqLite is not smart enough
  // to create it in <select> and IE barfs otherwise.
  self.unknownOption = jqLite(window.document.createElement('option'));

  // The empty option is an option with the value '' that the application developer can
  // provide inside the select. It is always selectable and indicates that a "null" selection has
  // been made by the user.
  // If the select has an empty option, and the model of the select is set to "undefined" or "null",
  // the empty option is selected.
  // If the model is set to a different unmatched value, the unknown option is rendered and
  // selected, i.e both are present, because a "null" selection and an unknown value are different.
  self.hasEmptyOption = false;
  self.emptyOption = undefined;

  self.renderUnknownOption = function(val) {
    var unknownVal = self.generateUnknownOptionValue(val);
    self.unknownOption.val(unknownVal);
    $element.prepend(self.unknownOption);
    setOptionSelectedStatus(self.unknownOption, true);
    $element.val(unknownVal);
  };

  self.updateUnknownOption = function(val) {
    var unknownVal = self.generateUnknownOptionValue(val);
    self.unknownOption.val(unknownVal);
    setOptionSelectedStatus(self.unknownOption, true);
    $element.val(unknownVal);
  };

  self.generateUnknownOptionValue = function(val) {
    return '? ' + hashKey(val) + ' ?';
  };

  self.removeUnknownOption = function() {
    if (self.unknownOption.parent()) self.unknownOption.remove();
  };

  self.selectEmptyOption = function() {
    if (self.emptyOption) {
      $element.val('');
      setOptionSelectedStatus(self.emptyOption, true);
    }
  };

  self.unselectEmptyOption = function() {
    if (self.hasEmptyOption) {
      setOptionSelectedStatus(self.emptyOption, false);
    }
  };

  $scope.$on('$destroy', function() {
    // disable unknown option so that we don't do work when the whole select is being destroyed
    self.renderUnknownOption = noop;
  });

  // Read the value of the select control, the implementation of this changes depending
  // upon whether the select can have multiple values and whether ngOptions is at work.
  self.readValue = function readSingleValue() {
    var val = $element.val();
    // ngValue added option values are stored in the selectValueMap, normal interpolations are not
    var realVal = val in self.selectValueMap ? self.selectValueMap[val] : val;

    if (self.hasOption(realVal)) {
      return realVal;
    }

    return null;
  };


  // Write the value to the select control, the implementation of this changes depending
  // upon whether the select can have multiple values and whether ngOptions is at work.
  self.writeValue = function writeSingleValue(value) {
    // Make sure to remove the selected attribute from the previously selected option
    // Otherwise, screen readers might get confused
    var currentlySelectedOption = $element[0].options[$element[0].selectedIndex];
    if (currentlySelectedOption) setOptionSelectedStatus(jqLite(currentlySelectedOption), false);

    if (self.hasOption(value)) {
      self.removeUnknownOption();

      var hashedVal = hashKey(value);
      $element.val(hashedVal in self.selectValueMap ? hashedVal : value);

      // Set selected attribute and property on selected option for screen readers
      var selectedOption = $element[0].options[$element[0].selectedIndex];
      setOptionSelectedStatus(jqLite(selectedOption), true);
    } else {
      self.selectUnknownOrEmptyOption(value);
    }
  };


  // Tell the select control that an option, with the given value, has been added
  self.addOption = function(value, element) {
    // Skip comment nodes, as they only pollute the `optionsMap`
    if (element[0].nodeType === NODE_TYPE_COMMENT) return;

    assertNotHasOwnProperty(value, '"option value"');
    if (value === '') {
      self.hasEmptyOption = true;
      self.emptyOption = element;
    }
    var count = optionsMap.get(value) || 0;
    optionsMap.set(value, count + 1);
    // Only render at the end of a digest. This improves render performance when many options
    // are added during a digest and ensures all relevant options are correctly marked as selected
    scheduleRender();
  };

  // Tell the select control that an option, with the given value, has been removed
  self.removeOption = function(value) {
    var count = optionsMap.get(value);
    if (count) {
      if (count === 1) {
        optionsMap.delete(value);
        if (value === '') {
          self.hasEmptyOption = false;
          self.emptyOption = undefined;
        }
      } else {
        optionsMap.set(value, count - 1);
      }
    }
  };

  // Check whether the select control has an option matching the given value
  self.hasOption = function(value) {
    return !!optionsMap.get(value);
  };

  /**
   * @ngdoc method
   * @name select.SelectController#$hasEmptyOption
   *
   * @description
   *
   * Returns `true` if the select element currently has an empty option
   * element, i.e. an option that signifies that the select is empty / the selection is null.
   *
   */
  self.$hasEmptyOption = function() {
    return self.hasEmptyOption;
  };

  /**
   * @ngdoc method
   * @name select.SelectController#$isUnknownOptionSelected
   *
   * @description
   *
   * Returns `true` if the select element's unknown option is selected. The unknown option is added
   * and automatically selected whenever the select model doesn't match any option.
   *
   */
  self.$isUnknownOptionSelected = function() {
    // Presence of the unknown option means it is selected
    return $element[0].options[0] === self.unknownOption[0];
  };

  /**
   * @ngdoc method
   * @name select.SelectController#$isEmptyOptionSelected
   *
   * @description
   *
   * Returns `true` if the select element has an empty option and this empty option is currently
   * selected. Returns `false` if the select element has no empty option or it is not selected.
   *
   */
  self.$isEmptyOptionSelected = function() {
    return self.hasEmptyOption && $element[0].options[$element[0].selectedIndex] === self.emptyOption[0];
  };

  self.selectUnknownOrEmptyOption = function(value) {
    if (value == null && self.emptyOption) {
      self.removeUnknownOption();
      self.selectEmptyOption();
    } else if (self.unknownOption.parent().length) {
      self.updateUnknownOption(value);
    } else {
      self.renderUnknownOption(value);
    }
  };

  var renderScheduled = false;
  function scheduleRender() {
    if (renderScheduled) return;
    renderScheduled = true;
    $scope.$$postDigest(function() {
      renderScheduled = false;
      self.ngModelCtrl.$render();
    });
  }

  var updateScheduled = false;
  function scheduleViewValueUpdate(renderAfter) {
    if (updateScheduled) return;

    updateScheduled = true;

    $scope.$$postDigest(function() {
      if ($scope.$$destroyed) return;

      updateScheduled = false;
      self.ngModelCtrl.$setViewValue(self.readValue());
      if (renderAfter) self.ngModelCtrl.$render();
    });
  }


  self.registerOption = function(optionScope, optionElement, optionAttrs, interpolateValueFn, interpolateTextFn) {

    if (optionAttrs.$attr.ngValue) {
      // The value attribute is set by ngValue
      var oldVal, hashedVal;
      optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) {

        var removal;
        var previouslySelected = optionElement.prop('selected');

        if (isDefined(hashedVal)) {
          self.removeOption(oldVal);
          delete self.selectValueMap[hashedVal];
          removal = true;
        }

        hashedVal = hashKey(newVal);
        oldVal = newVal;
        self.selectValueMap[hashedVal] = newVal;
        self.addOption(newVal, optionElement);
        // Set the attribute directly instead of using optionAttrs.$set - this stops the observer
        // from firing a second time. Other $observers on value will also get the result of the
        // ngValue expression, not the hashed value
        optionElement.attr('value', hashedVal);

        if (removal && previouslySelected) {
          scheduleViewValueUpdate();
        }

      });
    } else if (interpolateValueFn) {
      // The value attribute is interpolated
      optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) {
        // This method is overwritten in ngOptions and has side-effects!
        self.readValue();

        var removal;
        var previouslySelected = optionElement.prop('selected');

        if (isDefined(oldVal)) {
          self.removeOption(oldVal);
          removal = true;
        }
        oldVal = newVal;
        self.addOption(newVal, optionElement);

        if (removal && previouslySelected) {
          scheduleViewValueUpdate();
        }
      });
    } else if (interpolateTextFn) {
      // The text content is interpolated
      optionScope.$watch(interpolateTextFn, function interpolateWatchAction(newVal, oldVal) {
        optionAttrs.$set('value', newVal);
        var previouslySelected = optionElement.prop('selected');
        if (oldVal !== newVal) {
          self.removeOption(oldVal);
        }
        self.addOption(newVal, optionElement);

        if (oldVal && previouslySelected) {
          scheduleViewValueUpdate();
        }
      });
    } else {
      // The value attribute is static
      self.addOption(optionAttrs.value, optionElement);
    }


    optionAttrs.$observe('disabled', function(newVal) {

      // Since model updates will also select disabled options (like ngOptions),
      // we only have to handle options becoming disabled, not enabled

      if (newVal === 'true' || newVal && optionElement.prop('selected')) {
        if (self.multiple) {
          scheduleViewValueUpdate(true);
        } else {
          self.ngModelCtrl.$setViewValue(null);
          self.ngModelCtrl.$render();
        }
      }
    });

    optionElement.on('$destroy', function() {
      var currentValue = self.readValue();
      var removeValue = optionAttrs.value;

      self.removeOption(removeValue);
      scheduleRender();

      if (self.multiple && currentValue && currentValue.indexOf(removeValue) !== -1 ||
          currentValue === removeValue
      ) {
        // When multiple (selected) options are destroyed at the same time, we don't want
        // to run a model update for each of them. Instead, run a single update in the $$postDigest
        scheduleViewValueUpdate(true);
      }
    });
  };
}];

/**
 * @ngdoc directive
 * @name select
 * @restrict E
 *
 * @description
 * HTML `select` element with AngularJS data-binding.
 *
 * The `select` directive is used together with {@link ngModel `ngModel`} to provide data-binding
 * between the scope and the `<select>` control (including setting default values).
 * It also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or
 * {@link ngOptions `ngOptions`} directives.
 *
 * When an item in the `<select>` menu is selected, the value of the selected option will be bound
 * to the model identified by the `ngModel` directive. With static or repeated options, this is
 * the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing.
 * Value and textContent can be interpolated.
 *
 * The {@link select.SelectController select controller} exposes utility functions that can be used
 * to manipulate the select's behavior.
 *
 * ## Matching model and option values
 *
 * In general, the match between the model and an option is evaluated by strictly comparing the model
 * value against the value of the available options.
 *
 * If you are setting the option value with the option's `value` attribute, or textContent, the
 * value will always be a `string` which means that the model value must also be a string.
 * Otherwise the `select` directive cannot match them correctly.
 *
 * To bind the model to a non-string value, you can use one of the following strategies:
 * - the {@link ng.ngOptions `ngOptions`} directive
 *   ({@link ng.select#using-select-with-ngoptions-and-setting-a-default-value})
 * - the {@link ng.ngValue `ngValue`} directive, which allows arbitrary expressions to be
 *   option values ({@link ng.select#using-ngvalue-to-bind-the-model-to-an-array-of-objects Example})
 * - model $parsers / $formatters to convert the string value
 *   ({@link ng.select#binding-select-to-a-non-string-value-via-ngmodel-parsing-formatting Example})
 *
 * If the viewValue of `ngModel` does not match any of the options, then the control
 * will automatically add an "unknown" option, which it then removes when the mismatch is resolved.
 *
 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
 * be nested into the `<select>` element. This element will then represent the `null` or "not selected"
 * option. See example below for demonstration.
 *
 * ## Choosing between `ngRepeat` and `ngOptions`
 *
 * In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions
 * ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits:
 * - more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
 * comprehension expression
 * - reduced memory consumption by not creating a new scope for each repeated instance
 * - increased render speed by creating the options in a documentFragment instead of individually
 *
 * Specifically, select with repeated options slows down significantly starting at 2000 options in
 * Chrome and Internet Explorer / Edge.
 *
 *
 * @param {string} ngModel Assignable AngularJS expression to data-bind to.
 * @param {string=} name Property name of the form under which the control is published.
 * @param {string=} multiple Allows multiple options to be selected. The selected values will be
 *     bound to the model as an array.
 * @param {string=} required Sets `required` validation error key if the value is not entered.
 * @param {string=} ngRequired Adds required attribute and required validation constraint to
 * the element when the ngRequired expression evaluates to true. Use ngRequired instead of required
 * when you want to data-bind to the required attribute.
 * @param {string=} ngChange AngularJS expression to be executed when selected option(s) changes due to user
 *    interaction with the select element.
 * @param {string=} ngOptions sets the options that the select is populated with and defines what is
 * set on the model on selection. See {@link ngOptions `ngOptions`}.
 * @param {string=} ngAttrSize sets the size of the select element dynamically. Uses the
 * {@link guide/interpolation#-ngattr-for-binding-to-arbitrary-attributes ngAttr} directive.
 *
 *
 * @example
 * ### Simple `select` elements with static options
 *
 * <example name="static-select" module="staticSelect">
 * <file name="index.html">
 * <div ng-controller="ExampleController">
 *   <form name="myForm">
 *     <label for="singleSelect"> Single select: </label><br>
 *     <select name="singleSelect" ng-model="data.singleSelect">
 *       <option value="option-1">Option 1</option>
 *       <option value="option-2">Option 2</option>
 *     </select><br>
 *
 *     <label for="singleSelect"> Single select with "not selected" option and dynamic option values: </label><br>
 *     <select name="singleSelect" id="singleSelect" ng-model="data.singleSelect">
 *       <option value="">---Please select---</option> <!-- not selected / blank option -->
 *       <option value="{{data.option1}}">Option 1</option> <!-- interpolation -->
 *       <option value="option-2">Option 2</option>
 *     </select><br>
 *     <button ng-click="forceUnknownOption()">Force unknown option</button><br>
 *     <tt>singleSelect = {{data.singleSelect}}</tt>
 *
 *     <hr>
 *     <label for="multipleSelect"> Multiple select: </label><br>
 *     <select name="multipleSelect" id="multipleSelect" ng-model="data.multipleSelect" multiple>
 *       <option value="option-1">Option 1</option>
 *       <option value="option-2">Option 2</option>
 *       <option value="option-3">Option 3</option>
 *     </select><br>
 *     <tt>multipleSelect = {{data.multipleSelect}}</tt><br/>
 *   </form>
 * </div>
 * </file>
 * <file name="app.js">
 *  angular.module('staticSelect', [])
 *    .controller('ExampleController', ['$scope', function($scope) {
 *      $scope.data = {
 *       singleSelect: null,
 *       multipleSelect: [],
 *       option1: 'option-1'
 *      };
 *
 *      $scope.forceUnknownOption = function() {
 *        $scope.data.singleSelect = 'nonsense';
 *      };
 *   }]);
 * </file>
 *</example>
 *
 * @example
 * ### Using `ngRepeat` to generate `select` options
 * <example name="select-ngrepeat" module="ngrepeatSelect">
 * <file name="index.html">
 * <div ng-controller="ExampleController">
 *   <form name="myForm">
 *     <label for="repeatSelect"> Repeat select: </label>
 *     <select name="repeatSelect" id="repeatSelect" ng-model="data.model">
 *       <option ng-repeat="option in data.availableOptions" value="{{option.id}}">{{option.name}}</option>
 *     </select>
 *   </form>
 *   <hr>
 *   <tt>model = {{data.model}}</tt><br/>
 * </div>
 * </file>
 * <file name="app.js">
 *  angular.module('ngrepeatSelect', [])
 *    .controller('ExampleController', ['$scope', function($scope) {
 *      $scope.data = {
 *       model: null,
 *       availableOptions: [
 *         {id: '1', name: 'Option A'},
 *         {id: '2', name: 'Option B'},
 *         {id: '3', name: 'Option C'}
 *       ]
 *      };
 *   }]);
 * </file>
 *</example>
 *
 * @example
 * ### Using `ngValue` to bind the model to an array of objects
 * <example name="select-ngvalue" module="ngvalueSelect">
 * <file name="index.html">
 * <div ng-controller="ExampleController">
 *   <form name="myForm">
 *     <label for="ngvalueselect"> ngvalue select: </label>
 *     <select size="6" name="ngvalueselect" ng-model="data.model" multiple>
 *       <option ng-repeat="option in data.availableOptions" ng-value="option.value">{{option.name}}</option>
 *     </select>
 *   </form>
 *   <hr>
 *   <pre>model = {{data.model | json}}</pre><br/>
 * </div>
 * </file>
 * <file name="app.js">
 *  angular.module('ngvalueSelect', [])
 *    .controller('ExampleController', ['$scope', function($scope) {
 *      $scope.data = {
 *       model: null,
 *       availableOptions: [
           {value: 'myString', name: 'string'},
           {value: 1, name: 'integer'},
           {value: true, name: 'boolean'},
           {value: null, name: 'null'},
           {value: {prop: 'value'}, name: 'object'},
           {value: ['a'], name: 'array'}
 *       ]
 *      };
 *   }]);
 * </file>
 *</example>
 *
 * @example
 * ### Using `select` with `ngOptions` and setting a default value
 * See the {@link ngOptions ngOptions documentation} for more `ngOptions` usage examples.
 *
 * <example name="select-with-default-values" module="defaultValueSelect">
 * <file name="index.html">
 * <div ng-controller="ExampleController">
 *   <form name="myForm">
 *     <label for="mySelect">Make a choice:</label>
 *     <select name="mySelect" id="mySelect"
 *       ng-options="option.name for option in data.availableOptions track by option.id"
 *       ng-model="data.selectedOption"></select>
 *   </form>
 *   <hr>
 *   <tt>option = {{data.selectedOption}}</tt><br/>
 * </div>
 * </file>
 * <file name="app.js">
 *  angular.module('defaultValueSelect', [])
 *    .controller('ExampleController', ['$scope', function($scope) {
 *      $scope.data = {
 *       availableOptions: [
 *         {id: '1', name: 'Option A'},
 *         {id: '2', name: 'Option B'},
 *         {id: '3', name: 'Option C'}
 *       ],
 *       selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui
 *       };
 *   }]);
 * </file>
 *</example>
 *
 * @example
 * ### Binding `select` to a non-string value via `ngModel` parsing / formatting
 *
 * <example name="select-with-non-string-options" module="nonStringSelect">
 *   <file name="index.html">
 *     <select ng-model="model.id" convert-to-number>
 *       <option value="0">Zero</option>
 *       <option value="1">One</option>
 *       <option value="2">Two</option>
 *     </select>
 *     {{ model }}
 *   </file>
 *   <file name="app.js">
 *     angular.module('nonStringSelect', [])
 *       .run(function($rootScope) {
 *         $rootScope.model = { id: 2 };
 *       })
 *       .directive('convertToNumber', function() {
 *         return {
 *           require: 'ngModel',
 *           link: function(scope, element, attrs, ngModel) {
 *             ngModel.$parsers.push(function(val) {
 *               return parseInt(val, 10);
 *             });
 *             ngModel.$formatters.push(function(val) {
 *               return '' + val;
 *             });
 *           }
 *         };
 *       });
 *   </file>
 *   <file name="protractor.js" type="protractor">
 *     it('should initialize to model', function() {
 *       expect(element(by.model('model.id')).$('option:checked').getText()).toEqual('Two');
 *     });
 *   </file>
 * </example>
 *
 */
var selectDirective = function() {

  return {
    restrict: 'E',
    require: ['select', '?ngModel'],
    controller: SelectController,
    priority: 1,
    link: {
      pre: selectPreLink,
      post: selectPostLink
    }
  };

  function selectPreLink(scope, element, attr, ctrls) {

      var selectCtrl = ctrls[0];
      var ngModelCtrl = ctrls[1];

      // if ngModel is not defined, we don't need to do anything but set the registerOption
      // function to noop, so options don't get added internally
      if (!ngModelCtrl) {
        selectCtrl.registerOption = noop;
        return;
      }


      selectCtrl.ngModelCtrl = ngModelCtrl;

      // When the selected item(s) changes we delegate getting the value of the select control
      // to the `readValue` method, which can be changed if the select can have multiple
      // selected values or if the options are being generated by `ngOptions`
      element.on('change', function() {
        selectCtrl.removeUnknownOption();
        scope.$apply(function() {
          ngModelCtrl.$setViewValue(selectCtrl.readValue());
        });
      });

      // If the select allows multiple values then we need to modify how we read and write
      // values from and to the control; also what it means for the value to be empty and
      // we have to add an extra watch since ngModel doesn't work well with arrays - it
      // doesn't trigger rendering if only an item in the array changes.
      if (attr.multiple) {
        selectCtrl.multiple = true;

        // Read value now needs to check each option to see if it is selected
        selectCtrl.readValue = function readMultipleValue() {
          var array = [];
          forEach(element.find('option'), function(option) {
            if (option.selected && !option.disabled) {
              var val = option.value;
              array.push(val in selectCtrl.selectValueMap ? selectCtrl.selectValueMap[val] : val);
            }
          });
          return array;
        };

        // Write value now needs to set the selected property of each matching option
        selectCtrl.writeValue = function writeMultipleValue(value) {
          forEach(element.find('option'), function(option) {
            var shouldBeSelected = !!value && (includes(value, option.value) ||
                                               includes(value, selectCtrl.selectValueMap[option.value]));
            var currentlySelected = option.selected;

            // Support: IE 9-11 only, Edge 12-15+
            // In IE and Edge adding options to the selection via shift+click/UP/DOWN
            // will de-select already selected options if "selected" on those options was set
            // more than once (i.e. when the options were already selected)
            // So we only modify the selected property if necessary.
            // Note: this behavior cannot be replicated via unit tests because it only shows in the
            // actual user interface.
            if (shouldBeSelected !== currentlySelected) {
              setOptionSelectedStatus(jqLite(option), shouldBeSelected);
            }

          });
        };

        // we have to do it on each watch since ngModel watches reference, but
        // we need to work of an array, so we need to see if anything was inserted/removed
        var lastView, lastViewRef = NaN;
        scope.$watch(function selectMultipleWatch() {
          if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) {
            lastView = shallowCopy(ngModelCtrl.$viewValue);
            ngModelCtrl.$render();
          }
          lastViewRef = ngModelCtrl.$viewValue;
        });

        // If we are a multiple select then value is now a collection
        // so the meaning of $isEmpty changes
        ngModelCtrl.$isEmpty = function(value) {
          return !value || value.length === 0;
        };

      }
    }

    function selectPostLink(scope, element, attrs, ctrls) {
      // if ngModel is not defined, we don't need to do anything
      var ngModelCtrl = ctrls[1];
      if (!ngModelCtrl) return;

      var selectCtrl = ctrls[0];

      // We delegate rendering to the `writeValue` method, which can be changed
      // if the select can have multiple selected values or if the options are being
      // generated by `ngOptions`.
      // This must be done in the postLink fn to prevent $render to be called before
      // all nodes have been linked correctly.
      ngModelCtrl.$render = function() {
        selectCtrl.writeValue(ngModelCtrl.$viewValue);
      };
    }
};


// The option directive is purely designed to communicate the existence (or lack of)
// of dynamically created (and destroyed) option elements to their containing select
// directive via its controller.
var optionDirective = ['$interpolate', function($interpolate) {
  return {
    restrict: 'E',
    priority: 100,
    compile: function(element, attr) {
      var interpolateValueFn, interpolateTextFn;

      if (isDefined(attr.ngValue)) {
        // Will be handled by registerOption
      } else if (isDefined(attr.value)) {
        // If the value attribute is defined, check if it contains an interpolation
        interpolateValueFn = $interpolate(attr.value, true);
      } else {
        // If the value attribute is not defined then we fall back to the
        // text content of the option element, which may be interpolated
        interpolateTextFn = $interpolate(element.text(), true);
        if (!interpolateTextFn) {
          attr.$set('value', element.text());
        }
      }

      return function(scope, element, attr) {
        // This is an optimization over using ^^ since we don't want to have to search
        // all the way to the root of the DOM for every single option element
        var selectCtrlName = '$selectController',
            parent = element.parent(),
            selectCtrl = parent.data(selectCtrlName) ||
              parent.parent().data(selectCtrlName); // in case we are in optgroup

        if (selectCtrl) {
          selectCtrl.registerOption(scope, element, attr, interpolateValueFn, interpolateTextFn);
        }
      };
    }
  };
}];

/**
 * @ngdoc directive
 * @name ngRequired
 * @restrict A
 *
 * @param {expression} ngRequired AngularJS expression. If it evaluates to `true`, it sets the
 *                                `required` attribute to the element and adds the `required`
 *                                {@link ngModel.NgModelController#$validators `validator`}.
 *
 * @description
 *
 * ngRequired adds the required {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}.
 * It is most often used for {@link input `input`} and {@link select `select`} controls, but can also be
 * applied to custom controls.
 *
 * The directive sets the `required` attribute on the element if the AngularJS expression inside
 * `ngRequired` evaluates to true. A special directive for setting `required` is necessary because we
 * cannot use interpolation inside `required`. See the {@link guide/interpolation interpolation guide}
 * for more info.
 *
 * The validator will set the `required` error key to true if the `required` attribute is set and
 * calling {@link ngModel.NgModelController#$isEmpty `NgModelController.$isEmpty`} with the
 * {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} returns `true`. For example, the
 * `$isEmpty()` implementation for `input[text]` checks the length of the `$viewValue`. When developing
 * custom controls, `$isEmpty()` can be overwritten to account for a $viewValue that is not string-based.
 *
 * @example
 * <example name="ngRequiredDirective" module="ngRequiredExample">
 *   <file name="index.html">
 *     <script>
 *       angular.module('ngRequiredExample', [])
 *         .controller('ExampleController', ['$scope', function($scope) {
 *           $scope.required = true;
 *         }]);
 *     </script>
 *     <div ng-controller="ExampleController">
 *       <form name="form">
 *         <label for="required">Toggle required: </label>
 *         <input type="checkbox" ng-model="required" id="required" />
 *         <br>
 *         <label for="input">This input must be filled if `required` is true: </label>
 *         <input type="text" ng-model="model" id="input" name="input" ng-required="required" /><br>
 *         <hr>
 *         required error set? = <code>{{form.input.$error.required}}</code><br>
 *         model = <code>{{model}}</code>
 *       </form>
 *     </div>
 *   </file>
 *   <file name="protractor.js" type="protractor">
       var required = element(by.binding('form.input.$error.required'));
       var model = element(by.binding('model'));
       var input = element(by.id('input'));

       it('should set the required error', function() {
         expect(required.getText()).toContain('true');

         input.sendKeys('123');
         expect(required.getText()).not.toContain('true');
         expect(model.getText()).toContain('123');
       });
 *   </file>
 * </example>
 */
var requiredDirective = ['$parse', function($parse) {
  return {
    restrict: 'A',
    require: '?ngModel',
    link: function(scope, elm, attr, ctrl) {
      if (!ctrl) return;
      // For boolean attributes like required, presence means true
      var value = attr.hasOwnProperty('required') || $parse(attr.ngRequired)(scope);

      if (!attr.ngRequired) {
        // force truthy in case we are on non input element
        // (input elements do this automatically for boolean attributes like required)
        attr.required = true;
      }

      ctrl.$validators.required = function(modelValue, viewValue) {
        return !value || !ctrl.$isEmpty(viewValue);
      };

      attr.$observe('required', function(newVal) {

        if (value !== newVal) {
          value = newVal;
          ctrl.$validate();
        }
      });
    }
  };
}];

/**
 * @ngdoc directive
 * @name ngPattern
 * @restrict A
 *
 * @param {expression|RegExp} ngPattern AngularJS expression that must evaluate to a `RegExp` or a `String`
 *                                      parsable into a `RegExp`, or a `RegExp` literal. See above for
 *                                      more details.
 *
 * @description
 *
 * ngPattern adds the pattern {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}.
 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls.
 *
 * The validator sets the `pattern` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`}
 * does not match a RegExp which is obtained from the `ngPattern` attribute value:
 * - the value is an AngularJS expression:
 *   - If the expression evaluates to a RegExp object, then this is used directly.
 *   - If the expression evaluates to a string, then it will be converted to a RegExp after wrapping it
 *     in `^` and `$` characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`.
 * - If the value is a RegExp literal, e.g. `ngPattern="/^\d+$/"`, it is used directly.
 *
 * <div class="alert alert-info">
 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to
 * start at the index of the last search's match, thus not taking the whole input value into
 * account.
 * </div>
 *
 * <div class="alert alert-info">
 * **Note:** This directive is also added when the plain `pattern` attribute is used, with two
 * differences:
 * <ol>
 *   <li>
 *     `ngPattern` does not set the `pattern` attribute and therefore HTML5 constraint validation is
 *     not available.
 *   </li>
 *   <li>
 *     The `ngPattern` attribute must be an expression, while the `pattern` value must be
 *     interpolated.
 *   </li>
 * </ol>
 * </div>
 *
 * @example
 * <example name="ngPatternDirective" module="ngPatternExample">
 *   <file name="index.html">
 *     <script>
 *       angular.module('ngPatternExample', [])
 *         .controller('ExampleController', ['$scope', function($scope) {
 *           $scope.regex = '\\d+';
 *         }]);
 *     </script>
 *     <div ng-controller="ExampleController">
 *       <form name="form">
 *         <label for="regex">Set a pattern (regex string): </label>
 *         <input type="text" ng-model="regex" id="regex" />
 *         <br>
 *         <label for="input">This input is restricted by the current pattern: </label>
 *         <input type="text" ng-model="model" id="input" name="input" ng-pattern="regex" /><br>
 *         <hr>
 *         input valid? = <code>{{form.input.$valid}}</code><br>
 *         model = <code>{{model}}</code>
 *       </form>
 *     </div>
 *   </file>
 *   <file name="protractor.js" type="protractor">
       var model = element(by.binding('model'));
       var input = element(by.id('input'));

       it('should validate the input with the default pattern', function() {
         input.sendKeys('aaa');
         expect(model.getText()).not.toContain('aaa');

         input.clear().then(function() {
           input.sendKeys('123');
           expect(model.getText()).toContain('123');
         });
       });
 *   </file>
 * </example>
 */
var patternDirective = ['$parse', function($parse) {
  return {
    restrict: 'A',
    require: '?ngModel',
    compile: function(tElm, tAttr) {
      var patternExp;
      var parseFn;

      if (tAttr.ngPattern) {
        patternExp = tAttr.ngPattern;

        // ngPattern might be a scope expression, or an inlined regex, which is not parsable.
        // We get value of the attribute here, so we can compare the old and the new value
        // in the observer to avoid unnecessary validations
        if (tAttr.ngPattern.charAt(0) === '/' && REGEX_STRING_REGEXP.test(tAttr.ngPattern)) {
          parseFn = function() { return tAttr.ngPattern; };
        } else {
          parseFn = $parse(tAttr.ngPattern);
        }
      }

      return function(scope, elm, attr, ctrl) {
        if (!ctrl) return;

        var attrVal = attr.pattern;

        if (attr.ngPattern) {
          attrVal = parseFn(scope);
        } else {
          patternExp = attr.pattern;
        }

        var regexp = parsePatternAttr(attrVal, patternExp, elm);

        attr.$observe('pattern', function(newVal) {
          var oldRegexp = regexp;

          regexp = parsePatternAttr(newVal, patternExp, elm);

          if ((oldRegexp && oldRegexp.toString()) !== (regexp && regexp.toString())) {
            ctrl.$validate();
          }
        });

        ctrl.$validators.pattern = function(modelValue, viewValue) {
          // HTML5 pattern constraint validates the input value, so we validate the viewValue
          return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue);
        };
      };
    }

  };
}];

/**
 * @ngdoc directive
 * @name ngMaxlength
 * @restrict A
 *
 * @param {expression} ngMaxlength AngularJS expression that must evaluate to a `Number` or `String`
 *                                 parsable into a `Number`. Used as value for the `maxlength`
 *                                 {@link ngModel.NgModelController#$validators validator}.
 *
 * @description
 *
 * ngMaxlength adds the maxlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}.
 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls.
 *
 * The validator sets the `maxlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`}
 * is longer than the integer obtained by evaluating the AngularJS expression given in the
 * `ngMaxlength` attribute value.
 *
 * <div class="alert alert-info">
 * **Note:** This directive is also added when the plain `maxlength` attribute is used, with two
 * differences:
 * <ol>
 *   <li>
 *     `ngMaxlength` does not set the `maxlength` attribute and therefore HTML5 constraint
 *     validation is not available.
 *   </li>
 *   <li>
 *     The `ngMaxlength` attribute must be an expression, while the `maxlength` value must be
 *     interpolated.
 *   </li>
 * </ol>
 * </div>
 *
 * @example
 * <example name="ngMaxlengthDirective" module="ngMaxlengthExample">
 *   <file name="index.html">
 *     <script>
 *       angular.module('ngMaxlengthExample', [])
 *         .controller('ExampleController', ['$scope', function($scope) {
 *           $scope.maxlength = 5;
 *         }]);
 *     </script>
 *     <div ng-controller="ExampleController">
 *       <form name="form">
 *         <label for="maxlength">Set a maxlength: </label>
 *         <input type="number" ng-model="maxlength" id="maxlength" />
 *         <br>
 *         <label for="input">This input is restricted by the current maxlength: </label>
 *         <input type="text" ng-model="model" id="input" name="input" ng-maxlength="maxlength" /><br>
 *         <hr>
 *         input valid? = <code>{{form.input.$valid}}</code><br>
 *         model = <code>{{model}}</code>
 *       </form>
 *     </div>
 *   </file>
 *   <file name="protractor.js" type="protractor">
       var model = element(by.binding('model'));
       var input = element(by.id('input'));

       it('should validate the input with the default maxlength', function() {
         input.sendKeys('abcdef');
         expect(model.getText()).not.toContain('abcdef');

         input.clear().then(function() {
           input.sendKeys('abcde');
           expect(model.getText()).toContain('abcde');
         });
       });
 *   </file>
 * </example>
 */
var maxlengthDirective = ['$parse', function($parse) {
  return {
    restrict: 'A',
    require: '?ngModel',
    link: function(scope, elm, attr, ctrl) {
      if (!ctrl) return;

      var maxlength = attr.maxlength || $parse(attr.ngMaxlength)(scope);
      var maxlengthParsed = parseLength(maxlength);

      attr.$observe('maxlength', function(value) {
        if (maxlength !== value) {
          maxlengthParsed = parseLength(value);
          maxlength = value;
          ctrl.$validate();
        }
      });
      ctrl.$validators.maxlength = function(modelValue, viewValue) {
        return (maxlengthParsed < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlengthParsed);
      };
    }
  };
}];

/**
 * @ngdoc directive
 * @name ngMinlength
 * @restrict A
 *
 * @param {expression} ngMinlength AngularJS expression that must evaluate to a `Number` or `String`
 *                                 parsable into a `Number`. Used as value for the `minlength`
 *                                 {@link ngModel.NgModelController#$validators validator}.
 *
 * @description
 *
 * ngMinlength adds the minlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}.
 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls.
 *
 * The validator sets the `minlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`}
 * is shorter than the integer obtained by evaluating the AngularJS expression given in the
 * `ngMinlength` attribute value.
 *
 * <div class="alert alert-info">
 * **Note:** This directive is also added when the plain `minlength` attribute is used, with two
 * differences:
 * <ol>
 *   <li>
 *     `ngMinlength` does not set the `minlength` attribute and therefore HTML5 constraint
 *     validation is not available.
 *   </li>
 *   <li>
 *     The `ngMinlength` value must be an expression, while the `minlength` value must be
 *     interpolated.
 *   </li>
 * </ol>
 * </div>
 *
 * @example
 * <example name="ngMinlengthDirective" module="ngMinlengthExample">
 *   <file name="index.html">
 *     <script>
 *       angular.module('ngMinlengthExample', [])
 *         .controller('ExampleController', ['$scope', function($scope) {
 *           $scope.minlength = 3;
 *         }]);
 *     </script>
 *     <div ng-controller="ExampleController">
 *       <form name="form">
 *         <label for="minlength">Set a minlength: </label>
 *         <input type="number" ng-model="minlength" id="minlength" />
 *         <br>
 *         <label for="input">This input is restricted by the current minlength: </label>
 *         <input type="text" ng-model="model" id="input" name="input" ng-minlength="minlength" /><br>
 *         <hr>
 *         input valid? = <code>{{form.input.$valid}}</code><br>
 *         model = <code>{{model}}</code>
 *       </form>
 *     </div>
 *   </file>
 *   <file name="protractor.js" type="protractor">
       var model = element(by.binding('model'));
       var input = element(by.id('input'));

       it('should validate the input with the default minlength', function() {
         input.sendKeys('ab');
         expect(model.getText()).not.toContain('ab');

         input.sendKeys('abc');
         expect(model.getText()).toContain('abc');
       });
 *   </file>
 * </example>
 */
var minlengthDirective = ['$parse', function($parse) {
  return {
    restrict: 'A',
    require: '?ngModel',
    link: function(scope, elm, attr, ctrl) {
      if (!ctrl) return;

      var minlength = attr.minlength || $parse(attr.ngMinlength)(scope);
      var minlengthParsed = parseLength(minlength) || -1;

      attr.$observe('minlength', function(value) {
        if (minlength !== value) {
          minlengthParsed = parseLength(value) || -1;
          minlength = value;
          ctrl.$validate();
        }

      });
      ctrl.$validators.minlength = function(modelValue, viewValue) {
        return ctrl.$isEmpty(viewValue) || viewValue.length >= minlengthParsed;
      };
    }
  };
}];


function parsePatternAttr(regex, patternExp, elm) {
  if (!regex) return undefined;

  if (isString(regex)) {
    regex = new RegExp('^' + regex + '$');
  }

  if (!regex.test) {
    throw minErr('ngPattern')('noregexp',
      'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
      regex, startingTag(elm));
  }

  return regex;
}

function parseLength(val) {
  var intVal = toInt(val);
  return isNumberNaN(intVal) ? -1 : intVal;
}

if (window.angular.bootstrap) {
  // AngularJS is already loaded, so we can return here...
  if (window.console) {
    console.log('WARNING: Tried to load AngularJS more than once.');
  }
  return;
}

// try to bind to jquery now so that one can write jqLite(fn)
// but we will rebind on bootstrap again.
bindJQuery();

publishExternalAPI(angular);

angular.module("ngLocale", [], ["$provide", function($provide) {
var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"};
function getDecimals(n) {
  n = n + '';
  var i = n.indexOf('.');
  return (i == -1) ? 0 : n.length - i - 1;
}

function getVF(n, opt_precision) {
  var v = opt_precision;

  if (undefined === v) {
    v = Math.min(getDecimals(n), 3);
  }

  var base = Math.pow(10, v);
  var f = ((n * base) | 0) % base;
  return {v: v, f: f};
}

$provide.value("$locale", {
  "DATETIME_FORMATS": {
    "AMPMS": [
      "AM",
      "PM"
    ],
    "DAY": [
      "Sunday",
      "Monday",
      "Tuesday",
      "Wednesday",
      "Thursday",
      "Friday",
      "Saturday"
    ],
    "ERANAMES": [
      "Before Christ",
      "Anno Domini"
    ],
    "ERAS": [
      "BC",
      "AD"
    ],
    "FIRSTDAYOFWEEK": 6,
    "MONTH": [
      "January",
      "February",
      "March",
      "April",
      "May",
      "June",
      "July",
      "August",
      "September",
      "October",
      "November",
      "December"
    ],
    "SHORTDAY": [
      "Sun",
      "Mon",
      "Tue",
      "Wed",
      "Thu",
      "Fri",
      "Sat"
    ],
    "SHORTMONTH": [
      "Jan",
      "Feb",
      "Mar",
      "Apr",
      "May",
      "Jun",
      "Jul",
      "Aug",
      "Sep",
      "Oct",
      "Nov",
      "Dec"
    ],
    "STANDALONEMONTH": [
      "January",
      "February",
      "March",
      "April",
      "May",
      "June",
      "July",
      "August",
      "September",
      "October",
      "November",
      "December"
    ],
    "WEEKENDRANGE": [
      5,
      6
    ],
    "fullDate": "EEEE, MMMM d, y",
    "longDate": "MMMM d, y",
    "medium": "MMM d, y h:mm:ss a",
    "mediumDate": "MMM d, y",
    "mediumTime": "h:mm:ss a",
    "short": "M/d/yy h:mm a",
    "shortDate": "M/d/yy",
    "shortTime": "h:mm a"
  },
  "NUMBER_FORMATS": {
    "CURRENCY_SYM": "$",
    "DECIMAL_SEP": ".",
    "GROUP_SEP": ",",
    "PATTERNS": [
      {
        "gSize": 3,
        "lgSize": 3,
        "maxFrac": 3,
        "minFrac": 0,
        "minInt": 1,
        "negPre": "-",
        "negSuf": "",
        "posPre": "",
        "posSuf": ""
      },
      {
        "gSize": 3,
        "lgSize": 3,
        "maxFrac": 2,
        "minFrac": 2,
        "minInt": 1,
        "negPre": "-\u00a4",
        "negSuf": "",
        "posPre": "\u00a4",
        "posSuf": ""
      }
    ]
  },
  "id": "en-us",
  "localeID": "en_US",
  "pluralCat": function(n, opt_precision) {  var i = n | 0;  var vf = getVF(n, opt_precision);  if (i == 1 && vf.v == 0) {    return PLURAL_CATEGORY.ONE;  }  return PLURAL_CATEGORY.OTHER;}
});
}]);

  jqLite(function() {
    angularInit(window.document, bootstrap);
  });

})(window);

!window.angular.$$csp().noInlineStyle && window.angular.element(document.head).prepend(window.angular.element('<style>').text('@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{position:absolute;}'));
/* Modernizr 2.8.3 (Custom Build) | MIT & BSD
 * Build: http://modernizr.com/download/#-fontface-backgroundsize-borderimage-borderradius-boxshadow-flexbox-hsla-multiplebgs-opacity-rgba-textshadow-cssanimations-csscolumns-generatedcontent-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-applicationcache-canvas-canvastext-draganddrop-hashchange-history-audio-video-indexeddb-input-inputtypes-localstorage-postmessage-sessionstorage-websockets-websqldatabase-webworkers-geolocation-inlinesvg-smil-svg-svgclippaths-touch-webgl-shiv-mq-cssclasses-addtest-prefixed-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-load
 */
;window.Modernizr=function(a,b,c){function D(a){j.cssText=a}function E(a,b){return D(n.join(a+";")+(b||""))}function F(a,b){return typeof a===b}function G(a,b){return!!~(""+a).indexOf(b)}function H(a,b){for(var d in a){var e=a[d];if(!G(e,"-")&&j[e]!==c)return b=="pfx"?e:!0}return!1}function I(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:F(f,"function")?f.bind(d||b):f}return!1}function J(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+p.join(d+" ")+d).split(" ");return F(b,"string")||F(b,"undefined")?H(e,b):(e=(a+" "+q.join(d+" ")+d).split(" "),I(e,b,c))}function K(){e.input=function(c){for(var d=0,e=c.length;d<e;d++)u[c[d]]=c[d]in k;return u.list&&(u.list=!!b.createElement("datalist")&&!!a.HTMLDataListElement),u}("autocomplete autofocus list placeholder max min multiple pattern required step".split(" ")),e.inputtypes=function(a){for(var d=0,e,f,h,i=a.length;d<i;d++)k.setAttribute("type",f=a[d]),e=k.type!=="text",e&&(k.value=l,k.style.cssText="position:absolute;visibility:hidden;",/^range$/.test(f)&&k.style.WebkitAppearance!==c?(g.appendChild(k),h=b.defaultView,e=h.getComputedStyle&&h.getComputedStyle(k,null).WebkitAppearance!=="textfield"&&k.offsetHeight!==0,g.removeChild(k)):/^(search|tel)$/.test(f)||(/^(url|email)$/.test(f)?e=k.checkValidity&&k.checkValidity()===!1:e=k.value!=l)),t[a[d]]=!!e;return t}("search tel url email datetime date month week time datetime-local number range color".split(" "))}var d="2.8.3",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k=b.createElement("input"),l=":)",m={}.toString,n=" -webkit- -moz- -o- -ms- ".split(" "),o="Webkit Moz O ms",p=o.split(" "),q=o.toLowerCase().split(" "),r={svg:"http://www.w3.org/2000/svg"},s={},t={},u={},v=[],w=v.slice,x,y=function(a,c,d,e){var f,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),l.appendChild(j);return f=["&#173;",'<style id="s',h,'">',a,"</style>"].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},z=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b)&&c(b).matches||!1;var d;return y("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},A=function(){function d(d,e){e=e||b.createElement(a[d]||"div"),d="on"+d;var f=d in e;return f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=F(e[d],"function"),F(e[d],"undefined")||(e[d]=c),e.removeAttribute(d))),e=null,f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),B={}.hasOwnProperty,C;!F(B,"undefined")&&!F(B.call,"undefined")?C=function(a,b){return B.call(a,b)}:C=function(a,b){return b in a&&F(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=w.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(w.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(w.call(arguments)))};return e}),s.flexbox=function(){return J("flexWrap")},s.canvas=function(){var a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},s.canvastext=function(){return!!e.canvas&&!!F(b.createElement("canvas").getContext("2d").fillText,"function")},s.webgl=function(){return!!a.WebGLRenderingContext},s.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:y(["@media (",n.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},s.geolocation=function(){return"geolocation"in navigator},s.postmessage=function(){return!!a.postMessage},s.websqldatabase=function(){return!!a.openDatabase},s.indexedDB=function(){return!!J("indexedDB",a)},s.hashchange=function(){return A("hashchange",a)&&(b.documentMode===c||b.documentMode>7)},s.history=function(){return!!a.history&&!!history.pushState},s.draganddrop=function(){var a=b.createElement("div");return"draggable"in a||"ondragstart"in a&&"ondrop"in a},s.websockets=function(){return"WebSocket"in a||"MozWebSocket"in a},s.rgba=function(){return D("background-color:rgba(150,255,150,.5)"),G(j.backgroundColor,"rgba")},s.hsla=function(){return D("background-color:hsla(120,40%,100%,.5)"),G(j.backgroundColor,"rgba")||G(j.backgroundColor,"hsla")},s.multiplebgs=function(){return D("background:url(https://),url(https://),red url(https://)"),/(url\s*\(.*?){3}/.test(j.background)},s.backgroundsize=function(){return J("backgroundSize")},s.borderimage=function(){return J("borderImage")},s.borderradius=function(){return J("borderRadius")},s.boxshadow=function(){return J("boxShadow")},s.textshadow=function(){return b.createElement("div").style.textShadow===""},s.opacity=function(){return E("opacity:.55"),/^0.55$/.test(j.opacity)},s.cssanimations=function(){return J("animationName")},s.csscolumns=function(){return J("columnCount")},s.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";return D((a+"-webkit- ".split(" ").join(b+a)+n.join(c+a)).slice(0,-a.length)),G(j.backgroundImage,"gradient")},s.cssreflections=function(){return J("boxReflect")},s.csstransforms=function(){return!!J("transform")},s.csstransforms3d=function(){var a=!!J("perspective");return a&&"webkitPerspective"in g.style&&y("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},s.csstransitions=function(){return J("transition")},s.fontface=function(){var a;return y('@font-face {font-family:"font";src:url("https://")}',function(c,d){var e=b.getElementById("smodernizr"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"";a=/src/i.test(g)&&g.indexOf(d.split(" ")[0])===0}),a},s.generatedcontent=function(){var a;return y(["#",h,"{font:0/0 a}#",h,':after{content:"',l,'";visibility:hidden;font:3px/1 a}'].join(""),function(b){a=b.offsetHeight>=3}),a},s.video=function(){var a=b.createElement("video"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),c.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,"")}catch(d){}return c},s.audio=function(){var a=b.createElement("audio"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),c.mp3=a.canPlayType("audio/mpeg;").replace(/^no$/,""),c.wav=a.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),c.m4a=(a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;")).replace(/^no$/,"")}catch(d){}return c},s.localstorage=function(){try{return localStorage.setItem(h,h),localStorage.removeItem(h),!0}catch(a){return!1}},s.sessionstorage=function(){try{return sessionStorage.setItem(h,h),sessionStorage.removeItem(h),!0}catch(a){return!1}},s.webworkers=function(){return!!a.Worker},s.applicationcache=function(){return!!a.applicationCache},s.svg=function(){return!!b.createElementNS&&!!b.createElementNS(r.svg,"svg").createSVGRect},s.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="<svg/>",(a.firstChild&&a.firstChild.namespaceURI)==r.svg},s.smil=function(){return!!b.createElementNS&&/SVGAnimate/.test(m.call(b.createElementNS(r.svg,"animate")))},s.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(m.call(b.createElementNS(r.svg,"clipPath")))};for(var L in s)C(s,L)&&(x=L.toLowerCase(),e[x]=s[L](),v.push((e[x]?"":"no-")+x));return e.input||K(),e.addTest=function(a,b){if(typeof a=="object")for(var d in a)C(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},D(""),i=k=null,function(a,b){function l(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x<style>"+b+"</style>",d.insertBefore(c.lastChild,d.firstChild)}function m(){var a=s.elements;return typeof a=="string"?a.split(" "):a}function n(a){var b=j[a[h]];return b||(b={},i++,a[h]=i,j[i]=b),b}function o(a,c,d){c||(c=b);if(k)return c.createElement(a);d||(d=n(c));var g;return d.cache[a]?g=d.cache[a].cloneNode():f.test(a)?g=(d.cache[a]=d.createElem(a)).cloneNode():g=d.createElem(a),g.canHaveChildren&&!e.test(a)&&!g.tagUrn?d.frag.appendChild(g):g}function p(a,c){a||(a=b);if(k)return a.createDocumentFragment();c=c||n(a);var d=c.frag.cloneNode(),e=0,f=m(),g=f.length;for(;e<g;e++)d.createElement(f[e]);return d}function q(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return s.shivMethods?o(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/[\w\-]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(s,b.frag)}function r(a){a||(a=b);var c=n(a);return s.shivCSS&&!g&&!c.hasCSS&&(c.hasCSS=!!l(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),k||q(a,c),a}var c="3.7.0",d=a.html5||{},e=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,f=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,g,h="_html5shiv",i=0,j={},k;(function(){try{var a=b.createElement("a");a.innerHTML="<xyz></xyz>",g="hidden"in a,k=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){g=!0,k=!0}})();var s={elements:d.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:c,shivCSS:d.shivCSS!==!1,supportsUnknownElements:k,shivMethods:d.shivMethods!==!1,type:"default",shivDocument:r,createElement:o,createDocumentFragment:p};a.html5=s,r(b)}(this,b),e._version=d,e._prefixes=n,e._domPrefixes=q,e._cssomPrefixes=p,e.mq=z,e.hasEvent=A,e.testProp=function(a){return H([a])},e.testAllProps=J,e.testStyles=y,e.prefixed=function(a,b,c){return b?J(a,b,c):J(a,"pfx")},g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+v.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f<d;f++)g=a[f].split("="),(e=z[g.shift()])&&(c=e(c,g));for(f=0;f<b;f++)c=x[f](c);return c}function g(a,e,f,g,h){var i=b(a),j=i.autoCallback;i.url.split(".").pop().split("?").shift(),i.bypass||(e&&(e=d(e)?e:e[a]||e[g]||e[a.split("/").pop().split("?")[0]]),i.instead?i.instead(a,e,f,g,h):(y[i.url]?i.noexec=!0:y[i.url]=1,f.load(i.url,i.forceCSS||!i.forceJS&&"css"==i.url.split(".").pop().split("?").shift()?"c":c,i.noexec,i.attrs,i.timeout),(d(e)||d(j))&&f.load(function(){k(),e&&e(i.origUrl,h,g),j&&j(i.origUrl,h,g),y[i.url]=2})))}function h(a,b){function c(a,c){if(a){if(e(a))c||(j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}),g(a,j,b,0,h);else if(Object(a)===a)for(n in m=function(){var b=0,c;for(c in a)a.hasOwnProperty(c)&&b++;return b}(),a)a.hasOwnProperty(n)&&(!c&&!--m&&(d(j)?j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}:j[n]=function(a){return function(){var b=[].slice.call(arguments);a&&a.apply(this,b),l()}}(k[n])),g(a[n],j,b,n,h))}else!c&&l()}var h=!!a.test,i=a.load||a.both,j=a.callback||f,k=j,l=a.complete||f,m,n;c(h?a.yep:a.nope,!!i),i&&c(i)}var i,j,l=this.yepnope.loader;if(e(a))g(a,0,l,0);else if(w(a))for(i=0;i<a.length;i++)j=a[i],e(j)?g(j,0,l,0):w(j)?B(j):Object(j)===j&&h(j,l);else Object(a)===a&&h(a,l)},B.addPrefix=function(a,b){z[a]=b},B.addFilter=function(a){x.push(a)},B.errorTimeout=1e4,null==b.readyState&&b.addEventListener&&(b.readyState="loading",b.addEventListener("DOMContentLoaded",A=function(){b.removeEventListener("DOMContentLoaded",A,0),b.readyState="complete"},0)),a.yepnope=k(),a.yepnope.executeStack=h,a.yepnope.injectJs=function(a,c,d,e,i,j){var k=b.createElement("script"),l,o,e=e||B.errorTimeout;k.src=a;for(o in d)k.setAttribute(o,d[o]);c=j?h:c||f,k.onreadystatechange=k.onload=function(){!l&&g(k.readyState)&&(l=1,c(),k.onload=k.onreadystatechange=null)},m(function(){l||(l=1,c(1))},e),i?k.onload():n.parentNode.insertBefore(k,n)},a.yepnope.injectCss=function(a,c,d,e,g,i){var e=b.createElement("link"),j,c=i?h:c||f;e.href=a,e.rel="stylesheet",e.type="text/css";for(j in d)e.setAttribute(j,d[j]);g||(n.parentNode.insertBefore(e,n),m(c,0))}}(this,document),Modernizr.load=function(){yepnope.apply(window,[].slice.call(arguments,0))};

angular.module('SignalR', [])
.constant('$', window.jQuery)
.factory('Hub', ['$', function ($) {
	//This will allow same connection to be used for all Hubs
	//It also keeps connection as singleton.
	var globalConnections = [];

	function initNewConnection(options) {
		var connection = null;
		if (options && options.rootPath) {
			connection = $.hubConnection(options.rootPath, { useDefaultPath: false });
		} else {
			connection = $.hubConnection();
		}

		connection.logging = (options && options.logging ? true : false);
		return connection;
	}

	function getConnection(options) {
		var useSharedConnection = !(options && options.useSharedConnection === false);
		if (useSharedConnection) {
			return typeof globalConnections[options.rootPath] === 'undefined' ?
			globalConnections[options.rootPath] = initNewConnection(options) :
			globalConnections[options.rootPath];
		}
		else {
			return initNewConnection(options);
		}
	}

	return function (hubName, options) {
		var Hub = this;

		Hub.connection = getConnection(options);
		Hub.proxy = Hub.connection.createHubProxy(hubName);

		Hub.on = function (event, fn) {
			Hub.proxy.on(event, fn);
		};
		Hub.invoke = function (method, args) {
			return Hub.proxy.invoke.apply(Hub.proxy, arguments)
		};
		Hub.disconnect = function () {
			Hub.connection.stop();
		};
		Hub.connect = function (queryParams) {
			var startOptions = {};
			if (options.transport) startOptions.transport = options.transport;
			if (options.jsonp) startOptions.jsonp = options.jsonp;
			if (angular.isDefined(options.withCredentials)) startOptions.withCredentials = options.withCredentials;
			if(queryParams) Hub.connection.qs = queryParams;
			return Hub.connection.start(startOptions);
		};

		if (options && options.listeners) {
			Object.getOwnPropertyNames(options.listeners)
			.filter(function (propName) {
		        	return typeof options.listeners[propName] === 'function';})
		        .forEach(function (propName) {
		        	Hub.on(propName, options.listeners[propName]);
		    	});
		}
		if (options && options.methods) {
			angular.forEach(options.methods, function (method) {
				Hub[method] = function () {
					var args = $.makeArray(arguments);
					args.unshift(method);
					return Hub.invoke.apply(Hub, args);
				};
			});
		}
		if (options && options.queryParams) {
			Hub.connection.qs = options.queryParams;
		}
		if (options && options.errorHandler) {
			Hub.connection.error(options.errorHandler);
		}
		if (options && options.stateChanged) {
		    Hub.connection.stateChanged(options.stateChanged);
		}

		//Adding additional property of promise allows to access it in rest of the application.
		if(options.autoConnect === undefined || options.autoConnect){
			Hub.promise = Hub.connect();	
		}
		
		return Hub;
	};
}]);

// Common.js package manager support (e.g. ComponentJS, WebPack)
if (typeof module !== 'undefined' && typeof exports !== 'undefined' && module.exports === exports) {
  module.exports = 'SignalR';
}

/* jquery.signalR.core.js */
/*global window:false */
/*!
 * ASP.NET SignalR JavaScript Library 2.4.3
 * http://signalr.net/
 *
 * Copyright (c) .NET Foundation. All rights reserved.
 * Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 *
 */

/// <reference path="Scripts/jquery-1.6.4.js" />
/// <reference path="jquery.signalR.version.js" />
(function ($, window, undefined) {

    var resources = {
        nojQuery: "jQuery was not found. Please ensure jQuery is referenced before the SignalR client JavaScript file.",
        noTransportOnInit: "No transport could be initialized successfully. Try specifying a different transport or none at all for auto initialization.",
        errorOnNegotiate: "Error during negotiation request.",
        stoppedWhileLoading: "The connection was stopped during page load.",
        stoppedWhileNegotiating: "The connection was stopped during the negotiate request.",
        errorParsingNegotiateResponse: "Error parsing negotiate response.",
        errorRedirectionExceedsLimit: "Negotiate redirection limit exceeded.",
        errorDuringStartRequest: "Error during start request. Stopping the connection.",
        errorFromServer: "Error message received from the server: '{0}'.",
        stoppedDuringStartRequest: "The connection was stopped during the start request.",
        errorParsingStartResponse: "Error parsing start response: '{0}'. Stopping the connection.",
        invalidStartResponse: "Invalid start response: '{0}'. Stopping the connection.",
        protocolIncompatible: "You are using a version of the client that isn't compatible with the server. Client version {0}, server version {1}.",
        aspnetCoreSignalrServer: "Detected a connection attempt to an ASP.NET Core SignalR Server. This client only supports connecting to an ASP.NET SignalR Server. See https://aka.ms/signalr-core-differences for details.",
        sendFailed: "Send failed.",
        parseFailed: "Failed at parsing response: {0}",
        longPollFailed: "Long polling request failed.",
        eventSourceFailedToConnect: "EventSource failed to connect.",
        eventSourceError: "Error raised by EventSource",
        webSocketClosed: "WebSocket closed.",
        pingServerFailedInvalidResponse: "Invalid ping response when pinging server: '{0}'.",
        pingServerFailed: "Failed to ping server.",
        pingServerFailedStatusCode: "Failed to ping server.  Server responded with status code {0}, stopping the connection.",
        pingServerFailedParse: "Failed to parse ping server response, stopping the connection.",
        noConnectionTransport: "Connection is in an invalid state, there is no transport active.",
        webSocketsInvalidState: "The Web Socket transport is in an invalid state, transitioning into reconnecting.",
        reconnectTimeout: "Couldn't reconnect within the configured timeout of {0} ms, disconnecting.",
        reconnectWindowTimeout: "The client has been inactive since {0} and it has exceeded the inactivity timeout of {1} ms. Stopping the connection.",
        jsonpNotSupportedWithAccessToken: "The JSONP protocol does not support connections that require a Bearer token to connect, such as the Azure SignalR Service."
    };

    if (typeof ($) !== "function") {
        // no jQuery!
        throw new Error(resources.nojQuery);
    }

    var signalR,
        _connection,
        _pageLoaded = (window.document.readyState === "complete"),
        _pageWindow = $(window),
        _negotiateAbortText = "__Negotiate Aborted__",
        events = {
            onStart: "onStart",
            onStarting: "onStarting",
            onReceived: "onReceived",
            onError: "onError",
            onConnectionSlow: "onConnectionSlow",
            onReconnecting: "onReconnecting",
            onReconnect: "onReconnect",
            onStateChanged: "onStateChanged",
            onDisconnect: "onDisconnect"
        },
        ajaxDefaults = {
            processData: true,
            timeout: null,
            async: true,
            global: false,
            cache: false
        },
        log = function (msg, logging) {
            if (logging === false) {
                return;
            }
            var m;
            if (typeof (window.console) === "undefined") {
                return;
            }
            m = "[" + new Date().toTimeString() + "] SignalR: " + msg;
            if (window.console.debug) {
                window.console.debug(m);
            } else if (window.console.log) {
                window.console.log(m);
            }
        },

        changeState = function (connection, expectedState, newState) {
            if (expectedState === connection.state) {
                connection.state = newState;

                $(connection).triggerHandler(events.onStateChanged, [{ oldState: expectedState, newState: newState }]);
                return true;
            }

            return false;
        },

        isDisconnecting = function (connection) {
            return connection.state === signalR.connectionState.disconnected;
        },

        supportsKeepAlive = function (connection) {
            return connection._.keepAliveData.activated &&
                connection.transport.supportsKeepAlive(connection);
        },

        configureStopReconnectingTimeout = function (connection) {
            var stopReconnectingTimeout,
                onReconnectTimeout;

            // Check if this connection has already been configured to stop reconnecting after a specified timeout.
            // Without this check if a connection is stopped then started events will be bound multiple times.
            if (!connection._.configuredStopReconnectingTimeout) {
                onReconnectTimeout = function (connection) {
                    var message = signalR._.format(signalR.resources.reconnectTimeout, connection.disconnectTimeout);
                    connection.log(message);
                    $(connection).triggerHandler(events.onError, [signalR._.error(message, /* source */ "TimeoutException")]);
                    connection.stop(/* async */ false, /* notifyServer */ false);
                };

                connection.reconnecting(function () {
                    var connection = this;

                    // Guard against state changing in a previous user defined even handler
                    if (connection.state === signalR.connectionState.reconnecting) {
                        stopReconnectingTimeout = window.setTimeout(function () { onReconnectTimeout(connection); }, connection.disconnectTimeout);
                    }
                });

                connection.stateChanged(function (data) {
                    if (data.oldState === signalR.connectionState.reconnecting) {
                        // Clear the pending reconnect timeout check
                        window.clearTimeout(stopReconnectingTimeout);
                    }
                });

                connection._.configuredStopReconnectingTimeout = true;
            }
        };

    signalR = function (url, qs, logging) {
        /// <summary>Creates a new SignalR connection for the given url</summary>
        /// <param name="url" type="String">The URL of the long polling endpoint</param>
        /// <param name="qs" type="Object">
        ///     [Optional] Custom querystring parameters to add to the connection URL.
        ///     If an object, every non-function member will be added to the querystring.
        ///     If a string, it's added to the QS as specified.
        /// </param>
        /// <param name="logging" type="Boolean">
        ///     [Optional] A flag indicating whether connection logging is enabled to the browser
        ///     console/log. Defaults to false.
        /// </param>

        return new signalR.fn.init(url, qs, logging);
    };

    signalR._ = {
        defaultContentType: "application/x-www-form-urlencoded; charset=UTF-8",

        ieVersion: (function () {
            var version,
                matches;

            if (window.navigator.appName === 'Microsoft Internet Explorer') {
                // Check if the user agent has the pattern "MSIE (one or more numbers).(one or more numbers)";
                matches = /MSIE ([0-9]+\.[0-9]+)/.exec(window.navigator.userAgent);

                if (matches) {
                    version = window.parseFloat(matches[1]);
                }
            }

            // undefined value means not IE
            return version;
        })(),

        error: function (message, source, context) {
            var e = new Error(message);
            e.source = source;

            if (typeof context !== "undefined") {
                e.context = context;
            }

            return e;
        },

        transportError: function (message, transport, source, context) {
            var e = this.error(message, source, context);
            e.transport = transport ? transport.name : undefined;
            return e;
        },

        format: function () {
            /// <summary>Usage: format("Hi {0}, you are {1}!", "Foo", 100) </summary>
            var s = arguments[0];
            for (var i = 0; i < arguments.length - 1; i++) {
                s = s.replace("{" + i + "}", arguments[i + 1]);
            }
            return s;
        },

        firefoxMajorVersion: function (userAgent) {
            // Firefox user agents: http://useragentstring.com/pages/Firefox/
            var matches = userAgent.match(/Firefox\/(\d+)/);
            if (!matches || !matches.length || matches.length < 2) {
                return 0;
            }
            return parseInt(matches[1], 10 /* radix */);
        },

        configurePingInterval: function (connection) {
            var config = connection._.config,
                onFail = function (error) {
                    $(connection).triggerHandler(events.onError, [error]);
                };

            if (config && !connection._.pingIntervalId && config.pingInterval) {
                connection._.pingIntervalId = window.setInterval(function () {
                    signalR.transports._logic.pingServer(connection).fail(onFail);
                }, config.pingInterval);
            }
        }
    };

    signalR.events = events;

    signalR.resources = resources;

    signalR.ajaxDefaults = ajaxDefaults;

    signalR.changeState = changeState;

    signalR.isDisconnecting = isDisconnecting;

    signalR.connectionState = {
        connecting: 0,
        connected: 1,
        reconnecting: 2,
        disconnected: 4
    };

    signalR.hub = {
        start: function () {
            // This will get replaced with the real hub connection start method when hubs is referenced correctly
            throw new Error("SignalR: Error loading hubs. Ensure your hubs reference is correct, e.g. <script src='/signalr/js'></script>.");
        }
    };

    // .on() was added in version 1.7.0, .load() was removed in version 3.0.0 so we fallback to .load() if .on() does
    // not exist to not break existing applications
    if (typeof _pageWindow.on === "function") {
        _pageWindow.on("load", function () { _pageLoaded = true; });
    }
    else {
        _pageWindow.load(function () { _pageLoaded = true; });
    }

    function validateTransport(requestedTransport, connection) {
        /// <summary>Validates the requested transport by cross checking it with the pre-defined signalR.transports</summary>
        /// <param name="requestedTransport" type="Object">The designated transports that the user has specified.</param>
        /// <param name="connection" type="signalR">The connection that will be using the requested transports.  Used for logging purposes.</param>
        /// <returns type="Object" />

        if ($.isArray(requestedTransport)) {
            // Go through transport array and remove an "invalid" tranports
            for (var i = requestedTransport.length - 1; i >= 0; i--) {
                var transport = requestedTransport[i];
                if ($.type(transport) !== "string" || !signalR.transports[transport]) {
                    connection.log("Invalid transport: " + transport + ", removing it from the transports list.");
                    requestedTransport.splice(i, 1);
                }
            }

            // Verify we still have transports left, if we dont then we have invalid transports
            if (requestedTransport.length === 0) {
                connection.log("No transports remain within the specified transport array.");
                requestedTransport = null;
            }
        } else if (!signalR.transports[requestedTransport] && requestedTransport !== "auto") {
            connection.log("Invalid transport: " + requestedTransport.toString() + ".");
            requestedTransport = null;
        } else if (requestedTransport === "auto" && signalR._.ieVersion <= 8) {
            // If we're doing an auto transport and we're IE8 then force longPolling, #1764
            return ["longPolling"];

        }

        return requestedTransport;
    }

    function getDefaultPort(protocol) {
        if (protocol === "http:") {
            return 80;
        } else if (protocol === "https:") {
            return 443;
        }
    }

    function addDefaultPort(protocol, url) {
        // Remove ports  from url.  We have to check if there's a / or end of line
        // following the port in order to avoid removing ports such as 8080.
        if (url.match(/:\d+$/)) {
            return url;
        } else {
            return url + ":" + getDefaultPort(protocol);
        }
    }

    function ConnectingMessageBuffer(connection, drainCallback) {
        var that = this,
            buffer = [];

        that.tryBuffer = function (message) {
            if (connection.state === $.signalR.connectionState.connecting) {
                buffer.push(message);

                return true;
            }

            return false;
        };

        that.drain = function () {
            // Ensure that the connection is connected when we drain (do not want to drain while a connection is not active)
            if (connection.state === $.signalR.connectionState.connected) {
                while (buffer.length > 0) {
                    drainCallback(buffer.shift());
                }
            }
        };

        that.clear = function () {
            buffer = [];
        };
    }

    signalR.fn = signalR.prototype = {
        init: function (url, qs, logging) {
            var $connection = $(this);

            this.url = url;
            this.qs = qs;
            this.lastError = null;
            this._ = {
                keepAliveData: {},
                connectingMessageBuffer: new ConnectingMessageBuffer(this, function (message) {
                    $connection.triggerHandler(events.onReceived, [message]);
                }),
                lastMessageAt: new Date().getTime(),
                lastActiveAt: new Date().getTime(),
                beatInterval: 5000, // Default value, will only be overridden if keep alive is enabled,
                beatHandle: null,
                totalTransportConnectTimeout: 0, // This will be the sum of the TransportConnectTimeout sent in response to negotiate and connection.transportConnectTimeout
                redirectQs: null
            };
            if (typeof (logging) === "boolean") {
                this.logging = logging;
            }
        },

        _parseResponse: function (response) {
            var that = this;

            if (!response) {
                return response;
            } else if (typeof response === "string") {
                return that.json.parse(response);
            } else {
                return response;
            }
        },

        _originalJson: window.JSON,

        json: window.JSON,

        isCrossDomain: function (url, against) {
            /// <summary>Checks if url is cross domain</summary>
            /// <param name="url" type="String">The base URL</param>
            /// <param name="against" type="Object">
            ///     An optional argument to compare the URL against, if not specified it will be set to window.location.
            ///     If specified it must contain a protocol and a host property.
            /// </param>
            var link;

            url = $.trim(url);

            against = against || window.location;

            if (url.indexOf("http") !== 0) {
                return false;
            }

            // Create an anchor tag.
            link = window.document.createElement("a");
            link.href = url;

            // When checking for cross domain we have to special case port 80 because the window.location will remove the
            return link.protocol + addDefaultPort(link.protocol, link.host) !== against.protocol + addDefaultPort(against.protocol, against.host);
        },

        ajaxDataType: "text",

        contentType: "application/json; charset=UTF-8",

        logging: false,

        state: signalR.connectionState.disconnected,

        clientProtocol: "2.1",

        // We want to support older servers since the 2.0 change is to support redirection results, which isn't
        // really breaking in the protocol. So if a user updates their client to 2.0 protocol version there's
        // no reason they can't still connect to a 1.5 server. The 2.1 protocol is sent by the client so the SignalR
        // service knows the client will use they query string returned via the RedirectUrl for subsequent requests.
        // It doesn't matter whether the server reflects back 2.1 or continues using 2.0 as the protocol version.
        supportedProtocols: ["1.5", "2.0", "2.1"],

        negotiateRedirectSupportedProtocols: ["2.0", "2.1"],

        reconnectDelay: 2000,

        transportConnectTimeout: 0,

        disconnectTimeout: 30000, // This should be set by the server in response to the negotiate request (30s default)

        reconnectWindow: 30000, // This should be set by the server in response to the negotiate request

        keepAliveWarnAt: 2 / 3, // Warn user of slow connection if we breach the X% mark of the keep alive timeout

        start: function (options, callback) {
            /// <summary>Starts the connection</summary>
            /// <param name="options" type="Object">Options map</param>
            /// <param name="callback" type="Function">A callback function to execute when the connection has started</param>
            var connection = this,
                config = {
                    pingInterval: 300000,
                    waitForPageLoad: true,
                    transport: "auto",
                    jsonp: false
                },
                initialize,
                deferred = connection._deferral || $.Deferred(), // Check to see if there is a pre-existing deferral that's being built on, if so we want to keep using it
                parser = window.document.createElement("a"),
                setConnectionUrl = function (connection, url) {
                    if (connection.url === url && connection.baseUrl) {
                        // when the url related properties are already set
                        return;
                    }

                    connection.url = url;

                    // Resolve the full url
                    parser.href = connection.url;
                    if (!parser.protocol || parser.protocol === ":") {
                        connection.protocol = window.document.location.protocol;
                        connection.host = parser.host || window.document.location.host;
                    } else {
                        connection.protocol = parser.protocol;
                        connection.host = parser.host;
                    }

                    connection.baseUrl = connection.protocol + "//" + connection.host;

                    // Set the websocket protocol
                    connection.wsProtocol = connection.protocol === "https:" ? "wss://" : "ws://";

                    // If the url is protocol relative, prepend the current windows protocol to the url.
                    if (connection.url.indexOf("//") === 0) {
                        connection.url = window.location.protocol + connection.url;
                        connection.log("Protocol relative URL detected, normalizing it to '" + connection.url + "'.");
                    }

                    if (connection.isCrossDomain(connection.url)) {
                        connection.log("Auto detected cross domain url.");

                        if (config.transport === "auto") {
                            // Cross-domain does not support foreverFrame
                            config.transport = ["webSockets", "serverSentEvents", "longPolling"];
                        }

                        if (typeof connection.withCredentials === "undefined") {
                            connection.withCredentials = true;
                        }

                        // Determine if jsonp is the only choice for negotiation, ajaxSend and ajaxAbort.
                        // i.e. if the browser doesn't supports CORS
                        // If it is, ignore any preference to the contrary, and switch to jsonp.
                        if (!$.support.cors) {
                            connection.ajaxDataType = "jsonp";
                            connection.log("Using jsonp because this browser doesn't support CORS.");
                        }

                        connection.contentType = signalR._.defaultContentType;
                    }
                };

            connection.lastError = null;

            // Persist the deferral so that if start is called multiple times the same deferral is used.
            connection._deferral = deferred;

            if (!connection.json) {
                // no JSON!
                throw new Error("SignalR: No JSON parser found. Please ensure json2.js is referenced before the SignalR.js file if you need to support clients without native JSON parsing support, e.g. IE<8.");
            }

            if ($.type(options) === "function") {
                // Support calling with single callback parameter
                callback = options;
            } else if ($.type(options) === "object") {
                $.extend(config, options);
                if ($.type(config.callback) === "function") {
                    callback = config.callback;
                }
            }

            config.transport = validateTransport(config.transport, connection);

            // If the transport is invalid throw an error and abort start
            if (!config.transport) {
                throw new Error("SignalR: Invalid transport(s) specified, aborting start.");
            }

            connection._.config = config;

            // Check to see if start is being called prior to page load
            // If waitForPageLoad is true we then want to re-direct function call to the window load event
            if (!_pageLoaded && config.waitForPageLoad === true) {
                connection._.deferredStartHandler = function () {
                    connection.start(options, callback);
                };
                _pageWindow.bind("load", connection._.deferredStartHandler);

                return deferred.promise();
            }

            // If we're already connecting just return the same deferral as the original connection start
            if (connection.state === signalR.connectionState.connecting) {
                return deferred.promise();
            } else if (changeState(connection,
                signalR.connectionState.disconnected,
                signalR.connectionState.connecting) === false) {
                // We're not connecting so try and transition into connecting.
                // If we fail to transition then we're either in connected or reconnecting.

                deferred.resolve(connection);
                return deferred.promise();
            }

            configureStopReconnectingTimeout(connection);

            // If jsonp with no/auto transport is specified, then set the transport to long polling
            // since that is the only transport for which jsonp really makes sense.
            // Some developers might actually choose to specify jsonp for same origin requests
            // as demonstrated by Issue #623.
            if (config.transport === "auto" && config.jsonp === true) {
                config.transport = "longPolling";
            }

            connection.withCredentials = config.withCredentials;

            // Save the original url so that we can reset it when we stop and restart the connection
            connection._originalUrl = connection.url;

            connection.ajaxDataType = config.jsonp ? "jsonp" : "text";

            setConnectionUrl(connection, connection.url);

            $(connection).bind(events.onStart, function (e, data) {
                if ($.type(callback) === "function") {
                    callback.call(connection);
                }
                deferred.resolve(connection);
            });

            connection._.initHandler = signalR.transports._logic.initHandler(connection);

            initialize = function (transports, index) {
                var noTransportError = signalR._.error(resources.noTransportOnInit);

                index = index || 0;
                if (index >= transports.length) {
                    if (index === 0) {
                        connection.log("No transports supported by the server were selected.");
                    } else if (index === 1) {
                        connection.log("No fallback transports were selected.");
                    } else {
                        connection.log("Fallback transports exhausted.");
                    }

                    // No transport initialized successfully
                    $(connection).triggerHandler(events.onError, [noTransportError]);
                    deferred.reject(noTransportError);
                    // Stop the connection if it has connected and move it into the disconnected state
                    connection.stop();
                    return;
                }

                // The connection was aborted
                if (connection.state === signalR.connectionState.disconnected) {
                    return;
                }

                var transportName = transports[index],
                    transport = signalR.transports[transportName],
                    onFallback = function () {
                        initialize(transports, index + 1);
                    };

                connection.transport = transport;

                try {
                    connection._.initHandler.start(transport, function () { // success
                        // Firefox 11+ doesn't allow sync XHR withCredentials: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#withCredentials
                        var isFirefox11OrGreater = signalR._.firefoxMajorVersion(window.navigator.userAgent) >= 11,
                            asyncAbort = true;

                        connection.log("The start request succeeded. Transitioning to the connected state.");

                        if (supportsKeepAlive(connection)) {
                            signalR.transports._logic.monitorKeepAlive(connection);
                        }

                        if (connection._.keepAliveData.activated) {
                            signalR.transports._logic.startHeartbeat(connection);
                        }

                        // Used to ensure low activity clients maintain their authentication.
                        // Must be configured once a transport has been decided to perform valid ping requests.
                        signalR._.configurePingInterval(connection);

                        if (!changeState(connection,
                            signalR.connectionState.connecting,
                            signalR.connectionState.connected)) {
                            connection.log("WARNING! The connection was not in the connecting state.");
                        }

                        // Drain any incoming buffered messages (messages that came in prior to connect)
                        connection._.connectingMessageBuffer.drain();

                        $(connection).triggerHandler(events.onStart);

                        // wire the stop handler for when the user leaves the page
                        _pageWindow.bind("unload", function () {
                            connection.log("Window unloading, stopping the connection.");

                            connection.stop(asyncAbort);
                        });

                        if (isFirefox11OrGreater) {
                            // Firefox does not fire cross-domain XHRs in the normal unload handler on tab close.
                            // #2400
                            _pageWindow.bind("beforeunload", function () {
                                // If connection.stop() runs runs in beforeunload and fails, it will also fail
                                // in unload unless connection.stop() runs after a timeout.
                                window.setTimeout(function () {
                                    connection.stop(asyncAbort);
                                }, 0);
                            });
                        }
                    }, onFallback);
                }
                catch (error) {
                    connection.log(transport.name + " transport threw '" + error.message + "' when attempting to start.");
                    onFallback();
                }
            };

            var url = connection.url + "/negotiate",
                onFailed = function (error, connection) {
                    var err = signalR._.error(resources.errorOnNegotiate, error, connection._.negotiateRequest);

                    $(connection).triggerHandler(events.onError, err);
                    deferred.reject(err);
                    // Stop the connection if negotiate failed
                    connection.stop();
                };

            $(connection).triggerHandler(events.onStarting);

            url = signalR.transports._logic.prepareQueryString(connection, url);

            connection.log("Negotiating with '" + url + "'.");

            // Save the ajax negotiate request object so we can abort it if stop is called while the request is in flight.
            connection._.negotiateRequest = function () {
                var res,
                    redirects = 0,
                    MAX_REDIRECTS = 100,
                    keepAliveData,
                    protocolError,
                    transports = [],
                    supportedTransports = [],
                    negotiate = function (connection, onSuccess) {
                        var url = signalR.transports._logic.prepareQueryString(connection, connection.url + "/negotiate");
                        connection.log("Negotiating with '" + url + "'.");
                        var options = {
                            url: url,
                            error: function (error, statusText) {
                                // We don't want to cause any errors if we're aborting our own negotiate request.
                                if (statusText !== _negotiateAbortText) {
                                    onFailed(error, connection);
                                } else {
                                    // This rejection will noop if the deferred has already been resolved or rejected.
                                    deferred.reject(signalR._.error(resources.stoppedWhileNegotiating, null /* error */, connection._.negotiateRequest));
                                }
                            },
                            success: onSuccess
                        };

                        if (connection.accessToken) {
                            options.headers = { "Authorization": "Bearer " + connection.accessToken };
                        }

                        return signalR.transports._logic.ajax(connection, options);
                    },
                    callback = function (result) {
                        try {
                            res = connection._parseResponse(result);
                        } catch (error) {
                            onFailed(signalR._.error(resources.errorParsingNegotiateResponse, error), connection);
                            return;
                        }

                        // Check if the server is an ASP.NET Core app
                        if (res.availableTransports) {
                            protocolError = signalR._.error(resources.aspnetCoreSignalrServer);
                            $(connection).triggerHandler(events.onError, [protocolError]);
                            deferred.reject(protocolError);
                            return;
                        }

                        if (!res.ProtocolVersion || (connection.supportedProtocols.indexOf(res.ProtocolVersion) === -1)) {
                            protocolError = signalR._.error(signalR._.format(resources.protocolIncompatible, connection.clientProtocol, res.ProtocolVersion));
                            $(connection).triggerHandler(events.onError, [protocolError]);
                            deferred.reject(protocolError);

                            return;
                        }

                        // Check for a redirect response (which must have a ProtocolVersion of 2.0 or greater)
                        // ProtocolVersion 2.1 is the highest supported by the client, so we can just check for 2.0 or 2.1 for now
                        // instead of trying to do proper version string comparison in JavaScript.
                        if (connection.negotiateRedirectSupportedProtocols.indexOf(res.ProtocolVersion) !== -1) {
                            if (res.Error) {
                                protocolError = signalR._.error(signalR._.format(resources.errorFromServer, res.Error));
                                $(connection).triggerHandler(events.onError, [protocolError]);
                                deferred.reject(protocolError);
                                return;
                            }
                            else if (res.RedirectUrl) {
                                if (redirects === MAX_REDIRECTS) {
                                    onFailed(signalR._.error(resources.errorRedirectionExceedsLimit), connection);
                                    return;
                                }

                                if (config.transport === "auto") {
                                    // Redirected connections do not support foreverFrame
                                    config.transport = ["webSockets", "serverSentEvents", "longPolling"];
                                }

                                connection.log("Received redirect to: " + res.RedirectUrl);
                                connection.accessToken = res.AccessToken;

                                var splitUrlAndQs = res.RedirectUrl.split("?", 2);
                                setConnectionUrl(connection, splitUrlAndQs[0]);

                                // Update redirectQs with query string from only the most recent RedirectUrl.
                                connection._.redirectQs = splitUrlAndQs.length === 2 ? splitUrlAndQs[1] : null;

                                if (connection.ajaxDataType === "jsonp" && connection.accessToken) {
                                    onFailed(signalR._.error(resources.jsonpNotSupportedWithAccessToken), connection);
                                    return;
                                }

                                redirects++;
                                negotiate(connection, callback);
                                return;
                            }
                        }

                        keepAliveData = connection._.keepAliveData;
                        connection.appRelativeUrl = res.Url;
                        connection.id = res.ConnectionId;
                        connection.token = res.ConnectionToken;
                        connection.webSocketServerUrl = res.WebSocketServerUrl;

                        // The long poll timeout is the ConnectionTimeout plus 10 seconds
                        connection._.pollTimeout = res.ConnectionTimeout * 1000 + 10000; // in ms

                        // Once the server has labeled the PersistentConnection as Disconnected, we should stop attempting to reconnect
                        // after res.DisconnectTimeout seconds.
                        connection.disconnectTimeout = res.DisconnectTimeout * 1000; // in ms

                        // Add the TransportConnectTimeout from the response to the transportConnectTimeout from the client to calculate the total timeout
                        connection._.totalTransportConnectTimeout = connection.transportConnectTimeout + res.TransportConnectTimeout * 1000;

                        // If we have a keep alive
                        if (res.KeepAliveTimeout) {
                            // Register the keep alive data as activated
                            keepAliveData.activated = true;

                            // Timeout to designate when to force the connection into reconnecting converted to milliseconds
                            keepAliveData.timeout = res.KeepAliveTimeout * 1000;

                            // Timeout to designate when to warn the developer that the connection may be dead or is not responding.
                            keepAliveData.timeoutWarning = keepAliveData.timeout * connection.keepAliveWarnAt;

                            // Instantiate the frequency in which we check the keep alive.  It must be short in order to not miss/pick up any changes
                            connection._.beatInterval = (keepAliveData.timeout - keepAliveData.timeoutWarning) / 3;
                        } else {
                            keepAliveData.activated = false;
                        }

                        connection.reconnectWindow = connection.disconnectTimeout + (keepAliveData.timeout || 0);

                        $.each(signalR.transports, function (key) {
                            if ((key.indexOf("_") === 0) || (key === "webSockets" && !res.TryWebSockets)) {
                                return true;
                            }
                            supportedTransports.push(key);
                        });

                        if ($.isArray(config.transport)) {
                            $.each(config.transport, function (_, transport) {
                                if ($.inArray(transport, supportedTransports) >= 0) {
                                    transports.push(transport);
                                }
                            });
                        } else if (config.transport === "auto") {
                            transports = supportedTransports;
                        } else if ($.inArray(config.transport, supportedTransports) >= 0) {
                            transports.push(config.transport);
                        }

                        initialize(transports);
                    };

                return negotiate(connection, callback);
            }();

            return deferred.promise();
        },

        starting: function (callback) {
            /// <summary>Adds a callback that will be invoked before anything is sent over the connection</summary>
            /// <param name="callback" type="Function">A callback function to execute before the connection is fully instantiated.</param>
            /// <returns type="signalR" />
            var connection = this;
            $(connection).bind(events.onStarting, function (e, data) {
                callback.call(connection);
            });
            return connection;
        },

        send: function (data) {
            /// <summary>Sends data over the connection</summary>
            /// <param name="data" type="String">The data to send over the connection</param>
            /// <returns type="signalR" />
            var connection = this;

            if (connection.state === signalR.connectionState.disconnected) {
                // Connection hasn't been started yet
                throw new Error("SignalR: Connection must be started before data can be sent. Call .start() before .send()");
            }

            if (connection.state === signalR.connectionState.connecting) {
                // Connection hasn't been started yet
                throw new Error("SignalR: Connection has not been fully initialized. Use .start().done() or .start().fail() to run logic after the connection has started.");
            }

            connection.transport.send(connection, data);
            // REVIEW: Should we return deferred here?
            return connection;
        },

        received: function (callback) {
            /// <summary>Adds a callback that will be invoked after anything is received over the connection</summary>
            /// <param name="callback" type="Function">A callback function to execute when any data is received on the connection</param>
            /// <returns type="signalR" />
            var connection = this;
            $(connection).bind(events.onReceived, function (e, data) {
                callback.call(connection, data);
            });
            return connection;
        },

        stateChanged: function (callback) {
            /// <summary>Adds a callback that will be invoked when the connection state changes</summary>
            /// <param name="callback" type="Function">A callback function to execute when the connection state changes</param>
            /// <returns type="signalR" />
            var connection = this;
            $(connection).bind(events.onStateChanged, function (e, data) {
                callback.call(connection, data);
            });
            return connection;
        },

        error: function (callback) {
            /// <summary>Adds a callback that will be invoked after an error occurs with the connection</summary>
            /// <param name="callback" type="Function">A callback function to execute when an error occurs on the connection</param>
            /// <returns type="signalR" />
            var connection = this;
            $(connection).bind(events.onError, function (e, errorData, sendData) {
                connection.lastError = errorData;
                // In practice 'errorData' is the SignalR built error object.
                // In practice 'sendData' is undefined for all error events except those triggered by
                // 'ajaxSend' and 'webSockets.send'.'sendData' is the original send payload.
                callback.call(connection, errorData, sendData);
            });
            return connection;
        },

        disconnected: function (callback) {
            /// <summary>Adds a callback that will be invoked when the client disconnects</summary>
            /// <param name="callback" type="Function">A callback function to execute when the connection is broken</param>
            /// <returns type="signalR" />
            var connection = this;
            $(connection).bind(events.onDisconnect, function (e, data) {
                callback.call(connection);
            });
            return connection;
        },

        connectionSlow: function (callback) {
            /// <summary>Adds a callback that will be invoked when the client detects a slow connection</summary>
            /// <param name="callback" type="Function">A callback function to execute when the connection is slow</param>
            /// <returns type="signalR" />
            var connection = this;
            $(connection).bind(events.onConnectionSlow, function (e, data) {
                callback.call(connection);
            });

            return connection;
        },

        reconnecting: function (callback) {
            /// <summary>Adds a callback that will be invoked when the underlying transport begins reconnecting</summary>
            /// <param name="callback" type="Function">A callback function to execute when the connection enters a reconnecting state</param>
            /// <returns type="signalR" />
            var connection = this;
            $(connection).bind(events.onReconnecting, function (e, data) {
                callback.call(connection);
            });
            return connection;
        },

        reconnected: function (callback) {
            /// <summary>Adds a callback that will be invoked when the underlying transport reconnects</summary>
            /// <param name="callback" type="Function">A callback function to execute when the connection is restored</param>
            /// <returns type="signalR" />
            var connection = this;
            $(connection).bind(events.onReconnect, function (e, data) {
                callback.call(connection);
            });
            return connection;
        },

        stop: function (async, notifyServer) {
            /// <summary>Stops listening</summary>
            /// <param name="async" type="Boolean">Whether or not to asynchronously abort the connection</param>
            /// <param name="notifyServer" type="Boolean">Whether we want to notify the server that we are aborting the connection</param>
            /// <returns type="signalR" />
            var connection = this,
                // Save deferral because this is always cleaned up
                deferral = connection._deferral;

            // Verify that we've bound a load event.
            if (connection._.deferredStartHandler) {
                // Unbind the event.
                _pageWindow.unbind("load", connection._.deferredStartHandler);
            }

            // Always clean up private non-timeout based state.
            delete connection._.config;
            delete connection._.deferredStartHandler;

            // This needs to be checked despite the connection state because a connection start can be deferred until page load.
            // If we've deferred the start due to a page load we need to unbind the "onLoad" -> start event.
            if (!_pageLoaded && (!connection._.config || connection._.config.waitForPageLoad === true)) {
                connection.log("Stopping connection prior to negotiate.");

                // If we have a deferral we should reject it
                if (deferral) {
                    deferral.reject(signalR._.error(resources.stoppedWhileLoading));
                }

                // Short-circuit because the start has not been fully started.
                return;
            }

            if (connection.state === signalR.connectionState.disconnected) {
                return;
            }

            connection.log("Stopping connection.");

            // Clear this no matter what
            window.clearTimeout(connection._.beatHandle);
            window.clearInterval(connection._.pingIntervalId);

            if (connection.transport) {
                connection.transport.stop(connection);

                if (notifyServer !== false) {
                    connection.transport.abort(connection, async);
                }

                if (supportsKeepAlive(connection)) {
                    signalR.transports._logic.stopMonitoringKeepAlive(connection);
                }

                connection.transport = null;
            }

            if (connection._.negotiateRequest) {
                // If the negotiation request has already completed this will noop.
                connection._.negotiateRequest.abort(_negotiateAbortText);
                delete connection._.negotiateRequest;
            }

            // Ensure that initHandler.stop() is called before connection._deferral is deleted
            if (connection._.initHandler) {
                connection._.initHandler.stop();
            }

            delete connection._deferral;
            delete connection.messageId;
            delete connection.groupsToken;
            delete connection.id;
            delete connection._.pingIntervalId;
            delete connection._.lastMessageAt;
            delete connection._.lastActiveAt;

            // Clear out our message buffer
            connection._.connectingMessageBuffer.clear();

            // Clean up this event
            $(connection).unbind(events.onStart);

            // Reset the URL and clear the access token
            delete connection.accessToken;
            delete connection.protocol;
            delete connection.host;
            delete connection.baseUrl;
            delete connection.wsProtocol;
            delete connection.contentType;
            connection.url = connection._originalUrl;
            connection._.redirectQs = null;

            // Trigger the disconnect event
            changeState(connection, connection.state, signalR.connectionState.disconnected);
            $(connection).triggerHandler(events.onDisconnect);

            return connection;
        },

        log: function (msg) {
            log(msg, this.logging);
        }
    };

    signalR.fn.init.prototype = signalR.fn;

    signalR.noConflict = function () {
        /// <summary>Reinstates the original value of $.connection and returns the signalR object for manual assignment</summary>
        /// <returns type="signalR" />
        if ($.connection === signalR) {
            $.connection = _connection;
        }
        return signalR;
    };

    if ($.connection) {
        _connection = $.connection;
    }

    $.connection = $.signalR = signalR;

}(window.jQuery, window));
/* jquery.signalR.transports.common.js */
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

/*global window:false */
/// <reference path="jquery.signalR.core.js" />

(function ($, window, undefined) {

    var signalR = $.signalR,
        events = $.signalR.events,
        changeState = $.signalR.changeState,
        startAbortText = "__Start Aborted__",
        transportLogic;

    signalR.transports = {};

    function beat(connection) {
        if (connection._.keepAliveData.monitoring) {
            checkIfAlive(connection);
        }

        // Ensure that we successfully marked active before continuing the heartbeat.
        if (transportLogic.markActive(connection)) {
            connection._.beatHandle = window.setTimeout(function () {
                beat(connection);
            }, connection._.beatInterval);
        }
    }

    function checkIfAlive(connection) {
        var keepAliveData = connection._.keepAliveData,
            timeElapsed;

        // Only check if we're connected
        if (connection.state === signalR.connectionState.connected) {
            timeElapsed = new Date().getTime() - connection._.lastMessageAt;

            // Check if the keep alive has completely timed out
            if (timeElapsed >= keepAliveData.timeout) {
                connection.log("Keep alive timed out.  Notifying transport that connection has been lost.");

                // Notify transport that the connection has been lost
                connection.transport.lostConnection(connection);
            } else if (timeElapsed >= keepAliveData.timeoutWarning) {
                // This is to assure that the user only gets a single warning
                if (!keepAliveData.userNotified) {
                    connection.log("Keep alive has been missed, connection may be dead/slow.");
                    $(connection).triggerHandler(events.onConnectionSlow);
                    keepAliveData.userNotified = true;
                }
            } else {
                keepAliveData.userNotified = false;
            }
        }
    }

    function getAjaxUrl(connection, path) {
        var url = connection.url + path;

        if (connection.transport) {
            url += "?transport=" + connection.transport.name;
        }

        return transportLogic.prepareQueryString(connection, url);
    }

    function InitHandler(connection) {
        this.connection = connection;

        this.startRequested = false;
        this.startCompleted = false;
        this.connectionStopped = false;
    }

    InitHandler.prototype = {
        start: function (transport, onSuccess, onFallback) {
            var that = this,
                connection = that.connection,
                failCalled = false;

            if (that.startRequested || that.connectionStopped) {
                connection.log("WARNING! " + transport.name + " transport cannot be started. Initialization ongoing or completed.");
                return;
            }

            connection.log(transport.name + " transport starting.");

            transport.start(connection, function () {
                if (!failCalled) {
                    that.initReceived(transport, onSuccess);
                }
            }, function (error) {
                // Don't allow the same transport to cause onFallback to be called twice
                if (!failCalled) {
                    failCalled = true;
                    that.transportFailed(transport, error, onFallback);
                }

                // Returns true if the transport should stop;
                // false if it should attempt to reconnect
                return !that.startCompleted || that.connectionStopped;
            });

            that.transportTimeoutHandle = window.setTimeout(function () {
                if (!failCalled) {
                    failCalled = true;
                    connection.log(transport.name + " transport timed out when trying to connect.");
                    that.transportFailed(transport, undefined, onFallback);
                }
            }, connection._.totalTransportConnectTimeout);
        },

        stop: function () {
            this.connectionStopped = true;
            window.clearTimeout(this.transportTimeoutHandle);
            signalR.transports._logic.tryAbortStartRequest(this.connection);
        },

        initReceived: function (transport, onSuccess) {
            var that = this,
                connection = that.connection;

            if (that.startRequested) {
                connection.log("WARNING! The client received multiple init messages.");
                return;
            }

            if (that.connectionStopped) {
                return;
            }

            that.startRequested = true;
            window.clearTimeout(that.transportTimeoutHandle);

            connection.log(transport.name + " transport connected. Initiating start request.");
            signalR.transports._logic.ajaxStart(connection, function () {
                that.startCompleted = true;
                onSuccess();
            });
        },

        transportFailed: function (transport, error, onFallback) {
            var connection = this.connection,
                deferred = connection._deferral,
                wrappedError;

            if (this.connectionStopped) {
                return;
            }

            window.clearTimeout(this.transportTimeoutHandle);

            if (!this.startRequested) {
                transport.stop(connection);

                connection.log(transport.name + " transport failed to connect. Attempting to fall back.");
                onFallback();
            } else if (!this.startCompleted) {
                // Do not attempt to fall back if a start request is ongoing during a transport failure.
                // Instead, trigger an error and stop the connection.
                wrappedError = signalR._.error(signalR.resources.errorDuringStartRequest, error);

                connection.log(transport.name + " transport failed during the start request. Stopping the connection.");
                $(connection).triggerHandler(events.onError, [wrappedError]);
                if (deferred) {
                    deferred.reject(wrappedError);
                }

                connection.stop();
            } else {
                // The start request has completed, but the connection has not stopped.
                // No need to do anything here. The transport should attempt its normal reconnect logic.
            }
        }
    };

    transportLogic = signalR.transports._logic = {
        ajax: function (connection, options) {
            return $.ajax(
                $.extend(/*deep copy*/ true, {}, $.signalR.ajaxDefaults, {
                    type: "GET",
                    data: {},
                    xhrFields: { withCredentials: connection.withCredentials },
                    contentType: connection.contentType,
                    dataType: connection.ajaxDataType
                }, options));
        },

        pingServer: function (connection) {
            /// <summary>Pings the server</summary>
            /// <param name="connection" type="signalr">Connection associated with the server ping</param>
            /// <returns type="signalR" />
            var url,
                xhr,
                deferral = $.Deferred();

            if (connection.transport) {
                url = connection.url + "/ping";

                url = transportLogic.addQs(url, connection.qs);

                xhr = transportLogic.ajax(connection, {
                    url: url,
                    headers: connection.accessToken ? { "Authorization": "Bearer " + connection.accessToken } : {},
                    success: function (result) {
                        var data;

                        try {
                            data = connection._parseResponse(result);
                        }
                        catch (error) {
                            deferral.reject(
                                signalR._.transportError(
                                    signalR.resources.pingServerFailedParse,
                                    connection.transport,
                                    error,
                                    xhr
                                )
                            );
                            connection.stop();
                            return;
                        }

                        if (data.Response === "pong") {
                            deferral.resolve();
                        }
                        else {
                            deferral.reject(
                                signalR._.transportError(
                                    signalR._.format(signalR.resources.pingServerFailedInvalidResponse, result),
                                    connection.transport,
                                    null /* error */,
                                    xhr
                                )
                            );
                        }
                    },
                    error: function (error) {
                        if (error.status === 401 || error.status === 403) {
                            deferral.reject(
                                signalR._.transportError(
                                    signalR._.format(signalR.resources.pingServerFailedStatusCode, error.status),
                                    connection.transport,
                                    error,
                                    xhr
                                )
                            );
                            connection.stop();
                        }
                        else {
                            deferral.reject(
                                signalR._.transportError(
                                    signalR.resources.pingServerFailed,
                                    connection.transport,
                                    error,
                                    xhr
                                )
                            );
                        }
                    }
                });
            }
            else {
                deferral.reject(
                    signalR._.transportError(
                        signalR.resources.noConnectionTransport,
                        connection.transport
                    )
                );
            }

            return deferral.promise();
        },

        prepareQueryString: function (connection, url) {
            var preparedUrl;

            // Use addQs to start since it handles the ?/& prefix for us
            preparedUrl = transportLogic.addQs(url, "clientProtocol=" + connection.clientProtocol);

            if (typeof (connection._.redirectQs) === "string") {
                // Add the redirect-specified query string params if any
                preparedUrl = transportLogic.addQs(preparedUrl, connection._.redirectQs);
            } else {
                // Otherwise, add the user-specified query string params if any
                preparedUrl = transportLogic.addQs(preparedUrl, connection.qs);
            }

            if (connection.token) {
                preparedUrl += "&connectionToken=" + window.encodeURIComponent(connection.token);
            }

            if (connection.data) {
                preparedUrl += "&connectionData=" + window.encodeURIComponent(connection.data);
            }

            return preparedUrl;
        },

        addQs: function (url, qs) {
            var appender = url.indexOf("?") !== -1 ? "&" : "?",
                firstChar;

            if (!qs) {
                return url;
            }

            if (typeof (qs) === "object") {
                return url + appender + $.param(qs);
            }

            if (typeof (qs) === "string") {
                firstChar = qs.charAt(0);

                if (firstChar === "?" || firstChar === "&") {
                    appender = "";
                }

                return url + appender + qs;
            }

            throw new Error("Query string property must be either a string or object.");
        },

        // BUG #2953: The url needs to be same otherwise it will cause a memory leak
        getUrl: function (connection, transport, reconnecting, poll, ajaxPost) {
            /// <summary>Gets the url for making a GET based connect request</summary>
            var baseUrl = transport === "webSockets" ? "" : connection.baseUrl,
                url = baseUrl + connection.appRelativeUrl,
                qs = "transport=" + transport;

            if (!ajaxPost && connection.groupsToken) {
                qs += "&groupsToken=" + window.encodeURIComponent(connection.groupsToken);
            }

            if (!reconnecting) {
                url += "/connect";
            } else {
                if (poll) {
                    // longPolling transport specific
                    url += "/poll";
                } else {
                    url += "/reconnect";
                }

                if (!ajaxPost && connection.messageId) {
                    qs += "&messageId=" + window.encodeURIComponent(connection.messageId);
                }
            }
            url += "?" + qs;
            url = transportLogic.prepareQueryString(connection, url);

            // With sse or ws, access_token in request header is not supported
            if (connection.transport && connection.accessToken) {
                if (connection.transport.name === "serverSentEvents" || connection.transport.name === "webSockets") {
                    url += "&access_token=" + window.encodeURIComponent(connection.accessToken);
                }
            }

            if (!ajaxPost) {
                url += "&tid=" + Math.floor(Math.random() * 11);
            }

            return url;
        },

        maximizePersistentResponse: function (minPersistentResponse) {
            return {
                MessageId: minPersistentResponse.C,
                Messages: minPersistentResponse.M,
                Initialized: typeof (minPersistentResponse.S) !== "undefined" ? true : false,
                ShouldReconnect: typeof (minPersistentResponse.T) !== "undefined" ? true : false,
                LongPollDelay: minPersistentResponse.L,
                GroupsToken: minPersistentResponse.G,
                Error: minPersistentResponse.E
            };
        },

        updateGroups: function (connection, groupsToken) {
            if (groupsToken) {
                connection.groupsToken = groupsToken;
            }
        },

        stringifySend: function (connection, message) {
            if (typeof (message) === "string" || typeof (message) === "undefined" || message === null) {
                return message;
            }
            return connection.json.stringify(message);
        },

        ajaxSend: function (connection, data) {
            var payload = transportLogic.stringifySend(connection, data),
                url = getAjaxUrl(connection, "/send"),
                xhr,
                onFail = function (error, connection) {
                    $(connection).triggerHandler(events.onError, [signalR._.transportError(signalR.resources.sendFailed, connection.transport, error, xhr), data]);
                };


            xhr = transportLogic.ajax(connection, {
                url: url,
                type: connection.ajaxDataType === "jsonp" ? "GET" : "POST",
                contentType: signalR._.defaultContentType,
                headers: connection.accessToken ? { "Authorization": "Bearer " + connection.accessToken } : {},
                data: {
                    data: payload
                },
                success: function (result) {
                    var res;

                    if (result) {
                        try {
                            res = connection._parseResponse(result);
                        }
                        catch (error) {
                            onFail(error, connection);
                            connection.stop();
                            return;
                        }

                        transportLogic.triggerReceived(connection, res);
                    }
                },
                error: function (error, textStatus) {
                    if (textStatus === "abort" || textStatus === "parsererror") {
                        // The parsererror happens for sends that don't return any data, and hence
                        // don't write the jsonp callback to the response. This is harder to fix on the server
                        // so just hack around it on the client for now.
                        return;
                    }

                    onFail(error, connection);
                }
            });

            return xhr;
        },

        ajaxAbort: function (connection, async) {
            if (typeof (connection.transport) === "undefined") {
                return;
            }

            // Async by default unless explicitly overidden
            async = typeof async === "undefined" ? true : async;

            var url = getAjaxUrl(connection, "/abort");

            var requestHeaders = connection.accessToken ? { "Authorization": "Bearer " + connection.accessToken } : {};

            //option #1 - send "fetch" with keepalive
            if (window.fetch) {
                // use the fetch API with keepalive
                window.fetch(url, {
                    method: "POST",
                    keepalive: true,
                    headers: requestHeaders,
                    credentials: connection.withCredentials === true ? "include" : "same-origin"
                });
            }
            else { 
                // fetch is not available - fallback to $.ajax
                transportLogic.ajax(connection, {
                    url: url,
                    async: async,
                    timeout: 1000,
                    type: "POST",
                    headers: requestHeaders,
                    dataType: "text" // We don't want to use JSONP here even when JSONP is enabled
                });
            }

            connection.log("Fired ajax abort async = " + async + ".");
        },

        ajaxStart: function (connection, onSuccess) {
            var rejectDeferred = function (error) {
                    var deferred = connection._deferral;
                    if (deferred) {
                        deferred.reject(error);
                    }
                },
                triggerStartError = function (error) {
                    connection.log("The start request failed. Stopping the connection.");
                    $(connection).triggerHandler(events.onError, [error]);
                    rejectDeferred(error);
                    connection.stop();
                };

            connection._.startRequest = transportLogic.ajax(connection, {
                url: getAjaxUrl(connection, "/start"),
                headers: connection.accessToken ? { "Authorization": "Bearer " + connection.accessToken } : {},
                success: function (result, statusText, xhr) {
                    var data;

                    try {
                        data = connection._parseResponse(result);
                    } catch (error) {
                        triggerStartError(signalR._.error(
                            signalR._.format(signalR.resources.errorParsingStartResponse, result),
                            error, xhr));
                        return;
                    }

                    if (data.Response === "started") {
                        onSuccess();
                    } else {
                        triggerStartError(signalR._.error(
                            signalR._.format(signalR.resources.invalidStartResponse, result),
                            null /* error */, xhr));
                    }
                },
                error: function (xhr, statusText, error) {
                    if (statusText !== startAbortText) {
                        triggerStartError(signalR._.error(
                            signalR.resources.errorDuringStartRequest,
                            error, xhr));
                    } else {
                        // Stop has been called, no need to trigger the error handler
                        // or stop the connection again with onStartError
                        connection.log("The start request aborted because connection.stop() was called.");
                        rejectDeferred(signalR._.error(
                            signalR.resources.stoppedDuringStartRequest,
                            null /* error */, xhr));
                    }
                }
            });
        },

        tryAbortStartRequest: function (connection) {
            if (connection._.startRequest) {
                // If the start request has already completed this will noop.
                connection._.startRequest.abort(startAbortText);
                delete connection._.startRequest;
            }
        },

        tryInitialize: function (connection, persistentResponse, onInitialized) {
            if (persistentResponse.Initialized && onInitialized) {
                onInitialized();
            } else if (persistentResponse.Initialized) {
                connection.log("WARNING! The client received an init message after reconnecting.");
            }

        },

        triggerReceived: function (connection, data) {
            if (!connection._.connectingMessageBuffer.tryBuffer(data)) {
                $(connection).triggerHandler(events.onReceived, [data]);
            }
        },

        processMessages: function (connection, minData, onInitialized) {
            var data;

            if(minData && (typeof minData.I !== "undefined")) {
                // This is a response to a message the client sent
                transportLogic.triggerReceived(connection, minData);
                return;
            }

            // Update the last message time stamp
            transportLogic.markLastMessage(connection);

            if (minData) {
                // This is a message send directly to the client
                data = transportLogic.maximizePersistentResponse(minData);

                if (data.Error) {
                    // This is a global error, stop the connection.
                    connection.log("Received an error message from the server: " + minData.E);
                    $(connection).triggerHandler(signalR.events.onError, [signalR._.error(minData.E, /* source */ "ServerError")]);
                    connection.stop(/* async */ false, /* notifyServer */ false);
                    return;
                }

                transportLogic.updateGroups(connection, data.GroupsToken);

                if (data.MessageId) {
                    connection.messageId = data.MessageId;
                }

                if (data.Messages) {
                    $.each(data.Messages, function (index, message) {
                        transportLogic.triggerReceived(connection, message);
                    });

                    transportLogic.tryInitialize(connection, data, onInitialized);
                }
            }
        },

        monitorKeepAlive: function (connection) {
            var keepAliveData = connection._.keepAliveData;

            // If we haven't initiated the keep alive timeouts then we need to
            if (!keepAliveData.monitoring) {
                keepAliveData.monitoring = true;

                transportLogic.markLastMessage(connection);

                // Save the function so we can unbind it on stop
                connection._.keepAliveData.reconnectKeepAliveUpdate = function () {
                    // Mark a new message so that keep alive doesn't time out connections
                    transportLogic.markLastMessage(connection);
                };

                // Update Keep alive on reconnect
                $(connection).bind(events.onReconnect, connection._.keepAliveData.reconnectKeepAliveUpdate);

                connection.log("Now monitoring keep alive with a warning timeout of " + keepAliveData.timeoutWarning + ", keep alive timeout of " + keepAliveData.timeout + " and disconnecting timeout of " + connection.disconnectTimeout);
            } else {
                connection.log("Tried to monitor keep alive but it's already being monitored.");
            }
        },

        stopMonitoringKeepAlive: function (connection) {
            var keepAliveData = connection._.keepAliveData;

            // Only attempt to stop the keep alive monitoring if its being monitored
            if (keepAliveData.monitoring) {
                // Stop monitoring
                keepAliveData.monitoring = false;

                // Remove the updateKeepAlive function from the reconnect event
                $(connection).unbind(events.onReconnect, connection._.keepAliveData.reconnectKeepAliveUpdate);

                // Clear all the keep alive data
                connection._.keepAliveData = {};
                connection.log("Stopping the monitoring of the keep alive.");
            }
        },

        startHeartbeat: function (connection) {
            connection._.lastActiveAt = new Date().getTime();
            beat(connection);
        },

        markLastMessage: function (connection) {
            connection._.lastMessageAt = new Date().getTime();
            connection._.lastActiveAt = connection._.lastMessageAt;
        },

        markActive: function (connection) {
            if (transportLogic.verifyLastActive(connection)) {
                connection._.lastActiveAt = new Date().getTime();
                return true;
            }

            return false;
        },

        isConnectedOrReconnecting: function (connection) {
            return connection.state === signalR.connectionState.connected ||
                   connection.state === signalR.connectionState.reconnecting;
        },

        ensureReconnectingState: function (connection) {
            if (changeState(connection,
                        signalR.connectionState.connected,
                        signalR.connectionState.reconnecting) === true) {
                $(connection).triggerHandler(events.onReconnecting);
            }
            return connection.state === signalR.connectionState.reconnecting;
        },

        clearReconnectTimeout: function (connection) {
            if (connection && connection._.reconnectTimeout) {
                window.clearTimeout(connection._.reconnectTimeout);
                delete connection._.reconnectTimeout;
            }
        },

        verifyLastActive: function (connection) {
            // If there is no keep alive configured, we cannot assume that timer callbacks will
            // run frequently enough to keep lastActiveAt updated.
            // https://github.com/SignalR/SignalR/issues/4536
            if (!connection._.keepAliveData.activated ||
                new Date().getTime() - connection._.lastActiveAt < connection.reconnectWindow) {
                return true;
            }

            var message = signalR._.format(signalR.resources.reconnectWindowTimeout, new Date(connection._.lastActiveAt), connection.reconnectWindow);
            connection.log(message);
            $(connection).triggerHandler(events.onError, [signalR._.error(message, /* source */ "TimeoutException")]);
            connection.stop(/* async */ false, /* notifyServer */ false);
            return false;
        },

        reconnect: function (connection, transportName) {
            var transport = signalR.transports[transportName];

            // We should only set a reconnectTimeout if we are currently connected
            // and a reconnectTimeout isn't already set.
            if (transportLogic.isConnectedOrReconnecting(connection) && !connection._.reconnectTimeout) {
                // Need to verify before the setTimeout occurs because an application sleep could occur during the setTimeout duration.
                if (!transportLogic.verifyLastActive(connection)) {
                    return;
                }

                connection._.reconnectTimeout = window.setTimeout(function () {
                    if (!transportLogic.verifyLastActive(connection)) {
                        return;
                    }

                    transport.stop(connection);

                    if (transportLogic.ensureReconnectingState(connection)) {
                        connection.log(transportName + " reconnecting.");
                        transport.start(connection);
                    }
                }, connection.reconnectDelay);
            }
        },

        handleParseFailure: function (connection, result, error, onFailed, context) {
            var wrappedError = signalR._.transportError(
                signalR._.format(signalR.resources.parseFailed, result),
                connection.transport,
                error,
                context);

            // If we're in the initialization phase trigger onFailed, otherwise stop the connection.
            if (onFailed && onFailed(wrappedError)) {
                connection.log("Failed to parse server response while attempting to connect.");
            } else {
                $(connection).triggerHandler(events.onError, [wrappedError]);
                connection.stop();
            }
        },

        initHandler: function (connection) {
            return new InitHandler(connection);
        },

        foreverFrame: {
            count: 0,
            connections: {}
        }
    };

}(window.jQuery, window));
/* jquery.signalR.transports.webSockets.js */
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.


/*global window:false */
/// <reference path="jquery.signalR.transports.common.js" />

(function ($, window, undefined) {

    var signalR = $.signalR,
        events = $.signalR.events,
        changeState = $.signalR.changeState,
        transportLogic = signalR.transports._logic;

    signalR.transports.webSockets = {
        name: "webSockets",

        supportsKeepAlive: function () {
            return true;
        },

        send: function (connection, data) {
            var payload = transportLogic.stringifySend(connection, data);

            try {
                connection.socket.send(payload);
            } catch (ex) {
                $(connection).triggerHandler(events.onError,
                    [signalR._.transportError(
                        signalR.resources.webSocketsInvalidState,
                        connection.transport,
                        ex,
                        connection.socket
                    ),
                    data]);
            }
        },

        start: function (connection, onSuccess, onFailed) {
            var url,
                opened = false,
                that = this,
                reconnecting = !onSuccess,
                $connection = $(connection);

            if (!window.WebSocket) {
                onFailed();
                return;
            }

            if (!connection.socket) {
                if (connection.webSocketServerUrl) {
                    url = connection.webSocketServerUrl;
                } else {
                    url = connection.wsProtocol + connection.host;
                }

                url += transportLogic.getUrl(connection, this.name, reconnecting);

                connection.log("Connecting to websocket endpoint '" + url + "'.");
                connection.socket = new window.WebSocket(url);

                connection.socket.onopen = function () {
                    opened = true;
                    connection.log("Websocket opened.");

                    transportLogic.clearReconnectTimeout(connection);

                    if (changeState(connection,
                                    signalR.connectionState.reconnecting,
                                    signalR.connectionState.connected) === true) {
                        $connection.triggerHandler(events.onReconnect);
                    }
                };

                connection.socket.onclose = function (event) {
                    var error;

                    // Only handle a socket close if the close is from the current socket.
                    // Sometimes on disconnect the server will push down an onclose event
                    // to an expired socket.

                    if (this === connection.socket) {
                        if (opened && typeof event.wasClean !== "undefined" && event.wasClean === false) {
                            // Ideally this would use the websocket.onerror handler (rather than checking wasClean in onclose) but
                            // I found in some circumstances Chrome won't call onerror. This implementation seems to work on all browsers.
                            error = signalR._.transportError(
                                signalR.resources.webSocketClosed,
                                connection.transport,
                                event);

                            connection.log("Unclean disconnect from websocket: " + (event.reason || "[no reason given]."));
                        } else {
                            connection.log("Websocket closed.");
                        }

                        if (!onFailed || !onFailed(error)) {
                            if (error) {
                                $(connection).triggerHandler(events.onError, [error]);
                            }

                            that.reconnect(connection);
                        }
                    }
                };

                connection.socket.onmessage = function (event) {
                    var data;

                    try {
                        data = connection._parseResponse(event.data);
                    }
                    catch (error) {
                        transportLogic.handleParseFailure(connection, event.data, error, onFailed, event);
                        return;
                    }

                    if (data) {
                        transportLogic.processMessages(connection, data, onSuccess);
                    }
                };
            }
        },

        reconnect: function (connection) {
            transportLogic.reconnect(connection, this.name);
        },

        lostConnection: function (connection) {
            this.reconnect(connection);
        },

        stop: function (connection) {
            // Don't trigger a reconnect after stopping
            transportLogic.clearReconnectTimeout(connection);

            if (connection.socket) {
                connection.log("Closing the Websocket.");
                connection.socket.close();
                connection.socket = null;
            }
        },

        abort: function (connection, async) {
            transportLogic.ajaxAbort(connection, async);
        }
    };

}(window.jQuery, window));
/* jquery.signalR.transports.serverSentEvents.js */
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.


/*global window:false */
/// <reference path="jquery.signalR.transports.common.js" />

(function ($, window, undefined) {

    var signalR = $.signalR,
        events = $.signalR.events,
        changeState = $.signalR.changeState,
        transportLogic = signalR.transports._logic,
        clearReconnectAttemptTimeout = function (connection) {
            window.clearTimeout(connection._.reconnectAttemptTimeoutHandle);
            delete connection._.reconnectAttemptTimeoutHandle;
        };

    signalR.transports.serverSentEvents = {
        name: "serverSentEvents",

        supportsKeepAlive: function () {
            return true;
        },

        timeOut: 3000,

        start: function (connection, onSuccess, onFailed) {
            var that = this,
                opened = false,
                $connection = $(connection),
                reconnecting = !onSuccess,
                url;

            if (connection.eventSource) {
                connection.log("The connection already has an event source. Stopping it.");
                connection.stop();
            }

            if (!window.EventSource) {
                if (onFailed) {
                    connection.log("This browser doesn't support SSE.");
                    onFailed();
                }
                return;
            }

            url = transportLogic.getUrl(connection, this.name, reconnecting);

            try {
                connection.log("Attempting to connect to SSE endpoint '" + url + "'.");
                connection.eventSource = new window.EventSource(url, { withCredentials: connection.withCredentials });
            }
            catch (e) {
                connection.log("EventSource failed trying to connect with error " + e.Message + ".");
                if (onFailed) {
                    // The connection failed, call the failed callback
                    onFailed();
                } else {
                    $connection.triggerHandler(events.onError, [signalR._.transportError(signalR.resources.eventSourceFailedToConnect, connection.transport, e)]);
                    if (reconnecting) {
                        // If we were reconnecting, rather than doing initial connect, then try reconnect again
                        that.reconnect(connection);
                    }
                }
                return;
            }

            if (reconnecting) {
                connection._.reconnectAttemptTimeoutHandle = window.setTimeout(function () {
                    if (opened === false) {
                        // If we're reconnecting and the event source is attempting to connect,
                        // don't keep retrying. This causes duplicate connections to spawn.
                        if (connection.eventSource.readyState !== window.EventSource.OPEN) {
                            // If we were reconnecting, rather than doing initial connect, then try reconnect again
                            that.reconnect(connection);
                        }
                    }
                },
                that.timeOut);
            }

            connection.eventSource.addEventListener("open", function (e) {
                connection.log("EventSource connected.");

                clearReconnectAttemptTimeout(connection);
                transportLogic.clearReconnectTimeout(connection);

                if (opened === false) {
                    opened = true;

                    if (changeState(connection,
                                         signalR.connectionState.reconnecting,
                                         signalR.connectionState.connected) === true) {
                        $connection.triggerHandler(events.onReconnect);
                    }
                }
            }, false);

            connection.eventSource.addEventListener("message", function (e) {
                var res;

                // process messages
                if (e.data === "initialized") {
                    return;
                }

                try {
                    res = connection._parseResponse(e.data);
                }
                catch (error) {
                    transportLogic.handleParseFailure(connection, e.data, error, onFailed, e);
                    return;
                }

                transportLogic.processMessages(connection, res, onSuccess);
            }, false);

            connection.eventSource.addEventListener("error", function (e) {
                var error = signalR._.transportError(
                    signalR.resources.eventSourceError,
                    connection.transport,
                    e);

                // Only handle an error if the error is from the current Event Source.
                // Sometimes on disconnect the server will push down an error event
                // to an expired Event Source.
                if (this !== connection.eventSource) {
                    return;
                }

                if (onFailed && onFailed(error)) {
                    return;
                }

                connection.log("EventSource readyState: " + connection.eventSource.readyState + ".");

                if (e.eventPhase === window.EventSource.CLOSED) {
                    // We don't use the EventSource's native reconnect function as it
                    // doesn't allow us to change the URL when reconnecting. We need
                    // to change the URL to not include the /connect suffix, and pass
                    // the last message id we received.
                    connection.log("EventSource reconnecting due to the server connection ending.");
                    that.reconnect(connection);
                } else {
                    // connection error
                    connection.log("EventSource error.");
                    $connection.triggerHandler(events.onError, [error]);
                }
            }, false);
        },

        reconnect: function (connection) {
            transportLogic.reconnect(connection, this.name);
        },

        lostConnection: function (connection) {
            this.reconnect(connection);
        },

        send: function (connection, data) {
            transportLogic.ajaxSend(connection, data);
        },

        stop: function (connection) {
            // Don't trigger a reconnect after stopping
            clearReconnectAttemptTimeout(connection);
            transportLogic.clearReconnectTimeout(connection);

            if (connection && connection.eventSource) {
                connection.log("EventSource calling close().");
                connection.eventSource.close();
                connection.eventSource = null;
                delete connection.eventSource;
            }
        },

        abort: function (connection, async) {
            transportLogic.ajaxAbort(connection, async);
        }
    };

}(window.jQuery, window));
/* jquery.signalR.transports.foreverFrame.js */
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.


/*global window:false */
/// <reference path="jquery.signalR.transports.common.js" />

(function ($, window, undefined) {

    var signalR = $.signalR,
        events = $.signalR.events,
        changeState = $.signalR.changeState,
        transportLogic = signalR.transports._logic,
        createFrame = function () {
            var frame = window.document.createElement("iframe");
            frame.setAttribute("style", "position:absolute;top:0;left:0;width:0;height:0;visibility:hidden;");
            return frame;
        },
        // Used to prevent infinite loading icon spins in older versions of ie
        // We build this object inside a closure so we don't pollute the rest of
        // the foreverFrame transport with unnecessary functions/utilities.
        loadPreventer = (function () {
            var loadingFixIntervalId = null,
                loadingFixInterval = 1000,
                attachedTo = 0;

            return {
                prevent: function () {
                    // Prevent additional iframe removal procedures from newer browsers
                    if (signalR._.ieVersion <= 8) {
                        // We only ever want to set the interval one time, so on the first attachedTo
                        if (attachedTo === 0) {
                            // Create and destroy iframe every 3 seconds to prevent loading icon, super hacky
                            loadingFixIntervalId = window.setInterval(function () {
                                var tempFrame = createFrame();

                                window.document.body.appendChild(tempFrame);
                                window.document.body.removeChild(tempFrame);

                                tempFrame = null;
                            }, loadingFixInterval);
                        }

                        attachedTo++;
                    }
                },
                cancel: function () {
                    // Only clear the interval if there's only one more object that the loadPreventer is attachedTo
                    if (attachedTo === 1) {
                        window.clearInterval(loadingFixIntervalId);
                    }

                    if (attachedTo > 0) {
                        attachedTo--;
                    }
                }
            };
        })();

    signalR.transports.foreverFrame = {
        name: "foreverFrame",

        supportsKeepAlive: function () {
            return true;
        },

        // Added as a value here so we can create tests to verify functionality
        iframeClearThreshold: 50,

        start: function (connection, onSuccess, onFailed) {
            if (connection.accessToken) {
                if (onFailed) {
                    connection.log("Forever Frame does not support connections that require a Bearer token to connect, such as the Azure SignalR Service.");
                    onFailed();
                }
                return;
            }

            var that = this,
                frameId = (transportLogic.foreverFrame.count += 1),
                url,
                frame = createFrame(),
                frameLoadHandler = function () {
                    connection.log("Forever frame iframe finished loading and is no longer receiving messages.");
                    if (!onFailed || !onFailed()) {
                        that.reconnect(connection);
                    }
                };

            if (window.EventSource) {
                // If the browser supports SSE, don't use Forever Frame
                if (onFailed) {
                    connection.log("Forever Frame is not supported by SignalR on browsers with SSE support.");
                    onFailed();
                }
                return;
            }

            frame.setAttribute("data-signalr-connection-id", connection.id);

            // Start preventing loading icon
            // This will only perform work if the loadPreventer is not attached to another connection.
            loadPreventer.prevent();

            // Build the url
            url = transportLogic.getUrl(connection, this.name);
            url += "&frameId=" + frameId;

            // add frame to the document prior to setting URL to avoid caching issues.
            window.document.documentElement.appendChild(frame);

            connection.log("Binding to iframe's load event.");

            if (frame.addEventListener) {
                frame.addEventListener("load", frameLoadHandler, false);
            } else if (frame.attachEvent) {
                frame.attachEvent("onload", frameLoadHandler);
            }

            frame.src = url;
            transportLogic.foreverFrame.connections[frameId] = connection;

            connection.frame = frame;
            connection.frameId = frameId;

            if (onSuccess) {
                connection.onSuccess = function () {
                    connection.log("Iframe transport started.");
                    onSuccess();
                };
            }
        },

        reconnect: function (connection) {
            var that = this;

            // Need to verify connection state and verify before the setTimeout occurs because an application sleep could occur during the setTimeout duration.
            if (transportLogic.isConnectedOrReconnecting(connection) && transportLogic.verifyLastActive(connection)) {
                window.setTimeout(function () {
                    // Verify that we're ok to reconnect.
                    if (!transportLogic.verifyLastActive(connection)) {
                        return;
                    }

                    if (connection.frame && transportLogic.ensureReconnectingState(connection)) {
                        var frame = connection.frame,
                            src = transportLogic.getUrl(connection, that.name, true) + "&frameId=" + connection.frameId;
                        connection.log("Updating iframe src to '" + src + "'.");
                        frame.src = src;
                    }
                }, connection.reconnectDelay);
            }
        },

        lostConnection: function (connection) {
            this.reconnect(connection);
        },

        send: function (connection, data) {
            transportLogic.ajaxSend(connection, data);
        },

        receive: function (connection, data) {
            var cw,
                body,
                response;

            if (connection.json !== connection._originalJson) {
                // If there's a custom JSON parser configured then serialize the object
                // using the original (browser) JSON parser and then deserialize it using
                // the custom parser (connection._parseResponse does that). This is so we
                // can easily send the response from the server as "raw" JSON but still
                // support custom JSON deserialization in the browser.
                data = connection._originalJson.stringify(data);
            }

            response = connection._parseResponse(data);

            transportLogic.processMessages(connection, response, connection.onSuccess);

            // Protect against connection stopping from a callback trigger within the processMessages above.
            if (connection.state === $.signalR.connectionState.connected) {
                // Delete the script & div elements
                connection.frameMessageCount = (connection.frameMessageCount || 0) + 1;
                if (connection.frameMessageCount > signalR.transports.foreverFrame.iframeClearThreshold) {
                    connection.frameMessageCount = 0;
                    cw = connection.frame.contentWindow || connection.frame.contentDocument;
                    if (cw && cw.document && cw.document.body) {
                        body = cw.document.body;

                        // Remove all the child elements from the iframe's body to conserver memory
                        while (body.firstChild) {
                            body.removeChild(body.firstChild);
                        }
                    }
                }
            }
        },

        stop: function (connection) {
            var cw = null;

            // Stop attempting to prevent loading icon
            loadPreventer.cancel();

            if (connection.frame) {
                if (connection.frame.stop) {
                    connection.frame.stop();
                } else {
                    try {
                        cw = connection.frame.contentWindow || connection.frame.contentDocument;
                        if (cw.document && cw.document.execCommand) {
                            cw.document.execCommand("Stop");
                        }
                    }
                    catch (e) {
                        connection.log("Error occurred when stopping foreverFrame transport. Message = " + e.message + ".");
                    }
                }

                // Ensure the iframe is where we left it
                if (connection.frame.parentNode === window.document.documentElement) {
                    window.document.documentElement.removeChild(connection.frame);
                }

                delete transportLogic.foreverFrame.connections[connection.frameId];
                connection.frame = null;
                connection.frameId = null;
                delete connection.frame;
                delete connection.frameId;
                delete connection.onSuccess;
                delete connection.frameMessageCount;
                connection.log("Stopping forever frame.");
            }
        },

        abort: function (connection, async) {
            transportLogic.ajaxAbort(connection, async);
        },

        getConnection: function (id) {
            return transportLogic.foreverFrame.connections[id];
        },

        started: function (connection) {
            if (changeState(connection,
                signalR.connectionState.reconnecting,
                signalR.connectionState.connected) === true) {

                $(connection).triggerHandler(events.onReconnect);
            }
        }
    };

}(window.jQuery, window));
/* jquery.signalR.transports.longPolling.js */
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.


/*global window:false */
/// <reference path="jquery.signalR.transports.common.js" />

(function ($, window, undefined) {

    var signalR = $.signalR,
        events = $.signalR.events,
        changeState = $.signalR.changeState,
        isDisconnecting = $.signalR.isDisconnecting,
        transportLogic = signalR.transports._logic;

    signalR.transports.longPolling = {
        name: "longPolling",

        supportsKeepAlive: function () {
            return false;
        },

        reconnectDelay: 3000,

        start: function (connection, onSuccess, onFailed) {
            /// <summary>Starts the long polling connection</summary>
            /// <param name="connection" type="signalR">The SignalR connection to start</param>
            var that = this,
                fireConnect = function () {
                    fireConnect = $.noop;

                    connection.log("LongPolling connected.");

                    if (onSuccess) {
                        onSuccess();
                    } else {
                        connection.log("WARNING! The client received an init message after reconnecting.");
                    }
                },
                tryFailConnect = function (error) {
                    if (onFailed(error)) {
                        connection.log("LongPolling failed to connect.");
                        return true;
                    }

                    return false;
                },
                privateData = connection._,
                reconnectErrors = 0,
                fireReconnected = function (instance) {
                    window.clearTimeout(privateData.reconnectTimeoutId);
                    privateData.reconnectTimeoutId = null;

                    if (changeState(instance,
                                    signalR.connectionState.reconnecting,
                                    signalR.connectionState.connected) === true) {
                        // Successfully reconnected!
                        instance.log("Raising the reconnect event");
                        $(instance).triggerHandler(events.onReconnect);
                    }
                },
                // 1 hour
                maxFireReconnectedTimeout = 3600000;

            if (connection.pollXhr) {
                connection.log("Polling xhr requests already exists, aborting.");
                connection.stop();
            }

            connection.messageId = null;

            privateData.reconnectTimeoutId = null;

            privateData.pollTimeoutId = window.setTimeout(function () {
                (function poll(instance, raiseReconnect) {
                    var messageId = instance.messageId,
                        connect = (messageId === null),
                        reconnecting = !connect,
                        polling = !raiseReconnect,
                        url = transportLogic.getUrl(instance, that.name, reconnecting, polling, true /* use Post for longPolling */),
                        postData = {};

                    if (instance.messageId) {
                        postData.messageId = instance.messageId;
                    }

                    if (instance.groupsToken) {
                        postData.groupsToken = instance.groupsToken;
                    }

                    // If we've disconnected during the time we've tried to re-instantiate the poll then stop.
                    if (isDisconnecting(instance) === true) {
                        return;
                    }

                    connection.log("Opening long polling request to '" + url + "'.");
                    instance.pollXhr = transportLogic.ajax(connection, {
                        xhrFields: {
                            onprogress: function () {
                                transportLogic.markLastMessage(connection);
                            }
                        },
                        url: url,
                        type: "POST",
                        contentType: signalR._.defaultContentType,
                        data: postData,
                        timeout: connection._.pollTimeout,
                        headers: connection.accessToken ? { "Authorization": "Bearer " + connection.accessToken } : {},
                        success: function (result) {
                            var minData,
                                delay = 0,
                                data,
                                shouldReconnect;

                            connection.log("Long poll complete.");

                            // Reset our reconnect errors so if we transition into a reconnecting state again we trigger
                            // reconnected quickly
                            reconnectErrors = 0;

                            try {
                                // Remove any keep-alives from the beginning of the result
                                minData = connection._parseResponse(result);
                            }
                            catch (error) {
                                transportLogic.handleParseFailure(instance, result, error, tryFailConnect, instance.pollXhr);
                                return;
                            }

                            // If there's currently a timeout to trigger reconnect, fire it now before processing messages
                            if (privateData.reconnectTimeoutId !== null) {
                                fireReconnected(instance);
                            }

                            if (minData) {
                                data = transportLogic.maximizePersistentResponse(minData);
                            }

                            transportLogic.processMessages(instance, minData, fireConnect);

                            if (data &&
                                $.type(data.LongPollDelay) === "number") {
                                delay = data.LongPollDelay;
                            }

                            if (isDisconnecting(instance) === true) {
                                return;
                            }

                            shouldReconnect = data && data.ShouldReconnect;
                            if (shouldReconnect) {
                                // Transition into the reconnecting state
                                // If this fails then that means that the user transitioned the connection into a invalid state in processMessages.
                                if (!transportLogic.ensureReconnectingState(instance)) {
                                    return;
                                }
                            }

                            // We never want to pass a raiseReconnect flag after a successful poll.  This is handled via the error function
                            if (delay > 0) {
                                privateData.pollTimeoutId = window.setTimeout(function () {
                                    poll(instance, shouldReconnect);
                                }, delay);
                            } else {
                                poll(instance, shouldReconnect);
                            }
                        },

                        error: function (data, textStatus) {
                            var error = signalR._.transportError(signalR.resources.longPollFailed, connection.transport, data, instance.pollXhr);

                            // Stop trying to trigger reconnect, connection is in an error state
                            // If we're not in the reconnect state this will noop
                            window.clearTimeout(privateData.reconnectTimeoutId);
                            privateData.reconnectTimeoutId = null;

                            if (textStatus === "abort") {
                                connection.log("Aborted xhr request.");
                                return;
                            }

                            if (!tryFailConnect(error)) {

                                // Increment our reconnect errors, we assume all errors to be reconnect errors
                                // In the case that it's our first error this will cause Reconnect to be fired
                                // after 1 second due to reconnectErrors being = 1.
                                reconnectErrors++;

                                if (connection.state !== signalR.connectionState.reconnecting) {
                                    connection.log("An error occurred using longPolling. Status = " + textStatus + ".  Response = " + data.responseText + ".");
                                    $(instance).triggerHandler(events.onError, [error]);
                                }

                                // We check the state here to verify that we're not in an invalid state prior to verifying Reconnect.
                                // If we're not in connected or reconnecting then the next ensureReconnectingState check will fail and will return.
                                // Therefore we don't want to change that failure code path.
                                if ((connection.state === signalR.connectionState.connected ||
                                    connection.state === signalR.connectionState.reconnecting) &&
                                    !transportLogic.verifyLastActive(connection)) {
                                    return;
                                }

                                // Transition into the reconnecting state
                                // If this fails then that means that the user transitioned the connection into the disconnected or connecting state within the above error handler trigger.
                                if (!transportLogic.ensureReconnectingState(instance)) {
                                    return;
                                }

                                // Call poll with the raiseReconnect flag as true after the reconnect delay
                                privateData.pollTimeoutId = window.setTimeout(function () {
                                    poll(instance, true);
                                }, that.reconnectDelay);
                            }
                        }
                    });

                    // This will only ever pass after an error has occurred via the poll ajax procedure.
                    if (reconnecting && raiseReconnect === true) {
                        // We wait to reconnect depending on how many times we've failed to reconnect.
                        // This is essentially a heuristic that will exponentially increase in wait time before
                        // triggering reconnected.  This depends on the "error" handler of Poll to cancel this
                        // timeout if it triggers before the Reconnected event fires.
                        // The Math.min at the end is to ensure that the reconnect timeout does not overflow.
                        privateData.reconnectTimeoutId = window.setTimeout(function () { fireReconnected(instance); }, Math.min(1000 * (Math.pow(2, reconnectErrors) - 1), maxFireReconnectedTimeout));
                    }
                }(connection));
            }, 250); // Have to delay initial poll so Chrome doesn't show loader spinner in tab
        },

        lostConnection: function (connection) {
            if (connection.pollXhr) {
                connection.pollXhr.abort("lostConnection");
            }
        },

        send: function (connection, data) {
            transportLogic.ajaxSend(connection, data);
        },

        stop: function (connection) {
            /// <summary>Stops the long polling connection</summary>
            /// <param name="connection" type="signalR">The SignalR connection to stop</param>

            window.clearTimeout(connection._.pollTimeoutId);
            window.clearTimeout(connection._.reconnectTimeoutId);

            delete connection._.pollTimeoutId;
            delete connection._.reconnectTimeoutId;

            if (connection.pollXhr) {
                connection.pollXhr.abort();
                connection.pollXhr = null;
                delete connection.pollXhr;
            }
        },

        abort: function (connection, async) {
            transportLogic.ajaxAbort(connection, async);
        }
    };

}(window.jQuery, window));
/* jquery.signalR.hubs.js */
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

/*global window:false */
/// <reference path="jquery.signalR.core.js" />

(function ($, window, undefined) {

    var nextGuid = 0;
    var eventNamespace = ".hubProxy",
        signalR = $.signalR;

    function makeEventName(event) {
        return event + eventNamespace;
    }

    // Equivalent to Array.prototype.map
    function map(arr, fun, thisp) {
        var i,
            length = arr.length,
            result = [];
        for (i = 0; i < length; i += 1) {
            if (arr.hasOwnProperty(i)) {
                result[i] = fun.call(thisp, arr[i], i, arr);
            }
        }
        return result;
    }

    function getArgValue(a) {
        return $.isFunction(a) ? null : ($.type(a) === "undefined" ? null : a);
    }

    function hasMembers(obj) {
        for (var key in obj) {
            // If we have any properties in our callback map then we have callbacks and can exit the loop via return
            if (obj.hasOwnProperty(key)) {
                return true;
            }
        }

        return false;
    }

    function clearInvocationCallbacks(connection, error) {
        /// <param name="connection" type="hubConnection" />
        var callbacks = connection._.invocationCallbacks,
            callback;

        if (hasMembers(callbacks)) {
            connection.log("Clearing hub invocation callbacks with error: " + error + ".");
        }

        // Reset the callback cache now as we have a local var referencing it
        connection._.invocationCallbackId = 0;
        delete connection._.invocationCallbacks;
        connection._.invocationCallbacks = {};

        // Loop over the callbacks and invoke them.
        // We do this using a local var reference and *after* we've cleared the cache
        // so that if a fail callback itself tries to invoke another method we don't
        // end up with its callback in the list we're looping over.
        for (var callbackId in callbacks) {
            callback = callbacks[callbackId];
            callback.method.call(callback.scope, { E: error });
        }
    }

    function isCallbackFromGeneratedHubProxy(callback) {
        // https://github.com/SignalR/SignalR/issues/4310
        // The stringified callback from the old generated hub proxy is 137 characters in Edge, Firefox and Chrome.
        // We slice to avoid wasting too many cycles searching through the text of a long large function.
        return $.isFunction(callback) && callback.toString().slice(0, 256).indexOf("// Call the client hub method") >= 0;
    }

    // hubProxy
    function hubProxy(hubConnection, hubName) {
        /// <summary>
        ///     Creates a new proxy object for the given hub connection that can be used to invoke
        ///     methods on server hubs and handle client method invocation requests from the server.
        /// </summary>
        return new hubProxy.fn.init(hubConnection, hubName);
    }

    hubProxy.fn = hubProxy.prototype = {
        init: function (connection, hubName) {
            this.state = {};
            this.connection = connection;
            this.hubName = hubName;
            this._ = {
                callbackMap: {}
            };
        },

        constructor: hubProxy,

        hasSubscriptions: function () {
            return hasMembers(this._.callbackMap);
        },

        on: function (eventName, callback, callbackIdentity) {
            /// <summary>Wires up a callback to be invoked when a invocation request is received from the server hub.</summary>
            /// <param name="eventName" type="String">The name of the hub event to register the callback for.</param>
            /// <param name="callback" type="Function">The callback to be invoked.</param>
            /// <param name="callbackIdentity" type="Object">An optional object to use as the "identity" for the callback when checking if the handler has already been registered. Defaults to the value of 'callback' if not provided.</param>
            var that = this,
                callbackMap = that._.callbackMap,
                isFromOldGeneratedHubProxy = !callbackIdentity && isCallbackFromGeneratedHubProxy(callback);

            // We need the third "identity" argument because the registerHubProxies call made by signalr/js wraps the user-provided callback in a custom wrapper which breaks the identity comparison.
            // callbackIdentity allows the caller of `on` to provide a separate object to use as the "identity". `registerHubProxies` uses the original user callback as this identity object.
            callbackIdentity = callbackIdentity || callback;

            // Assign a global ID to the identity object. This tags the object so we can detect the same object when it comes back.
            if(!callbackIdentity._signalRGuid) {
                callbackIdentity._signalRGuid = nextGuid++;
            }

            // Normalize the event name to lowercase
            eventName = eventName.toLowerCase();

            // If there is not an event registered for this callback yet we want to create its event space in the callback map.
            var callbackSpace = callbackMap[eventName];
            if (!callbackSpace) {
                callbackSpace = [];
                callbackMap[eventName] = callbackSpace;
            }

            // Check if there's already a registration
            var registration;
            for (var i = 0; i < callbackSpace.length; i++) {
                if (callbackSpace[i].guid === callbackIdentity._signalRGuid || (isFromOldGeneratedHubProxy && callbackSpace[i].isFromOldGeneratedHubProxy)) {
                    registration = callbackSpace[i];
                }
            }

            // Create a registration if there isn't one already
            if (!registration) {
                registration = {
                    guid: callbackIdentity._signalRGuid,
                    eventHandlers: [],
                    isFromOldGeneratedHubProxy: isFromOldGeneratedHubProxy
                };
                callbackMap[eventName].push(registration);
            }

            var handler = function (e, data) {
                callback.apply(that, data);
            };
            registration.eventHandlers.push(handler);

            $(that).bind(makeEventName(eventName), handler);

            return that;
        },

        off: function (eventName, callback, callbackIdentity) {
            /// <summary>Removes the callback invocation request from the server hub for the given event name.</summary>
            /// <param name="eventName" type="String">The name of the hub event to unregister the callback for.</param>
            /// <param name="callback" type="Function">The callback to be removed.</param>
            /// <param name="callbackIdentity" type="Object">An optional object to use as the "identity" when looking up the callback. Corresponds to the same parameter provided to 'on'. Defaults to the value of 'callback' if not provided.</param>
            var that = this,
                callbackMap = that._.callbackMap,
                callbackSpace,
                isFromOldGeneratedHubProxy = !callbackIdentity && isCallbackFromGeneratedHubProxy(callback);

            callbackIdentity = callbackIdentity || callback;

            // Normalize the event name to lowercase
            eventName = eventName.toLowerCase();

            callbackSpace = callbackMap[eventName];

            // Verify that there is an event space to unbind
            if (callbackSpace) {

                if (callback) {
                    // Find the callback registration
                    var callbackRegistration;
                    var callbackIndex;
                    for (var i = 0; i < callbackSpace.length; i++) {
                        if (callbackSpace[i].guid === callbackIdentity._signalRGuid || (isFromOldGeneratedHubProxy && callbackSpace[i].isFromOldGeneratedHubProxy)) {
                            callbackIndex = i;
                            callbackRegistration = callbackSpace[i];
                        }
                    }

                    // Only unbind if there's an event bound with eventName and a callback with the specified callback
                    if (callbackRegistration) {
                        // Unbind all event handlers associated with the registration.
                        for (var j = 0; j < callbackRegistration.eventHandlers.length; j++) {
                            $(that).unbind(makeEventName(eventName), callbackRegistration.eventHandlers[j]);
                        }

                        // Remove the registration from the list
                        callbackSpace.splice(i, 1);

                        // Check if there are any registrations left, if not we need to destroy it.
                        if (callbackSpace.length === 0) {
                            delete callbackMap[eventName];
                        }
                    }
                } else if (!callback) { // Check if we're removing the whole event and we didn't error because of an invalid callback
                    $(that).unbind(makeEventName(eventName));

                    delete callbackMap[eventName];
                }
            }

            return that;
        },

        invoke: function (methodName) {
            /// <summary>Invokes a server hub method with the given arguments.</summary>
            /// <param name="methodName" type="String">The name of the server hub method.</param>

            var that = this,
                connection = that.connection,
                args = $.makeArray(arguments).slice(1),
                argValues = map(args, getArgValue),
                data = { H: that.hubName, M: methodName, A: argValues, I: connection._.invocationCallbackId },
                d = $.Deferred(),
                callback = function (minResult) {
                    var result = that._maximizeHubResponse(minResult),
                        source,
                        error;

                    // Update the hub state
                    $.extend(that.state, result.State);

                    if (result.Progress) {
                        if (d.notifyWith) {
                            // Progress is only supported in jQuery 1.7+
                            d.notifyWith(that, [result.Progress.Data]);
                        } else if (!connection._.progressjQueryVersionLogged) {
                            connection.log("A hub method invocation progress update was received but the version of jQuery in use (" + $.prototype.jquery + ") does not support progress updates. Upgrade to jQuery 1.7+ to receive progress notifications.");
                            connection._.progressjQueryVersionLogged = true;
                        }
                    } else if (result.Error) {
                        // Server hub method threw an exception, log it & reject the deferred
                        if (result.StackTrace) {
                            connection.log(result.Error + "\n" + result.StackTrace + ".");
                        }

                        // result.ErrorData is only set if a HubException was thrown
                        source = result.IsHubException ? "HubException" : "Exception";
                        error = signalR._.error(result.Error, source);
                        error.data = result.ErrorData;

                        connection.log(that.hubName + "." + methodName + " failed to execute. Error: " + error.message);
                        d.rejectWith(that, [error]);
                    } else {
                        // Server invocation succeeded, resolve the deferred
                        connection.log("Invoked " + that.hubName + "." + methodName);
                        d.resolveWith(that, [result.Result]);
                    }
                };

            connection._.invocationCallbacks[connection._.invocationCallbackId.toString()] = { scope: that, method: callback };
            connection._.invocationCallbackId += 1;

            if (!$.isEmptyObject(that.state)) {
                data.S = that.state;
            }

            connection.log("Invoking " + that.hubName + "." + methodName);
            connection.send(data);

            return d.promise();
        },

        _maximizeHubResponse: function (minHubResponse) {
            return {
                State: minHubResponse.S,
                Result: minHubResponse.R,
                Progress: minHubResponse.P ? {
                    Id: minHubResponse.P.I,
                    Data: minHubResponse.P.D
                } : null,
                Id: minHubResponse.I,
                IsHubException: minHubResponse.H,
                Error: minHubResponse.E,
                StackTrace: minHubResponse.T,
                ErrorData: minHubResponse.D
            };
        }
    };

    hubProxy.fn.init.prototype = hubProxy.fn;

    // hubConnection
    function hubConnection(url, options) {
        /// <summary>Creates a new hub connection.</summary>
        /// <param name="url" type="String">[Optional] The hub route url, defaults to "/signalr".</param>
        /// <param name="options" type="Object">[Optional] Settings to use when creating the hubConnection.</param>
        var settings = {
            qs: null,
            logging: false,
            useDefaultPath: true
        };

        $.extend(settings, options);

        if (!url || settings.useDefaultPath) {
            url = (url || "") + "/signalr";
        }
        return new hubConnection.fn.init(url, settings);
    }

    hubConnection.fn = hubConnection.prototype = $.connection();

    hubConnection.fn.init = function (url, options) {
        var settings = {
            qs: null,
            logging: false,
            useDefaultPath: true
        },
            connection = this;

        $.extend(settings, options);

        // Call the base constructor
        $.signalR.fn.init.call(connection, url, settings.qs, settings.logging);

        // Object to store hub proxies for this connection
        connection.proxies = {};

        connection._.invocationCallbackId = 0;
        connection._.invocationCallbacks = {};

        // Wire up the received handler
        connection.received(function (minData) {
            var data, proxy, dataCallbackId, callback, hubName, eventName;
            if (!minData) {
                return;
            }

            // We have to handle progress updates first in order to ensure old clients that receive
            // progress updates enter the return value branch and then no-op when they can't find
            // the callback in the map (because the minData.I value will not be a valid callback ID)
            // Process progress notification
            if (typeof (minData.P) !== "undefined") {
                dataCallbackId = minData.P.I.toString();
                callback = connection._.invocationCallbacks[dataCallbackId];
                if (callback) {
                    callback.method.call(callback.scope, minData);
                }
            } else if (typeof (minData.I) !== "undefined") {
                // We received the return value from a server method invocation, look up callback by id and call it
                dataCallbackId = minData.I.toString();
                callback = connection._.invocationCallbacks[dataCallbackId];
                if (callback) {
                    // Delete the callback from the proxy
                    connection._.invocationCallbacks[dataCallbackId] = null;
                    delete connection._.invocationCallbacks[dataCallbackId];

                    // Invoke the callback
                    callback.method.call(callback.scope, minData);
                }
            } else {
                data = this._maximizeClientHubInvocation(minData);

                // We received a client invocation request, i.e. broadcast from server hub
                connection.log("Triggering client hub event '" + data.Method + "' on hub '" + data.Hub + "'.");

                // Normalize the names to lowercase
                hubName = data.Hub.toLowerCase();
                eventName = data.Method.toLowerCase();

                // Trigger the local invocation event
                proxy = this.proxies[hubName];

                // Update the hub state
                $.extend(proxy.state, data.State);
                $(proxy).triggerHandler(makeEventName(eventName), [data.Args]);
            }
        });

        connection.error(function (errData, origData) {
            var callbackId, callback;

            if (!origData) {
                // No original data passed so this is not a send error
                return;
            }

            callbackId = origData.I;
            callback = connection._.invocationCallbacks[callbackId];

            // Verify that there is a callback bound (could have been cleared)
            if (callback) {
                // Delete the callback
                connection._.invocationCallbacks[callbackId] = null;
                delete connection._.invocationCallbacks[callbackId];

                // Invoke the callback with an error to reject the promise
                callback.method.call(callback.scope, { E: errData });
            }
        });

        connection.reconnecting(function () {
            if (connection.transport && connection.transport.name === "webSockets") {
                clearInvocationCallbacks(connection, "Connection started reconnecting before invocation result was received.");
            }
        });

        connection.disconnected(function () {
            clearInvocationCallbacks(connection, "Connection was disconnected before invocation result was received.");
        });
    };

    hubConnection.fn._maximizeClientHubInvocation = function (minClientHubInvocation) {
        return {
            Hub: minClientHubInvocation.H,
            Method: minClientHubInvocation.M,
            Args: minClientHubInvocation.A,
            State: minClientHubInvocation.S
        };
    };

    hubConnection.fn._registerSubscribedHubs = function () {
        /// <summary>
        ///     Sets the starting event to loop through the known hubs and register any new hubs
        ///     that have been added to the proxy.
        /// </summary>
        var connection = this;

        if (!connection._subscribedToHubs) {
            connection._subscribedToHubs = true;
            connection.starting(function () {
                // Set the connection's data object with all the hub proxies with active subscriptions.
                // These proxies will receive notifications from the server.
                var subscribedHubs = [];

                $.each(connection.proxies, function (key) {
                    if (this.hasSubscriptions()) {
                        subscribedHubs.push({ name: key });
                        connection.log("Client subscribed to hub '" + key + "'.");
                    }
                });

                if (subscribedHubs.length === 0) {
                    connection.log("No hubs have been subscribed to.  The client will not receive data from hubs.  To fix, declare at least one client side function prior to connection start for each hub you wish to subscribe to.");
                }

                connection.data = connection.json.stringify(subscribedHubs);
            });
        }
    };

    hubConnection.fn.createHubProxy = function (hubName) {
        /// <summary>
        ///     Creates a new proxy object for the given hub connection that can be used to invoke
        ///     methods on server hubs and handle client method invocation requests from the server.
        /// </summary>
        /// <param name="hubName" type="String">
        ///     The name of the hub on the server to create the proxy for.
        /// </param>

        // Normalize the name to lowercase
        hubName = hubName.toLowerCase();

        var proxy = this.proxies[hubName];
        if (!proxy) {
            proxy = hubProxy(this, hubName);
            this.proxies[hubName] = proxy;
        }

        this._registerSubscribedHubs();

        return proxy;
    };

    hubConnection.fn.init.prototype = hubConnection.fn;

    $.hubConnection = hubConnection;

}(window.jQuery, window));
/* jquery.signalR.version.js */
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.


/*global window:false */
/// <reference path="jquery.signalR.core.js" />
(function ($, undefined) {
    // This will be modified by the build script
    $.signalR.version = "2.4.3";
}(window.jQuery));

/**
 * @license AngularJS 1.8.11
 * (c) 2010-2020 Google LLC. http://angularjs.org
 * (c) 2025 Perforce
 * Use of these services or software is subject to specific restrictions and limitations. All rights are reserved.
 * License: MIT
 */
(function(window, angular) {'use strict';

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *     Any commits to this file should be reviewed with security in mind.  *
 *   Changes to this file can potentially create security vulnerabilities. *
 *          An approval from 2 Core members with history of modifying      *
 *                         this file is required.                          *
 *                                                                         *
 *  Does the change somehow allow for arbitrary javascript to be executed? *
 *    Or allows for someone to change the prototype of built-in objects?   *
 *     Or gives undesired access to variables likes document or window?    *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

var $sanitizeMinErr = angular.$$minErr('$sanitize');
var bind;
var extend;
var forEach;
var isArray;
var isDefined;
var lowercase;
var noop;
var nodeContains;
var htmlParser;
var htmlSanitizeWriter;

/**
 * @ngdoc module
 * @name ngSanitize
 * @description
 *
 * The `ngSanitize` module provides functionality to sanitize HTML.
 *
 * See {@link ngSanitize.$sanitize `$sanitize`} for usage.
 */

/**
 * @ngdoc service
 * @name $sanitize
 * @kind function
 *
 * @description
 *   Sanitizes an html string by stripping all potentially dangerous tokens.
 *
 *   The input is sanitized by parsing the HTML into tokens. All safe tokens (from a trusted URI list) are
 *   then serialized back to a properly escaped HTML string. This means that no unsafe input can make
 *   it into the returned string.
 *
 *   The trusted URIs for URL sanitization of attribute values is configured using the functions
 *   `aHrefSanitizationTrustedUrlList` and `imgSrcSanitizationTrustedUrlList` of {@link $compileProvider}.
 *
 *   The input may also contain SVG markup if this is enabled via {@link $sanitizeProvider}.
 *
 * @param {string} html HTML input.
 * @returns {string} Sanitized HTML.
 *
 * @example
   <example module="sanitizeExample" deps="angular-sanitize.js" name="sanitize-service">
   <file name="index.html">
     <script>
         angular.module('sanitizeExample', ['ngSanitize'])
           .controller('ExampleController', ['$scope', '$sce', function($scope, $sce) {
             $scope.snippet =
               '<p style="color:blue">an html\n' +
               '<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +
               'snippet</p>';
             $scope.deliberatelyTrustDangerousSnippet = function() {
               return $sce.trustAsHtml($scope.snippet);
             };
           }]);
     </script>
     <div ng-controller="ExampleController">
        Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
       <table>
         <tr>
           <td>Directive</td>
           <td>How</td>
           <td>Source</td>
           <td>Rendered</td>
         </tr>
         <tr id="bind-html-with-sanitize">
           <td>ng-bind-html</td>
           <td>Automatically uses $sanitize</td>
           <td><pre>&lt;div ng-bind-html="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
           <td><div ng-bind-html="snippet"></div></td>
         </tr>
         <tr id="bind-html-with-trust">
           <td>ng-bind-html</td>
           <td>Bypass $sanitize by explicitly trusting the dangerous value</td>
           <td>
           <pre>&lt;div ng-bind-html="deliberatelyTrustDangerousSnippet()"&gt;
&lt;/div&gt;</pre>
           </td>
           <td><div ng-bind-html="deliberatelyTrustDangerousSnippet()"></div></td>
         </tr>
         <tr id="bind-default">
           <td>ng-bind</td>
           <td>Automatically escapes</td>
           <td><pre>&lt;div ng-bind="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
           <td><div ng-bind="snippet"></div></td>
         </tr>
       </table>
       </div>
   </file>
   <file name="protractor.js" type="protractor">
     it('should sanitize the html snippet by default', function() {
       expect(element(by.css('#bind-html-with-sanitize div')).getAttribute('innerHTML')).
         toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
     });

     it('should inline raw snippet if bound to a trusted value', function() {
       expect(element(by.css('#bind-html-with-trust div')).getAttribute('innerHTML')).
         toBe("<p style=\"color:blue\">an html\n" +
              "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
              "snippet</p>");
     });

     it('should escape snippet without any filter', function() {
       expect(element(by.css('#bind-default div')).getAttribute('innerHTML')).
         toBe("&lt;p style=\"color:blue\"&gt;an html\n" +
              "&lt;em onmouseover=\"this.textContent='PWN3D!'\"&gt;click here&lt;/em&gt;\n" +
              "snippet&lt;/p&gt;");
     });

     it('should update', function() {
       element(by.model('snippet')).clear();
       element(by.model('snippet')).sendKeys('new <b onclick="alert(1)">text</b>');
       expect(element(by.css('#bind-html-with-sanitize div')).getAttribute('innerHTML')).
         toBe('new <b>text</b>');
       expect(element(by.css('#bind-html-with-trust div')).getAttribute('innerHTML')).toBe(
         'new <b onclick="alert(1)">text</b>');
       expect(element(by.css('#bind-default div')).getAttribute('innerHTML')).toBe(
         "new &lt;b onclick=\"alert(1)\"&gt;text&lt;/b&gt;");
     });
   </file>
   </example>
 */


/**
 * @ngdoc provider
 * @name $sanitizeProvider
 * @this
 *
 * @description
 * Creates and configures {@link $sanitize} instance.
 */
function $SanitizeProvider() {
  var hasBeenInstantiated = false;
  var svgEnabled = false;

  this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
    hasBeenInstantiated = true;
    if (svgEnabled) {
      extend(validElements, svgElements);
    }
    return function(html) {
      var buf = [];
      htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) {
        return !/^unsafe:/.test($$sanitizeUri(uri, isImage));
      }));
      return buf.join('');
    };
  }];


  /**
   * @ngdoc method
   * @name $sanitizeProvider#enableSvg
   * @kind function
   *
   * @description
   * Enables a subset of svg to be supported by the sanitizer.
   *
   * <div class="alert alert-warning">
   *   <p>By enabling this setting without taking other precautions, you might expose your
   *   application to click-hijacking attacks. In these attacks, sanitized svg elements could be positioned
   *   outside of the containing element and be rendered over other elements on the page (e.g. a login
   *   link). Such behavior can then result in phishing incidents.</p>
   *
   *   <p>To protect against these, explicitly setup `overflow: hidden` css rule for all potential svg
   *   tags within the sanitized content:</p>
   *
   *   <br>
   *
   *   <pre><code>
   *   .rootOfTheIncludedContent svg {
   *     overflow: hidden !important;
   *   }
   *   </code></pre>
   * </div>
   *
   * @param {boolean=} flag Enable or disable SVG support in the sanitizer.
   * @returns {boolean|$sanitizeProvider} Returns the currently configured value if called
   *    without an argument or self for chaining otherwise.
   */
  this.enableSvg = function(enableSvg) {
    if (isDefined(enableSvg)) {
      svgEnabled = enableSvg;
      return this;
    } else {
      return svgEnabled;
    }
  };


  /**
   * @ngdoc method
   * @name $sanitizeProvider#addValidElements
   * @kind function
   *
   * @description
   * Extends the built-in lists of valid HTML/SVG elements, i.e. elements that are considered safe
   * and are not stripped off during sanitization. You can extend the following lists of elements:
   *
   * - `htmlElements`: A list of elements (tag names) to extend the current list of safe HTML
   *   elements. HTML elements considered safe will not be removed during sanitization. All other
   *   elements will be stripped off.
   *
   * - `htmlVoidElements`: This is similar to `htmlElements`, but marks the elements as
   *   "void elements" (similar to HTML
   *   [void elements](https://rawgit.com/w3c/html/html5.1-2/single-page.html#void-elements)). These
   *   elements have no end tag and cannot have content.
   *
   * - `svgElements`: This is similar to `htmlElements`, but for SVG elements. This list is only
   *   taken into account if SVG is {@link ngSanitize.$sanitizeProvider#enableSvg enabled} for
   *   `$sanitize`.
   *
   * <div class="alert alert-info">
   *   This method must be called during the {@link angular.Module#config config} phase. Once the
   *   `$sanitize` service has been instantiated, this method has no effect.
   * </div>
   *
   * <div class="alert alert-warning">
   *   Keep in mind that extending the built-in lists of elements may expose your app to XSS or
   *   other vulnerabilities. Be very mindful of the elements you add.
   * </div>
   *
   * @param {Array<String>|Object} elements - A list of valid HTML elements or an object with one or
   *   more of the following properties:
   *   - **htmlElements** - `{Array<String>}` - A list of elements to extend the current list of
   *     HTML elements.
   *   - **htmlVoidElements** - `{Array<String>}` - A list of elements to extend the current list of
   *     void HTML elements; i.e. elements that do not have an end tag.
   *   - **svgElements** - `{Array<String>}` - A list of elements to extend the current list of SVG
   *     elements. The list of SVG elements is only taken into account if SVG is
   *     {@link ngSanitize.$sanitizeProvider#enableSvg enabled} for `$sanitize`.
   *
   * Passing an array (`[...]`) is equivalent to passing `{htmlElements: [...]}`.
   *
   * @return {$sanitizeProvider} Returns self for chaining.
   */
  this.addValidElements = function(elements) {
    if (!hasBeenInstantiated) {
      if (isArray(elements)) {
        elements = {htmlElements: elements};
      }

      addElementsTo(svgElements, elements.svgElements);
      addElementsTo(voidElements, elements.htmlVoidElements);
      addElementsTo(validElements, elements.htmlVoidElements);
      addElementsTo(validElements, elements.htmlElements);
    }

    return this;
  };


  /**
   * @ngdoc method
   * @name $sanitizeProvider#addValidAttrs
   * @kind function
   *
   * @description
   * Extends the built-in list of valid attributes, i.e. attributes that are considered safe and are
   * not stripped off during sanitization.
   *
   * **Note**:
   * The new attributes will not be treated as URI attributes, which means their values will not be
   * sanitized as URIs using `$compileProvider`'s
   * {@link ng.$compileProvider#aHrefSanitizationTrustedUrlList aHrefSanitizationTrustedUrlList} and
   * {@link ng.$compileProvider#imgSrcSanitizationTrustedUrlList imgSrcSanitizationTrustedUrlList}.
   *
   * <div class="alert alert-info">
   *   This method must be called during the {@link angular.Module#config config} phase. Once the
   *   `$sanitize` service has been instantiated, this method has no effect.
   * </div>
   *
   * <div class="alert alert-warning">
   *   Keep in mind that extending the built-in list of attributes may expose your app to XSS or
   *   other vulnerabilities. Be very mindful of the attributes you add.
   * </div>
   *
   * @param {Array<String>} attrs - A list of valid attributes.
   *
   * @returns {$sanitizeProvider} Returns self for chaining.
   */
  this.addValidAttrs = function(attrs) {
    if (!hasBeenInstantiated) {
      extend(validAttrs, arrayToMap(attrs, true));
    }
    return this;
  };

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // Private stuff
  //////////////////////////////////////////////////////////////////////////////////////////////////

  bind = angular.bind;
  extend = angular.extend;
  forEach = angular.forEach;
  isArray = angular.isArray;
  isDefined = angular.isDefined;
  lowercase = angular.$$lowercase;
  noop = angular.noop;

  htmlParser = htmlParserImpl;
  htmlSanitizeWriter = htmlSanitizeWriterImpl;

  nodeContains = window.Node.prototype.contains || /** @this */ function(arg) {
    // eslint-disable-next-line no-bitwise
    return !!(this.compareDocumentPosition(arg) & 16);
  };

  // Regular Expressions for parsing tags and attributes
  var SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
    // Match everything outside of normal chars and " (quote character)
    NON_ALPHANUMERIC_REGEXP = /([^#-~ |!])/g;


  // Good source of info about elements and attributes
  // http://dev.w3.org/html5/spec/Overview.html#semantics
  // http://simon.html5.org/html-elements

  // Safe Void Elements - HTML5
  // http://dev.w3.org/html5/spec/Overview.html#void-elements
  var voidElements = stringToMap('area,br,col,hr,img,wbr');

  // Elements that you can, intentionally, leave open (and which close themselves)
  // http://dev.w3.org/html5/spec/Overview.html#optional-tags
  var optionalEndTagBlockElements = stringToMap('colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr'),
      optionalEndTagInlineElements = stringToMap('rp,rt'),
      optionalEndTagElements = extend({},
                                              optionalEndTagInlineElements,
                                              optionalEndTagBlockElements);

  // Safe Block Elements - HTML5
  var blockElements = extend({}, optionalEndTagBlockElements, stringToMap('address,article,' +
          'aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,' +
          'h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,section,table,ul'));

  // Inline Elements - HTML5
  var inlineElements = extend({}, optionalEndTagInlineElements, stringToMap('a,abbr,acronym,b,' +
          'bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,' +
          'samp,small,span,strike,strong,sub,sup,time,tt,u,var'));

  // SVG Elements
  // https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements
  // Note: the elements animate,animateColor,animateMotion,animateTransform,set are intentionally omitted.
  // They can potentially allow for arbitrary javascript to be executed. See #11290
  var svgElements = stringToMap('circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph,' +
          'hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline,' +
          'radialGradient,rect,stop,svg,switch,text,title,tspan');

  // Blocked Elements (will be stripped)
  var blockedElements = stringToMap('script,style');

  var validElements = extend({},
                                     voidElements,
                                     blockElements,
                                     inlineElements,
                                     optionalEndTagElements);

  //Attributes that have href and hence need to be sanitized
  var uriAttrs = stringToMap('background,cite,href,longdesc,src,xlink:href,xml:base');

  var htmlAttrs = stringToMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' +
      'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' +
      'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' +
      'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' +
      'valign,value,vspace,width');

  // SVG attributes (without "id" and "name" attributes)
  // https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes
  var svgAttrs = stringToMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' +
      'baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,' +
      'cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,' +
      'font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,' +
      'height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,' +
      'marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,' +
      'max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,' +
      'path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,' +
      'requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,' +
      'stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,' +
      'stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,' +
      'stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,' +
      'underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,' +
      'width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,' +
      'xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan', true);

  var validAttrs = extend({},
                                  uriAttrs,
                                  svgAttrs,
                                  htmlAttrs);

  function stringToMap(str, lowercaseKeys) {
    return arrayToMap(str.split(','), lowercaseKeys);
  }

  function arrayToMap(items, lowercaseKeys) {
    var obj = {}, i;
    for (i = 0; i < items.length; i++) {
      obj[lowercaseKeys ? lowercase(items[i]) : items[i]] = true;
    }
    return obj;
  }

  function addElementsTo(elementsMap, newElements) {
    if (newElements && newElements.length) {
      extend(elementsMap, arrayToMap(newElements));
    }
  }

  /**
   * Create an inert document that contains the dirty HTML that needs sanitizing.
   * We use the DOMParser API by default and fall back to createHTMLDocument if DOMParser is not
   * available.
   */
  var getInertBodyElement /* function(html: string): HTMLBodyElement */ = (function(window, document) {
    if (isDOMParserAvailable()) {
      return getInertBodyElement_DOMParser;
    }

    if (!document || !document.implementation) {
      throw $sanitizeMinErr('noinert', 'Can\'t create an inert html document');
    }
    var inertDocument = document.implementation.createHTMLDocument('inert');
    var inertBodyElement = (inertDocument.documentElement || inertDocument.getDocumentElement()).querySelector('body');
    return getInertBodyElement_InertDocument;

    function isDOMParserAvailable() {
      try {
        return !!getInertBodyElement_DOMParser('');
      } catch (e) {
        return false;
      }
    }

    function getInertBodyElement_DOMParser(html) {
      // We add this dummy element to ensure that the rest of the content is parsed as expected
      // e.g. leading whitespace is maintained and tags like `<meta>` do not get hoisted to the `<head>` tag.
      html = '<remove></remove>' + html;
      try {
        var body = new window.DOMParser().parseFromString(html, 'text/html').body;
        body.firstChild.remove();
        return body;
      } catch (e) {
        return undefined;
      }
    }

    function getInertBodyElement_InertDocument(html) {
      inertBodyElement.innerHTML = html;

      // Support: IE 9-11 only
      // strip custom-namespaced attributes on IE<=11
      if (document.documentMode) {
        stripCustomNsAttrs(inertBodyElement);
      }

      return inertBodyElement;
    }
  })(window, window.document);

  /**
   * @example
   * htmlParser(htmlString, {
   *     start: function(tag, attrs) {},
   *     end: function(tag) {},
   *     chars: function(text) {},
   *     comment: function(text) {}
   * });
   *
   * @param {string} html string
   * @param {object} handler
   */
  function htmlParserImpl(html, handler) {
    if (html === null || html === undefined) {
      html = '';
    } else if (typeof html !== 'string') {
      html = '' + html;
    }

    var inertBodyElement = getInertBodyElement(html);
    if (!inertBodyElement) return '';

    //mXSS protection
    var mXSSAttempts = 5;
    do {
      if (mXSSAttempts === 0) {
        throw $sanitizeMinErr('uinput', 'Failed to sanitize html because the input is unstable');
      }
      mXSSAttempts--;

      // trigger mXSS if it is going to happen by reading and writing the innerHTML
      html = inertBodyElement.innerHTML;
      inertBodyElement = getInertBodyElement(html);
    } while (html !== inertBodyElement.innerHTML);

    var node = inertBodyElement.firstChild;
    while (node) {
      switch (node.nodeType) {
        case 1: // ELEMENT_NODE
          handler.start(node.nodeName.toLowerCase(), attrToMap(node.attributes));
          break;
        case 3: // TEXT NODE
          handler.chars(node.textContent);
          break;
      }

      var nextNode;
      if (!(nextNode = node.firstChild)) {
        if (node.nodeType === 1) {
          handler.end(node.nodeName.toLowerCase());
        }
        nextNode = getNonDescendant('nextSibling', node);
        if (!nextNode) {
          while (nextNode == null) {
            node = getNonDescendant('parentNode', node);
            if (node === inertBodyElement) break;
            nextNode = getNonDescendant('nextSibling', node);
            if (node.nodeType === 1) {
              handler.end(node.nodeName.toLowerCase());
            }
          }
        }
      }
      node = nextNode;
    }

    while ((node = inertBodyElement.firstChild)) {
      inertBodyElement.removeChild(node);
    }
  }

  function attrToMap(attrs) {
    var map = {};
    for (var i = 0, ii = attrs.length; i < ii; i++) {
      var attr = attrs[i];
      map[attr.name] = attr.value;
    }
    return map;
  }


  /**
   * Escapes all potentially dangerous characters, so that the
   * resulting string can be safely inserted into attribute or
   * element text.
   * @param value
   * @returns {string} escaped text
   */
  function encodeEntities(value) {
    return value.
      replace(/&/g, '&amp;').
      replace(SURROGATE_PAIR_REGEXP, function(value) {
        var hi = value.charCodeAt(0);
        var low = value.charCodeAt(1);
        return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
      }).
      replace(NON_ALPHANUMERIC_REGEXP, function(value) {
        return '&#' + value.charCodeAt(0) + ';';
      }).
      replace(/</g, '&lt;').
      replace(/>/g, '&gt;');
  }

  /**
   * create an HTML/XML writer which writes to buffer
   * @param {Array} buf use buf.join('') to get out sanitized html string
   * @returns {object} in the form of {
   *     start: function(tag, attrs) {},
   *     end: function(tag) {},
   *     chars: function(text) {},
   *     comment: function(text) {}
   * }
   */
  function htmlSanitizeWriterImpl(buf, uriValidator) {
    var ignoreCurrentElement = false;
    var out = bind(buf, buf.push);
    return {
      start: function(tag, attrs) {
        tag = lowercase(tag);
        if (!ignoreCurrentElement && blockedElements[tag]) {
          ignoreCurrentElement = tag;
        }
        if (!ignoreCurrentElement && validElements[tag] === true) {
          out('<');
          out(tag);
          forEach(attrs, function(value, key) {
            var lkey = lowercase(key);
            var isSvgImage = (tag === 'image' && (lkey === 'href' || lkey === 'xlink:href'));
            var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background') || isSvgImage;
            if (validAttrs[lkey] === true &&
              (uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
              out(' ');
              out(key);
              out('="');
              out(encodeEntities(value));
              out('"');
            }
          });
          out('>');
        }
      },
      end: function(tag) {
        tag = lowercase(tag);
        if (!ignoreCurrentElement && validElements[tag] === true && voidElements[tag] !== true) {
          out('</');
          out(tag);
          out('>');
        }
        // eslint-disable-next-line eqeqeq
        if (tag == ignoreCurrentElement) {
          ignoreCurrentElement = false;
        }
      },
      chars: function(chars) {
        if (!ignoreCurrentElement) {
          out(encodeEntities(chars));
        }
      }
    };
  }


  /**
   * When IE9-11 comes across an unknown namespaced attribute e.g. 'xlink:foo' it adds 'xmlns:ns1' attribute to declare
   * ns1 namespace and prefixes the attribute with 'ns1' (e.g. 'ns1:xlink:foo'). This is undesirable since we don't want
   * to allow any of these custom attributes. This method strips them all.
   *
   * @param node Root element to process
   */
  function stripCustomNsAttrs(node) {
    while (node) {
      if (node.nodeType === window.Node.ELEMENT_NODE) {
        var attrs = node.attributes;
        for (var i = 0, l = attrs.length; i < l; i++) {
          var attrNode = attrs[i];
          var attrName = attrNode.name.toLowerCase();
          if (attrName === 'xmlns:ns1' || attrName.lastIndexOf('ns1:', 0) === 0) {
            node.removeAttributeNode(attrNode);
            i--;
            l--;
          }
        }
      }

      var nextNode = node.firstChild;
      if (nextNode) {
        stripCustomNsAttrs(nextNode);
      }

      node = getNonDescendant('nextSibling', node);
    }
  }

  function getNonDescendant(propName, node) {
    // An element is clobbered if its `propName` property points to one of its descendants
    var nextNode = node[propName];
    if (nextNode && nodeContains.call(node, nextNode)) {
      throw $sanitizeMinErr('elclob', 'Failed to sanitize html because the element is clobbered: {0}', node.outerHTML || node.outerText);
    }
    return nextNode;
  }
}

function sanitizeText(chars) {
  var buf = [];
  var writer = htmlSanitizeWriter(buf, noop);
  writer.chars(chars);
  return buf.join('');
}


// define ngSanitize module and register $sanitize service
angular.module('ngSanitize', [])
  .provider('$sanitize', $SanitizeProvider)
  .info({ angularVersion: '1.8.11' });

/**
 * @ngdoc filter
 * @name linky
 * @kind function
 *
 * @description
 * Finds links in text input and turns them into html links. Supports `http/https/ftp/sftp/mailto` and
 * plain email address links.
 *
 * Requires the {@link ngSanitize `ngSanitize`} module to be installed.
 *
 * @param {string} text Input text.
 * @param {string} [target] Window (`_blank|_self|_parent|_top`) or named frame to open links in.
 * @param {object|function(url)} [attributes] Add custom attributes to the link element.
 *
 *    Can be one of:
 *
 *    - `object`: A map of attributes
 *    - `function`: Takes the url as a parameter and returns a map of attributes
 *
 *    If the map of attributes contains a value for `target`, it overrides the value of
 *    the target parameter.
 *
 *
 * @returns {string} Html-linkified and {@link $sanitize sanitized} text.
 *
 * @usage
   <span ng-bind-html="linky_expression | linky"></span>
 *
 * @example
   <example module="linkyExample" deps="angular-sanitize.js" name="linky-filter">
     <file name="index.html">
       <div ng-controller="ExampleController">
       Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
       <table>
         <tr>
           <th>Filter</th>
           <th>Source</th>
           <th>Rendered</th>
         </tr>
         <tr id="linky-filter">
           <td>linky filter</td>
           <td>
             <pre>&lt;div ng-bind-html="snippet | linky"&gt;<br>&lt;/div&gt;</pre>
           </td>
           <td>
             <div ng-bind-html="snippet | linky"></div>
           </td>
         </tr>
         <tr id="linky-target">
          <td>linky target</td>
          <td>
            <pre>&lt;div ng-bind-html="snippetWithSingleURL | linky:'_blank'"&gt;<br>&lt;/div&gt;</pre>
          </td>
          <td>
            <div ng-bind-html="snippetWithSingleURL | linky:'_blank'"></div>
          </td>
         </tr>
         <tr id="linky-custom-attributes">
          <td>linky custom attributes</td>
          <td>
            <pre>&lt;div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}"&gt;<br>&lt;/div&gt;</pre>
          </td>
          <td>
            <div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}"></div>
          </td>
         </tr>
         <tr id="escaped-html">
           <td>no filter</td>
           <td><pre>&lt;div ng-bind="snippet"&gt;<br>&lt;/div&gt;</pre></td>
           <td><div ng-bind="snippet"></div></td>
         </tr>
       </table>
     </file>
     <file name="script.js">
       angular.module('linkyExample', ['ngSanitize'])
         .controller('ExampleController', ['$scope', function($scope) {
           $scope.snippet =
             'Pretty text with some links:\n' +
             'http://angularjs.org/,\n' +
             'mailto:us@somewhere.org,\n' +
             'another@somewhere.org,\n' +
             'and one more: ftp://127.0.0.1/.';
           $scope.snippetWithSingleURL = 'http://angularjs.org/';
         }]);
     </file>
     <file name="protractor.js" type="protractor">
       it('should linkify the snippet with urls', function() {
         expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
             toBe('Pretty text with some links: http://angularjs.org/, us@somewhere.org, ' +
                  'another@somewhere.org, and one more: ftp://127.0.0.1/.');
         expect(element.all(by.css('#linky-filter a')).count()).toEqual(4);
       });

       it('should not linkify snippet without the linky filter', function() {
         expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()).
             toBe('Pretty text with some links: http://angularjs.org/, mailto:us@somewhere.org, ' +
                  'another@somewhere.org, and one more: ftp://127.0.0.1/.');
         expect(element.all(by.css('#escaped-html a')).count()).toEqual(0);
       });

       it('should update', function() {
         element(by.model('snippet')).clear();
         element(by.model('snippet')).sendKeys('new http://link.');
         expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
             toBe('new http://link.');
         expect(element.all(by.css('#linky-filter a')).count()).toEqual(1);
         expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText())
             .toBe('new http://link.');
       });

       it('should work with the target property', function() {
        expect(element(by.id('linky-target')).
            element(by.binding("snippetWithSingleURL | linky:'_blank'")).getText()).
            toBe('http://angularjs.org/');
        expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank');
       });

       it('should optionally add custom attributes', function() {
        expect(element(by.id('linky-custom-attributes')).
            element(by.binding("snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}")).getText()).
            toBe('http://angularjs.org/');
        expect(element(by.css('#linky-custom-attributes a')).getAttribute('rel')).toEqual('nofollow');
       });
     </file>
   </example>
 */
angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
  var LINKY_URL_REGEXP =
        /((s?ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)(?:[\[A-Za-z0-9\-._~:/?#@!$&'()*+=%"]*[A-Za-z0-9\-_/~#@\]])/i,
      MAILTO_REGEXP = /^mailto:/i;

  var linkyMinErr = angular.$$minErr('linky');
  var isDefined = angular.isDefined;
  var isFunction = angular.isFunction;
  var isObject = angular.isObject;
  var isString = angular.isString;

  return function(text, target, attributes) {
    if (text == null || text === '') return text;
    if (!isString(text)) throw linkyMinErr('notstring', 'Expected string but received: {0}', text);

    var attributesFn =
      isFunction(attributes) ? attributes :
      isObject(attributes) ? function getAttributesObject() {return attributes;} :
      function getEmptyAttributesObject() {return {};};

    var match;
    var raw = text;
    var html = [];
    var url;
    var i;
    while ((match = raw.match(LINKY_URL_REGEXP))) {
      // We can not end in these as they are sometimes found at the end of the sentence
      url = match[0];
      // if we did not match ftp/http/www/mailto then assume mailto
      if (!match[2] && !match[4]) {
        url = (match[3] ? 'http://' : 'mailto:') + url;
      }
      i = match.index;
      addText(raw.substr(0, i));
      addLink(url, match[0].replace(MAILTO_REGEXP, ''));
      raw = raw.substring(i + match[0].length);
    }
    addText(raw);
    return $sanitize(html.join(''));

    function addText(text) {
      if (!text) {
        return;
      }
      html.push(sanitizeText(text));
    }

    function addLink(url, text) {
      var key, linkAttributes = attributesFn(url);
      html.push('<a ');

      for (key in linkAttributes) {
        html.push(key + '="' + linkAttributes[key] + '" ');
      }

      if (isDefined(target) && !('target' in linkAttributes)) {
        html.push('target="',
                  target,
                  '" ');
      }
      html.push('href="',
                url.replace(/"/g, '&quot;'),
                '">');
      addText(text);
      html.push('</a>');
    }
  };
}]);


})(window, window.angular);

/**
 * @license AngularJS 1.8.11
 * (c) 2010-2020 Google LLC. http://angularjs.org
 * (c) 2025 Perforce
 * Use of these services or software is subject to specific restrictions and limitations. All rights are reserved.
 * License: MIT
 */
(function(window, angular) {'use strict';

/* global shallowCopy: true */

/**
 * Creates a shallow copy of an object, an array or a primitive.
 *
 * Assumes that there are no proto properties for objects.
 */
function shallowCopy(src, dst) {
  if (isArray(src)) {
    dst = dst || [];

    for (var i = 0, ii = src.length; i < ii; i++) {
      dst[i] = src[i];
    }
  } else if (isObject(src)) {
    dst = dst || {};

    for (var key in src) {
      if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) {
        dst[key] = src[key];
      }
    }
  }

  return dst || src;
}

/* global routeToRegExp: true */

/**
 * @param {string} path - The path to parse. (It is assumed to have query and hash stripped off.)
 * @param {Object} opts - Options.
 * @return {Object} - An object containing an array of path parameter names (`keys`) and a regular
 *     expression (`regexp`) that can be used to identify a matching URL and extract the path
 *     parameter values.
 *
 * @description
 * Parses the given path, extracting path parameter names and a regular expression to match URLs.
 *
 * Originally inspired by `pathRexp` in `visionmedia/express/lib/utils.js`.
 */
function routeToRegExp(path, opts) {
  var keys = [];

  var pattern = path
    .replace(/([().])/g, '\\$1')
    .replace(/(\/)?:(\w+)(\*\?|[?*])?/g, function(_, slash, key, option) {
      var optional = option === '?' || option === '*?';
      var star = option === '*' || option === '*?';
      keys.push({name: key, optional: optional});
      slash = slash || '';
      return (
        (optional ? '(?:' + slash : slash + '(?:') +
        (star ? '(.+?)' : '([^/]+)') +
        (optional ? '?)?' : ')')
      );
    })
    .replace(/([/$*])/g, '\\$1');

  if (opts.ignoreTrailingSlashes) {
    pattern = pattern.replace(/\/+$/, '') + '/*';
  }

  return {
    keys: keys,
    regexp: new RegExp(
      '^' + pattern + '(?:[?#]|$)',
      opts.caseInsensitiveMatch ? 'i' : ''
    )
  };
}

/* global routeToRegExp: false */
/* global shallowCopy: false */

// `isArray` and `isObject` are necessary for `shallowCopy()` (included via `src/shallowCopy.js`).
// They are initialized inside the `$RouteProvider`, to ensure `window.angular` is available.
var isArray;
var isObject;
var isDefined;
var noop;

/**
 * @ngdoc module
 * @name ngRoute
 * @description
 *
 * The `ngRoute` module provides routing and deeplinking services and directives for AngularJS apps.
 *
 * ## Example
 * See {@link ngRoute.$route#examples $route} for an example of configuring and using `ngRoute`.
 *
 */
/* global -ngRouteModule */
var ngRouteModule = angular.
  module('ngRoute', []).
  info({ angularVersion: '1.8.11' }).
  provider('$route', $RouteProvider).
  // Ensure `$route` will be instantiated in time to capture the initial `$locationChangeSuccess`
  // event (unless explicitly disabled). This is necessary in case `ngView` is included in an
  // asynchronously loaded template.
  run(instantiateRoute);
var $routeMinErr = angular.$$minErr('ngRoute');
var isEagerInstantiationEnabled;


/**
 * @ngdoc provider
 * @name $routeProvider
 * @this
 *
 * @description
 *
 * Used for configuring routes.
 *
 * ## Example
 * See {@link ngRoute.$route#examples $route} for an example of configuring and using `ngRoute`.
 *
 * ## Dependencies
 * Requires the {@link ngRoute `ngRoute`} module to be installed.
 */
function $RouteProvider() {
  isArray = angular.isArray;
  isObject = angular.isObject;
  isDefined = angular.isDefined;
  noop = angular.noop;

  function inherit(parent, extra) {
    return angular.extend(Object.create(parent), extra);
  }

  var routes = {};

  /**
   * @ngdoc method
   * @name $routeProvider#when
   *
   * @param {string} path Route path (matched against `$location.path`). If `$location.path`
   *    contains redundant trailing slash or is missing one, the route will still match and the
   *    `$location.path` will be updated to add or drop the trailing slash to exactly match the
   *    route definition.
   *
   *    * `path` can contain named groups starting with a colon: e.g. `:name`. All characters up
   *        to the next slash are matched and stored in `$routeParams` under the given `name`
   *        when the route matches.
   *    * `path` can contain named groups starting with a colon and ending with a star:
   *        e.g.`:name*`. All characters are eagerly stored in `$routeParams` under the given `name`
   *        when the route matches.
   *    * `path` can contain optional named groups with a question mark: e.g.`:name?`.
   *
   *    For example, routes like `/color/:color/largecode/:largecode*\/edit` will match
   *    `/color/brown/largecode/code/with/slashes/edit` and extract:
   *
   *    * `color: brown`
   *    * `largecode: code/with/slashes`.
   *
   *
   * @param {Object} route Mapping information to be assigned to `$route.current` on route
   *    match.
   *
   *    Object properties:
   *
   *    - `controller` – `{(string|Function)=}` – Controller fn that should be associated with
   *      newly created scope or the name of a {@link angular.Module#controller registered
   *      controller} if passed as a string.
   *    - `controllerAs` – `{string=}` – An identifier name for a reference to the controller.
   *      If present, the controller will be published to scope under the `controllerAs` name.
   *    - `template` – `{(string|Function)=}` – html template as a string or a function that
   *      returns an html template as a string which should be used by {@link
   *      ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives.
   *      This property takes precedence over `templateUrl`.
   *
   *      If `template` is a function, it will be called with the following parameters:
   *
   *      - `{Array.<Object>}` - route parameters extracted from the current
   *        `$location.path()` by applying the current route
   *
   *      One of `template` or `templateUrl` is required.
   *
   *    - `templateUrl` – `{(string|Function)=}` – path or function that returns a path to an html
   *      template that should be used by {@link ngRoute.directive:ngView ngView}.
   *
   *      If `templateUrl` is a function, it will be called with the following parameters:
   *
   *      - `{Array.<Object>}` - route parameters extracted from the current
   *        `$location.path()` by applying the current route
   *
   *      One of `templateUrl` or `template` is required.
   *
   *    - `resolve` - `{Object.<string, Function>=}` - An optional map of dependencies which should
   *      be injected into the controller. If any of these dependencies are promises, the router
   *      will wait for them all to be resolved or one to be rejected before the controller is
   *      instantiated.
   *      If all the promises are resolved successfully, the values of the resolved promises are
   *      injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is
   *      fired. If any of the promises are rejected the
   *      {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired.
   *      For easier access to the resolved dependencies from the template, the `resolve` map will
   *      be available on the scope of the route, under `$resolve` (by default) or a custom name
   *      specified by the `resolveAs` property (see below). This can be particularly useful, when
   *      working with {@link angular.Module#component components} as route templates.<br />
   *      <div class="alert alert-warning">
   *        **Note:** If your scope already contains a property with this name, it will be hidden
   *        or overwritten. Make sure, you specify an appropriate name for this property, that
   *        does not collide with other properties on the scope.
   *      </div>
   *      The map object is:
   *
   *      - `key` – `{string}`: a name of a dependency to be injected into the controller.
   *      - `factory` - `{string|Function}`: If `string` then it is an alias for a service.
   *        Otherwise if function, then it is {@link auto.$injector#invoke injected}
   *        and the return value is treated as the dependency. If the result is a promise, it is
   *        resolved before its value is injected into the controller. Be aware that
   *        `ngRoute.$routeParams` will still refer to the previous route within these resolve
   *        functions.  Use `$route.current.params` to access the new route parameters, instead.
   *
   *    - `resolveAs` - `{string=}` - The name under which the `resolve` map will be available on
   *      the scope of the route. If omitted, defaults to `$resolve`.
   *
   *    - `redirectTo` – `{(string|Function)=}` – value to update
   *      {@link ng.$location $location} path with and trigger route redirection.
   *
   *      If `redirectTo` is a function, it will be called with the following parameters:
   *
   *      - `{Object.<string>}` - route parameters extracted from the current
   *        `$location.path()` by applying the current route templateUrl.
   *      - `{string}` - current `$location.path()`
   *      - `{Object}` - current `$location.search()`
   *
   *      The custom `redirectTo` function is expected to return a string which will be used
   *      to update `$location.url()`. If the function throws an error, no further processing will
   *      take place and the {@link ngRoute.$route#$routeChangeError $routeChangeError} event will
   *      be fired.
   *
   *      Routes that specify `redirectTo` will not have their controllers, template functions
   *      or resolves called, the `$location` will be changed to the redirect url and route
   *      processing will stop. The exception to this is if the `redirectTo` is a function that
   *      returns `undefined`. In this case the route transition occurs as though there was no
   *      redirection.
   *
   *    - `resolveRedirectTo` – `{Function=}` – a function that will (eventually) return the value
   *      to update {@link ng.$location $location} URL with and trigger route redirection. In
   *      contrast to `redirectTo`, dependencies can be injected into `resolveRedirectTo` and the
   *      return value can be either a string or a promise that will be resolved to a string.
   *
   *      Similar to `redirectTo`, if the return value is `undefined` (or a promise that gets
   *      resolved to `undefined`), no redirection takes place and the route transition occurs as
   *      though there was no redirection.
   *
   *      If the function throws an error or the returned promise gets rejected, no further
   *      processing will take place and the
   *      {@link ngRoute.$route#$routeChangeError $routeChangeError} event will be fired.
   *
   *      `redirectTo` takes precedence over `resolveRedirectTo`, so specifying both on the same
   *      route definition, will cause the latter to be ignored.
   *
   *    - `[reloadOnUrl=true]` - `{boolean=}` - reload route when any part of the URL changes
   *      (including the path) even if the new URL maps to the same route.
   *
   *      If the option is set to `false` and the URL in the browser changes, but the new URL maps
   *      to the same route, then a `$routeUpdate` event is broadcasted on the root scope (without
   *      reloading the route).
   *
   *    - `[reloadOnSearch=true]` - `{boolean=}` - reload route when only `$location.search()`
   *      or `$location.hash()` changes.
   *
   *      If the option is set to `false` and the URL in the browser changes, then a `$routeUpdate`
   *      event is broadcasted on the root scope (without reloading the route).
   *
   *      <div class="alert alert-warning">
   *        **Note:** This option has no effect if `reloadOnUrl` is set to `false`.
   *      </div>
   *
   *    - `[caseInsensitiveMatch=false]` - `{boolean=}` - match routes without being case sensitive
   *
   *      If the option is set to `true`, then the particular route can be matched without being
   *      case sensitive
   *
   * @returns {Object} self
   *
   * @description
   * Adds a new route definition to the `$route` service.
   */
  this.when = function(path, route) {
    //copy original route object to preserve params inherited from proto chain
    var routeCopy = shallowCopy(route);
    if (angular.isUndefined(routeCopy.reloadOnUrl)) {
      routeCopy.reloadOnUrl = true;
    }
    if (angular.isUndefined(routeCopy.reloadOnSearch)) {
      routeCopy.reloadOnSearch = true;
    }
    if (angular.isUndefined(routeCopy.caseInsensitiveMatch)) {
      routeCopy.caseInsensitiveMatch = this.caseInsensitiveMatch;
    }
    routes[path] = angular.extend(
      routeCopy,
      {originalPath: path},
      path && routeToRegExp(path, routeCopy)
    );

    // create redirection for trailing slashes
    if (path) {
      var redirectPath = (path[path.length - 1] === '/')
            ? path.substr(0, path.length - 1)
            : path + '/';

      routes[redirectPath] = angular.extend(
        {originalPath: path, redirectTo: path},
        routeToRegExp(redirectPath, routeCopy)
      );
    }

    return this;
  };

  /**
   * @ngdoc property
   * @name $routeProvider#caseInsensitiveMatch
   * @description
   *
   * A boolean property indicating if routes defined
   * using this provider should be matched using a case insensitive
   * algorithm. Defaults to `false`.
   */
  this.caseInsensitiveMatch = false;

  /**
   * @ngdoc method
   * @name $routeProvider#otherwise
   *
   * @description
   * Sets route definition that will be used on route change when no other route definition
   * is matched.
   *
   * @param {Object|string} params Mapping information to be assigned to `$route.current`.
   * If called with a string, the value maps to `redirectTo`.
   * @returns {Object} self
   */
  this.otherwise = function(params) {
    if (typeof params === 'string') {
      params = {redirectTo: params};
    }
    this.when(null, params);
    return this;
  };

  /**
   * @ngdoc method
   * @name $routeProvider#eagerInstantiationEnabled
   * @kind function
   *
   * @description
   * Call this method as a setter to enable/disable eager instantiation of the
   * {@link ngRoute.$route $route} service upon application bootstrap. You can also call it as a
   * getter (i.e. without any arguments) to get the current value of the
   * `eagerInstantiationEnabled` flag.
   *
   * Instantiating `$route` early is necessary for capturing the initial
   * {@link ng.$location#$locationChangeStart $locationChangeStart} event and navigating to the
   * appropriate route. Usually, `$route` is instantiated in time by the
   * {@link ngRoute.ngView ngView} directive. Yet, in cases where `ngView` is included in an
   * asynchronously loaded template (e.g. in another directive's template), the directive factory
   * might not be called soon enough for `$route` to be instantiated _before_ the initial
   * `$locationChangeSuccess` event is fired. Eager instantiation ensures that `$route` is always
   * instantiated in time, regardless of when `ngView` will be loaded.
   *
   * The default value is true.
   *
   * **Note**:<br />
   * You may want to disable the default behavior when unit-testing modules that depend on
   * `ngRoute`, in order to avoid an unexpected request for the default route's template.
   *
   * @param {boolean=} enabled - If provided, update the internal `eagerInstantiationEnabled` flag.
   *
   * @returns {*} The current value of the `eagerInstantiationEnabled` flag if used as a getter or
   *     itself (for chaining) if used as a setter.
   */
  isEagerInstantiationEnabled = true;
  this.eagerInstantiationEnabled = function eagerInstantiationEnabled(enabled) {
    if (isDefined(enabled)) {
      isEagerInstantiationEnabled = enabled;
      return this;
    }

    return isEagerInstantiationEnabled;
  };


  this.$get = ['$rootScope',
               '$location',
               '$routeParams',
               '$q',
               '$injector',
               '$templateRequest',
               '$sce',
               '$browser',
      function($rootScope, $location, $routeParams, $q, $injector, $templateRequest, $sce, $browser) {

    /**
     * @ngdoc service
     * @name $route
     * @requires $location
     * @requires $routeParams
     *
     * @property {Object} current Reference to the current route definition.
     * The route definition contains:
     *
     *   - `controller`: The controller constructor as defined in the route definition.
     *   - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for
     *     controller instantiation. The `locals` contain
     *     the resolved values of the `resolve` map. Additionally the `locals` also contain:
     *
     *     - `$scope` - The current route scope.
     *     - `$template` - The current route template HTML.
     *
     *     The `locals` will be assigned to the route scope's `$resolve` property. You can override
     *     the property name, using `resolveAs` in the route definition. See
     *     {@link ngRoute.$routeProvider $routeProvider} for more info.
     *
     * @property {Object} routes Object with all route configuration Objects as its properties.
     *
     * @description
     * `$route` is used for deep-linking URLs to controllers and views (HTML partials).
     * It watches `$location.url()` and tries to map the path to an existing route definition.
     *
     * Requires the {@link ngRoute `ngRoute`} module to be installed.
     *
     * You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API.
     *
     * The `$route` service is typically used in conjunction with the
     * {@link ngRoute.directive:ngView `ngView`} directive and the
     * {@link ngRoute.$routeParams `$routeParams`} service.
     *
     * @example
     * This example shows how changing the URL hash causes the `$route` to match a route against the
     * URL, and the `ngView` pulls in the partial.
     *
     * <example name="$route-service" module="ngRouteExample"
     *          deps="angular-route.js" fixBase="true">
     *   <file name="index.html">
     *     <div ng-controller="MainController">
     *       Choose:
     *       <a href="Book/Moby">Moby</a> |
     *       <a href="Book/Moby/ch/1">Moby: Ch1</a> |
     *       <a href="Book/Gatsby">Gatsby</a> |
     *       <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
     *       <a href="Book/Scarlet">Scarlet Letter</a><br/>
     *
     *       <div ng-view></div>
     *
     *       <hr />
     *
     *       <pre>$location.path() = {{$location.path()}}</pre>
     *       <pre>$route.current.templateUrl = {{$route.current.templateUrl}}</pre>
     *       <pre>$route.current.params = {{$route.current.params}}</pre>
     *       <pre>$route.current.scope.name = {{$route.current.scope.name}}</pre>
     *       <pre>$routeParams = {{$routeParams}}</pre>
     *     </div>
     *   </file>
     *
     *   <file name="book.html">
     *     controller: {{name}}<br />
     *     Book Id: {{params.bookId}}<br />
     *   </file>
     *
     *   <file name="chapter.html">
     *     controller: {{name}}<br />
     *     Book Id: {{params.bookId}}<br />
     *     Chapter Id: {{params.chapterId}}
     *   </file>
     *
     *   <file name="script.js">
     *     angular.module('ngRouteExample', ['ngRoute'])
     *
     *      .controller('MainController', function($scope, $route, $routeParams, $location) {
     *          $scope.$route = $route;
     *          $scope.$location = $location;
     *          $scope.$routeParams = $routeParams;
     *      })
     *
     *      .controller('BookController', function($scope, $routeParams) {
     *          $scope.name = 'BookController';
     *          $scope.params = $routeParams;
     *      })
     *
     *      .controller('ChapterController', function($scope, $routeParams) {
     *          $scope.name = 'ChapterController';
     *          $scope.params = $routeParams;
     *      })
     *
     *     .config(function($routeProvider, $locationProvider) {
     *       $routeProvider
     *        .when('/Book/:bookId', {
     *         templateUrl: 'book.html',
     *         controller: 'BookController',
     *         resolve: {
     *           // I will cause a 1 second delay
     *           delay: function($q, $timeout) {
     *             var delay = $q.defer();
     *             $timeout(delay.resolve, 1000);
     *             return delay.promise;
     *           }
     *         }
     *       })
     *       .when('/Book/:bookId/ch/:chapterId', {
     *         templateUrl: 'chapter.html',
     *         controller: 'ChapterController'
     *       });
     *
     *       // configure html5 to get links working on jsfiddle
     *       $locationProvider.html5Mode(true);
     *     });
     *
     *   </file>
     *
     *   <file name="protractor.js" type="protractor">
     *     it('should load and compile correct template', function() {
     *       element(by.linkText('Moby: Ch1')).click();
     *       var content = element(by.css('[ng-view]')).getText();
     *       expect(content).toMatch(/controller: ChapterController/);
     *       expect(content).toMatch(/Book Id: Moby/);
     *       expect(content).toMatch(/Chapter Id: 1/);
     *
     *       element(by.partialLinkText('Scarlet')).click();
     *
     *       content = element(by.css('[ng-view]')).getText();
     *       expect(content).toMatch(/controller: BookController/);
     *       expect(content).toMatch(/Book Id: Scarlet/);
     *     });
     *   </file>
     * </example>
     */

    /**
     * @ngdoc event
     * @name $route#$routeChangeStart
     * @eventType broadcast on root scope
     * @description
     * Broadcasted before a route change. At this  point the route services starts
     * resolving all of the dependencies needed for the route change to occur.
     * Typically this involves fetching the view template as well as any dependencies
     * defined in `resolve` route property. Once  all of the dependencies are resolved
     * `$routeChangeSuccess` is fired.
     *
     * The route change (and the `$location` change that triggered it) can be prevented
     * by calling `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on}
     * for more details about event object.
     *
     * @param {Object} angularEvent Synthetic event object.
     * @param {Route} next Future route information.
     * @param {Route} current Current route information.
     */

    /**
     * @ngdoc event
     * @name $route#$routeChangeSuccess
     * @eventType broadcast on root scope
     * @description
     * Broadcasted after a route change has happened successfully.
     * The `resolve` dependencies are now available in the `current.locals` property.
     *
     * {@link ngRoute.directive:ngView ngView} listens for the directive
     * to instantiate the controller and render the view.
     *
     * @param {Object} angularEvent Synthetic event object.
     * @param {Route} current Current route information.
     * @param {Route|Undefined} previous Previous route information, or undefined if current is
     * first route entered.
     */

    /**
     * @ngdoc event
     * @name $route#$routeChangeError
     * @eventType broadcast on root scope
     * @description
     * Broadcasted if a redirection function fails or any redirection or resolve promises are
     * rejected.
     *
     * @param {Object} angularEvent Synthetic event object
     * @param {Route} current Current route information.
     * @param {Route} previous Previous route information.
     * @param {Route} rejection The thrown error or the rejection reason of the promise. Usually
     * the rejection reason is the error that caused the promise to get rejected.
     */

    /**
     * @ngdoc event
     * @name $route#$routeUpdate
     * @eventType broadcast on root scope
     * @description
     * Broadcasted if the same instance of a route (including template, controller instance,
     * resolved dependencies, etc.) is being reused. This can happen if either `reloadOnSearch` or
     * `reloadOnUrl` has been set to `false`.
     *
     * @param {Object} angularEvent Synthetic event object
     * @param {Route} current Current/previous route information.
     */

    var forceReload = false,
        preparedRoute,
        preparedRouteIsUpdateOnly,
        $route = {
          routes: routes,

          /**
           * @ngdoc method
           * @name $route#reload
           *
           * @description
           * Causes `$route` service to reload the current route even if
           * {@link ng.$location $location} hasn't changed.
           *
           * As a result of that, {@link ngRoute.directive:ngView ngView}
           * creates new scope and reinstantiates the controller.
           */
          reload: function() {
            forceReload = true;

            var fakeLocationEvent = {
              defaultPrevented: false,
              preventDefault: function fakePreventDefault() {
                this.defaultPrevented = true;
                forceReload = false;
              }
            };

            $rootScope.$evalAsync(function() {
              prepareRoute(fakeLocationEvent);
              if (!fakeLocationEvent.defaultPrevented) commitRoute();
            });
          },

          /**
           * @ngdoc method
           * @name $route#updateParams
           *
           * @description
           * Causes `$route` service to update the current URL, replacing
           * current route parameters with those specified in `newParams`.
           * Provided property names that match the route's path segment
           * definitions will be interpolated into the location's path, while
           * remaining properties will be treated as query params.
           *
           * @param {!Object<string, string>} newParams mapping of URL parameter names to values
           */
          updateParams: function(newParams) {
            if (this.current && this.current.$$route) {
              newParams = angular.extend({}, this.current.params, newParams);
              $location.path(interpolate(this.current.$$route.originalPath, newParams));
              // interpolate modifies newParams, only query params are left
              $location.search(newParams);
            } else {
              throw $routeMinErr('norout', 'Tried updating route with no current route');
            }
          }
        };

    $rootScope.$on('$locationChangeStart', prepareRoute);
    $rootScope.$on('$locationChangeSuccess', commitRoute);

    return $route;

    /////////////////////////////////////////////////////

    /**
     * @param on {string} current url
     * @param route {Object} route regexp to match the url against
     * @return {?Object}
     *
     * @description
     * Check if the route matches the current url.
     *
     * Inspired by match in
     * visionmedia/express/lib/router/router.js.
     */
    function switchRouteMatcher(on, route) {
      var keys = route.keys,
          params = {};

      if (!route.regexp) return null;

      var m = route.regexp.exec(on);
      if (!m) return null;

      for (var i = 1, len = m.length; i < len; ++i) {
        var key = keys[i - 1];

        var val = m[i];

        if (key && val) {
          params[key.name] = val;
        }
      }
      return params;
    }

    function prepareRoute($locationEvent) {
      var lastRoute = $route.current;

      preparedRoute = parseRoute();
      preparedRouteIsUpdateOnly = isNavigationUpdateOnly(preparedRoute, lastRoute);

      if (!preparedRouteIsUpdateOnly && (lastRoute || preparedRoute)) {
        if ($rootScope.$broadcast('$routeChangeStart', preparedRoute, lastRoute).defaultPrevented) {
          if ($locationEvent) {
            $locationEvent.preventDefault();
          }
        }
      }
    }

    function commitRoute() {
      var lastRoute = $route.current;
      var nextRoute = preparedRoute;

      if (preparedRouteIsUpdateOnly) {
        lastRoute.params = nextRoute.params;
        angular.copy(lastRoute.params, $routeParams);
        $rootScope.$broadcast('$routeUpdate', lastRoute);
      } else if (nextRoute || lastRoute) {
        forceReload = false;
        $route.current = nextRoute;

        var nextRoutePromise = $q.resolve(nextRoute);

        $browser.$$incOutstandingRequestCount('$route');

        nextRoutePromise.
          then(getRedirectionData).
          then(handlePossibleRedirection).
          then(function(keepProcessingRoute) {
            return keepProcessingRoute && nextRoutePromise.
              then(resolveLocals).
              then(function(locals) {
                // after route change
                if (nextRoute === $route.current) {
                  if (nextRoute) {
                    nextRoute.locals = locals;
                    angular.copy(nextRoute.params, $routeParams);
                  }
                  $rootScope.$broadcast('$routeChangeSuccess', nextRoute, lastRoute);
                }
              });
          }).catch(function(error) {
            if (nextRoute === $route.current) {
              $rootScope.$broadcast('$routeChangeError', nextRoute, lastRoute, error);
            }
          }).finally(function() {
            // Because `commitRoute()` is called from a `$rootScope.$evalAsync` block (see
            // `$locationWatch`), this `$$completeOutstandingRequest()` call will not cause
            // `outstandingRequestCount` to hit zero.  This is important in case we are redirecting
            // to a new route which also requires some asynchronous work.

            $browser.$$completeOutstandingRequest(noop, '$route');
          });
      }
    }

    function getRedirectionData(route) {
      var data = {
        route: route,
        hasRedirection: false
      };

      if (route) {
        if (route.redirectTo) {
          if (angular.isString(route.redirectTo)) {
            data.path = interpolate(route.redirectTo, route.params);
            data.search = route.params;
            data.hasRedirection = true;
          } else {
            var oldPath = $location.path();
            var oldSearch = $location.search();
            var newUrl = route.redirectTo(route.pathParams, oldPath, oldSearch);

            if (angular.isDefined(newUrl)) {
              data.url = newUrl;
              data.hasRedirection = true;
            }
          }
        } else if (route.resolveRedirectTo) {
          return $q.
            resolve($injector.invoke(route.resolveRedirectTo)).
            then(function(newUrl) {
              if (angular.isDefined(newUrl)) {
                data.url = newUrl;
                data.hasRedirection = true;
              }

              return data;
            });
        }
      }

      return data;
    }

    function handlePossibleRedirection(data) {
      var keepProcessingRoute = true;

      if (data.route !== $route.current) {
        keepProcessingRoute = false;
      } else if (data.hasRedirection) {
        var oldUrl = $location.url();
        var newUrl = data.url;

        if (newUrl) {
          $location.
            url(newUrl).
            replace();
        } else {
          newUrl = $location.
            path(data.path).
            search(data.search).
            replace().
            url();
        }

        if (newUrl !== oldUrl) {
          // Exit out and don't process current next value,
          // wait for next location change from redirect
          keepProcessingRoute = false;
        }
      }

      return keepProcessingRoute;
    }

    function resolveLocals(route) {
      if (route) {
        var locals = angular.extend({}, route.resolve);
        angular.forEach(locals, function(value, key) {
          locals[key] = angular.isString(value) ?
              $injector.get(value) :
              $injector.invoke(value, null, null, key);
        });
        var template = getTemplateFor(route);
        if (angular.isDefined(template)) {
          locals['$template'] = template;
        }
        return $q.all(locals);
      }
    }

    function getTemplateFor(route) {
      var template, templateUrl;
      if (angular.isDefined(template = route.template)) {
        if (angular.isFunction(template)) {
          template = template(route.params);
        }
      } else if (angular.isDefined(templateUrl = route.templateUrl)) {
        if (angular.isFunction(templateUrl)) {
          templateUrl = templateUrl(route.params);
        }
        if (angular.isDefined(templateUrl)) {
          route.loadedTemplateUrl = $sce.valueOf(templateUrl);
          template = $templateRequest(templateUrl);
        }
      }
      return template;
    }

    /**
     * @returns {Object} the current active route, by matching it against the URL
     */
    function parseRoute() {
      // Match a route
      var params, match;
      angular.forEach(routes, function(route, path) {
        if (!match && (params = switchRouteMatcher($location.path(), route))) {
          match = inherit(route, {
            params: angular.extend({}, $location.search(), params),
            pathParams: params});
          match.$$route = route;
        }
      });
      // No route matched; fallback to "otherwise" route
      return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}});
    }

    /**
     * @param {Object} newRoute - The new route configuration (as returned by `parseRoute()`).
     * @param {Object} oldRoute - The previous route configuration (as returned by `parseRoute()`).
     * @returns {boolean} Whether this is an "update-only" navigation, i.e. the URL maps to the same
     *                    route and it can be reused (based on the config and the type of change).
     */
    function isNavigationUpdateOnly(newRoute, oldRoute) {
      // IF this is not a forced reload
      return !forceReload
          // AND both `newRoute`/`oldRoute` are defined
          && newRoute && oldRoute
          // AND they map to the same Route Definition Object
          && (newRoute.$$route === oldRoute.$$route)
          // AND `reloadOnUrl` is disabled
          && (!newRoute.reloadOnUrl
              // OR `reloadOnSearch` is disabled
              || (!newRoute.reloadOnSearch
                  // AND both routes have the same path params
                  && angular.equals(newRoute.pathParams, oldRoute.pathParams)
              )
          );
    }

    /**
     * @returns {string} interpolation of the redirect path with the parameters
     */
    function interpolate(string, params) {
      var result = [];
      angular.forEach((string || '').split(':'), function(segment, i) {
        if (i === 0) {
          result.push(segment);
        } else {
          var segmentMatch = segment.match(/(\w+)(?:[?*])?(.*)/);
          var key = segmentMatch[1];
          result.push(params[key]);
          result.push(segmentMatch[2] || '');
          delete params[key];
        }
      });
      return result.join('');
    }
  }];
}

instantiateRoute.$inject = ['$injector'];
function instantiateRoute($injector) {
  if (isEagerInstantiationEnabled) {
    // Instantiate `$route`
    $injector.get('$route');
  }
}

ngRouteModule.provider('$routeParams', $RouteParamsProvider);


/**
 * @ngdoc service
 * @name $routeParams
 * @requires $route
 * @this
 *
 * @description
 * The `$routeParams` service allows you to retrieve the current set of route parameters.
 *
 * Requires the {@link ngRoute `ngRoute`} module to be installed.
 *
 * The route parameters are a combination of {@link ng.$location `$location`}'s
 * {@link ng.$location#search `search()`} and {@link ng.$location#path `path()`}.
 * The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched.
 *
 * In case of parameter name collision, `path` params take precedence over `search` params.
 *
 * The service guarantees that the identity of the `$routeParams` object will remain unchanged
 * (but its properties will likely change) even when a route change occurs.
 *
 * Note that the `$routeParams` are only updated *after* a route change completes successfully.
 * This means that you cannot rely on `$routeParams` being correct in route resolve functions.
 * Instead you can use `$route.current.params` to access the new route's parameters.
 *
 * @example
 * ```js
 *  // Given:
 *  // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
 *  // Route: /Chapter/:chapterId/Section/:sectionId
 *  //
 *  // Then
 *  $routeParams ==> {chapterId:'1', sectionId:'2', search:'moby'}
 * ```
 */
function $RouteParamsProvider() {
  this.$get = function() { return {}; };
}

ngRouteModule.directive('ngView', ngViewFactory);
ngRouteModule.directive('ngView', ngViewFillContentFactory);


/**
 * @ngdoc directive
 * @name ngView
 * @restrict ECA
 *
 * @description
 * `ngView` is a directive that complements the {@link ngRoute.$route $route} service by
 * including the rendered template of the current route into the main layout (`index.html`) file.
 * Every time the current route changes, the included view changes with it according to the
 * configuration of the `$route` service.
 *
 * Requires the {@link ngRoute `ngRoute`} module to be installed.
 *
 * @animations
 * | Animation                        | Occurs                              |
 * |----------------------------------|-------------------------------------|
 * | {@link ng.$animate#enter enter}  | when the new element is inserted to the DOM |
 * | {@link ng.$animate#leave leave}  | when the old element is removed from to the DOM  |
 *
 * The enter and leave animation occur concurrently.
 *
 * @scope
 * @priority 400
 * @param {string=} onload Expression to evaluate whenever the view updates.
 *
 * @param {string=} autoscroll Whether `ngView` should call {@link ng.$anchorScroll
 *                  $anchorScroll} to scroll the viewport after the view is updated.
 *
 *                  - If the attribute is not set, disable scrolling.
 *                  - If the attribute is set without value, enable scrolling.
 *                  - Otherwise enable scrolling only if the `autoscroll` attribute value evaluated
 *                    as an expression yields a truthy value.
 * @example
    <example name="ngView-directive" module="ngViewExample"
             deps="angular-route.js;angular-animate.js"
             animations="true" fixBase="true">
      <file name="index.html">
        <div ng-controller="MainCtrl as main">
          Choose:
          <a href="Book/Moby">Moby</a> |
          <a href="Book/Moby/ch/1">Moby: Ch1</a> |
          <a href="Book/Gatsby">Gatsby</a> |
          <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
          <a href="Book/Scarlet">Scarlet Letter</a><br/>

          <div class="view-animate-container">
            <div ng-view class="view-animate"></div>
          </div>
          <hr />

          <pre>$location.path() = {{main.$location.path()}}</pre>
          <pre>$route.current.templateUrl = {{main.$route.current.templateUrl}}</pre>
          <pre>$route.current.params = {{main.$route.current.params}}</pre>
          <pre>$routeParams = {{main.$routeParams}}</pre>
        </div>
      </file>

      <file name="book.html">
        <div>
          controller: {{book.name}}<br />
          Book Id: {{book.params.bookId}}<br />
        </div>
      </file>

      <file name="chapter.html">
        <div>
          controller: {{chapter.name}}<br />
          Book Id: {{chapter.params.bookId}}<br />
          Chapter Id: {{chapter.params.chapterId}}
        </div>
      </file>

      <file name="animations.css">
        .view-animate-container {
          position:relative;
          height:100px!important;
          background:white;
          border:1px solid black;
          height:40px;
          overflow:hidden;
        }

        .view-animate {
          padding:10px;
        }

        .view-animate.ng-enter, .view-animate.ng-leave {
          transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;

          display:block;
          width:100%;
          border-left:1px solid black;

          position:absolute;
          top:0;
          left:0;
          right:0;
          bottom:0;
          padding:10px;
        }

        .view-animate.ng-enter {
          left:100%;
        }
        .view-animate.ng-enter.ng-enter-active {
          left:0;
        }
        .view-animate.ng-leave.ng-leave-active {
          left:-100%;
        }
      </file>

      <file name="script.js">
        angular.module('ngViewExample', ['ngRoute', 'ngAnimate'])
          .config(['$routeProvider', '$locationProvider',
            function($routeProvider, $locationProvider) {
              $routeProvider
                .when('/Book/:bookId', {
                  templateUrl: 'book.html',
                  controller: 'BookCtrl',
                  controllerAs: 'book'
                })
                .when('/Book/:bookId/ch/:chapterId', {
                  templateUrl: 'chapter.html',
                  controller: 'ChapterCtrl',
                  controllerAs: 'chapter'
                });

              $locationProvider.html5Mode(true);
          }])
          .controller('MainCtrl', ['$route', '$routeParams', '$location',
            function MainCtrl($route, $routeParams, $location) {
              this.$route = $route;
              this.$location = $location;
              this.$routeParams = $routeParams;
          }])
          .controller('BookCtrl', ['$routeParams', function BookCtrl($routeParams) {
            this.name = 'BookCtrl';
            this.params = $routeParams;
          }])
          .controller('ChapterCtrl', ['$routeParams', function ChapterCtrl($routeParams) {
            this.name = 'ChapterCtrl';
            this.params = $routeParams;
          }]);

      </file>

      <file name="protractor.js" type="protractor">
        it('should load and compile correct template', function() {
          element(by.linkText('Moby: Ch1')).click();
          var content = element(by.css('[ng-view]')).getText();
          expect(content).toMatch(/controller: ChapterCtrl/);
          expect(content).toMatch(/Book Id: Moby/);
          expect(content).toMatch(/Chapter Id: 1/);

          element(by.partialLinkText('Scarlet')).click();

          content = element(by.css('[ng-view]')).getText();
          expect(content).toMatch(/controller: BookCtrl/);
          expect(content).toMatch(/Book Id: Scarlet/);
        });
      </file>
    </example>
 */


/**
 * @ngdoc event
 * @name ngView#$viewContentLoaded
 * @eventType emit on the current ngView scope
 * @description
 * Emitted every time the ngView content is reloaded.
 */
ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate'];
function ngViewFactory($route, $anchorScroll, $animate) {
  return {
    restrict: 'ECA',
    terminal: true,
    priority: 400,
    transclude: 'element',
    link: function(scope, $element, attr, ctrl, $transclude) {
        var currentScope,
            currentElement,
            previousLeaveAnimation,
            autoScrollExp = attr.autoscroll,
            onloadExp = attr.onload || '';

        scope.$on('$routeChangeSuccess', update);
        update();

        function cleanupLastView() {
          if (previousLeaveAnimation) {
            $animate.cancel(previousLeaveAnimation);
            previousLeaveAnimation = null;
          }

          if (currentScope) {
            currentScope.$destroy();
            currentScope = null;
          }
          if (currentElement) {
            previousLeaveAnimation = $animate.leave(currentElement);
            previousLeaveAnimation.done(function(response) {
              if (response !== false) previousLeaveAnimation = null;
            });
            currentElement = null;
          }
        }

        function update() {
          var locals = $route.current && $route.current.locals,
              template = locals && locals.$template;

          if (angular.isDefined(template)) {
            var newScope = scope.$new();
            var current = $route.current;

            // Note: This will also link all children of ng-view that were contained in the original
            // html. If that content contains controllers, ... they could pollute/change the scope.
            // However, using ng-view on an element with additional content does not make sense...
            // Note: We can't remove them in the cloneAttchFn of $transclude as that
            // function is called before linking the content, which would apply child
            // directives to non existing elements.
            var clone = $transclude(newScope, function(clone) {
              $animate.enter(clone, null, currentElement || $element).done(function onNgViewEnter(response) {
                if (response !== false && angular.isDefined(autoScrollExp)
                  && (!autoScrollExp || scope.$eval(autoScrollExp))) {
                  $anchorScroll();
                }
              });
              cleanupLastView();
            });

            currentElement = clone;
            currentScope = current.scope = newScope;
            currentScope.$emit('$viewContentLoaded');
            currentScope.$eval(onloadExp);
          } else {
            cleanupLastView();
          }
        }
    }
  };
}

// This directive is called during the $transclude call of the first `ngView` directive.
// It will replace and compile the content of the element with the loaded template.
// We need this directive so that the element content is already filled when
// the link function of another directive on the same element as ngView
// is called.
ngViewFillContentFactory.$inject = ['$compile', '$controller', '$route'];
function ngViewFillContentFactory($compile, $controller, $route) {
  return {
    restrict: 'ECA',
    priority: -400,
    link: function(scope, $element) {
      var current = $route.current,
          locals = current.locals;

      $element.html(locals.$template);

      var link = $compile($element.contents());

      if (current.controller) {
        locals.$scope = scope;
        var controller = $controller(current.controller, locals);
        if (current.controllerAs) {
          scope[current.controllerAs] = controller;
        }
        $element.data('$ngControllerController', controller);
        $element.children().data('$ngControllerController', controller);
      }
      scope[current.resolveAs || '$resolve'] = locals;

      link(scope);
    }
  };
}


})(window, window.angular);

/*
 * angular-ui-bootstrap
 * http://angular-ui.github.io/bootstrap/

 * Version: 0.13.4 - 2015-09-03
 * License: MIT
 */
angular.module("ui.bootstrap",["ui.bootstrap.tpls","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.transition","ui.bootstrap.typeahead"]),angular.module("ui.bootstrap.tpls",["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/carousel/carousel.html","template/carousel/slide.html","template/datepicker/datepicker.html","template/datepicker/day.html","template/datepicker/month.html","template/datepicker/popup.html","template/datepicker/year.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-popup.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/tooltip/tooltip-template-popup.html","template/popover/popover-html.html","template/popover/popover-template.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/timepicker/timepicker.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]),angular.module("ui.bootstrap.collapse",[]).directive("collapse",["$animate",function(a){return{link:function(b,c,d){function e(){c.removeClass("collapse").addClass("collapsing").attr("aria-expanded",!0).attr("aria-hidden",!1),a.addClass(c,"in",{to:{height:c[0].scrollHeight+"px"}}).then(f)}function f(){c.removeClass("collapsing"),c.css({height:"auto"})}function g(){return c.hasClass("collapse")||c.hasClass("in")?(c.css({height:c[0].scrollHeight+"px"}).removeClass("collapse").addClass("collapsing").attr("aria-expanded",!1).attr("aria-hidden",!0),void a.removeClass(c,"in",{to:{height:"0"}}).then(h)):h()}function h(){c.css({height:"0"}),c.removeClass("collapsing"),c.addClass("collapse")}b.$watch(d.collapse,function(a){a?g():e()})}}}]),angular.module("ui.bootstrap.accordion",["ui.bootstrap.collapse"]).constant("accordionConfig",{closeOthers:!0}).controller("AccordionController",["$scope","$attrs","accordionConfig",function(a,b,c){this.groups=[],this.closeOthers=function(d){var e=angular.isDefined(b.closeOthers)?a.$eval(b.closeOthers):c.closeOthers;e&&angular.forEach(this.groups,function(a){a!==d&&(a.isOpen=!1)})},this.addGroup=function(a){var b=this;this.groups.push(a),a.$on("$destroy",function(c){b.removeGroup(a)})},this.removeGroup=function(a){var b=this.groups.indexOf(a);-1!==b&&this.groups.splice(b,1)}}]).directive("accordion",function(){return{restrict:"EA",controller:"AccordionController",controllerAs:"accordion",transclude:!0,replace:!1,templateUrl:function(a,b){return b.templateUrl||"template/accordion/accordion.html"}}}).directive("accordionGroup",function(){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:function(a,b){return b.templateUrl||"template/accordion/accordion-group.html"},scope:{heading:"@",isOpen:"=?",isDisabled:"=?"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(a,b,c,d){d.addGroup(a),a.openClass=c.openClass||"panel-open",a.panelClass=c.panelClass,a.$watch("isOpen",function(c){b.toggleClass(a.openClass,c),c&&d.closeOthers(a)}),a.toggleOpen=function(b){a.isDisabled||b&&32!==b.which||(a.isOpen=!a.isOpen)}}}}).directive("accordionHeading",function(){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",link:function(a,b,c,d,e){d.setHeading(e(a,angular.noop))}}}).directive("accordionTransclude",function(){return{require:"^accordionGroup",link:function(a,b,c,d){a.$watch(function(){return d[c.accordionTransclude]},function(a){a&&(b.find("span").html(""),b.find("span").append(a))})}}}),angular.module("ui.bootstrap.alert",[]).controller("AlertController",["$scope","$attrs",function(a,b){a.closeable=!!b.close,this.close=a.close}]).directive("alert",function(){return{controller:"AlertController",controllerAs:"alert",templateUrl:function(a,b){return b.templateUrl||"template/alert/alert.html"},transclude:!0,replace:!0,scope:{type:"@",close:"&"}}}).directive("dismissOnTimeout",["$timeout",function(a){return{require:"alert",link:function(b,c,d,e){a(function(){e.close()},parseInt(d.dismissOnTimeout,10))}}}]),angular.module("ui.bootstrap.bindHtml",[]).value("$bindHtmlUnsafeSuppressDeprecated",!1).directive("bindHtmlUnsafe",["$log","$bindHtmlUnsafeSuppressDeprecated",function(a,b){return function(c,d,e){b||a.warn("bindHtmlUnsafe is now deprecated. Use ngBindHtml instead"),d.addClass("ng-binding").data("$binding",e.bindHtmlUnsafe),c.$watch(e.bindHtmlUnsafe,function(a){d.html(a||"")})}}]),angular.module("ui.bootstrap.buttons",[]).constant("buttonConfig",{activeClass:"active",toggleEvent:"click"}).controller("ButtonsController",["buttonConfig",function(a){this.activeClass=a.activeClass||"active",this.toggleEvent=a.toggleEvent||"click"}]).directive("btnRadio",function(){return{require:["btnRadio","ngModel"],controller:"ButtonsController",controllerAs:"buttons",link:function(a,b,c,d){var e=d[0],f=d[1];b.find("input").css({display:"none"}),f.$render=function(){b.toggleClass(e.activeClass,angular.equals(f.$modelValue,a.$eval(c.btnRadio)))},b.bind(e.toggleEvent,function(){if(!c.disabled){var d=b.hasClass(e.activeClass);(!d||angular.isDefined(c.uncheckable))&&a.$apply(function(){f.$setViewValue(d?null:a.$eval(c.btnRadio)),f.$render()})}})}}}).directive("btnCheckbox",["$document",function(a){return{require:["btnCheckbox","ngModel"],controller:"ButtonsController",controllerAs:"button",link:function(b,c,d,e){function f(){return h(d.btnCheckboxTrue,!0)}function g(){return h(d.btnCheckboxFalse,!1)}function h(a,c){var d=b.$eval(a);return angular.isDefined(d)?d:c}var i=e[0],j=e[1];c.find("input").css({display:"none"}),j.$render=function(){c.toggleClass(i.activeClass,angular.equals(j.$modelValue,f()))},c.bind(i.toggleEvent,function(){d.disabled||b.$apply(function(){j.$setViewValue(c.hasClass(i.activeClass)?g():f()),j.$render()})}),c.on("keypress",function(e){d.disabled||32!==e.which||a[0].activeElement!==c[0]||b.$apply(function(){j.$setViewValue(c.hasClass(i.activeClass)?g():f()),j.$render()})})}}}]),angular.module("ui.bootstrap.carousel",[]).controller("CarouselController",["$scope","$element","$interval","$animate",function(a,b,c,d){function e(b,c,e){r||(angular.extend(b,{direction:e,active:!0}),angular.extend(l.currentSlide||{},{direction:e,active:!1}),d.enabled()&&!a.noTransition&&!a.$currentTransition&&b.$element&&l.slides.length>1&&(b.$element.data(p,b.direction),l.currentSlide&&l.currentSlide.$element&&l.currentSlide.$element.data(p,b.direction),a.$currentTransition=!0,n?d.on("addClass",b.$element,function(b,c){"close"===c&&(a.$currentTransition=null,d.off("addClass",b))}):b.$element.one("$animate:close",function(){a.$currentTransition=null})),l.currentSlide=b,q=c,g())}function f(a){if(angular.isUndefined(m[a].index))return m[a];var b;m.length;for(b=0;b<m.length;++b)if(m[b].index==a)return m[b]}function g(){h();var b=+a.interval;!isNaN(b)&&b>0&&(j=c(i,b))}function h(){j&&(c.cancel(j),j=null)}function i(){var b=+a.interval;k&&!isNaN(b)&&b>0&&m.length?a.next():a.pause()}var j,k,l=this,m=l.slides=a.slides=[],n=angular.version.minor>=4,o="uib-noTransition",p="uib-slideDirection",q=-1;l.currentSlide=null;var r=!1;l.select=a.select=function(b,c){var d=a.indexOfSlide(b);void 0===c&&(c=d>l.getCurrentIndex()?"next":"prev"),b&&b!==l.currentSlide&&!a.$currentTransition&&e(b,d,c)},a.$on("$destroy",function(){r=!0}),l.getCurrentIndex=function(){return l.currentSlide&&angular.isDefined(l.currentSlide.index)?+l.currentSlide.index:q},a.indexOfSlide=function(a){return angular.isDefined(a.index)?+a.index:m.indexOf(a)},a.next=function(){var b=(l.getCurrentIndex()+1)%m.length;return 0===b&&a.noWrap()?void a.pause():l.select(f(b),"next")},a.prev=function(){var b=l.getCurrentIndex()-1<0?m.length-1:l.getCurrentIndex()-1;return a.noWrap()&&b===m.length-1?void a.pause():l.select(f(b),"prev")},a.isActive=function(a){return l.currentSlide===a},a.$watch("interval",g),a.$on("$destroy",h),a.play=function(){k||(k=!0,g())},a.pause=function(){a.noPause||(k=!1,h())},l.addSlide=function(b,c){b.$element=c,m.push(b),1===m.length||b.active?(l.select(m[m.length-1]),1==m.length&&a.play()):b.active=!1},l.removeSlide=function(a){angular.isDefined(a.index)&&m.sort(function(a,b){return+a.index>+b.index});var b=m.indexOf(a);m.splice(b,1),m.length>0&&a.active?b>=m.length?l.select(m[b-1]):l.select(m[b]):q>b&&q--,0===m.length&&(l.currentSlide=null)},a.$watch("noTransition",function(a){b.data(o,a)})}]).directive("carousel",[function(){return{restrict:"EA",transclude:!0,replace:!0,controller:"CarouselController",controllerAs:"carousel",require:"carousel",templateUrl:function(a,b){return b.templateUrl||"template/carousel/carousel.html"},scope:{interval:"=",noTransition:"=",noPause:"=",noWrap:"&"}}}]).directive("slide",function(){return{require:"^carousel",restrict:"EA",transclude:!0,replace:!0,templateUrl:function(a,b){return b.templateUrl||"template/carousel/slide.html"},scope:{active:"=?",actual:"=?",index:"=?"},link:function(a,b,c,d){d.addSlide(a,b),a.$on("$destroy",function(){d.removeSlide(a)}),a.$watch("active",function(b){b&&d.select(a)})}}}).animation(".item",["$injector","$animate",function(a,b){function c(a,b,c){a.removeClass(b),c&&c()}var d="uib-noTransition",e="uib-slideDirection",f=null;return a.has("$animateCss")&&(f=a.get("$animateCss")),{beforeAddClass:function(a,g,h){if("active"==g&&a.parent()&&!a.parent().data(d)){var i=!1,j=a.data(e),k="next"==j?"left":"right",l=c.bind(this,a,k+" "+j,h);return a.addClass(j),f?f(a,{addClass:k}).start().done(l):b.addClass(a,k).then(function(){i||l(),h()}),function(){i=!0}}h()},beforeRemoveClass:function(a,g,h){if("active"===g&&a.parent()&&!a.parent().data(d)){var i=!1,j=a.data(e),k="next"==j?"left":"right",l=c.bind(this,a,k,h);return f?f(a,{addClass:k}).start().done(l):b.addClass(a,k).then(function(){i||l(),h()}),function(){i=!0}}h()}}}]),angular.module("ui.bootstrap.dateparser",[]).service("dateParser",["$log","$locale","orderByFilter",function(a,b,c){function d(a){var b=[],d=a.split("");return angular.forEach(g,function(c,e){var f=a.indexOf(e);if(f>-1){a=a.split(""),d[f]="("+c.regex+")",a[f]="$";for(var g=f+1,h=f+e.length;h>g;g++)d[g]="",a[g]="$";a=a.join(""),b.push({index:f,apply:c.apply})}}),{regex:new RegExp("^"+d.join("")+"$"),map:c(b,"index")}}function e(a,b,c){return 1>c?!1:1===b&&c>28?29===c&&(a%4===0&&a%100!==0||a%400===0):3===b||5===b||8===b||10===b?31>c:!0}var f=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;this.parsers={};var g={yyyy:{regex:"\\d{4}",apply:function(a){this.year=+a}},yy:{regex:"\\d{2}",apply:function(a){this.year=+a+2e3}},y:{regex:"\\d{1,4}",apply:function(a){this.year=+a}},MMMM:{regex:b.DATETIME_FORMATS.MONTH.join("|"),apply:function(a){this.month=b.DATETIME_FORMATS.MONTH.indexOf(a)}},MMM:{regex:b.DATETIME_FORMATS.SHORTMONTH.join("|"),apply:function(a){this.month=b.DATETIME_FORMATS.SHORTMONTH.indexOf(a)}},MM:{regex:"0[1-9]|1[0-2]",apply:function(a){this.month=a-1}},M:{regex:"[1-9]|1[0-2]",apply:function(a){this.month=a-1}},dd:{regex:"[0-2][0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a}},d:{regex:"[1-2]?[0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a}},EEEE:{regex:b.DATETIME_FORMATS.DAY.join("|")},EEE:{regex:b.DATETIME_FORMATS.SHORTDAY.join("|")},HH:{regex:"(?:0|1)[0-9]|2[0-3]",apply:function(a){this.hours=+a}},hh:{regex:"0[0-9]|1[0-2]",apply:function(a){this.hours=+a}},H:{regex:"1?[0-9]|2[0-3]",apply:function(a){this.hours=+a}},h:{regex:"[0-9]|1[0-2]",apply:function(a){this.hours=+a}},mm:{regex:"[0-5][0-9]",apply:function(a){this.minutes=+a}},m:{regex:"[0-9]|[1-5][0-9]",apply:function(a){this.minutes=+a}},sss:{regex:"[0-9][0-9][0-9]",apply:function(a){this.milliseconds=+a}},ss:{regex:"[0-5][0-9]",apply:function(a){this.seconds=+a}},s:{regex:"[0-9]|[1-5][0-9]",apply:function(a){this.seconds=+a}},a:{regex:b.DATETIME_FORMATS.AMPMS.join("|"),apply:function(a){12===this.hours&&(this.hours=0),"PM"===a&&(this.hours+=12)}}};this.parse=function(c,g,h){if(!angular.isString(c)||!g)return c;g=b.DATETIME_FORMATS[g]||g,g=g.replace(f,"\\$&"),this.parsers[g]||(this.parsers[g]=d(g));var i=this.parsers[g],j=i.regex,k=i.map,l=c.match(j);if(l&&l.length){var m,n;angular.isDate(h)&&!isNaN(h.getTime())?m={year:h.getFullYear(),month:h.getMonth(),date:h.getDate(),hours:h.getHours(),minutes:h.getMinutes(),seconds:h.getSeconds(),milliseconds:h.getMilliseconds()}:(h&&a.warn("dateparser:","baseDate is not a valid date"),m={year:1900,month:0,date:1,hours:0,minutes:0,seconds:0,milliseconds:0});for(var o=1,p=l.length;p>o;o++){var q=k[o-1];q.apply&&q.apply.call(m,l[o])}return e(m.year,m.month,m.date)&&(n=new Date(m.year,m.month,m.date,m.hours,m.minutes,m.seconds,m.milliseconds||0)),n}}}]),angular.module("ui.bootstrap.position",[]).factory("$position",["$document","$window",function(a,b){function c(a,c){return a.currentStyle?a.currentStyle[c]:b.getComputedStyle?b.getComputedStyle(a)[c]:a.style[c]}function d(a){return"static"===(c(a,"position")||"static")}var e=function(b){for(var c=a[0],e=b.offsetParent||c;e&&e!==c&&d(e);)e=e.offsetParent;return e||c};return{position:function(b){var c=this.offset(b),d={top:0,left:0},f=e(b[0]);f!=a[0]&&(d=this.offset(angular.element(f)),d.top+=f.clientTop-f.scrollTop,d.left+=f.clientLeft-f.scrollLeft);var g=b[0].getBoundingClientRect();return{width:g.width||b.prop("offsetWidth"),height:g.height||b.prop("offsetHeight"),top:c.top-d.top,left:c.left-d.left}},offset:function(c){var d=c[0].getBoundingClientRect();return{width:d.width||c.prop("offsetWidth"),height:d.height||c.prop("offsetHeight"),top:d.top+(b.pageYOffset||a[0].documentElement.scrollTop),left:d.left+(b.pageXOffset||a[0].documentElement.scrollLeft)}},positionElements:function(a,b,c,d){var e,f,g,h,i=c.split("-"),j=i[0],k=i[1]||"center";e=d?this.offset(a):this.position(a),f=b.prop("offsetWidth"),g=b.prop("offsetHeight");var l={center:function(){return e.left+e.width/2-f/2},left:function(){return e.left},right:function(){return e.left+e.width}},m={center:function(){return e.top+e.height/2-g/2},top:function(){return e.top},bottom:function(){return e.top+e.height}};switch(j){case"right":h={top:m[k](),left:l[j]()};break;case"left":h={top:m[k](),left:e.left-f};break;case"bottom":h={top:m[j](),left:l[k]()};break;default:h={top:e.top-g,left:l[k]()}}return h}}}]),angular.module("ui.bootstrap.datepicker",["ui.bootstrap.dateparser","ui.bootstrap.position"]).value("$datepickerSuppressError",!1).constant("datepickerConfig",{formatDay:"dd",formatMonth:"MMMM",formatYear:"yyyy",formatDayHeader:"EEE",formatDayTitle:"MMMM yyyy",formatMonthTitle:"yyyy",datepickerMode:"day",minMode:"day",maxMode:"year",showWeeks:!0,startingDay:0,yearRange:20,minDate:null,maxDate:null,shortcutPropagation:!1}).controller("DatepickerController",["$scope","$attrs","$parse","$interpolate","$log","dateFilter","datepickerConfig","$datepickerSuppressError",function(a,b,c,d,e,f,g,h){var i=this,j={$setViewValue:angular.noop};this.modes=["day","month","year"],angular.forEach(["formatDay","formatMonth","formatYear","formatDayHeader","formatDayTitle","formatMonthTitle","showWeeks","startingDay","yearRange","shortcutPropagation"],function(c,e){i[c]=angular.isDefined(b[c])?6>e?d(b[c])(a.$parent):a.$parent.$eval(b[c]):g[c]}),angular.forEach(["minDate","maxDate"],function(d){b[d]?a.$parent.$watch(c(b[d]),function(a){i[d]=a?new Date(a):null,i.refreshView()}):i[d]=g[d]?new Date(g[d]):null}),angular.forEach(["minMode","maxMode"],function(d){b[d]?a.$parent.$watch(c(b[d]),function(c){i[d]=angular.isDefined(c)?c:b[d],a[d]=i[d],("minMode"==d&&i.modes.indexOf(a.datepickerMode)<i.modes.indexOf(i[d])||"maxMode"==d&&i.modes.indexOf(a.datepickerMode)>i.modes.indexOf(i[d]))&&(a.datepickerMode=i[d])}):(i[d]=g[d]||null,a[d]=i[d])}),a.datepickerMode=a.datepickerMode||g.datepickerMode,a.uniqueId="datepicker-"+a.$id+"-"+Math.floor(1e4*Math.random()),angular.isDefined(b.initDate)?(this.activeDate=a.$parent.$eval(b.initDate)||new Date,a.$parent.$watch(b.initDate,function(a){a&&(j.$isEmpty(j.$modelValue)||j.$invalid)&&(i.activeDate=a,i.refreshView())})):this.activeDate=new Date,a.isActive=function(b){return 0===i.compare(b.date,i.activeDate)?(a.activeDateId=b.uid,!0):!1},this.init=function(a){j=a,j.$render=function(){i.render()}},this.render=function(){if(j.$viewValue){var a=new Date(j.$viewValue),b=!isNaN(a);b?this.activeDate=a:h||e.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.')}this.refreshView()},this.refreshView=function(){if(this.element){this._refreshView();var a=j.$viewValue?new Date(j.$viewValue):null;j.$setValidity("dateDisabled",!a||this.element&&!this.isDisabled(a))}},this.createDateObject=function(a,b){var c=j.$viewValue?new Date(j.$viewValue):null;return{date:a,label:f(a,b),selected:c&&0===this.compare(a,c),disabled:this.isDisabled(a),current:0===this.compare(a,new Date),customClass:this.customClass(a)}},this.isDisabled=function(c){return this.minDate&&this.compare(c,this.minDate)<0||this.maxDate&&this.compare(c,this.maxDate)>0||b.dateDisabled&&a.dateDisabled({date:c,mode:a.datepickerMode})},this.customClass=function(b){return a.customClass({date:b,mode:a.datepickerMode})},this.split=function(a,b){for(var c=[];a.length>0;)c.push(a.splice(0,b));return c},this.fixTimeZone=function(a){var b=a.getHours();a.setHours(23===b?b+2:0)},a.select=function(b){if(a.datepickerMode===i.minMode){var c=j.$viewValue?new Date(j.$viewValue):new Date(0,0,0,0,0,0,0);c.setFullYear(b.getFullYear(),b.getMonth(),b.getDate()),j.$setViewValue(c),j.$render()}else i.activeDate=b,a.datepickerMode=i.modes[i.modes.indexOf(a.datepickerMode)-1]},a.move=function(a){var b=i.activeDate.getFullYear()+a*(i.step.years||0),c=i.activeDate.getMonth()+a*(i.step.months||0);i.activeDate.setFullYear(b,c,1),i.refreshView()},a.toggleMode=function(b){b=b||1,a.datepickerMode===i.maxMode&&1===b||a.datepickerMode===i.minMode&&-1===b||(a.datepickerMode=i.modes[i.modes.indexOf(a.datepickerMode)+b])},a.keys={13:"enter",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down"};var k=function(){i.element[0].focus()};a.$on("datepicker.focus",k),a.keydown=function(b){var c=a.keys[b.which];if(c&&!b.shiftKey&&!b.altKey)if(b.preventDefault(),i.shortcutPropagation||b.stopPropagation(),"enter"===c||"space"===c){if(i.isDisabled(i.activeDate))return;a.select(i.activeDate),k()}else!b.ctrlKey||"up"!==c&&"down"!==c?(i.handleKeyDown(c,b),i.refreshView()):(a.toggleMode("up"===c?1:-1),k())}}]).directive("datepicker",function(){return{restrict:"EA",replace:!0,templateUrl:function(a,b){return b.templateUrl||"template/datepicker/datepicker.html"},scope:{datepickerMode:"=?",dateDisabled:"&",customClass:"&",shortcutPropagation:"&?"},require:["datepicker","^ngModel"],controller:"DatepickerController",controllerAs:"datepicker",link:function(a,b,c,d){var e=d[0],f=d[1];e.init(f)}}}).directive("daypicker",["dateFilter",function(a){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/day.html",require:"^datepicker",link:function(b,c,d,e){function f(a,b){return 1!==b||a%4!==0||a%100===0&&a%400!==0?i[b]:29}function g(a,b){for(var c,d=new Array(b),f=new Date(a),g=0;b>g;)c=new Date(f),e.fixTimeZone(c),d[g++]=c,f.setDate(f.getDate()+1);return d}function h(a){var b=new Date(a);b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();return b.setMonth(0),b.setDate(1),Math.floor(Math.round((c-b)/864e5)/7)+1}b.showWeeks=e.showWeeks,e.step={months:1},e.element=c;var i=[31,28,31,30,31,30,31,31,30,31,30,31];e._refreshView=function(){var c=e.activeDate.getFullYear(),d=e.activeDate.getMonth(),f=new Date(c,d,1),i=e.startingDay-f.getDay(),j=i>0?7-i:-i,k=new Date(f);j>0&&k.setDate(-j+1);for(var l=g(k,42),m=0;42>m;m++)l[m]=angular.extend(e.createDateObject(l[m],e.formatDay),{secondary:l[m].getMonth()!==d,uid:b.uniqueId+"-"+m});b.labels=new Array(7);for(var n=0;7>n;n++)b.labels[n]={abbr:a(l[n].date,e.formatDayHeader),full:a(l[n].date,"EEEE")};if(b.title=a(e.activeDate,e.formatDayTitle),b.rows=e.split(l,7),b.showWeeks){b.weekNumbers=[];for(var o=(11-e.startingDay)%7,p=b.rows.length,q=0;p>q;q++)b.weekNumbers.push(h(b.rows[q][o].date))}},e.compare=function(a,b){return new Date(a.getFullYear(),a.getMonth(),a.getDate())-new Date(b.getFullYear(),b.getMonth(),b.getDate())},e.handleKeyDown=function(a,b){var c=e.activeDate.getDate();if("left"===a)c-=1;else if("up"===a)c-=7;else if("right"===a)c+=1;else if("down"===a)c+=7;else if("pageup"===a||"pagedown"===a){var d=e.activeDate.getMonth()+("pageup"===a?-1:1);e.activeDate.setMonth(d,1),c=Math.min(f(e.activeDate.getFullYear(),e.activeDate.getMonth()),c)}else"home"===a?c=1:"end"===a&&(c=f(e.activeDate.getFullYear(),e.activeDate.getMonth()));e.activeDate.setDate(c)},e.refreshView()}}}]).directive("monthpicker",["dateFilter",function(a){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/month.html",require:"^datepicker",link:function(b,c,d,e){e.step={years:1},e.element=c,e._refreshView=function(){for(var c,d=new Array(12),f=e.activeDate.getFullYear(),g=0;12>g;g++)c=new Date(f,g,1),e.fixTimeZone(c),d[g]=angular.extend(e.createDateObject(c,e.formatMonth),{uid:b.uniqueId+"-"+g});b.title=a(e.activeDate,e.formatMonthTitle),b.rows=e.split(d,3)},e.compare=function(a,b){return new Date(a.getFullYear(),a.getMonth())-new Date(b.getFullYear(),b.getMonth())},e.handleKeyDown=function(a,b){var c=e.activeDate.getMonth();if("left"===a)c-=1;else if("up"===a)c-=3;else if("right"===a)c+=1;else if("down"===a)c+=3;else if("pageup"===a||"pagedown"===a){var d=e.activeDate.getFullYear()+("pageup"===a?-1:1);e.activeDate.setFullYear(d)}else"home"===a?c=0:"end"===a&&(c=11);e.activeDate.setMonth(c)},e.refreshView()}}}]).directive("yearpicker",["dateFilter",function(a){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/year.html",require:"^datepicker",link:function(a,b,c,d){function e(a){return parseInt((a-1)/f,10)*f+1}var f=d.yearRange;d.step={years:f},d.element=b,d._refreshView=function(){for(var b,c=new Array(f),g=0,h=e(d.activeDate.getFullYear());f>g;g++)b=new Date(h+g,0,1),d.fixTimeZone(b),c[g]=angular.extend(d.createDateObject(b,d.formatYear),{uid:a.uniqueId+"-"+g});a.title=[c[0].label,c[f-1].label].join(" - "),a.rows=d.split(c,5)},d.compare=function(a,b){return a.getFullYear()-b.getFullYear()},d.handleKeyDown=function(a,b){var c=d.activeDate.getFullYear();"left"===a?c-=1:"up"===a?c-=5:"right"===a?c+=1:"down"===a?c+=5:"pageup"===a||"pagedown"===a?c+=("pageup"===a?-1:1)*d.step.years:"home"===a?c=e(d.activeDate.getFullYear()):"end"===a&&(c=e(d.activeDate.getFullYear())+f-1),d.activeDate.setFullYear(c)},d.refreshView()}}}]).constant("datepickerPopupConfig",{datepickerPopup:"yyyy-MM-dd",datepickerPopupTemplateUrl:"template/datepicker/popup.html",datepickerTemplateUrl:"template/datepicker/datepicker.html",html5Types:{date:"yyyy-MM-dd","datetime-local":"yyyy-MM-ddTHH:mm:ss.sss",month:"yyyy-MM"},currentText:"Today",clearText:"Clear",closeText:"Done",closeOnDateSelection:!0,appendToBody:!1,showButtonBar:!0,onOpenFocus:!0}).directive("datepickerPopup",["$compile","$parse","$document","$rootScope","$position","dateFilter","dateParser","datepickerPopupConfig","$timeout",function(a,b,c,d,e,f,g,h,i){return{restrict:"EA",require:"ngModel",scope:{isOpen:"=?",currentText:"@",clearText:"@",closeText:"@",dateDisabled:"&",customClass:"&"},link:function(j,k,l,m){function n(a){return a.replace(/([A-Z])/g,function(a){return"-"+a.toLowerCase()})}function o(a){if(angular.isNumber(a)&&(a=new Date(a)),a){if(angular.isDate(a)&&!isNaN(a))return a;if(angular.isString(a)){var b=g.parse(a,q,j.date);return isNaN(b)?void 0:b}return void 0}return null}function p(a,b){var c=a||b;if(!l.ngRequired&&!c)return!0;if(angular.isNumber(c)&&(c=new Date(c)),c){if(angular.isDate(c)&&!isNaN(c))return!0;if(angular.isString(c)){var d=g.parse(c,q);return!isNaN(d)}return!1}return!0}var q,r=angular.isDefined(l.closeOnDateSelection)?j.$parent.$eval(l.closeOnDateSelection):h.closeOnDateSelection,s=angular.isDefined(l.datepickerAppendToBody)?j.$parent.$eval(l.datepickerAppendToBody):h.appendToBody,t=angular.isDefined(l.onOpenFocus)?j.$parent.$eval(l.onOpenFocus):h.onOpenFocus,u=angular.isDefined(l.datepickerPopupTemplateUrl)?l.datepickerPopupTemplateUrl:h.datepickerPopupTemplateUrl,v=angular.isDefined(l.datepickerTemplateUrl)?l.datepickerTemplateUrl:h.datepickerTemplateUrl,w={};j.showButtonBar=angular.isDefined(l.showButtonBar)?j.$parent.$eval(l.showButtonBar):h.showButtonBar,j.getText=function(a){return j[a+"Text"]||h[a+"Text"]},j.isDisabled=function(a){return"today"===a&&(a=new Date),j.watchData.minDate&&j.compare(a,w.minDate)<0||j.watchData.maxDate&&j.compare(a,w.maxDate)>0},j.compare=function(a,b){return new Date(a.getFullYear(),a.getMonth(),a.getDate())-new Date(b.getFullYear(),b.getMonth(),b.getDate())};var x=!1;if(h.html5Types[l.type]?(q=h.html5Types[l.type],x=!0):(q=l.datepickerPopup||h.datepickerPopup,l.$observe("datepickerPopup",function(a,b){var c=a||h.datepickerPopup;if(c!==q&&(q=c,m.$modelValue=null,!q))throw new Error("datepickerPopup must have a date format specified.")})),!q)throw new Error("datepickerPopup must have a date format specified.");if(x&&l.datepickerPopup)throw new Error("HTML5 date input types do not support custom formats.");var y=angular.element("<div datepicker-popup-wrap><div datepicker></div></div>");y.attr({"ng-model":"date","ng-change":"dateSelection(date)","template-url":u});var z=angular.element(y.children()[0]);if(z.attr("template-url",v),x&&"month"===l.type&&(z.attr("datepicker-mode",'"month"'),z.attr("min-mode","month")),l.datepickerOptions){var A=j.$parent.$eval(l.datepickerOptions);A&&A.initDate&&(j.initDate=A.initDate,z.attr("init-date","initDate"),delete A.initDate),angular.forEach(A,function(a,b){z.attr(n(b),a)})}j.watchData={},angular.forEach(["minMode","maxMode","minDate","maxDate","datepickerMode","initDate","shortcutPropagation"],function(a){if(l[a]){var c=b(l[a]);if(j.$parent.$watch(c,function(b){j.watchData[a]=b,("minDate"===a||"maxDate"===a)&&(w[a]=new Date(b))}),z.attr(n(a),"watchData."+a),"datepickerMode"===a){var d=c.assign;j.$watch("watchData."+a,function(a,b){angular.isFunction(d)&&a!==b&&d(j.$parent,a)})}}}),l.dateDisabled&&z.attr("date-disabled","dateDisabled({ date: date, mode: mode })"),l.showWeeks&&z.attr("show-weeks",l.showWeeks),l.customClass&&z.attr("custom-class","customClass({ date: date, mode: mode })"),x?m.$formatters.push(function(a){return j.date=a,a}):(m.$$parserName="date",m.$validators.date=p,m.$parsers.unshift(o),m.$formatters.push(function(a){return j.date=a,m.$isEmpty(a)?a:f(a,q)})),j.dateSelection=function(a){angular.isDefined(a)&&(j.date=a);var b=j.date?f(j.date,q):null;k.val(b),m.$setViewValue(b),r&&(j.isOpen=!1,k[0].focus())},m.$viewChangeListeners.push(function(){j.date=g.parse(m.$viewValue,q,j.date)});var B=function(a){!j.isOpen||k[0].contains(a.target)||y[0].contains(a.target)||j.$apply(function(){j.isOpen=!1})},C=function(a){27===a.which&&j.isOpen?(a.preventDefault(),a.stopPropagation(),j.$apply(function(){j.isOpen=!1}),k[0].focus()):40!==a.which||j.isOpen||(a.preventDefault(),a.stopPropagation(),j.$apply(function(){j.isOpen=!0}))};k.bind("keydown",C),j.keydown=function(a){27===a.which&&(j.isOpen=!1,k[0].focus())},j.$watch("isOpen",function(a){a?(j.position=s?e.offset(k):e.position(k),j.position.top=j.position.top+k.prop("offsetHeight"),i(function(){t&&j.$broadcast("datepicker.focus"),c.bind("click",B)},0,!1)):c.unbind("click",B)}),j.select=function(a){if("today"===a){var b=new Date;angular.isDate(j.date)?(a=new Date(j.date),a.setFullYear(b.getFullYear(),b.getMonth(),b.getDate())):a=new Date(b.setHours(0,0,0,0))}j.dateSelection(a)},j.close=function(){j.isOpen=!1,k[0].focus()};var D=a(y)(j);y.remove(),s?c.find("body").append(D):k.after(D),j.$on("$destroy",function(){j.isOpen===!0&&(d.$$phase||j.$apply(function(){j.isOpen=!1})),D.remove(),k.unbind("keydown",C),c.unbind("click",B)})}}}]).directive("datepickerPopupWrap",function(){return{restrict:"EA",replace:!0,transclude:!0,templateUrl:function(a,b){return b.templateUrl||"template/datepicker/popup.html"}}}),angular.module("ui.bootstrap.dropdown",["ui.bootstrap.position"]).constant("dropdownConfig",{openClass:"open"}).service("dropdownService",["$document","$rootScope",function(a,b){var c=null;this.open=function(b){c||(a.bind("click",d),a.bind("keydown",e)),c&&c!==b&&(c.isOpen=!1),c=b},this.close=function(b){c===b&&(c=null,a.unbind("click",d),a.unbind("keydown",e))};var d=function(a){if(c&&(!a||"disabled"!==c.getAutoClose())){var d=c.getToggleElement();if(!(a&&d&&d[0].contains(a.target))){var e=c.getDropdownElement();a&&"outsideClick"===c.getAutoClose()&&e&&e[0].contains(a.target)||(c.isOpen=!1,b.$$phase||c.$apply())}}},e=function(a){27===a.which?(c.focusToggleElement(),d()):c.isKeynavEnabled()&&/(38|40)/.test(a.which)&&c.isOpen&&(a.preventDefault(),a.stopPropagation(),c.focusDropdownEntry(a.which))}}]).controller("DropdownController",["$scope","$attrs","$parse","dropdownConfig","dropdownService","$animate","$position","$document","$compile","$templateRequest",function(a,b,c,d,e,f,g,h,i,j){var k,l,m=this,n=a.$new(),o=d.openClass,p=angular.noop,q=b.onToggle?c(b.onToggle):angular.noop,r=!1,s=!1,t=h.find("body");this.init=function(d){m.$element=d,b.isOpen&&(l=c(b.isOpen),p=l.assign,a.$watch(l,function(a){n.isOpen=!!a})),r=angular.isDefined(b.dropdownAppendToBody),s=angular.isDefined(b.keyboardNav),r&&m.dropdownMenu&&(t.append(m.dropdownMenu),t.addClass("dropdown"),d.on("$destroy",function(){m.dropdownMenu.remove()}))},this.toggle=function(a){return n.isOpen=arguments.length?!!a:!n.isOpen},this.isOpen=function(){return n.isOpen},n.getToggleElement=function(){return m.toggleElement},n.getAutoClose=function(){return b.autoClose||"always"},n.getElement=function(){return m.$element},n.isKeynavEnabled=function(){return s},n.focusDropdownEntry=function(a){var b=m.dropdownMenu?angular.element(m.dropdownMenu).find("a"):angular.element(m.$element).find("ul").eq(0).find("a");switch(a){case 40:angular.isNumber(m.selectedOption)?m.selectedOption=m.selectedOption===b.length-1?m.selectedOption:m.selectedOption+1:m.selectedOption=0;break;case 38:angular.isNumber(m.selectedOption)?m.selectedOption=0===m.selectedOption?0:m.selectedOption-1:m.selectedOption=b.length-1}b[m.selectedOption].focus()},n.getDropdownElement=function(){return m.dropdownMenu},n.focusToggleElement=function(){m.toggleElement&&m.toggleElement[0].focus()},n.$watch("isOpen",function(b,c){if(r&&m.dropdownMenu){var d=g.positionElements(m.$element,m.dropdownMenu,"bottom-left",!0),h={top:d.top+"px",display:b?"block":"none"},l=m.dropdownMenu.hasClass("dropdown-menu-right");l?(h.left="auto",h.right=window.innerWidth-(d.left+m.$element.prop("offsetWidth"))+"px"):(h.left=d.left+"px",h.right="auto"),m.dropdownMenu.css(h)}var s=r?t:m.$element;if(f[b?"addClass":"removeClass"](s,o).then(function(){angular.isDefined(b)&&b!==c&&q(a,{open:!!b})}),b)m.dropdownMenuTemplateUrl&&j(m.dropdownMenuTemplateUrl).then(function(a){k=n.$new(),i(a.trim())(k,function(a){var b=a;m.dropdownMenu.replaceWith(b),m.dropdownMenu=b})}),n.focusToggleElement(),e.open(n);else{if(m.dropdownMenuTemplateUrl){k&&k.$destroy();var u=angular.element('<ul class="dropdown-menu"></ul>');m.dropdownMenu.replaceWith(u),m.dropdownMenu=u}e.close(n),m.selectedOption=null}angular.isFunction(p)&&p(a,b)}),a.$on("$locationChangeSuccess",function(){"disabled"!==n.getAutoClose()&&(n.isOpen=!1)});var u=a.$on("$destroy",function(){
n.$destroy()});n.$on("$destroy",u)}]).directive("dropdown",function(){return{controller:"DropdownController",link:function(a,b,c,d){d.init(b),b.addClass("dropdown")}}}).directive("dropdownMenu",function(){return{restrict:"AC",require:"?^dropdown",link:function(a,b,c,d){if(d){var e=c.templateUrl;e&&(d.dropdownMenuTemplateUrl=e),d.dropdownMenu||(d.dropdownMenu=b)}}}}).directive("keyboardNav",function(){return{restrict:"A",require:"?^dropdown",link:function(a,b,c,d){b.bind("keydown",function(a){if(-1!==[38,40].indexOf(a.which)){a.preventDefault(),a.stopPropagation();var b=d.dropdownMenu.find("a");switch(a.which){case 40:angular.isNumber(d.selectedOption)?d.selectedOption=d.selectedOption===b.length-1?d.selectedOption:d.selectedOption+1:d.selectedOption=0;break;case 38:angular.isNumber(d.selectedOption)?d.selectedOption=0===d.selectedOption?0:d.selectedOption-1:d.selectedOption=b.length-1}b[d.selectedOption].focus()}})}}}).directive("dropdownToggle",function(){return{require:"?^dropdown",link:function(a,b,c,d){if(d){b.addClass("dropdown-toggle"),d.toggleElement=b;var e=function(e){e.preventDefault(),b.hasClass("disabled")||c.disabled||a.$apply(function(){d.toggle()})};b.bind("click",e),b.attr({"aria-haspopup":!0,"aria-expanded":!1}),a.$watch(d.isOpen,function(a){b.attr("aria-expanded",!!a)}),a.$on("$destroy",function(){b.unbind("click",e)})}}}}),angular.module("ui.bootstrap.modal",[]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c<a.length;c++)if(b==a[c].key)return a[c]},keys:function(){for(var b=[],c=0;c<a.length;c++)b.push(a[c].key);return b},top:function(){return a[a.length-1]},remove:function(b){for(var c=-1,d=0;d<a.length;d++)if(b==a[d].key){c=d;break}return a.splice(c,1)[0]},removeTop:function(){return a.splice(a.length-1,1)[0]},length:function(){return a.length}}}}}).factory("$$multiMap",function(){return{createNew:function(){var a={};return{entries:function(){return Object.keys(a).map(function(b){return{key:b,value:a[b]}})},get:function(b){return a[b]},hasKey:function(b){return!!a[b]},keys:function(){return Object.keys(a)},put:function(b,c){a[b]||(a[b]=[]),a[b].push(c)},remove:function(b,c){var d=a[b];if(d){var e=d.indexOf(c);-1!==e&&d.splice(e,1),d.length||delete a[b]}}}}}}).directive("modalBackdrop",["$animate","$injector","$modalStack",function(a,b,c){function d(b,d,f){f.modalInClass&&(e?e(d,{addClass:f.modalInClass}).start():a.addClass(d,f.modalInClass),b.$on(c.NOW_CLOSING_EVENT,function(b,c){var g=c();e?e(d,{removeClass:f.modalInClass}).start().then(g):a.removeClass(d,f.modalInClass).then(g)}))}var e=null;return b.has("$animateCss")&&(e=b.get("$animateCss")),{restrict:"EA",replace:!0,templateUrl:"template/modal/backdrop.html",compile:function(a,b){return a.addClass(b.backdropClass),d}}}]).directive("modalWindow",["$modalStack","$q","$animate","$injector",function(a,b,c,d){var e=null;return d.has("$animateCss")&&(e=d.get("$animateCss")),{restrict:"EA",scope:{index:"@"},replace:!0,transclude:!0,templateUrl:function(a,b){return b.templateUrl||"template/modal/window.html"},link:function(d,f,g){f.addClass(g.windowClass||""),d.size=g.size,d.close=function(b){var c=a.getTop();c&&c.value.backdrop&&"static"!==c.value.backdrop&&b.target===b.currentTarget&&(b.preventDefault(),b.stopPropagation(),a.dismiss(c.key,"backdrop click"))},d.$isRendered=!0;var h=b.defer();g.$observe("modalRender",function(a){"true"==a&&h.resolve()}),h.promise.then(function(){var h=null;g.modalInClass&&(h=e?e(f,{addClass:g.modalInClass}).start():c.addClass(f,g.modalInClass),d.$on(a.NOW_CLOSING_EVENT,function(a,b){var d=b();e?e(f,{removeClass:g.modalInClass}).start().then(d):c.removeClass(f,g.modalInClass).then(d)})),b.when(h).then(function(){var a=f[0].querySelectorAll("[autofocus]");a.length?a[0].focus():f[0].focus()});var i=a.getTop();i&&a.modalRendered(i.key)})}}}]).directive("modalAnimationClass",[function(){return{compile:function(a,b){b.modalAnimation&&a.addClass(b.modalAnimationClass)}}}]).directive("modalTransclude",function(){return{link:function(a,b,c,d,e){e(a.$parent,function(a){b.empty(),b.append(a)})}}}).factory("$modalStack",["$animate","$timeout","$document","$compile","$rootScope","$q","$injector","$$multiMap","$$stackedMap",function(a,b,c,d,e,f,g,h,i){function j(){for(var a=-1,b=t.keys(),c=0;c<b.length;c++)t.get(b[c]).value.backdrop&&(a=c);return a}function k(a,b){var d=c.find("body").eq(0),e=t.get(a).value;t.remove(a),m(e.modalDomEl,e.modalScope,function(){var b=e.openedClass||s;u.remove(b,a),d.toggleClass(b,u.hasKey(b))}),l(),b&&b.focus?b.focus():d.focus()}function l(){if(p&&-1==j()){var a=q;m(p,q,function(){a=null}),p=void 0,q=void 0}}function m(b,c,d){function e(){e.done||(e.done=!0,o?o(b,{event:"leave"}).start().then(function(){b.remove()}):a.leave(b),c.$destroy(),d&&d())}var g,h=null,i=function(){return g||(g=f.defer(),h=g.promise),function(){g.resolve()}};return c.$broadcast(v.NOW_CLOSING_EVENT,i),f.when(h).then(e)}function n(a,b,c){return!a.value.modalScope.$broadcast("modal.closing",b,c).defaultPrevented}var o=null;g.has("$animateCss")&&(o=g.get("$animateCss"));var p,q,r,s="modal-open",t=i.createNew(),u=h.createNew(),v={NOW_CLOSING_EVENT:"modal.stack.now-closing"},w=0,x="a[href], area[href], input:not([disabled]), button:not([disabled]),select:not([disabled]), textarea:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable=true]";return e.$watch(j,function(a){q&&(q.index=a)}),c.bind("keydown",function(a){if(a.isDefaultPrevented())return a;var b=t.top();if(b&&b.value.keyboard)switch(a.which){case 27:a.preventDefault(),e.$apply(function(){v.dismiss(b.key,"escape key press")});break;case 9:v.loadFocusElementList(b);var c=!1;a.shiftKey?v.isFocusInFirstItem(a)&&(c=v.focusLastFocusableElement()):v.isFocusInLastItem(a)&&(c=v.focusFirstFocusableElement()),c&&(a.preventDefault(),a.stopPropagation())}}),v.open=function(a,b){var f=c[0].activeElement,g=b.openedClass||s;t.add(a,{deferred:b.deferred,renderDeferred:b.renderDeferred,modalScope:b.scope,backdrop:b.backdrop,keyboard:b.keyboard,openedClass:b.openedClass}),u.put(g,a);var h=c.find("body").eq(0),i=j();if(i>=0&&!p){q=e.$new(!0),q.index=i;var k=angular.element('<div modal-backdrop="modal-backdrop"></div>');k.attr("backdrop-class",b.backdropClass),b.animation&&k.attr("modal-animation","true"),p=d(k)(q),h.append(p)}var l=angular.element('<div modal-window="modal-window"></div>');l.attr({"template-url":b.windowTemplateUrl,"window-class":b.windowClass,size:b.size,index:t.length()-1,animate:"animate"}).html(b.content),b.animation&&l.attr("modal-animation","true");var m=d(l)(b.scope);t.top().value.modalDomEl=m,t.top().value.modalOpener=f,h.append(m),h.addClass(g),v.clearFocusListCache()},v.close=function(a,b){var c=t.get(a);return c&&n(c,b,!0)?(c.value.modalScope.$$uibDestructionScheduled=!0,c.value.deferred.resolve(b),k(a,c.value.modalOpener),!0):!c},v.dismiss=function(a,b){var c=t.get(a);return c&&n(c,b,!1)?(c.value.modalScope.$$uibDestructionScheduled=!0,c.value.deferred.reject(b),k(a,c.value.modalOpener),!0):!c},v.dismissAll=function(a){for(var b=this.getTop();b&&this.dismiss(b.key,a);)b=this.getTop()},v.getTop=function(){return t.top()},v.modalRendered=function(a){var b=t.get(a);b&&b.value.renderDeferred.resolve()},v.focusFirstFocusableElement=function(){return r.length>0?(r[0].focus(),!0):!1},v.focusLastFocusableElement=function(){return r.length>0?(r[r.length-1].focus(),!0):!1},v.isFocusInFirstItem=function(a){return r.length>0?(a.target||a.srcElement)==r[0]:!1},v.isFocusInLastItem=function(a){return r.length>0?(a.target||a.srcElement)==r[r.length-1]:!1},v.clearFocusListCache=function(){r=[],w=0},v.loadFocusElementList=function(a){if((void 0===r||!r.length0)&&a){var b=a.value.modalDomEl;b&&b.length&&(r=b[0].querySelectorAll(x))}},v}]).provider("$modal",function(){var a={options:{animation:!0,backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$templateRequest","$controller","$modalStack",function(b,c,d,e,f,g){function h(a){return a.template?d.when(a.template):e(angular.isFunction(a.templateUrl)?a.templateUrl():a.templateUrl)}function i(a){var c=[];return angular.forEach(a,function(a){angular.isFunction(a)||angular.isArray(a)?c.push(d.when(b.invoke(a))):angular.isString(a)?c.push(d.when(b.get(a))):c.push(d.when(a))}),c}var j={},k=null;return j.getPromiseChain=function(){return k},j.open=function(b){var e=d.defer(),j=d.defer(),l=d.defer(),m={result:e.promise,opened:j.promise,rendered:l.promise,close:function(a){return g.close(m,a)},dismiss:function(a){return g.dismiss(m,a)}};if(b=angular.extend({},a.options,b),b.resolve=b.resolve||{},!b.template&&!b.templateUrl)throw new Error("One of template or templateUrl options is required.");var n,o=d.all([h(b)].concat(i(b.resolve)));return n=k=d.all([k]).then(function(){return o},function(){return o}).then(function(a){var d=(b.scope||c).$new();d.$close=m.close,d.$dismiss=m.dismiss,d.$on("$destroy",function(){d.$$uibDestructionScheduled||d.$dismiss("$uibUnscheduledDestruction")});var h,i={},k=1;b.controller&&(i.$scope=d,i.$modalInstance=m,angular.forEach(b.resolve,function(b,c){i[c]=a[k++]}),h=f(b.controller,i),b.controllerAs&&(b.bindToController&&angular.extend(h,d),d[b.controllerAs]=h)),g.open(m,{scope:d,deferred:e,renderDeferred:l,content:a[0],animation:b.animation,backdrop:b.backdrop,keyboard:b.keyboard,backdropClass:b.backdropClass,windowClass:b.windowClass,windowTemplateUrl:b.windowTemplateUrl,size:b.size,openedClass:b.openedClass}),j.resolve(!0)},function(a){j.reject(a),e.reject(a)})["finally"](function(){k===n&&(k=null)}),m},j}]};return a}),angular.module("ui.bootstrap.pagination",[]).controller("PaginationController",["$scope","$attrs","$parse",function(a,b,c){var d=this,e={$setViewValue:angular.noop},f=b.numPages?c(b.numPages).assign:angular.noop;this.init=function(g,h){e=g,this.config=h,e.$render=function(){d.render()},b.itemsPerPage?a.$parent.$watch(c(b.itemsPerPage),function(b){d.itemsPerPage=parseInt(b,10),a.totalPages=d.calculateTotalPages()}):this.itemsPerPage=h.itemsPerPage,a.$watch("totalItems",function(){a.totalPages=d.calculateTotalPages()}),a.$watch("totalPages",function(b){f(a.$parent,b),a.page>b?a.selectPage(b):e.$render()})},this.calculateTotalPages=function(){var b=this.itemsPerPage<1?1:Math.ceil(a.totalItems/this.itemsPerPage);return Math.max(b||0,1)},this.render=function(){a.page=parseInt(e.$viewValue,10)||1},a.selectPage=function(b,c){c&&c.preventDefault();var d=!a.ngDisabled||!c;d&&a.page!==b&&b>0&&b<=a.totalPages&&(c&&c.target&&c.target.blur(),e.$setViewValue(b),e.$render())},a.getText=function(b){return a[b+"Text"]||d.config[b+"Text"]},a.noPrevious=function(){return 1===a.page},a.noNext=function(){return a.page===a.totalPages}}]).constant("paginationConfig",{itemsPerPage:10,boundaryLinks:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0}).directive("pagination",["$parse","paginationConfig",function(a,b){return{restrict:"EA",scope:{totalItems:"=",firstText:"@",previousText:"@",nextText:"@",lastText:"@",ngDisabled:"="},require:["pagination","?ngModel"],controller:"PaginationController",controllerAs:"pagination",templateUrl:function(a,b){return b.templateUrl||"template/pagination/pagination.html"},replace:!0,link:function(c,d,e,f){function g(a,b,c){return{number:a,text:b,active:c}}function h(a,b){var c=[],d=1,e=b,f=angular.isDefined(k)&&b>k;f&&(l?(d=Math.max(a-Math.floor(k/2),1),e=d+k-1,e>b&&(e=b,d=e-k+1)):(d=(Math.ceil(a/k)-1)*k+1,e=Math.min(d+k-1,b)));for(var h=d;e>=h;h++){var i=g(h,h,h===a);c.push(i)}if(f&&!l){if(d>1){var j=g(d-1,"...",!1);c.unshift(j)}if(b>e){var m=g(e+1,"...",!1);c.push(m)}}return c}var i=f[0],j=f[1];if(j){var k=angular.isDefined(e.maxSize)?c.$parent.$eval(e.maxSize):b.maxSize,l=angular.isDefined(e.rotate)?c.$parent.$eval(e.rotate):b.rotate;c.boundaryLinks=angular.isDefined(e.boundaryLinks)?c.$parent.$eval(e.boundaryLinks):b.boundaryLinks,c.directionLinks=angular.isDefined(e.directionLinks)?c.$parent.$eval(e.directionLinks):b.directionLinks,i.init(j,b),e.maxSize&&c.$parent.$watch(a(e.maxSize),function(a){k=parseInt(a,10),i.render()});var m=i.render;i.render=function(){m(),c.page>0&&c.page<=c.totalPages&&(c.pages=h(c.page,c.totalPages))}}}}}]).constant("pagerConfig",{itemsPerPage:10,previousText:"« Previous",nextText:"Next »",align:!0}).directive("pager",["pagerConfig",function(a){return{restrict:"EA",scope:{totalItems:"=",previousText:"@",nextText:"@",ngDisabled:"="},require:["pager","?ngModel"],controller:"PaginationController",controllerAs:"pagination",templateUrl:function(a,b){return b.templateUrl||"template/pagination/pager.html"},replace:!0,link:function(b,c,d,e){var f=e[0],g=e[1];g&&(b.align=angular.isDefined(d.align)?b.$parent.$eval(d.align):a.align,f.init(g,a))}}}]),angular.module("ui.bootstrap.tooltip",["ui.bootstrap.position","ui.bootstrap.bindHtml"]).provider("$tooltip",function(){function a(a){var b=/[A-Z]/g,c="-";return a.replace(b,function(a,b){return(b?c:"")+a.toLowerCase()})}var b={placement:"top",animation:!0,popupDelay:0,useContentExp:!1},c={mouseenter:"mouseleave",click:"click",focus:"blur",none:""},d={};this.options=function(a){angular.extend(d,a)},this.setTriggers=function(a){angular.extend(c,a)},this.$get=["$window","$compile","$timeout","$document","$position","$interpolate","$rootScope","$parse",function(e,f,g,h,i,j,k,l){return function(e,m,n,o){function p(a){var b=(a||o.trigger||n).split(" "),d=b.map(function(a){return c[a]||a});return{show:b,hide:d}}o=angular.extend({},b,d,o);var q=a(e),r=j.startSymbol(),s=j.endSymbol(),t="<div "+q+'-popup title="'+r+"title"+s+'" '+(o.useContentExp?'content-exp="contentExp()" ':'content="'+r+"content"+s+'" ')+'placement="'+r+"placement"+s+'" popup-class="'+r+"popupClass"+s+'" animation="animation" is-open="isOpen"origin-scope="origScope" ></div>';return{restrict:"EA",compile:function(a,b){var c=f(t);return function(a,b,d,f){function j(){I.isOpen?q():n()}function n(){(!H||a.$eval(d[m+"Enable"]))&&(v(),I.popupDelay?D||(D=g(r,I.popupDelay,!1)):r())}function q(){s(),k.$$phase||k.$digest()}function r(){return D=null,C&&(g.cancel(C),C=null),(o.useContentExp?I.contentExp():I.content)?(t(),I.isOpen=!0,K&&K.assign(I.origScope,I.isOpen),k.$$phase||I.$apply(),A.css({display:"block"}),void L()):angular.noop}function s(){I.isOpen=!1,K&&K.assign(I.origScope,I.isOpen),g.cancel(D),D=null,g.cancel(E),E=null,I.animation?C||(C=g(u,500)):u()}function t(){A&&u(),B=I.$new(),A=c(B,function(a){F?h.find("body").append(a):b.after(a)}),o.useContentExp&&(B.$watch("contentExp()",function(a){!a&&I.isOpen&&s()}),B.$watch(function(){J||(J=!0,B.$$postDigest(function(){J=!1,I.isOpen&&L()}))}))}function u(){C=null,A&&(A.remove(),A=null),B&&(B.$destroy(),B=null)}function v(){w(),x(),y()}function w(){I.popupClass=d[m+"Class"]}function x(){var a=d[m+"Placement"];I.placement=angular.isDefined(a)?a:o.placement}function y(){var a=d[m+"PopupDelay"],b=parseInt(a,10);I.popupDelay=isNaN(b)?o.popupDelay:b}function z(){var a=d[m+"Trigger"];M(),G=p(a),"none"!==G.show&&G.show.forEach(function(a,c){a===G.hide[c]?b[0].addEventListener(a,j):a&&(b[0].addEventListener(a,n),b[0].addEventListener(G.hide[c],q))})}var A,B,C,D,E,F=angular.isDefined(o.appendToBody)?o.appendToBody:!1,G=p(void 0),H=angular.isDefined(d[m+"Enable"]),I=a.$new(!0),J=!1,K=angular.isDefined(d[m+"IsOpen"])?l(d[m+"IsOpen"]):!1,L=function(){A&&(E||(E=g(function(){A.css({top:0,left:0,width:"auto",height:"auto"});var a=i.position(A),c=i.positionElements(b,A,I.placement,F);c.top+="px",c.left+="px",c.width=a.width+"px",c.height=a.height+"px",A.css(c),E=null},0,!1)))};I.origScope=a,I.isOpen=!1,I.contentExp=function(){return a.$eval(d[e])},o.useContentExp||d.$observe(e,function(a){I.content=a,!a&&I.isOpen?s():L()}),d.$observe("disabled",function(a){D&&a&&(g.cancel(D),D=null),a&&I.isOpen&&s()}),d.$observe(m+"Title",function(a){I.title=a,L()}),d.$observe(m+"Placement",function(){I.isOpen&&(x(),L())}),K&&a.$watch(K,function(a){a!==I.isOpen&&j()});var M=function(){G.show.forEach(function(a){b.unbind(a,n)}),G.hide.forEach(function(a){b.unbind(a,q)})};z();var N=a.$eval(d[m+"Animation"]);I.animation=angular.isDefined(N)?!!N:o.animation;var O=a.$eval(d[m+"AppendToBody"]);F=angular.isDefined(O)?O:F,F&&a.$on("$locationChangeSuccess",function(){I.isOpen&&s()}),a.$on("$destroy",function(){g.cancel(C),g.cancel(D),g.cancel(E),M(),u(),I=null})}}}}}]}).directive("tooltipTemplateTransclude",["$animate","$sce","$compile","$templateRequest",function(a,b,c,d){return{link:function(e,f,g){var h,i,j,k=e.$eval(g.tooltipTemplateTranscludeScope),l=0,m=function(){i&&(i.remove(),i=null),h&&(h.$destroy(),h=null),j&&(a.leave(j).then(function(){i=null}),i=j,j=null)};e.$watch(b.parseAsResourceUrl(g.tooltipTemplateTransclude),function(b){var g=++l;b?(d(b,!0).then(function(d){if(g===l){var e=k.$new(),i=d,n=c(i)(e,function(b){m(),a.enter(b,f)});h=e,j=n,h.$emit("$includeContentLoaded",b)}},function(){g===l&&(m(),e.$emit("$includeContentError",b))}),e.$emit("$includeContentRequested",b)):m()}),e.$on("$destroy",m)}}}]).directive("tooltipClasses",function(){return{restrict:"A",link:function(a,b,c){a.placement&&b.addClass(a.placement),a.popupClass&&b.addClass(a.popupClass),a.animation()&&b.addClass(c.tooltipAnimationClass)}}}).directive("tooltipPopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html"}}).directive("tooltip",["$tooltip",function(a){return a("tooltip","tooltip","mouseenter")}]).directive("tooltipTemplatePopup",function(){return{restrict:"EA",replace:!0,scope:{contentExp:"&",placement:"@",popupClass:"@",animation:"&",isOpen:"&",originScope:"&"},templateUrl:"template/tooltip/tooltip-template-popup.html"}}).directive("tooltipTemplate",["$tooltip",function(a){return a("tooltipTemplate","tooltip","mouseenter",{useContentExp:!0})}]).directive("tooltipHtmlPopup",function(){return{restrict:"EA",replace:!0,scope:{contentExp:"&",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-popup.html"}}).directive("tooltipHtml",["$tooltip",function(a){return a("tooltipHtml","tooltip","mouseenter",{useContentExp:!0})}]).directive("tooltipHtmlUnsafePopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-unsafe-popup.html"}}).value("tooltipHtmlUnsafeSuppressDeprecated",!1).directive("tooltipHtmlUnsafe",["$tooltip","tooltipHtmlUnsafeSuppressDeprecated","$log",function(a,b,c){return b||c.warn("tooltip-html-unsafe is now deprecated. Use tooltip-html or tooltip-template instead."),a("tooltipHtmlUnsafe","tooltip","mouseenter")}]),angular.module("ui.bootstrap.popover",["ui.bootstrap.tooltip"]).directive("popoverTemplatePopup",function(){return{restrict:"EA",replace:!0,scope:{title:"@",contentExp:"&",placement:"@",popupClass:"@",animation:"&",isOpen:"&",originScope:"&"},templateUrl:"template/popover/popover-template.html"}}).directive("popoverTemplate",["$tooltip",function(a){return a("popoverTemplate","popover","click",{useContentExp:!0})}]).directive("popoverHtmlPopup",function(){return{restrict:"EA",replace:!0,scope:{contentExp:"&",title:"@",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover-html.html"}}).directive("popoverHtml",["$tooltip",function(a){return a("popoverHtml","popover","click",{useContentExp:!0})}]).directive("popoverPopup",function(){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",popupClass:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover.html"}}).directive("popover",["$tooltip",function(a){return a("popover","popover","click")}]),angular.module("ui.bootstrap.progressbar",[]).constant("progressConfig",{animate:!0,max:100}).value("$progressSuppressWarning",!1).controller("ProgressController",["$scope","$attrs","progressConfig",function(a,b,c){var d=this,e=angular.isDefined(b.animate)?a.$parent.$eval(b.animate):c.animate;this.bars=[],a.max=angular.isDefined(a.max)?a.max:c.max,this.addBar=function(b,c){e||c.css({transition:"none"}),this.bars.push(b),b.max=a.max,b.$watch("value",function(a){b.recalculatePercentage()}),b.recalculatePercentage=function(){b.percent=+(100*b.value/b.max).toFixed(2);var a=d.bars.reduce(function(a,b){return a+b.percent},0);a>100&&(b.percent-=a-100)},b.$on("$destroy",function(){c=null,d.removeBar(b)})},this.removeBar=function(a){this.bars.splice(this.bars.indexOf(a),1)},a.$watch("max",function(b){d.bars.forEach(function(b){b.max=a.max,b.recalculatePercentage()})})}]).directive("uibProgress",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",require:"uibProgress",scope:{max:"=?"},templateUrl:"template/progressbar/progress.html"}}).directive("progress",["$log","$progressSuppressWarning",function(a,b){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",require:"progress",scope:{max:"=?"},templateUrl:"template/progressbar/progress.html",link:function(){b&&a.warn("progress is now deprecated. Use uib-progress instead")}}}]).directive("uibBar",function(){return{restrict:"EA",replace:!0,transclude:!0,require:"^uibProgress",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/bar.html",link:function(a,b,c,d){d.addBar(a,b)}}}).directive("bar",["$log","$progressSuppressWarning",function(a,b){return{restrict:"EA",replace:!0,transclude:!0,require:"^progress",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/bar.html",link:function(c,d,e,f){b&&a.warn("bar is now deprecated. Use uib-bar instead"),f.addBar(c,d)}}}]).directive("progressbar",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",scope:{value:"=",max:"=?",type:"@"},templateUrl:"template/progressbar/progressbar.html",link:function(a,b,c,d){d.addBar(a,angular.element(b.children()[0]))}}}),angular.module("ui.bootstrap.rating",[]).constant("ratingConfig",{max:5,stateOn:null,stateOff:null,titles:["one","two","three","four","five"]}).controller("RatingController",["$scope","$attrs","ratingConfig",function(a,b,c){var d={$setViewValue:angular.noop};this.init=function(e){d=e,d.$render=this.render,d.$formatters.push(function(a){return angular.isNumber(a)&&a<<0!==a&&(a=Math.round(a)),a}),this.stateOn=angular.isDefined(b.stateOn)?a.$parent.$eval(b.stateOn):c.stateOn,this.stateOff=angular.isDefined(b.stateOff)?a.$parent.$eval(b.stateOff):c.stateOff;var f=angular.isDefined(b.titles)?a.$parent.$eval(b.titles):c.titles;this.titles=angular.isArray(f)&&f.length>0?f:c.titles;var g=angular.isDefined(b.ratingStates)?a.$parent.$eval(b.ratingStates):new Array(angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max);a.range=this.buildTemplateObjects(g)},this.buildTemplateObjects=function(a){for(var b=0,c=a.length;c>b;b++)a[b]=angular.extend({index:b},{stateOn:this.stateOn,stateOff:this.stateOff,title:this.getTitle(b)},a[b]);return a},this.getTitle=function(a){return a>=this.titles.length?a+1:this.titles[a]},a.rate=function(b){!a.readonly&&b>=0&&b<=a.range.length&&(d.$setViewValue(d.$viewValue===b?0:b),d.$render())},a.enter=function(b){a.readonly||(a.value=b),a.onHover({value:b})},a.reset=function(){a.value=d.$viewValue,a.onLeave()},a.onKeydown=function(b){/(37|38|39|40)/.test(b.which)&&(b.preventDefault(),b.stopPropagation(),a.rate(a.value+(38===b.which||39===b.which?1:-1)))},this.render=function(){a.value=d.$viewValue}}]).directive("rating",function(){return{restrict:"EA",require:["rating","ngModel"],scope:{readonly:"=?",onHover:"&",onLeave:"&"},controller:"RatingController",templateUrl:"template/rating/rating.html",replace:!0,link:function(a,b,c,d){var e=d[0],f=d[1];e.init(f)}}}),angular.module("ui.bootstrap.tabs",[]).controller("TabsetController",["$scope",function(a){var b=this,c=b.tabs=a.tabs=[];b.select=function(a){angular.forEach(c,function(b){b.active&&b!==a&&(b.active=!1,b.onDeselect(),a.selectCalled=!1)}),a.active=!0,a.selectCalled||(a.onSelect(),a.selectCalled=!0)},b.addTab=function(a){c.push(a),1===c.length&&a.active!==!1?a.active=!0:a.active?b.select(a):a.active=!1},b.removeTab=function(a){var e=c.indexOf(a);if(a.active&&c.length>1&&!d){var f=e==c.length-1?e-1:e+1;b.select(c[f])}c.splice(e,1)};var d;a.$on("$destroy",function(){d=!0})}]).directive("tabset",function(){return{restrict:"EA",transclude:!0,replace:!0,scope:{type:"@"},controller:"TabsetController",templateUrl:"template/tabs/tabset.html",link:function(a,b,c){a.vertical=angular.isDefined(c.vertical)?a.$parent.$eval(c.vertical):!1,a.justified=angular.isDefined(c.justified)?a.$parent.$eval(c.justified):!1}}}).directive("tab",["$parse","$log",function(a,b){return{require:"^tabset",restrict:"EA",replace:!0,templateUrl:"template/tabs/tab.html",transclude:!0,scope:{active:"=?",heading:"@",onSelect:"&select",onDeselect:"&deselect"},controller:function(){},link:function(c,d,e,f,g){c.$watch("active",function(a){a&&f.select(c)}),c.disabled=!1,e.disable&&c.$parent.$watch(a(e.disable),function(a){c.disabled=!!a}),e.disabled&&(b.warn('Use of "disabled" attribute has been deprecated, please use "disable"'),c.$parent.$watch(a(e.disabled),function(a){c.disabled=!!a})),c.select=function(){c.disabled||(c.active=!0)},f.addTab(c),c.$on("$destroy",function(){f.removeTab(c)}),c.$transcludeFn=g}}}]).directive("tabHeadingTransclude",function(){return{restrict:"A",require:"^tab",link:function(a,b,c,d){a.$watch("headingElement",function(a){a&&(b.html(""),b.append(a))})}}}).directive("tabContentTransclude",function(){function a(a){return a.tagName&&(a.hasAttribute("tab-heading")||a.hasAttribute("data-tab-heading")||a.hasAttribute("x-tab-heading")||"tab-heading"===a.tagName.toLowerCase()||"data-tab-heading"===a.tagName.toLowerCase()||"x-tab-heading"===a.tagName.toLowerCase())}return{restrict:"A",require:"^tabset",link:function(b,c,d){var e=b.$eval(d.tabContentTransclude);e.$transcludeFn(e.$parent,function(b){angular.forEach(b,function(b){a(b)?e.headingElement=b:c.append(b)})})}}}),angular.module("ui.bootstrap.timepicker",[]).constant("timepickerConfig",{hourStep:1,minuteStep:1,showMeridian:!0,meridians:null,readonlyInput:!1,mousewheel:!0,arrowkeys:!0,showSpinners:!0}).controller("TimepickerController",["$scope","$attrs","$parse","$log","$locale","timepickerConfig",function(a,b,c,d,e,f){function g(){var b=parseInt(a.hours,10),c=a.showMeridian?b>0&&13>b:b>=0&&24>b;return c?(a.showMeridian&&(12===b&&(b=0),a.meridian===q[1]&&(b+=12)),b):void 0}function h(){var b=parseInt(a.minutes,10);return b>=0&&60>b?b:void 0}function i(a){return angular.isDefined(a)&&a.toString().length<2?"0"+a:a.toString()}function j(a){k(),p.$setViewValue(new Date(o)),l(a)}function k(){p.$setValidity("time",!0),a.invalidHours=!1,a.invalidMinutes=!1}function l(b){var c=o.getHours(),d=o.getMinutes();a.showMeridian&&(c=0===c||12===c?12:c%12),a.hours="h"===b?c:i(c),"m"!==b&&(a.minutes=i(d)),a.meridian=o.getHours()<12?q[0]:q[1]}function m(a,b){var c=new Date(a.getTime()+6e4*b),d=new Date(a);return d.setHours(c.getHours(),c.getMinutes()),d}function n(a){o=m(o,a),j()}var o=new Date,p={$setViewValue:angular.noop},q=angular.isDefined(b.meridians)?a.$parent.$eval(b.meridians):f.meridians||e.DATETIME_FORMATS.AMPMS;this.init=function(c,d){p=c,p.$render=this.render,p.$formatters.unshift(function(a){return a?new Date(a):null});var e=d.eq(0),g=d.eq(1),h=angular.isDefined(b.mousewheel)?a.$parent.$eval(b.mousewheel):f.mousewheel;h&&this.setupMousewheelEvents(e,g);var i=angular.isDefined(b.arrowkeys)?a.$parent.$eval(b.arrowkeys):f.arrowkeys;i&&this.setupArrowkeyEvents(e,g),a.readonlyInput=angular.isDefined(b.readonlyInput)?a.$parent.$eval(b.readonlyInput):f.readonlyInput,this.setupInputEvents(e,g)};var r=f.hourStep;b.hourStep&&a.$parent.$watch(c(b.hourStep),function(a){r=parseInt(a,10)});var s=f.minuteStep;b.minuteStep&&a.$parent.$watch(c(b.minuteStep),function(a){s=parseInt(a,10)});var t;a.$parent.$watch(c(b.min),function(a){var b=new Date(a);t=isNaN(b)?void 0:b});var u;a.$parent.$watch(c(b.max),function(a){var b=new Date(a);u=isNaN(b)?void 0:b}),a.noIncrementHours=function(){var a=m(o,60*r);return a>u||o>a&&t>a},a.noDecrementHours=function(){var a=m(o,60*-r);return t>a||a>o&&a>u},a.noIncrementMinutes=function(){var a=m(o,s);return a>u||o>a&&t>a},a.noDecrementMinutes=function(){var a=m(o,-s);return t>a||a>o&&a>u},a.noToggleMeridian=function(){return o.getHours()<13?m(o,720)>u:m(o,-720)<t},a.showMeridian=f.showMeridian,b.showMeridian&&a.$parent.$watch(c(b.showMeridian),function(b){if(a.showMeridian=!!b,p.$error.time){var c=g(),d=h();angular.isDefined(c)&&angular.isDefined(d)&&(o.setHours(c),j())}else l()}),this.setupMousewheelEvents=function(b,c){var d=function(a){a.originalEvent&&(a=a.originalEvent);var b=a.wheelDelta?a.wheelDelta:-a.deltaY;return a.detail||b>0};b.bind("mousewheel wheel",function(b){a.$apply(d(b)?a.incrementHours():a.decrementHours()),b.preventDefault()}),c.bind("mousewheel wheel",function(b){a.$apply(d(b)?a.incrementMinutes():a.decrementMinutes()),b.preventDefault()})},this.setupArrowkeyEvents=function(b,c){b.bind("keydown",function(b){38===b.which?(b.preventDefault(),a.incrementHours(),a.$apply()):40===b.which&&(b.preventDefault(),a.decrementHours(),a.$apply())}),c.bind("keydown",function(b){38===b.which?(b.preventDefault(),a.incrementMinutes(),a.$apply()):40===b.which&&(b.preventDefault(),a.decrementMinutes(),a.$apply())})},this.setupInputEvents=function(b,c){if(a.readonlyInput)return a.updateHours=angular.noop,void(a.updateMinutes=angular.noop);var d=function(b,c){p.$setViewValue(null),p.$setValidity("time",!1),angular.isDefined(b)&&(a.invalidHours=b),angular.isDefined(c)&&(a.invalidMinutes=c)};a.updateHours=function(){var a=g(),b=h();angular.isDefined(a)&&angular.isDefined(b)?(o.setHours(a),t>o||o>u?d(!0):j("h")):d(!0)},b.bind("blur",function(b){!a.invalidHours&&a.hours<10&&a.$apply(function(){a.hours=i(a.hours)})}),a.updateMinutes=function(){var a=h(),b=g();angular.isDefined(a)&&angular.isDefined(b)?(o.setMinutes(a),t>o||o>u?d(void 0,!0):j("m")):d(void 0,!0)},c.bind("blur",function(b){!a.invalidMinutes&&a.minutes<10&&a.$apply(function(){a.minutes=i(a.minutes)})})},this.render=function(){var b=p.$viewValue;isNaN(b)?(p.$setValidity("time",!1),d.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.')):(b&&(o=b),t>o||o>u?(p.$setValidity("time",!1),a.invalidHours=!0,a.invalidMinutes=!0):k(),l())},a.showSpinners=angular.isDefined(b.showSpinners)?a.$parent.$eval(b.showSpinners):f.showSpinners,a.incrementHours=function(){a.noIncrementHours()||n(60*r)},a.decrementHours=function(){a.noDecrementHours()||n(60*-r)},a.incrementMinutes=function(){a.noIncrementMinutes()||n(s)},a.decrementMinutes=function(){a.noDecrementMinutes()||n(-s)},a.toggleMeridian=function(){a.noToggleMeridian()||n(720*(o.getHours()<12?1:-1))}}]).directive("timepicker",function(){return{restrict:"EA",require:["timepicker","?^ngModel"],controller:"TimepickerController",controllerAs:"timepicker",replace:!0,scope:{},templateUrl:function(a,b){return b.templateUrl||"template/timepicker/timepicker.html"},link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f,b.find("input"))}}}),angular.module("ui.bootstrap.transition",[]).value("$transitionSuppressDeprecated",!1).factory("$transition",["$q","$timeout","$rootScope","$log","$transitionSuppressDeprecated",function(a,b,c,d,e){function f(a){for(var b in a)if(void 0!==h.style[b])return a[b]}e||d.warn("$transition is now deprecated. Use $animate from ngAnimate instead.");var g=function(d,e,f){f=f||{};var h=a.defer(),i=g[f.animation?"animationEndEventName":"transitionEndEventName"],j=function(a){c.$apply(function(){d.unbind(i,j),h.resolve(d)})};return i&&d.bind(i,j),b(function(){angular.isString(e)?d.addClass(e):angular.isFunction(e)?e(d):angular.isObject(e)&&d.css(e),i||h.resolve(d)}),h.promise.cancel=function(){
i&&d.unbind(i,j),h.reject("Transition cancelled")},h.promise},h=document.createElement("trans"),i={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"},j={WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"};return g.transitionEndEventName=f(i),g.animationEndEventName=f(j),g}]),angular.module("ui.bootstrap.typeahead",["ui.bootstrap.position"]).factory("typeaheadParser",["$parse",function(a){var b=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;return{parse:function(c){var d=c.match(b);if(!d)throw new Error('Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_" but got "'+c+'".');return{itemName:d[3],source:a(d[4]),viewMapper:a(d[2]||d[1]),modelMapper:a(d[1])}}}}]).directive("typeahead",["$compile","$parse","$q","$timeout","$document","$window","$rootScope","$position","typeaheadParser",function(a,b,c,d,e,f,g,h,i){var j=[9,13,27,38,40],k=200;return{require:["ngModel","^?ngModelOptions"],link:function(l,m,n,o){function p(){K.moveInProgress||(K.moveInProgress=!0,K.$digest()),S&&d.cancel(S),S=d(function(){K.matches.length&&q(),K.moveInProgress=!1,K.$digest()},k)}function q(){K.position=D?h.offset(m):h.position(m),K.position.top+=m.prop("offsetHeight")}var r=o[0],s=o[1],t=l.$eval(n.typeaheadMinLength);t||0===t||(t=1);var u,v,w=l.$eval(n.typeaheadWaitMs)||0,x=l.$eval(n.typeaheadEditable)!==!1,y=b(n.typeaheadLoading).assign||angular.noop,z=b(n.typeaheadOnSelect),A=angular.isDefined(n.typeaheadSelectOnBlur)?l.$eval(n.typeaheadSelectOnBlur):!1,B=b(n.typeaheadNoResults).assign||angular.noop,C=n.typeaheadInputFormatter?b(n.typeaheadInputFormatter):void 0,D=n.typeaheadAppendToBody?l.$eval(n.typeaheadAppendToBody):!1,E=l.$eval(n.typeaheadFocusFirst)!==!1,F=n.typeaheadSelectOnExact?l.$eval(n.typeaheadSelectOnExact):!1,G=b(n.ngModel),H=b(n.ngModel+"($$$p)"),I=function(a,b){return angular.isFunction(G(l))&&s&&s.$options&&s.$options.getterSetter?H(a,{$$$p:b}):G.assign(a,b)},J=i.parse(n.typeahead),K=l.$new(),L=l.$on("$destroy",function(){K.$destroy()});K.$on("$destroy",L);var M="typeahead-"+K.$id+"-"+Math.floor(1e4*Math.random());m.attr({"aria-autocomplete":"list","aria-expanded":!1,"aria-owns":M});var N=angular.element("<div typeahead-popup></div>");N.attr({id:M,matches:"matches",active:"activeIdx",select:"select(activeIdx)","move-in-progress":"moveInProgress",query:"query",position:"position"}),angular.isDefined(n.typeaheadTemplateUrl)&&N.attr("template-url",n.typeaheadTemplateUrl),angular.isDefined(n.typeaheadPopupTemplateUrl)&&N.attr("popup-template-url",n.typeaheadPopupTemplateUrl);var O=function(){K.matches=[],K.activeIdx=-1,m.attr("aria-expanded",!1)},P=function(a){return M+"-option-"+a};K.$watch("activeIdx",function(a){0>a?m.removeAttr("aria-activedescendant"):m.attr("aria-activedescendant",P(a))});var Q=function(a,b){return K.matches.length>b&&a?a.toUpperCase()===K.matches[b].label.toUpperCase():!1},R=function(a){var b={$viewValue:a};y(l,!0),B(l,!1),c.when(J.source(l,b)).then(function(c){var d=a===r.$viewValue;if(d&&u)if(c&&c.length>0){K.activeIdx=E?0:-1,B(l,!1),K.matches.length=0;for(var e=0;e<c.length;e++)b[J.itemName]=c[e],K.matches.push({id:P(e),label:J.viewMapper(K,b),model:c[e]});K.query=a,q(),m.attr("aria-expanded",!0),F&&1===K.matches.length&&Q(a,0)&&K.select(0)}else O(),B(l,!0);d&&y(l,!1)},function(){O(),y(l,!1),B(l,!0)})};D&&(angular.element(f).bind("resize",p),e.find("body").bind("scroll",p));var S;K.moveInProgress=!1,O(),K.query=void 0;var T,U=function(a){T=d(function(){R(a)},w)},V=function(){T&&d.cancel(T)};r.$parsers.unshift(function(a){return u=!0,0===t||a&&a.length>=t?w>0?(V(),U(a)):R(a):(y(l,!1),V(),O()),x?a:a?void r.$setValidity("editable",!1):(r.$setValidity("editable",!0),null)}),r.$formatters.push(function(a){var b,c,d={};return x||r.$setValidity("editable",!0),C?(d.$model=a,C(l,d)):(d[J.itemName]=a,b=J.viewMapper(l,d),d[J.itemName]=void 0,c=J.viewMapper(l,d),b!==c?b:a)}),K.select=function(a){var b,c,e={};v=!0,e[J.itemName]=c=K.matches[a].model,b=J.modelMapper(l,e),I(l,b),r.$setValidity("editable",!0),r.$setValidity("parse",!0),z(l,{$item:c,$model:b,$label:J.viewMapper(l,e)}),O(),K.$eval(n.typeaheadFocusOnSelect)!==!1&&d(function(){m[0].focus()},0,!1)},m.bind("keydown",function(a){if(0!==K.matches.length&&-1!==j.indexOf(a.which)){if(-1===K.activeIdx&&(9===a.which||13===a.which))return O(),void K.$digest();a.preventDefault(),40===a.which?(K.activeIdx=(K.activeIdx+1)%K.matches.length,K.$digest()):38===a.which?(K.activeIdx=(K.activeIdx>0?K.activeIdx:K.matches.length)-1,K.$digest()):13===a.which||9===a.which?K.$apply(function(){K.select(K.activeIdx)}):27===a.which&&(a.stopPropagation(),O(),K.$digest())}}),m.bind("blur",function(){A&&K.matches.length&&-1!==K.activeIdx&&!v&&(v=!0,K.$apply(function(){K.select(K.activeIdx)})),u=!1,v=!1});var W=function(a){m[0]!==a.target&&3!==a.which&&0!==K.matches.length&&(O(),g.$$phase||K.$digest())};e.bind("click",W),l.$on("$destroy",function(){e.unbind("click",W),D&&X.remove(),N.remove()});var X=a(N)(K);D?e.find("body").append(X):m.after(X)}}}]).directive("typeaheadPopup",function(){return{restrict:"EA",scope:{matches:"=",query:"=",active:"=",position:"&",moveInProgress:"=",select:"&"},replace:!0,templateUrl:function(a,b){return b.popupTemplateUrl||"template/typeahead/typeahead-popup.html"},link:function(a,b,c){a.templateUrl=c.templateUrl,a.isOpen=function(){return a.matches.length>0},a.isActive=function(b){return a.active==b},a.selectActive=function(b){a.active=b},a.selectMatch=function(b){a.select({activeIdx:b})}}}}).directive("typeaheadMatch",["$templateRequest","$compile","$parse",function(a,b,c){return{restrict:"EA",scope:{index:"=",match:"=",query:"="},link:function(d,e,f){var g=c(f.templateUrl)(d.$parent)||"template/typeahead/typeahead-match.html";a(g).then(function(a){b(a.trim())(d,function(a){e.replaceWith(a)})})}}}]).filter("typeaheadHighlight",["$sce","$injector","$log",function(a,b,c){function d(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}function e(a){return/<.*>/g.test(a)}var f;return f=b.has("$sanitize"),function(b,g){return!f&&e(b)&&c.warn("Unsafe use of typeahead please use ngSanitize"),b=g?(""+b).replace(new RegExp(d(g),"gi"),"<strong>$&</strong>"):b,f||(b=a.trustAsHtml(b)),b}}]),angular.module("template/accordion/accordion-group.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion-group.html",'<div class="panel {{panelClass || \'panel-default\'}}">\n  <div class="panel-heading" ng-keypress="toggleOpen($event)">\n    <h4 class="panel-title">\n      <a href tabindex="0" class="accordion-toggle" ng-click="toggleOpen()" accordion-transclude="heading"><span ng-class="{\'text-muted\': isDisabled}">{{heading}}</span></a>\n    </h4>\n  </div>\n  <div class="panel-collapse collapse" collapse="!isOpen">\n	  <div class="panel-body" ng-transclude></div>\n  </div>\n</div>\n')}]),angular.module("template/accordion/accordion.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion.html",'<div class="panel-group" ng-transclude></div>')}]),angular.module("template/alert/alert.html",[]).run(["$templateCache",function(a){a.put("template/alert/alert.html",'<div class="alert" ng-class="[\'alert-\' + (type || \'warning\'), closeable ? \'alert-dismissible\' : null]" role="alert">\n    <button ng-show="closeable" type="button" class="close" ng-click="close($event)">\n        <span aria-hidden="true">&times;</span>\n        <span class="sr-only">Close</span>\n    </button>\n    <div ng-transclude></div>\n</div>\n')}]),angular.module("template/carousel/carousel.html",[]).run(["$templateCache",function(a){a.put("template/carousel/carousel.html",'<div ng-mouseenter="pause()" ng-mouseleave="play()" class="carousel" ng-swipe-right="prev()" ng-swipe-left="next()">\n    <ol class="carousel-indicators" ng-show="slides.length > 1">\n        <li ng-repeat="slide in slides | orderBy:indexOfSlide track by $index" ng-class="{active: isActive(slide)}" ng-click="select(slide)"></li>\n    </ol>\n    <div class="carousel-inner" ng-transclude></div>\n    <a class="left carousel-control" ng-click="prev()" ng-show="slides.length > 1"><span class="glyphicon glyphicon-chevron-left"></span></a>\n    <a class="right carousel-control" ng-click="next()" ng-show="slides.length > 1"><span class="glyphicon glyphicon-chevron-right"></span></a>\n</div>\n')}]),angular.module("template/carousel/slide.html",[]).run(["$templateCache",function(a){a.put("template/carousel/slide.html",'<div ng-class="{\n    \'active\': active\n  }" class="item text-center" ng-transclude></div>\n')}]),angular.module("template/datepicker/datepicker.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/datepicker.html",'<div ng-switch="datepickerMode" role="application" ng-keydown="keydown($event)">\n  <daypicker ng-switch-when="day" tabindex="0"></daypicker>\n  <monthpicker ng-switch-when="month" tabindex="0"></monthpicker>\n  <yearpicker ng-switch-when="year" tabindex="0"></yearpicker>\n</div>')}]),angular.module("template/datepicker/day.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/day.html",'<table role="grid" aria-labelledby="{{::uniqueId}}-title" aria-activedescendant="{{activeDateId}}">\n  <thead>\n    <tr>\n      <th><button type="button" class="btn btn-default btn-sm pull-left" ng-click="move(-1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-left"></i></button></th>\n      <th colspan="{{::5 + showWeeks}}"><button id="{{::uniqueId}}-title" role="heading" aria-live="assertive" aria-atomic="true" type="button" class="btn btn-default btn-sm" ng-click="toggleMode()" ng-disabled="datepickerMode === maxMode" tabindex="-1" style="width:100%;"><strong>{{title}}</strong></button></th>\n      <th><button type="button" class="btn btn-default btn-sm pull-right" ng-click="move(1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-right"></i></button></th>\n    </tr>\n    <tr>\n      <th ng-if="showWeeks" class="text-center"></th>\n      <th ng-repeat="label in ::labels track by $index" class="text-center"><small aria-label="{{::label.full}}">{{::label.abbr}}</small></th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr ng-repeat="row in rows track by $index">\n      <td ng-if="showWeeks" class="text-center h6"><em>{{ weekNumbers[$index] }}</em></td>\n      <td ng-repeat="dt in row track by dt.date" class="text-center" role="gridcell" id="{{::dt.uid}}" ng-class="::dt.customClass">\n        <button type="button" style="min-width:100%;" class="btn btn-default btn-sm" ng-class="{\'btn-info\': dt.selected, active: isActive(dt)}" ng-click="select(dt.date)" ng-disabled="dt.disabled" tabindex="-1"><span ng-class="::{\'text-muted\': dt.secondary, \'text-info\': dt.current}">{{::dt.label}}</span></button>\n      </td>\n    </tr>\n  </tbody>\n</table>\n')}]),angular.module("template/datepicker/month.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/month.html",'<table role="grid" aria-labelledby="{{::uniqueId}}-title" aria-activedescendant="{{activeDateId}}">\n  <thead>\n    <tr>\n      <th><button type="button" class="btn btn-default btn-sm pull-left" ng-click="move(-1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-left"></i></button></th>\n      <th><button id="{{::uniqueId}}-title" role="heading" aria-live="assertive" aria-atomic="true" type="button" class="btn btn-default btn-sm" ng-click="toggleMode()" ng-disabled="datepickerMode === maxMode" tabindex="-1" style="width:100%;"><strong>{{title}}</strong></button></th>\n      <th><button type="button" class="btn btn-default btn-sm pull-right" ng-click="move(1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-right"></i></button></th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr ng-repeat="row in rows track by $index">\n      <td ng-repeat="dt in row track by dt.date" class="text-center" role="gridcell" id="{{::dt.uid}}" ng-class="::dt.customClass">\n        <button type="button" style="min-width:100%;" class="btn btn-default" ng-class="{\'btn-info\': dt.selected, active: isActive(dt)}" ng-click="select(dt.date)" ng-disabled="dt.disabled" tabindex="-1"><span ng-class="::{\'text-info\': dt.current}">{{::dt.label}}</span></button>\n      </td>\n    </tr>\n  </tbody>\n</table>\n')}]),angular.module("template/datepicker/popup.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/popup.html",'<ul class="dropdown-menu" ng-if="isOpen" style="display: block" ng-style="{top: position.top+\'px\', left: position.left+\'px\'}" ng-keydown="keydown($event)" ng-click="$event.stopPropagation()">\n	<li ng-transclude></li>\n	<li ng-if="showButtonBar" style="padding:10px 9px 2px">\n		<span class="btn-group pull-left">\n			<button type="button" class="btn btn-sm btn-info" ng-click="select(\'today\')" ng-disabled="isDisabled(\'today\')">{{ getText(\'current\') }}</button>\n			<button type="button" class="btn btn-sm btn-danger" ng-click="select(null)">{{ getText(\'clear\') }}</button>\n		</span>\n		<button type="button" class="btn btn-sm btn-success pull-right" ng-click="close()">{{ getText(\'close\') }}</button>\n	</li>\n</ul>\n')}]),angular.module("template/datepicker/year.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/year.html",'<table role="grid" aria-labelledby="{{::uniqueId}}-title" aria-activedescendant="{{activeDateId}}">\n  <thead>\n    <tr>\n      <th><button type="button" class="btn btn-default btn-sm pull-left" ng-click="move(-1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-left"></i></button></th>\n      <th colspan="3"><button id="{{::uniqueId}}-title" role="heading" aria-live="assertive" aria-atomic="true" type="button" class="btn btn-default btn-sm" ng-click="toggleMode()" ng-disabled="datepickerMode === maxMode" tabindex="-1" style="width:100%;"><strong>{{title}}</strong></button></th>\n      <th><button type="button" class="btn btn-default btn-sm pull-right" ng-click="move(1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-right"></i></button></th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr ng-repeat="row in rows track by $index">\n      <td ng-repeat="dt in row track by dt.date" class="text-center" role="gridcell" id="{{::dt.uid}}">\n        <button type="button" style="min-width:100%;" class="btn btn-default" ng-class="{\'btn-info\': dt.selected, active: isActive(dt)}" ng-click="select(dt.date)" ng-disabled="dt.disabled" tabindex="-1"><span ng-class="::{\'text-info\': dt.current}">{{::dt.label}}</span></button>\n      </td>\n    </tr>\n  </tbody>\n</table>\n')}]),angular.module("template/modal/backdrop.html",[]).run(["$templateCache",function(a){a.put("template/modal/backdrop.html",'<div class="modal-backdrop"\n     modal-animation-class="fade"\n     modal-in-class="in"\n     ng-style="{\'z-index\': 1040 + (index && 1 || 0) + index*10}"\n></div>\n')}]),angular.module("template/modal/window.html",[]).run(["$templateCache",function(a){a.put("template/modal/window.html",'<div modal-render="{{$isRendered}}" tabindex="-1" role="dialog" class="modal"\n    modal-animation-class="fade"\n    modal-in-class="in"\n	ng-style="{\'z-index\': 1050 + index*10, display: \'block\'}" ng-click="close($event)">\n    <div class="modal-dialog" ng-class="size ? \'modal-\' + size : \'\'"><div class="modal-content" modal-transclude></div></div>\n</div>\n')}]),angular.module("template/pagination/pager.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pager.html",'<ul class="pager">\n  <li ng-class="{disabled: noPrevious()||ngDisabled, previous: align}"><a href ng-click="selectPage(page - 1, $event)">{{::getText(\'previous\')}}</a></li>\n  <li ng-class="{disabled: noNext()||ngDisabled, next: align}"><a href ng-click="selectPage(page + 1, $event)">{{::getText(\'next\')}}</a></li>\n</ul>\n')}]),angular.module("template/pagination/pagination.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pagination.html",'<ul class="pagination">\n  <li ng-if="::boundaryLinks" ng-class="{disabled: noPrevious()||ngDisabled}" class="pagination-first"><a href ng-click="selectPage(1, $event)">{{::getText(\'first\')}}</a></li>\n  <li ng-if="::directionLinks" ng-class="{disabled: noPrevious()||ngDisabled}" class="pagination-prev"><a href ng-click="selectPage(page - 1, $event)">{{::getText(\'previous\')}}</a></li>\n  <li ng-repeat="page in pages track by $index" ng-class="{active: page.active,disabled: ngDisabled&&!page.active}" class="pagination-page"><a href ng-click="selectPage(page.number, $event)">{{page.text}}</a></li>\n  <li ng-if="::directionLinks" ng-class="{disabled: noNext()||ngDisabled}" class="pagination-next"><a href ng-click="selectPage(page + 1, $event)">{{::getText(\'next\')}}</a></li>\n  <li ng-if="::boundaryLinks" ng-class="{disabled: noNext()||ngDisabled}" class="pagination-last"><a href ng-click="selectPage(totalPages, $event)">{{::getText(\'last\')}}</a></li>\n</ul>\n')}]),angular.module("template/tooltip/tooltip-html-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-html-popup.html",'<div class="tooltip"\n  tooltip-animation-class="fade"\n  tooltip-classes\n  ng-class="{ in: isOpen() }">\n  <div class="tooltip-arrow"></div>\n  <div class="tooltip-inner" ng-bind-html="contentExp()"></div>\n</div>\n')}]),angular.module("template/tooltip/tooltip-html-unsafe-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-html-unsafe-popup.html",'<div class="tooltip"\n  tooltip-animation-class="fade"\n  tooltip-classes\n  ng-class="{ in: isOpen() }">\n  <div class="tooltip-arrow"></div>\n  <div class="tooltip-inner" bind-html-unsafe="content"></div>\n</div>\n')}]),angular.module("template/tooltip/tooltip-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-popup.html",'<div class="tooltip"\n  tooltip-animation-class="fade"\n  tooltip-classes\n  ng-class="{ in: isOpen() }">\n  <div class="tooltip-arrow"></div>\n  <div class="tooltip-inner" ng-bind="content"></div>\n</div>\n')}]),angular.module("template/tooltip/tooltip-template-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-template-popup.html",'<div class="tooltip"\n  tooltip-animation-class="fade"\n  tooltip-classes\n  ng-class="{ in: isOpen() }">\n  <div class="tooltip-arrow"></div>\n  <div class="tooltip-inner"\n    tooltip-template-transclude="contentExp()"\n    tooltip-template-transclude-scope="originScope()"></div>\n</div>\n')}]),angular.module("template/popover/popover-html.html",[]).run(["$templateCache",function(a){a.put("template/popover/popover-html.html",'<div class="popover"\n  tooltip-animation-class="fade"\n  tooltip-classes\n  ng-class="{ in: isOpen() }">\n  <div class="arrow"></div>\n\n  <div class="popover-inner">\n      <h3 class="popover-title" ng-bind="title" ng-if="title"></h3>\n      <div class="popover-content" ng-bind-html="contentExp()"></div>\n  </div>\n</div>\n')}]),angular.module("template/popover/popover-template.html",[]).run(["$templateCache",function(a){a.put("template/popover/popover-template.html",'<div class="popover"\n  tooltip-animation-class="fade"\n  tooltip-classes\n  ng-class="{ in: isOpen() }">\n  <div class="arrow"></div>\n\n  <div class="popover-inner">\n      <h3 class="popover-title" ng-bind="title" ng-if="title"></h3>\n      <div class="popover-content"\n        tooltip-template-transclude="contentExp()"\n        tooltip-template-transclude-scope="originScope()"></div>\n  </div>\n</div>\n')}]),angular.module("template/popover/popover.html",[]).run(["$templateCache",function(a){a.put("template/popover/popover.html",'<div class="popover"\n  tooltip-animation-class="fade"\n  tooltip-classes\n  ng-class="{ in: isOpen() }">\n  <div class="arrow"></div>\n\n  <div class="popover-inner">\n      <h3 class="popover-title" ng-bind="title" ng-if="title"></h3>\n      <div class="popover-content" ng-bind="content"></div>\n  </div>\n</div>\n')}]),angular.module("template/progressbar/bar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/bar.html",'<div class="progress-bar" ng-class="type && \'progress-bar-\' + type" role="progressbar" aria-valuenow="{{value}}" aria-valuemin="0" aria-valuemax="{{max}}" ng-style="{width: (percent < 100 ? percent : 100) + \'%\'}" aria-valuetext="{{percent | number:0}}%" style="min-width: 0;" ng-transclude></div>\n')}]),angular.module("template/progressbar/progress.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progress.html",'<div class="progress" ng-transclude></div>')}]),angular.module("template/progressbar/progressbar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progressbar.html",'<div class="progress">\n  <div class="progress-bar" ng-class="type && \'progress-bar-\' + type" role="progressbar" aria-valuenow="{{value}}" aria-valuemin="0" aria-valuemax="{{max}}" ng-style="{width: (percent < 100 ? percent : 100) + \'%\'}" aria-valuetext="{{percent | number:0}}%" style="min-width: 0;" ng-transclude></div>\n</div>\n')}]),angular.module("template/rating/rating.html",[]).run(["$templateCache",function(a){a.put("template/rating/rating.html",'<span ng-mouseleave="reset()" ng-keydown="onKeydown($event)" tabindex="0" role="slider" aria-valuemin="0" aria-valuemax="{{range.length}}" aria-valuenow="{{value}}">\n    <span ng-repeat-start="r in range track by $index" class="sr-only">({{ $index < value ? \'*\' : \' \' }})</span>\n    <i ng-repeat-end ng-mouseenter="enter($index + 1)" ng-click="rate($index + 1)" class="glyphicon" ng-class="$index < value && (r.stateOn || \'glyphicon-star\') || (r.stateOff || \'glyphicon-star-empty\')" ng-attr-title="{{r.title}}" ></i>\n</span>\n')}]),angular.module("template/tabs/tab.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tab.html",'<li ng-class="{active: active, disabled: disabled}">\n  <a href ng-click="select()" tab-heading-transclude>{{heading}}</a>\n</li>\n')}]),angular.module("template/tabs/tabset.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html",'<div>\n  <ul class="nav nav-{{type || \'tabs\'}}" ng-class="{\'nav-stacked\': vertical, \'nav-justified\': justified}" ng-transclude></ul>\n  <div class="tab-content">\n    <div class="tab-pane" \n         ng-repeat="tab in tabs" \n         ng-class="{active: tab.active}"\n         tab-content-transclude="tab">\n    </div>\n  </div>\n</div>\n')}]),angular.module("template/timepicker/timepicker.html",[]).run(["$templateCache",function(a){a.put("template/timepicker/timepicker.html",'<table>\n  <tbody>\n    <tr class="text-center" ng-show="::showSpinners">\n      <td><a ng-click="incrementHours()" ng-class="{disabled: noIncrementHours()}" class="btn btn-link"><span class="glyphicon glyphicon-chevron-up"></span></a></td>\n      <td>&nbsp;</td>\n      <td><a ng-click="incrementMinutes()" ng-class="{disabled: noIncrementMinutes()}" class="btn btn-link"><span class="glyphicon glyphicon-chevron-up"></span></a></td>\n      <td ng-show="showMeridian"></td>\n    </tr>\n    <tr>\n      <td class="form-group" ng-class="{\'has-error\': invalidHours}">\n        <input style="width:50px;" type="text" ng-model="hours" ng-change="updateHours()" class="form-control text-center" ng-readonly="::readonlyInput" maxlength="2">\n      </td>\n      <td>:</td>\n      <td class="form-group" ng-class="{\'has-error\': invalidMinutes}">\n        <input style="width:50px;" type="text" ng-model="minutes" ng-change="updateMinutes()" class="form-control text-center" ng-readonly="::readonlyInput" maxlength="2">\n      </td>\n      <td ng-show="showMeridian"><button type="button" ng-class="{disabled: noToggleMeridian()}" class="btn btn-default text-center" ng-click="toggleMeridian()">{{meridian}}</button></td>\n    </tr>\n    <tr class="text-center" ng-show="::showSpinners">\n      <td><a ng-click="decrementHours()" ng-class="{disabled: noDecrementHours()}" class="btn btn-link"><span class="glyphicon glyphicon-chevron-down"></span></a></td>\n      <td>&nbsp;</td>\n      <td><a ng-click="decrementMinutes()" ng-class="{disabled: noDecrementMinutes()}" class="btn btn-link"><span class="glyphicon glyphicon-chevron-down"></span></a></td>\n      <td ng-show="showMeridian"></td>\n    </tr>\n  </tbody>\n</table>\n')}]),angular.module("template/typeahead/typeahead-match.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-match.html",'<a href tabindex="-1" ng-bind-html="match.label | typeaheadHighlight:query"></a>\n')}]),angular.module("template/typeahead/typeahead-popup.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-popup.html",'<ul class="dropdown-menu" ng-show="isOpen() && !moveInProgress" ng-style="{top: position().top+\'px\', left: position().left+\'px\'}" style="display: block;" role="listbox" aria-hidden="{{!isOpen()}}">\n    <li ng-repeat="match in matches track by $index" ng-class="{active: isActive($index) }" ng-mouseenter="selectActive($index)" ng-click="selectMatch($index)" role="option" id="{{::match.id}}">\n        <div typeahead-match index="$index" match="match" query="query" template-url="templateUrl"></div>\n    </li>\n</ul>\n')}]),!angular.$$csp()&&angular.element(document).find("head").prepend('<style type="text/css">.ng-animate.item:not(.left):not(.right){-webkit-transition:0s ease-in-out left;transition:0s ease-in-out left}</style>');
/*
 Copyright 2011-2013 Abdulla Abdurakhmanov
 Original sources are available at https://code.google.com/p/x2js/

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

function X2JS(config) {
	'use strict';
		
	var VERSION = "1.1.7";
	
	config = config || {};
	initConfigDefaults();
	initRequiredPolyfills();
	
	function initConfigDefaults() {
		if(config.escapeMode === undefined) {
			config.escapeMode = true;
		}
		config.attributePrefix = config.attributePrefix || "_";
		config.arrayAccessForm = config.arrayAccessForm || "none";
		config.emptyNodeForm = config.emptyNodeForm || "text";
		if(config.enableToStringFunc === undefined) {
			config.enableToStringFunc = true; 
		}
		config.arrayAccessFormPaths = config.arrayAccessFormPaths || []; 
		if(config.skipEmptyTextNodesForObj === undefined) {
			config.skipEmptyTextNodesForObj = true;
		}
		if(config.stripWhitespaces === undefined) {
			config.stripWhitespaces = true;
		}
		config.datetimeAccessFormPaths = config.datetimeAccessFormPaths || [];

		if(config.useDoubleQuotes === undefined) {
			config.useDoubleQuotes = false;
		}
	}

	var DOMNodeTypes = {
		ELEMENT_NODE 	   : 1,
		TEXT_NODE    	   : 3,
		CDATA_SECTION_NODE : 4,
		COMMENT_NODE	   : 8,
		DOCUMENT_NODE 	   : 9
	};
	
	function initRequiredPolyfills() {
		function pad(number) {
	      var r = String(number);
	      if ( r.length === 1 ) {
	        r = '0' + r;
	      }
	      return r;
	    }
		// Hello IE8-
		if(typeof String.prototype.trim !== 'function') {			
			String.prototype.trim = function() {
				return this.replace(/^\s+|^\n+|(\s|\n)+$/g, '');
			}
		}
		if(typeof Date.prototype.toISOString !== 'function') {
			// Implementation from http://stackoverflow.com/questions/2573521/how-do-i-output-an-iso-8601-formatted-string-in-javascript
			Date.prototype.toISOString = function() {
		      return this.getUTCFullYear()
		        + '-' + pad( this.getUTCMonth() + 1 )
		        + '-' + pad( this.getUTCDate() )
		        + 'T' + pad( this.getUTCHours() )
		        + ':' + pad( this.getUTCMinutes() )
		        + ':' + pad( this.getUTCSeconds() )
		        + '.' + String( (this.getUTCMilliseconds()/1000).toFixed(3) ).slice( 2, 5 )
		        + 'Z';
		    };
		}
	}
	
	function getNodeLocalName( node ) {
		var nodeLocalName = node.localName;			
		if(nodeLocalName == null) // Yeah, this is IE!! 
			nodeLocalName = node.baseName;
		if(nodeLocalName == null || nodeLocalName=="") // =="" is IE too
			nodeLocalName = node.nodeName;
		return nodeLocalName;
	}
	
	function getNodePrefix(node) {
		return node.prefix;
	}
		
	function escapeXmlChars(str) {
		if(typeof(str) == "string")
			return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;');
		else
			return str;
	}

	function unescapeXmlChars(str) {
		return str.replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"').replace(/&#x27;/g, "'").replace(/&amp;/g, '&');
	}
	
	function toArrayAccessForm(obj, childName, path) {
		switch(config.arrayAccessForm) {
		case "property":
			if(!(obj[childName] instanceof Array))
				obj[childName+"_asArray"] = [obj[childName]];
			else
				obj[childName+"_asArray"] = obj[childName];
			break;		
		/*case "none":
			break;*/
		}
		
		if(!(obj[childName] instanceof Array) && config.arrayAccessFormPaths.length > 0) {
			var idx = 0;
			for(; idx < config.arrayAccessFormPaths.length; idx++) {
				var arrayPath = config.arrayAccessFormPaths[idx];
				if( typeof arrayPath === "string" ) {
					if(arrayPath == path)
						break;
				}
				else
				if( arrayPath instanceof RegExp) {
					if(arrayPath.test(path))
						break;
				}				
				else
				if( typeof arrayPath === "function") {
					if(arrayPath(obj, childName, path))
						break;
				}
			}
			if(idx!=config.arrayAccessFormPaths.length) {
				obj[childName] = [obj[childName]];
			}
		}
	}
	
	function fromXmlDateTime(prop) {
		// Implementation based up on http://stackoverflow.com/questions/8178598/xml-datetime-to-javascript-date-object
		// Improved to support full spec and optional parts
		var bits = prop.split(/[-T:+Z]/g);
		
		var d = new Date(bits[0], bits[1]-1, bits[2]);			
		var secondBits = bits[5].split("\.");
		d.setHours(bits[3], bits[4], secondBits[0]);
		if(secondBits.length>1)
			d.setMilliseconds(secondBits[1]);

		// Get supplied time zone offset in minutes
		if(bits[6] && bits[7]) {
			var offsetMinutes = bits[6] * 60 + Number(bits[7]);
			var sign = /\d\d-\d\d:\d\d$/.test(prop)? '-' : '+';

			// Apply the sign
			offsetMinutes = 0 + (sign == '-'? -1 * offsetMinutes : offsetMinutes);

			// Apply offset and local timezone
			d.setMinutes(d.getMinutes() - offsetMinutes - d.getTimezoneOffset())
		}
		else
			if(prop.indexOf("Z", prop.length - 1) !== -1) {
				d = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds()));					
			}

		// d is now a local time equivalent to the supplied time
		return d;
	}
	
	function checkFromXmlDateTimePaths(value, childName, fullPath) {
		if(config.datetimeAccessFormPaths.length > 0) {
			var path = fullPath.split("\.#")[0];
			var idx = 0;
			for(; idx < config.datetimeAccessFormPaths.length; idx++) {
				var dtPath = config.datetimeAccessFormPaths[idx];
				if( typeof dtPath === "string" ) {
					if(dtPath == path)
						break;
				}
				else
				if( dtPath instanceof RegExp) {
					if(dtPath.test(path))
						break;
				}				
				else
				if( typeof dtPath === "function") {
					if(dtPath(obj, childName, path))
						break;
				}
			}
			if(idx!=config.datetimeAccessFormPaths.length) {
				return fromXmlDateTime(value);
			}
			else
				return value;
		}
		else
			return value;
	}

	function parseDOMChildren( node, path ) {
		if(node.nodeType == DOMNodeTypes.DOCUMENT_NODE) {
			var result = new Object;
			var nodeChildren = node.childNodes;
			// Alternative for firstElementChild which is not supported in some environments
			for(var cidx=0; cidx <nodeChildren.length; cidx++) {
				var child = nodeChildren.item(cidx);
				if(child.nodeType == DOMNodeTypes.ELEMENT_NODE) {
					var childName = getNodeLocalName(child);
					result[childName] = parseDOMChildren(child, childName);
				}
			}
			return result;
		}
		else
		if(node.nodeType == DOMNodeTypes.ELEMENT_NODE) {
			var result = new Object;
			result.__cnt=0;
			
			var nodeChildren = node.childNodes;
			
			// Children nodes
			for(var cidx=0; cidx <nodeChildren.length; cidx++) {
				var child = nodeChildren.item(cidx); // nodeChildren[cidx];
				var childName = getNodeLocalName(child);
				
				if(child.nodeType!= DOMNodeTypes.COMMENT_NODE) {
					result.__cnt++;
					if(result[childName] == null) {
						result[childName] = parseDOMChildren(child, path+"."+childName);
						toArrayAccessForm(result, childName, path+"."+childName);					
					}
					else {
						if(result[childName] != null) {
							if( !(result[childName] instanceof Array)) {
								result[childName] = [result[childName]];
								toArrayAccessForm(result, childName, path+"."+childName);
							}
						}
						(result[childName])[result[childName].length] = parseDOMChildren(child, path+"."+childName);
					}
				}								
			}
			
			// Attributes
			for(var aidx=0; aidx <node.attributes.length; aidx++) {
				var attr = node.attributes.item(aidx); // [aidx];
				result.__cnt++;
				result[config.attributePrefix+attr.name]=attr.value;
			}
			
			// Node namespace prefix
			var nodePrefix = getNodePrefix(node);
			if(nodePrefix!=null && nodePrefix!="") {
				result.__cnt++;
				result.__prefix=nodePrefix;
			}
			
			if(result["#text"]!=null) {				
				result.__text = result["#text"];
				if(result.__text instanceof Array) {
					result.__text = result.__text.join("\n");
				}
				//if(config.escapeMode)
				//	result.__text = unescapeXmlChars(result.__text);
				if(config.stripWhitespaces)
					result.__text = result.__text.trim();
				delete result["#text"];
				if(config.arrayAccessForm=="property")
					delete result["#text_asArray"];
				result.__text = checkFromXmlDateTimePaths(result.__text, childName, path+"."+childName);
			}
			if(result["#cdata-section"]!=null) {
				result.__cdata = result["#cdata-section"];
				delete result["#cdata-section"];
				if(config.arrayAccessForm=="property")
					delete result["#cdata-section_asArray"];
			}
			
			if( result.__cnt == 1 && result.__text!=null  ) {
				result = result.__text;
			}
			else
			if( result.__cnt == 0 && config.emptyNodeForm=="text" ) {
				result = '';
			}
			else
			if ( result.__cnt > 1 && result.__text!=null && config.skipEmptyTextNodesForObj) {
				if( (config.stripWhitespaces && result.__text=="") || (result.__text.trim()=="")) {
					delete result.__text;
				}
			}
			delete result.__cnt;			
			
			if( config.enableToStringFunc && (result.__text!=null || result.__cdata!=null )) {
				result.toString = function() {
					return (this.__text!=null? this.__text:'')+( this.__cdata!=null ? this.__cdata:'');
				};
			}
			
			return result;
		}
		else
		if(node.nodeType == DOMNodeTypes.TEXT_NODE || node.nodeType == DOMNodeTypes.CDATA_SECTION_NODE) {
			return node.nodeValue;
		}	
	}
	
	function startTag(jsonObj, element, attrList, closed) {
		var resultStr = "<"+ ( (jsonObj!=null && jsonObj.__prefix!=null)? (jsonObj.__prefix+":"):"") + element;
		if(attrList!=null) {
			for(var aidx = 0; aidx < attrList.length; aidx++) {
				var attrName = attrList[aidx];
				var attrVal = jsonObj[attrName];
				if(config.escapeMode)
					attrVal=escapeXmlChars(attrVal);
				resultStr+=" "+attrName.substr(config.attributePrefix.length)+"=";
				if(config.useDoubleQuotes)
					resultStr+='"'+attrVal+'"';
				else
					resultStr+="'"+attrVal+"'";
			}
		}
		if(!closed)
			resultStr+=">";
		else
			resultStr+="/>";
		return resultStr;
	}
	
	function endTag(jsonObj,elementName) {
		return "</"+ (jsonObj.__prefix!=null? (jsonObj.__prefix+":"):"")+elementName+">";
	}
	
	function endsWith(str, suffix) {
	    return str.indexOf(suffix, str.length - suffix.length) !== -1;
	}
	
	function jsonXmlSpecialElem ( jsonObj, jsonObjField ) {
		if((config.arrayAccessForm=="property" && endsWith(jsonObjField.toString(),("_asArray"))) 
				|| jsonObjField.toString().indexOf(config.attributePrefix)==0 
				|| jsonObjField.toString().indexOf("__")==0
				|| (jsonObj[jsonObjField] instanceof Function) )
			return true;
		else
			return false;
	}
	
	function jsonXmlElemCount ( jsonObj ) {
		var elementsCnt = 0;
		if(jsonObj instanceof Object ) {
			for( var it in jsonObj  ) {
				if(jsonXmlSpecialElem ( jsonObj, it) )
					continue;			
				elementsCnt++;
			}
		}
		return elementsCnt;
	}
	
	function parseJSONAttributes ( jsonObj ) {
		var attrList = [];
		if(jsonObj instanceof Object ) {
			for( var ait in jsonObj  ) {
				if(ait.toString().indexOf("__")== -1 && ait.toString().indexOf(config.attributePrefix)==0) {
					attrList.push(ait);
				}
			}
		}
		return attrList;
	}
	
	function parseJSONTextAttrs ( jsonTxtObj ) {
		var result ="";
		
		if(jsonTxtObj.__cdata!=null) {										
			result+="<![CDATA["+jsonTxtObj.__cdata+"]]>";					
		}
		
		if(jsonTxtObj.__text!=null) {			
			if(config.escapeMode)
				result+=escapeXmlChars(jsonTxtObj.__text);
			else
				result+=jsonTxtObj.__text;
		}
		return result;
	}
	
	function parseJSONTextObject ( jsonTxtObj ) {
		var result ="";

		if( jsonTxtObj instanceof Object ) {
			result+=parseJSONTextAttrs ( jsonTxtObj );
		}
		else
			if(jsonTxtObj!=null) {
				if(config.escapeMode)
					result+=escapeXmlChars(jsonTxtObj);
				else
					result+=jsonTxtObj;
			}
		
		return result;
	}
	
	function parseJSONArray ( jsonArrRoot, jsonArrObj, attrList ) {
		var result = ""; 
		if(jsonArrRoot.length == 0) {
			result+=startTag(jsonArrRoot, jsonArrObj, attrList, true);
		}
		else {
			for(var arIdx = 0; arIdx < jsonArrRoot.length; arIdx++) {
				result+=startTag(jsonArrRoot[arIdx], jsonArrObj, parseJSONAttributes(jsonArrRoot[arIdx]), false);
				result+=parseJSONObject(jsonArrRoot[arIdx]);
				result+=endTag(jsonArrRoot[arIdx],jsonArrObj);						
			}
		}
		return result;
	}
	
	function parseJSONObject ( jsonObj ) {
		var result = "";	

		var elementsCnt = jsonXmlElemCount ( jsonObj );
		
		if(elementsCnt > 0) {
			for( var it in jsonObj ) {
				
				if(jsonXmlSpecialElem ( jsonObj, it) )
					continue;			
				
				var subObj = jsonObj[it];						
				
				var attrList = parseJSONAttributes( subObj )
				
				if(subObj == null || subObj == undefined) {
					result+=startTag(subObj, it, attrList, true);
				}
				else
				if(subObj instanceof Object) {
					
					if(subObj instanceof Array) {					
						result+=parseJSONArray( subObj, it, attrList );					
					}
					else if(subObj instanceof Date) {
						result+=startTag(subObj, it, attrList, false);
						result+=subObj.toISOString();
						result+=endTag(subObj,it);
					}
					else {
						var subObjElementsCnt = jsonXmlElemCount ( subObj );
						if(subObjElementsCnt > 0 || subObj.__text!=null || subObj.__cdata!=null) {
							result+=startTag(subObj, it, attrList, false);
							result+=parseJSONObject(subObj);
							result+=endTag(subObj,it);
						}
						else {
							result+=startTag(subObj, it, attrList, true);
						}
					}
				}
				else {
					result+=startTag(subObj, it, attrList, false);
					result+=parseJSONTextObject(subObj);
					result+=endTag(subObj,it);
				}
			}
		}
		result+=parseJSONTextObject(jsonObj);
		
		return result;
	}
	
	this.parseXmlString = function(xmlDocStr) {
		var isIEParser = window.ActiveXObject || "ActiveXObject" in window;
		if (xmlDocStr === undefined) {
			return null;
		}
		var xmlDoc;
		if (window.DOMParser) {
			var parser=new window.DOMParser();			
			var parsererrorNS = null;
			// IE9+ now is here
			if(!isIEParser) {
				try {
					parsererrorNS = parser.parseFromString("INVALID", "text/xml").childNodes[0].namespaceURI;
				}
				catch(err) {					
					parsererrorNS = null;
				}
			}
			try {
				xmlDoc = parser.parseFromString( xmlDocStr, "text/xml" );
				if( parsererrorNS!= null && xmlDoc.getElementsByTagNameNS(parsererrorNS, "parsererror").length > 0) {
					//throw new Error('Error parsing XML: '+xmlDocStr);
					xmlDoc = null;
				}
			}
			catch(err) {
				xmlDoc = null;
			}
		}
		else {
			// IE :(
			if(xmlDocStr.indexOf("<?")==0) {
				xmlDocStr = xmlDocStr.substr( xmlDocStr.indexOf("?>") + 2 );
			}
			xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
			xmlDoc.async="false";
			xmlDoc.loadXML(xmlDocStr);
		}
		return xmlDoc;
	};
	
	this.asArray = function(prop) {
		if (prop === undefined || prop == null)
			return [];
		else
		if(prop instanceof Array)
			return prop;
		else
			return [prop];
	};
	
	this.toXmlDateTime = function(dt) {
		if(dt instanceof Date)
			return dt.toISOString();
		else
		if(typeof(dt) === 'number' )
			return new Date(dt).toISOString();
		else	
			return null;
	};
	
	this.asDateTime = function(prop) {
		if(typeof(prop) == "string") {
			return fromXmlDateTime(prop);
		}
		else
			return prop;
	};

	this.xml2json = function (xmlDoc) {
		return parseDOMChildren ( xmlDoc );
	};
	
	this.xml_str2json = function (xmlDocStr) {
		var xmlDoc = this.parseXmlString(xmlDocStr);
		if(xmlDoc!=null)
			return this.xml2json(xmlDoc);
		else
			return null;
	};

	this.json2xml_str = function (jsonObj) {
		return parseJSONObject ( jsonObj );
	};

	this.json2xml = function (jsonObj) {
		var xmlDocStr = this.json2xml_str (jsonObj);
		return this.parseXmlString(xmlDocStr);
	};
	
	this.getVersion = function () {
		return VERSION;
	};
	
}

/**
@fileOverview

@toc

*/

'use strict';

angular.module('cb.x2js', [])
  .factory('x2js', [ function () {
    return new X2JS();
}]);

/*
  angular-md5 - v0.1.7 
  2014-01-20
*/
(function(window, angular, undefined) {
  angular.module("angular-md5", [ "gdi2290.md5" ]);
  angular.module("ngMd5", [ "gdi2290.md5" ]);
  angular.module("gdi2290.md5", [ "gdi2290.gravatar-filter", "gdi2290.md5-service", "gdi2290.md5-filter" ]);
  "use strict";
  angular.module("gdi2290.gravatar-filter", []).filter("gravatar", [ "md5", function(md5) {
    var cache = {};
    return function(text, defaultText) {
      if (!cache[text]) {
        defaultText = defaultText ? md5.createHash(defaultText.toString().toLowerCase()) : "";
        cache[text] = text ? md5.createHash(text.toString().toLowerCase()) : defaultText;
      }
      return cache[text];
    };
  } ]);
  "use strict";
  angular.module("gdi2290.md5-filter", []).filter("md5", [ "md5", function(md5) {
    return function(text) {
      return text ? md5.createHash(text.toString().toLowerCase()) : text;
    };
  } ]);
  "use strict";
  angular.module("gdi2290.md5-service", []).factory("md5", [ function() {
    var md5 = {
      createHash: function(str) {
        var xl;
        var rotateLeft = function(lValue, iShiftBits) {
          return lValue << iShiftBits | lValue >>> 32 - iShiftBits;
        };
        var addUnsigned = function(lX, lY) {
          var lX4, lY4, lX8, lY8, lResult;
          lX8 = lX & 2147483648;
          lY8 = lY & 2147483648;
          lX4 = lX & 1073741824;
          lY4 = lY & 1073741824;
          lResult = (lX & 1073741823) + (lY & 1073741823);
          if (lX4 & lY4) {
            return lResult ^ 2147483648 ^ lX8 ^ lY8;
          }
          if (lX4 | lY4) {
            if (lResult & 1073741824) {
              return lResult ^ 3221225472 ^ lX8 ^ lY8;
            } else {
              return lResult ^ 1073741824 ^ lX8 ^ lY8;
            }
          } else {
            return lResult ^ lX8 ^ lY8;
          }
        };
        var _F = function(x, y, z) {
          return x & y | ~x & z;
        };
        var _G = function(x, y, z) {
          return x & z | y & ~z;
        };
        var _H = function(x, y, z) {
          return x ^ y ^ z;
        };
        var _I = function(x, y, z) {
          return y ^ (x | ~z);
        };
        var _FF = function(a, b, c, d, x, s, ac) {
          a = addUnsigned(a, addUnsigned(addUnsigned(_F(b, c, d), x), ac));
          return addUnsigned(rotateLeft(a, s), b);
        };
        var _GG = function(a, b, c, d, x, s, ac) {
          a = addUnsigned(a, addUnsigned(addUnsigned(_G(b, c, d), x), ac));
          return addUnsigned(rotateLeft(a, s), b);
        };
        var _HH = function(a, b, c, d, x, s, ac) {
          a = addUnsigned(a, addUnsigned(addUnsigned(_H(b, c, d), x), ac));
          return addUnsigned(rotateLeft(a, s), b);
        };
        var _II = function(a, b, c, d, x, s, ac) {
          a = addUnsigned(a, addUnsigned(addUnsigned(_I(b, c, d), x), ac));
          return addUnsigned(rotateLeft(a, s), b);
        };
        var convertToWordArray = function(str) {
          var lWordCount;
          var lMessageLength = str.length;
          var lNumberOfWords_temp1 = lMessageLength + 8;
          var lNumberOfWords_temp2 = (lNumberOfWords_temp1 - lNumberOfWords_temp1 % 64) / 64;
          var lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16;
          var lWordArray = new Array(lNumberOfWords - 1);
          var lBytePosition = 0;
          var lByteCount = 0;
          while (lByteCount < lMessageLength) {
            lWordCount = (lByteCount - lByteCount % 4) / 4;
            lBytePosition = lByteCount % 4 * 8;
            lWordArray[lWordCount] = lWordArray[lWordCount] | str.charCodeAt(lByteCount) << lBytePosition;
            lByteCount++;
          }
          lWordCount = (lByteCount - lByteCount % 4) / 4;
          lBytePosition = lByteCount % 4 * 8;
          lWordArray[lWordCount] = lWordArray[lWordCount] | 128 << lBytePosition;
          lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
          lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
          return lWordArray;
        };
        var wordToHex = function(lValue) {
          var wordToHexValue = "", wordToHexValue_temp = "", lByte, lCount;
          for (lCount = 0; lCount <= 3; lCount++) {
            lByte = lValue >>> lCount * 8 & 255;
            wordToHexValue_temp = "0" + lByte.toString(16);
            wordToHexValue = wordToHexValue + wordToHexValue_temp.substr(wordToHexValue_temp.length - 2, 2);
          }
          return wordToHexValue;
        };
        var x = [], k, AA, BB, CC, DD, a, b, c, d, S11 = 7, S12 = 12, S13 = 17, S14 = 22, S21 = 5, S22 = 9, S23 = 14, S24 = 20, S31 = 4, S32 = 11, S33 = 16, S34 = 23, S41 = 6, S42 = 10, S43 = 15, S44 = 21;
        x = convertToWordArray(str);
        a = 1732584193;
        b = 4023233417;
        c = 2562383102;
        d = 271733878;
        xl = x.length;
        for (k = 0; k < xl; k += 16) {
          AA = a;
          BB = b;
          CC = c;
          DD = d;
          a = _FF(a, b, c, d, x[k + 0], S11, 3614090360);
          d = _FF(d, a, b, c, x[k + 1], S12, 3905402710);
          c = _FF(c, d, a, b, x[k + 2], S13, 606105819);
          b = _FF(b, c, d, a, x[k + 3], S14, 3250441966);
          a = _FF(a, b, c, d, x[k + 4], S11, 4118548399);
          d = _FF(d, a, b, c, x[k + 5], S12, 1200080426);
          c = _FF(c, d, a, b, x[k + 6], S13, 2821735955);
          b = _FF(b, c, d, a, x[k + 7], S14, 4249261313);
          a = _FF(a, b, c, d, x[k + 8], S11, 1770035416);
          d = _FF(d, a, b, c, x[k + 9], S12, 2336552879);
          c = _FF(c, d, a, b, x[k + 10], S13, 4294925233);
          b = _FF(b, c, d, a, x[k + 11], S14, 2304563134);
          a = _FF(a, b, c, d, x[k + 12], S11, 1804603682);
          d = _FF(d, a, b, c, x[k + 13], S12, 4254626195);
          c = _FF(c, d, a, b, x[k + 14], S13, 2792965006);
          b = _FF(b, c, d, a, x[k + 15], S14, 1236535329);
          a = _GG(a, b, c, d, x[k + 1], S21, 4129170786);
          d = _GG(d, a, b, c, x[k + 6], S22, 3225465664);
          c = _GG(c, d, a, b, x[k + 11], S23, 643717713);
          b = _GG(b, c, d, a, x[k + 0], S24, 3921069994);
          a = _GG(a, b, c, d, x[k + 5], S21, 3593408605);
          d = _GG(d, a, b, c, x[k + 10], S22, 38016083);
          c = _GG(c, d, a, b, x[k + 15], S23, 3634488961);
          b = _GG(b, c, d, a, x[k + 4], S24, 3889429448);
          a = _GG(a, b, c, d, x[k + 9], S21, 568446438);
          d = _GG(d, a, b, c, x[k + 14], S22, 3275163606);
          c = _GG(c, d, a, b, x[k + 3], S23, 4107603335);
          b = _GG(b, c, d, a, x[k + 8], S24, 1163531501);
          a = _GG(a, b, c, d, x[k + 13], S21, 2850285829);
          d = _GG(d, a, b, c, x[k + 2], S22, 4243563512);
          c = _GG(c, d, a, b, x[k + 7], S23, 1735328473);
          b = _GG(b, c, d, a, x[k + 12], S24, 2368359562);
          a = _HH(a, b, c, d, x[k + 5], S31, 4294588738);
          d = _HH(d, a, b, c, x[k + 8], S32, 2272392833);
          c = _HH(c, d, a, b, x[k + 11], S33, 1839030562);
          b = _HH(b, c, d, a, x[k + 14], S34, 4259657740);
          a = _HH(a, b, c, d, x[k + 1], S31, 2763975236);
          d = _HH(d, a, b, c, x[k + 4], S32, 1272893353);
          c = _HH(c, d, a, b, x[k + 7], S33, 4139469664);
          b = _HH(b, c, d, a, x[k + 10], S34, 3200236656);
          a = _HH(a, b, c, d, x[k + 13], S31, 681279174);
          d = _HH(d, a, b, c, x[k + 0], S32, 3936430074);
          c = _HH(c, d, a, b, x[k + 3], S33, 3572445317);
          b = _HH(b, c, d, a, x[k + 6], S34, 76029189);
          a = _HH(a, b, c, d, x[k + 9], S31, 3654602809);
          d = _HH(d, a, b, c, x[k + 12], S32, 3873151461);
          c = _HH(c, d, a, b, x[k + 15], S33, 530742520);
          b = _HH(b, c, d, a, x[k + 2], S34, 3299628645);
          a = _II(a, b, c, d, x[k + 0], S41, 4096336452);
          d = _II(d, a, b, c, x[k + 7], S42, 1126891415);
          c = _II(c, d, a, b, x[k + 14], S43, 2878612391);
          b = _II(b, c, d, a, x[k + 5], S44, 4237533241);
          a = _II(a, b, c, d, x[k + 12], S41, 1700485571);
          d = _II(d, a, b, c, x[k + 3], S42, 2399980690);
          c = _II(c, d, a, b, x[k + 10], S43, 4293915773);
          b = _II(b, c, d, a, x[k + 1], S44, 2240044497);
          a = _II(a, b, c, d, x[k + 8], S41, 1873313359);
          d = _II(d, a, b, c, x[k + 15], S42, 4264355552);
          c = _II(c, d, a, b, x[k + 6], S43, 2734768916);
          b = _II(b, c, d, a, x[k + 13], S44, 1309151649);
          a = _II(a, b, c, d, x[k + 4], S41, 4149444226);
          d = _II(d, a, b, c, x[k + 11], S42, 3174756917);
          c = _II(c, d, a, b, x[k + 2], S43, 718787259);
          b = _II(b, c, d, a, x[k + 9], S44, 3951481745);
          a = addUnsigned(a, AA);
          b = addUnsigned(b, BB);
          c = addUnsigned(c, CC);
          d = addUnsigned(d, DD);
        }
        var temp = wordToHex(a) + wordToHex(b) + wordToHex(c) + wordToHex(d);
        return temp.toLowerCase();
      }
    };
    return md5;
  } ]);
})(this, this.angular, void 0);
; (function(angular) {
  'use strict';

  /**
   * Called with an array this acts like map, otherwise it acts like _.mapValues
   * in lodash.
   * @return {Array|Object} The same type as the input argument.
   */
  var mapValues = function(obj, callback) {
    if (angular.isArray(obj))
      return obj.map(callback)

    var ret = {}
    Object.keys(obj).forEach(function(key, val) {
      ret[key] = callback(obj[key], key)
    })
    return ret
  }

  angular.module('ngPromiseExtras', []).config([ '$provide', function($provide) {
    $provide.decorator('$q', [ '$delegate', function($delegate) {
      var $q = $delegate

      $q.allSettled = function(promises) {
        return $q.all(mapValues(promises, function(promiseOrValue) {
          if (! promiseOrValue.then)
            return { state: 'fulfilled', value: promiseOrValue }

          return promiseOrValue.then(function(value) {
            return { state: 'fulfilled', value: value }
          }, function(reason) {
            return { state: 'rejected', reason: reason }
          })
        }))
      }

      $q.map = function(values, callback) {
        return $q.all(mapValues(values, callback))
      }

      $q.mapSettled = function(values, callback) {
        return $q.allSettled(mapValues(values, callback))
      }

      if (! $q.resolve) {
        /**
         * Like Bluebird.resolve (was introduced in angular 1.4).
         */
        $q.resolve = function(value) {
          if (value && value.then)
            return value
          else
            return $q(function(resolve) { resolve(value) })
        }
      }

      return $q
    } ])
  } ])

})(window.angular);

/*! jQuery UI Virtual Keyboard v1.25.29 */
!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof module&&"object"==typeof module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){"use strict";var b=a.keyboard=function(c,d){var e,f=this;f.version="1.25.29",f.$el=a(c),f.el=c,f.$el.data("keyboard",f),f.init=function(){var c,g,h,i=b.css,j=b.events;f.settings=d||{},d&&d.position&&(g=a.extend({},d.position),d.position=null),f.options=e=a.extend(!0,{},b.defaultOptions,d),g&&(e.position=g,d.position=g),f.el.active=!0,f.namespace=".keyboard"+Math.random().toString(16).slice(2),f.extensionNamespace=[],f.shiftActive=f.altActive=f.metaActive=f.sets=f.capsLock=!1,f.rows=["","-shift","-alt","-alt-shift"],f.inPlaceholder=f.$el.attr("placeholder")||"",f.watermark=b.watermark&&""!==f.inPlaceholder,f.repeatTime=1e3/(e.repeatRate||20),e.preventDoubleEventTime=e.preventDoubleEventTime||100,f.isOpen=!1,f.wheel=a.isFunction(a.fn.mousewheel),f.escapeRegex=/[-\/\\^$*+?.()|[\]{}]/g,c=b.keyCodes,f.alwaysAllowed=[c.capsLock,c.pageUp,c.pageDown,c.end,c.home,c.left,c.up,c.right,c.down,c.insert,c["delete"]],f.$keyboard=[],f.enabled=!0,a.isEmptyObject(e.position)||(e.position.orig_at=e.position.at),f.checkCaret=e.lockInput||b.checkCaretSupport(),f.last={start:0,end:0,key:"",val:"",preVal:"",layout:"",virtual:!0,keyset:[!1,!1,!1],wheel_$Keys:null,wheelIndex:0,wheelLayers:[]},f.temp=["",0,0],a.each([j.kbInit,j.kbBeforeVisible,j.kbVisible,j.kbHidden,j.inputCanceled,j.inputAccepted,j.kbBeforeClose],function(b,c){a.isFunction(e[c])&&f.$el.bind(c+f.namespace+"callbacks",e[c])}),e.alwaysOpen&&(e.stayOpen=!0),h=a(document),f.el.ownerDocument!==document&&(h=h.add(f.el.ownerDocument)),h.bind("mousedown keyup touchstart checkkeyboard ".split(" ").join(f.namespace+" "),f.checkClose),f.$el.addClass(i.input+" "+e.css.input).attr({"aria-haspopup":"true",role:"textbox"}),(e.lockInput||f.el.readOnly)&&(e.lockInput=!0,f.$el.addClass(i.locked).attr({readonly:"readonly"})),(f.$el.is(":disabled")||f.$el.attr("readonly")&&!f.$el.hasClass(i.locked))&&f.$el.addClass(i.noKeyboard),e.openOn&&f.bindFocus(),f.watermark||""!==f.$el.val()||""===f.inPlaceholder||""===f.$el.attr("placeholder")||f.$el.addClass(i.placeholder).val(f.inPlaceholder),f.$el.trigger(j.kbInit,[f,f.el]),e.alwaysOpen&&f.reveal()},f.toggle=function(){var a=f.$keyboard.find("."+b.css.keyToggle),c=!f.enabled;f.$preview.prop("readonly",c||f.options.lockInput),f.$keyboard.toggleClass(b.css.keyDisabled,c).find("."+b.css.keyButton).not(a).prop("disabled",c).attr("aria-disabled",c),a.toggleClass(b.css.keyDisabled,c),c&&f.typing_options&&(f.typing_options.text="")},f.setCurrent=function(){var c=b.css,d=a("."+c.isCurrent),e=d.data("keyboard");a.isEmptyObject(e)||e.el===f.el||e.close(e.options.autoAccept?"true":!1),d.removeClass(c.isCurrent),a("."+c.hasFocus).removeClass(c.hasFocus),f.$el.addClass(c.isCurrent),f.$keyboard.addClass(c.hasFocus),f.isCurrent(!0),f.isOpen=!0},f.isCurrent=function(a){var c=b.currentKeyboard||!1;return a?c=b.currentKeyboard=f.el:a===!1&&c===f.el&&(c=b.currentKeyboard=""),c===f.el},f.isVisible=function(){return f.$keyboard&&f.$keyboard.length?f.$keyboard.is(":visible"):!1},f.focusOn=function(){!f&&f.el.active||f.isVisible()||(clearTimeout(f.timer),f.reveal())},f.redraw=function(){f.$keyboard.length&&(f.last.preVal=""+f.last.val,f.last.val=f.$preview&&f.$preview.val()||f.$el.val(),f.$el.val(f.last.val),f.removeKeyboard(),f.shiftActive=f.altActive=f.metaActive=!1),f.isOpen=e.alwaysOpen,f.reveal(!0)},f.reveal=function(c){var d=f.isOpen,g=b.css;return f.opening=!d,a("."+g.keyboard).not("."+g.alwaysOpen).each(function(){var b=a(this).data("keyboard");a.isEmptyObject(b)||b.close(b.options.autoAccept&&b.options.autoAcceptOnEsc?"true":!1)}),f.$el.is(":disabled")||f.$el.attr("readonly")&&!f.$el.hasClass(g.locked)?void f.$el.addClass(g.noKeyboard):(f.$el.removeClass(g.noKeyboard),e.openOn&&f.$el.unbind(a.trim((e.openOn+" ").split(/\s+/).join(f.namespace+" "))),f.$keyboard&&(!f.$keyboard||f.$keyboard.length&&!a.contains(document.body,f.$keyboard[0]))||f.startup(),f.watermark||f.el.value!==f.inPlaceholder||f.$el.removeClass(g.placeholder).val(""),f.originalContent=f.$el.val(),f.$preview.val(f.originalContent),e.acceptValid&&f.checkValid(),e.resetDefault&&(f.shiftActive=f.altActive=f.metaActive=!1),f.showSet(),f.isVisible()||f.$el.trigger(b.events.kbBeforeVisible,[f,f.el]),f.setCurrent(),f.toggle(),f.$keyboard.show(),e.usePreview&&b.msie&&("undefined"==typeof f.width&&(f.$preview.hide(),f.width=Math.ceil(f.$keyboard.width()),f.$preview.show()),f.$preview.width(f.width)),f.position=a.isEmptyObject(e.position)?!1:e.position,a.ui&&a.ui.position&&f.position&&(f.position.of=f.position.of||f.$el.data("keyboardPosition")||f.$el,f.position.collision=f.position.collision||"flipfit flipfit",e.position.at=e.usePreview?e.position.orig_at:e.position.at2,f.$keyboard.position(f.position)),f.checkDecimal(),f.lineHeight=parseInt(f.$preview.css("lineHeight"),10)||parseInt(f.$preview.css("font-size"),10)+4,e.caretToEnd&&f.saveCaret(f.originalContent.length,f.originalContent.length),b.allie&&(0===f.last.end&&f.last.start>0&&(f.last.end=f.last.start),f.last.start<0&&(f.last.start=f.last.end=f.originalContent.length)),d||c?(b.caret(f.$preview,f.last),f):(f.timer2=setTimeout(function(){var a;f.opening=!1,/(number|email)/i.test(f.el.type)||e.caretToEnd||f.saveCaret(a,a,f.$el),e.initialFocus&&b.caret(f.$preview,f.last),f.last.eventTime=(new Date).getTime(),f.$el.trigger(b.events.kbVisible,[f,f.el]),f.timer=setTimeout(function(){f&&f.saveCaret()},200)},10),f))},f.updateLanguage=function(){var c=b.layouts,d=e.language||c[e.layout]&&c[e.layout].lang&&c[e.layout].lang||[e.language||"en"],g=b.language;d=(a.isArray(d)?d[0]:d).split("-")[0],e.display=a.extend(!0,{},g.en.display,g[d]&&g[d].display||{},f.settings.display),e.combos=a.extend(!0,{},g.en.combos,g[d]&&g[d].combos||{},f.settings.combos),e.wheelMessage=g[d]&&g[d].wheelMessage||g.en.wheelMessage,e.rtl=c[e.layout]&&c[e.layout].rtl||g[d]&&g[d].rtl||!1,f.regex=g[d]&&g[d].comboRegex||b.comboRegex,f.decimal=/^\./.test(e.display.dec),f.$el.toggleClass("rtl",e.rtl).css("direction",e.rtl?"rtl":"")},f.startup=function(){var c=b.css;(e.alwaysOpen||e.userClosed)&&f.$preview||f.makePreview(),f.$keyboard&&f.$keyboard.length||("custom"===e.layout&&(e.layoutHash="custom"+f.customHash()),f.layout="custom"===e.layout?e.layoutHash:e.layout,f.last.layout=f.layout,f.updateLanguage(),"undefined"==typeof b.builtLayouts[f.layout]&&(a.isFunction(e.create)?f.$keyboard=e.create(f):f.$keyboard.length||f.buildKeyboard(f.layout,!0)),f.$keyboard=b.builtLayouts[f.layout].$keyboard.clone(),f.$keyboard.data("keyboard",f),""!==(f.el.id||"")&&f.$keyboard.attr("id",f.el.id+b.css.idSuffix),f.makePreview(),e.usePreview?a.isEmptyObject(e.position)||(e.position.at=e.position.orig_at):a.isEmptyObject(e.position)||(e.position.at=e.position.at2)),f.$decBtn=f.$keyboard.find("."+c.keyPrefix+"dec"),(e.enterNavigation||"TEXTAREA"===f.el.nodeName)&&f.alwaysAllowed.push(13),f.bindKeyboard(),f.$keyboard.appendTo(e.appendLocally?f.$el.parent():e.appendTo||"body"),f.bindKeys(),e.reposition&&a.ui&&a.ui.position&&"body"==e.appendTo&&a(window).bind("resize"+f.namespace,function(){f.position&&f.isVisible()&&f.$keyboard.position(f.position)})},f.makePreview=function(){if(e.usePreview){var c,d,g,h,i=b.css;for(f.$preview=f.$el.clone(!1).data("keyboard",f).removeClass(i.placeholder+" "+i.input).addClass(i.preview+" "+e.css.input).attr("tabindex","-1").show(),f.preview=f.$preview[0],"number"===f.preview.type&&(f.preview.type="text"),h=/^(data-|id|aria-haspopup)/i,d=f.$preview.get(0).attributes,c=d.length-1;c>=0;c--)g=d[c]&&d[c].name,h.test(g)&&f.preview.removeAttribute(g);a("<div />").addClass(i.wrapper).append(f.$preview).prependTo(f.$keyboard)}else f.$preview=f.$el,f.preview=f.el},f.saveCaret=function(a,c,d){var e=b.caret(d||f.$preview,a,c);f.last.start="undefined"==typeof a?e.start:a,f.last.end="undefined"==typeof c?e.end:c},f.setScroll=function(){if(f.last.virtual){var a,c,d,g,h="TEXTAREA"===f.preview.nodeName,i=f.last.val.substring(0,Math.max(f.last.start,f.last.end));f.$previewCopy||(f.$previewCopy=f.$preview.clone().removeAttr("id").css({position:"absolute",left:0,zIndex:-10,visibility:"hidden"}).addClass(b.css.inputClone),h||f.$previewCopy.css({"white-space":"pre",width:0}),e.usePreview?f.$preview.after(f.$previewCopy):f.$keyboard.prepend(f.$previewCopy)),h?(f.$previewCopy.height(f.lineHeight).val(i),f.preview.scrollTop=f.lineHeight*(Math.floor(f.$previewCopy[0].scrollHeight/f.lineHeight)-1)):(f.$previewCopy.val(i.replace(/\s/g," ")),d=/c/i.test(e.scrollAdjustment)?f.preview.clientWidth/2:e.scrollAdjustment,a=f.$previewCopy[0].scrollWidth-1,"undefined"==typeof f.last.scrollWidth&&(f.last.scrollWidth=a,f.last.direction=!0),g=f.last.scrollWidth===a?f.last.direction:f.last.scrollWidth<a,c=f.preview.clientWidth-d,g?c>a?f.preview.scrollLeft=0:f.preview.scrollLeft=a-c:a>=f.preview.scrollWidth-c?f.preview.scrollLeft=f.preview.scrollWidth-d:a-d>0?f.preview.scrollLeft=a-d:f.preview.scrollLeft=0,f.last.scrollWidth=a,f.last.direction=g)}},f.bindFocus=function(){e.openOn&&f&&f.el.active&&(f.$el.bind(e.openOn+f.namespace,function(){f.focusOn()}),a(":focus")[0]===f.el&&f.$el.blur())},f.bindKeyboard=function(){var c,d=b.keyCodes,g=b.builtLayouts[f.layout];f.$preview.unbind(f.namespace).bind("click"+f.namespace+" touchstart"+f.namespace,function(){e.alwaysOpen&&!f.isCurrent()&&f.reveal(),f.timer2=setTimeout(function(){f&&f.saveCaret()},150)}).bind("keypress"+f.namespace,function(h){if(e.lockInput||!f.isCurrent())return!1;var i=h.charCode||h.which,j=i>=d.A&&i<=d.Z,k=i>=d.a&&i<=d.z,l=f.last.key=String.fromCharCode(i);if(f.last.virtual=!1,f.last.event=h,f.last.$key=[],f.checkCaret&&f.saveCaret(),i!==d.capsLock&&(j||k)&&(f.capsLock=j&&!h.shiftKey||k&&h.shiftKey,f.capsLock&&!f.shiftActive&&(f.shiftActive=!0,f.showSet())),e.restrictInput){if((h.which===d.backSpace||0===h.which)&&a.inArray(h.keyCode,f.alwaysAllowed))return;-1===a.inArray(l,g.acceptedKeys)&&(h.preventDefault(),c=a.extend({},h),c.type=b.events.inputRestricted,f.$el.trigger(c,[f,f.el]),a.isFunction(e.restricted)&&e.restricted(c,f,f.el))}else if((h.ctrlKey||h.metaKey)&&(h.which===d.A||h.which===d.C||h.which===d.V||h.which>=d.X&&h.which<=d.Z))return;g.hasMappedKeys&&g.mappedKeys.hasOwnProperty(l)&&(f.last.key=g.mappedKeys[l],f.insertText(f.last.key),h.preventDefault()),f.checkMaxLength()}).bind("keyup"+f.namespace,function(c){if(f.isCurrent()){switch(f.last.virtual=!1,c.which){case d.tab:if(f.tab&&e.tabNavigation&&!e.lockInput){f.shiftActive=c.shiftKey;var g=b.keyaction.tab(f);if(f.tab=!1,!g)return!1}else c.preventDefault();break;case d.escape:return e.ignoreEsc||f.close(e.autoAccept&&e.autoAcceptOnEsc?"true":!1),!1}return clearTimeout(f.throttled),f.throttled=setTimeout(function(){f&&f.isVisible()&&f.checkCombos()},100),f.checkMaxLength(),f.last.preVal=""+f.last.val,f.last.val=f.$preview.val(),c.type=b.events.kbChange,c.action=f.last.key,f.$el.trigger(c,[f,f.el]),a.isFunction(e.change)?(c.type=b.events.inputChange,e.change(c,f,f.el),!1):void(e.acceptValid&&e.autoAcceptOnValid&&a.isFunction(e.validate)&&e.validate(f,f.$preview.val())&&(f.$preview.blur(),f.accept()))}}).bind("keydown"+f.namespace,function(a){if(e.alwaysOpen&&!f.isCurrent()&&f.reveal(),a.which===d.tab)return f.tab=!0,!1;if(e.lockInput)return!1;switch(f.last.virtual=!1,a.which){case d.backSpace:b.keyaction.bksp(f,null,a),a.preventDefault();break;case d.enter:b.keyaction.enter(f,null,a);break;case d.capsLock:f.shiftActive=f.capsLock=!f.capsLock,f.showSet();break;case d.V:if(a.ctrlKey||a.metaKey){if(e.preventPaste)return void a.preventDefault();f.checkCombos()}}}).bind("mouseup touchend ".split(" ").join(f.namespace+" "),function(){f.last.virtual=!0,f.saveCaret()}),f.$keyboard.bind("mousedown click touchstart ".split(" ").join(f.namespace+" "),function(b){b.stopPropagation(),f.isCurrent()||(f.reveal(),a(document).trigger("checkkeyboard"+f.namespace)),e.noFocus||f.$preview.focus()}),e.preventPaste&&(f.$preview.bind("contextmenu"+f.namespace,function(a){a.preventDefault()}),f.$el.bind("contextmenu"+f.namespace,function(a){a.preventDefault()}))},f.bindKeys=function(){var c=b.css;f.$allKeys=f.$keyboard.find("button."+c.keyButton).unbind(f.namespace+" "+f.namespace+"kb").bind("mouseenter mouseleave touchstart ".split(" ").join(f.namespace+" "),function(c){if(!e.alwaysOpen&&!e.userClosed||"mouseleave"===c.type||f.isCurrent()||(f.reveal(),f.$preview.focus(),b.caret(f.$preview,f.last)),f.isCurrent()){var d,g,h=f.last,i=a(this),j=c.type;e.useWheel&&f.wheel&&(d=f.getLayers(i),g=(d.length?d.map(function(){return a(this).attr("data-value")||""}).get():"")||[i.text()],h.wheel_$Keys=d,h.wheelLayers=g,h.wheelIndex=a.inArray(i.attr("data-value"),g)),"mouseenter"!==j&&"touchstart"!==j||"password"===f.el.type||i.hasClass(e.css.buttonDisabled)||(i.addClass(e.css.buttonHover),e.useWheel&&f.wheel&&i.attr("title",function(a,b){return f.wheel&&""===b&&f.sets&&g.length>1&&"touchstart"!==j?e.wheelMessage:b})),"mouseleave"===j&&(i.removeClass("password"===f.el.type?"":e.css.buttonHover),e.useWheel&&f.wheel&&(h.wheelIndex=0,h.wheelLayers=[],h.wheel_$Keys=null,i.attr("title",function(a,b){return b===e.wheelMessage?"":b}).html(i.attr("data-html"))))}}).bind(e.keyBinding.split(" ").join(f.namespace+" ")+f.namespace+" "+b.events.kbRepeater,function(d){if(d.preventDefault(),!f.$keyboard.is(":visible"))return!1;var g,h,i=f.last,j=this,k=a(j),l=(new Date).getTime();if(e.useWheel&&f.wheel&&(h=i.wheel_$Keys,k=h&&i.wheelIndex>-1?h.eq(i.wheelIndex):k),g=k.attr("data-action"),!(l-(i.eventTime||0)<e.preventDoubleEventTime)){if(i.eventTime=l,i.event=d,i.virtual=!0,e.noFocus||f.$preview.focus(),i.$key=k,i.key=k.attr("data-value"),f.checkCaret&&b.caret(f.$preview,i),g.match("meta")&&(g="meta"),g===i.key&&"string"==typeof b.keyaction[g])i.key=g=b.keyaction[g];else if(g in b.keyaction&&a.isFunction(b.keyaction[g])){if(b.keyaction[g](f,this,d)===!1)return!1;g=null}return"undefined"!=typeof g&&null!==g&&(i.key=a(this).hasClass(c.keyAction)?g:i.key,f.insertText(i.key),f.capsLock||e.stickyShift||d.shiftKey||(f.shiftActive=!1,f.showSet(k.attr("data-name")))),b.caret(f.$preview,i),f.checkCombos(),d.type=b.events.kbChange,d.action=i.key,f.$el.trigger(d,[f,f.el]),i.preVal=""+i.val,i.val=f.$preview.val(),a.isFunction(e.change)?(d.type=b.events.inputChange,e.change(d,f,f.el),!1):void 0}}).bind("mouseup"+f.namespace+" "+"mouseleave touchend touchmove touchcancel ".split(" ").join(f.namespace+"kb "),function(c){f.last.virtual=!0;var d,g=a(this);if("touchmove"===c.type){if(d=g.offset(),d.right=d.left+g.outerWidth(),d.bottom=d.top+g.outerHeight(),c.originalEvent.touches[0].pageX>=d.left&&c.originalEvent.touches[0].pageX<d.right&&c.originalEvent.touches[0].pageY>=d.top&&c.originalEvent.touches[0].pageY<d.bottom)return!0}else/(mouseleave|touchend|touchcancel)/i.test(c.type)?g.removeClass(e.css.buttonHover):(!e.noFocus&&f.isCurrent()&&f.isVisible()&&f.$preview.focus(),f.checkCaret&&b.caret(f.$preview,f.last));return f.mouseRepeat=[!1,""],clearTimeout(f.repeater),!1}).bind("click"+f.namespace,function(){return!1}).not("."+c.keyAction).bind("mousewheel"+f.namespace,function(b,c){if(e.useWheel&&f.wheel){c=c||b.deltaY;var d,g=f.last.wheelLayers||[];return g.length>1?(d=f.last.wheelIndex+(c>0?-1:1),d>g.length-1&&(d=0),0>d&&(d=g.length-1)):d=0,f.last.wheelIndex=d,a(this).html(g[d]),!1}}).add("."+c.keyPrefix+"tab bksp space enter".split(" ").join(",."+c.keyPrefix),f.$keyboard).bind("mousedown touchstart ".split(" ").join(f.namespace+"kb "),function(){if(0!==e.repeatRate){var b=a(this);f.mouseRepeat=[!0,b],setTimeout(function(){f&&f.mouseRepeat[0]&&f.mouseRepeat[1]===b&&!b[0].disabled&&f.repeatKey(b)},e.repeatDelay)}return!1})},f.insertText=function(a){if("undefined"!=typeof a){var c,d,e="\b"===a,g=f.$preview.val(),h=b.caret(f.$preview),i=g.length;h.end<h.start&&(h.end=h.start),h.start>i&&(h.end=h.start=i),"TEXTAREA"===f.preview.nodeName&&b.msie&&"\n"===g.substr(h.start,1)&&(h.start+=1,h.end+=1),"{d}"===a&&(a="",d=h.start,h.end+=1),c=e&&h.start===h.end,a=e?"":a,g=g.substr(0,h.start-(c?1:0))+a+g.substr(h.end),d=h.start+(c?-1:a.length),f.$preview.val(g),f.saveCaret(d,d),f.setScroll()}},f.checkMaxLength=function(){if(f.isCurrent()){var a,c,d=f.$preview.val();e.maxLength!==!1&&d.length>e.maxLength&&(a=b.caret(f.$preview).start,c=Math.min(a,e.maxLength),e.maxInsert||(d=f.last.val,c=a-1),f.$preview.val(d.substring(0,e.maxLength)),f.saveCaret(c,c)),f.$decBtn.length&&f.checkDecimal()}},f.repeatKey=function(a){a.trigger(b.events.kbRepeater),f.mouseRepeat[0]&&(f.repeater=setTimeout(function(){f&&f.repeatKey(a)},f.repeatTime))},f.showKeySet=function(a){"string"==typeof a?(f.last.keyset=[f.shiftActive,f.altActive,f.metaActive],f.shiftActive=/shift/i.test(a),f.altActive=/alt/i.test(a),/meta/.test(a)?(f.metaActive=!0,f.showSet(a.match(/meta\d+/i)[0])):(f.metaActive=!1,f.showSet())):f.showSet(a)},f.showSet=function(a){e=f.options;var c=b.css,d="."+c.keyPrefix,g=e.css.buttonActive,h="",i=(f.shiftActive?1:0)+(f.altActive?2:0);return f.shiftActive||(f.capsLock=!1),f.metaActive?(h=/meta/i.test(a)?a:"",""===h?h=f.metaActive===!0?"":f.metaActive:f.metaActive=h,(!e.stickyShift&&f.last.keyset[2]!==f.metaActive||(f.shiftActive||f.altActive)&&!f.$keyboard.find("."+c.keySet+"-"+h+f.rows[i]).length)&&(f.shiftActive=f.altActive=!1)):!e.stickyShift&&f.last.keyset[2]!==f.metaActive&&f.shiftActive&&(f.shiftActive=f.altActive=!1),i=(f.shiftActive?1:0)+(f.altActive?2:0),h=0!==i||f.metaActive?""===h?"":"-"+h:"-normal",f.$keyboard.find("."+c.keySet+h+f.rows[i]).length?(f.$keyboard.find(d+"alt,"+d+"shift,."+c.keyAction+"[class*=meta]").removeClass(g).end().find(d+"alt").toggleClass(g,f.altActive).end().find(d+"shift").toggleClass(g,f.shiftActive).end().find(d+"lock").toggleClass(g,f.capsLock).end().find("."+c.keySet).hide().end().find("."+c.keyAction+d+h).addClass(g),f.$keyboard.find("."+c.keySet+h+f.rows[i])[0].style.display="inline-block",f.metaActive&&f.$keyboard.find(d+f.metaActive).toggleClass(g,f.metaActive!==!1),f.last.keyset=[f.shiftActive,f.altActive,f.metaActive],void f.$el.trigger(b.events.kbKeysetChange,[f,f.el])):(f.shiftActive=f.last.keyset[0],f.altActive=f.last.keyset[1],void(f.metaActive=f.last.keyset[2]))},f.checkCombos=function(){if(!f.isVisible()&&!f.$keyboard.hasClass(b.css.hasFocus))return f.$preview.val();var c,d,g,h=f.$preview.val(),i=b.caret(f.$preview),j=b.builtLayouts[f.layout],k=h.length;return""===h?(e.acceptValid&&f.checkValid(),h):(i.end<i.start&&(i.end=i.start),i.start>k&&(i.end=i.start=k),b.msie&&"\n"===h.substr(i.start,1)&&(i.start+=1,i.end+=1),e.useCombos&&(b.msie?h=h.replace(f.regex,function(a,b,c){return e.combos.hasOwnProperty(b)?e.combos[b][c]||a:a}):f.$preview.length&&(d=i.start-(i.start-2>=0?2:0),b.caret(f.$preview,d,i.end),g=(b.caret(f.$preview).text||"").replace(f.regex,function(a,b,c){return e.combos.hasOwnProperty(b)?e.combos[b][c]||a:a}),f.$preview.val(b.caret(f.$preview).replaceStr(g)),h=f.$preview.val())),e.restrictInput&&""!==h&&(d=j.acceptedKeys.length,c=j.acceptedKeysRegex,c||(g=a.map(j.acceptedKeys,function(a){return a.replace(f.escapeRegex,"\\$&")}),c=j.acceptedKeysRegex=new RegExp("("+g.join("|")+")","g")),g=h.match(c),g?h=g.join(""):(h="",k=0)),i.start+=h.length-k,i.end+=h.length-k,f.$preview.val(h),f.saveCaret(i.start,i.end),f.setScroll(),f.checkMaxLength(),e.acceptValid&&f.checkValid(),h)},f.checkValid=function(){var c=b.css,d=f.$keyboard.find("."+c.keyPrefix+"accept"),g=!0;a.isFunction(e.validate)&&(g=e.validate(f,f.$preview.val(),!1)),d.toggleClass(c.inputInvalid,!g).toggleClass(c.inputValid,g).attr("title",d.attr("data-title")+" ("+e.display[g?"valid":"invalid"]+")")},f.checkDecimal=function(){f.decimal&&/\./g.test(f.preview.value)||!f.decimal&&/\,/g.test(f.preview.value)?f.$decBtn.attr({disabled:"disabled","aria-disabled":"true"}).removeClass(e.css.buttonHover).addClass(e.css.buttonDisabled):f.$decBtn.removeAttr("disabled").attr({"aria-disabled":"false"}).addClass(e.css.buttonDefault).removeClass(e.css.buttonDisabled)},f.getLayers=function(c){var d=b.css,e=c.attr("data-pos"),f=c.closest("."+d.keyboard).find('button[data-pos="'+e+'"]');return f.filter(function(){return""!==a(this).find("."+d.keyText).text()}).add(c)},f.switchInput=function(b,c){if(a.isFunction(e.switchInput))e.switchInput(f,b,c);else{f.$keyboard.length&&f.$keyboard.hide();var d,g=!1,h=a("button, input, textarea, a").filter(":visible").not(":disabled"),i=h.index(f.$el)+(b?1:-1);if(f.$keyboard.length&&f.$keyboard.show(),i>h.length-1&&(g=e.stopAtEnd,i=0),0>i&&(g=e.stopAtEnd,i=h.length-1),!g){if(c=f.close(c),!c)return;d=h.eq(i).data("keyboard"),d&&d.options.openOn.length?d.focusOn():h.eq(i).focus()}}return!1},f.close=function(c){if(f.isOpen&&f.$keyboard.length){clearTimeout(f.throttled);var d=b.css,g=b.events,h=c?f.checkCombos():f.originalContent;if(c&&a.isFunction(e.validate)&&!e.validate(f,h,!0)&&(h=f.originalContent,c=!1,e.cancelClose))return;f.isCurrent(!1),f.isOpen=e.alwaysOpen||e.userClosed,f.$preview.val(h),f.$el.removeClass(d.isCurrent+" "+d.inputAutoAccepted).addClass(c?c===!0?"":d.inputAutoAccepted:"").val(h).trigger(g.inputChange),e.alwaysOpen||f.$el.trigger(g.kbBeforeClose,[f,f.el,c||!1]),f.$el.trigger(c?g.inputAccepted:g.inputCanceled,[f,f.el]).trigger(e.alwaysOpen?g.kbInactive:g.kbHidden,[f,f.el]).blur(),b.caret(f.$preview,f.last),f&&(f.last.eventTime=(new Date).getTime(),e.alwaysOpen||e.userClosed&&"true"===c||!f.$keyboard.length||(f.removeKeyboard(),f.timer=setTimeout(function(){f&&f.bindFocus()},500)),f.watermark||""!==f.el.value||""===f.inPlaceholder||f.$el.addClass(d.placeholder).val(f.inPlaceholder))}return!!c},f.accept=function(){return f.close(!0)},f.checkClose=function(b){if(!f.opening){f.escClose(b);var c=a.keyboard.css,d=a(b.target);if(d.hasClass(c.input)){var e=d.data("keyboard");e!==f||e.$el.hasClass(c.isCurrent)||b.type!==e.options.openOn||e.focusOn()}}},f.escClose=function(c){if(c&&"keyup"===c.type)return c.which!==b.keyCodes.escape||e.ignoreEsc?"":f.close(e.autoAccept&&e.autoAcceptOnEsc?"true":!1);if(f.isOpen&&(!f.isCurrent()&&f.isOpen||f.isOpen&&c.target!==f.el)){if((e.stayOpen||e.userClosed)&&!a(c.target).hasClass(b.css.input))return;b.allie&&c.preventDefault(),f.close(e.autoAccept?"true":!1)}},f.keyBtn=a("<button />").attr({role:"button",type:"button","aria-disabled":"false",tabindex:"-1"}).addClass(b.css.keyButton),f.processName=function(a){var b,c,d=(a||"").replace(/[^a-z0-9-_]/gi,""),e=d.length,f=[];if(e>1&&a===d)return a;if(e=a.length){for(b=0;e>b;b++)c=a[b],f.push(/[a-z0-9-_]/i.test(c)?/[-_]/.test(c)&&0!==b?"":c:(0===b?"":"-")+c.charCodeAt(0));return f.join("")}return a},f.processKeys=function(b){var c,d=b.split(":"),e={name:null,map:"",title:""};return/\(.+\)/.test(d[0])||/^:\(.+\)/.test(b)||/\([(:)]\)/.test(b)?/\([(:)]\)/.test(b)?(c=d[0].match(/([^(]+)\((.+)\)/),c&&c.length?(e.name=c[1],e.map=c[2],e.title=d.length>1?d.slice(1).join(":"):""):(e.name=b.match(/([^(]+)/)[0],":"===e.name&&(d=d.slice(1)),null===c&&(e.map=":",d=d.slice(2)),e.title=d.length?d.join(":"):"")):(e.map=b.match(/\(([^()]+?)\)/)[1],b=b.replace(/\(([^()]+)\)/,""),c=b.split(":"),""===c[0]?(e.name=":",d=d.slice(1)):e.name=c[0],e.title=d.length>1?d.slice(1).join(":"):""):(""===d[0]?(e.name=":",d=d.slice(1)):e.name=d[0],e.title=d.length>1?d.slice(1).join(":"):""),e.title=a.trim(e.title).replace(/_/g," "),e},f.addKey=function(a,c,d){var g,h,i,j={},k=f.processKeys(d?a:c),l=b.css;return!d&&e.display[k.name]?(i=f.processKeys(e.display[k.name]),i.action=f.processKeys(a).name):(i=k,i.action=k.name),j.name=f.processName(k.name),""!==i.map?(b.builtLayouts[f.layout].mappedKeys[i.map]=i.name,b.builtLayouts[f.layout].acceptedKeys.push(i.name)):d&&b.builtLayouts[f.layout].acceptedKeys.push(i.name),g=d?""===j.name?"":l.keyPrefix+j.name:l.keyAction+" "+l.keyPrefix+i.action,g+=(i.name.length>2?" "+l.keyWide:"")+" "+e.css.buttonDefault,j.html='<span class="'+l.keyText+'">'+i.name.replace(/[\u00A0-\u9999]/gim,function(a){return"&#"+a.charCodeAt(0)+";"})+"</span>",j.$key=f.keyBtn.clone().attr({"data-value":d?i.name:i.action,"data-name":i.action,"data-pos":f.temp[1]+","+f.temp[2],"data-action":i.action,"data-html":j.html}).addClass(g).html(j.html).appendTo(f.temp[0]),i.map&&j.$key.attr("data-mapped",i.map),(i.title||k.title)&&j.$key.attr({"data-title":k.title||i.title,title:k.title||i.title}),"function"==typeof e.buildKey&&(j=e.buildKey(f,j),h=j.$key.html(),j.$key.attr("data-html",h)),j.$key},f.customHash=function(a){var b,c,d,f,g,h=[],i=[];a="undefined"==typeof a?e.customLayout:a;for(c in a)a.hasOwnProperty(c)&&h.push(a[c]);if(i=i.concat.apply(i,h).join(" "),d=0,g=i.length,0===g)return d;for(b=0;g>b;b++)f=i.charCodeAt(b),d=(d<<5)-d+f,d&=d;return d},f.buildKeyboard=function(c,d){a.isEmptyObject(e.display)&&f.updateLanguage();var g,h,i,j=b.css,k=0,l=b.builtLayouts[c||f.layout||e.layout]={mappedKeys:{},acceptedKeys:[]},m=l.acceptedKeys=e.restrictInclude?(""+e.restrictInclude).split(/\s+/)||[]:[],n=j.keyboard+" "+e.css.popup+" "+e.css.container+(e.alwaysOpen||e.userClosed?" "+j.alwaysOpen:""),o=a("<div />").addClass(n).attr({role:"textbox"}).hide();return d&&"custom"===e.layout||!b.layouts.hasOwnProperty(e.layout)?(e.layout="custom",n=b.layouts.custom=e.customLayout||{normal:["{cancel}"]}):n=b.layouts[d?e.layout:c||f.layout||e.layout],a.each(n,function(b,c){if(""!==b&&!/^(name|lang|rtl)$/i.test(b))for("default"===b&&(b="normal"),k++,h=a("<div />").attr("name",b).addClass(j.keySet+" "+j.keySet+"-"+b).appendTo(o).toggle("normal"===b),g=0;g<c.length;g++)i=a.trim(c[g]).replace(/\{(\.?)[\s+]?:[\s+]?(\.?)\}/g,"{$1:$2}"),f.buildRow(h,g,i.split(/\s+/),m),h.find("."+j.keyButton+",."+j.keySpacer).filter(":last").after('<br class="'+j.endRow+'"/>')}),k>1&&(f.sets=!0),l.hasMappedKeys=!a.isEmptyObject(l.mappedKeys),l.$keyboard=o,o},f.buildRow=function(c,d,g,h){var i,j,k,l,m,n,o=b.css;for(k=0;k<g.length;k++)if(f.temp=[c,d,k],l=!1,0!==g[k].length)if(/^\{\S+\}$/.test(g[k])){if(m=g[k].match(/^\{(\S+)\}$/)[1],/\!\!/.test(m)&&(m=m.replace("!!",""),l=!0),/^sp:((\d+)?([\.|,]\d+)?)(em|px)?$/i.test(m)&&(n=parseFloat(m.replace(/,/,".").match(/^sp:((\d+)?([\.|,]\d+)?)(em|px)?$/i)[1]||0),a('<span class="'+o.keyText+'"></span>').width(m.match(/px/i)?n+"px":2*n+"em").addClass(o.keySpacer).appendTo(c)),/^empty(:((\d+)?([\.|,]\d+)?)(em|px)?)?$/i.test(m)&&(n=/:/.test(m)?parseFloat(m.replace(/,/,".").match(/^empty:((\d+)?([\.|,]\d+)?)(em|px)?$/i)[1]||0):"",f.addKey(""," ").addClass(e.css.buttonDisabled+" "+e.css.buttonEmpty).attr("aria-disabled",!0).width(n?m.match("px")?n+"px":2*n+"em":"")),/^meta\d+\:?(\w+)?/i.test(m)){f.addKey(m.split(":")[0],m).addClass(o.keyHasActive);continue}switch(j=m.split(":"),j[0].toLowerCase()){case"a":case"accept":f.addKey("accept",m).addClass(e.css.buttonAction+" "+o.keyAction);break;case"alt":case"altgr":f.addKey("alt",m).addClass(o.keyHasActive);break;case"b":case"bksp":f.addKey("bksp",m);break;case"c":case"cancel":f.addKey("cancel",m).addClass(e.css.buttonAction+" "+o.keyAction);break;case"combo":f.addKey("combo",m).addClass(o.keyHasActive).attr("title",function(a,b){return b+" "+e.display[e.useCombos?"active":"disabled"]}).toggleClass(e.css.buttonActive,e.useCombos);break;case"dec":h.push(f.decimal?".":","),f.addKey("dec",m);break;case"e":case"enter":f.addKey("enter",m).addClass(e.css.buttonAction+" "+o.keyAction);break;case"lock":f.addKey("lock",m).addClass(o.keyHasActive);break;case"s":case"shift":f.addKey("shift",m).addClass(o.keyHasActive);break;case"sign":h.push("-"),f.addKey("sign",m);break;case"space":h.push(" "),f.addKey("space",m);break;case"t":case"tab":f.addKey("tab",m);break;default:b.keyaction.hasOwnProperty(j[0])&&f.addKey(j[0],m).toggleClass(e.css.buttonAction+" "+o.keyAction,l)}}else i=g[k],f.addKey(i,i,!0)},f.removeBindings=function(b){a(document).unbind(b),f.el.ownerDocument!==document&&a(f.el.ownerDocument).unbind(b),a(window).unbind(b),f.$el.unbind(b)},f.removeKeyboard=function(){f.$allKeys=null,f.$decBtn=null,e.usePreview&&f.$preview.removeData("keyboard"),f.preview=null,f.$preview=null,f.$previewCopy=null,f.$keyboard.removeData("keyboard"),f.$keyboard.remove(),f.$keyboard=[],f.isOpen=!1,f.isCurrent(!1)},f.destroy=function(a){var c,d=b.css,g=f.extensionNamespace.length,h=[d.input,d.locked,d.placeholder,d.noKeyboard,d.alwaysOpen,e.css.input,d.isCurrent].join(" ");for(clearTimeout(f.timer),clearTimeout(f.timer2),f.$keyboard.length&&f.removeKeyboard(),f.removeBindings(f.namespace),f.removeBindings(f.namespace+"callbacks"),c=0;g>c;c++)f.removeBindings(f.extensionNamespace[c]);f.el.active=!1,f.$el.removeClass(h).removeAttr("aria-haspopup").removeAttr("role").removeData("keyboard"),f=null,"function"==typeof a&&a()},f.init()};return b.keyCodes={backSpace:8,tab:9,enter:13,capsLock:20,escape:27,space:32,pageUp:33,pageDown:34,end:35,home:36,left:37,up:38,right:39,down:40,insert:45,"delete":46,A:65,Z:90,V:86,C:67,X:88,a:97,z:122},b.css={idSuffix:"_keyboard",input:"ui-keyboard-input",inputClone:"ui-keyboard-preview-clone",wrapper:"ui-keyboard-preview-wrapper",preview:"ui-keyboard-preview",keyboard:"ui-keyboard",keySet:"ui-keyboard-keyset",keyButton:"ui-keyboard-button",keyWide:"ui-keyboard-widekey",keyPrefix:"ui-keyboard-",keyText:"ui-keyboard-text",keyHasActive:"ui-keyboard-hasactivestate",keyAction:"ui-keyboard-actionkey",keySpacer:"ui-keyboard-spacer",keyToggle:"ui-keyboard-toggle",keyDisabled:"ui-keyboard-disabled",locked:"ui-keyboard-lockedinput",alwaysOpen:"ui-keyboard-always-open",noKeyboard:"ui-keyboard-nokeyboard",placeholder:"ui-keyboard-placeholder",hasFocus:"ui-keyboard-has-focus",isCurrent:"ui-keyboard-input-current",inputValid:"ui-keyboard-valid-input",inputInvalid:"ui-keyboard-invalid-input",inputAutoAccepted:"ui-keyboard-autoaccepted",endRow:"ui-keyboard-button-endrow"},b.events={kbChange:"keyboardChange",kbBeforeClose:"beforeClose",kbBeforeVisible:"beforeVisible",kbVisible:"visible",kbInit:"initialized",kbInactive:"inactive",kbHidden:"hidden",kbRepeater:"repeater",kbKeysetChange:"keysetChange",inputAccepted:"accepted",inputCanceled:"canceled",inputChange:"change",inputRestricted:"restricted"},b.keyaction={accept:function(a){return a.close(!0),!1},alt:function(a){a.altActive=!a.altActive,a.showSet()},bksp:function(a){a.insertText("\b")},cancel:function(a){return a.close(),!1},clear:function(a){a.$preview.val(""),a.$decBtn.length&&a.checkDecimal()},combo:function(a){var c=a.options,d=!c.useCombos,e=a.$keyboard.find("."+b.css.keyPrefix+"combo");return c.useCombos=d,e.toggleClass(c.css.buttonActive,d).attr("title",e.attr("data-title")+" ("+c.display[d?"active":"disabled"]+")"),d&&a.checkCombos(),!1},dec:function(a){a.insertText(a.decimal?".":",")},del:function(a){a.insertText("{d}")},"default":function(a){a.shiftActive=a.altActive=a.metaActive=!1,a.showSet()},enter:function(c,d,e){var f=c.el.nodeName,g=c.options;return e.shiftKey?g.enterNavigation?c.switchInput(!e[g.enterMod],!0):c.close(!0):g.enterNavigation&&("TEXTAREA"!==f||e[g.enterMod])?c.switchInput(!e[g.enterMod],g.autoAccept?"true":!1):void("TEXTAREA"===f&&a(e.target).closest("button").length&&c.insertText((b.msie?" ":"")+"\n"))},lock:function(a){a.last.keyset[0]=a.shiftActive=a.capsLock=!a.capsLock,a.showSet()},left:function(a){var c=b.caret(a.$preview);c.start-1>=0&&(a.last.start=a.last.end=c.start-1,b.caret(a.$preview,a.last),a.setScroll())},meta:function(b,c){var d=a(c);b.metaActive=!d.hasClass(b.options.css.buttonActive),b.showSet(d.attr("data-name"))},next:function(a){return a.switchInput(!0,a.options.autoAccept),!1},normal:function(a){a.shiftActive=a.altActive=a.metaActive=!1,a.showSet()},prev:function(a){return a.switchInput(!1,a.options.autoAccept),!1},right:function(a){var c=b.caret(a.$preview);c.start+1<=a.$preview.val().length&&(a.last.start=a.last.end=c.start+1,b.caret(a.$preview,a.last),a.setScroll())},shift:function(a){a.last.keyset[0]=a.shiftActive=!a.shiftActive,a.showSet()},sign:function(a){/^\-?\d*\.?\d*$/.test(a.$preview.val())&&a.$preview.val(-1*a.$preview.val())},space:function(a){
a.insertText(" ")},tab:function(a){var b=a.el.nodeName,c=a.options;return"INPUT"===b?c.tabNavigation?a.switchInput(!a.shiftActive,!0):!1:void a.insertText("	")},toggle:function(a){a.enabled=!a.enabled,a.toggle()},NBSP:" ",ZWSP:"​",ZWNJ:"‌",ZWJ:"‍",LRM:"‎",RLM:"‏"},b.builtLayouts={},b.layouts={alpha:{normal:["` 1 2 3 4 5 6 7 8 9 0 - = {bksp}","{tab} a b c d e f g h i j [ ] \\","k l m n o p q r s ; ' {enter}","{shift} t u v w x y z , . / {shift}","{accept} {space} {cancel}"],shift:["~ ! @ # $ % ^ & * ( ) _ + {bksp}","{tab} A B C D E F G H I J { } |",'K L M N O P Q R S : " {enter}',"{shift} T U V W X Y Z < > ? {shift}","{accept} {space} {cancel}"]},qwerty:{normal:["` 1 2 3 4 5 6 7 8 9 0 - = {bksp}","{tab} q w e r t y u i o p [ ] \\","a s d f g h j k l ; ' {enter}","{shift} z x c v b n m , . / {shift}","{accept} {space} {cancel}"],shift:["~ ! @ # $ % ^ & * ( ) _ + {bksp}","{tab} Q W E R T Y U I O P { } |",'A S D F G H J K L : " {enter}',"{shift} Z X C V B N M < > ? {shift}","{accept} {space} {cancel}"]},international:{normal:["` 1 2 3 4 5 6 7 8 9 0 - = {bksp}","{tab} q w e r t y u i o p [ ] \\","a s d f g h j k l ; ' {enter}","{shift} z x c v b n m , . / {shift}","{accept} {alt} {space} {alt} {cancel}"],shift:["~ ! @ # $ % ^ & * ( ) _ + {bksp}","{tab} Q W E R T Y U I O P { } |",'A S D F G H J K L : " {enter}',"{shift} Z X C V B N M < > ? {shift}","{accept} {alt} {space} {alt} {cancel}"],alt:["~ ¡ ² ³ ¤ € ¼ ½ ¾ ‘ ’ ¥ × {bksp}","{tab} ä å é ® þ ü ú í ó ö « » ¬","á ß ð f g h j k ø ¶ ´ {enter}","{shift} æ x © v b ñ µ ç > ¿ {shift}","{accept} {alt} {space} {alt} {cancel}"],"alt-shift":["~ ¹ ² ³ £ € ¼ ½ ¾ ‘ ’ ¥ ÷ {bksp}","{tab} Ä Å É ® Þ Ü Ú Í Ó Ö « » ¦","Ä § Ð F G H J K Ø ° ¨ {enter}","{shift} Æ X ¢ V B Ñ µ Ç . ¿ {shift}","{accept} {alt} {space} {alt} {cancel}"]},colemak:{normal:["` 1 2 3 4 5 6 7 8 9 0 - = {bksp}","{tab} q w f p g j l u y ; [ ] \\","{bksp} a r s t d h n e i o ' {enter}","{shift} z x c v b k m , . / {shift}","{accept} {space} {cancel}"],shift:["~ ! @ # $ % ^ & * ( ) _ + {bksp}","{tab} Q W F P G J L U Y : { } |",'{bksp} A R S T D H N E I O " {enter}',"{shift} Z X C V B K M < > ? {shift}","{accept} {space} {cancel}"]},dvorak:{normal:["` 1 2 3 4 5 6 7 8 9 0 [ ] {bksp}","{tab} ' , . p y f g c r l / = \\","a o e u i d h t n s - {enter}","{shift} ; q j k x b m w v z {shift}","{accept} {space} {cancel}"],shift:["~ ! @ # $ % ^ & * ( ) { } {bksp}",'{tab} " < > P Y F G C R L ? + |',"A O E U I D H T N S _ {enter}","{shift} : Q J K X B M W V Z {shift}","{accept} {space} {cancel}"]},num:{normal:["= ( ) {b}","{clear} / * -","7 8 9 +","4 5 6 {sign}","1 2 3 %","0 {dec} {a} {c}"]}},b.language={en:{display:{a:"✔:Accept (Shift+Enter)",accept:"Accept:Accept (Shift+Enter)",alt:"Alt:⌥ AltGr",b:"⌫:Backspace",bksp:"Bksp:Backspace",c:"✖:Cancel (Esc)",cancel:"Cancel:Cancel (Esc)",clear:"C:Clear",combo:"ö:Toggle Combo Keys",dec:".:Decimal",e:"⏎:Enter",empty:" ",enter:"Enter:Enter ⏎",left:"←",lock:"Lock:⇪ Caps Lock",next:"Next ⇨",prev:"⇦ Prev",right:"→",s:"⇧:Shift",shift:"Shift:Shift",sign:"±:Change Sign",space:" :Space",t:"⇥:Tab",tab:"⇥ Tab:Tab",toggle:" ",valid:"valid",invalid:"invalid",active:"active",disabled:"disabled"},wheelMessage:"Use mousewheel to see other keys",comboRegex:/([`\'~\^\"ao])([a-z])/gim,combos:{"`":{a:"à",A:"À",e:"è",E:"È",i:"ì",I:"Ì",o:"ò",O:"Ò",u:"ù",U:"Ù",y:"ỳ",Y:"Ỳ"},"'":{a:"á",A:"Á",e:"é",E:"É",i:"í",I:"Í",o:"ó",O:"Ó",u:"ú",U:"Ú",y:"ý",Y:"Ý"},'"':{a:"ä",A:"Ä",e:"ë",E:"Ë",i:"ï",I:"Ï",o:"ö",O:"Ö",u:"ü",U:"Ü",y:"ÿ",Y:"Ÿ"},"^":{a:"â",A:"Â",e:"ê",E:"Ê",i:"î",I:"Î",o:"ô",O:"Ô",u:"û",U:"Û",y:"ŷ",Y:"Ŷ"},"~":{a:"ã",A:"Ã",e:"ẽ",E:"Ẽ",i:"ĩ",I:"Ĩ",o:"õ",O:"Õ",u:"ũ",U:"Ũ",y:"ỹ",Y:"Ỹ",n:"ñ",N:"Ñ"}}}},b.defaultOptions={language:null,rtl:!1,layout:"qwerty",customLayout:null,position:{of:null,my:"center top",at:"center top",at2:"center bottom"},reposition:!0,usePreview:!0,alwaysOpen:!1,initialFocus:!0,noFocus:!1,stayOpen:!1,ignoreEsc:!1,css:{input:"ui-widget-content ui-corner-all",container:"ui-widget-content ui-widget ui-corner-all ui-helper-clearfix",popup:"",buttonDefault:"ui-state-default ui-corner-all",buttonHover:"ui-state-hover",buttonAction:"ui-state-active",buttonActive:"ui-state-active",buttonDisabled:"ui-state-disabled",buttonEmpty:"ui-keyboard-empty"},autoAccept:!1,autoAcceptOnEsc:!1,lockInput:!1,restrictInput:!1,restrictInclude:"",acceptValid:!1,autoAcceptOnValid:!1,cancelClose:!0,tabNavigation:!1,enterNavigation:!1,enterMod:"altKey",stopAtEnd:!0,appendLocally:!1,appendTo:"body",stickyShift:!0,preventPaste:!1,caretToEnd:!1,scrollAdjustment:10,maxLength:!1,maxInsert:!0,repeatDelay:500,repeatRate:20,resetDefault:!0,openOn:"focus",keyBinding:"mousedown touchstart",useWheel:!0,useCombos:!0,validate:function(a,b,c){return!0}},b.comboRegex=/([`\'~\^\"ao])([a-z])/gim,b.currentKeyboard="",a('<!--[if lte IE 8]><script>jQuery("body").addClass("oldie");</script><![endif]--><!--[if IE]><script>jQuery("body").addClass("ie");</script><![endif]-->').appendTo("body").remove(),b.msie=a("body").hasClass("oldie"),b.allie=a("body").hasClass("ie"),b.watermark="undefined"!=typeof document.createElement("input").placeholder,b.checkCaretSupport=function(){if("boolean"!=typeof b.checkCaret){var c=a('<div style="height:0px;width:0px;overflow:hidden;position:fixed;top:0;left:-100px;"><input type="text" value="testing"/></div>').prependTo("body");b.caret(c.find("input"),3,3),b.checkCaret=3!==b.caret(c.find("input").hide().show()).start,c.remove()}return b.checkCaret},b.caret=function(a,b,c){if(!a||!a.length||a.is(":hidden")||"hidden"===a.css("visibility"))return{};var d,e,f,g,h=a.data("keyboard"),i=h&&h.options.noFocus;return i||a.focus(),"undefined"!=typeof b?("object"==typeof b&&"start"in b&&"end"in b?(d=b.start,e=b.end):"undefined"==typeof c&&(c=b),"number"==typeof b&&"number"==typeof c?(d=b,e=c):"start"===b?d=e=0:"string"==typeof b&&(d=e=a.val().length),a.caret(d,e,i)):(g=a.caret(),d=g.start,e=g.end,f=a[0].value||a.text()||"",{start:d,end:e,text:f.substring(d,e),replaceStr:function(a){return f.substring(0,d)+a+f.substring(e,f.length)}})},a.fn.keyboard=function(b){return this.each(function(){a(this).data("keyboard")||new a.keyboard(this,b)})},a.fn.getkeyboard=function(){return this.data("keyboard")},a.fn.caret=function(a,b,c){if("undefined"==typeof this[0]||this.is(":hidden")||"hidden"===this.css("visibility"))return this;var d,e,f,g,h,i=document.selection,j=this,k=j[0],l=k.scrollTop,m=!1,n=!0;try{m="selectionStart"in k}catch(o){n=!1}return n&&"undefined"!=typeof a?(/(email|number)/i.test(k.type)||(m?(k.selectionStart=a,k.selectionEnd=b):(d=k.createTextRange(),d.collapse(!0),d.moveStart("character",a),d.moveEnd("character",b-a),d.select())),c||!j.is(":visible")&&"hidden"===j.css("visibility")||k.focus(),k.scrollTop=l,this):(/(email|number)/i.test(k.type)?a=b=j.val().length:m?(a=k.selectionStart,b=k.selectionEnd):i?"TEXTAREA"===k.nodeName?(h=j.val(),e=i.createRange(),f=e.duplicate(),f.moveToElementText(k),f.setEndPoint("EndToEnd",e),a=f.text.replace(/\r/g,"\n").length,b=a+e.text.replace(/\r/g,"\n").length):(h=j.val().replace(/\r/g,"\n"),e=i.createRange().duplicate(),e.moveEnd("character",h.length),a=""===e.text?h.length:h.lastIndexOf(e.text),e=i.createRange().duplicate(),e.moveStart("character",-h.length),b=e.text.length):a=b=(k.value||"").length,g=k.value||"",{start:a,end:b,text:g.substring(a,b),replace:function(c){return g.substring(0,a)+c+g.substring(b,g.length)}})},b});
!function(e,a){"object"==typeof exports&&"undefined"!=typeof module?module.exports=a():"function"==typeof define&&define.amd?define(a):e.moment=a()}(this,function(){"use strict";var E;function M(){return E.apply(null,arguments)}function F(e){return e instanceof Array||"[object Array]"===Object.prototype.toString.call(e)}function z(e){return null!=e&&"[object Object]"===Object.prototype.toString.call(e)}function l(e,a){return Object.prototype.hasOwnProperty.call(e,a)}function N(e){if(Object.getOwnPropertyNames)return 0===Object.getOwnPropertyNames(e).length;for(var a in e)if(l(e,a))return;return 1}function d(e){return void 0===e}function J(e){return"number"==typeof e||"[object Number]"===Object.prototype.toString.call(e)}function R(e){return e instanceof Date||"[object Date]"===Object.prototype.toString.call(e)}function C(e,a){for(var t=[],s=e.length,n=0;n<s;++n)t.push(a(e[n],n));return t}function I(e,a){for(var t in a)l(a,t)&&(e[t]=a[t]);return l(a,"toString")&&(e.toString=a.toString),l(a,"valueOf")&&(e.valueOf=a.valueOf),e}function U(e,a,t,s){return Fa(e,a,t,s,!0).utc()}function h(e){return null==e._pf&&(e._pf={empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidEra:null,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1,parsedDateParts:[],era:null,meridiem:null,rfc2822:!1,weekdayMismatch:!1}),e._pf}function G(e){if(null==e._isValid){var a=h(e),t=B.call(a.parsedDateParts,function(e){return null!=e}),t=!isNaN(e._d.getTime())&&a.overflow<0&&!a.empty&&!a.invalidEra&&!a.invalidMonth&&!a.invalidWeekday&&!a.weekdayMismatch&&!a.nullInput&&!a.invalidFormat&&!a.userInvalidated&&(!a.meridiem||a.meridiem&&t);if(e._strict&&(t=t&&0===a.charsLeftOver&&0===a.unusedTokens.length&&void 0===a.bigHour),null!=Object.isFrozen&&Object.isFrozen(e))return t;e._isValid=t}return e._isValid}function V(e){var a=U(NaN);return null!=e?I(h(a),e):h(a).userInvalidated=!0,a}var B=Array.prototype.some||function(e){for(var a=Object(this),t=a.length>>>0,s=0;s<t;s++)if(s in a&&e.call(this,a[s],s,a))return!0;return!1},K=M.momentProperties=[],q=!1;function Z(e,a){var t,s,n,r=K.length;if(d(a._isAMomentObject)||(e._isAMomentObject=a._isAMomentObject),d(a._i)||(e._i=a._i),d(a._f)||(e._f=a._f),d(a._l)||(e._l=a._l),d(a._strict)||(e._strict=a._strict),d(a._tzm)||(e._tzm=a._tzm),d(a._isUTC)||(e._isUTC=a._isUTC),d(a._offset)||(e._offset=a._offset),d(a._pf)||(e._pf=h(a)),d(a._locale)||(e._locale=a._locale),0<r)for(t=0;t<r;t++)d(n=a[s=K[t]])||(e[s]=n);return e}function $(e){Z(this,e),this._d=new Date(null!=e._d?e._d.getTime():NaN),this.isValid()||(this._d=new Date(NaN)),!1===q&&(q=!0,M.updateOffset(this),q=!1)}function Q(e){return e instanceof $||null!=e&&null!=e._isAMomentObject}function X(e){!1===M.suppressDeprecationWarnings&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+e)}function e(r,d){var i=!0;return I(function(){if(null!=M.deprecationHandler&&M.deprecationHandler(null,r),i){for(var e,a,t=[],s=arguments.length,n=0;n<s;n++){if(e="","object"==typeof arguments[n]){for(a in e+="\n["+n+"] ",arguments[0])l(arguments[0],a)&&(e+=a+": "+arguments[0][a]+", ");e=e.slice(0,-2)}else e=arguments[n];t.push(e)}X(r+"\nArguments: "+Array.prototype.slice.call(t).join("")+"\n"+(new Error).stack),i=!1}return d.apply(this,arguments)},d)}var ee={};function ae(e,a){null!=M.deprecationHandler&&M.deprecationHandler(e,a),ee[e]||(X(a),ee[e]=!0)}function te(e){return"undefined"!=typeof Function&&e instanceof Function||"[object Function]"===Object.prototype.toString.call(e)}function se(e,a){var t,s=I({},e);for(t in a)l(a,t)&&(z(e[t])&&z(a[t])?(s[t]={},I(s[t],e[t]),I(s[t],a[t])):null!=a[t]?s[t]=a[t]:delete s[t]);for(t in e)l(e,t)&&!l(a,t)&&z(e[t])&&(s[t]=I({},s[t]));return s}function ne(e){null!=e&&this.set(e)}M.suppressDeprecationWarnings=!1,M.deprecationHandler=null;var re=Object.keys||function(e){var a,t=[];for(a in e)l(e,a)&&t.push(a);return t};function de(e,a,t){var s=""+Math.abs(e);return(0<=e?t?"+":"":"-")+Math.pow(10,Math.max(0,a-s.length)).toString().substr(1)+s}var ie=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,_e=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,oe={},me={};function s(e,a,t,s){var n="string"==typeof s?function(){return this[s]()}:s;e&&(me[e]=n),a&&(me[a[0]]=function(){return de(n.apply(this,arguments),a[1],a[2])}),t&&(me[t]=function(){return this.localeData().ordinal(n.apply(this,arguments),e)})}function ue(e,a){return e.isValid()?(a=le(a,e.localeData()),oe[a]=oe[a]||function(s){for(var e,n=s.match(ie),a=0,r=n.length;a<r;a++)me[n[a]]?n[a]=me[n[a]]:n[a]=(e=n[a]).match(/\[[\s\S]/)?e.replace(/^\[|\]$/g,""):e.replace(/\\/g,"");return function(e){for(var a="",t=0;t<r;t++)a+=te(n[t])?n[t].call(e,s):n[t];return a}}(a),oe[a](e)):e.localeData().invalidDate()}function le(e,a){var t=5;function s(e){return a.longDateFormat(e)||e}for(_e.lastIndex=0;0<=t&&_e.test(e);)e=e.replace(_e,s),_e.lastIndex=0,--t;return e}var Me={};function a(e,a){var t=e.toLowerCase();Me[t]=Me[t+"s"]=Me[a]=e}function i(e){return"string"==typeof e?Me[e]||Me[e.toLowerCase()]:void 0}function he(e){var a,t,s={};for(t in e)l(e,t)&&(a=i(t))&&(s[a]=e[t]);return s}var ce={};function t(e,a){ce[e]=a}function Le(e){return e%4==0&&e%100!=0||e%400==0}function u(e){return e<0?Math.ceil(e)||0:Math.floor(e)}function m(e){var e=+e,a=0;return a=0!=e&&isFinite(e)?u(e):a}function Ye(a,t){return function(e){return null!=e?(fe(this,a,e),M.updateOffset(this,t),this):ye(this,a)}}function ye(e,a){return e.isValid()?e._d["get"+(e._isUTC?"UTC":"")+a]():NaN}function fe(e,a,t){e.isValid()&&!isNaN(t)&&("FullYear"===a&&Le(e.year())&&1===e.month()&&29===e.date()?(t=m(t),e._d["set"+(e._isUTC?"UTC":"")+a](t,e.month(),ze(t,e.month()))):e._d["set"+(e._isUTC?"UTC":"")+a](t))}var n=/\d/,r=/\d\d/,pe=/\d{3}/,ke=/\d{4}/,_=/[+-]?\d{6}/,o=/\d\d?/,c=/\d\d\d\d?/,L=/\d\d\d\d\d\d?/,De=/\d{1,3}/,Y=/\d{1,4}/,y=/[+-]?\d{1,6}/,Te=/\d+/,ge=/[+-]?\d+/,we=/Z|[+-]\d\d:?\d\d/gi,be=/Z|[+-]\d\d(?::?\d\d)?/gi,f=/[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i;function p(e,t,s){He[e]=te(t)?t:function(e,a){return e&&s?s:t}}function Se(e,a){return l(He,e)?He[e](a._strict,a._locale):new RegExp(k(e.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(e,a,t,s,n){return a||t||s||n})))}function k(e){return e.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}var He={},ve={};function D(e,t){var a,s,n=t;for("string"==typeof e&&(e=[e]),J(t)&&(n=function(e,a){a[t]=m(e)}),s=e.length,a=0;a<s;a++)ve[e[a]]=n}function je(e,n){D(e,function(e,a,t,s){t._w=t._w||{},n(e,t._w,t,s)})}var T,g=0,xe=1,Pe=2,w=3,Oe=4,We=5,Ae=6,Ee=7,Fe=8;function ze(e,a){if(isNaN(e)||isNaN(a))return NaN;var t=(a%(t=12)+t)%t;return e+=(a-t)/12,1==t?Le(e)?29:28:31-t%7%2}T=Array.prototype.indexOf||function(e){for(var a=0;a<this.length;++a)if(this[a]===e)return a;return-1},s("M",["MM",2],"Mo",function(){return this.month()+1}),s("MMM",0,0,function(e){return this.localeData().monthsShort(this,e)}),s("MMMM",0,0,function(e){return this.localeData().months(this,e)}),a("month","M"),t("month",8),p("M",o),p("MM",o,r),p("MMM",function(e,a){return a.monthsShortRegex(e)}),p("MMMM",function(e,a){return a.monthsRegex(e)}),D(["M","MM"],function(e,a){a[xe]=m(e)-1}),D(["MMM","MMMM"],function(e,a,t,s){s=t._locale.monthsParse(e,s,t._strict);null!=s?a[xe]=s:h(t).invalidMonth=e});var Ne="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),Je="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),Re=/D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/,Ce=f,Ie=f;function Ue(e,a){var t;if(e.isValid()){if("string"==typeof a)if(/^\d+$/.test(a))a=m(a);else if(!J(a=e.localeData().monthsParse(a)))return;t=Math.min(e.date(),ze(e.year(),a)),e._d["set"+(e._isUTC?"UTC":"")+"Month"](a,t)}}function Ge(e){return null!=e?(Ue(this,e),M.updateOffset(this,!0),this):ye(this,"Month")}function Ve(){function e(e,a){return a.length-e.length}for(var a,t=[],s=[],n=[],r=0;r<12;r++)a=U([2e3,r]),t.push(this.monthsShort(a,"")),s.push(this.months(a,"")),n.push(this.months(a,"")),n.push(this.monthsShort(a,""));for(t.sort(e),s.sort(e),n.sort(e),r=0;r<12;r++)t[r]=k(t[r]),s[r]=k(s[r]);for(r=0;r<24;r++)n[r]=k(n[r]);this._monthsRegex=new RegExp("^("+n.join("|")+")","i"),this._monthsShortRegex=this._monthsRegex,this._monthsStrictRegex=new RegExp("^("+s.join("|")+")","i"),this._monthsShortStrictRegex=new RegExp("^("+t.join("|")+")","i")}function Be(e){return Le(e)?366:365}s("Y",0,0,function(){var e=this.year();return e<=9999?de(e,4):"+"+e}),s(0,["YY",2],0,function(){return this.year()%100}),s(0,["YYYY",4],0,"year"),s(0,["YYYYY",5],0,"year"),s(0,["YYYYYY",6,!0],0,"year"),a("year","y"),t("year",1),p("Y",ge),p("YY",o,r),p("YYYY",Y,ke),p("YYYYY",y,_),p("YYYYYY",y,_),D(["YYYYY","YYYYYY"],g),D("YYYY",function(e,a){a[g]=2===e.length?M.parseTwoDigitYear(e):m(e)}),D("YY",function(e,a){a[g]=M.parseTwoDigitYear(e)}),D("Y",function(e,a){a[g]=parseInt(e,10)}),M.parseTwoDigitYear=function(e){return m(e)+(68<m(e)?1900:2e3)};var Ke=Ye("FullYear",!0);function qe(e,a,t,s,n,r,d){var i;return e<100&&0<=e?(i=new Date(e+400,a,t,s,n,r,d),isFinite(i.getFullYear())&&i.setFullYear(e)):i=new Date(e,a,t,s,n,r,d),i}function Ze(e){var a;return e<100&&0<=e?((a=Array.prototype.slice.call(arguments))[0]=e+400,a=new Date(Date.UTC.apply(null,a)),isFinite(a.getUTCFullYear())&&a.setUTCFullYear(e)):a=new Date(Date.UTC.apply(null,arguments)),a}function $e(e,a,t){t=7+a-t;return t-(7+Ze(e,0,t).getUTCDay()-a)%7-1}function Qe(e,a,t,s,n){var r,a=1+7*(a-1)+(7+t-s)%7+$e(e,s,n),t=a<=0?Be(r=e-1)+a:a>Be(e)?(r=e+1,a-Be(e)):(r=e,a);return{year:r,dayOfYear:t}}function Xe(e,a,t){var s,n,r=$e(e.year(),a,t),r=Math.floor((e.dayOfYear()-r-1)/7)+1;return r<1?s=r+ea(n=e.year()-1,a,t):r>ea(e.year(),a,t)?(s=r-ea(e.year(),a,t),n=e.year()+1):(n=e.year(),s=r),{week:s,year:n}}function ea(e,a,t){var s=$e(e,a,t),a=$e(e+1,a,t);return(Be(e)-s+a)/7}s("w",["ww",2],"wo","week"),s("W",["WW",2],"Wo","isoWeek"),a("week","w"),a("isoWeek","W"),t("week",5),t("isoWeek",5),p("w",o),p("ww",o,r),p("W",o),p("WW",o,r),je(["w","ww","W","WW"],function(e,a,t,s){a[s.substr(0,1)]=m(e)});function aa(e,a){return e.slice(a,7).concat(e.slice(0,a))}s("d",0,"do","day"),s("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),s("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),s("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),s("e",0,0,"weekday"),s("E",0,0,"isoWeekday"),a("day","d"),a("weekday","e"),a("isoWeekday","E"),t("day",11),t("weekday",11),t("isoWeekday",11),p("d",o),p("e",o),p("E",o),p("dd",function(e,a){return a.weekdaysMinRegex(e)}),p("ddd",function(e,a){return a.weekdaysShortRegex(e)}),p("dddd",function(e,a){return a.weekdaysRegex(e)}),je(["dd","ddd","dddd"],function(e,a,t,s){s=t._locale.weekdaysParse(e,s,t._strict);null!=s?a.d=s:h(t).invalidWeekday=e}),je(["d","e","E"],function(e,a,t,s){a[s]=m(e)});var ta="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),sa="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),na="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),ra=f,da=f,ia=f;function _a(){function e(e,a){return a.length-e.length}for(var a,t,s,n=[],r=[],d=[],i=[],_=0;_<7;_++)s=U([2e3,1]).day(_),a=k(this.weekdaysMin(s,"")),t=k(this.weekdaysShort(s,"")),s=k(this.weekdays(s,"")),n.push(a),r.push(t),d.push(s),i.push(a),i.push(t),i.push(s);n.sort(e),r.sort(e),d.sort(e),i.sort(e),this._weekdaysRegex=new RegExp("^("+i.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+d.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+r.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+n.join("|")+")","i")}function oa(){return this.hours()%12||12}function ma(e,a){s(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),a)})}function ua(e,a){return a._meridiemParse}s("H",["HH",2],0,"hour"),s("h",["hh",2],0,oa),s("k",["kk",2],0,function(){return this.hours()||24}),s("hmm",0,0,function(){return""+oa.apply(this)+de(this.minutes(),2)}),s("hmmss",0,0,function(){return""+oa.apply(this)+de(this.minutes(),2)+de(this.seconds(),2)}),s("Hmm",0,0,function(){return""+this.hours()+de(this.minutes(),2)}),s("Hmmss",0,0,function(){return""+this.hours()+de(this.minutes(),2)+de(this.seconds(),2)}),ma("a",!0),ma("A",!1),a("hour","h"),t("hour",13),p("a",ua),p("A",ua),p("H",o),p("h",o),p("k",o),p("HH",o,r),p("hh",o,r),p("kk",o,r),p("hmm",c),p("hmmss",L),p("Hmm",c),p("Hmmss",L),D(["H","HH"],w),D(["k","kk"],function(e,a,t){e=m(e);a[w]=24===e?0:e}),D(["a","A"],function(e,a,t){t._isPm=t._locale.isPM(e),t._meridiem=e}),D(["h","hh"],function(e,a,t){a[w]=m(e),h(t).bigHour=!0}),D("hmm",function(e,a,t){var s=e.length-2;a[w]=m(e.substr(0,s)),a[Oe]=m(e.substr(s)),h(t).bigHour=!0}),D("hmmss",function(e,a,t){var s=e.length-4,n=e.length-2;a[w]=m(e.substr(0,s)),a[Oe]=m(e.substr(s,2)),a[We]=m(e.substr(n)),h(t).bigHour=!0}),D("Hmm",function(e,a,t){var s=e.length-2;a[w]=m(e.substr(0,s)),a[Oe]=m(e.substr(s))}),D("Hmmss",function(e,a,t){var s=e.length-4,n=e.length-2;a[w]=m(e.substr(0,s)),a[Oe]=m(e.substr(s,2)),a[We]=m(e.substr(n))});f=Ye("Hours",!0);var la,Ma={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",w:"a week",ww:"%d weeks",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Ne,monthsShort:Je,week:{dow:0,doy:6},weekdays:ta,weekdaysMin:na,weekdaysShort:sa,meridiemParse:/[ap]\.?m?\.?/i},b={},ha={};function ca(e){return e&&e.toLowerCase().replace("_","-")}function La(e){for(var a,t,s,n,r=0;r<e.length;){for(a=(n=ca(e[r]).split("-")).length,t=(t=ca(e[r+1]))?t.split("-"):null;0<a;){if(s=Ya(n.slice(0,a).join("-")))return s;if(t&&t.length>=a&&function(e,a){for(var t=Math.min(e.length,a.length),s=0;s<t;s+=1)if(e[s]!==a[s])return s;return t}(n,t)>=a-1)break;a--}r++}return la}function Ya(a){var e;if(void 0===b[a]&&"undefined"!=typeof module&&module&&module.exports&&null!=a.match("^[^/\\\\]*$"))try{e=la._abbr,require("./locale/"+a),ya(e)}catch(e){b[a]=null}return b[a]}function ya(e,a){return e&&((a=d(a)?pa(e):fa(e,a))?la=a:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+e+" not found. Did you forget to load it?")),la._abbr}function fa(e,a){if(null===a)return delete b[e],null;var t,s=Ma;if(a.abbr=e,null!=b[e])ae("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),s=b[e]._config;else if(null!=a.parentLocale)if(null!=b[a.parentLocale])s=b[a.parentLocale]._config;else{if(null==(t=Ya(a.parentLocale)))return ha[a.parentLocale]||(ha[a.parentLocale]=[]),ha[a.parentLocale].push({name:e,config:a}),null;s=t._config}return b[e]=new ne(se(s,a)),ha[e]&&ha[e].forEach(function(e){fa(e.name,e.config)}),ya(e),b[e]}function pa(e){var a;if(!(e=e&&e._locale&&e._locale._abbr?e._locale._abbr:e))return la;if(!F(e)){if(a=Ya(e))return a;e=[e]}return La(e)}function ka(e){var a=e._a;return a&&-2===h(e).overflow&&(a=a[xe]<0||11<a[xe]?xe:a[Pe]<1||a[Pe]>ze(a[g],a[xe])?Pe:a[w]<0||24<a[w]||24===a[w]&&(0!==a[Oe]||0!==a[We]||0!==a[Ae])?w:a[Oe]<0||59<a[Oe]?Oe:a[We]<0||59<a[We]?We:a[Ae]<0||999<a[Ae]?Ae:-1,h(e)._overflowDayOfYear&&(a<g||Pe<a)&&(a=Pe),h(e)._overflowWeeks&&-1===a&&(a=Ee),h(e)._overflowWeekday&&-1===a&&(a=Fe),h(e).overflow=a),e}var Da=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/,Ta=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d|))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/,ga=/Z|[+-]\d\d(?::?\d\d)?/,wa=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/],["YYYYMM",/\d{6}/,!1],["YYYY",/\d{4}/,!1]],ba=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],Sa=/^\/?Date\((-?\d+)/i,Ha=/^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/,va={UT:0,GMT:0,EDT:-240,EST:-300,CDT:-300,CST:-360,MDT:-360,MST:-420,PDT:-420,PST:-480};function ja(e){var a,t,s,n,r,d,i=e._i,_=Da.exec(i)||Ta.exec(i),i=wa.length,o=ba.length;if(_){for(h(e).iso=!0,a=0,t=i;a<t;a++)if(wa[a][1].exec(_[1])){n=wa[a][0],s=!1!==wa[a][2];break}if(null==n)e._isValid=!1;else{if(_[3]){for(a=0,t=o;a<t;a++)if(ba[a][1].exec(_[3])){r=(_[2]||" ")+ba[a][0];break}if(null==r)return void(e._isValid=!1)}if(s||null==r){if(_[4]){if(!ga.exec(_[4]))return void(e._isValid=!1);d="Z"}e._f=n+(r||"")+(d||""),Aa(e)}else e._isValid=!1}}else e._isValid=!1}function xa(e,a,t,s,n,r){e=[function(e){e=parseInt(e,10);{if(e<=49)return 2e3+e;if(e<=999)return 1900+e}return e}(e),Je.indexOf(a),parseInt(t,10),parseInt(s,10),parseInt(n,10)];return r&&e.push(parseInt(r,10)),e}function Pa(e){var a,t,s,n,r=Ha.exec(e._i.replace(/\([^()]*\)|[\n\t]/g," ").replace(/(\s\s+)/g," ").replace(/^\s\s*/,"").replace(/\s\s*$/,""));r?(a=xa(r[4],r[3],r[2],r[5],r[6],r[7]),t=r[1],s=a,n=e,t&&sa.indexOf(t)!==new Date(s[0],s[1],s[2]).getDay()?(h(n).weekdayMismatch=!0,n._isValid=!1):(e._a=a,e._tzm=(t=r[8],s=r[9],n=r[10],t?va[t]:s?0:60*(((t=parseInt(n,10))-(s=t%100))/100)+s),e._d=Ze.apply(null,e._a),e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),h(e).rfc2822=!0)):e._isValid=!1}function Oa(e,a,t){return null!=e?e:null!=a?a:t}function Wa(e){var a,t,s,n,r,d,i,_,o,m,u,l=[];if(!e._d){for(s=e,n=new Date(M.now()),t=s._useUTC?[n.getUTCFullYear(),n.getUTCMonth(),n.getUTCDate()]:[n.getFullYear(),n.getMonth(),n.getDate()],e._w&&null==e._a[Pe]&&null==e._a[xe]&&(null!=(n=(s=e)._w).GG||null!=n.W||null!=n.E?(_=1,o=4,r=Oa(n.GG,s._a[g],Xe(S(),1,4).year),d=Oa(n.W,1),((i=Oa(n.E,1))<1||7<i)&&(m=!0)):(_=s._locale._week.dow,o=s._locale._week.doy,u=Xe(S(),_,o),r=Oa(n.gg,s._a[g],u.year),d=Oa(n.w,u.week),null!=n.d?((i=n.d)<0||6<i)&&(m=!0):null!=n.e?(i=n.e+_,(n.e<0||6<n.e)&&(m=!0)):i=_),d<1||d>ea(r,_,o)?h(s)._overflowWeeks=!0:null!=m?h(s)._overflowWeekday=!0:(u=Qe(r,d,i,_,o),s._a[g]=u.year,s._dayOfYear=u.dayOfYear)),null!=e._dayOfYear&&(n=Oa(e._a[g],t[g]),(e._dayOfYear>Be(n)||0===e._dayOfYear)&&(h(e)._overflowDayOfYear=!0),m=Ze(n,0,e._dayOfYear),e._a[xe]=m.getUTCMonth(),e._a[Pe]=m.getUTCDate()),a=0;a<3&&null==e._a[a];++a)e._a[a]=l[a]=t[a];for(;a<7;a++)e._a[a]=l[a]=null==e._a[a]?2===a?1:0:e._a[a];24===e._a[w]&&0===e._a[Oe]&&0===e._a[We]&&0===e._a[Ae]&&(e._nextDay=!0,e._a[w]=0),e._d=(e._useUTC?Ze:qe).apply(null,l),r=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[w]=24),e._w&&void 0!==e._w.d&&e._w.d!==r&&(h(e).weekdayMismatch=!0)}}function Aa(e){if(e._f===M.ISO_8601)ja(e);else if(e._f===M.RFC_2822)Pa(e);else{e._a=[],h(e).empty=!0;for(var a,t,s,n,r,d=""+e._i,i=d.length,_=0,o=le(e._f,e._locale).match(ie)||[],m=o.length,u=0;u<m;u++)t=o[u],(a=(d.match(Se(t,e))||[])[0])&&(0<(s=d.substr(0,d.indexOf(a))).length&&h(e).unusedInput.push(s),d=d.slice(d.indexOf(a)+a.length),_+=a.length),me[t]?(a?h(e).empty=!1:h(e).unusedTokens.push(t),s=t,r=e,null!=(n=a)&&l(ve,s)&&ve[s](n,r._a,r,s)):e._strict&&!a&&h(e).unusedTokens.push(t);h(e).charsLeftOver=i-_,0<d.length&&h(e).unusedInput.push(d),e._a[w]<=12&&!0===h(e).bigHour&&0<e._a[w]&&(h(e).bigHour=void 0),h(e).parsedDateParts=e._a.slice(0),h(e).meridiem=e._meridiem,e._a[w]=function(e,a,t){if(null==t)return a;return null!=e.meridiemHour?e.meridiemHour(a,t):null!=e.isPM?((e=e.isPM(t))&&a<12&&(a+=12),a=e||12!==a?a:0):a}(e._locale,e._a[w],e._meridiem),null!==(i=h(e).era)&&(e._a[g]=e._locale.erasConvertYear(i,e._a[g])),Wa(e),ka(e)}}function Ea(e){var a,t,s,n=e._i,r=e._f;if(e._locale=e._locale||pa(e._l),null===n||void 0===r&&""===n)return V({nullInput:!0});if("string"==typeof n&&(e._i=n=e._locale.preparse(n)),Q(n))return new $(ka(n));if(R(n))e._d=n;else if(F(r))!function(e){var a,t,s,n,r,d,i=!1,_=e._f.length;if(0===_)return h(e).invalidFormat=!0,e._d=new Date(NaN);for(n=0;n<_;n++)r=0,d=!1,a=Z({},e),null!=e._useUTC&&(a._useUTC=e._useUTC),a._f=e._f[n],Aa(a),G(a)&&(d=!0),r=(r+=h(a).charsLeftOver)+10*h(a).unusedTokens.length,h(a).score=r,i?r<s&&(s=r,t=a):(null==s||r<s||d)&&(s=r,t=a,d&&(i=!0));I(e,t||a)}(e);else if(r)Aa(e);else if(d(r=(n=e)._i))n._d=new Date(M.now());else R(r)?n._d=new Date(r.valueOf()):"string"==typeof r?(t=n,null!==(a=Sa.exec(t._i))?t._d=new Date(+a[1]):(ja(t),!1===t._isValid&&(delete t._isValid,Pa(t),!1===t._isValid&&(delete t._isValid,t._strict?t._isValid=!1:M.createFromInputFallback(t))))):F(r)?(n._a=C(r.slice(0),function(e){return parseInt(e,10)}),Wa(n)):z(r)?(a=n)._d||(s=void 0===(t=he(a._i)).day?t.date:t.day,a._a=C([t.year,t.month,s,t.hour,t.minute,t.second,t.millisecond],function(e){return e&&parseInt(e,10)}),Wa(a)):J(r)?n._d=new Date(r):M.createFromInputFallback(n);return G(e)||(e._d=null),e}function Fa(e,a,t,s,n){var r={};return!0!==a&&!1!==a||(s=a,a=void 0),!0!==t&&!1!==t||(s=t,t=void 0),(z(e)&&N(e)||F(e)&&0===e.length)&&(e=void 0),r._isAMomentObject=!0,r._useUTC=r._isUTC=n,r._l=t,r._i=e,r._f=a,r._strict=s,(n=new $(ka(Ea(n=r))))._nextDay&&(n.add(1,"d"),n._nextDay=void 0),n}function S(e,a,t,s){return Fa(e,a,t,s,!1)}M.createFromInputFallback=e("value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are discouraged. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.",function(e){e._d=new Date(e._i+(e._useUTC?" UTC":""))}),M.ISO_8601=function(){},M.RFC_2822=function(){};c=e("moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/",function(){var e=S.apply(null,arguments);return this.isValid()&&e.isValid()?e<this?this:e:V()}),L=e("moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/",function(){var e=S.apply(null,arguments);return this.isValid()&&e.isValid()?this<e?this:e:V()});function za(e,a){var t,s;if(!(a=1===a.length&&F(a[0])?a[0]:a).length)return S();for(t=a[0],s=1;s<a.length;++s)a[s].isValid()&&!a[s][e](t)||(t=a[s]);return t}var Na=["year","quarter","month","week","day","hour","minute","second","millisecond"];function Ja(e){var e=he(e),a=e.year||0,t=e.quarter||0,s=e.month||0,n=e.week||e.isoWeek||0,r=e.day||0,d=e.hour||0,i=e.minute||0,_=e.second||0,o=e.millisecond||0;this._isValid=function(e){var a,t,s=!1,n=Na.length;for(a in e)if(l(e,a)&&(-1===T.call(Na,a)||null!=e[a]&&isNaN(e[a])))return!1;for(t=0;t<n;++t)if(e[Na[t]]){if(s)return!1;parseFloat(e[Na[t]])!==m(e[Na[t]])&&(s=!0)}return!0}(e),this._milliseconds=+o+1e3*_+6e4*i+1e3*d*60*60,this._days=+r+7*n,this._months=+s+3*t+12*a,this._data={},this._locale=pa(),this._bubble()}function Ra(e){return e instanceof Ja}function Ca(e){return e<0?-1*Math.round(-1*e):Math.round(e)}function Ia(e,t){s(e,0,0,function(){var e=this.utcOffset(),a="+";return e<0&&(e=-e,a="-"),a+de(~~(e/60),2)+t+de(~~e%60,2)})}Ia("Z",":"),Ia("ZZ",""),p("Z",be),p("ZZ",be),D(["Z","ZZ"],function(e,a,t){t._useUTC=!0,t._tzm=Ga(be,e)});var Ua=/([\+\-]|\d\d)/gi;function Ga(e,a){var a=(a||"").match(e);return null===a?null:0===(a=60*(e=((a[a.length-1]||[])+"").match(Ua)||["-",0,0])[1]+m(e[2]))?0:"+"===e[0]?a:-a}function Va(e,a){var t;return a._isUTC?(a=a.clone(),t=(Q(e)||R(e)?e:S(e)).valueOf()-a.valueOf(),a._d.setTime(a._d.valueOf()+t),M.updateOffset(a,!1),a):S(e).local()}function Ba(e){return-Math.round(e._d.getTimezoneOffset())}function Ka(){return!!this.isValid()&&(this._isUTC&&0===this._offset)}M.updateOffset=function(){};var qa=/^(-|\+)?(?:(\d*)[. ])?(\d+):(\d+)(?::(\d+)(\.\d*)?)?$/,Za=/^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;function $a(e,a){var t,s=e,n=null;return Ra(e)?s={ms:e._milliseconds,d:e._days,M:e._months}:J(e)||!isNaN(+e)?(s={},a?s[a]=+e:s.milliseconds=+e):(n=qa.exec(e))?(t="-"===n[1]?-1:1,s={y:0,d:m(n[Pe])*t,h:m(n[w])*t,m:m(n[Oe])*t,s:m(n[We])*t,ms:m(Ca(1e3*n[Ae]))*t}):(n=Za.exec(e))?(t="-"===n[1]?-1:1,s={y:Qa(n[2],t),M:Qa(n[3],t),w:Qa(n[4],t),d:Qa(n[5],t),h:Qa(n[6],t),m:Qa(n[7],t),s:Qa(n[8],t)}):null==s?s={}:"object"==typeof s&&("from"in s||"to"in s)&&(a=function(e,a){var t;if(!e.isValid()||!a.isValid())return{milliseconds:0,months:0};a=Va(a,e),e.isBefore(a)?t=Xa(e,a):((t=Xa(a,e)).milliseconds=-t.milliseconds,t.months=-t.months);return t}(S(s.from),S(s.to)),(s={}).ms=a.milliseconds,s.M=a.months),n=new Ja(s),Ra(e)&&l(e,"_locale")&&(n._locale=e._locale),Ra(e)&&l(e,"_isValid")&&(n._isValid=e._isValid),n}function Qa(e,a){e=e&&parseFloat(e.replace(",","."));return(isNaN(e)?0:e)*a}function Xa(e,a){var t={};return t.months=a.month()-e.month()+12*(a.year()-e.year()),e.clone().add(t.months,"M").isAfter(a)&&--t.months,t.milliseconds=+a-+e.clone().add(t.months,"M"),t}function et(s,n){return function(e,a){var t;return null===a||isNaN(+a)||(ae(n,"moment()."+n+"(period, number) is deprecated. Please use moment()."+n+"(number, period). See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info."),t=e,e=a,a=t),at(this,$a(e,a),s),this}}function at(e,a,t,s){var n=a._milliseconds,r=Ca(a._days),a=Ca(a._months);e.isValid()&&(s=null==s||s,a&&Ue(e,ye(e,"Month")+a*t),r&&fe(e,"Date",ye(e,"Date")+r*t),n&&e._d.setTime(e._d.valueOf()+n*t),s&&M.updateOffset(e,r||a))}$a.fn=Ja.prototype,$a.invalid=function(){return $a(NaN)};Ne=et(1,"add"),ta=et(-1,"subtract");function tt(e){return"string"==typeof e||e instanceof String}function st(e){return Q(e)||R(e)||tt(e)||J(e)||function(a){var e=F(a),t=!1;e&&(t=0===a.filter(function(e){return!J(e)&&tt(a)}).length);return e&&t}(e)||function(e){var a,t,s=z(e)&&!N(e),n=!1,r=["years","year","y","months","month","M","days","day","d","dates","date","D","hours","hour","h","minutes","minute","m","seconds","second","s","milliseconds","millisecond","ms"],d=r.length;for(a=0;a<d;a+=1)t=r[a],n=n||l(e,t);return s&&n}(e)||null==e}function nt(e,a){if(e.date()<a.date())return-nt(a,e);var t=12*(a.year()-e.year())+(a.month()-e.month()),s=e.clone().add(t,"months"),a=a-s<0?(a-s)/(s-e.clone().add(t-1,"months")):(a-s)/(e.clone().add(1+t,"months")-s);return-(t+a)||0}function rt(e){return void 0===e?this._locale._abbr:(null!=(e=pa(e))&&(this._locale=e),this)}M.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",M.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";na=e("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(e){return void 0===e?this.localeData():this.locale(e)});function dt(){return this._locale}var it=126227808e5;function _t(e,a){return(e%a+a)%a}function ot(e,a,t){return e<100&&0<=e?new Date(e+400,a,t)-it:new Date(e,a,t).valueOf()}function mt(e,a,t){return e<100&&0<=e?Date.UTC(e+400,a,t)-it:Date.UTC(e,a,t)}function ut(e,a){return a.erasAbbrRegex(e)}function lt(){for(var e=[],a=[],t=[],s=[],n=this.eras(),r=0,d=n.length;r<d;++r)a.push(k(n[r].name)),e.push(k(n[r].abbr)),t.push(k(n[r].narrow)),s.push(k(n[r].name)),s.push(k(n[r].abbr)),s.push(k(n[r].narrow));this._erasRegex=new RegExp("^("+s.join("|")+")","i"),this._erasNameRegex=new RegExp("^("+a.join("|")+")","i"),this._erasAbbrRegex=new RegExp("^("+e.join("|")+")","i"),this._erasNarrowRegex=new RegExp("^("+t.join("|")+")","i")}function Mt(e,a){s(0,[e,e.length],0,a)}function ht(e,a,t,s,n){var r;return null==e?Xe(this,s,n).year:(r=ea(e,s,n),function(e,a,t,s,n){e=Qe(e,a,t,s,n),a=Ze(e.year,0,e.dayOfYear);return this.year(a.getUTCFullYear()),this.month(a.getUTCMonth()),this.date(a.getUTCDate()),this}.call(this,e,a=r<a?r:a,t,s,n))}s("N",0,0,"eraAbbr"),s("NN",0,0,"eraAbbr"),s("NNN",0,0,"eraAbbr"),s("NNNN",0,0,"eraName"),s("NNNNN",0,0,"eraNarrow"),s("y",["y",1],"yo","eraYear"),s("y",["yy",2],0,"eraYear"),s("y",["yyy",3],0,"eraYear"),s("y",["yyyy",4],0,"eraYear"),p("N",ut),p("NN",ut),p("NNN",ut),p("NNNN",function(e,a){return a.erasNameRegex(e)}),p("NNNNN",function(e,a){return a.erasNarrowRegex(e)}),D(["N","NN","NNN","NNNN","NNNNN"],function(e,a,t,s){s=t._locale.erasParse(e,s,t._strict);s?h(t).era=s:h(t).invalidEra=e}),p("y",Te),p("yy",Te),p("yyy",Te),p("yyyy",Te),p("yo",function(e,a){return a._eraYearOrdinalRegex||Te}),D(["y","yy","yyy","yyyy"],g),D(["yo"],function(e,a,t,s){var n;t._locale._eraYearOrdinalRegex&&(n=e.match(t._locale._eraYearOrdinalRegex)),t._locale.eraYearOrdinalParse?a[g]=t._locale.eraYearOrdinalParse(e,n):a[g]=parseInt(e,10)}),s(0,["gg",2],0,function(){return this.weekYear()%100}),s(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Mt("gggg","weekYear"),Mt("ggggg","weekYear"),Mt("GGGG","isoWeekYear"),Mt("GGGGG","isoWeekYear"),a("weekYear","gg"),a("isoWeekYear","GG"),t("weekYear",1),t("isoWeekYear",1),p("G",ge),p("g",ge),p("GG",o,r),p("gg",o,r),p("GGGG",Y,ke),p("gggg",Y,ke),p("GGGGG",y,_),p("ggggg",y,_),je(["gggg","ggggg","GGGG","GGGGG"],function(e,a,t,s){a[s.substr(0,2)]=m(e)}),je(["gg","GG"],function(e,a,t,s){a[s]=M.parseTwoDigitYear(e)}),s("Q",0,"Qo","quarter"),a("quarter","Q"),t("quarter",7),p("Q",n),D("Q",function(e,a){a[xe]=3*(m(e)-1)}),s("D",["DD",2],"Do","date"),a("date","D"),t("date",9),p("D",o),p("DD",o,r),p("Do",function(e,a){return e?a._dayOfMonthOrdinalParse||a._ordinalParse:a._dayOfMonthOrdinalParseLenient}),D(["D","DD"],Pe),D("Do",function(e,a){a[Pe]=m(e.match(o)[0])});Y=Ye("Date",!0);s("DDD",["DDDD",3],"DDDo","dayOfYear"),a("dayOfYear","DDD"),t("dayOfYear",4),p("DDD",De),p("DDDD",pe),D(["DDD","DDDD"],function(e,a,t){t._dayOfYear=m(e)}),s("m",["mm",2],0,"minute"),a("minute","m"),t("minute",14),p("m",o),p("mm",o,r),D(["m","mm"],Oe);var ct,ke=Ye("Minutes",!1),y=(s("s",["ss",2],0,"second"),a("second","s"),t("second",15),p("s",o),p("ss",o,r),D(["s","ss"],We),Ye("Seconds",!1));for(s("S",0,0,function(){return~~(this.millisecond()/100)}),s(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),s(0,["SSS",3],0,"millisecond"),s(0,["SSSS",4],0,function(){return 10*this.millisecond()}),s(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),s(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),s(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),s(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),s(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),a("millisecond","ms"),t("millisecond",16),p("S",De,n),p("SS",De,r),p("SSS",De,pe),ct="SSSS";ct.length<=9;ct+="S")p(ct,Te);function Lt(e,a){a[Ae]=m(1e3*("0."+e))}for(ct="S";ct.length<=9;ct+="S")D(ct,Lt);_=Ye("Milliseconds",!1),s("z",0,0,"zoneAbbr"),s("zz",0,0,"zoneName");n=$.prototype;function Yt(e){return e}n.add=Ne,n.calendar=function(e,a){1===arguments.length&&(arguments[0]?st(arguments[0])?(e=arguments[0],a=void 0):function(e){for(var a=z(e)&&!N(e),t=!1,s=["sameDay","nextDay","lastDay","nextWeek","lastWeek","sameElse"],n=0;n<s.length;n+=1)t=t||l(e,s[n]);return a&&t}(arguments[0])&&(a=arguments[0],e=void 0):a=e=void 0);var e=e||S(),t=Va(e,this).startOf("day"),t=M.calendarFormat(this,t)||"sameElse",a=a&&(te(a[t])?a[t].call(this,e):a[t]);return this.format(a||this.localeData().calendar(t,this,S(e)))},n.clone=function(){return new $(this)},n.diff=function(e,a,t){var s,n,r;if(!this.isValid())return NaN;if(!(s=Va(e,this)).isValid())return NaN;switch(n=6e4*(s.utcOffset()-this.utcOffset()),a=i(a)){case"year":r=nt(this,s)/12;break;case"month":r=nt(this,s);break;case"quarter":r=nt(this,s)/3;break;case"second":r=(this-s)/1e3;break;case"minute":r=(this-s)/6e4;break;case"hour":r=(this-s)/36e5;break;case"day":r=(this-s-n)/864e5;break;case"week":r=(this-s-n)/6048e5;break;default:r=this-s}return t?r:u(r)},n.endOf=function(e){var a,t;if(void 0===(e=i(e))||"millisecond"===e||!this.isValid())return this;switch(t=this._isUTC?mt:ot,e){case"year":a=t(this.year()+1,0,1)-1;break;case"quarter":a=t(this.year(),this.month()-this.month()%3+3,1)-1;break;case"month":a=t(this.year(),this.month()+1,1)-1;break;case"week":a=t(this.year(),this.month(),this.date()-this.weekday()+7)-1;break;case"isoWeek":a=t(this.year(),this.month(),this.date()-(this.isoWeekday()-1)+7)-1;break;case"day":case"date":a=t(this.year(),this.month(),this.date()+1)-1;break;case"hour":a=this._d.valueOf(),a+=36e5-_t(a+(this._isUTC?0:6e4*this.utcOffset()),36e5)-1;break;case"minute":a=this._d.valueOf(),a+=6e4-_t(a,6e4)-1;break;case"second":a=this._d.valueOf(),a+=1e3-_t(a,1e3)-1;break}return this._d.setTime(a),M.updateOffset(this,!0),this},n.format=function(e){return e=e||(this.isUtc()?M.defaultFormatUtc:M.defaultFormat),e=ue(this,e),this.localeData().postformat(e)},n.from=function(e,a){return this.isValid()&&(Q(e)&&e.isValid()||S(e).isValid())?$a({to:this,from:e}).locale(this.locale()).humanize(!a):this.localeData().invalidDate()},n.fromNow=function(e){return this.from(S(),e)},n.to=function(e,a){return this.isValid()&&(Q(e)&&e.isValid()||S(e).isValid())?$a({from:this,to:e}).locale(this.locale()).humanize(!a):this.localeData().invalidDate()},n.toNow=function(e){return this.to(S(),e)},n.get=function(e){return te(this[e=i(e)])?this[e]():this},n.invalidAt=function(){return h(this).overflow},n.isAfter=function(e,a){return e=Q(e)?e:S(e),!(!this.isValid()||!e.isValid())&&("millisecond"===(a=i(a)||"millisecond")?this.valueOf()>e.valueOf():e.valueOf()<this.clone().startOf(a).valueOf())},n.isBefore=function(e,a){return e=Q(e)?e:S(e),!(!this.isValid()||!e.isValid())&&("millisecond"===(a=i(a)||"millisecond")?this.valueOf()<e.valueOf():this.clone().endOf(a).valueOf()<e.valueOf())},n.isBetween=function(e,a,t,s){return e=Q(e)?e:S(e),a=Q(a)?a:S(a),!!(this.isValid()&&e.isValid()&&a.isValid())&&(("("===(s=s||"()")[0]?this.isAfter(e,t):!this.isBefore(e,t))&&(")"===s[1]?this.isBefore(a,t):!this.isAfter(a,t)))},n.isSame=function(e,a){var e=Q(e)?e:S(e);return!(!this.isValid()||!e.isValid())&&("millisecond"===(a=i(a)||"millisecond")?this.valueOf()===e.valueOf():(e=e.valueOf(),this.clone().startOf(a).valueOf()<=e&&e<=this.clone().endOf(a).valueOf()))},n.isSameOrAfter=function(e,a){return this.isSame(e,a)||this.isAfter(e,a)},n.isSameOrBefore=function(e,a){return this.isSame(e,a)||this.isBefore(e,a)},n.isValid=function(){return G(this)},n.lang=na,n.locale=rt,n.localeData=dt,n.max=L,n.min=c,n.parsingFlags=function(){return I({},h(this))},n.set=function(e,a){if("object"==typeof e)for(var t=function(e){var a,t=[];for(a in e)l(e,a)&&t.push({unit:a,priority:ce[a]});return t.sort(function(e,a){return e.priority-a.priority}),t}(e=he(e)),s=t.length,n=0;n<s;n++)this[t[n].unit](e[t[n].unit]);else if(te(this[e=i(e)]))return this[e](a);return this},n.startOf=function(e){var a,t;if(void 0===(e=i(e))||"millisecond"===e||!this.isValid())return this;switch(t=this._isUTC?mt:ot,e){case"year":a=t(this.year(),0,1);break;case"quarter":a=t(this.year(),this.month()-this.month()%3,1);break;case"month":a=t(this.year(),this.month(),1);break;case"week":a=t(this.year(),this.month(),this.date()-this.weekday());break;case"isoWeek":a=t(this.year(),this.month(),this.date()-(this.isoWeekday()-1));break;case"day":case"date":a=t(this.year(),this.month(),this.date());break;case"hour":a=this._d.valueOf(),a-=_t(a+(this._isUTC?0:6e4*this.utcOffset()),36e5);break;case"minute":a=this._d.valueOf(),a-=_t(a,6e4);break;case"second":a=this._d.valueOf(),a-=_t(a,1e3);break}return this._d.setTime(a),M.updateOffset(this,!0),this},n.subtract=ta,n.toArray=function(){var e=this;return[e.year(),e.month(),e.date(),e.hour(),e.minute(),e.second(),e.millisecond()]},n.toObject=function(){var e=this;return{years:e.year(),months:e.month(),date:e.date(),hours:e.hours(),minutes:e.minutes(),seconds:e.seconds(),milliseconds:e.milliseconds()}},n.toDate=function(){return new Date(this.valueOf())},n.toISOString=function(e){if(!this.isValid())return null;var a=(e=!0!==e)?this.clone().utc():this;return a.year()<0||9999<a.year()?ue(a,e?"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYYYY-MM-DD[T]HH:mm:ss.SSSZ"):te(Date.prototype.toISOString)?e?this.toDate().toISOString():new Date(this.valueOf()+60*this.utcOffset()*1e3).toISOString().replace("Z",ue(a,"Z")):ue(a,e?"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYY-MM-DD[T]HH:mm:ss.SSSZ")},n.inspect=function(){if(!this.isValid())return"moment.invalid(/* "+this._i+" */)";var e,a="moment",t="";return this.isLocal()||(a=0===this.utcOffset()?"moment.utc":"moment.parseZone",t="Z"),a="["+a+'("]',e=0<=this.year()&&this.year()<=9999?"YYYY":"YYYYYY",this.format(a+e+"-MM-DD[T]HH:mm:ss.SSS"+(t+'[")]'))},"undefined"!=typeof Symbol&&null!=Symbol.for&&(n[Symbol.for("nodejs.util.inspect.custom")]=function(){return"Moment<"+this.format()+">"}),n.toJSON=function(){return this.isValid()?this.toISOString():null},n.toString=function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},n.unix=function(){return Math.floor(this.valueOf()/1e3)},n.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},n.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},n.eraName=function(){for(var e,a=this.localeData().eras(),t=0,s=a.length;t<s;++t){if(e=this.clone().startOf("day").valueOf(),a[t].since<=e&&e<=a[t].until)return a[t].name;if(a[t].until<=e&&e<=a[t].since)return a[t].name}return""},n.eraNarrow=function(){for(var e,a=this.localeData().eras(),t=0,s=a.length;t<s;++t){if(e=this.clone().startOf("day").valueOf(),a[t].since<=e&&e<=a[t].until)return a[t].narrow;if(a[t].until<=e&&e<=a[t].since)return a[t].narrow}return""},n.eraAbbr=function(){for(var e,a=this.localeData().eras(),t=0,s=a.length;t<s;++t){if(e=this.clone().startOf("day").valueOf(),a[t].since<=e&&e<=a[t].until)return a[t].abbr;if(a[t].until<=e&&e<=a[t].since)return a[t].abbr}return""},n.eraYear=function(){for(var e,a,t=this.localeData().eras(),s=0,n=t.length;s<n;++s)if(e=t[s].since<=t[s].until?1:-1,a=this.clone().startOf("day").valueOf(),t[s].since<=a&&a<=t[s].until||t[s].until<=a&&a<=t[s].since)return(this.year()-M(t[s].since).year())*e+t[s].offset;return this.year()},n.year=Ke,n.isLeapYear=function(){return Le(this.year())},n.weekYear=function(e){return ht.call(this,e,this.week(),this.weekday(),this.localeData()._week.dow,this.localeData()._week.doy)},n.isoWeekYear=function(e){return ht.call(this,e,this.isoWeek(),this.isoWeekday(),1,4)},n.quarter=n.quarters=function(e){return null==e?Math.ceil((this.month()+1)/3):this.month(3*(e-1)+this.month()%3)},n.month=Ge,n.daysInMonth=function(){return ze(this.year(),this.month())},n.week=n.weeks=function(e){var a=this.localeData().week(this);return null==e?a:this.add(7*(e-a),"d")},n.isoWeek=n.isoWeeks=function(e){var a=Xe(this,1,4).week;return null==e?a:this.add(7*(e-a),"d")},n.weeksInYear=function(){var e=this.localeData()._week;return ea(this.year(),e.dow,e.doy)},n.weeksInWeekYear=function(){var e=this.localeData()._week;return ea(this.weekYear(),e.dow,e.doy)},n.isoWeeksInYear=function(){return ea(this.year(),1,4)},n.isoWeeksInISOWeekYear=function(){return ea(this.isoWeekYear(),1,4)},n.date=Y,n.day=n.days=function(e){if(!this.isValid())return null!=e?this:NaN;var a,t,s=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=e?(a=e,t=this.localeData(),e="string"!=typeof a?a:isNaN(a)?"number"==typeof(a=t.weekdaysParse(a))?a:null:parseInt(a,10),this.add(e-s,"d")):s},n.weekday=function(e){if(!this.isValid())return null!=e?this:NaN;var a=(this.day()+7-this.localeData()._week.dow)%7;return null==e?a:this.add(e-a,"d")},n.isoWeekday=function(e){return this.isValid()?null!=e?(a=e,t=this.localeData(),t="string"==typeof a?t.weekdaysParse(a)%7||7:isNaN(a)?null:a,this.day(this.day()%7?t:t-7)):this.day()||7:null!=e?this:NaN;var a,t},n.dayOfYear=function(e){var a=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==e?a:this.add(e-a,"d")},n.hour=n.hours=f,n.minute=n.minutes=ke,n.second=n.seconds=y,n.millisecond=n.milliseconds=_,n.utcOffset=function(e,a,t){var s,n=this._offset||0;if(!this.isValid())return null!=e?this:NaN;if(null==e)return this._isUTC?n:Ba(this);if("string"==typeof e){if(null===(e=Ga(be,e)))return this}else Math.abs(e)<16&&!t&&(e*=60);return!this._isUTC&&a&&(s=Ba(this)),this._offset=e,this._isUTC=!0,null!=s&&this.add(s,"m"),n!==e&&(!a||this._changeInProgress?at(this,$a(e-n,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,M.updateOffset(this,!0),this._changeInProgress=null)),this},n.utc=function(e){return this.utcOffset(0,e)},n.local=function(e){return this._isUTC&&(this.utcOffset(0,e),this._isUTC=!1,e&&this.subtract(Ba(this),"m")),this},n.parseZone=function(){var e;return null!=this._tzm?this.utcOffset(this._tzm,!1,!0):"string"==typeof this._i&&(null!=(e=Ga(we,this._i))?this.utcOffset(e):this.utcOffset(0,!0)),this},n.hasAlignedHourOffset=function(e){return!!this.isValid()&&(e=e?S(e).utcOffset():0,(this.utcOffset()-e)%60==0)},n.isDST=function(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},n.isLocal=function(){return!!this.isValid()&&!this._isUTC},n.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},n.isUtc=Ka,n.isUTC=Ka,n.zoneAbbr=function(){return this._isUTC?"UTC":""},n.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},n.dates=e("dates accessor is deprecated. Use date instead.",Y),n.months=e("months accessor is deprecated. Use month instead",Ge),n.years=e("years accessor is deprecated. Use year instead",Ke),n.zone=e("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(e,a){return null!=e?(this.utcOffset(e="string"!=typeof e?-e:e,a),this):-this.utcOffset()}),n.isDSTShifted=e("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!d(this._isDSTShifted))return this._isDSTShifted;var e,a={};return Z(a,this),(a=Ea(a))._a?(e=(a._isUTC?U:S)(a._a),this._isDSTShifted=this.isValid()&&0<function(e,a,t){for(var s=Math.min(e.length,a.length),n=Math.abs(e.length-a.length),r=0,d=0;d<s;d++)(t&&e[d]!==a[d]||!t&&m(e[d])!==m(a[d]))&&r++;return r+n}(a._a,e.toArray())):this._isDSTShifted=!1,this._isDSTShifted});r=ne.prototype;function yt(e,a,t,s){var n=pa(),s=U().set(s,a);return n[t](s,e)}function ft(e,a,t){if(J(e)&&(a=e,e=void 0),e=e||"",null!=a)return yt(e,a,t,"month");for(var s=[],n=0;n<12;n++)s[n]=yt(e,n,t,"month");return s}function pt(e,a,t,s){a=("boolean"==typeof e?J(a)&&(t=a,a=void 0):(a=e,e=!1,J(t=a)&&(t=a,a=void 0)),a||"");var n,r=pa(),d=e?r._week.dow:0,i=[];if(null!=t)return yt(a,(t+d)%7,s,"day");for(n=0;n<7;n++)i[n]=yt(a,(n+d)%7,s,"day");return i}r.calendar=function(e,a,t){return te(e=this._calendar[e]||this._calendar.sameElse)?e.call(a,t):e},r.longDateFormat=function(e){var a=this._longDateFormat[e],t=this._longDateFormat[e.toUpperCase()];return a||!t?a:(this._longDateFormat[e]=t.match(ie).map(function(e){return"MMMM"===e||"MM"===e||"DD"===e||"dddd"===e?e.slice(1):e}).join(""),this._longDateFormat[e])},r.invalidDate=function(){return this._invalidDate},r.ordinal=function(e){return this._ordinal.replace("%d",e)},r.preparse=Yt,r.postformat=Yt,r.relativeTime=function(e,a,t,s){var n=this._relativeTime[t];return te(n)?n(e,a,t,s):n.replace(/%d/i,e)},r.pastFuture=function(e,a){return te(e=this._relativeTime[0<e?"future":"past"])?e(a):e.replace(/%s/i,a)},r.set=function(e){var a,t;for(t in e)l(e,t)&&(te(a=e[t])?this[t]=a:this["_"+t]=a);this._config=e,this._dayOfMonthOrdinalParseLenient=new RegExp((this._dayOfMonthOrdinalParse.source||this._ordinalParse.source)+"|"+/\d{1,2}/.source)},r.eras=function(e,a){for(var t,s=this._eras||pa("en")._eras,n=0,r=s.length;n<r;++n){switch(typeof s[n].since){case"string":t=M(s[n].since).startOf("day"),s[n].since=t.valueOf();break}switch(typeof s[n].until){case"undefined":s[n].until=1/0;break;case"string":t=M(s[n].until).startOf("day").valueOf(),s[n].until=t.valueOf();break}}return s},r.erasParse=function(e,a,t){var s,n,r,d,i,_=this.eras();for(e=e.toUpperCase(),s=0,n=_.length;s<n;++s)if(r=_[s].name.toUpperCase(),d=_[s].abbr.toUpperCase(),i=_[s].narrow.toUpperCase(),t)switch(a){case"N":case"NN":case"NNN":if(d===e)return _[s];break;case"NNNN":if(r===e)return _[s];break;case"NNNNN":if(i===e)return _[s];break}else if(0<=[r,d,i].indexOf(e))return _[s]},r.erasConvertYear=function(e,a){var t=e.since<=e.until?1:-1;return void 0===a?M(e.since).year():M(e.since).year()+(a-e.offset)*t},r.erasAbbrRegex=function(e){return l(this,"_erasAbbrRegex")||lt.call(this),e?this._erasAbbrRegex:this._erasRegex},r.erasNameRegex=function(e){return l(this,"_erasNameRegex")||lt.call(this),e?this._erasNameRegex:this._erasRegex},r.erasNarrowRegex=function(e){return l(this,"_erasNarrowRegex")||lt.call(this),e?this._erasNarrowRegex:this._erasRegex},r.months=function(e,a){return e?(F(this._months)?this._months:this._months[(this._months.isFormat||Re).test(a)?"format":"standalone"])[e.month()]:F(this._months)?this._months:this._months.standalone},r.monthsShort=function(e,a){return e?(F(this._monthsShort)?this._monthsShort:this._monthsShort[Re.test(a)?"format":"standalone"])[e.month()]:F(this._monthsShort)?this._monthsShort:this._monthsShort.standalone},r.monthsParse=function(e,a,t){var s,n;if(this._monthsParseExact)return function(e,a,t){var s,n,r,e=e.toLocaleLowerCase();if(!this._monthsParse)for(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[],s=0;s<12;++s)r=U([2e3,s]),this._shortMonthsParse[s]=this.monthsShort(r,"").toLocaleLowerCase(),this._longMonthsParse[s]=this.months(r,"").toLocaleLowerCase();return t?"MMM"===a?-1!==(n=T.call(this._shortMonthsParse,e))?n:null:-1!==(n=T.call(this._longMonthsParse,e))?n:null:"MMM"===a?-1!==(n=T.call(this._shortMonthsParse,e))||-1!==(n=T.call(this._longMonthsParse,e))?n:null:-1!==(n=T.call(this._longMonthsParse,e))||-1!==(n=T.call(this._shortMonthsParse,e))?n:null}.call(this,e,a,t);for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),s=0;s<12;s++){if(n=U([2e3,s]),t&&!this._longMonthsParse[s]&&(this._longMonthsParse[s]=new RegExp("^"+this.months(n,"").replace(".","")+"$","i"),this._shortMonthsParse[s]=new RegExp("^"+this.monthsShort(n,"").replace(".","")+"$","i")),t||this._monthsParse[s]||(n="^"+this.months(n,"")+"|^"+this.monthsShort(n,""),this._monthsParse[s]=new RegExp(n.replace(".",""),"i")),t&&"MMMM"===a&&this._longMonthsParse[s].test(e))return s;if(t&&"MMM"===a&&this._shortMonthsParse[s].test(e))return s;if(!t&&this._monthsParse[s].test(e))return s}},r.monthsRegex=function(e){return this._monthsParseExact?(l(this,"_monthsRegex")||Ve.call(this),e?this._monthsStrictRegex:this._monthsRegex):(l(this,"_monthsRegex")||(this._monthsRegex=Ie),this._monthsStrictRegex&&e?this._monthsStrictRegex:this._monthsRegex)},r.monthsShortRegex=function(e){return this._monthsParseExact?(l(this,"_monthsRegex")||Ve.call(this),e?this._monthsShortStrictRegex:this._monthsShortRegex):(l(this,"_monthsShortRegex")||(this._monthsShortRegex=Ce),this._monthsShortStrictRegex&&e?this._monthsShortStrictRegex:this._monthsShortRegex)},r.week=function(e){return Xe(e,this._week.dow,this._week.doy).week},r.firstDayOfYear=function(){return this._week.doy},r.firstDayOfWeek=function(){return this._week.dow},r.weekdays=function(e,a){return a=F(this._weekdays)?this._weekdays:this._weekdays[e&&!0!==e&&this._weekdays.isFormat.test(a)?"format":"standalone"],!0===e?aa(a,this._week.dow):e?a[e.day()]:a},r.weekdaysMin=function(e){return!0===e?aa(this._weekdaysMin,this._week.dow):e?this._weekdaysMin[e.day()]:this._weekdaysMin},r.weekdaysShort=function(e){return!0===e?aa(this._weekdaysShort,this._week.dow):e?this._weekdaysShort[e.day()]:this._weekdaysShort},r.weekdaysParse=function(e,a,t){var s,n;if(this._weekdaysParseExact)return function(e,a,t){var s,n,r,e=e.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],s=0;s<7;++s)r=U([2e3,1]).day(s),this._minWeekdaysParse[s]=this.weekdaysMin(r,"").toLocaleLowerCase(),this._shortWeekdaysParse[s]=this.weekdaysShort(r,"").toLocaleLowerCase(),this._weekdaysParse[s]=this.weekdays(r,"").toLocaleLowerCase();return t?"dddd"===a?-1!==(n=T.call(this._weekdaysParse,e))?n:null:"ddd"===a?-1!==(n=T.call(this._shortWeekdaysParse,e))?n:null:-1!==(n=T.call(this._minWeekdaysParse,e))?n:null:"dddd"===a?-1!==(n=T.call(this._weekdaysParse,e))||-1!==(n=T.call(this._shortWeekdaysParse,e))||-1!==(n=T.call(this._minWeekdaysParse,e))?n:null:"ddd"===a?-1!==(n=T.call(this._shortWeekdaysParse,e))||-1!==(n=T.call(this._weekdaysParse,e))||-1!==(n=T.call(this._minWeekdaysParse,e))?n:null:-1!==(n=T.call(this._minWeekdaysParse,e))||-1!==(n=T.call(this._weekdaysParse,e))||-1!==(n=T.call(this._shortWeekdaysParse,e))?n:null}.call(this,e,a,t);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),s=0;s<7;s++){if(n=U([2e3,1]).day(s),t&&!this._fullWeekdaysParse[s]&&(this._fullWeekdaysParse[s]=new RegExp("^"+this.weekdays(n,"").replace(".","\\.?")+"$","i"),this._shortWeekdaysParse[s]=new RegExp("^"+this.weekdaysShort(n,"").replace(".","\\.?")+"$","i"),this._minWeekdaysParse[s]=new RegExp("^"+this.weekdaysMin(n,"").replace(".","\\.?")+"$","i")),this._weekdaysParse[s]||(n="^"+this.weekdays(n,"")+"|^"+this.weekdaysShort(n,"")+"|^"+this.weekdaysMin(n,""),this._weekdaysParse[s]=new RegExp(n.replace(".",""),"i")),t&&"dddd"===a&&this._fullWeekdaysParse[s].test(e))return s;if(t&&"ddd"===a&&this._shortWeekdaysParse[s].test(e))return s;if(t&&"dd"===a&&this._minWeekdaysParse[s].test(e))return s;if(!t&&this._weekdaysParse[s].test(e))return s}},r.weekdaysRegex=function(e){return this._weekdaysParseExact?(l(this,"_weekdaysRegex")||_a.call(this),e?this._weekdaysStrictRegex:this._weekdaysRegex):(l(this,"_weekdaysRegex")||(this._weekdaysRegex=ra),this._weekdaysStrictRegex&&e?this._weekdaysStrictRegex:this._weekdaysRegex)},r.weekdaysShortRegex=function(e){return this._weekdaysParseExact?(l(this,"_weekdaysRegex")||_a.call(this),e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(l(this,"_weekdaysShortRegex")||(this._weekdaysShortRegex=da),this._weekdaysShortStrictRegex&&e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)},r.weekdaysMinRegex=function(e){return this._weekdaysParseExact?(l(this,"_weekdaysRegex")||_a.call(this),e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(l(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=ia),this._weekdaysMinStrictRegex&&e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)},r.isPM=function(e){return"p"===(e+"").toLowerCase().charAt(0)},r.meridiem=function(e,a,t){return 11<e?t?"pm":"PM":t?"am":"AM"},ya("en",{eras:[{since:"0001-01-01",until:1/0,offset:1,name:"Anno Domini",narrow:"AD",abbr:"AD"},{since:"0000-12-31",until:-1/0,offset:1,name:"Before Christ",narrow:"BC",abbr:"BC"}],dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(e){var a=e%10;return e+(1===m(e%100/10)?"th":1==a?"st":2==a?"nd":3==a?"rd":"th")}}),M.lang=e("moment.lang is deprecated. Use moment.locale instead.",ya),M.langData=e("moment.langData is deprecated. Use moment.localeData instead.",pa);var kt=Math.abs;function Dt(e,a,t,s){a=$a(a,t);return e._milliseconds+=s*a._milliseconds,e._days+=s*a._days,e._months+=s*a._months,e._bubble()}function Tt(e){return e<0?Math.floor(e):Math.ceil(e)}function gt(e){return 4800*e/146097}function wt(e){return 146097*e/4800}function bt(e){return function(){return this.as(e)}}De=bt("ms"),pe=bt("s"),Ne=bt("m"),L=bt("h"),c=bt("d"),ta=bt("w"),f=bt("M"),ke=bt("Q"),y=bt("y");function St(e){return function(){return this.isValid()?this._data[e]:NaN}}var _=St("milliseconds"),Y=St("seconds"),Ke=St("minutes"),r=St("hours"),Ht=St("days"),vt=St("months"),jt=St("years");var xt=Math.round,Pt={ss:44,s:45,m:45,h:22,d:26,w:null,M:11};function Ot(e,a,t,s){var n=$a(e).abs(),r=xt(n.as("s")),d=xt(n.as("m")),i=xt(n.as("h")),_=xt(n.as("d")),o=xt(n.as("M")),m=xt(n.as("w")),n=xt(n.as("y")),r=(r<=t.ss?["s",r]:r<t.s&&["ss",r])||d<=1&&["m"]||d<t.m&&["mm",d]||i<=1&&["h"]||i<t.h&&["hh",i]||_<=1&&["d"]||_<t.d&&["dd",_];return(r=(r=null!=t.w?r||m<=1&&["w"]||m<t.w&&["ww",m]:r)||o<=1&&["M"]||o<t.M&&["MM",o]||n<=1&&["y"]||["yy",n])[2]=a,r[3]=0<+e,r[4]=s,function(e,a,t,s,n){return n.relativeTime(a||1,!!t,e,s)}.apply(null,r)}var Wt=Math.abs;function At(e){return(0<e)-(e<0)||+e}function Et(){if(!this.isValid())return this.localeData().invalidDate();var e,a,t,s,n,r,d,i=Wt(this._milliseconds)/1e3,_=Wt(this._days),o=Wt(this._months),m=this.asSeconds();return m?(e=u(i/60),a=u(e/60),i%=60,e%=60,t=u(o/12),o%=12,s=i?i.toFixed(3).replace(/\.?0+$/,""):"",n=At(this._months)!==At(m)?"-":"",r=At(this._days)!==At(m)?"-":"",d=At(this._milliseconds)!==At(m)?"-":"",(m<0?"-":"")+"P"+(t?n+t+"Y":"")+(o?n+o+"M":"")+(_?r+_+"D":"")+(a||e||i?"T":"")+(a?d+a+"H":"")+(e?d+e+"M":"")+(i?d+s+"S":"")):"P0D"}function Ft(e){return 0===e?0:1===e?1:2===e?2:3<=e%100&&e%100<=10?3:11<=e%100?4:5}function H(d){return function(e,a,t,s){var n=Ft(e),r=Jt[d][Ft(e)];return(r=2===n?r[a?0:1]:r).replace(/%d/i,e)}}function zt(e){return 0===e?0:1===e?1:2===e?2:3<=e%100&&e%100<=10?3:11<=e%100?4:5}function v(d){return function(e,a,t,s){var n=zt(e),r=Ct[d][zt(e)];return(r=2===n?r[a?0:1]:r).replace(/%d/i,e)}}function Nt(e){return 0===e?0:1===e?1:2===e?2:3<=e%100&&e%100<=10?3:11<=e%100?4:5}function j(d){return function(e,a,t,s){var n=Nt(e),r=Bt[d][Nt(e)];return(r=2===n?r[a?0:1]:r).replace(/%d/i,e)}}var x=Ja.prototype,Jt=(x.isValid=function(){return this._isValid},x.abs=function(){var e=this._data;return this._milliseconds=kt(this._milliseconds),this._days=kt(this._days),this._months=kt(this._months),e.milliseconds=kt(e.milliseconds),e.seconds=kt(e.seconds),e.minutes=kt(e.minutes),e.hours=kt(e.hours),e.months=kt(e.months),e.years=kt(e.years),this},x.add=function(e,a){return Dt(this,e,a,1)},x.subtract=function(e,a){return Dt(this,e,a,-1)},x.as=function(e){if(!this.isValid())return NaN;var a,t,s=this._milliseconds;if("month"===(e=i(e))||"quarter"===e||"year"===e)switch(a=this._days+s/864e5,t=this._months+gt(a),e){case"month":return t;case"quarter":return t/3;case"year":return t/12}else switch(a=this._days+Math.round(wt(this._months)),e){case"week":return a/7+s/6048e5;case"day":return a+s/864e5;case"hour":return 24*a+s/36e5;case"minute":return 1440*a+s/6e4;case"second":return 86400*a+s/1e3;case"millisecond":return Math.floor(864e5*a)+s;default:throw new Error("Unknown unit "+e)}},x.asMilliseconds=De,x.asSeconds=pe,x.asMinutes=Ne,x.asHours=L,x.asDays=c,x.asWeeks=ta,x.asMonths=f,x.asQuarters=ke,x.asYears=y,x.valueOf=function(){return this.isValid()?this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*m(this._months/12):NaN},x._bubble=function(){var e=this._milliseconds,a=this._days,t=this._months,s=this._data;return 0<=e&&0<=a&&0<=t||e<=0&&a<=0&&t<=0||(e+=864e5*Tt(wt(t)+a),t=a=0),s.milliseconds=e%1e3,e=u(e/1e3),s.seconds=e%60,e=u(e/60),s.minutes=e%60,e=u(e/60),s.hours=e%24,a+=u(e/24),t+=e=u(gt(a)),a-=Tt(wt(e)),e=u(t/12),t%=12,s.days=a,s.months=t,s.years=e,this},x.clone=function(){return $a(this)},x.get=function(e){return e=i(e),this.isValid()?this[e+"s"]():NaN},x.milliseconds=_,x.seconds=Y,x.minutes=Ke,x.hours=r,x.days=Ht,x.weeks=function(){return u(this.days()/7)},x.months=vt,x.years=jt,x.humanize=function(e,a){if(!this.isValid())return this.localeData().invalidDate();var t=!1,s=Pt;return"object"==typeof e&&(a=e,e=!1),"boolean"==typeof e&&(t=e),"object"==typeof a&&(s=Object.assign({},Pt,a),null!=a.s&&null==a.ss&&(s.ss=a.s-1)),e=this.localeData(),a=Ot(this,!t,s,e),t&&(a=e.pastFuture(+this,a)),e.postformat(a)},x.toISOString=Et,x.toString=Et,x.toJSON=Et,x.locale=rt,x.localeData=dt,x.toIsoString=e("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",Et),x.lang=na,s("X",0,0,"unix"),s("x",0,0,"valueOf"),p("x",ge),p("X",/[+-]?\d+(\.\d{1,3})?/),D("X",function(e,a,t){t._d=new Date(1e3*parseFloat(e))}),D("x",function(e,a,t){t._d=new Date(m(e))}),M.version="2.29.4",E=S,M.fn=n,M.min=function(){return za("isBefore",[].slice.call(arguments,0))},M.max=function(){return za("isAfter",[].slice.call(arguments,0))},M.now=function(){return Date.now?Date.now():+new Date},M.utc=U,M.unix=function(e){return S(1e3*e)},M.months=function(e,a){return ft(e,a,"months")},M.isDate=R,M.locale=ya,M.invalid=V,M.duration=$a,M.isMoment=Q,M.weekdays=function(e,a,t){return pt(e,a,t,"weekdays")},M.parseZone=function(){return S.apply(null,arguments).parseZone()},M.localeData=pa,M.isDuration=Ra,M.monthsShort=function(e,a){return ft(e,a,"monthsShort")},M.weekdaysMin=function(e,a,t){return pt(e,a,t,"weekdaysMin")},M.defineLocale=fa,M.updateLocale=function(e,a){var t,s;return null!=a?(s=Ma,null!=b[e]&&null!=b[e].parentLocale?b[e].set(se(b[e]._config,a)):(a=se(s=null!=(t=Ya(e))?t._config:s,a),null==t&&(a.abbr=e),(s=new ne(a)).parentLocale=b[e],b[e]=s),ya(e)):null!=b[e]&&(null!=b[e].parentLocale?(b[e]=b[e].parentLocale,e===ya()&&ya(e)):null!=b[e]&&delete b[e]),b[e]},M.locales=function(){return re(b)},M.weekdaysShort=function(e,a,t){return pt(e,a,t,"weekdaysShort")},M.normalizeUnits=i,M.relativeTimeRounding=function(e){return void 0===e?xt:"function"==typeof e&&(xt=e,!0)},M.relativeTimeThreshold=function(e,a){return void 0!==Pt[e]&&(void 0===a?Pt[e]:(Pt[e]=a,"s"===e&&(Pt.ss=a-1),!0))},M.calendarFormat=function(e,a){return(e=e.diff(a,"days",!0))<-6?"sameElse":e<-1?"lastWeek":e<0?"lastDay":e<1?"sameDay":e<2?"nextDay":e<7?"nextWeek":"sameElse"},M.prototype=n,M.HTML5_FMT={DATETIME_LOCAL:"YYYY-MM-DDTHH:mm",DATETIME_LOCAL_SECONDS:"YYYY-MM-DDTHH:mm:ss",DATETIME_LOCAL_MS:"YYYY-MM-DDTHH:mm:ss.SSS",DATE:"YYYY-MM-DD",TIME:"HH:mm",TIME_SECONDS:"HH:mm:ss",TIME_MS:"HH:mm:ss.SSS",WEEK:"GGGG-[W]WW",MONTH:"YYYY-MM"},M.defineLocale("af",{months:"Januarie_Februarie_Maart_April_Mei_Junie_Julie_Augustus_September_Oktober_November_Desember".split("_"),monthsShort:"Jan_Feb_Mrt_Apr_Mei_Jun_Jul_Aug_Sep_Okt_Nov_Des".split("_"),weekdays:"Sondag_Maandag_Dinsdag_Woensdag_Donderdag_Vrydag_Saterdag".split("_"),weekdaysShort:"Son_Maa_Din_Woe_Don_Vry_Sat".split("_"),weekdaysMin:"So_Ma_Di_Wo_Do_Vr_Sa".split("_"),meridiemParse:/vm|nm/i,isPM:function(e){return/^nm$/i.test(e)},meridiem:function(e,a,t){return e<12?t?"vm":"VM":t?"nm":"NM"},longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Vandag om] LT",nextDay:"[M\xf4re om] LT",nextWeek:"dddd [om] LT",lastDay:"[Gister om] LT",lastWeek:"[Laas] dddd [om] LT",sameElse:"L"},relativeTime:{future:"oor %s",past:"%s gelede",s:"'n paar sekondes",ss:"%d sekondes",m:"'n minuut",mm:"%d minute",h:"'n uur",hh:"%d ure",d:"'n dag",dd:"%d dae",M:"'n maand",MM:"%d maande",y:"'n jaar",yy:"%d jaar"},dayOfMonthOrdinalParse:/\d{1,2}(ste|de)/,ordinal:function(e){return e+(1===e||8===e||20<=e?"ste":"de")},week:{dow:1,doy:4}}),{s:["\u0623\u0642\u0644 \u0645\u0646 \u062b\u0627\u0646\u064a\u0629","\u062b\u0627\u0646\u064a\u0629 \u0648\u0627\u062d\u062f\u0629",["\u062b\u0627\u0646\u064a\u062a\u0627\u0646","\u062b\u0627\u0646\u064a\u062a\u064a\u0646"],"%d \u062b\u0648\u0627\u0646","%d \u062b\u0627\u0646\u064a\u0629","%d \u062b\u0627\u0646\u064a\u0629"],m:["\u0623\u0642\u0644 \u0645\u0646 \u062f\u0642\u064a\u0642\u0629","\u062f\u0642\u064a\u0642\u0629 \u0648\u0627\u062d\u062f\u0629",["\u062f\u0642\u064a\u0642\u062a\u0627\u0646","\u062f\u0642\u064a\u0642\u062a\u064a\u0646"],"%d \u062f\u0642\u0627\u0626\u0642","%d \u062f\u0642\u064a\u0642\u0629","%d \u062f\u0642\u064a\u0642\u0629"],h:["\u0623\u0642\u0644 \u0645\u0646 \u0633\u0627\u0639\u0629","\u0633\u0627\u0639\u0629 \u0648\u0627\u062d\u062f\u0629",["\u0633\u0627\u0639\u062a\u0627\u0646","\u0633\u0627\u0639\u062a\u064a\u0646"],"%d \u0633\u0627\u0639\u0627\u062a","%d \u0633\u0627\u0639\u0629","%d \u0633\u0627\u0639\u0629"],d:["\u0623\u0642\u0644 \u0645\u0646 \u064a\u0648\u0645","\u064a\u0648\u0645 \u0648\u0627\u062d\u062f",["\u064a\u0648\u0645\u0627\u0646","\u064a\u0648\u0645\u064a\u0646"],"%d \u0623\u064a\u0627\u0645","%d \u064a\u0648\u0645\u064b\u0627","%d \u064a\u0648\u0645"],M:["\u0623\u0642\u0644 \u0645\u0646 \u0634\u0647\u0631","\u0634\u0647\u0631 \u0648\u0627\u062d\u062f",["\u0634\u0647\u0631\u0627\u0646","\u0634\u0647\u0631\u064a\u0646"],"%d \u0623\u0634\u0647\u0631","%d \u0634\u0647\u0631\u0627","%d \u0634\u0647\u0631"],y:["\u0623\u0642\u0644 \u0645\u0646 \u0639\u0627\u0645","\u0639\u0627\u0645 \u0648\u0627\u062d\u062f",["\u0639\u0627\u0645\u0627\u0646","\u0639\u0627\u0645\u064a\u0646"],"%d \u0623\u0639\u0648\u0627\u0645","%d \u0639\u0627\u0645\u064b\u0627","%d \u0639\u0627\u0645"]}),De=["\u062c\u0627\u0646\u0641\u064a","\u0641\u064a\u0641\u0631\u064a","\u0645\u0627\u0631\u0633","\u0623\u0641\u0631\u064a\u0644","\u0645\u0627\u064a","\u062c\u0648\u0627\u0646","\u062c\u0648\u064a\u0644\u064a\u0629","\u0623\u0648\u062a","\u0633\u0628\u062a\u0645\u0628\u0631","\u0623\u0643\u062a\u0648\u0628\u0631","\u0646\u0648\u0641\u0645\u0628\u0631","\u062f\u064a\u0633\u0645\u0628\u0631"],Rt=(M.defineLocale("ar-dz",{months:De,monthsShort:De,weekdays:"\u0627\u0644\u0623\u062d\u062f_\u0627\u0644\u0625\u062b\u0646\u064a\u0646_\u0627\u0644\u062b\u0644\u0627\u062b\u0627\u0621_\u0627\u0644\u0623\u0631\u0628\u0639\u0627\u0621_\u0627\u0644\u062e\u0645\u064a\u0633_\u0627\u0644\u062c\u0645\u0639\u0629_\u0627\u0644\u0633\u0628\u062a".split("_"),weekdaysShort:"\u0623\u062d\u062f_\u0625\u062b\u0646\u064a\u0646_\u062b\u0644\u0627\u062b\u0627\u0621_\u0623\u0631\u0628\u0639\u0627\u0621_\u062e\u0645\u064a\u0633_\u062c\u0645\u0639\u0629_\u0633\u0628\u062a".split("_"),weekdaysMin:"\u062d_\u0646_\u062b_\u0631_\u062e_\u062c_\u0633".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"D/\u200fM/\u200fYYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},meridiemParse:/\u0635|\u0645/,isPM:function(e){return"\u0645"===e},meridiem:function(e,a,t){return e<12?"\u0635":"\u0645"},calendar:{sameDay:"[\u0627\u0644\u064a\u0648\u0645 \u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextDay:"[\u063a\u062f\u064b\u0627 \u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextWeek:"dddd [\u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastDay:"[\u0623\u0645\u0633 \u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastWeek:"dddd [\u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",sameElse:"L"},relativeTime:{future:"\u0628\u0639\u062f %s",past:"\u0645\u0646\u0630 %s",s:H("s"),ss:H("s"),m:H("m"),mm:H("m"),h:H("h"),hh:H("h"),d:H("d"),dd:H("d"),M:H("M"),MM:H("M"),y:H("y"),yy:H("y")},postformat:function(e){return e.replace(/,/g,"\u060c")},week:{dow:0,doy:4}}),M.defineLocale("ar-kw",{months:"\u064a\u0646\u0627\u064a\u0631_\u0641\u0628\u0631\u0627\u064a\u0631_\u0645\u0627\u0631\u0633_\u0623\u0628\u0631\u064a\u0644_\u0645\u0627\u064a_\u064a\u0648\u0646\u064a\u0648_\u064a\u0648\u0644\u064a\u0648\u0632_\u063a\u0634\u062a_\u0634\u062a\u0646\u0628\u0631_\u0623\u0643\u062a\u0648\u0628\u0631_\u0646\u0648\u0646\u0628\u0631_\u062f\u062c\u0646\u0628\u0631".split("_"),monthsShort:"\u064a\u0646\u0627\u064a\u0631_\u0641\u0628\u0631\u0627\u064a\u0631_\u0645\u0627\u0631\u0633_\u0623\u0628\u0631\u064a\u0644_\u0645\u0627\u064a_\u064a\u0648\u0646\u064a\u0648_\u064a\u0648\u0644\u064a\u0648\u0632_\u063a\u0634\u062a_\u0634\u062a\u0646\u0628\u0631_\u0623\u0643\u062a\u0648\u0628\u0631_\u0646\u0648\u0646\u0628\u0631_\u062f\u062c\u0646\u0628\u0631".split("_"),weekdays:"\u0627\u0644\u0623\u062d\u062f_\u0627\u0644\u0625\u062a\u0646\u064a\u0646_\u0627\u0644\u062b\u0644\u0627\u062b\u0627\u0621_\u0627\u0644\u0623\u0631\u0628\u0639\u0627\u0621_\u0627\u0644\u062e\u0645\u064a\u0633_\u0627\u0644\u062c\u0645\u0639\u0629_\u0627\u0644\u0633\u0628\u062a".split("_"),weekdaysShort:"\u0627\u062d\u062f_\u0627\u062a\u0646\u064a\u0646_\u062b\u0644\u0627\u062b\u0627\u0621_\u0627\u0631\u0628\u0639\u0627\u0621_\u062e\u0645\u064a\u0633_\u062c\u0645\u0639\u0629_\u0633\u0628\u062a".split("_"),weekdaysMin:"\u062d_\u0646_\u062b_\u0631_\u062e_\u062c_\u0633".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[\u0627\u0644\u064a\u0648\u0645 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextDay:"[\u063a\u062f\u0627 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextWeek:"dddd [\u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastDay:"[\u0623\u0645\u0633 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastWeek:"dddd [\u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",sameElse:"L"},relativeTime:{future:"\u0641\u064a %s",past:"\u0645\u0646\u0630 %s",s:"\u062b\u0648\u0627\u0646",ss:"%d \u062b\u0627\u0646\u064a\u0629",m:"\u062f\u0642\u064a\u0642\u0629",mm:"%d \u062f\u0642\u0627\u0626\u0642",h:"\u0633\u0627\u0639\u0629",hh:"%d \u0633\u0627\u0639\u0627\u062a",d:"\u064a\u0648\u0645",dd:"%d \u0623\u064a\u0627\u0645",M:"\u0634\u0647\u0631",MM:"%d \u0623\u0634\u0647\u0631",y:"\u0633\u0646\u0629",yy:"%d \u0633\u0646\u0648\u0627\u062a"},week:{dow:0,doy:12}}),{1:"1",2:"2",3:"3",4:"4",5:"5",6:"6",7:"7",8:"8",9:"9",0:"0"}),Ct={s:["\u0623\u0642\u0644 \u0645\u0646 \u062b\u0627\u0646\u064a\u0629","\u062b\u0627\u0646\u064a\u0629 \u0648\u0627\u062d\u062f\u0629",["\u062b\u0627\u0646\u064a\u062a\u0627\u0646","\u062b\u0627\u0646\u064a\u062a\u064a\u0646"],"%d \u062b\u0648\u0627\u0646","%d \u062b\u0627\u0646\u064a\u0629","%d \u062b\u0627\u0646\u064a\u0629"],m:["\u0623\u0642\u0644 \u0645\u0646 \u062f\u0642\u064a\u0642\u0629","\u062f\u0642\u064a\u0642\u0629 \u0648\u0627\u062d\u062f\u0629",["\u062f\u0642\u064a\u0642\u062a\u0627\u0646","\u062f\u0642\u064a\u0642\u062a\u064a\u0646"],"%d \u062f\u0642\u0627\u0626\u0642","%d \u062f\u0642\u064a\u0642\u0629","%d \u062f\u0642\u064a\u0642\u0629"],h:["\u0623\u0642\u0644 \u0645\u0646 \u0633\u0627\u0639\u0629","\u0633\u0627\u0639\u0629 \u0648\u0627\u062d\u062f\u0629",["\u0633\u0627\u0639\u062a\u0627\u0646","\u0633\u0627\u0639\u062a\u064a\u0646"],"%d \u0633\u0627\u0639\u0627\u062a","%d \u0633\u0627\u0639\u0629","%d \u0633\u0627\u0639\u0629"],d:["\u0623\u0642\u0644 \u0645\u0646 \u064a\u0648\u0645","\u064a\u0648\u0645 \u0648\u0627\u062d\u062f",["\u064a\u0648\u0645\u0627\u0646","\u064a\u0648\u0645\u064a\u0646"],"%d \u0623\u064a\u0627\u0645","%d \u064a\u0648\u0645\u064b\u0627","%d \u064a\u0648\u0645"],M:["\u0623\u0642\u0644 \u0645\u0646 \u0634\u0647\u0631","\u0634\u0647\u0631 \u0648\u0627\u062d\u062f",["\u0634\u0647\u0631\u0627\u0646","\u0634\u0647\u0631\u064a\u0646"],"%d \u0623\u0634\u0647\u0631","%d \u0634\u0647\u0631\u0627","%d \u0634\u0647\u0631"],y:["\u0623\u0642\u0644 \u0645\u0646 \u0639\u0627\u0645","\u0639\u0627\u0645 \u0648\u0627\u062d\u062f",["\u0639\u0627\u0645\u0627\u0646","\u0639\u0627\u0645\u064a\u0646"],"%d \u0623\u0639\u0648\u0627\u0645","%d \u0639\u0627\u0645\u064b\u0627","%d \u0639\u0627\u0645"]},pe=["\u064a\u0646\u0627\u064a\u0631","\u0641\u0628\u0631\u0627\u064a\u0631","\u0645\u0627\u0631\u0633","\u0623\u0628\u0631\u064a\u0644","\u0645\u0627\u064a\u0648","\u064a\u0648\u0646\u064a\u0648","\u064a\u0648\u0644\u064a\u0648","\u0623\u063a\u0633\u0637\u0633","\u0633\u0628\u062a\u0645\u0628\u0631","\u0623\u0643\u062a\u0648\u0628\u0631","\u0646\u0648\u0641\u0645\u0628\u0631","\u062f\u064a\u0633\u0645\u0628\u0631"],It=(M.defineLocale("ar-ly",{months:pe,monthsShort:pe,weekdays:"\u0627\u0644\u0623\u062d\u062f_\u0627\u0644\u0625\u062b\u0646\u064a\u0646_\u0627\u0644\u062b\u0644\u0627\u062b\u0627\u0621_\u0627\u0644\u0623\u0631\u0628\u0639\u0627\u0621_\u0627\u0644\u062e\u0645\u064a\u0633_\u0627\u0644\u062c\u0645\u0639\u0629_\u0627\u0644\u0633\u0628\u062a".split("_"),weekdaysShort:"\u0623\u062d\u062f_\u0625\u062b\u0646\u064a\u0646_\u062b\u0644\u0627\u062b\u0627\u0621_\u0623\u0631\u0628\u0639\u0627\u0621_\u062e\u0645\u064a\u0633_\u062c\u0645\u0639\u0629_\u0633\u0628\u062a".split("_"),weekdaysMin:"\u062d_\u0646_\u062b_\u0631_\u062e_\u062c_\u0633".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"D/\u200fM/\u200fYYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},meridiemParse:/\u0635|\u0645/,isPM:function(e){return"\u0645"===e},meridiem:function(e,a,t){return e<12?"\u0635":"\u0645"},calendar:{sameDay:"[\u0627\u0644\u064a\u0648\u0645 \u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextDay:"[\u063a\u062f\u064b\u0627 \u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextWeek:"dddd [\u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastDay:"[\u0623\u0645\u0633 \u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastWeek:"dddd [\u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",sameElse:"L"},relativeTime:{future:"\u0628\u0639\u062f %s",past:"\u0645\u0646\u0630 %s",s:v("s"),ss:v("s"),m:v("m"),mm:v("m"),h:v("h"),hh:v("h"),d:v("d"),dd:v("d"),M:v("M"),MM:v("M"),y:v("y"),yy:v("y")},preparse:function(e){return e.replace(/\u060c/g,",")},postformat:function(e){return e.replace(/\d/g,function(e){return Rt[e]}).replace(/,/g,"\u060c")},week:{dow:6,doy:12}}),M.defineLocale("ar-ma",{months:"\u064a\u0646\u0627\u064a\u0631_\u0641\u0628\u0631\u0627\u064a\u0631_\u0645\u0627\u0631\u0633_\u0623\u0628\u0631\u064a\u0644_\u0645\u0627\u064a_\u064a\u0648\u0646\u064a\u0648_\u064a\u0648\u0644\u064a\u0648\u0632_\u063a\u0634\u062a_\u0634\u062a\u0646\u0628\u0631_\u0623\u0643\u062a\u0648\u0628\u0631_\u0646\u0648\u0646\u0628\u0631_\u062f\u062c\u0646\u0628\u0631".split("_"),monthsShort:"\u064a\u0646\u0627\u064a\u0631_\u0641\u0628\u0631\u0627\u064a\u0631_\u0645\u0627\u0631\u0633_\u0623\u0628\u0631\u064a\u0644_\u0645\u0627\u064a_\u064a\u0648\u0646\u064a\u0648_\u064a\u0648\u0644\u064a\u0648\u0632_\u063a\u0634\u062a_\u0634\u062a\u0646\u0628\u0631_\u0623\u0643\u062a\u0648\u0628\u0631_\u0646\u0648\u0646\u0628\u0631_\u062f\u062c\u0646\u0628\u0631".split("_"),weekdays:"\u0627\u0644\u0623\u062d\u062f_\u0627\u0644\u0625\u062b\u0646\u064a\u0646_\u0627\u0644\u062b\u0644\u0627\u062b\u0627\u0621_\u0627\u0644\u0623\u0631\u0628\u0639\u0627\u0621_\u0627\u0644\u062e\u0645\u064a\u0633_\u0627\u0644\u062c\u0645\u0639\u0629_\u0627\u0644\u0633\u0628\u062a".split("_"),weekdaysShort:"\u0627\u062d\u062f_\u0627\u062b\u0646\u064a\u0646_\u062b\u0644\u0627\u062b\u0627\u0621_\u0627\u0631\u0628\u0639\u0627\u0621_\u062e\u0645\u064a\u0633_\u062c\u0645\u0639\u0629_\u0633\u0628\u062a".split("_"),weekdaysMin:"\u062d_\u0646_\u062b_\u0631_\u062e_\u062c_\u0633".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[\u0627\u0644\u064a\u0648\u0645 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextDay:"[\u063a\u062f\u0627 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextWeek:"dddd [\u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastDay:"[\u0623\u0645\u0633 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastWeek:"dddd [\u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",sameElse:"L"},relativeTime:{future:"\u0641\u064a %s",past:"\u0645\u0646\u0630 %s",s:"\u062b\u0648\u0627\u0646",ss:"%d \u062b\u0627\u0646\u064a\u0629",m:"\u062f\u0642\u064a\u0642\u0629",mm:"%d \u062f\u0642\u0627\u0626\u0642",h:"\u0633\u0627\u0639\u0629",hh:"%d \u0633\u0627\u0639\u0627\u062a",d:"\u064a\u0648\u0645",dd:"%d \u0623\u064a\u0627\u0645",M:"\u0634\u0647\u0631",MM:"%d \u0623\u0634\u0647\u0631",y:"\u0633\u0646\u0629",yy:"%d \u0633\u0646\u0648\u0627\u062a"},week:{dow:1,doy:4}}),{1:"\u0661",2:"\u0662",3:"\u0663",4:"\u0664",5:"\u0665",6:"\u0666",7:"\u0667",8:"\u0668",9:"\u0669",0:"\u0660"}),Ut={"\u0661":"1","\u0662":"2","\u0663":"3","\u0664":"4","\u0665":"5","\u0666":"6","\u0667":"7","\u0668":"8","\u0669":"9","\u0660":"0"},Gt=(M.defineLocale("ar-sa",{months:"\u064a\u0646\u0627\u064a\u0631_\u0641\u0628\u0631\u0627\u064a\u0631_\u0645\u0627\u0631\u0633_\u0623\u0628\u0631\u064a\u0644_\u0645\u0627\u064a\u0648_\u064a\u0648\u0646\u064a\u0648_\u064a\u0648\u0644\u064a\u0648_\u0623\u063a\u0633\u0637\u0633_\u0633\u0628\u062a\u0645\u0628\u0631_\u0623\u0643\u062a\u0648\u0628\u0631_\u0646\u0648\u0641\u0645\u0628\u0631_\u062f\u064a\u0633\u0645\u0628\u0631".split("_"),monthsShort:"\u064a\u0646\u0627\u064a\u0631_\u0641\u0628\u0631\u0627\u064a\u0631_\u0645\u0627\u0631\u0633_\u0623\u0628\u0631\u064a\u0644_\u0645\u0627\u064a\u0648_\u064a\u0648\u0646\u064a\u0648_\u064a\u0648\u0644\u064a\u0648_\u0623\u063a\u0633\u0637\u0633_\u0633\u0628\u062a\u0645\u0628\u0631_\u0623\u0643\u062a\u0648\u0628\u0631_\u0646\u0648\u0641\u0645\u0628\u0631_\u062f\u064a\u0633\u0645\u0628\u0631".split("_"),weekdays:"\u0627\u0644\u0623\u062d\u062f_\u0627\u0644\u0625\u062b\u0646\u064a\u0646_\u0627\u0644\u062b\u0644\u0627\u062b\u0627\u0621_\u0627\u0644\u0623\u0631\u0628\u0639\u0627\u0621_\u0627\u0644\u062e\u0645\u064a\u0633_\u0627\u0644\u062c\u0645\u0639\u0629_\u0627\u0644\u0633\u0628\u062a".split("_"),weekdaysShort:"\u0623\u062d\u062f_\u0625\u062b\u0646\u064a\u0646_\u062b\u0644\u0627\u062b\u0627\u0621_\u0623\u0631\u0628\u0639\u0627\u0621_\u062e\u0645\u064a\u0633_\u062c\u0645\u0639\u0629_\u0633\u0628\u062a".split("_"),weekdaysMin:"\u062d_\u0646_\u062b_\u0631_\u062e_\u062c_\u0633".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},meridiemParse:/\u0635|\u0645/,isPM:function(e){return"\u0645"===e},meridiem:function(e,a,t){return e<12?"\u0635":"\u0645"},calendar:{sameDay:"[\u0627\u0644\u064a\u0648\u0645 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextDay:"[\u063a\u062f\u0627 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextWeek:"dddd [\u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastDay:"[\u0623\u0645\u0633 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastWeek:"dddd [\u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",sameElse:"L"},relativeTime:{future:"\u0641\u064a %s",past:"\u0645\u0646\u0630 %s",s:"\u062b\u0648\u0627\u0646",ss:"%d \u062b\u0627\u0646\u064a\u0629",m:"\u062f\u0642\u064a\u0642\u0629",mm:"%d \u062f\u0642\u0627\u0626\u0642",h:"\u0633\u0627\u0639\u0629",hh:"%d \u0633\u0627\u0639\u0627\u062a",d:"\u064a\u0648\u0645",dd:"%d \u0623\u064a\u0627\u0645",M:"\u0634\u0647\u0631",MM:"%d \u0623\u0634\u0647\u0631",y:"\u0633\u0646\u0629",yy:"%d \u0633\u0646\u0648\u0627\u062a"},preparse:function(e){return e.replace(/[\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u0660]/g,function(e){return Ut[e]}).replace(/\u060c/g,",")},postformat:function(e){return e.replace(/\d/g,function(e){return It[e]}).replace(/,/g,"\u060c")},week:{dow:0,doy:6}}),M.defineLocale("ar-tn",{months:"\u062c\u0627\u0646\u0641\u064a_\u0641\u064a\u0641\u0631\u064a_\u0645\u0627\u0631\u0633_\u0623\u0641\u0631\u064a\u0644_\u0645\u0627\u064a_\u062c\u0648\u0627\u0646_\u062c\u0648\u064a\u0644\u064a\u0629_\u0623\u0648\u062a_\u0633\u0628\u062a\u0645\u0628\u0631_\u0623\u0643\u062a\u0648\u0628\u0631_\u0646\u0648\u0641\u0645\u0628\u0631_\u062f\u064a\u0633\u0645\u0628\u0631".split("_"),monthsShort:"\u062c\u0627\u0646\u0641\u064a_\u0641\u064a\u0641\u0631\u064a_\u0645\u0627\u0631\u0633_\u0623\u0641\u0631\u064a\u0644_\u0645\u0627\u064a_\u062c\u0648\u0627\u0646_\u062c\u0648\u064a\u0644\u064a\u0629_\u0623\u0648\u062a_\u0633\u0628\u062a\u0645\u0628\u0631_\u0623\u0643\u062a\u0648\u0628\u0631_\u0646\u0648\u0641\u0645\u0628\u0631_\u062f\u064a\u0633\u0645\u0628\u0631".split("_"),weekdays:"\u0627\u0644\u0623\u062d\u062f_\u0627\u0644\u0625\u062b\u0646\u064a\u0646_\u0627\u0644\u062b\u0644\u0627\u062b\u0627\u0621_\u0627\u0644\u0623\u0631\u0628\u0639\u0627\u0621_\u0627\u0644\u062e\u0645\u064a\u0633_\u0627\u0644\u062c\u0645\u0639\u0629_\u0627\u0644\u0633\u0628\u062a".split("_"),weekdaysShort:"\u0623\u062d\u062f_\u0625\u062b\u0646\u064a\u0646_\u062b\u0644\u0627\u062b\u0627\u0621_\u0623\u0631\u0628\u0639\u0627\u0621_\u062e\u0645\u064a\u0633_\u062c\u0645\u0639\u0629_\u0633\u0628\u062a".split("_"),weekdaysMin:"\u062d_\u0646_\u062b_\u0631_\u062e_\u062c_\u0633".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[\u0627\u0644\u064a\u0648\u0645 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextDay:"[\u063a\u062f\u0627 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextWeek:"dddd [\u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastDay:"[\u0623\u0645\u0633 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastWeek:"dddd [\u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",sameElse:"L"},relativeTime:{future:"\u0641\u064a %s",past:"\u0645\u0646\u0630 %s",s:"\u062b\u0648\u0627\u0646",ss:"%d \u062b\u0627\u0646\u064a\u0629",m:"\u062f\u0642\u064a\u0642\u0629",mm:"%d \u062f\u0642\u0627\u0626\u0642",h:"\u0633\u0627\u0639\u0629",hh:"%d \u0633\u0627\u0639\u0627\u062a",d:"\u064a\u0648\u0645",dd:"%d \u0623\u064a\u0627\u0645",M:"\u0634\u0647\u0631",MM:"%d \u0623\u0634\u0647\u0631",y:"\u0633\u0646\u0629",yy:"%d \u0633\u0646\u0648\u0627\u062a"},week:{dow:1,doy:4}}),{1:"\u0661",2:"\u0662",3:"\u0663",4:"\u0664",5:"\u0665",6:"\u0666",7:"\u0667",8:"\u0668",9:"\u0669",0:"\u0660"}),Vt={"\u0661":"1","\u0662":"2","\u0663":"3","\u0664":"4","\u0665":"5","\u0666":"6","\u0667":"7","\u0668":"8","\u0669":"9","\u0660":"0"},Bt={s:["\u0623\u0642\u0644 \u0645\u0646 \u062b\u0627\u0646\u064a\u0629","\u062b\u0627\u0646\u064a\u0629 \u0648\u0627\u062d\u062f\u0629",["\u062b\u0627\u0646\u064a\u062a\u0627\u0646","\u062b\u0627\u0646\u064a\u062a\u064a\u0646"],"%d \u062b\u0648\u0627\u0646","%d \u062b\u0627\u0646\u064a\u0629","%d \u062b\u0627\u0646\u064a\u0629"],m:["\u0623\u0642\u0644 \u0645\u0646 \u062f\u0642\u064a\u0642\u0629","\u062f\u0642\u064a\u0642\u0629 \u0648\u0627\u062d\u062f\u0629",["\u062f\u0642\u064a\u0642\u062a\u0627\u0646","\u062f\u0642\u064a\u0642\u062a\u064a\u0646"],"%d \u062f\u0642\u0627\u0626\u0642","%d \u062f\u0642\u064a\u0642\u0629","%d \u062f\u0642\u064a\u0642\u0629"],h:["\u0623\u0642\u0644 \u0645\u0646 \u0633\u0627\u0639\u0629","\u0633\u0627\u0639\u0629 \u0648\u0627\u062d\u062f\u0629",["\u0633\u0627\u0639\u062a\u0627\u0646","\u0633\u0627\u0639\u062a\u064a\u0646"],"%d \u0633\u0627\u0639\u0627\u062a","%d \u0633\u0627\u0639\u0629","%d \u0633\u0627\u0639\u0629"],d:["\u0623\u0642\u0644 \u0645\u0646 \u064a\u0648\u0645","\u064a\u0648\u0645 \u0648\u0627\u062d\u062f",["\u064a\u0648\u0645\u0627\u0646","\u064a\u0648\u0645\u064a\u0646"],"%d \u0623\u064a\u0627\u0645","%d \u064a\u0648\u0645\u064b\u0627","%d \u064a\u0648\u0645"],M:["\u0623\u0642\u0644 \u0645\u0646 \u0634\u0647\u0631","\u0634\u0647\u0631 \u0648\u0627\u062d\u062f",["\u0634\u0647\u0631\u0627\u0646","\u0634\u0647\u0631\u064a\u0646"],"%d \u0623\u0634\u0647\u0631","%d \u0634\u0647\u0631\u0627","%d \u0634\u0647\u0631"],y:["\u0623\u0642\u0644 \u0645\u0646 \u0639\u0627\u0645","\u0639\u0627\u0645 \u0648\u0627\u062d\u062f",["\u0639\u0627\u0645\u0627\u0646","\u0639\u0627\u0645\u064a\u0646"],"%d \u0623\u0639\u0648\u0627\u0645","%d \u0639\u0627\u0645\u064b\u0627","%d \u0639\u0627\u0645"]},Ne=["\u064a\u0646\u0627\u064a\u0631","\u0641\u0628\u0631\u0627\u064a\u0631","\u0645\u0627\u0631\u0633","\u0623\u0628\u0631\u064a\u0644","\u0645\u0627\u064a\u0648","\u064a\u0648\u0646\u064a\u0648","\u064a\u0648\u0644\u064a\u0648","\u0623\u063a\u0633\u0637\u0633","\u0633\u0628\u062a\u0645\u0628\u0631","\u0623\u0643\u062a\u0648\u0628\u0631","\u0646\u0648\u0641\u0645\u0628\u0631","\u062f\u064a\u0633\u0645\u0628\u0631"],Kt=(M.defineLocale("ar",{months:Ne,monthsShort:Ne,weekdays:"\u0627\u0644\u0623\u062d\u062f_\u0627\u0644\u0625\u062b\u0646\u064a\u0646_\u0627\u0644\u062b\u0644\u0627\u062b\u0627\u0621_\u0627\u0644\u0623\u0631\u0628\u0639\u0627\u0621_\u0627\u0644\u062e\u0645\u064a\u0633_\u0627\u0644\u062c\u0645\u0639\u0629_\u0627\u0644\u0633\u0628\u062a".split("_"),weekdaysShort:"\u0623\u062d\u062f_\u0625\u062b\u0646\u064a\u0646_\u062b\u0644\u0627\u062b\u0627\u0621_\u0623\u0631\u0628\u0639\u0627\u0621_\u062e\u0645\u064a\u0633_\u062c\u0645\u0639\u0629_\u0633\u0628\u062a".split("_"),weekdaysMin:"\u062d_\u0646_\u062b_\u0631_\u062e_\u062c_\u0633".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"D/\u200fM/\u200fYYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},meridiemParse:/\u0635|\u0645/,isPM:function(e){return"\u0645"===e},meridiem:function(e,a,t){return e<12?"\u0635":"\u0645"},calendar:{sameDay:"[\u0627\u0644\u064a\u0648\u0645 \u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextDay:"[\u063a\u062f\u064b\u0627 \u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextWeek:"dddd [\u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastDay:"[\u0623\u0645\u0633 \u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastWeek:"dddd [\u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",sameElse:"L"},relativeTime:{future:"\u0628\u0639\u062f %s",past:"\u0645\u0646\u0630 %s",s:j("s"),ss:j("s"),m:j("m"),mm:j("m"),h:j("h"),hh:j("h"),d:j("d"),dd:j("d"),M:j("M"),MM:j("M"),y:j("y"),yy:j("y")},preparse:function(e){return e.replace(/[\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u0660]/g,function(e){return Vt[e]}).replace(/\u060c/g,",")},postformat:function(e){return e.replace(/\d/g,function(e){return Gt[e]}).replace(/,/g,"\u060c")},week:{dow:6,doy:12}}),{1:"-inci",5:"-inci",8:"-inci",70:"-inci",80:"-inci",2:"-nci",7:"-nci",20:"-nci",50:"-nci",3:"-\xfcnc\xfc",4:"-\xfcnc\xfc",100:"-\xfcnc\xfc",6:"-nc\u0131",9:"-uncu",10:"-uncu",30:"-uncu",60:"-\u0131nc\u0131",90:"-\u0131nc\u0131"});function qt(e,a,t){return"m"===t?a?"\u0445\u0432\u0456\u043b\u0456\u043d\u0430":"\u0445\u0432\u0456\u043b\u0456\u043d\u0443":"h"===t?a?"\u0433\u0430\u0434\u0437\u0456\u043d\u0430":"\u0433\u0430\u0434\u0437\u0456\u043d\u0443":e+" "+(e=+e,a=(a={ss:a?"\u0441\u0435\u043a\u0443\u043d\u0434\u0430_\u0441\u0435\u043a\u0443\u043d\u0434\u044b_\u0441\u0435\u043a\u0443\u043d\u0434":"\u0441\u0435\u043a\u0443\u043d\u0434\u0443_\u0441\u0435\u043a\u0443\u043d\u0434\u044b_\u0441\u0435\u043a\u0443\u043d\u0434",mm:a?"\u0445\u0432\u0456\u043b\u0456\u043d\u0430_\u0445\u0432\u0456\u043b\u0456\u043d\u044b_\u0445\u0432\u0456\u043b\u0456\u043d":"\u0445\u0432\u0456\u043b\u0456\u043d\u0443_\u0445\u0432\u0456\u043b\u0456\u043d\u044b_\u0445\u0432\u0456\u043b\u0456\u043d",hh:a?"\u0433\u0430\u0434\u0437\u0456\u043d\u0430_\u0433\u0430\u0434\u0437\u0456\u043d\u044b_\u0433\u0430\u0434\u0437\u0456\u043d":"\u0433\u0430\u0434\u0437\u0456\u043d\u0443_\u0433\u0430\u0434\u0437\u0456\u043d\u044b_\u0433\u0430\u0434\u0437\u0456\u043d",dd:"\u0434\u0437\u0435\u043d\u044c_\u0434\u043d\u0456_\u0434\u0437\u0451\u043d",MM:"\u043c\u0435\u0441\u044f\u0446_\u043c\u0435\u0441\u044f\u0446\u044b_\u043c\u0435\u0441\u044f\u0446\u0430\u045e",yy:"\u0433\u043e\u0434_\u0433\u0430\u0434\u044b_\u0433\u0430\u0434\u043e\u045e"}[t]).split("_"),e%10==1&&e%100!=11?a[0]:2<=e%10&&e%10<=4&&(e%100<10||20<=e%100)?a[1]:a[2])}M.defineLocale("az",{months:"yanvar_fevral_mart_aprel_may_iyun_iyul_avqust_sentyabr_oktyabr_noyabr_dekabr".split("_"),monthsShort:"yan_fev_mar_apr_may_iyn_iyl_avq_sen_okt_noy_dek".split("_"),weekdays:"Bazar_Bazar ert\u0259si_\xc7\u0259r\u015f\u0259nb\u0259 ax\u015fam\u0131_\xc7\u0259r\u015f\u0259nb\u0259_C\xfcm\u0259 ax\u015fam\u0131_C\xfcm\u0259_\u015e\u0259nb\u0259".split("_"),weekdaysShort:"Baz_BzE_\xc7Ax_\xc7\u0259r_CAx_C\xfcm_\u015e\u0259n".split("_"),weekdaysMin:"Bz_BE_\xc7A_\xc7\u0259_CA_C\xfc_\u015e\u0259".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[bug\xfcn saat] LT",nextDay:"[sabah saat] LT",nextWeek:"[g\u0259l\u0259n h\u0259ft\u0259] dddd [saat] LT",lastDay:"[d\xfcn\u0259n] LT",lastWeek:"[ke\xe7\u0259n h\u0259ft\u0259] dddd [saat] LT",sameElse:"L"},relativeTime:{future:"%s sonra",past:"%s \u0259vv\u0259l",s:"bir ne\xe7\u0259 saniy\u0259",ss:"%d saniy\u0259",m:"bir d\u0259qiq\u0259",mm:"%d d\u0259qiq\u0259",h:"bir saat",hh:"%d saat",d:"bir g\xfcn",dd:"%d g\xfcn",M:"bir ay",MM:"%d ay",y:"bir il",yy:"%d il"},meridiemParse:/gec\u0259|s\u0259h\u0259r|g\xfcnd\xfcz|ax\u015fam/,isPM:function(e){return/^(g\xfcnd\xfcz|ax\u015fam)$/.test(e)},meridiem:function(e,a,t){return e<4?"gec\u0259":e<12?"s\u0259h\u0259r":e<17?"g\xfcnd\xfcz":"ax\u015fam"},dayOfMonthOrdinalParse:/\d{1,2}-(\u0131nc\u0131|inci|nci|\xfcnc\xfc|nc\u0131|uncu)/,ordinal:function(e){if(0===e)return e+"-\u0131nc\u0131";var a=e%10;return e+(Kt[a]||Kt[e%100-a]||Kt[100<=e?100:null])},week:{dow:1,doy:7}}),M.defineLocale("be",{months:{format:"\u0441\u0442\u0443\u0434\u0437\u0435\u043d\u044f_\u043b\u044e\u0442\u0430\u0433\u0430_\u0441\u0430\u043a\u0430\u0432\u0456\u043a\u0430_\u043a\u0440\u0430\u0441\u0430\u0432\u0456\u043a\u0430_\u0442\u0440\u0430\u045e\u043d\u044f_\u0447\u044d\u0440\u0432\u0435\u043d\u044f_\u043b\u0456\u043f\u0435\u043d\u044f_\u0436\u043d\u0456\u045e\u043d\u044f_\u0432\u0435\u0440\u0430\u0441\u043d\u044f_\u043a\u0430\u0441\u0442\u0440\u044b\u0447\u043d\u0456\u043a\u0430_\u043b\u0456\u0441\u0442\u0430\u043f\u0430\u0434\u0430_\u0441\u043d\u0435\u0436\u043d\u044f".split("_"),standalone:"\u0441\u0442\u0443\u0434\u0437\u0435\u043d\u044c_\u043b\u044e\u0442\u044b_\u0441\u0430\u043a\u0430\u0432\u0456\u043a_\u043a\u0440\u0430\u0441\u0430\u0432\u0456\u043a_\u0442\u0440\u0430\u0432\u0435\u043d\u044c_\u0447\u044d\u0440\u0432\u0435\u043d\u044c_\u043b\u0456\u043f\u0435\u043d\u044c_\u0436\u043d\u0456\u0432\u0435\u043d\u044c_\u0432\u0435\u0440\u0430\u0441\u0435\u043d\u044c_\u043a\u0430\u0441\u0442\u0440\u044b\u0447\u043d\u0456\u043a_\u043b\u0456\u0441\u0442\u0430\u043f\u0430\u0434_\u0441\u043d\u0435\u0436\u0430\u043d\u044c".split("_")},monthsShort:"\u0441\u0442\u0443\u0434_\u043b\u044e\u0442_\u0441\u0430\u043a_\u043a\u0440\u0430\u0441_\u0442\u0440\u0430\u0432_\u0447\u044d\u0440\u0432_\u043b\u0456\u043f_\u0436\u043d\u0456\u0432_\u0432\u0435\u0440_\u043a\u0430\u0441\u0442_\u043b\u0456\u0441\u0442_\u0441\u043d\u0435\u0436".split("_"),weekdays:{format:"\u043d\u044f\u0434\u0437\u0435\u043b\u044e_\u043f\u0430\u043d\u044f\u0434\u0437\u0435\u043b\u0430\u043a_\u0430\u045e\u0442\u043e\u0440\u0430\u043a_\u0441\u0435\u0440\u0430\u0434\u0443_\u0447\u0430\u0446\u0432\u0435\u0440_\u043f\u044f\u0442\u043d\u0456\u0446\u0443_\u0441\u0443\u0431\u043e\u0442\u0443".split("_"),standalone:"\u043d\u044f\u0434\u0437\u0435\u043b\u044f_\u043f\u0430\u043d\u044f\u0434\u0437\u0435\u043b\u0430\u043a_\u0430\u045e\u0442\u043e\u0440\u0430\u043a_\u0441\u0435\u0440\u0430\u0434\u0430_\u0447\u0430\u0446\u0432\u0435\u0440_\u043f\u044f\u0442\u043d\u0456\u0446\u0430_\u0441\u0443\u0431\u043e\u0442\u0430".split("_"),isFormat:/\[ ?[\u0423\u0443\u045e] ?(?:\u043c\u0456\u043d\u0443\u043b\u0443\u044e|\u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0443\u044e)? ?\] ?dddd/},weekdaysShort:"\u043d\u0434_\u043f\u043d_\u0430\u0442_\u0441\u0440_\u0447\u0446_\u043f\u0442_\u0441\u0431".split("_"),weekdaysMin:"\u043d\u0434_\u043f\u043d_\u0430\u0442_\u0441\u0440_\u0447\u0446_\u043f\u0442_\u0441\u0431".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY \u0433.",LLL:"D MMMM YYYY \u0433., HH:mm",LLLL:"dddd, D MMMM YYYY \u0433., HH:mm"},calendar:{sameDay:"[\u0421\u0451\u043d\u043d\u044f \u045e] LT",nextDay:"[\u0417\u0430\u045e\u0442\u0440\u0430 \u045e] LT",lastDay:"[\u0423\u0447\u043e\u0440\u0430 \u045e] LT",nextWeek:function(){return"[\u0423] dddd [\u045e] LT"},lastWeek:function(){switch(this.day()){case 0:case 3:case 5:case 6:return"[\u0423 \u043c\u0456\u043d\u0443\u043b\u0443\u044e] dddd [\u045e] LT";case 1:case 2:case 4:return"[\u0423 \u043c\u0456\u043d\u0443\u043b\u044b] dddd [\u045e] LT"}},sameElse:"L"},relativeTime:{future:"\u043f\u0440\u0430\u0437 %s",past:"%s \u0442\u0430\u043c\u0443",s:"\u043d\u0435\u043a\u0430\u043b\u044c\u043a\u0456 \u0441\u0435\u043a\u0443\u043d\u0434",m:qt,mm:qt,h:qt,hh:qt,d:"\u0434\u0437\u0435\u043d\u044c",dd:qt,M:"\u043c\u0435\u0441\u044f\u0446",MM:qt,y:"\u0433\u043e\u0434",yy:qt},meridiemParse:/\u043d\u043e\u0447\u044b|\u0440\u0430\u043d\u0456\u0446\u044b|\u0434\u043d\u044f|\u0432\u0435\u0447\u0430\u0440\u0430/,isPM:function(e){return/^(\u0434\u043d\u044f|\u0432\u0435\u0447\u0430\u0440\u0430)$/.test(e)},meridiem:function(e,a,t){return e<4?"\u043d\u043e\u0447\u044b":e<12?"\u0440\u0430\u043d\u0456\u0446\u044b":e<17?"\u0434\u043d\u044f":"\u0432\u0435\u0447\u0430\u0440\u0430"},dayOfMonthOrdinalParse:/\d{1,2}-(\u0456|\u044b|\u0433\u0430)/,ordinal:function(e,a){switch(a){case"M":case"d":case"DDD":case"w":case"W":return e%10!=2&&e%10!=3||e%100==12||e%100==13?e+"-\u044b":e+"-\u0456";case"D":return e+"-\u0433\u0430";default:return e}},week:{dow:1,doy:7}}),M.defineLocale("bg",{months:"\u044f\u043d\u0443\u0430\u0440\u0438_\u0444\u0435\u0432\u0440\u0443\u0430\u0440\u0438_\u043c\u0430\u0440\u0442_\u0430\u043f\u0440\u0438\u043b_\u043c\u0430\u0439_\u044e\u043d\u0438_\u044e\u043b\u0438_\u0430\u0432\u0433\u0443\u0441\u0442_\u0441\u0435\u043f\u0442\u0435\u043c\u0432\u0440\u0438_\u043e\u043a\u0442\u043e\u043c\u0432\u0440\u0438_\u043d\u043e\u0435\u043c\u0432\u0440\u0438_\u0434\u0435\u043a\u0435\u043c\u0432\u0440\u0438".split("_"),monthsShort:"\u044f\u043d\u0443_\u0444\u0435\u0432_\u043c\u0430\u0440_\u0430\u043f\u0440_\u043c\u0430\u0439_\u044e\u043d\u0438_\u044e\u043b\u0438_\u0430\u0432\u0433_\u0441\u0435\u043f_\u043e\u043a\u0442_\u043d\u043e\u0435_\u0434\u0435\u043a".split("_"),weekdays:"\u043d\u0435\u0434\u0435\u043b\u044f_\u043f\u043e\u043d\u0435\u0434\u0435\u043b\u043d\u0438\u043a_\u0432\u0442\u043e\u0440\u043d\u0438\u043a_\u0441\u0440\u044f\u0434\u0430_\u0447\u0435\u0442\u0432\u044a\u0440\u0442\u044a\u043a_\u043f\u0435\u0442\u044a\u043a_\u0441\u044a\u0431\u043e\u0442\u0430".split("_"),weekdaysShort:"\u043d\u0435\u0434_\u043f\u043e\u043d_\u0432\u0442\u043e_\u0441\u0440\u044f_\u0447\u0435\u0442_\u043f\u0435\u0442_\u0441\u044a\u0431".split("_"),weekdaysMin:"\u043d\u0434_\u043f\u043d_\u0432\u0442_\u0441\u0440_\u0447\u0442_\u043f\u0442_\u0441\u0431".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"D.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY H:mm",LLLL:"dddd, D MMMM YYYY H:mm"},calendar:{sameDay:"[\u0414\u043d\u0435\u0441 \u0432] LT",nextDay:"[\u0423\u0442\u0440\u0435 \u0432] LT",nextWeek:"dddd [\u0432] LT",lastDay:"[\u0412\u0447\u0435\u0440\u0430 \u0432] LT",lastWeek:function(){switch(this.day()){case 0:case 3:case 6:return"[\u041c\u0438\u043d\u0430\u043b\u0430\u0442\u0430] dddd [\u0432] LT";case 1:case 2:case 4:case 5:return"[\u041c\u0438\u043d\u0430\u043b\u0438\u044f] dddd [\u0432] LT"}},sameElse:"L"},relativeTime:{future:"\u0441\u043b\u0435\u0434 %s",past:"\u043f\u0440\u0435\u0434\u0438 %s",s:"\u043d\u044f\u043a\u043e\u043b\u043a\u043e \u0441\u0435\u043a\u0443\u043d\u0434\u0438",ss:"%d \u0441\u0435\u043a\u0443\u043d\u0434\u0438",m:"\u043c\u0438\u043d\u0443\u0442\u0430",mm:"%d \u043c\u0438\u043d\u0443\u0442\u0438",h:"\u0447\u0430\u0441",hh:"%d \u0447\u0430\u0441\u0430",d:"\u0434\u0435\u043d",dd:"%d \u0434\u0435\u043d\u0430",w:"\u0441\u0435\u0434\u043c\u0438\u0446\u0430",ww:"%d \u0441\u0435\u0434\u043c\u0438\u0446\u0438",M:"\u043c\u0435\u0441\u0435\u0446",MM:"%d \u043c\u0435\u0441\u0435\u0446\u0430",y:"\u0433\u043e\u0434\u0438\u043d\u0430",yy:"%d \u0433\u043e\u0434\u0438\u043d\u0438"},dayOfMonthOrdinalParse:/\d{1,2}-(\u0435\u0432|\u0435\u043d|\u0442\u0438|\u0432\u0438|\u0440\u0438|\u043c\u0438)/,ordinal:function(e){var a=e%10,t=e%100;return 0===e?e+"-\u0435\u0432":0==t?e+"-\u0435\u043d":10<t&&t<20?e+"-\u0442\u0438":1==a?e+"-\u0432\u0438":2==a?e+"-\u0440\u0438":7==a||8==a?e+"-\u043c\u0438":e+"-\u0442\u0438"},week:{dow:1,doy:7}}),M.defineLocale("bm",{months:"Zanwuyekalo_Fewuruyekalo_Marisikalo_Awirilikalo_M\u025bkalo_Zuw\u025bnkalo_Zuluyekalo_Utikalo_S\u025btanburukalo_\u0254kut\u0254burukalo_Nowanburukalo_Desanburukalo".split("_"),monthsShort:"Zan_Few_Mar_Awi_M\u025b_Zuw_Zul_Uti_S\u025bt_\u0254ku_Now_Des".split("_"),weekdays:"Kari_Nt\u025bn\u025bn_Tarata_Araba_Alamisa_Juma_Sibiri".split("_"),weekdaysShort:"Kar_Nt\u025b_Tar_Ara_Ala_Jum_Sib".split("_"),weekdaysMin:"Ka_Nt_Ta_Ar_Al_Ju_Si".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"MMMM [tile] D [san] YYYY",LLL:"MMMM [tile] D [san] YYYY [l\u025br\u025b] HH:mm",LLLL:"dddd MMMM [tile] D [san] YYYY [l\u025br\u025b] HH:mm"},calendar:{sameDay:"[Bi l\u025br\u025b] LT",nextDay:"[Sini l\u025br\u025b] LT",nextWeek:"dddd [don l\u025br\u025b] LT",lastDay:"[Kunu l\u025br\u025b] LT",lastWeek:"dddd [t\u025bm\u025bnen l\u025br\u025b] LT",sameElse:"L"},relativeTime:{future:"%s k\u0254n\u0254",past:"a b\u025b %s b\u0254",s:"sanga dama dama",ss:"sekondi %d",m:"miniti kelen",mm:"miniti %d",h:"l\u025br\u025b kelen",hh:"l\u025br\u025b %d",d:"tile kelen",dd:"tile %d",M:"kalo kelen",MM:"kalo %d",y:"san kelen",yy:"san %d"},week:{dow:1,doy:4}});var Zt={1:"\u09e7",2:"\u09e8",3:"\u09e9",4:"\u09ea",5:"\u09eb",6:"\u09ec",7:"\u09ed",8:"\u09ee",9:"\u09ef",0:"\u09e6"},$t={"\u09e7":"1","\u09e8":"2","\u09e9":"3","\u09ea":"4","\u09eb":"5","\u09ec":"6","\u09ed":"7","\u09ee":"8","\u09ef":"9","\u09e6":"0"},Qt=(M.defineLocale("bn-bd",{months:"\u099c\u09be\u09a8\u09c1\u09df\u09be\u09b0\u09bf_\u09ab\u09c7\u09ac\u09cd\u09b0\u09c1\u09df\u09be\u09b0\u09bf_\u09ae\u09be\u09b0\u09cd\u099a_\u098f\u09aa\u09cd\u09b0\u09bf\u09b2_\u09ae\u09c7_\u099c\u09c1\u09a8_\u099c\u09c1\u09b2\u09be\u0987_\u0986\u0997\u09b8\u09cd\u099f_\u09b8\u09c7\u09aa\u09cd\u099f\u09c7\u09ae\u09cd\u09ac\u09b0_\u0985\u0995\u09cd\u099f\u09cb\u09ac\u09b0_\u09a8\u09ad\u09c7\u09ae\u09cd\u09ac\u09b0_\u09a1\u09bf\u09b8\u09c7\u09ae\u09cd\u09ac\u09b0".split("_"),monthsShort:"\u099c\u09be\u09a8\u09c1_\u09ab\u09c7\u09ac\u09cd\u09b0\u09c1_\u09ae\u09be\u09b0\u09cd\u099a_\u098f\u09aa\u09cd\u09b0\u09bf\u09b2_\u09ae\u09c7_\u099c\u09c1\u09a8_\u099c\u09c1\u09b2\u09be\u0987_\u0986\u0997\u09b8\u09cd\u099f_\u09b8\u09c7\u09aa\u09cd\u099f_\u0985\u0995\u09cd\u099f\u09cb_\u09a8\u09ad\u09c7_\u09a1\u09bf\u09b8\u09c7".split("_"),weekdays:"\u09b0\u09ac\u09bf\u09ac\u09be\u09b0_\u09b8\u09cb\u09ae\u09ac\u09be\u09b0_\u09ae\u0999\u09cd\u0997\u09b2\u09ac\u09be\u09b0_\u09ac\u09c1\u09a7\u09ac\u09be\u09b0_\u09ac\u09c3\u09b9\u09b8\u09cd\u09aa\u09a4\u09bf\u09ac\u09be\u09b0_\u09b6\u09c1\u0995\u09cd\u09b0\u09ac\u09be\u09b0_\u09b6\u09a8\u09bf\u09ac\u09be\u09b0".split("_"),weekdaysShort:"\u09b0\u09ac\u09bf_\u09b8\u09cb\u09ae_\u09ae\u0999\u09cd\u0997\u09b2_\u09ac\u09c1\u09a7_\u09ac\u09c3\u09b9\u09b8\u09cd\u09aa\u09a4\u09bf_\u09b6\u09c1\u0995\u09cd\u09b0_\u09b6\u09a8\u09bf".split("_"),weekdaysMin:"\u09b0\u09ac\u09bf_\u09b8\u09cb\u09ae_\u09ae\u0999\u09cd\u0997\u09b2_\u09ac\u09c1\u09a7_\u09ac\u09c3\u09b9_\u09b6\u09c1\u0995\u09cd\u09b0_\u09b6\u09a8\u09bf".split("_"),longDateFormat:{LT:"A h:mm \u09b8\u09ae\u09df",LTS:"A h:mm:ss \u09b8\u09ae\u09df",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm \u09b8\u09ae\u09df",LLLL:"dddd, D MMMM YYYY, A h:mm \u09b8\u09ae\u09df"},calendar:{sameDay:"[\u0986\u099c] LT",nextDay:"[\u0986\u0997\u09be\u09ae\u09c0\u0995\u09be\u09b2] LT",nextWeek:"dddd, LT",lastDay:"[\u0997\u09a4\u0995\u09be\u09b2] LT",lastWeek:"[\u0997\u09a4] dddd, LT",sameElse:"L"},relativeTime:{future:"%s \u09aa\u09b0\u09c7",past:"%s \u0986\u0997\u09c7",s:"\u0995\u09df\u09c7\u0995 \u09b8\u09c7\u0995\u09c7\u09a8\u09cd\u09a1",ss:"%d \u09b8\u09c7\u0995\u09c7\u09a8\u09cd\u09a1",m:"\u098f\u0995 \u09ae\u09bf\u09a8\u09bf\u099f",mm:"%d \u09ae\u09bf\u09a8\u09bf\u099f",h:"\u098f\u0995 \u0998\u09a8\u09cd\u099f\u09be",hh:"%d \u0998\u09a8\u09cd\u099f\u09be",d:"\u098f\u0995 \u09a6\u09bf\u09a8",dd:"%d \u09a6\u09bf\u09a8",M:"\u098f\u0995 \u09ae\u09be\u09b8",MM:"%d \u09ae\u09be\u09b8",y:"\u098f\u0995 \u09ac\u099b\u09b0",yy:"%d \u09ac\u099b\u09b0"},preparse:function(e){return e.replace(/[\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u09e6]/g,function(e){return $t[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return Zt[e]})},meridiemParse:/\u09b0\u09be\u09a4|\u09ad\u09cb\u09b0|\u09b8\u0995\u09be\u09b2|\u09a6\u09c1\u09aa\u09c1\u09b0|\u09ac\u09bf\u0995\u09be\u09b2|\u09b8\u09a8\u09cd\u09a7\u09cd\u09af\u09be|\u09b0\u09be\u09a4/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u09b0\u09be\u09a4"===a?e<4?e:e+12:"\u09ad\u09cb\u09b0"===a||"\u09b8\u0995\u09be\u09b2"===a?e:"\u09a6\u09c1\u09aa\u09c1\u09b0"===a?3<=e?e:e+12:"\u09ac\u09bf\u0995\u09be\u09b2"===a||"\u09b8\u09a8\u09cd\u09a7\u09cd\u09af\u09be"===a?e+12:void 0},meridiem:function(e,a,t){return e<4?"\u09b0\u09be\u09a4":e<6?"\u09ad\u09cb\u09b0":e<12?"\u09b8\u0995\u09be\u09b2":e<15?"\u09a6\u09c1\u09aa\u09c1\u09b0":e<18?"\u09ac\u09bf\u0995\u09be\u09b2":e<20?"\u09b8\u09a8\u09cd\u09a7\u09cd\u09af\u09be":"\u09b0\u09be\u09a4"},week:{dow:0,doy:6}}),{1:"\u09e7",2:"\u09e8",3:"\u09e9",4:"\u09ea",5:"\u09eb",6:"\u09ec",7:"\u09ed",8:"\u09ee",9:"\u09ef",0:"\u09e6"}),Xt={"\u09e7":"1","\u09e8":"2","\u09e9":"3","\u09ea":"4","\u09eb":"5","\u09ec":"6","\u09ed":"7","\u09ee":"8","\u09ef":"9","\u09e6":"0"},es=(M.defineLocale("bn",{months:"\u099c\u09be\u09a8\u09c1\u09df\u09be\u09b0\u09bf_\u09ab\u09c7\u09ac\u09cd\u09b0\u09c1\u09df\u09be\u09b0\u09bf_\u09ae\u09be\u09b0\u09cd\u099a_\u098f\u09aa\u09cd\u09b0\u09bf\u09b2_\u09ae\u09c7_\u099c\u09c1\u09a8_\u099c\u09c1\u09b2\u09be\u0987_\u0986\u0997\u09b8\u09cd\u099f_\u09b8\u09c7\u09aa\u09cd\u099f\u09c7\u09ae\u09cd\u09ac\u09b0_\u0985\u0995\u09cd\u099f\u09cb\u09ac\u09b0_\u09a8\u09ad\u09c7\u09ae\u09cd\u09ac\u09b0_\u09a1\u09bf\u09b8\u09c7\u09ae\u09cd\u09ac\u09b0".split("_"),monthsShort:"\u099c\u09be\u09a8\u09c1_\u09ab\u09c7\u09ac\u09cd\u09b0\u09c1_\u09ae\u09be\u09b0\u09cd\u099a_\u098f\u09aa\u09cd\u09b0\u09bf\u09b2_\u09ae\u09c7_\u099c\u09c1\u09a8_\u099c\u09c1\u09b2\u09be\u0987_\u0986\u0997\u09b8\u09cd\u099f_\u09b8\u09c7\u09aa\u09cd\u099f_\u0985\u0995\u09cd\u099f\u09cb_\u09a8\u09ad\u09c7_\u09a1\u09bf\u09b8\u09c7".split("_"),weekdays:"\u09b0\u09ac\u09bf\u09ac\u09be\u09b0_\u09b8\u09cb\u09ae\u09ac\u09be\u09b0_\u09ae\u0999\u09cd\u0997\u09b2\u09ac\u09be\u09b0_\u09ac\u09c1\u09a7\u09ac\u09be\u09b0_\u09ac\u09c3\u09b9\u09b8\u09cd\u09aa\u09a4\u09bf\u09ac\u09be\u09b0_\u09b6\u09c1\u0995\u09cd\u09b0\u09ac\u09be\u09b0_\u09b6\u09a8\u09bf\u09ac\u09be\u09b0".split("_"),weekdaysShort:"\u09b0\u09ac\u09bf_\u09b8\u09cb\u09ae_\u09ae\u0999\u09cd\u0997\u09b2_\u09ac\u09c1\u09a7_\u09ac\u09c3\u09b9\u09b8\u09cd\u09aa\u09a4\u09bf_\u09b6\u09c1\u0995\u09cd\u09b0_\u09b6\u09a8\u09bf".split("_"),weekdaysMin:"\u09b0\u09ac\u09bf_\u09b8\u09cb\u09ae_\u09ae\u0999\u09cd\u0997\u09b2_\u09ac\u09c1\u09a7_\u09ac\u09c3\u09b9_\u09b6\u09c1\u0995\u09cd\u09b0_\u09b6\u09a8\u09bf".split("_"),longDateFormat:{LT:"A h:mm \u09b8\u09ae\u09df",LTS:"A h:mm:ss \u09b8\u09ae\u09df",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm \u09b8\u09ae\u09df",LLLL:"dddd, D MMMM YYYY, A h:mm \u09b8\u09ae\u09df"},calendar:{sameDay:"[\u0986\u099c] LT",nextDay:"[\u0986\u0997\u09be\u09ae\u09c0\u0995\u09be\u09b2] LT",nextWeek:"dddd, LT",lastDay:"[\u0997\u09a4\u0995\u09be\u09b2] LT",lastWeek:"[\u0997\u09a4] dddd, LT",sameElse:"L"},relativeTime:{future:"%s \u09aa\u09b0\u09c7",past:"%s \u0986\u0997\u09c7",s:"\u0995\u09df\u09c7\u0995 \u09b8\u09c7\u0995\u09c7\u09a8\u09cd\u09a1",ss:"%d \u09b8\u09c7\u0995\u09c7\u09a8\u09cd\u09a1",m:"\u098f\u0995 \u09ae\u09bf\u09a8\u09bf\u099f",mm:"%d \u09ae\u09bf\u09a8\u09bf\u099f",h:"\u098f\u0995 \u0998\u09a8\u09cd\u099f\u09be",hh:"%d \u0998\u09a8\u09cd\u099f\u09be",d:"\u098f\u0995 \u09a6\u09bf\u09a8",dd:"%d \u09a6\u09bf\u09a8",M:"\u098f\u0995 \u09ae\u09be\u09b8",MM:"%d \u09ae\u09be\u09b8",y:"\u098f\u0995 \u09ac\u099b\u09b0",yy:"%d \u09ac\u099b\u09b0"},preparse:function(e){return e.replace(/[\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u09e6]/g,function(e){return Xt[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return Qt[e]})},meridiemParse:/\u09b0\u09be\u09a4|\u09b8\u0995\u09be\u09b2|\u09a6\u09c1\u09aa\u09c1\u09b0|\u09ac\u09bf\u0995\u09be\u09b2|\u09b0\u09be\u09a4/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u09b0\u09be\u09a4"===a&&4<=e||"\u09a6\u09c1\u09aa\u09c1\u09b0"===a&&e<5||"\u09ac\u09bf\u0995\u09be\u09b2"===a?e+12:e},meridiem:function(e,a,t){return e<4?"\u09b0\u09be\u09a4":e<10?"\u09b8\u0995\u09be\u09b2":e<17?"\u09a6\u09c1\u09aa\u09c1\u09b0":e<20?"\u09ac\u09bf\u0995\u09be\u09b2":"\u09b0\u09be\u09a4"},week:{dow:0,doy:6}}),{1:"\u0f21",2:"\u0f22",3:"\u0f23",4:"\u0f24",5:"\u0f25",6:"\u0f26",7:"\u0f27",8:"\u0f28",9:"\u0f29",0:"\u0f20"}),as={"\u0f21":"1","\u0f22":"2","\u0f23":"3","\u0f24":"4","\u0f25":"5","\u0f26":"6","\u0f27":"7","\u0f28":"8","\u0f29":"9","\u0f20":"0"};function ts(e,a,t){return e+" "+(t={mm:"munutenn",MM:"miz",dd:"devezh"}[t],2!==(e=e)?t:void 0!==(e={m:"v",b:"v",d:"z"})[(t=t).charAt(0)]?e[t.charAt(0)]+t.substring(1):t)}M.defineLocale("bo",{months:"\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f51\u0f44\u0f0b\u0f54\u0f7c_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f42\u0f49\u0f72\u0f66\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f42\u0f66\u0f74\u0f58\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f5e\u0f72\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f63\u0f94\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f51\u0fb2\u0f74\u0f42\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f51\u0f74\u0f53\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f62\u0f92\u0fb1\u0f51\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f51\u0f42\u0f74\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f45\u0f74\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f45\u0f74\u0f0b\u0f42\u0f45\u0f72\u0f42\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f45\u0f74\u0f0b\u0f42\u0f49\u0f72\u0f66\u0f0b\u0f54".split("_"),monthsShort:"\u0f5f\u0fb3\u0f0b1_\u0f5f\u0fb3\u0f0b2_\u0f5f\u0fb3\u0f0b3_\u0f5f\u0fb3\u0f0b4_\u0f5f\u0fb3\u0f0b5_\u0f5f\u0fb3\u0f0b6_\u0f5f\u0fb3\u0f0b7_\u0f5f\u0fb3\u0f0b8_\u0f5f\u0fb3\u0f0b9_\u0f5f\u0fb3\u0f0b10_\u0f5f\u0fb3\u0f0b11_\u0f5f\u0fb3\u0f0b12".split("_"),monthsShortRegex:/^(\u0f5f\u0fb3\u0f0b\d{1,2})/,monthsParseExact:!0,weekdays:"\u0f42\u0f5f\u0f60\u0f0b\u0f49\u0f72\u0f0b\u0f58\u0f0b_\u0f42\u0f5f\u0f60\u0f0b\u0f5f\u0fb3\u0f0b\u0f56\u0f0b_\u0f42\u0f5f\u0f60\u0f0b\u0f58\u0f72\u0f42\u0f0b\u0f51\u0f58\u0f62\u0f0b_\u0f42\u0f5f\u0f60\u0f0b\u0f63\u0fb7\u0f42\u0f0b\u0f54\u0f0b_\u0f42\u0f5f\u0f60\u0f0b\u0f55\u0f74\u0f62\u0f0b\u0f56\u0f74_\u0f42\u0f5f\u0f60\u0f0b\u0f54\u0f0b\u0f66\u0f44\u0f66\u0f0b_\u0f42\u0f5f\u0f60\u0f0b\u0f66\u0fa4\u0f7a\u0f53\u0f0b\u0f54\u0f0b".split("_"),weekdaysShort:"\u0f49\u0f72\u0f0b\u0f58\u0f0b_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b_\u0f58\u0f72\u0f42\u0f0b\u0f51\u0f58\u0f62\u0f0b_\u0f63\u0fb7\u0f42\u0f0b\u0f54\u0f0b_\u0f55\u0f74\u0f62\u0f0b\u0f56\u0f74_\u0f54\u0f0b\u0f66\u0f44\u0f66\u0f0b_\u0f66\u0fa4\u0f7a\u0f53\u0f0b\u0f54\u0f0b".split("_"),weekdaysMin:"\u0f49\u0f72_\u0f5f\u0fb3_\u0f58\u0f72\u0f42_\u0f63\u0fb7\u0f42_\u0f55\u0f74\u0f62_\u0f66\u0f44\u0f66_\u0f66\u0fa4\u0f7a\u0f53".split("_"),longDateFormat:{LT:"A h:mm",LTS:"A h:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm",LLLL:"dddd, D MMMM YYYY, A h:mm"},calendar:{sameDay:"[\u0f51\u0f72\u0f0b\u0f62\u0f72\u0f44] LT",nextDay:"[\u0f66\u0f44\u0f0b\u0f49\u0f72\u0f53] LT",nextWeek:"[\u0f56\u0f51\u0f74\u0f53\u0f0b\u0f55\u0fb2\u0f42\u0f0b\u0f62\u0f97\u0f7a\u0f66\u0f0b\u0f58], LT",lastDay:"[\u0f41\u0f0b\u0f66\u0f44] LT",lastWeek:"[\u0f56\u0f51\u0f74\u0f53\u0f0b\u0f55\u0fb2\u0f42\u0f0b\u0f58\u0f50\u0f60\u0f0b\u0f58] dddd, LT",sameElse:"L"},relativeTime:{future:"%s \u0f63\u0f0b",past:"%s \u0f66\u0f94\u0f53\u0f0b\u0f63",s:"\u0f63\u0f58\u0f0b\u0f66\u0f44",ss:"%d \u0f66\u0f90\u0f62\u0f0b\u0f46\u0f0d",m:"\u0f66\u0f90\u0f62\u0f0b\u0f58\u0f0b\u0f42\u0f45\u0f72\u0f42",mm:"%d \u0f66\u0f90\u0f62\u0f0b\u0f58",h:"\u0f46\u0f74\u0f0b\u0f5a\u0f7c\u0f51\u0f0b\u0f42\u0f45\u0f72\u0f42",hh:"%d \u0f46\u0f74\u0f0b\u0f5a\u0f7c\u0f51",d:"\u0f49\u0f72\u0f53\u0f0b\u0f42\u0f45\u0f72\u0f42",dd:"%d \u0f49\u0f72\u0f53\u0f0b",M:"\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f42\u0f45\u0f72\u0f42",MM:"%d \u0f5f\u0fb3\u0f0b\u0f56",y:"\u0f63\u0f7c\u0f0b\u0f42\u0f45\u0f72\u0f42",yy:"%d \u0f63\u0f7c"},preparse:function(e){return e.replace(/[\u0f21\u0f22\u0f23\u0f24\u0f25\u0f26\u0f27\u0f28\u0f29\u0f20]/g,function(e){return as[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return es[e]})},meridiemParse:/\u0f58\u0f5a\u0f53\u0f0b\u0f58\u0f7c|\u0f5e\u0f7c\u0f42\u0f66\u0f0b\u0f40\u0f66|\u0f49\u0f72\u0f53\u0f0b\u0f42\u0f74\u0f44|\u0f51\u0f42\u0f7c\u0f44\u0f0b\u0f51\u0f42|\u0f58\u0f5a\u0f53\u0f0b\u0f58\u0f7c/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u0f58\u0f5a\u0f53\u0f0b\u0f58\u0f7c"===a&&4<=e||"\u0f49\u0f72\u0f53\u0f0b\u0f42\u0f74\u0f44"===a&&e<5||"\u0f51\u0f42\u0f7c\u0f44\u0f0b\u0f51\u0f42"===a?e+12:e},meridiem:function(e,a,t){return e<4?"\u0f58\u0f5a\u0f53\u0f0b\u0f58\u0f7c":e<10?"\u0f5e\u0f7c\u0f42\u0f66\u0f0b\u0f40\u0f66":e<17?"\u0f49\u0f72\u0f53\u0f0b\u0f42\u0f74\u0f44":e<20?"\u0f51\u0f42\u0f7c\u0f44\u0f0b\u0f51\u0f42":"\u0f58\u0f5a\u0f53\u0f0b\u0f58\u0f7c"},week:{dow:0,doy:6}});L=[/^gen/i,/^c[\u02bc\']hwe/i,/^meu/i,/^ebr/i,/^mae/i,/^(mez|eve)/i,/^gou/i,/^eos/i,/^gwe/i,/^her/i,/^du/i,/^ker/i],c=/^(genver|c[\u02bc\']hwevrer|meurzh|ebrel|mae|mezheven|gouere|eost|gwengolo|here|du|kerzu|gen|c[\u02bc\']hwe|meu|ebr|mae|eve|gou|eos|gwe|her|du|ker)/i,ta=[/^Su/i,/^Lu/i,/^Me([^r]|$)/i,/^Mer/i,/^Ya/i,/^Gw/i,/^Sa/i];function ss(e,a,t){var s=e+" ";switch(t){case"ss":return s+=1===e?"sekunda":2===e||3===e||4===e?"sekunde":"sekundi";case"m":return a?"jedna minuta":"jedne minute";case"mm":return s+=1!==e&&(2===e||3===e||4===e)?"minute":"minuta";case"h":return a?"jedan sat":"jednog sata";case"hh":return s+=1===e?"sat":2===e||3===e||4===e?"sata":"sati";case"dd":return s+=1===e?"dan":"dana";case"MM":return s+=1===e?"mjesec":2===e||3===e||4===e?"mjeseca":"mjeseci";case"yy":return s+=1!==e&&(2===e||3===e||4===e)?"godine":"godina"}}M.defineLocale("br",{months:"Genver_C\u02bchwevrer_Meurzh_Ebrel_Mae_Mezheven_Gouere_Eost_Gwengolo_Here_Du_Kerzu".split("_"),monthsShort:"Gen_C\u02bchwe_Meu_Ebr_Mae_Eve_Gou_Eos_Gwe_Her_Du_Ker".split("_"),weekdays:"Sul_Lun_Meurzh_Merc\u02bcher_Yaou_Gwener_Sadorn".split("_"),weekdaysShort:"Sul_Lun_Meu_Mer_Yao_Gwe_Sad".split("_"),weekdaysMin:"Su_Lu_Me_Mer_Ya_Gw_Sa".split("_"),weekdaysParse:ta,fullWeekdaysParse:[/^sul/i,/^lun/i,/^meurzh/i,/^merc[\u02bc\']her/i,/^yaou/i,/^gwener/i,/^sadorn/i],shortWeekdaysParse:[/^Sul/i,/^Lun/i,/^Meu/i,/^Mer/i,/^Yao/i,/^Gwe/i,/^Sad/i],minWeekdaysParse:ta,monthsRegex:c,monthsShortRegex:c,monthsStrictRegex:/^(genver|c[\u02bc\']hwevrer|meurzh|ebrel|mae|mezheven|gouere|eost|gwengolo|here|du|kerzu)/i,monthsShortStrictRegex:/^(gen|c[\u02bc\']hwe|meu|ebr|mae|eve|gou|eos|gwe|her|du|ker)/i,monthsParse:L,longMonthsParse:L,shortMonthsParse:L,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D [a viz] MMMM YYYY",LLL:"D [a viz] MMMM YYYY HH:mm",LLLL:"dddd, D [a viz] MMMM YYYY HH:mm"},calendar:{sameDay:"[Hiziv da] LT",nextDay:"[Warc\u02bchoazh da] LT",nextWeek:"dddd [da] LT",lastDay:"[Dec\u02bch da] LT",lastWeek:"dddd [paset da] LT",sameElse:"L"},relativeTime:{future:"a-benn %s",past:"%s \u02bczo",s:"un nebeud segondenno\xf9",ss:"%d eilenn",m:"ur vunutenn",mm:ts,h:"un eur",hh:"%d eur",d:"un devezh",dd:ts,M:"ur miz",MM:ts,y:"ur bloaz",yy:function(e){switch(function e(a){if(9<a)return e(a%10);return a}(e)){case 1:case 3:case 4:case 5:case 9:return e+" bloaz";default:return e+" vloaz"}}},dayOfMonthOrdinalParse:/\d{1,2}(a\xf1|vet)/,ordinal:function(e){return e+(1===e?"a\xf1":"vet")},week:{dow:1,doy:4},meridiemParse:/a.m.|g.m./,isPM:function(e){return"g.m."===e},meridiem:function(e,a,t){return e<12?"a.m.":"g.m."}}),M.defineLocale("bs",{months:"januar_februar_mart_april_maj_juni_juli_august_septembar_oktobar_novembar_decembar".split("_"),monthsShort:"jan._feb._mar._apr._maj._jun._jul._aug._sep._okt._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"nedjelja_ponedjeljak_utorak_srijeda_\u010detvrtak_petak_subota".split("_"),weekdaysShort:"ned._pon._uto._sri._\u010det._pet._sub.".split("_"),weekdaysMin:"ne_po_ut_sr_\u010de_pe_su".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedjelju] [u] LT";case 3:return"[u] [srijedu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[ju\u010der u] LT",lastWeek:function(){switch(this.day()){case 0:case 3:return"[pro\u0161lu] dddd [u] LT";case 6:return"[pro\u0161le] [subote] [u] LT";case 1:case 2:case 4:case 5:return"[pro\u0161li] dddd [u] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"prije %s",s:"par sekundi",ss:ss,m:ss,mm:ss,h:ss,hh:ss,d:"dan",dd:ss,M:"mjesec",MM:ss,y:"godinu",yy:ss},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}}),M.defineLocale("ca",{months:{standalone:"gener_febrer_mar\xe7_abril_maig_juny_juliol_agost_setembre_octubre_novembre_desembre".split("_"),format:"de gener_de febrer_de mar\xe7_d'abril_de maig_de juny_de juliol_d'agost_de setembre_d'octubre_de novembre_de desembre".split("_"),isFormat:/D[oD]?(\s)+MMMM/},monthsShort:"gen._febr._mar\xe7_abr._maig_juny_jul._ag._set._oct._nov._des.".split("_"),monthsParseExact:!0,weekdays:"diumenge_dilluns_dimarts_dimecres_dijous_divendres_dissabte".split("_"),weekdaysShort:"dg._dl._dt._dc._dj._dv._ds.".split("_"),weekdaysMin:"dg_dl_dt_dc_dj_dv_ds".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM [de] YYYY",ll:"D MMM YYYY",LLL:"D MMMM [de] YYYY [a les] H:mm",lll:"D MMM YYYY, H:mm",LLLL:"dddd D MMMM [de] YYYY [a les] H:mm",llll:"ddd D MMM YYYY, H:mm"},calendar:{sameDay:function(){return"[avui a "+(1!==this.hours()?"les":"la")+"] LT"},nextDay:function(){return"[dem\xe0 a "+(1!==this.hours()?"les":"la")+"] LT"},nextWeek:function(){return"dddd [a "+(1!==this.hours()?"les":"la")+"] LT"},lastDay:function(){return"[ahir a "+(1!==this.hours()?"les":"la")+"] LT"},lastWeek:function(){return"[el] dddd [passat a "+(1!==this.hours()?"les":"la")+"] LT"},sameElse:"L"},relativeTime:{future:"d'aqu\xed %s",past:"fa %s",s:"uns segons",ss:"%d segons",m:"un minut",mm:"%d minuts",h:"una hora",hh:"%d hores",d:"un dia",dd:"%d dies",M:"un mes",MM:"%d mesos",y:"un any",yy:"%d anys"},dayOfMonthOrdinalParse:/\d{1,2}(r|n|t|\xe8|a)/,ordinal:function(e,a){return e+("w"!==a&&"W"!==a?1===e?"r":2===e?"n":3===e?"r":4===e?"t":"\xe8":"a")},week:{dow:1,doy:4}});f={format:"leden_\xfanor_b\u0159ezen_duben_kv\u011bten_\u010derven_\u010dervenec_srpen_z\xe1\u0159\xed_\u0159\xedjen_listopad_prosinec".split("_"),standalone:"ledna_\xfanora_b\u0159ezna_dubna_kv\u011btna_\u010dervna_\u010dervence_srpna_z\xe1\u0159\xed_\u0159\xedjna_listopadu_prosince".split("_")},ke="led_\xfano_b\u0159e_dub_kv\u011b_\u010dvn_\u010dvc_srp_z\xe1\u0159_\u0159\xedj_lis_pro".split("_"),y=[/^led/i,/^\xfano/i,/^b\u0159e/i,/^dub/i,/^kv\u011b/i,/^(\u010dvn|\u010derven$|\u010dervna)/i,/^(\u010dvc|\u010dervenec|\u010dervence)/i,/^srp/i,/^z\xe1\u0159/i,/^\u0159\xedj/i,/^lis/i,/^pro/i],_=/^(leden|\xfanor|b\u0159ezen|duben|kv\u011bten|\u010dervenec|\u010dervence|\u010derven|\u010dervna|srpen|z\xe1\u0159\xed|\u0159\xedjen|listopad|prosinec|led|\xfano|b\u0159e|dub|kv\u011b|\u010dvn|\u010dvc|srp|z\xe1\u0159|\u0159\xedj|lis|pro)/i;function ns(e){return 1<e&&e<5&&1!=~~(e/10)}function P(e,a,t,s){var n=e+" ";switch(t){case"s":return a||s?"p\xe1r sekund":"p\xe1r sekundami";case"ss":return a||s?n+(ns(e)?"sekundy":"sekund"):n+"sekundami";case"m":return a?"minuta":s?"minutu":"minutou";case"mm":return a||s?n+(ns(e)?"minuty":"minut"):n+"minutami";case"h":return a?"hodina":s?"hodinu":"hodinou";case"hh":return a||s?n+(ns(e)?"hodiny":"hodin"):n+"hodinami";case"d":return a||s?"den":"dnem";case"dd":return a||s?n+(ns(e)?"dny":"dn\xed"):n+"dny";case"M":return a||s?"m\u011bs\xedc":"m\u011bs\xedcem";case"MM":return a||s?n+(ns(e)?"m\u011bs\xedce":"m\u011bs\xedc\u016f"):n+"m\u011bs\xedci";case"y":return a||s?"rok":"rokem";case"yy":return a||s?n+(ns(e)?"roky":"let"):n+"lety"}}function rs(e,a,t,s){e={m:["eine Minute","einer Minute"],h:["eine Stunde","einer Stunde"],d:["ein Tag","einem Tag"],dd:[e+" Tage",e+" Tagen"],w:["eine Woche","einer Woche"],M:["ein Monat","einem Monat"],MM:[e+" Monate",e+" Monaten"],y:["ein Jahr","einem Jahr"],yy:[e+" Jahre",e+" Jahren"]};return a?e[t][0]:e[t][1]}function ds(e,a,t,s){e={m:["eine Minute","einer Minute"],h:["eine Stunde","einer Stunde"],d:["ein Tag","einem Tag"],dd:[e+" Tage",e+" Tagen"],w:["eine Woche","einer Woche"],M:["ein Monat","einem Monat"],MM:[e+" Monate",e+" Monaten"],y:["ein Jahr","einem Jahr"],yy:[e+" Jahre",e+" Jahren"]};return a?e[t][0]:e[t][1]}function is(e,a,t,s){e={m:["eine Minute","einer Minute"],h:["eine Stunde","einer Stunde"],d:["ein Tag","einem Tag"],dd:[e+" Tage",e+" Tagen"],w:["eine Woche","einer Woche"],M:["ein Monat","einem Monat"],MM:[e+" Monate",e+" Monaten"],y:["ein Jahr","einem Jahr"],yy:[e+" Jahre",e+" Jahren"]};return a?e[t][0]:e[t][1]}M.defineLocale("cs",{months:f,monthsShort:ke,monthsRegex:_,monthsShortRegex:_,monthsStrictRegex:/^(leden|ledna|\xfanora|\xfanor|b\u0159ezen|b\u0159ezna|duben|dubna|kv\u011bten|kv\u011btna|\u010dervenec|\u010dervence|\u010derven|\u010dervna|srpen|srpna|z\xe1\u0159\xed|\u0159\xedjen|\u0159\xedjna|listopadu|listopad|prosinec|prosince)/i,monthsShortStrictRegex:/^(led|\xfano|b\u0159e|dub|kv\u011b|\u010dvn|\u010dvc|srp|z\xe1\u0159|\u0159\xedj|lis|pro)/i,monthsParse:y,longMonthsParse:y,shortMonthsParse:y,weekdays:"ned\u011ble_pond\u011bl\xed_\xfater\xfd_st\u0159eda_\u010dtvrtek_p\xe1tek_sobota".split("_"),weekdaysShort:"ne_po_\xfat_st_\u010dt_p\xe1_so".split("_"),weekdaysMin:"ne_po_\xfat_st_\u010dt_p\xe1_so".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd D. MMMM YYYY H:mm",l:"D. M. YYYY"},calendar:{sameDay:"[dnes v] LT",nextDay:"[z\xedtra v] LT",nextWeek:function(){switch(this.day()){case 0:return"[v ned\u011bli v] LT";case 1:case 2:return"[v] dddd [v] LT";case 3:return"[ve st\u0159edu v] LT";case 4:return"[ve \u010dtvrtek v] LT";case 5:return"[v p\xe1tek v] LT";case 6:return"[v sobotu v] LT"}},lastDay:"[v\u010dera v] LT",lastWeek:function(){switch(this.day()){case 0:return"[minulou ned\u011bli v] LT";case 1:case 2:return"[minul\xe9] dddd [v] LT";case 3:return"[minulou st\u0159edu v] LT";case 4:case 5:return"[minul\xfd] dddd [v] LT";case 6:return"[minulou sobotu v] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"p\u0159ed %s",s:P,ss:P,m:P,mm:P,h:P,hh:P,d:P,dd:P,M:P,MM:P,y:P,yy:P},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),M.defineLocale("cv",{months:"\u043a\u04d1\u0440\u043b\u0430\u0447_\u043d\u0430\u0440\u04d1\u0441_\u043f\u0443\u0448_\u0430\u043a\u0430_\u043c\u0430\u0439_\u04ab\u04d7\u0440\u0442\u043c\u0435_\u0443\u0442\u04d1_\u04ab\u0443\u0440\u043b\u0430_\u0430\u0432\u04d1\u043d_\u044e\u043f\u0430_\u0447\u04f3\u043a_\u0440\u0430\u0448\u0442\u0430\u0432".split("_"),monthsShort:"\u043a\u04d1\u0440_\u043d\u0430\u0440_\u043f\u0443\u0448_\u0430\u043a\u0430_\u043c\u0430\u0439_\u04ab\u04d7\u0440_\u0443\u0442\u04d1_\u04ab\u0443\u0440_\u0430\u0432\u043d_\u044e\u043f\u0430_\u0447\u04f3\u043a_\u0440\u0430\u0448".split("_"),weekdays:"\u0432\u044b\u0440\u0441\u0430\u0440\u043d\u0438\u043a\u0443\u043d_\u0442\u0443\u043d\u0442\u0438\u043a\u0443\u043d_\u044b\u0442\u043b\u0430\u0440\u0438\u043a\u0443\u043d_\u044e\u043d\u043a\u0443\u043d_\u043a\u04d7\u04ab\u043d\u0435\u0440\u043d\u0438\u043a\u0443\u043d_\u044d\u0440\u043d\u0435\u043a\u0443\u043d_\u0448\u04d1\u043c\u0430\u0442\u043a\u0443\u043d".split("_"),weekdaysShort:"\u0432\u044b\u0440_\u0442\u0443\u043d_\u044b\u0442\u043b_\u044e\u043d_\u043a\u04d7\u04ab_\u044d\u0440\u043d_\u0448\u04d1\u043c".split("_"),weekdaysMin:"\u0432\u0440_\u0442\u043d_\u044b\u0442_\u044e\u043d_\u043a\u04ab_\u044d\u0440_\u0448\u043c".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD-MM-YYYY",LL:"YYYY [\u04ab\u0443\u043b\u0445\u0438] MMMM [\u0443\u0439\u04d1\u0445\u04d7\u043d] D[-\u043c\u04d7\u0448\u04d7]",LLL:"YYYY [\u04ab\u0443\u043b\u0445\u0438] MMMM [\u0443\u0439\u04d1\u0445\u04d7\u043d] D[-\u043c\u04d7\u0448\u04d7], HH:mm",LLLL:"dddd, YYYY [\u04ab\u0443\u043b\u0445\u0438] MMMM [\u0443\u0439\u04d1\u0445\u04d7\u043d] D[-\u043c\u04d7\u0448\u04d7], HH:mm"},calendar:{sameDay:"[\u041f\u0430\u044f\u043d] LT [\u0441\u0435\u0445\u0435\u0442\u0440\u0435]",nextDay:"[\u042b\u0440\u0430\u043d] LT [\u0441\u0435\u0445\u0435\u0442\u0440\u0435]",lastDay:"[\u04d6\u043d\u0435\u0440] LT [\u0441\u0435\u0445\u0435\u0442\u0440\u0435]",nextWeek:"[\u04aa\u0438\u0442\u0435\u0441] dddd LT [\u0441\u0435\u0445\u0435\u0442\u0440\u0435]",lastWeek:"[\u0418\u0440\u0442\u043d\u04d7] dddd LT [\u0441\u0435\u0445\u0435\u0442\u0440\u0435]",sameElse:"L"},relativeTime:{future:function(e){return e+(/\u0441\u0435\u0445\u0435\u0442$/i.exec(e)?"\u0440\u0435\u043d":/\u04ab\u0443\u043b$/i.exec(e)?"\u0442\u0430\u043d":"\u0440\u0430\u043d")},past:"%s \u043a\u0430\u044f\u043b\u043b\u0430",s:"\u043f\u04d7\u0440-\u0438\u043a \u04ab\u0435\u043a\u043a\u0443\u043d\u0442",ss:"%d \u04ab\u0435\u043a\u043a\u0443\u043d\u0442",m:"\u043f\u04d7\u0440 \u043c\u0438\u043d\u0443\u0442",mm:"%d \u043c\u0438\u043d\u0443\u0442",h:"\u043f\u04d7\u0440 \u0441\u0435\u0445\u0435\u0442",hh:"%d \u0441\u0435\u0445\u0435\u0442",d:"\u043f\u04d7\u0440 \u043a\u0443\u043d",dd:"%d \u043a\u0443\u043d",M:"\u043f\u04d7\u0440 \u0443\u0439\u04d1\u0445",MM:"%d \u0443\u0439\u04d1\u0445",y:"\u043f\u04d7\u0440 \u04ab\u0443\u043b",yy:"%d \u04ab\u0443\u043b"},dayOfMonthOrdinalParse:/\d{1,2}-\u043c\u04d7\u0448/,ordinal:"%d-\u043c\u04d7\u0448",week:{dow:1,doy:7}}),M.defineLocale("cy",{months:"Ionawr_Chwefror_Mawrth_Ebrill_Mai_Mehefin_Gorffennaf_Awst_Medi_Hydref_Tachwedd_Rhagfyr".split("_"),monthsShort:"Ion_Chwe_Maw_Ebr_Mai_Meh_Gor_Aws_Med_Hyd_Tach_Rhag".split("_"),weekdays:"Dydd Sul_Dydd Llun_Dydd Mawrth_Dydd Mercher_Dydd Iau_Dydd Gwener_Dydd Sadwrn".split("_"),weekdaysShort:"Sul_Llun_Maw_Mer_Iau_Gwe_Sad".split("_"),weekdaysMin:"Su_Ll_Ma_Me_Ia_Gw_Sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Heddiw am] LT",nextDay:"[Yfory am] LT",nextWeek:"dddd [am] LT",lastDay:"[Ddoe am] LT",lastWeek:"dddd [diwethaf am] LT",sameElse:"L"},relativeTime:{future:"mewn %s",past:"%s yn \xf4l",s:"ychydig eiliadau",ss:"%d eiliad",m:"munud",mm:"%d munud",h:"awr",hh:"%d awr",d:"diwrnod",dd:"%d diwrnod",M:"mis",MM:"%d mis",y:"blwyddyn",yy:"%d flynedd"},dayOfMonthOrdinalParse:/\d{1,2}(fed|ain|af|il|ydd|ed|eg)/,ordinal:function(e){var a="";return 20<e?a=40===e||50===e||60===e||80===e||100===e?"fed":"ain":0<e&&(a=["","af","il","ydd","ydd","ed","ed","ed","fed","fed","fed","eg","fed","eg","eg","fed","eg","eg","fed","eg","fed"][e]),e+a},week:{dow:1,doy:4}}),M.defineLocale("da",{months:"januar_februar_marts_april_maj_juni_juli_august_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"s\xf8ndag_mandag_tirsdag_onsdag_torsdag_fredag_l\xf8rdag".split("_"),weekdaysShort:"s\xf8n_man_tir_ons_tor_fre_l\xf8r".split("_"),weekdaysMin:"s\xf8_ma_ti_on_to_fr_l\xf8".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY HH:mm",LLLL:"dddd [d.] D. MMMM YYYY [kl.] HH:mm"},calendar:{sameDay:"[i dag kl.] LT",nextDay:"[i morgen kl.] LT",nextWeek:"p\xe5 dddd [kl.] LT",lastDay:"[i g\xe5r kl.] LT",lastWeek:"[i] dddd[s kl.] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"%s siden",s:"f\xe5 sekunder",ss:"%d sekunder",m:"et minut",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dage",M:"en m\xe5ned",MM:"%d m\xe5neder",y:"et \xe5r",yy:"%d \xe5r"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),M.defineLocale("de-at",{months:"J\xe4nner_Februar_M\xe4rz_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"J\xe4n._Feb._M\xe4rz_Apr._Mai_Juni_Juli_Aug._Sep._Okt._Nov._Dez.".split("_"),monthsParseExact:!0,weekdays:"Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"),weekdaysShort:"So._Mo._Di._Mi._Do._Fr._Sa.".split("_"),weekdaysMin:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY HH:mm",LLLL:"dddd, D. MMMM YYYY HH:mm"},calendar:{sameDay:"[heute um] LT [Uhr]",sameElse:"L",nextDay:"[morgen um] LT [Uhr]",nextWeek:"dddd [um] LT [Uhr]",lastDay:"[gestern um] LT [Uhr]",lastWeek:"[letzten] dddd [um] LT [Uhr]"},relativeTime:{future:"in %s",past:"vor %s",s:"ein paar Sekunden",ss:"%d Sekunden",m:rs,mm:"%d Minuten",h:rs,hh:"%d Stunden",d:rs,dd:rs,w:rs,ww:"%d Wochen",M:rs,MM:rs,y:rs,yy:rs},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),M.defineLocale("de-ch",{months:"Januar_Februar_M\xe4rz_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jan._Feb._M\xe4rz_Apr._Mai_Juni_Juli_Aug._Sep._Okt._Nov._Dez.".split("_"),monthsParseExact:!0,weekdays:"Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"),weekdaysShort:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),weekdaysMin:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY HH:mm",LLLL:"dddd, D. MMMM YYYY HH:mm"},calendar:{sameDay:"[heute um] LT [Uhr]",sameElse:"L",nextDay:"[morgen um] LT [Uhr]",nextWeek:"dddd [um] LT [Uhr]",lastDay:"[gestern um] LT [Uhr]",lastWeek:"[letzten] dddd [um] LT [Uhr]"},relativeTime:{future:"in %s",past:"vor %s",s:"ein paar Sekunden",ss:"%d Sekunden",m:ds,mm:"%d Minuten",h:ds,hh:"%d Stunden",d:ds,dd:ds,w:ds,ww:"%d Wochen",M:ds,MM:ds,y:ds,yy:ds},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),M.defineLocale("de",{months:"Januar_Februar_M\xe4rz_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jan._Feb._M\xe4rz_Apr._Mai_Juni_Juli_Aug._Sep._Okt._Nov._Dez.".split("_"),monthsParseExact:!0,weekdays:"Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"),weekdaysShort:"So._Mo._Di._Mi._Do._Fr._Sa.".split("_"),weekdaysMin:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY HH:mm",LLLL:"dddd, D. MMMM YYYY HH:mm"},calendar:{sameDay:"[heute um] LT [Uhr]",sameElse:"L",nextDay:"[morgen um] LT [Uhr]",nextWeek:"dddd [um] LT [Uhr]",lastDay:"[gestern um] LT [Uhr]",lastWeek:"[letzten] dddd [um] LT [Uhr]"},relativeTime:{future:"in %s",past:"vor %s",s:"ein paar Sekunden",ss:"%d Sekunden",m:is,mm:"%d Minuten",h:is,hh:"%d Stunden",d:is,dd:is,w:is,ww:"%d Wochen",M:is,MM:is,y:is,yy:is},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}});Y=["\u0796\u07ac\u0782\u07aa\u0787\u07a6\u0783\u07a9","\u078a\u07ac\u0784\u07b0\u0783\u07aa\u0787\u07a6\u0783\u07a9","\u0789\u07a7\u0783\u07a8\u0797\u07aa","\u0787\u07ad\u0795\u07b0\u0783\u07a9\u078d\u07aa","\u0789\u07ad","\u0796\u07ab\u0782\u07b0","\u0796\u07aa\u078d\u07a6\u0787\u07a8","\u0787\u07af\u078e\u07a6\u0790\u07b0\u0793\u07aa","\u0790\u07ac\u0795\u07b0\u0793\u07ac\u0789\u07b0\u0784\u07a6\u0783\u07aa","\u0787\u07ae\u0786\u07b0\u0793\u07af\u0784\u07a6\u0783\u07aa","\u0782\u07ae\u0788\u07ac\u0789\u07b0\u0784\u07a6\u0783\u07aa","\u0791\u07a8\u0790\u07ac\u0789\u07b0\u0784\u07a6\u0783\u07aa"],Ke=["\u0787\u07a7\u078b\u07a8\u0787\u07b0\u078c\u07a6","\u0780\u07af\u0789\u07a6","\u0787\u07a6\u0782\u07b0\u078e\u07a7\u0783\u07a6","\u0784\u07aa\u078b\u07a6","\u0784\u07aa\u0783\u07a7\u0790\u07b0\u078a\u07a6\u078c\u07a8","\u0780\u07aa\u0786\u07aa\u0783\u07aa","\u0780\u07ae\u0782\u07a8\u0780\u07a8\u0783\u07aa"];M.defineLocale("dv",{months:Y,monthsShort:Y,weekdays:Ke,weekdaysShort:Ke,weekdaysMin:"\u0787\u07a7\u078b\u07a8_\u0780\u07af\u0789\u07a6_\u0787\u07a6\u0782\u07b0_\u0784\u07aa\u078b\u07a6_\u0784\u07aa\u0783\u07a7_\u0780\u07aa\u0786\u07aa_\u0780\u07ae\u0782\u07a8".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"D/M/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},meridiemParse:/\u0789\u0786|\u0789\u078a/,isPM:function(e){return"\u0789\u078a"===e},meridiem:function(e,a,t){return e<12?"\u0789\u0786":"\u0789\u078a"},calendar:{sameDay:"[\u0789\u07a8\u0787\u07a6\u078b\u07aa] LT",nextDay:"[\u0789\u07a7\u078b\u07a6\u0789\u07a7] LT",nextWeek:"dddd LT",lastDay:"[\u0787\u07a8\u0787\u07b0\u0794\u07ac] LT",lastWeek:"[\u078a\u07a7\u0787\u07a8\u078c\u07aa\u0788\u07a8] dddd LT",sameElse:"L"},relativeTime:{future:"\u078c\u07ac\u0783\u07ad\u078e\u07a6\u0787\u07a8 %s",past:"\u0786\u07aa\u0783\u07a8\u0782\u07b0 %s",s:"\u0790\u07a8\u0786\u07aa\u0782\u07b0\u078c\u07aa\u0786\u07ae\u0785\u07ac\u0787\u07b0",ss:"d% \u0790\u07a8\u0786\u07aa\u0782\u07b0\u078c\u07aa",m:"\u0789\u07a8\u0782\u07a8\u0793\u07ac\u0787\u07b0",mm:"\u0789\u07a8\u0782\u07a8\u0793\u07aa %d",h:"\u078e\u07a6\u0791\u07a8\u0787\u07a8\u0783\u07ac\u0787\u07b0",hh:"\u078e\u07a6\u0791\u07a8\u0787\u07a8\u0783\u07aa %d",d:"\u078b\u07aa\u0788\u07a6\u0780\u07ac\u0787\u07b0",dd:"\u078b\u07aa\u0788\u07a6\u0790\u07b0 %d",M:"\u0789\u07a6\u0780\u07ac\u0787\u07b0",MM:"\u0789\u07a6\u0790\u07b0 %d",y:"\u0787\u07a6\u0780\u07a6\u0783\u07ac\u0787\u07b0",yy:"\u0787\u07a6\u0780\u07a6\u0783\u07aa %d"},preparse:function(e){return e.replace(/\u060c/g,",")},postformat:function(e){return e.replace(/,/g,"\u060c")},week:{dow:7,doy:12}}),M.defineLocale("el",{monthsNominativeEl:"\u0399\u03b1\u03bd\u03bf\u03c5\u03ac\u03c1\u03b9\u03bf\u03c2_\u03a6\u03b5\u03b2\u03c1\u03bf\u03c5\u03ac\u03c1\u03b9\u03bf\u03c2_\u039c\u03ac\u03c1\u03c4\u03b9\u03bf\u03c2_\u0391\u03c0\u03c1\u03af\u03bb\u03b9\u03bf\u03c2_\u039c\u03ac\u03b9\u03bf\u03c2_\u0399\u03bf\u03cd\u03bd\u03b9\u03bf\u03c2_\u0399\u03bf\u03cd\u03bb\u03b9\u03bf\u03c2_\u0391\u03cd\u03b3\u03bf\u03c5\u03c3\u03c4\u03bf\u03c2_\u03a3\u03b5\u03c0\u03c4\u03ad\u03bc\u03b2\u03c1\u03b9\u03bf\u03c2_\u039f\u03ba\u03c4\u03ce\u03b2\u03c1\u03b9\u03bf\u03c2_\u039d\u03bf\u03ad\u03bc\u03b2\u03c1\u03b9\u03bf\u03c2_\u0394\u03b5\u03ba\u03ad\u03bc\u03b2\u03c1\u03b9\u03bf\u03c2".split("_"),monthsGenitiveEl:"\u0399\u03b1\u03bd\u03bf\u03c5\u03b1\u03c1\u03af\u03bf\u03c5_\u03a6\u03b5\u03b2\u03c1\u03bf\u03c5\u03b1\u03c1\u03af\u03bf\u03c5_\u039c\u03b1\u03c1\u03c4\u03af\u03bf\u03c5_\u0391\u03c0\u03c1\u03b9\u03bb\u03af\u03bf\u03c5_\u039c\u03b1\u0390\u03bf\u03c5_\u0399\u03bf\u03c5\u03bd\u03af\u03bf\u03c5_\u0399\u03bf\u03c5\u03bb\u03af\u03bf\u03c5_\u0391\u03c5\u03b3\u03bf\u03cd\u03c3\u03c4\u03bf\u03c5_\u03a3\u03b5\u03c0\u03c4\u03b5\u03bc\u03b2\u03c1\u03af\u03bf\u03c5_\u039f\u03ba\u03c4\u03c9\u03b2\u03c1\u03af\u03bf\u03c5_\u039d\u03bf\u03b5\u03bc\u03b2\u03c1\u03af\u03bf\u03c5_\u0394\u03b5\u03ba\u03b5\u03bc\u03b2\u03c1\u03af\u03bf\u03c5".split("_"),months:function(e,a){return e?("string"==typeof a&&/D/.test(a.substring(0,a.indexOf("MMMM")))?this._monthsGenitiveEl:this._monthsNominativeEl)[e.month()]:this._monthsNominativeEl},monthsShort:"\u0399\u03b1\u03bd_\u03a6\u03b5\u03b2_\u039c\u03b1\u03c1_\u0391\u03c0\u03c1_\u039c\u03b1\u03ca_\u0399\u03bf\u03c5\u03bd_\u0399\u03bf\u03c5\u03bb_\u0391\u03c5\u03b3_\u03a3\u03b5\u03c0_\u039f\u03ba\u03c4_\u039d\u03bf\u03b5_\u0394\u03b5\u03ba".split("_"),weekdays:"\u039a\u03c5\u03c1\u03b9\u03b1\u03ba\u03ae_\u0394\u03b5\u03c5\u03c4\u03ad\u03c1\u03b1_\u03a4\u03c1\u03af\u03c4\u03b7_\u03a4\u03b5\u03c4\u03ac\u03c1\u03c4\u03b7_\u03a0\u03ad\u03bc\u03c0\u03c4\u03b7_\u03a0\u03b1\u03c1\u03b1\u03c3\u03ba\u03b5\u03c5\u03ae_\u03a3\u03ac\u03b2\u03b2\u03b1\u03c4\u03bf".split("_"),weekdaysShort:"\u039a\u03c5\u03c1_\u0394\u03b5\u03c5_\u03a4\u03c1\u03b9_\u03a4\u03b5\u03c4_\u03a0\u03b5\u03bc_\u03a0\u03b1\u03c1_\u03a3\u03b1\u03b2".split("_"),weekdaysMin:"\u039a\u03c5_\u0394\u03b5_\u03a4\u03c1_\u03a4\u03b5_\u03a0\u03b5_\u03a0\u03b1_\u03a3\u03b1".split("_"),meridiem:function(e,a,t){return 11<e?t?"\u03bc\u03bc":"\u039c\u039c":t?"\u03c0\u03bc":"\u03a0\u039c"},isPM:function(e){return"\u03bc"===(e+"").toLowerCase()[0]},meridiemParse:/[\u03a0\u039c]\.?\u039c?\.?/i,longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY h:mm A",LLLL:"dddd, D MMMM YYYY h:mm A"},calendarEl:{sameDay:"[\u03a3\u03ae\u03bc\u03b5\u03c1\u03b1 {}] LT",nextDay:"[\u0391\u03cd\u03c1\u03b9\u03bf {}] LT",nextWeek:"dddd [{}] LT",lastDay:"[\u03a7\u03b8\u03b5\u03c2 {}] LT",lastWeek:function(){switch(this.day()){case 6:return"[\u03c4\u03bf \u03c0\u03c1\u03bf\u03b7\u03b3\u03bf\u03cd\u03bc\u03b5\u03bd\u03bf] dddd [{}] LT";default:return"[\u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03b7\u03b3\u03bf\u03cd\u03bc\u03b5\u03bd\u03b7] dddd [{}] LT"}},sameElse:"L"},calendar:function(e,a){var t,e=this._calendarEl[e],s=a&&a.hours();return t=e,(e="undefined"!=typeof Function&&t instanceof Function||"[object Function]"===Object.prototype.toString.call(t)?e.apply(a):e).replace("{}",s%12==1?"\u03c3\u03c4\u03b7":"\u03c3\u03c4\u03b9\u03c2")},relativeTime:{future:"\u03c3\u03b5 %s",past:"%s \u03c0\u03c1\u03b9\u03bd",s:"\u03bb\u03af\u03b3\u03b1 \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1",ss:"%d \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1",m:"\u03ad\u03bd\u03b1 \u03bb\u03b5\u03c0\u03c4\u03cc",mm:"%d \u03bb\u03b5\u03c0\u03c4\u03ac",h:"\u03bc\u03af\u03b1 \u03ce\u03c1\u03b1",hh:"%d \u03ce\u03c1\u03b5\u03c2",d:"\u03bc\u03af\u03b1 \u03bc\u03ad\u03c1\u03b1",dd:"%d \u03bc\u03ad\u03c1\u03b5\u03c2",M:"\u03ad\u03bd\u03b1\u03c2 \u03bc\u03ae\u03bd\u03b1\u03c2",MM:"%d \u03bc\u03ae\u03bd\u03b5\u03c2",y:"\u03ad\u03bd\u03b1\u03c2 \u03c7\u03c1\u03cc\u03bd\u03bf\u03c2",yy:"%d \u03c7\u03c1\u03cc\u03bd\u03b9\u03b1"},dayOfMonthOrdinalParse:/\d{1,2}\u03b7/,ordinal:"%d\u03b7",week:{dow:1,doy:4}}),M.defineLocale("en-au",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY h:mm A",LLLL:"dddd, D MMMM YYYY h:mm A"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var a=e%10;return e+(1==~~(e%100/10)?"th":1==a?"st":2==a?"nd":3==a?"rd":"th")},week:{dow:0,doy:4}}),M.defineLocale("en-ca",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"YYYY-MM-DD",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var a=e%10;return e+(1==~~(e%100/10)?"th":1==a?"st":2==a?"nd":3==a?"rd":"th")}}),M.defineLocale("en-gb",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var a=e%10;return e+(1==~~(e%100/10)?"th":1==a?"st":2==a?"nd":3==a?"rd":"th")},week:{dow:1,doy:4}}),M.defineLocale("en-ie",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var a=e%10;return e+(1==~~(e%100/10)?"th":1==a?"st":2==a?"nd":3==a?"rd":"th")},week:{dow:1,doy:4}}),M.defineLocale("en-il",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var a=e%10;return e+(1==~~(e%100/10)?"th":1==a?"st":2==a?"nd":3==a?"rd":"th")}}),M.defineLocale("en-in",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY h:mm A",LLLL:"dddd, D MMMM YYYY h:mm A"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var a=e%10;return e+(1==~~(e%100/10)?"th":1==a?"st":2==a?"nd":3==a?"rd":"th")},week:{dow:0,doy:6}}),M.defineLocale("en-nz",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY h:mm A",LLLL:"dddd, D MMMM YYYY h:mm A"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var a=e%10;return e+(1==~~(e%100/10)?"th":1==a?"st":2==a?"nd":3==a?"rd":"th")},week:{dow:1,doy:4}}),M.defineLocale("en-sg",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var a=e%10;return e+(1==~~(e%100/10)?"th":1==a?"st":2==a?"nd":3==a?"rd":"th")},week:{dow:1,doy:4}}),M.defineLocale("eo",{months:"januaro_februaro_marto_aprilo_majo_junio_julio_a\u016dgusto_septembro_oktobro_novembro_decembro".split("_"),monthsShort:"jan_feb_mart_apr_maj_jun_jul_a\u016dg_sept_okt_nov_dec".split("_"),weekdays:"diman\u0109o_lundo_mardo_merkredo_\u0135a\u016ddo_vendredo_sabato".split("_"),weekdaysShort:"dim_lun_mard_merk_\u0135a\u016d_ven_sab".split("_"),weekdaysMin:"di_lu_ma_me_\u0135a_ve_sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"[la] D[-an de] MMMM, YYYY",LLL:"[la] D[-an de] MMMM, YYYY HH:mm",LLLL:"dddd[n], [la] D[-an de] MMMM, YYYY HH:mm",llll:"ddd, [la] D[-an de] MMM, YYYY HH:mm"},meridiemParse:/[ap]\.t\.m/i,isPM:function(e){return"p"===e.charAt(0).toLowerCase()},meridiem:function(e,a,t){return 11<e?t?"p.t.m.":"P.T.M.":t?"a.t.m.":"A.T.M."},calendar:{sameDay:"[Hodia\u016d je] LT",nextDay:"[Morga\u016d je] LT",nextWeek:"dddd[n je] LT",lastDay:"[Hiera\u016d je] LT",lastWeek:"[pasintan] dddd[n je] LT",sameElse:"L"},relativeTime:{future:"post %s",past:"anta\u016d %s",s:"kelkaj sekundoj",ss:"%d sekundoj",m:"unu minuto",mm:"%d minutoj",h:"unu horo",hh:"%d horoj",d:"unu tago",dd:"%d tagoj",M:"unu monato",MM:"%d monatoj",y:"unu jaro",yy:"%d jaroj"},dayOfMonthOrdinalParse:/\d{1,2}a/,ordinal:"%da",week:{dow:1,doy:7}});var _s="ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.".split("_"),os="ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic".split("_"),r=[/^ene/i,/^feb/i,/^mar/i,/^abr/i,/^may/i,/^jun/i,/^jul/i,/^ago/i,/^sep/i,/^oct/i,/^nov/i,/^dic/i],Ht=/^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre|ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i,ms=(M.defineLocale("es-do",{months:"enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"),monthsShort:function(e,a){return e?(/-MMM-/.test(a)?os:_s)[e.month()]:_s},monthsRegex:Ht,monthsShortRegex:Ht,monthsStrictRegex:/^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre)/i,monthsShortStrictRegex:/^(ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i,monthsParse:r,longMonthsParse:r,shortMonthsParse:r,weekdays:"domingo_lunes_martes_mi\xe9rcoles_jueves_viernes_s\xe1bado".split("_"),weekdaysShort:"dom._lun._mar._mi\xe9._jue._vie._s\xe1b.".split("_"),weekdaysMin:"do_lu_ma_mi_ju_vi_s\xe1".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY h:mm A",LLLL:"dddd, D [de] MMMM [de] YYYY h:mm A"},calendar:{sameDay:function(){return"[hoy a la"+(1!==this.hours()?"s":"")+"] LT"},nextDay:function(){return"[ma\xf1ana a la"+(1!==this.hours()?"s":"")+"] LT"},nextWeek:function(){return"dddd [a la"+(1!==this.hours()?"s":"")+"] LT"},lastDay:function(){return"[ayer a la"+(1!==this.hours()?"s":"")+"] LT"},lastWeek:function(){return"[el] dddd [pasado a la"+(1!==this.hours()?"s":"")+"] LT"},sameElse:"L"},relativeTime:{future:"en %s",past:"hace %s",s:"unos segundos",ss:"%d segundos",m:"un minuto",mm:"%d minutos",h:"una hora",hh:"%d horas",d:"un d\xeda",dd:"%d d\xedas",w:"una semana",ww:"%d semanas",M:"un mes",MM:"%d meses",y:"un a\xf1o",yy:"%d a\xf1os"},dayOfMonthOrdinalParse:/\d{1,2}\xba/,ordinal:"%d\xba",week:{dow:1,doy:4}}),"ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.".split("_")),us="ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic".split("_"),vt=[/^ene/i,/^feb/i,/^mar/i,/^abr/i,/^may/i,/^jun/i,/^jul/i,/^ago/i,/^sep/i,/^oct/i,/^nov/i,/^dic/i],jt=/^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre|ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i,ls=(M.defineLocale("es-mx",{months:"enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"),monthsShort:function(e,a){return e?(/-MMM-/.test(a)?us:ms)[e.month()]:ms},monthsRegex:jt,monthsShortRegex:jt,monthsStrictRegex:/^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre)/i,monthsShortStrictRegex:/^(ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i,monthsParse:vt,longMonthsParse:vt,shortMonthsParse:vt,weekdays:"domingo_lunes_martes_mi\xe9rcoles_jueves_viernes_s\xe1bado".split("_"),weekdaysShort:"dom._lun._mar._mi\xe9._jue._vie._s\xe1b.".split("_"),weekdaysMin:"do_lu_ma_mi_ju_vi_s\xe1".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY H:mm",LLLL:"dddd, D [de] MMMM [de] YYYY H:mm"},calendar:{sameDay:function(){return"[hoy a la"+(1!==this.hours()?"s":"")+"] LT"},nextDay:function(){return"[ma\xf1ana a la"+(1!==this.hours()?"s":"")+"] LT"},nextWeek:function(){return"dddd [a la"+(1!==this.hours()?"s":"")+"] LT"},lastDay:function(){return"[ayer a la"+(1!==this.hours()?"s":"")+"] LT"},lastWeek:function(){return"[el] dddd [pasado a la"+(1!==this.hours()?"s":"")+"] LT"},sameElse:"L"},relativeTime:{future:"en %s",past:"hace %s",s:"unos segundos",ss:"%d segundos",m:"un minuto",mm:"%d minutos",h:"una hora",hh:"%d horas",d:"un d\xeda",dd:"%d d\xedas",w:"una semana",ww:"%d semanas",M:"un mes",MM:"%d meses",y:"un a\xf1o",yy:"%d a\xf1os"},dayOfMonthOrdinalParse:/\d{1,2}\xba/,ordinal:"%d\xba",week:{dow:0,doy:4},invalidDate:"Fecha inv\xe1lida"}),"ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.".split("_")),Ms="ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic".split("_"),x=[/^ene/i,/^feb/i,/^mar/i,/^abr/i,/^may/i,/^jun/i,/^jul/i,/^ago/i,/^sep/i,/^oct/i,/^nov/i,/^dic/i],na=/^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre|ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i,hs=(M.defineLocale("es-us",{months:"enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"),monthsShort:function(e,a){return e?(/-MMM-/.test(a)?Ms:ls)[e.month()]:ls},monthsRegex:na,monthsShortRegex:na,monthsStrictRegex:/^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre)/i,monthsShortStrictRegex:/^(ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i,monthsParse:x,longMonthsParse:x,shortMonthsParse:x,weekdays:"domingo_lunes_martes_mi\xe9rcoles_jueves_viernes_s\xe1bado".split("_"),weekdaysShort:"dom._lun._mar._mi\xe9._jue._vie._s\xe1b.".split("_"),weekdaysMin:"do_lu_ma_mi_ju_vi_s\xe1".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"MM/DD/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY h:mm A",LLLL:"dddd, D [de] MMMM [de] YYYY h:mm A"},calendar:{sameDay:function(){return"[hoy a la"+(1!==this.hours()?"s":"")+"] LT"},nextDay:function(){return"[ma\xf1ana a la"+(1!==this.hours()?"s":"")+"] LT"},nextWeek:function(){return"dddd [a la"+(1!==this.hours()?"s":"")+"] LT"},lastDay:function(){return"[ayer a la"+(1!==this.hours()?"s":"")+"] LT"},lastWeek:function(){return"[el] dddd [pasado a la"+(1!==this.hours()?"s":"")+"] LT"},sameElse:"L"},relativeTime:{future:"en %s",past:"hace %s",s:"unos segundos",ss:"%d segundos",m:"un minuto",mm:"%d minutos",h:"una hora",hh:"%d horas",d:"un d\xeda",dd:"%d d\xedas",w:"una semana",ww:"%d semanas",M:"un mes",MM:"%d meses",y:"un a\xf1o",yy:"%d a\xf1os"},dayOfMonthOrdinalParse:/\d{1,2}\xba/,ordinal:"%d\xba",week:{dow:0,doy:6}}),"ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.".split("_")),cs="ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic".split("_"),ge=[/^ene/i,/^feb/i,/^mar/i,/^abr/i,/^may/i,/^jun/i,/^jul/i,/^ago/i,/^sep/i,/^oct/i,/^nov/i,/^dic/i],n=/^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre|ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i;function Ls(e,a,t,s){e={s:["m\xf5ne sekundi","m\xf5ni sekund","paar sekundit"],ss:[e+"sekundi",e+"sekundit"],m:["\xfche minuti","\xfcks minut"],mm:[e+" minuti",e+" minutit"],h:["\xfche tunni","tund aega","\xfcks tund"],hh:[e+" tunni",e+" tundi"],d:["\xfche p\xe4eva","\xfcks p\xe4ev"],M:["kuu aja","kuu aega","\xfcks kuu"],MM:[e+" kuu",e+" kuud"],y:["\xfche aasta","aasta","\xfcks aasta"],yy:[e+" aasta",e+" aastat"]};return a?e[t][2]||e[t][1]:s?e[t][0]:e[t][1]}M.defineLocale("es",{months:"enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"),monthsShort:function(e,a){return e?(/-MMM-/.test(a)?cs:hs)[e.month()]:hs},monthsRegex:n,monthsShortRegex:n,monthsStrictRegex:/^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre)/i,monthsShortStrictRegex:/^(ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i,monthsParse:ge,longMonthsParse:ge,shortMonthsParse:ge,weekdays:"domingo_lunes_martes_mi\xe9rcoles_jueves_viernes_s\xe1bado".split("_"),weekdaysShort:"dom._lun._mar._mi\xe9._jue._vie._s\xe1b.".split("_"),weekdaysMin:"do_lu_ma_mi_ju_vi_s\xe1".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY H:mm",LLLL:"dddd, D [de] MMMM [de] YYYY H:mm"},calendar:{sameDay:function(){return"[hoy a la"+(1!==this.hours()?"s":"")+"] LT"},nextDay:function(){return"[ma\xf1ana a la"+(1!==this.hours()?"s":"")+"] LT"},nextWeek:function(){return"dddd [a la"+(1!==this.hours()?"s":"")+"] LT"},lastDay:function(){return"[ayer a la"+(1!==this.hours()?"s":"")+"] LT"},lastWeek:function(){return"[el] dddd [pasado a la"+(1!==this.hours()?"s":"")+"] LT"},sameElse:"L"},relativeTime:{future:"en %s",past:"hace %s",s:"unos segundos",ss:"%d segundos",m:"un minuto",mm:"%d minutos",h:"una hora",hh:"%d horas",d:"un d\xeda",dd:"%d d\xedas",w:"una semana",ww:"%d semanas",M:"un mes",MM:"%d meses",y:"un a\xf1o",yy:"%d a\xf1os"},dayOfMonthOrdinalParse:/\d{1,2}\xba/,ordinal:"%d\xba",week:{dow:1,doy:4},invalidDate:"Fecha inv\xe1lida"}),M.defineLocale("et",{months:"jaanuar_veebruar_m\xe4rts_aprill_mai_juuni_juuli_august_september_oktoober_november_detsember".split("_"),monthsShort:"jaan_veebr_m\xe4rts_apr_mai_juuni_juuli_aug_sept_okt_nov_dets".split("_"),weekdays:"p\xfchap\xe4ev_esmasp\xe4ev_teisip\xe4ev_kolmap\xe4ev_neljap\xe4ev_reede_laup\xe4ev".split("_"),weekdaysShort:"P_E_T_K_N_R_L".split("_"),weekdaysMin:"P_E_T_K_N_R_L".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[T\xe4na,] LT",nextDay:"[Homme,] LT",nextWeek:"[J\xe4rgmine] dddd LT",lastDay:"[Eile,] LT",lastWeek:"[Eelmine] dddd LT",sameElse:"L"},relativeTime:{future:"%s p\xe4rast",past:"%s tagasi",s:Ls,ss:Ls,m:Ls,mm:Ls,h:Ls,hh:Ls,d:Ls,dd:"%d p\xe4eva",M:Ls,MM:Ls,y:Ls,yy:Ls},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),M.defineLocale("eu",{months:"urtarrila_otsaila_martxoa_apirila_maiatza_ekaina_uztaila_abuztua_iraila_urria_azaroa_abendua".split("_"),monthsShort:"urt._ots._mar._api._mai._eka._uzt._abu._ira._urr._aza._abe.".split("_"),monthsParseExact:!0,weekdays:"igandea_astelehena_asteartea_asteazkena_osteguna_ostirala_larunbata".split("_"),weekdaysShort:"ig._al._ar._az._og._ol._lr.".split("_"),weekdaysMin:"ig_al_ar_az_og_ol_lr".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"YYYY[ko] MMMM[ren] D[a]",LLL:"YYYY[ko] MMMM[ren] D[a] HH:mm",LLLL:"dddd, YYYY[ko] MMMM[ren] D[a] HH:mm",l:"YYYY-M-D",ll:"YYYY[ko] MMM D[a]",lll:"YYYY[ko] MMM D[a] HH:mm",llll:"ddd, YYYY[ko] MMM D[a] HH:mm"},calendar:{sameDay:"[gaur] LT[etan]",nextDay:"[bihar] LT[etan]",nextWeek:"dddd LT[etan]",lastDay:"[atzo] LT[etan]",lastWeek:"[aurreko] dddd LT[etan]",sameElse:"L"},relativeTime:{future:"%s barru",past:"duela %s",s:"segundo batzuk",ss:"%d segundo",m:"minutu bat",mm:"%d minutu",h:"ordu bat",hh:"%d ordu",d:"egun bat",dd:"%d egun",M:"hilabete bat",MM:"%d hilabete",y:"urte bat",yy:"%d urte"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}});var Ys={1:"\u06f1",2:"\u06f2",3:"\u06f3",4:"\u06f4",5:"\u06f5",6:"\u06f6",7:"\u06f7",8:"\u06f8",9:"\u06f9",0:"\u06f0"},ys={"\u06f1":"1","\u06f2":"2","\u06f3":"3","\u06f4":"4","\u06f5":"5","\u06f6":"6","\u06f7":"7","\u06f8":"8","\u06f9":"9","\u06f0":"0"},fs=(M.defineLocale("fa",{months:"\u0698\u0627\u0646\u0648\u06cc\u0647_\u0641\u0648\u0631\u06cc\u0647_\u0645\u0627\u0631\u0633_\u0622\u0648\u0631\u06cc\u0644_\u0645\u0647_\u0698\u0648\u0626\u0646_\u0698\u0648\u0626\u06cc\u0647_\u0627\u0648\u062a_\u0633\u067e\u062a\u0627\u0645\u0628\u0631_\u0627\u06a9\u062a\u0628\u0631_\u0646\u0648\u0627\u0645\u0628\u0631_\u062f\u0633\u0627\u0645\u0628\u0631".split("_"),monthsShort:"\u0698\u0627\u0646\u0648\u06cc\u0647_\u0641\u0648\u0631\u06cc\u0647_\u0645\u0627\u0631\u0633_\u0622\u0648\u0631\u06cc\u0644_\u0645\u0647_\u0698\u0648\u0626\u0646_\u0698\u0648\u0626\u06cc\u0647_\u0627\u0648\u062a_\u0633\u067e\u062a\u0627\u0645\u0628\u0631_\u0627\u06a9\u062a\u0628\u0631_\u0646\u0648\u0627\u0645\u0628\u0631_\u062f\u0633\u0627\u0645\u0628\u0631".split("_"),weekdays:"\u06cc\u06a9\u200c\u0634\u0646\u0628\u0647_\u062f\u0648\u0634\u0646\u0628\u0647_\u0633\u0647\u200c\u0634\u0646\u0628\u0647_\u0686\u0647\u0627\u0631\u0634\u0646\u0628\u0647_\u067e\u0646\u062c\u200c\u0634\u0646\u0628\u0647_\u062c\u0645\u0639\u0647_\u0634\u0646\u0628\u0647".split("_"),weekdaysShort:"\u06cc\u06a9\u200c\u0634\u0646\u0628\u0647_\u062f\u0648\u0634\u0646\u0628\u0647_\u0633\u0647\u200c\u0634\u0646\u0628\u0647_\u0686\u0647\u0627\u0631\u0634\u0646\u0628\u0647_\u067e\u0646\u062c\u200c\u0634\u0646\u0628\u0647_\u062c\u0645\u0639\u0647_\u0634\u0646\u0628\u0647".split("_"),weekdaysMin:"\u06cc_\u062f_\u0633_\u0686_\u067e_\u062c_\u0634".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},meridiemParse:/\u0642\u0628\u0644 \u0627\u0632 \u0638\u0647\u0631|\u0628\u0639\u062f \u0627\u0632 \u0638\u0647\u0631/,isPM:function(e){return/\u0628\u0639\u062f \u0627\u0632 \u0638\u0647\u0631/.test(e)},meridiem:function(e,a,t){return e<12?"\u0642\u0628\u0644 \u0627\u0632 \u0638\u0647\u0631":"\u0628\u0639\u062f \u0627\u0632 \u0638\u0647\u0631"},calendar:{sameDay:"[\u0627\u0645\u0631\u0648\u0632 \u0633\u0627\u0639\u062a] LT",nextDay:"[\u0641\u0631\u062f\u0627 \u0633\u0627\u0639\u062a] LT",nextWeek:"dddd [\u0633\u0627\u0639\u062a] LT",lastDay:"[\u062f\u06cc\u0631\u0648\u0632 \u0633\u0627\u0639\u062a] LT",lastWeek:"dddd [\u067e\u06cc\u0634] [\u0633\u0627\u0639\u062a] LT",sameElse:"L"},relativeTime:{future:"\u062f\u0631 %s",past:"%s \u067e\u06cc\u0634",s:"\u0686\u0646\u062f \u062b\u0627\u0646\u06cc\u0647",ss:"%d \u062b\u0627\u0646\u06cc\u0647",m:"\u06cc\u06a9 \u062f\u0642\u06cc\u0642\u0647",mm:"%d \u062f\u0642\u06cc\u0642\u0647",h:"\u06cc\u06a9 \u0633\u0627\u0639\u062a",hh:"%d \u0633\u0627\u0639\u062a",d:"\u06cc\u06a9 \u0631\u0648\u0632",dd:"%d \u0631\u0648\u0632",M:"\u06cc\u06a9 \u0645\u0627\u0647",MM:"%d \u0645\u0627\u0647",y:"\u06cc\u06a9 \u0633\u0627\u0644",yy:"%d \u0633\u0627\u0644"},preparse:function(e){return e.replace(/[\u06f0-\u06f9]/g,function(e){return ys[e]}).replace(/\u060c/g,",")},postformat:function(e){return e.replace(/\d/g,function(e){return Ys[e]}).replace(/,/g,"\u060c")},dayOfMonthOrdinalParse:/\d{1,2}\u0645/,ordinal:"%d\u0645",week:{dow:6,doy:12}}),"nolla yksi kaksi kolme nelj\xe4 viisi kuusi seitsem\xe4n kahdeksan yhdeks\xe4n".split(" ")),ps=["nolla","yhden","kahden","kolmen","nelj\xe4n","viiden","kuuden",fs[7],fs[8],fs[9]];function O(e,a,t,s){var n="";switch(t){case"s":return s?"muutaman sekunnin":"muutama sekunti";case"ss":n=s?"sekunnin":"sekuntia";break;case"m":return s?"minuutin":"minuutti";case"mm":n=s?"minuutin":"minuuttia";break;case"h":return s?"tunnin":"tunti";case"hh":n=s?"tunnin":"tuntia";break;case"d":return s?"p\xe4iv\xe4n":"p\xe4iv\xe4";case"dd":n=s?"p\xe4iv\xe4n":"p\xe4iv\xe4\xe4";break;case"M":return s?"kuukauden":"kuukausi";case"MM":n=s?"kuukauden":"kuukautta";break;case"y":return s?"vuoden":"vuosi";case"yy":n=s?"vuoden":"vuotta";break}return t=s,n=((e=e)<10?(t?ps:fs)[e]:e)+" "+n}M.defineLocale("fi",{months:"tammikuu_helmikuu_maaliskuu_huhtikuu_toukokuu_kes\xe4kuu_hein\xe4kuu_elokuu_syyskuu_lokakuu_marraskuu_joulukuu".split("_"),monthsShort:"tammi_helmi_maalis_huhti_touko_kes\xe4_hein\xe4_elo_syys_loka_marras_joulu".split("_"),weekdays:"sunnuntai_maanantai_tiistai_keskiviikko_torstai_perjantai_lauantai".split("_"),weekdaysShort:"su_ma_ti_ke_to_pe_la".split("_"),weekdaysMin:"su_ma_ti_ke_to_pe_la".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD.MM.YYYY",LL:"Do MMMM[ta] YYYY",LLL:"Do MMMM[ta] YYYY, [klo] HH.mm",LLLL:"dddd, Do MMMM[ta] YYYY, [klo] HH.mm",l:"D.M.YYYY",ll:"Do MMM YYYY",lll:"Do MMM YYYY, [klo] HH.mm",llll:"ddd, Do MMM YYYY, [klo] HH.mm"},calendar:{sameDay:"[t\xe4n\xe4\xe4n] [klo] LT",nextDay:"[huomenna] [klo] LT",nextWeek:"dddd [klo] LT",lastDay:"[eilen] [klo] LT",lastWeek:"[viime] dddd[na] [klo] LT",sameElse:"L"},relativeTime:{future:"%s p\xe4\xe4st\xe4",past:"%s sitten",s:O,ss:O,m:O,mm:O,h:O,hh:O,d:O,dd:O,M:O,MM:O,y:O,yy:O},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),M.defineLocale("fil",{months:"Enero_Pebrero_Marso_Abril_Mayo_Hunyo_Hulyo_Agosto_Setyembre_Oktubre_Nobyembre_Disyembre".split("_"),monthsShort:"Ene_Peb_Mar_Abr_May_Hun_Hul_Ago_Set_Okt_Nob_Dis".split("_"),weekdays:"Linggo_Lunes_Martes_Miyerkules_Huwebes_Biyernes_Sabado".split("_"),weekdaysShort:"Lin_Lun_Mar_Miy_Huw_Biy_Sab".split("_"),weekdaysMin:"Li_Lu_Ma_Mi_Hu_Bi_Sab".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"MM/D/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY HH:mm",LLLL:"dddd, MMMM DD, YYYY HH:mm"},calendar:{sameDay:"LT [ngayong araw]",nextDay:"[Bukas ng] LT",nextWeek:"LT [sa susunod na] dddd",lastDay:"LT [kahapon]",lastWeek:"LT [noong nakaraang] dddd",sameElse:"L"},relativeTime:{future:"sa loob ng %s",past:"%s ang nakalipas",s:"ilang segundo",ss:"%d segundo",m:"isang minuto",mm:"%d minuto",h:"isang oras",hh:"%d oras",d:"isang araw",dd:"%d araw",M:"isang buwan",MM:"%d buwan",y:"isang taon",yy:"%d taon"},dayOfMonthOrdinalParse:/\d{1,2}/,ordinal:function(e){return e},week:{dow:1,doy:4}}),M.defineLocale("fo",{months:"januar_februar_mars_apr\xedl_mai_juni_juli_august_september_oktober_november_desember".split("_"),monthsShort:"jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des".split("_"),weekdays:"sunnudagur_m\xe1nadagur_t\xfdsdagur_mikudagur_h\xf3sdagur_fr\xedggjadagur_leygardagur".split("_"),weekdaysShort:"sun_m\xe1n_t\xfds_mik_h\xf3s_fr\xed_ley".split("_"),weekdaysMin:"su_m\xe1_t\xfd_mi_h\xf3_fr_le".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D. MMMM, YYYY HH:mm"},calendar:{sameDay:"[\xcd dag kl.] LT",nextDay:"[\xcd morgin kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[\xcd gj\xe1r kl.] LT",lastWeek:"[s\xed\xf0stu] dddd [kl] LT",sameElse:"L"},relativeTime:{future:"um %s",past:"%s s\xed\xf0ani",s:"f\xe1 sekund",ss:"%d sekundir",m:"ein minuttur",mm:"%d minuttir",h:"ein t\xedmi",hh:"%d t\xedmar",d:"ein dagur",dd:"%d dagar",M:"ein m\xe1na\xf0ur",MM:"%d m\xe1na\xf0ir",y:"eitt \xe1r",yy:"%d \xe1r"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),M.defineLocale("fr-ca",{months:"janvier_f\xe9vrier_mars_avril_mai_juin_juillet_ao\xfbt_septembre_octobre_novembre_d\xe9cembre".split("_"),monthsShort:"janv._f\xe9vr._mars_avr._mai_juin_juil._ao\xfbt_sept._oct._nov._d\xe9c.".split("_"),monthsParseExact:!0,weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"di_lu_ma_me_je_ve_sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[Aujourd\u2019hui \xe0] LT",nextDay:"[Demain \xe0] LT",nextWeek:"dddd [\xe0] LT",lastDay:"[Hier \xe0] LT",lastWeek:"dddd [dernier \xe0] LT",sameElse:"L"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",ss:"%d secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},dayOfMonthOrdinalParse:/\d{1,2}(er|e)/,ordinal:function(e,a){switch(a){default:case"M":case"Q":case"D":case"DDD":case"d":return e+(1===e?"er":"e");case"w":case"W":return e+(1===e?"re":"e")}}}),M.defineLocale("fr-ch",{months:"janvier_f\xe9vrier_mars_avril_mai_juin_juillet_ao\xfbt_septembre_octobre_novembre_d\xe9cembre".split("_"),monthsShort:"janv._f\xe9vr._mars_avr._mai_juin_juil._ao\xfbt_sept._oct._nov._d\xe9c.".split("_"),monthsParseExact:!0,weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"di_lu_ma_me_je_ve_sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[Aujourd\u2019hui \xe0] LT",nextDay:"[Demain \xe0] LT",nextWeek:"dddd [\xe0] LT",lastDay:"[Hier \xe0] LT",lastWeek:"dddd [dernier \xe0] LT",sameElse:"L"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",ss:"%d secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},dayOfMonthOrdinalParse:/\d{1,2}(er|e)/,ordinal:function(e,a){switch(a){default:case"M":case"Q":case"D":case"DDD":case"d":return e+(1===e?"er":"e");case"w":case"W":return e+(1===e?"re":"e")}},week:{dow:1,doy:4}});var De=/(janv\.?|f\xe9vr\.?|mars|avr\.?|mai|juin|juil\.?|ao\xfbt|sept\.?|oct\.?|nov\.?|d\xe9c\.?|janvier|f\xe9vrier|mars|avril|mai|juin|juillet|ao\xfbt|septembre|octobre|novembre|d\xe9cembre)/i,pe=[/^janv/i,/^f\xe9vr/i,/^mars/i,/^avr/i,/^mai/i,/^juin/i,/^juil/i,/^ao\xfbt/i,/^sept/i,/^oct/i,/^nov/i,/^d\xe9c/i],ks=(M.defineLocale("fr",{months:"janvier_f\xe9vrier_mars_avril_mai_juin_juillet_ao\xfbt_septembre_octobre_novembre_d\xe9cembre".split("_"),monthsShort:"janv._f\xe9vr._mars_avr._mai_juin_juil._ao\xfbt_sept._oct._nov._d\xe9c.".split("_"),monthsRegex:De,monthsShortRegex:De,monthsStrictRegex:/^(janvier|f\xe9vrier|mars|avril|mai|juin|juillet|ao\xfbt|septembre|octobre|novembre|d\xe9cembre)/i,monthsShortStrictRegex:/(janv\.?|f\xe9vr\.?|mars|avr\.?|mai|juin|juil\.?|ao\xfbt|sept\.?|oct\.?|nov\.?|d\xe9c\.?)/i,monthsParse:pe,longMonthsParse:pe,shortMonthsParse:pe,weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"di_lu_ma_me_je_ve_sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[Aujourd\u2019hui \xe0] LT",nextDay:"[Demain \xe0] LT",nextWeek:"dddd [\xe0] LT",lastDay:"[Hier \xe0] LT",lastWeek:"dddd [dernier \xe0] LT",sameElse:"L"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",ss:"%d secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",w:"une semaine",ww:"%d semaines",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},dayOfMonthOrdinalParse:/\d{1,2}(er|)/,ordinal:function(e,a){switch(a){case"D":return e+(1===e?"er":"");default:case"M":case"Q":case"DDD":case"d":return e+(1===e?"er":"e");case"w":case"W":return e+(1===e?"re":"e")}},week:{dow:1,doy:4}}),"jan._feb._mrt._apr._mai_jun._jul._aug._sep._okt._nov._des.".split("_")),Ds="jan_feb_mrt_apr_mai_jun_jul_aug_sep_okt_nov_des".split("_");M.defineLocale("fy",{months:"jannewaris_febrewaris_maart_april_maaie_juny_july_augustus_septimber_oktober_novimber_desimber".split("_"),monthsShort:function(e,a){return e?(/-MMM-/.test(a)?Ds:ks)[e.month()]:ks},monthsParseExact:!0,weekdays:"snein_moandei_tiisdei_woansdei_tongersdei_freed_sneon".split("_"),weekdaysShort:"si._mo._ti._wo._to._fr._so.".split("_"),weekdaysMin:"Si_Mo_Ti_Wo_To_Fr_So".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD-MM-YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[hjoed om] LT",nextDay:"[moarn om] LT",nextWeek:"dddd [om] LT",lastDay:"[juster om] LT",lastWeek:"[\xf4fr\xfbne] dddd [om] LT",sameElse:"L"},relativeTime:{future:"oer %s",past:"%s lyn",s:"in pear sekonden",ss:"%d sekonden",m:"ien min\xfat",mm:"%d minuten",h:"ien oere",hh:"%d oeren",d:"ien dei",dd:"%d dagen",M:"ien moanne",MM:"%d moannen",y:"ien jier",yy:"%d jierren"},dayOfMonthOrdinalParse:/\d{1,2}(ste|de)/,ordinal:function(e){return e+(1===e||8===e||20<=e?"ste":"de")},week:{dow:1,doy:4}}),M.defineLocale("ga",{months:["Ean\xe1ir","Feabhra","M\xe1rta","Aibre\xe1n","Bealtaine","Meitheamh","I\xfail","L\xfanasa","Me\xe1n F\xf3mhair","Deireadh F\xf3mhair","Samhain","Nollaig"],monthsShort:["Ean","Feabh","M\xe1rt","Aib","Beal","Meith","I\xfail","L\xfan","M.F.","D.F.","Samh","Noll"],monthsParseExact:!0,weekdays:["D\xe9 Domhnaigh","D\xe9 Luain","D\xe9 M\xe1irt","D\xe9 C\xe9adaoin","D\xe9ardaoin","D\xe9 hAoine","D\xe9 Sathairn"],weekdaysShort:["Domh","Luan","M\xe1irt","C\xe9ad","D\xe9ar","Aoine","Sath"],weekdaysMin:["Do","Lu","M\xe1","C\xe9","D\xe9","A","Sa"],longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Inniu ag] LT",nextDay:"[Am\xe1rach ag] LT",nextWeek:"dddd [ag] LT",lastDay:"[Inn\xe9 ag] LT",lastWeek:"dddd [seo caite] [ag] LT",sameElse:"L"},relativeTime:{future:"i %s",past:"%s \xf3 shin",s:"c\xfapla soicind",ss:"%d soicind",m:"n\xf3im\xe9ad",mm:"%d n\xf3im\xe9ad",h:"uair an chloig",hh:"%d uair an chloig",d:"l\xe1",dd:"%d l\xe1",M:"m\xed",MM:"%d m\xedonna",y:"bliain",yy:"%d bliain"},dayOfMonthOrdinalParse:/\d{1,2}(d|na|mh)/,ordinal:function(e){return e+(1===e?"d":e%10==2?"na":"mh")},week:{dow:1,doy:4}});function Ts(e,a,t,s){e={s:["\u0925\u094b\u0921\u092f\u093e \u0938\u0945\u0915\u0902\u0921\u093e\u0902\u0928\u0940","\u0925\u094b\u0921\u0947 \u0938\u0945\u0915\u0902\u0921"],ss:[e+" \u0938\u0945\u0915\u0902\u0921\u093e\u0902\u0928\u0940",e+" \u0938\u0945\u0915\u0902\u0921"],m:["\u090f\u0915\u093e \u092e\u093f\u0923\u091f\u093e\u0928","\u090f\u0915 \u092e\u093f\u0928\u0942\u091f"],mm:[e+" \u092e\u093f\u0923\u091f\u093e\u0902\u0928\u0940",e+" \u092e\u093f\u0923\u091f\u093e\u0902"],h:["\u090f\u0915\u093e \u0935\u0930\u093e\u0928","\u090f\u0915 \u0935\u0930"],hh:[e+" \u0935\u0930\u093e\u0902\u0928\u0940",e+" \u0935\u0930\u093e\u0902"],d:["\u090f\u0915\u093e \u0926\u093f\u0938\u093e\u0928","\u090f\u0915 \u0926\u0940\u0938"],dd:[e+" \u0926\u093f\u0938\u093e\u0902\u0928\u0940",e+" \u0926\u0940\u0938"],M:["\u090f\u0915\u093e \u092e\u094d\u0939\u092f\u0928\u094d\u092f\u093e\u0928","\u090f\u0915 \u092e\u094d\u0939\u092f\u0928\u094b"],MM:[e+" \u092e\u094d\u0939\u092f\u0928\u094d\u092f\u093e\u0928\u0940",e+" \u092e\u094d\u0939\u092f\u0928\u0947"],y:["\u090f\u0915\u093e \u0935\u0930\u094d\u0938\u093e\u0928","\u090f\u0915 \u0935\u0930\u094d\u0938"],yy:[e+" \u0935\u0930\u094d\u0938\u093e\u0902\u0928\u0940",e+" \u0935\u0930\u094d\u0938\u093e\u0902"]};return s?e[t][0]:e[t][1]}function gs(e,a,t,s){e={s:["thoddea sekondamni","thodde sekond"],ss:[e+" sekondamni",e+" sekond"],m:["eka mintan","ek minut"],mm:[e+" mintamni",e+" mintam"],h:["eka voran","ek vor"],hh:[e+" voramni",e+" voram"],d:["eka disan","ek dis"],dd:[e+" disamni",e+" dis"],M:["eka mhoinean","ek mhoino"],MM:[e+" mhoineamni",e+" mhoine"],y:["eka vorsan","ek voros"],yy:[e+" vorsamni",e+" vorsam"]};return s?e[t][0]:e[t][1]}M.defineLocale("gd",{months:["Am Faoilleach","An Gearran","Am M\xe0rt","An Giblean","An C\xe8itean","An t-\xd2gmhios","An t-Iuchar","An L\xf9nastal","An t-Sultain","An D\xe0mhair","An t-Samhain","An D\xf9bhlachd"],monthsShort:["Faoi","Gear","M\xe0rt","Gibl","C\xe8it","\xd2gmh","Iuch","L\xf9n","Sult","D\xe0mh","Samh","D\xf9bh"],monthsParseExact:!0,weekdays:["Did\xf2mhnaich","Diluain","Dim\xe0irt","Diciadain","Diardaoin","Dihaoine","Disathairne"],weekdaysShort:["Did","Dil","Dim","Dic","Dia","Dih","Dis"],weekdaysMin:["D\xf2","Lu","M\xe0","Ci","Ar","Ha","Sa"],longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[An-diugh aig] LT",nextDay:"[A-m\xe0ireach aig] LT",nextWeek:"dddd [aig] LT",lastDay:"[An-d\xe8 aig] LT",lastWeek:"dddd [seo chaidh] [aig] LT",sameElse:"L"},relativeTime:{future:"ann an %s",past:"bho chionn %s",s:"beagan diogan",ss:"%d diogan",m:"mionaid",mm:"%d mionaidean",h:"uair",hh:"%d uairean",d:"latha",dd:"%d latha",M:"m\xecos",MM:"%d m\xecosan",y:"bliadhna",yy:"%d bliadhna"},dayOfMonthOrdinalParse:/\d{1,2}(d|na|mh)/,ordinal:function(e){return e+(1===e?"d":e%10==2?"na":"mh")},week:{dow:1,doy:4}}),M.defineLocale("gl",{months:"xaneiro_febreiro_marzo_abril_maio_xu\xf1o_xullo_agosto_setembro_outubro_novembro_decembro".split("_"),monthsShort:"xan._feb._mar._abr._mai._xu\xf1._xul._ago._set._out._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"domingo_luns_martes_m\xe9rcores_xoves_venres_s\xe1bado".split("_"),weekdaysShort:"dom._lun._mar._m\xe9r._xov._ven._s\xe1b.".split("_"),weekdaysMin:"do_lu_ma_m\xe9_xo_ve_s\xe1".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY H:mm",LLLL:"dddd, D [de] MMMM [de] YYYY H:mm"},calendar:{sameDay:function(){return"[hoxe "+(1!==this.hours()?"\xe1s":"\xe1")+"] LT"},nextDay:function(){return"[ma\xf1\xe1 "+(1!==this.hours()?"\xe1s":"\xe1")+"] LT"},nextWeek:function(){return"dddd ["+(1!==this.hours()?"\xe1s":"a")+"] LT"},lastDay:function(){return"[onte "+(1!==this.hours()?"\xe1":"a")+"] LT"},lastWeek:function(){return"[o] dddd [pasado "+(1!==this.hours()?"\xe1s":"a")+"] LT"},sameElse:"L"},relativeTime:{future:function(e){return 0===e.indexOf("un")?"n"+e:"en "+e},past:"hai %s",s:"uns segundos",ss:"%d segundos",m:"un minuto",mm:"%d minutos",h:"unha hora",hh:"%d horas",d:"un d\xeda",dd:"%d d\xedas",M:"un mes",MM:"%d meses",y:"un ano",yy:"%d anos"},dayOfMonthOrdinalParse:/\d{1,2}\xba/,ordinal:"%d\xba",week:{dow:1,doy:4}}),M.defineLocale("gom-deva",{months:{standalone:"\u091c\u093e\u0928\u0947\u0935\u093e\u0930\u0940_\u092b\u0947\u092c\u094d\u0930\u0941\u0935\u093e\u0930\u0940_\u092e\u093e\u0930\u094d\u091a_\u090f\u092a\u094d\u0930\u0940\u0932_\u092e\u0947_\u091c\u0942\u0928_\u091c\u0941\u0932\u092f_\u0911\u0917\u0938\u094d\u091f_\u0938\u092a\u094d\u091f\u0947\u0902\u092c\u0930_\u0911\u0915\u094d\u091f\u094b\u092c\u0930_\u0928\u094b\u0935\u094d\u0939\u0947\u0902\u092c\u0930_\u0921\u093f\u0938\u0947\u0902\u092c\u0930".split("_"),format:"\u091c\u093e\u0928\u0947\u0935\u093e\u0930\u0940\u091a\u094d\u092f\u093e_\u092b\u0947\u092c\u094d\u0930\u0941\u0935\u093e\u0930\u0940\u091a\u094d\u092f\u093e_\u092e\u093e\u0930\u094d\u091a\u093e\u091a\u094d\u092f\u093e_\u090f\u092a\u094d\u0930\u0940\u0932\u093e\u091a\u094d\u092f\u093e_\u092e\u0947\u092f\u093e\u091a\u094d\u092f\u093e_\u091c\u0942\u0928\u093e\u091a\u094d\u092f\u093e_\u091c\u0941\u0932\u092f\u093e\u091a\u094d\u092f\u093e_\u0911\u0917\u0938\u094d\u091f\u093e\u091a\u094d\u092f\u093e_\u0938\u092a\u094d\u091f\u0947\u0902\u092c\u0930\u093e\u091a\u094d\u092f\u093e_\u0911\u0915\u094d\u091f\u094b\u092c\u0930\u093e\u091a\u094d\u092f\u093e_\u0928\u094b\u0935\u094d\u0939\u0947\u0902\u092c\u0930\u093e\u091a\u094d\u092f\u093e_\u0921\u093f\u0938\u0947\u0902\u092c\u0930\u093e\u091a\u094d\u092f\u093e".split("_"),isFormat:/MMMM(\s)+D[oD]?/},monthsShort:"\u091c\u093e\u0928\u0947._\u092b\u0947\u092c\u094d\u0930\u0941._\u092e\u093e\u0930\u094d\u091a_\u090f\u092a\u094d\u0930\u0940._\u092e\u0947_\u091c\u0942\u0928_\u091c\u0941\u0932._\u0911\u0917._\u0938\u092a\u094d\u091f\u0947\u0902._\u0911\u0915\u094d\u091f\u094b._\u0928\u094b\u0935\u094d\u0939\u0947\u0902._\u0921\u093f\u0938\u0947\u0902.".split("_"),monthsParseExact:!0,weekdays:"\u0906\u092f\u0924\u093e\u0930_\u0938\u094b\u092e\u093e\u0930_\u092e\u0902\u0917\u0933\u093e\u0930_\u092c\u0941\u0927\u0935\u093e\u0930_\u092c\u093f\u0930\u0947\u0938\u094d\u0924\u093e\u0930_\u0938\u0941\u0915\u094d\u0930\u093e\u0930_\u0936\u0947\u0928\u0935\u093e\u0930".split("_"),weekdaysShort:"\u0906\u092f\u0924._\u0938\u094b\u092e._\u092e\u0902\u0917\u0933._\u092c\u0941\u0927._\u092c\u094d\u0930\u0947\u0938\u094d\u0924._\u0938\u0941\u0915\u094d\u0930._\u0936\u0947\u0928.".split("_"),weekdaysMin:"\u0906_\u0938\u094b_\u092e\u0902_\u092c\u0941_\u092c\u094d\u0930\u0947_\u0938\u0941_\u0936\u0947".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"A h:mm [\u0935\u093e\u091c\u0924\u093e\u0902]",LTS:"A h:mm:ss [\u0935\u093e\u091c\u0924\u093e\u0902]",L:"DD-MM-YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY A h:mm [\u0935\u093e\u091c\u0924\u093e\u0902]",LLLL:"dddd, MMMM Do, YYYY, A h:mm [\u0935\u093e\u091c\u0924\u093e\u0902]",llll:"ddd, D MMM YYYY, A h:mm [\u0935\u093e\u091c\u0924\u093e\u0902]"},calendar:{sameDay:"[\u0906\u092f\u091c] LT",nextDay:"[\u092b\u093e\u0932\u094d\u092f\u093e\u0902] LT",nextWeek:"[\u092b\u0941\u0921\u0932\u094b] dddd[,] LT",lastDay:"[\u0915\u093e\u0932] LT",lastWeek:"[\u092b\u093e\u091f\u0932\u094b] dddd[,] LT",sameElse:"L"},relativeTime:{future:"%s",past:"%s \u0906\u0926\u0940\u0902",s:Ts,ss:Ts,m:Ts,mm:Ts,h:Ts,hh:Ts,d:Ts,dd:Ts,M:Ts,MM:Ts,y:Ts,yy:Ts},dayOfMonthOrdinalParse:/\d{1,2}(\u0935\u0947\u0930)/,ordinal:function(e,a){switch(a){case"D":return e+"\u0935\u0947\u0930";default:case"M":case"Q":case"DDD":case"d":case"w":case"W":return e}},week:{dow:0,doy:3},meridiemParse:/\u0930\u093e\u0924\u0940|\u0938\u0915\u093e\u0933\u0940\u0902|\u0926\u0928\u092a\u093e\u0930\u093e\u0902|\u0938\u093e\u0902\u091c\u0947/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u0930\u093e\u0924\u0940"===a?e<4?e:e+12:"\u0938\u0915\u093e\u0933\u0940\u0902"===a?e:"\u0926\u0928\u092a\u093e\u0930\u093e\u0902"===a?12<e?e:e+12:"\u0938\u093e\u0902\u091c\u0947"===a?e+12:void 0},meridiem:function(e,a,t){return e<4?"\u0930\u093e\u0924\u0940":e<12?"\u0938\u0915\u093e\u0933\u0940\u0902":e<16?"\u0926\u0928\u092a\u093e\u0930\u093e\u0902":e<20?"\u0938\u093e\u0902\u091c\u0947":"\u0930\u093e\u0924\u0940"}}),M.defineLocale("gom-latn",{months:{standalone:"Janer_Febrer_Mars_Abril_Mai_Jun_Julai_Agost_Setembr_Otubr_Novembr_Dezembr".split("_"),format:"Janerachea_Febrerachea_Marsachea_Abrilachea_Maiachea_Junachea_Julaiachea_Agostachea_Setembrachea_Otubrachea_Novembrachea_Dezembrachea".split("_"),isFormat:/MMMM(\s)+D[oD]?/},monthsShort:"Jan._Feb._Mars_Abr._Mai_Jun_Jul._Ago._Set._Otu._Nov._Dez.".split("_"),monthsParseExact:!0,weekdays:"Aitar_Somar_Mongllar_Budhvar_Birestar_Sukrar_Son'var".split("_"),weekdaysShort:"Ait._Som._Mon._Bud._Bre._Suk._Son.".split("_"),weekdaysMin:"Ai_Sm_Mo_Bu_Br_Su_Sn".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"A h:mm [vazta]",LTS:"A h:mm:ss [vazta]",L:"DD-MM-YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY A h:mm [vazta]",LLLL:"dddd, MMMM Do, YYYY, A h:mm [vazta]",llll:"ddd, D MMM YYYY, A h:mm [vazta]"},calendar:{sameDay:"[Aiz] LT",nextDay:"[Faleam] LT",nextWeek:"[Fuddlo] dddd[,] LT",lastDay:"[Kal] LT",lastWeek:"[Fattlo] dddd[,] LT",sameElse:"L"},relativeTime:{future:"%s",past:"%s adim",s:gs,ss:gs,m:gs,mm:gs,h:gs,hh:gs,d:gs,dd:gs,M:gs,MM:gs,y:gs,yy:gs},dayOfMonthOrdinalParse:/\d{1,2}(er)/,ordinal:function(e,a){switch(a){case"D":return e+"er";default:case"M":case"Q":case"DDD":case"d":case"w":case"W":return e}},week:{dow:0,doy:3},meridiemParse:/rati|sokallim|donparam|sanje/,meridiemHour:function(e,a){return 12===e&&(e=0),"rati"===a?e<4?e:e+12:"sokallim"===a?e:"donparam"===a?12<e?e:e+12:"sanje"===a?e+12:void 0},meridiem:function(e,a,t){return e<4?"rati":e<12?"sokallim":e<16?"donparam":e<20?"sanje":"rati"}});var ws={1:"\u0ae7",2:"\u0ae8",3:"\u0ae9",4:"\u0aea",5:"\u0aeb",6:"\u0aec",7:"\u0aed",8:"\u0aee",9:"\u0aef",0:"\u0ae6"},bs={"\u0ae7":"1","\u0ae8":"2","\u0ae9":"3","\u0aea":"4","\u0aeb":"5","\u0aec":"6","\u0aed":"7","\u0aee":"8","\u0aef":"9","\u0ae6":"0"},Ss=(M.defineLocale("gu",{months:"\u0a9c\u0abe\u0aa8\u0acd\u0aaf\u0ac1\u0a86\u0ab0\u0ac0_\u0aab\u0ac7\u0aac\u0acd\u0ab0\u0ac1\u0a86\u0ab0\u0ac0_\u0aae\u0abe\u0ab0\u0acd\u0a9a_\u0a8f\u0aaa\u0acd\u0ab0\u0abf\u0ab2_\u0aae\u0ac7_\u0a9c\u0ac2\u0aa8_\u0a9c\u0ac1\u0ab2\u0abe\u0a88_\u0a91\u0a97\u0ab8\u0acd\u0a9f_\u0ab8\u0aaa\u0acd\u0a9f\u0ac7\u0aae\u0acd\u0aac\u0ab0_\u0a91\u0a95\u0acd\u0a9f\u0acd\u0aac\u0ab0_\u0aa8\u0ab5\u0ac7\u0aae\u0acd\u0aac\u0ab0_\u0aa1\u0abf\u0ab8\u0ac7\u0aae\u0acd\u0aac\u0ab0".split("_"),monthsShort:"\u0a9c\u0abe\u0aa8\u0acd\u0aaf\u0ac1._\u0aab\u0ac7\u0aac\u0acd\u0ab0\u0ac1._\u0aae\u0abe\u0ab0\u0acd\u0a9a_\u0a8f\u0aaa\u0acd\u0ab0\u0abf._\u0aae\u0ac7_\u0a9c\u0ac2\u0aa8_\u0a9c\u0ac1\u0ab2\u0abe._\u0a91\u0a97._\u0ab8\u0aaa\u0acd\u0a9f\u0ac7._\u0a91\u0a95\u0acd\u0a9f\u0acd._\u0aa8\u0ab5\u0ac7._\u0aa1\u0abf\u0ab8\u0ac7.".split("_"),monthsParseExact:!0,weekdays:"\u0ab0\u0ab5\u0abf\u0ab5\u0abe\u0ab0_\u0ab8\u0acb\u0aae\u0ab5\u0abe\u0ab0_\u0aae\u0a82\u0a97\u0ab3\u0ab5\u0abe\u0ab0_\u0aac\u0ac1\u0aa7\u0acd\u0ab5\u0abe\u0ab0_\u0a97\u0ac1\u0ab0\u0ac1\u0ab5\u0abe\u0ab0_\u0ab6\u0ac1\u0a95\u0acd\u0ab0\u0ab5\u0abe\u0ab0_\u0ab6\u0aa8\u0abf\u0ab5\u0abe\u0ab0".split("_"),weekdaysShort:"\u0ab0\u0ab5\u0abf_\u0ab8\u0acb\u0aae_\u0aae\u0a82\u0a97\u0ab3_\u0aac\u0ac1\u0aa7\u0acd_\u0a97\u0ac1\u0ab0\u0ac1_\u0ab6\u0ac1\u0a95\u0acd\u0ab0_\u0ab6\u0aa8\u0abf".split("_"),weekdaysMin:"\u0ab0_\u0ab8\u0acb_\u0aae\u0a82_\u0aac\u0ac1_\u0a97\u0ac1_\u0ab6\u0ac1_\u0ab6".split("_"),longDateFormat:{LT:"A h:mm \u0ab5\u0abe\u0a97\u0acd\u0aaf\u0ac7",LTS:"A h:mm:ss \u0ab5\u0abe\u0a97\u0acd\u0aaf\u0ac7",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm \u0ab5\u0abe\u0a97\u0acd\u0aaf\u0ac7",LLLL:"dddd, D MMMM YYYY, A h:mm \u0ab5\u0abe\u0a97\u0acd\u0aaf\u0ac7"},calendar:{sameDay:"[\u0a86\u0a9c] LT",nextDay:"[\u0a95\u0abe\u0ab2\u0ac7] LT",nextWeek:"dddd, LT",lastDay:"[\u0a97\u0a87\u0a95\u0abe\u0ab2\u0ac7] LT",lastWeek:"[\u0aaa\u0abe\u0a9b\u0ab2\u0abe] dddd, LT",sameElse:"L"},relativeTime:{future:"%s \u0aae\u0abe",past:"%s \u0aaa\u0ab9\u0ac7\u0ab2\u0abe",s:"\u0a85\u0aae\u0ac1\u0a95 \u0aaa\u0ab3\u0acb",ss:"%d \u0ab8\u0ac7\u0a95\u0a82\u0aa1",m:"\u0a8f\u0a95 \u0aae\u0abf\u0aa8\u0abf\u0a9f",mm:"%d \u0aae\u0abf\u0aa8\u0abf\u0a9f",h:"\u0a8f\u0a95 \u0a95\u0ab2\u0abe\u0a95",hh:"%d \u0a95\u0ab2\u0abe\u0a95",d:"\u0a8f\u0a95 \u0aa6\u0abf\u0ab5\u0ab8",dd:"%d \u0aa6\u0abf\u0ab5\u0ab8",M:"\u0a8f\u0a95 \u0aae\u0ab9\u0abf\u0aa8\u0acb",MM:"%d \u0aae\u0ab9\u0abf\u0aa8\u0acb",y:"\u0a8f\u0a95 \u0ab5\u0ab0\u0acd\u0ab7",yy:"%d \u0ab5\u0ab0\u0acd\u0ab7"},preparse:function(e){return e.replace(/[\u0ae7\u0ae8\u0ae9\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef\u0ae6]/g,function(e){return bs[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return ws[e]})},meridiemParse:/\u0ab0\u0abe\u0aa4|\u0aac\u0aaa\u0acb\u0ab0|\u0ab8\u0ab5\u0abe\u0ab0|\u0ab8\u0abe\u0a82\u0a9c/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u0ab0\u0abe\u0aa4"===a?e<4?e:e+12:"\u0ab8\u0ab5\u0abe\u0ab0"===a?e:"\u0aac\u0aaa\u0acb\u0ab0"===a?10<=e?e:e+12:"\u0ab8\u0abe\u0a82\u0a9c"===a?e+12:void 0},meridiem:function(e,a,t){return e<4?"\u0ab0\u0abe\u0aa4":e<10?"\u0ab8\u0ab5\u0abe\u0ab0":e<17?"\u0aac\u0aaa\u0acb\u0ab0":e<20?"\u0ab8\u0abe\u0a82\u0a9c":"\u0ab0\u0abe\u0aa4"},week:{dow:0,doy:6}}),M.defineLocale("he",{months:"\u05d9\u05e0\u05d5\u05d0\u05e8_\u05e4\u05d1\u05e8\u05d5\u05d0\u05e8_\u05de\u05e8\u05e5_\u05d0\u05e4\u05e8\u05d9\u05dc_\u05de\u05d0\u05d9_\u05d9\u05d5\u05e0\u05d9_\u05d9\u05d5\u05dc\u05d9_\u05d0\u05d5\u05d2\u05d5\u05e1\u05d8_\u05e1\u05e4\u05d8\u05de\u05d1\u05e8_\u05d0\u05d5\u05e7\u05d8\u05d5\u05d1\u05e8_\u05e0\u05d5\u05d1\u05de\u05d1\u05e8_\u05d3\u05e6\u05de\u05d1\u05e8".split("_"),monthsShort:"\u05d9\u05e0\u05d5\u05f3_\u05e4\u05d1\u05e8\u05f3_\u05de\u05e8\u05e5_\u05d0\u05e4\u05e8\u05f3_\u05de\u05d0\u05d9_\u05d9\u05d5\u05e0\u05d9_\u05d9\u05d5\u05dc\u05d9_\u05d0\u05d5\u05d2\u05f3_\u05e1\u05e4\u05d8\u05f3_\u05d0\u05d5\u05e7\u05f3_\u05e0\u05d5\u05d1\u05f3_\u05d3\u05e6\u05de\u05f3".split("_"),weekdays:"\u05e8\u05d0\u05e9\u05d5\u05df_\u05e9\u05e0\u05d9_\u05e9\u05dc\u05d9\u05e9\u05d9_\u05e8\u05d1\u05d9\u05e2\u05d9_\u05d7\u05de\u05d9\u05e9\u05d9_\u05e9\u05d9\u05e9\u05d9_\u05e9\u05d1\u05ea".split("_"),weekdaysShort:"\u05d0\u05f3_\u05d1\u05f3_\u05d2\u05f3_\u05d3\u05f3_\u05d4\u05f3_\u05d5\u05f3_\u05e9\u05f3".split("_"),weekdaysMin:"\u05d0_\u05d1_\u05d2_\u05d3_\u05d4_\u05d5_\u05e9".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D [\u05d1]MMMM YYYY",LLL:"D [\u05d1]MMMM YYYY HH:mm",LLLL:"dddd, D [\u05d1]MMMM YYYY HH:mm",l:"D/M/YYYY",ll:"D MMM YYYY",lll:"D MMM YYYY HH:mm",llll:"ddd, D MMM YYYY HH:mm"},calendar:{sameDay:"[\u05d4\u05d9\u05d5\u05dd \u05d1\u05be]LT",nextDay:"[\u05de\u05d7\u05e8 \u05d1\u05be]LT",nextWeek:"dddd [\u05d1\u05e9\u05e2\u05d4] LT",lastDay:"[\u05d0\u05ea\u05de\u05d5\u05dc \u05d1\u05be]LT",lastWeek:"[\u05d1\u05d9\u05d5\u05dd] dddd [\u05d4\u05d0\u05d7\u05e8\u05d5\u05df \u05d1\u05e9\u05e2\u05d4] LT",sameElse:"L"},relativeTime:{future:"\u05d1\u05e2\u05d5\u05d3 %s",past:"\u05dc\u05e4\u05e0\u05d9 %s",s:"\u05de\u05e1\u05e4\u05e8 \u05e9\u05e0\u05d9\u05d5\u05ea",ss:"%d \u05e9\u05e0\u05d9\u05d5\u05ea",m:"\u05d3\u05e7\u05d4",mm:"%d \u05d3\u05e7\u05d5\u05ea",h:"\u05e9\u05e2\u05d4",hh:function(e){return 2===e?"\u05e9\u05e2\u05ea\u05d9\u05d9\u05dd":e+" \u05e9\u05e2\u05d5\u05ea"},d:"\u05d9\u05d5\u05dd",dd:function(e){return 2===e?"\u05d9\u05d5\u05de\u05d9\u05d9\u05dd":e+" \u05d9\u05de\u05d9\u05dd"},M:"\u05d7\u05d5\u05d3\u05e9",MM:function(e){return 2===e?"\u05d7\u05d5\u05d3\u05e9\u05d9\u05d9\u05dd":e+" \u05d7\u05d5\u05d3\u05e9\u05d9\u05dd"},y:"\u05e9\u05e0\u05d4",yy:function(e){return 2===e?"\u05e9\u05e0\u05ea\u05d9\u05d9\u05dd":e%10==0&&10!==e?e+" \u05e9\u05e0\u05d4":e+" \u05e9\u05e0\u05d9\u05dd"}},meridiemParse:/\u05d0\u05d7\u05d4"\u05e6|\u05dc\u05e4\u05e0\u05d4"\u05e6|\u05d0\u05d7\u05e8\u05d9 \u05d4\u05e6\u05d4\u05e8\u05d9\u05d9\u05dd|\u05dc\u05e4\u05e0\u05d9 \u05d4\u05e6\u05d4\u05e8\u05d9\u05d9\u05dd|\u05dc\u05e4\u05e0\u05d5\u05ea \u05d1\u05d5\u05e7\u05e8|\u05d1\u05d1\u05d5\u05e7\u05e8|\u05d1\u05e2\u05e8\u05d1/i,isPM:function(e){return/^(\u05d0\u05d7\u05d4"\u05e6|\u05d0\u05d7\u05e8\u05d9 \u05d4\u05e6\u05d4\u05e8\u05d9\u05d9\u05dd|\u05d1\u05e2\u05e8\u05d1)$/.test(e)},meridiem:function(e,a,t){return e<5?"\u05dc\u05e4\u05e0\u05d5\u05ea \u05d1\u05d5\u05e7\u05e8":e<10?"\u05d1\u05d1\u05d5\u05e7\u05e8":e<12?t?'\u05dc\u05e4\u05e0\u05d4"\u05e6':"\u05dc\u05e4\u05e0\u05d9 \u05d4\u05e6\u05d4\u05e8\u05d9\u05d9\u05dd":e<18?t?'\u05d0\u05d7\u05d4"\u05e6':"\u05d0\u05d7\u05e8\u05d9 \u05d4\u05e6\u05d4\u05e8\u05d9\u05d9\u05dd":"\u05d1\u05e2\u05e8\u05d1"}}),{1:"\u0967",2:"\u0968",3:"\u0969",4:"\u096a",5:"\u096b",6:"\u096c",7:"\u096d",8:"\u096e",9:"\u096f",0:"\u0966"}),Hs={"\u0967":"1","\u0968":"2","\u0969":"3","\u096a":"4","\u096b":"5","\u096c":"6","\u096d":"7","\u096e":"8","\u096f":"9","\u0966":"0"},Ne=[/^\u091c\u0928/i,/^\u092b\u093c\u0930|\u092b\u0930/i,/^\u092e\u093e\u0930\u094d\u091a/i,/^\u0905\u092a\u094d\u0930\u0948/i,/^\u092e\u0908/i,/^\u091c\u0942\u0928/i,/^\u091c\u0941\u0932/i,/^\u0905\u0917/i,/^\u0938\u093f\u0924\u0902|\u0938\u093f\u0924/i,/^\u0905\u0915\u094d\u091f\u0942/i,/^\u0928\u0935|\u0928\u0935\u0902/i,/^\u0926\u093f\u0938\u0902|\u0926\u093f\u0938/i];function vs(e,a,t){var s=e+" ";switch(t){case"ss":return s+=1===e?"sekunda":2===e||3===e||4===e?"sekunde":"sekundi";case"m":return a?"jedna minuta":"jedne minute";case"mm":return s+=1!==e&&(2===e||3===e||4===e)?"minute":"minuta";case"h":return a?"jedan sat":"jednog sata";case"hh":return s+=1===e?"sat":2===e||3===e||4===e?"sata":"sati";case"dd":return s+=1===e?"dan":"dana";case"MM":return s+=1===e?"mjesec":2===e||3===e||4===e?"mjeseca":"mjeseci";case"yy":return s+=1!==e&&(2===e||3===e||4===e)?"godine":"godina"}}M.defineLocale("hi",{months:{format:"\u091c\u0928\u0935\u0930\u0940_\u092b\u093c\u0930\u0935\u0930\u0940_\u092e\u093e\u0930\u094d\u091a_\u0905\u092a\u094d\u0930\u0948\u0932_\u092e\u0908_\u091c\u0942\u0928_\u091c\u0941\u0932\u093e\u0908_\u0905\u0917\u0938\u094d\u0924_\u0938\u093f\u0924\u092e\u094d\u092c\u0930_\u0905\u0915\u094d\u091f\u0942\u092c\u0930_\u0928\u0935\u092e\u094d\u092c\u0930_\u0926\u093f\u0938\u092e\u094d\u092c\u0930".split("_"),standalone:"\u091c\u0928\u0935\u0930\u0940_\u092b\u0930\u0935\u0930\u0940_\u092e\u093e\u0930\u094d\u091a_\u0905\u092a\u094d\u0930\u0948\u0932_\u092e\u0908_\u091c\u0942\u0928_\u091c\u0941\u0932\u093e\u0908_\u0905\u0917\u0938\u094d\u0924_\u0938\u093f\u0924\u0902\u092c\u0930_\u0905\u0915\u094d\u091f\u0942\u092c\u0930_\u0928\u0935\u0902\u092c\u0930_\u0926\u093f\u0938\u0902\u092c\u0930".split("_")},monthsShort:"\u091c\u0928._\u092b\u093c\u0930._\u092e\u093e\u0930\u094d\u091a_\u0905\u092a\u094d\u0930\u0948._\u092e\u0908_\u091c\u0942\u0928_\u091c\u0941\u0932._\u0905\u0917._\u0938\u093f\u0924._\u0905\u0915\u094d\u091f\u0942._\u0928\u0935._\u0926\u093f\u0938.".split("_"),weekdays:"\u0930\u0935\u093f\u0935\u093e\u0930_\u0938\u094b\u092e\u0935\u093e\u0930_\u092e\u0902\u0917\u0932\u0935\u093e\u0930_\u092c\u0941\u0927\u0935\u093e\u0930_\u0917\u0941\u0930\u0942\u0935\u093e\u0930_\u0936\u0941\u0915\u094d\u0930\u0935\u093e\u0930_\u0936\u0928\u093f\u0935\u093e\u0930".split("_"),weekdaysShort:"\u0930\u0935\u093f_\u0938\u094b\u092e_\u092e\u0902\u0917\u0932_\u092c\u0941\u0927_\u0917\u0941\u0930\u0942_\u0936\u0941\u0915\u094d\u0930_\u0936\u0928\u093f".split("_"),weekdaysMin:"\u0930_\u0938\u094b_\u092e\u0902_\u092c\u0941_\u0917\u0941_\u0936\u0941_\u0936".split("_"),longDateFormat:{LT:"A h:mm \u092c\u091c\u0947",LTS:"A h:mm:ss \u092c\u091c\u0947",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm \u092c\u091c\u0947",LLLL:"dddd, D MMMM YYYY, A h:mm \u092c\u091c\u0947"},monthsParse:Ne,longMonthsParse:Ne,shortMonthsParse:[/^\u091c\u0928/i,/^\u092b\u093c\u0930/i,/^\u092e\u093e\u0930\u094d\u091a/i,/^\u0905\u092a\u094d\u0930\u0948/i,/^\u092e\u0908/i,/^\u091c\u0942\u0928/i,/^\u091c\u0941\u0932/i,/^\u0905\u0917/i,/^\u0938\u093f\u0924/i,/^\u0905\u0915\u094d\u091f\u0942/i,/^\u0928\u0935/i,/^\u0926\u093f\u0938/i],monthsRegex:/^(\u091c\u0928\u0935\u0930\u0940|\u091c\u0928\.?|\u092b\u093c\u0930\u0935\u0930\u0940|\u092b\u0930\u0935\u0930\u0940|\u092b\u093c\u0930\.?|\u092e\u093e\u0930\u094d\u091a?|\u0905\u092a\u094d\u0930\u0948\u0932|\u0905\u092a\u094d\u0930\u0948\.?|\u092e\u0908?|\u091c\u0942\u0928?|\u091c\u0941\u0932\u093e\u0908|\u091c\u0941\u0932\.?|\u0905\u0917\u0938\u094d\u0924|\u0905\u0917\.?|\u0938\u093f\u0924\u092e\u094d\u092c\u0930|\u0938\u093f\u0924\u0902\u092c\u0930|\u0938\u093f\u0924\.?|\u0905\u0915\u094d\u091f\u0942\u092c\u0930|\u0905\u0915\u094d\u091f\u0942\.?|\u0928\u0935\u092e\u094d\u092c\u0930|\u0928\u0935\u0902\u092c\u0930|\u0928\u0935\.?|\u0926\u093f\u0938\u092e\u094d\u092c\u0930|\u0926\u093f\u0938\u0902\u092c\u0930|\u0926\u093f\u0938\.?)/i,monthsShortRegex:/^(\u091c\u0928\u0935\u0930\u0940|\u091c\u0928\.?|\u092b\u093c\u0930\u0935\u0930\u0940|\u092b\u0930\u0935\u0930\u0940|\u092b\u093c\u0930\.?|\u092e\u093e\u0930\u094d\u091a?|\u0905\u092a\u094d\u0930\u0948\u0932|\u0905\u092a\u094d\u0930\u0948\.?|\u092e\u0908?|\u091c\u0942\u0928?|\u091c\u0941\u0932\u093e\u0908|\u091c\u0941\u0932\.?|\u0905\u0917\u0938\u094d\u0924|\u0905\u0917\.?|\u0938\u093f\u0924\u092e\u094d\u092c\u0930|\u0938\u093f\u0924\u0902\u092c\u0930|\u0938\u093f\u0924\.?|\u0905\u0915\u094d\u091f\u0942\u092c\u0930|\u0905\u0915\u094d\u091f\u0942\.?|\u0928\u0935\u092e\u094d\u092c\u0930|\u0928\u0935\u0902\u092c\u0930|\u0928\u0935\.?|\u0926\u093f\u0938\u092e\u094d\u092c\u0930|\u0926\u093f\u0938\u0902\u092c\u0930|\u0926\u093f\u0938\.?)/i,monthsStrictRegex:/^(\u091c\u0928\u0935\u0930\u0940?|\u092b\u093c\u0930\u0935\u0930\u0940|\u092b\u0930\u0935\u0930\u0940?|\u092e\u093e\u0930\u094d\u091a?|\u0905\u092a\u094d\u0930\u0948\u0932?|\u092e\u0908?|\u091c\u0942\u0928?|\u091c\u0941\u0932\u093e\u0908?|\u0905\u0917\u0938\u094d\u0924?|\u0938\u093f\u0924\u092e\u094d\u092c\u0930|\u0938\u093f\u0924\u0902\u092c\u0930|\u0938\u093f\u0924?\.?|\u0905\u0915\u094d\u091f\u0942\u092c\u0930|\u0905\u0915\u094d\u091f\u0942\.?|\u0928\u0935\u092e\u094d\u092c\u0930|\u0928\u0935\u0902\u092c\u0930?|\u0926\u093f\u0938\u092e\u094d\u092c\u0930|\u0926\u093f\u0938\u0902\u092c\u0930?)/i,monthsShortStrictRegex:/^(\u091c\u0928\.?|\u092b\u093c\u0930\.?|\u092e\u093e\u0930\u094d\u091a?|\u0905\u092a\u094d\u0930\u0948\.?|\u092e\u0908?|\u091c\u0942\u0928?|\u091c\u0941\u0932\.?|\u0905\u0917\.?|\u0938\u093f\u0924\.?|\u0905\u0915\u094d\u091f\u0942\.?|\u0928\u0935\.?|\u0926\u093f\u0938\.?)/i,calendar:{sameDay:"[\u0906\u091c] LT",nextDay:"[\u0915\u0932] LT",nextWeek:"dddd, LT",lastDay:"[\u0915\u0932] LT",lastWeek:"[\u092a\u093f\u091b\u0932\u0947] dddd, LT",sameElse:"L"},relativeTime:{future:"%s \u092e\u0947\u0902",past:"%s \u092a\u0939\u0932\u0947",s:"\u0915\u0941\u091b \u0939\u0940 \u0915\u094d\u0937\u0923",ss:"%d \u0938\u0947\u0915\u0902\u0921",m:"\u090f\u0915 \u092e\u093f\u0928\u091f",mm:"%d \u092e\u093f\u0928\u091f",h:"\u090f\u0915 \u0918\u0902\u091f\u093e",hh:"%d \u0918\u0902\u091f\u0947",d:"\u090f\u0915 \u0926\u093f\u0928",dd:"%d \u0926\u093f\u0928",M:"\u090f\u0915 \u092e\u0939\u0940\u0928\u0947",MM:"%d \u092e\u0939\u0940\u0928\u0947",y:"\u090f\u0915 \u0935\u0930\u094d\u0937",yy:"%d \u0935\u0930\u094d\u0937"},preparse:function(e){return e.replace(/[\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f\u0966]/g,function(e){return Hs[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return Ss[e]})},meridiemParse:/\u0930\u093e\u0924|\u0938\u0941\u092c\u0939|\u0926\u094b\u092a\u0939\u0930|\u0936\u093e\u092e/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u0930\u093e\u0924"===a?e<4?e:e+12:"\u0938\u0941\u092c\u0939"===a?e:"\u0926\u094b\u092a\u0939\u0930"===a?10<=e?e:e+12:"\u0936\u093e\u092e"===a?e+12:void 0},meridiem:function(e,a,t){return e<4?"\u0930\u093e\u0924":e<10?"\u0938\u0941\u092c\u0939":e<17?"\u0926\u094b\u092a\u0939\u0930":e<20?"\u0936\u093e\u092e":"\u0930\u093e\u0924"},week:{dow:0,doy:6}}),M.defineLocale("hr",{months:{format:"sije\u010dnja_velja\u010de_o\u017eujka_travnja_svibnja_lipnja_srpnja_kolovoza_rujna_listopada_studenoga_prosinca".split("_"),standalone:"sije\u010danj_velja\u010da_o\u017eujak_travanj_svibanj_lipanj_srpanj_kolovoz_rujan_listopad_studeni_prosinac".split("_")},monthsShort:"sij._velj._o\u017eu._tra._svi._lip._srp._kol._ruj._lis._stu._pro.".split("_"),monthsParseExact:!0,weekdays:"nedjelja_ponedjeljak_utorak_srijeda_\u010detvrtak_petak_subota".split("_"),weekdaysShort:"ned._pon._uto._sri._\u010det._pet._sub.".split("_"),weekdaysMin:"ne_po_ut_sr_\u010de_pe_su".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"Do MMMM YYYY",LLL:"Do MMMM YYYY H:mm",LLLL:"dddd, Do MMMM YYYY H:mm"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedjelju] [u] LT";case 3:return"[u] [srijedu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[ju\u010der u] LT",lastWeek:function(){switch(this.day()){case 0:return"[pro\u0161lu] [nedjelju] [u] LT";case 3:return"[pro\u0161lu] [srijedu] [u] LT";case 6:return"[pro\u0161le] [subote] [u] LT";case 1:case 2:case 4:case 5:return"[pro\u0161li] dddd [u] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"prije %s",s:"par sekundi",ss:vs,m:vs,mm:vs,h:vs,hh:vs,d:"dan",dd:vs,M:"mjesec",MM:vs,y:"godinu",yy:vs},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}});var js="vas\xe1rnap h\xe9tf\u0151n kedden szerd\xe1n cs\xfct\xf6rt\xf6k\xf6n p\xe9nteken szombaton".split(" ");function xs(e,a,t,s){var n=e;switch(t){case"s":return s||a?"n\xe9h\xe1ny m\xe1sodperc":"n\xe9h\xe1ny m\xe1sodperce";case"ss":return n+(s||a)?" m\xe1sodperc":" m\xe1sodperce";case"m":return"egy"+(s||a?" perc":" perce");case"mm":return n+(s||a?" perc":" perce");case"h":return"egy"+(s||a?" \xf3ra":" \xf3r\xe1ja");case"hh":return n+(s||a?" \xf3ra":" \xf3r\xe1ja");case"d":return"egy"+(s||a?" nap":" napja");case"dd":return n+(s||a?" nap":" napja");case"M":return"egy"+(s||a?" h\xf3nap":" h\xf3napja");case"MM":return n+(s||a?" h\xf3nap":" h\xf3napja");case"y":return"egy"+(s||a?" \xe9v":" \xe9ve");case"yy":return n+(s||a?" \xe9v":" \xe9ve")}return""}function Ps(e){return(e?"":"[m\xfalt] ")+"["+js[this.day()]+"] LT[-kor]"}function Os(e){return e%100==11||e%10!=1}function Ws(e,a,t,s){var n=e+" ";switch(t){case"s":return a||s?"nokkrar sek\xfandur":"nokkrum sek\xfandum";case"ss":return Os(e)?n+(a||s?"sek\xfandur":"sek\xfandum"):n+"sek\xfanda";case"m":return a?"m\xedn\xfata":"m\xedn\xfatu";case"mm":return Os(e)?n+(a||s?"m\xedn\xfatur":"m\xedn\xfatum"):a?n+"m\xedn\xfata":n+"m\xedn\xfatu";case"hh":return Os(e)?n+(a||s?"klukkustundir":"klukkustundum"):n+"klukkustund";case"d":return a?"dagur":s?"dag":"degi";case"dd":return Os(e)?a?n+"dagar":n+(s?"daga":"d\xf6gum"):a?n+"dagur":n+(s?"dag":"degi");case"M":return a?"m\xe1nu\xf0ur":s?"m\xe1nu\xf0":"m\xe1nu\xf0i";case"MM":return Os(e)?a?n+"m\xe1nu\xf0ir":n+(s?"m\xe1nu\xf0i":"m\xe1nu\xf0um"):a?n+"m\xe1nu\xf0ur":n+(s?"m\xe1nu\xf0":"m\xe1nu\xf0i");case"y":return a||s?"\xe1r":"\xe1ri";case"yy":return Os(e)?n+(a||s?"\xe1r":"\xe1rum"):n+(a||s?"\xe1r":"\xe1ri")}}M.defineLocale("hu",{months:"janu\xe1r_febru\xe1r_m\xe1rcius_\xe1prilis_m\xe1jus_j\xfanius_j\xfalius_augusztus_szeptember_okt\xf3ber_november_december".split("_"),monthsShort:"jan._feb._m\xe1rc._\xe1pr._m\xe1j._j\xfan._j\xfal._aug._szept._okt._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"vas\xe1rnap_h\xe9tf\u0151_kedd_szerda_cs\xfct\xf6rt\xf6k_p\xe9ntek_szombat".split("_"),weekdaysShort:"vas_h\xe9t_kedd_sze_cs\xfct_p\xe9n_szo".split("_"),weekdaysMin:"v_h_k_sze_cs_p_szo".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"YYYY.MM.DD.",LL:"YYYY. MMMM D.",LLL:"YYYY. MMMM D. H:mm",LLLL:"YYYY. MMMM D., dddd H:mm"},meridiemParse:/de|du/i,isPM:function(e){return"u"===e.charAt(1).toLowerCase()},meridiem:function(e,a,t){return e<12?!0===t?"de":"DE":!0===t?"du":"DU"},calendar:{sameDay:"[ma] LT[-kor]",nextDay:"[holnap] LT[-kor]",nextWeek:function(){return Ps.call(this,!0)},lastDay:"[tegnap] LT[-kor]",lastWeek:function(){return Ps.call(this,!1)},sameElse:"L"},relativeTime:{future:"%s m\xfalva",past:"%s",s:xs,ss:xs,m:xs,mm:xs,h:xs,hh:xs,d:xs,dd:xs,M:xs,MM:xs,y:xs,yy:xs},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),M.defineLocale("hy-am",{months:{format:"\u0570\u0578\u0582\u0576\u057e\u0561\u0580\u056b_\u0583\u0565\u057f\u0580\u057e\u0561\u0580\u056b_\u0574\u0561\u0580\u057f\u056b_\u0561\u057a\u0580\u056b\u056c\u056b_\u0574\u0561\u0575\u056b\u057d\u056b_\u0570\u0578\u0582\u0576\u056b\u057d\u056b_\u0570\u0578\u0582\u056c\u056b\u057d\u056b_\u0585\u0563\u0578\u057d\u057f\u0578\u057d\u056b_\u057d\u0565\u057a\u057f\u0565\u0574\u0562\u0565\u0580\u056b_\u0570\u0578\u056f\u057f\u0565\u0574\u0562\u0565\u0580\u056b_\u0576\u0578\u0575\u0565\u0574\u0562\u0565\u0580\u056b_\u0564\u0565\u056f\u057f\u0565\u0574\u0562\u0565\u0580\u056b".split("_"),standalone:"\u0570\u0578\u0582\u0576\u057e\u0561\u0580_\u0583\u0565\u057f\u0580\u057e\u0561\u0580_\u0574\u0561\u0580\u057f_\u0561\u057a\u0580\u056b\u056c_\u0574\u0561\u0575\u056b\u057d_\u0570\u0578\u0582\u0576\u056b\u057d_\u0570\u0578\u0582\u056c\u056b\u057d_\u0585\u0563\u0578\u057d\u057f\u0578\u057d_\u057d\u0565\u057a\u057f\u0565\u0574\u0562\u0565\u0580_\u0570\u0578\u056f\u057f\u0565\u0574\u0562\u0565\u0580_\u0576\u0578\u0575\u0565\u0574\u0562\u0565\u0580_\u0564\u0565\u056f\u057f\u0565\u0574\u0562\u0565\u0580".split("_")},monthsShort:"\u0570\u0576\u057e_\u0583\u057f\u0580_\u0574\u0580\u057f_\u0561\u057a\u0580_\u0574\u0575\u057d_\u0570\u0576\u057d_\u0570\u056c\u057d_\u0585\u0563\u057d_\u057d\u057a\u057f_\u0570\u056f\u057f_\u0576\u0574\u0562_\u0564\u056f\u057f".split("_"),weekdays:"\u056f\u056b\u0580\u0561\u056f\u056b_\u0565\u0580\u056f\u0578\u0582\u0577\u0561\u0562\u0569\u056b_\u0565\u0580\u0565\u0584\u0577\u0561\u0562\u0569\u056b_\u0579\u0578\u0580\u0565\u0584\u0577\u0561\u0562\u0569\u056b_\u0570\u056b\u0576\u0563\u0577\u0561\u0562\u0569\u056b_\u0578\u0582\u0580\u0562\u0561\u0569_\u0577\u0561\u0562\u0561\u0569".split("_"),weekdaysShort:"\u056f\u0580\u056f_\u0565\u0580\u056f_\u0565\u0580\u0584_\u0579\u0580\u0584_\u0570\u0576\u0563_\u0578\u0582\u0580\u0562_\u0577\u0562\u0569".split("_"),weekdaysMin:"\u056f\u0580\u056f_\u0565\u0580\u056f_\u0565\u0580\u0584_\u0579\u0580\u0584_\u0570\u0576\u0563_\u0578\u0582\u0580\u0562_\u0577\u0562\u0569".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY \u0569.",LLL:"D MMMM YYYY \u0569., HH:mm",LLLL:"dddd, D MMMM YYYY \u0569., HH:mm"},calendar:{sameDay:"[\u0561\u0575\u057d\u0585\u0580] LT",nextDay:"[\u057e\u0561\u0572\u0568] LT",lastDay:"[\u0565\u0580\u0565\u056f] LT",nextWeek:function(){return"dddd [\u0585\u0580\u0568 \u056a\u0561\u0574\u0568] LT"},lastWeek:function(){return"[\u0561\u0576\u0581\u0561\u056e] dddd [\u0585\u0580\u0568 \u056a\u0561\u0574\u0568] LT"},sameElse:"L"},relativeTime:{future:"%s \u0570\u0565\u057f\u0578",past:"%s \u0561\u057c\u0561\u057b",s:"\u0574\u056b \u0584\u0561\u0576\u056b \u057e\u0561\u0575\u0580\u056f\u0575\u0561\u0576",ss:"%d \u057e\u0561\u0575\u0580\u056f\u0575\u0561\u0576",m:"\u0580\u0578\u057a\u0565",mm:"%d \u0580\u0578\u057a\u0565",h:"\u056a\u0561\u0574",hh:"%d \u056a\u0561\u0574",d:"\u0585\u0580",dd:"%d \u0585\u0580",M:"\u0561\u0574\u056b\u057d",MM:"%d \u0561\u0574\u056b\u057d",y:"\u057f\u0561\u0580\u056b",yy:"%d \u057f\u0561\u0580\u056b"},meridiemParse:/\u0563\u056b\u0577\u0565\u0580\u057e\u0561|\u0561\u057c\u0561\u057e\u0578\u057f\u057e\u0561|\u0581\u0565\u0580\u0565\u056f\u057e\u0561|\u0565\u0580\u0565\u056f\u0578\u0575\u0561\u0576/,isPM:function(e){return/^(\u0581\u0565\u0580\u0565\u056f\u057e\u0561|\u0565\u0580\u0565\u056f\u0578\u0575\u0561\u0576)$/.test(e)},meridiem:function(e){return e<4?"\u0563\u056b\u0577\u0565\u0580\u057e\u0561":e<12?"\u0561\u057c\u0561\u057e\u0578\u057f\u057e\u0561":e<17?"\u0581\u0565\u0580\u0565\u056f\u057e\u0561":"\u0565\u0580\u0565\u056f\u0578\u0575\u0561\u0576"},dayOfMonthOrdinalParse:/\d{1,2}|\d{1,2}-(\u056b\u0576|\u0580\u0564)/,ordinal:function(e,a){switch(a){case"DDD":case"w":case"W":case"DDDo":return 1===e?e+"-\u056b\u0576":e+"-\u0580\u0564";default:return e}},week:{dow:1,doy:7}}),M.defineLocale("id",{months:"Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_November_Desember".split("_"),monthsShort:"Jan_Feb_Mar_Apr_Mei_Jun_Jul_Agt_Sep_Okt_Nov_Des".split("_"),weekdays:"Minggu_Senin_Selasa_Rabu_Kamis_Jumat_Sabtu".split("_"),weekdaysShort:"Min_Sen_Sel_Rab_Kam_Jum_Sab".split("_"),weekdaysMin:"Mg_Sn_Sl_Rb_Km_Jm_Sb".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [pukul] HH.mm",LLLL:"dddd, D MMMM YYYY [pukul] HH.mm"},meridiemParse:/pagi|siang|sore|malam/,meridiemHour:function(e,a){return 12===e&&(e=0),"pagi"===a?e:"siang"===a?11<=e?e:e+12:"sore"===a||"malam"===a?e+12:void 0},meridiem:function(e,a,t){return e<11?"pagi":e<15?"siang":e<19?"sore":"malam"},calendar:{sameDay:"[Hari ini pukul] LT",nextDay:"[Besok pukul] LT",nextWeek:"dddd [pukul] LT",lastDay:"[Kemarin pukul] LT",lastWeek:"dddd [lalu pukul] LT",sameElse:"L"},relativeTime:{future:"dalam %s",past:"%s yang lalu",s:"beberapa detik",ss:"%d detik",m:"semenit",mm:"%d menit",h:"sejam",hh:"%d jam",d:"sehari",dd:"%d hari",M:"sebulan",MM:"%d bulan",y:"setahun",yy:"%d tahun"},week:{dow:0,doy:6}}),M.defineLocale("is",{months:"jan\xfaar_febr\xfaar_mars_apr\xedl_ma\xed_j\xfan\xed_j\xfal\xed_\xe1g\xfast_september_okt\xf3ber_n\xf3vember_desember".split("_"),monthsShort:"jan_feb_mar_apr_ma\xed_j\xfan_j\xfal_\xe1g\xfa_sep_okt_n\xf3v_des".split("_"),weekdays:"sunnudagur_m\xe1nudagur_\xferi\xf0judagur_mi\xf0vikudagur_fimmtudagur_f\xf6studagur_laugardagur".split("_"),weekdaysShort:"sun_m\xe1n_\xferi_mi\xf0_fim_f\xf6s_lau".split("_"),weekdaysMin:"Su_M\xe1_\xder_Mi_Fi_F\xf6_La".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY [kl.] H:mm",LLLL:"dddd, D. MMMM YYYY [kl.] H:mm"},calendar:{sameDay:"[\xed dag kl.] LT",nextDay:"[\xe1 morgun kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[\xed g\xe6r kl.] LT",lastWeek:"[s\xed\xf0asta] dddd [kl.] LT",sameElse:"L"},relativeTime:{future:"eftir %s",past:"fyrir %s s\xed\xf0an",s:Ws,ss:Ws,m:Ws,mm:Ws,h:"klukkustund",hh:Ws,d:Ws,dd:Ws,M:Ws,MM:Ws,y:Ws,yy:Ws},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),M.defineLocale("it-ch",{months:"gennaio_febbraio_marzo_aprile_maggio_giugno_luglio_agosto_settembre_ottobre_novembre_dicembre".split("_"),monthsShort:"gen_feb_mar_apr_mag_giu_lug_ago_set_ott_nov_dic".split("_"),weekdays:"domenica_luned\xec_marted\xec_mercoled\xec_gioved\xec_venerd\xec_sabato".split("_"),weekdaysShort:"dom_lun_mar_mer_gio_ven_sab".split("_"),weekdaysMin:"do_lu_ma_me_gi_ve_sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[Oggi alle] LT",nextDay:"[Domani alle] LT",nextWeek:"dddd [alle] LT",lastDay:"[Ieri alle] LT",lastWeek:function(){switch(this.day()){case 0:return"[la scorsa] dddd [alle] LT";default:return"[lo scorso] dddd [alle] LT"}},sameElse:"L"},relativeTime:{future:function(e){return(/^[0-9].+$/.test(e)?"tra":"in")+" "+e},past:"%s fa",s:"alcuni secondi",ss:"%d secondi",m:"un minuto",mm:"%d minuti",h:"un'ora",hh:"%d ore",d:"un giorno",dd:"%d giorni",M:"un mese",MM:"%d mesi",y:"un anno",yy:"%d anni"},dayOfMonthOrdinalParse:/\d{1,2}\xba/,ordinal:"%d\xba",week:{dow:1,doy:4}}),M.defineLocale("it",{months:"gennaio_febbraio_marzo_aprile_maggio_giugno_luglio_agosto_settembre_ottobre_novembre_dicembre".split("_"),monthsShort:"gen_feb_mar_apr_mag_giu_lug_ago_set_ott_nov_dic".split("_"),weekdays:"domenica_luned\xec_marted\xec_mercoled\xec_gioved\xec_venerd\xec_sabato".split("_"),weekdaysShort:"dom_lun_mar_mer_gio_ven_sab".split("_"),weekdaysMin:"do_lu_ma_me_gi_ve_sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:function(){return"[Oggi a"+(1<this.hours()?"lle ":0===this.hours()?" ":"ll'")+"]LT"},nextDay:function(){return"[Domani a"+(1<this.hours()?"lle ":0===this.hours()?" ":"ll'")+"]LT"},nextWeek:function(){return"dddd [a"+(1<this.hours()?"lle ":0===this.hours()?" ":"ll'")+"]LT"},lastDay:function(){return"[Ieri a"+(1<this.hours()?"lle ":0===this.hours()?" ":"ll'")+"]LT"},lastWeek:function(){switch(this.day()){case 0:return"[La scorsa] dddd [a"+(1<this.hours()?"lle ":0===this.hours()?" ":"ll'")+"]LT";default:return"[Lo scorso] dddd [a"+(1<this.hours()?"lle ":0===this.hours()?" ":"ll'")+"]LT"}},sameElse:"L"},relativeTime:{future:"tra %s",past:"%s fa",s:"alcuni secondi",ss:"%d secondi",m:"un minuto",mm:"%d minuti",h:"un'ora",hh:"%d ore",d:"un giorno",dd:"%d giorni",w:"una settimana",ww:"%d settimane",M:"un mese",MM:"%d mesi",y:"un anno",yy:"%d anni"},dayOfMonthOrdinalParse:/\d{1,2}\xba/,ordinal:"%d\xba",week:{dow:1,doy:4}}),M.defineLocale("ja",{eras:[{since:"2019-05-01",offset:1,name:"\u4ee4\u548c",narrow:"\u32ff",abbr:"R"},{since:"1989-01-08",until:"2019-04-30",offset:1,name:"\u5e73\u6210",narrow:"\u337b",abbr:"H"},{since:"1926-12-25",until:"1989-01-07",offset:1,name:"\u662d\u548c",narrow:"\u337c",abbr:"S"},{since:"1912-07-30",until:"1926-12-24",offset:1,name:"\u5927\u6b63",narrow:"\u337d",abbr:"T"},{since:"1873-01-01",until:"1912-07-29",offset:6,name:"\u660e\u6cbb",narrow:"\u337e",abbr:"M"},{since:"0001-01-01",until:"1873-12-31",offset:1,name:"\u897f\u66a6",narrow:"AD",abbr:"AD"},{since:"0000-12-31",until:-1/0,offset:1,name:"\u7d00\u5143\u524d",narrow:"BC",abbr:"BC"}],eraYearOrdinalRegex:/(\u5143|\d+)\u5e74/,eraYearOrdinalParse:function(e,a){return"\u5143"===a[1]?1:parseInt(a[1]||e,10)},months:"1\u6708_2\u6708_3\u6708_4\u6708_5\u6708_6\u6708_7\u6708_8\u6708_9\u6708_10\u6708_11\u6708_12\u6708".split("_"),monthsShort:"1\u6708_2\u6708_3\u6708_4\u6708_5\u6708_6\u6708_7\u6708_8\u6708_9\u6708_10\u6708_11\u6708_12\u6708".split("_"),weekdays:"\u65e5\u66dc\u65e5_\u6708\u66dc\u65e5_\u706b\u66dc\u65e5_\u6c34\u66dc\u65e5_\u6728\u66dc\u65e5_\u91d1\u66dc\u65e5_\u571f\u66dc\u65e5".split("_"),weekdaysShort:"\u65e5_\u6708_\u706b_\u6c34_\u6728_\u91d1_\u571f".split("_"),weekdaysMin:"\u65e5_\u6708_\u706b_\u6c34_\u6728_\u91d1_\u571f".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY/MM/DD",LL:"YYYY\u5e74M\u6708D\u65e5",LLL:"YYYY\u5e74M\u6708D\u65e5 HH:mm",LLLL:"YYYY\u5e74M\u6708D\u65e5 dddd HH:mm",l:"YYYY/MM/DD",ll:"YYYY\u5e74M\u6708D\u65e5",lll:"YYYY\u5e74M\u6708D\u65e5 HH:mm",llll:"YYYY\u5e74M\u6708D\u65e5(ddd) HH:mm"},meridiemParse:/\u5348\u524d|\u5348\u5f8c/i,isPM:function(e){return"\u5348\u5f8c"===e},meridiem:function(e,a,t){return e<12?"\u5348\u524d":"\u5348\u5f8c"},calendar:{sameDay:"[\u4eca\u65e5] LT",nextDay:"[\u660e\u65e5] LT",nextWeek:function(e){return e.week()!==this.week()?"[\u6765\u9031]dddd LT":"dddd LT"},lastDay:"[\u6628\u65e5] LT",lastWeek:function(e){return this.week()!==e.week()?"[\u5148\u9031]dddd LT":"dddd LT"},sameElse:"L"},dayOfMonthOrdinalParse:/\d{1,2}\u65e5/,ordinal:function(e,a){switch(a){case"y":return 1===e?"\u5143\u5e74":e+"\u5e74";case"d":case"D":case"DDD":return e+"\u65e5";default:return e}},relativeTime:{future:"%s\u5f8c",past:"%s\u524d",s:"\u6570\u79d2",ss:"%d\u79d2",m:"1\u5206",mm:"%d\u5206",h:"1\u6642\u9593",hh:"%d\u6642\u9593",d:"1\u65e5",dd:"%d\u65e5",M:"1\u30f6\u6708",MM:"%d\u30f6\u6708",y:"1\u5e74",yy:"%d\u5e74"}}),M.defineLocale("jv",{months:"Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_Nopember_Desember".split("_"),monthsShort:"Jan_Feb_Mar_Apr_Mei_Jun_Jul_Ags_Sep_Okt_Nop_Des".split("_"),weekdays:"Minggu_Senen_Seloso_Rebu_Kemis_Jemuwah_Septu".split("_"),weekdaysShort:"Min_Sen_Sel_Reb_Kem_Jem_Sep".split("_"),weekdaysMin:"Mg_Sn_Sl_Rb_Km_Jm_Sp".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [pukul] HH.mm",LLLL:"dddd, D MMMM YYYY [pukul] HH.mm"},meridiemParse:/enjing|siyang|sonten|ndalu/,meridiemHour:function(e,a){return 12===e&&(e=0),"enjing"===a?e:"siyang"===a?11<=e?e:e+12:"sonten"===a||"ndalu"===a?e+12:void 0},meridiem:function(e,a,t){return e<11?"enjing":e<15?"siyang":e<19?"sonten":"ndalu"},calendar:{sameDay:"[Dinten puniko pukul] LT",nextDay:"[Mbenjang pukul] LT",nextWeek:"dddd [pukul] LT",lastDay:"[Kala wingi pukul] LT",lastWeek:"dddd [kepengker pukul] LT",sameElse:"L"},relativeTime:{future:"wonten ing %s",past:"%s ingkang kepengker",s:"sawetawis detik",ss:"%d detik",m:"setunggal menit",mm:"%d menit",h:"setunggal jam",hh:"%d jam",d:"sedinten",dd:"%d dinten",M:"sewulan",MM:"%d wulan",y:"setaun",yy:"%d taun"},week:{dow:1,doy:7}}),M.defineLocale("ka",{months:"\u10d8\u10d0\u10dc\u10d5\u10d0\u10e0\u10d8_\u10d7\u10d4\u10d1\u10d4\u10e0\u10d5\u10d0\u10da\u10d8_\u10db\u10d0\u10e0\u10e2\u10d8_\u10d0\u10de\u10e0\u10d8\u10da\u10d8_\u10db\u10d0\u10d8\u10e1\u10d8_\u10d8\u10d5\u10dc\u10d8\u10e1\u10d8_\u10d8\u10d5\u10da\u10d8\u10e1\u10d8_\u10d0\u10d2\u10d5\u10d8\u10e1\u10e2\u10dd_\u10e1\u10d4\u10e5\u10e2\u10d4\u10db\u10d1\u10d4\u10e0\u10d8_\u10dd\u10e5\u10e2\u10dd\u10db\u10d1\u10d4\u10e0\u10d8_\u10dc\u10dd\u10d4\u10db\u10d1\u10d4\u10e0\u10d8_\u10d3\u10d4\u10d9\u10d4\u10db\u10d1\u10d4\u10e0\u10d8".split("_"),monthsShort:"\u10d8\u10d0\u10dc_\u10d7\u10d4\u10d1_\u10db\u10d0\u10e0_\u10d0\u10de\u10e0_\u10db\u10d0\u10d8_\u10d8\u10d5\u10dc_\u10d8\u10d5\u10da_\u10d0\u10d2\u10d5_\u10e1\u10d4\u10e5_\u10dd\u10e5\u10e2_\u10dc\u10dd\u10d4_\u10d3\u10d4\u10d9".split("_"),weekdays:{standalone:"\u10d9\u10d5\u10d8\u10e0\u10d0_\u10dd\u10e0\u10e8\u10d0\u10d1\u10d0\u10d7\u10d8_\u10e1\u10d0\u10db\u10e8\u10d0\u10d1\u10d0\u10d7\u10d8_\u10dd\u10d7\u10ee\u10e8\u10d0\u10d1\u10d0\u10d7\u10d8_\u10ee\u10e3\u10d7\u10e8\u10d0\u10d1\u10d0\u10d7\u10d8_\u10de\u10d0\u10e0\u10d0\u10e1\u10d9\u10d4\u10d5\u10d8_\u10e8\u10d0\u10d1\u10d0\u10d7\u10d8".split("_"),format:"\u10d9\u10d5\u10d8\u10e0\u10d0\u10e1_\u10dd\u10e0\u10e8\u10d0\u10d1\u10d0\u10d7\u10e1_\u10e1\u10d0\u10db\u10e8\u10d0\u10d1\u10d0\u10d7\u10e1_\u10dd\u10d7\u10ee\u10e8\u10d0\u10d1\u10d0\u10d7\u10e1_\u10ee\u10e3\u10d7\u10e8\u10d0\u10d1\u10d0\u10d7\u10e1_\u10de\u10d0\u10e0\u10d0\u10e1\u10d9\u10d4\u10d5\u10e1_\u10e8\u10d0\u10d1\u10d0\u10d7\u10e1".split("_"),isFormat:/(\u10ec\u10d8\u10dc\u10d0|\u10e8\u10d4\u10db\u10d3\u10d4\u10d2)/},weekdaysShort:"\u10d9\u10d5\u10d8_\u10dd\u10e0\u10e8_\u10e1\u10d0\u10db_\u10dd\u10d7\u10ee_\u10ee\u10e3\u10d7_\u10de\u10d0\u10e0_\u10e8\u10d0\u10d1".split("_"),weekdaysMin:"\u10d9\u10d5_\u10dd\u10e0_\u10e1\u10d0_\u10dd\u10d7_\u10ee\u10e3_\u10de\u10d0_\u10e8\u10d0".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[\u10d3\u10e6\u10d4\u10e1] LT[-\u10d6\u10d4]",nextDay:"[\u10ee\u10d5\u10d0\u10da] LT[-\u10d6\u10d4]",lastDay:"[\u10d2\u10e3\u10e8\u10d8\u10dc] LT[-\u10d6\u10d4]",nextWeek:"[\u10e8\u10d4\u10db\u10d3\u10d4\u10d2] dddd LT[-\u10d6\u10d4]",lastWeek:"[\u10ec\u10d8\u10dc\u10d0] dddd LT-\u10d6\u10d4",sameElse:"L"},relativeTime:{future:function(e){return e.replace(/(\u10ec\u10d0\u10db|\u10ec\u10e3\u10d7|\u10e1\u10d0\u10d0\u10d7|\u10ec\u10d4\u10da|\u10d3\u10e6|\u10d7\u10d5)(\u10d8|\u10d4)/,function(e,a,t){return"\u10d8"===t?a+"\u10e8\u10d8":a+t+"\u10e8\u10d8"})},past:function(e){return/(\u10ec\u10d0\u10db\u10d8|\u10ec\u10e3\u10d7\u10d8|\u10e1\u10d0\u10d0\u10d7\u10d8|\u10d3\u10e6\u10d4|\u10d7\u10d5\u10d4)/.test(e)?e.replace(/(\u10d8|\u10d4)$/,"\u10d8\u10e1 \u10ec\u10d8\u10dc"):/\u10ec\u10d4\u10da\u10d8/.test(e)?e.replace(/\u10ec\u10d4\u10da\u10d8$/,"\u10ec\u10da\u10d8\u10e1 \u10ec\u10d8\u10dc"):e},s:"\u10e0\u10d0\u10db\u10d3\u10d4\u10dc\u10d8\u10db\u10d4 \u10ec\u10d0\u10db\u10d8",ss:"%d \u10ec\u10d0\u10db\u10d8",m:"\u10ec\u10e3\u10d7\u10d8",mm:"%d \u10ec\u10e3\u10d7\u10d8",h:"\u10e1\u10d0\u10d0\u10d7\u10d8",hh:"%d \u10e1\u10d0\u10d0\u10d7\u10d8",d:"\u10d3\u10e6\u10d4",dd:"%d \u10d3\u10e6\u10d4",M:"\u10d7\u10d5\u10d4",MM:"%d \u10d7\u10d5\u10d4",y:"\u10ec\u10d4\u10da\u10d8",yy:"%d \u10ec\u10d4\u10da\u10d8"},dayOfMonthOrdinalParse:/0|1-\u10da\u10d8|\u10db\u10d4-\d{1,2}|\d{1,2}-\u10d4/,ordinal:function(e){return 0===e?e:1===e?e+"-\u10da\u10d8":e<20||e<=100&&e%20==0||e%100==0?"\u10db\u10d4-"+e:e+"-\u10d4"},week:{dow:1,doy:7}});var As={0:"-\u0448\u0456",1:"-\u0448\u0456",2:"-\u0448\u0456",3:"-\u0448\u0456",4:"-\u0448\u0456",5:"-\u0448\u0456",6:"-\u0448\u044b",7:"-\u0448\u0456",8:"-\u0448\u0456",9:"-\u0448\u044b",10:"-\u0448\u044b",20:"-\u0448\u044b",30:"-\u0448\u044b",40:"-\u0448\u044b",50:"-\u0448\u0456",60:"-\u0448\u044b",70:"-\u0448\u0456",80:"-\u0448\u0456",90:"-\u0448\u044b",100:"-\u0448\u0456"},Es=(M.defineLocale("kk",{months:"\u049b\u0430\u04a3\u0442\u0430\u0440_\u0430\u049b\u043f\u0430\u043d_\u043d\u0430\u0443\u0440\u044b\u0437_\u0441\u04d9\u0443\u0456\u0440_\u043c\u0430\u043c\u044b\u0440_\u043c\u0430\u0443\u0441\u044b\u043c_\u0448\u0456\u043b\u0434\u0435_\u0442\u0430\u043c\u044b\u0437_\u049b\u044b\u0440\u043a\u04af\u0439\u0435\u043a_\u049b\u0430\u0437\u0430\u043d_\u049b\u0430\u0440\u0430\u0448\u0430_\u0436\u0435\u043b\u0442\u043e\u049b\u0441\u0430\u043d".split("_"),monthsShort:"\u049b\u0430\u04a3_\u0430\u049b\u043f_\u043d\u0430\u0443_\u0441\u04d9\u0443_\u043c\u0430\u043c_\u043c\u0430\u0443_\u0448\u0456\u043b_\u0442\u0430\u043c_\u049b\u044b\u0440_\u049b\u0430\u0437_\u049b\u0430\u0440_\u0436\u0435\u043b".split("_"),weekdays:"\u0436\u0435\u043a\u0441\u0435\u043d\u0431\u0456_\u0434\u04af\u0439\u0441\u0435\u043d\u0431\u0456_\u0441\u0435\u0439\u0441\u0435\u043d\u0431\u0456_\u0441\u04d9\u0440\u0441\u0435\u043d\u0431\u0456_\u0431\u0435\u0439\u0441\u0435\u043d\u0431\u0456_\u0436\u04b1\u043c\u0430_\u0441\u0435\u043d\u0431\u0456".split("_"),weekdaysShort:"\u0436\u0435\u043a_\u0434\u04af\u0439_\u0441\u0435\u0439_\u0441\u04d9\u0440_\u0431\u0435\u0439_\u0436\u04b1\u043c_\u0441\u0435\u043d".split("_"),weekdaysMin:"\u0436\u043a_\u0434\u0439_\u0441\u0439_\u0441\u0440_\u0431\u0439_\u0436\u043c_\u0441\u043d".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[\u0411\u04af\u0433\u0456\u043d \u0441\u0430\u0493\u0430\u0442] LT",nextDay:"[\u0415\u0440\u0442\u0435\u04a3 \u0441\u0430\u0493\u0430\u0442] LT",nextWeek:"dddd [\u0441\u0430\u0493\u0430\u0442] LT",lastDay:"[\u041a\u0435\u0448\u0435 \u0441\u0430\u0493\u0430\u0442] LT",lastWeek:"[\u04e8\u0442\u043a\u0435\u043d \u0430\u043f\u0442\u0430\u043d\u044b\u04a3] dddd [\u0441\u0430\u0493\u0430\u0442] LT",sameElse:"L"},relativeTime:{future:"%s \u0456\u0448\u0456\u043d\u0434\u0435",past:"%s \u0431\u04b1\u0440\u044b\u043d",s:"\u0431\u0456\u0440\u043d\u0435\u0448\u0435 \u0441\u0435\u043a\u0443\u043d\u0434",ss:"%d \u0441\u0435\u043a\u0443\u043d\u0434",m:"\u0431\u0456\u0440 \u043c\u0438\u043d\u0443\u0442",mm:"%d \u043c\u0438\u043d\u0443\u0442",h:"\u0431\u0456\u0440 \u0441\u0430\u0493\u0430\u0442",hh:"%d \u0441\u0430\u0493\u0430\u0442",d:"\u0431\u0456\u0440 \u043a\u04af\u043d",dd:"%d \u043a\u04af\u043d",M:"\u0431\u0456\u0440 \u0430\u0439",MM:"%d \u0430\u0439",y:"\u0431\u0456\u0440 \u0436\u044b\u043b",yy:"%d \u0436\u044b\u043b"},dayOfMonthOrdinalParse:/\d{1,2}-(\u0448\u0456|\u0448\u044b)/,ordinal:function(e){return e+(As[e]||As[e%10]||As[100<=e?100:null])},week:{dow:1,doy:7}}),{1:"\u17e1",2:"\u17e2",3:"\u17e3",4:"\u17e4",5:"\u17e5",6:"\u17e6",7:"\u17e7",8:"\u17e8",9:"\u17e9",0:"\u17e0"}),Fs={"\u17e1":"1","\u17e2":"2","\u17e3":"3","\u17e4":"4","\u17e5":"5","\u17e6":"6","\u17e7":"7","\u17e8":"8","\u17e9":"9","\u17e0":"0"},zs=(M.defineLocale("km",{months:"\u1798\u1780\u179a\u17b6_\u1780\u17bb\u1798\u17d2\u1797\u17c8_\u1798\u17b8\u1793\u17b6_\u1798\u17c1\u179f\u17b6_\u17a7\u179f\u1797\u17b6_\u1798\u17b7\u1790\u17bb\u1793\u17b6_\u1780\u1780\u17d2\u1780\u178a\u17b6_\u179f\u17b8\u17a0\u17b6_\u1780\u1789\u17d2\u1789\u17b6_\u178f\u17bb\u179b\u17b6_\u179c\u17b7\u1785\u17d2\u1786\u17b7\u1780\u17b6_\u1792\u17d2\u1793\u17bc".split("_"),monthsShort:"\u1798\u1780\u179a\u17b6_\u1780\u17bb\u1798\u17d2\u1797\u17c8_\u1798\u17b8\u1793\u17b6_\u1798\u17c1\u179f\u17b6_\u17a7\u179f\u1797\u17b6_\u1798\u17b7\u1790\u17bb\u1793\u17b6_\u1780\u1780\u17d2\u1780\u178a\u17b6_\u179f\u17b8\u17a0\u17b6_\u1780\u1789\u17d2\u1789\u17b6_\u178f\u17bb\u179b\u17b6_\u179c\u17b7\u1785\u17d2\u1786\u17b7\u1780\u17b6_\u1792\u17d2\u1793\u17bc".split("_"),weekdays:"\u17a2\u17b6\u1791\u17b7\u178f\u17d2\u1799_\u1785\u17d0\u1793\u17d2\u1791_\u17a2\u1784\u17d2\u1782\u17b6\u179a_\u1796\u17bb\u1792_\u1796\u17d2\u179a\u17a0\u179f\u17d2\u1794\u178f\u17b7\u17cd_\u179f\u17bb\u1780\u17d2\u179a_\u179f\u17c5\u179a\u17cd".split("_"),weekdaysShort:"\u17a2\u17b6_\u1785_\u17a2_\u1796_\u1796\u17d2\u179a_\u179f\u17bb_\u179f".split("_"),weekdaysMin:"\u17a2\u17b6_\u1785_\u17a2_\u1796_\u1796\u17d2\u179a_\u179f\u17bb_\u179f".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},meridiemParse:/\u1796\u17d2\u179a\u17b9\u1780|\u179b\u17d2\u1784\u17b6\u1785/,isPM:function(e){return"\u179b\u17d2\u1784\u17b6\u1785"===e},meridiem:function(e,a,t){return e<12?"\u1796\u17d2\u179a\u17b9\u1780":"\u179b\u17d2\u1784\u17b6\u1785"},calendar:{sameDay:"[\u1790\u17d2\u1784\u17c3\u1793\u17c1\u17c7 \u1798\u17c9\u17c4\u1784] LT",nextDay:"[\u179f\u17d2\u17a2\u17c2\u1780 \u1798\u17c9\u17c4\u1784] LT",nextWeek:"dddd [\u1798\u17c9\u17c4\u1784] LT",lastDay:"[\u1798\u17d2\u179f\u17b7\u179b\u1798\u17b7\u1789 \u1798\u17c9\u17c4\u1784] LT",lastWeek:"dddd [\u179f\u1794\u17d2\u178f\u17b6\u17a0\u17cd\u1798\u17bb\u1793] [\u1798\u17c9\u17c4\u1784] LT",sameElse:"L"},relativeTime:{future:"%s\u1791\u17c0\u178f",past:"%s\u1798\u17bb\u1793",s:"\u1794\u17c9\u17bb\u1793\u17d2\u1798\u17b6\u1793\u179c\u17b7\u1793\u17b6\u1791\u17b8",ss:"%d \u179c\u17b7\u1793\u17b6\u1791\u17b8",m:"\u1798\u17bd\u1799\u1793\u17b6\u1791\u17b8",mm:"%d \u1793\u17b6\u1791\u17b8",h:"\u1798\u17bd\u1799\u1798\u17c9\u17c4\u1784",hh:"%d \u1798\u17c9\u17c4\u1784",d:"\u1798\u17bd\u1799\u1790\u17d2\u1784\u17c3",dd:"%d \u1790\u17d2\u1784\u17c3",M:"\u1798\u17bd\u1799\u1781\u17c2",MM:"%d \u1781\u17c2",y:"\u1798\u17bd\u1799\u1786\u17d2\u1793\u17b6\u17c6",yy:"%d \u1786\u17d2\u1793\u17b6\u17c6"},dayOfMonthOrdinalParse:/\u1791\u17b8\d{1,2}/,ordinal:"\u1791\u17b8%d",preparse:function(e){return e.replace(/[\u17e1\u17e2\u17e3\u17e4\u17e5\u17e6\u17e7\u17e8\u17e9\u17e0]/g,function(e){return Fs[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return Es[e]})},week:{dow:1,doy:4}}),{1:"\u0ce7",2:"\u0ce8",3:"\u0ce9",4:"\u0cea",5:"\u0ceb",6:"\u0cec",7:"\u0ced",8:"\u0cee",9:"\u0cef",0:"\u0ce6"}),Ns={"\u0ce7":"1","\u0ce8":"2","\u0ce9":"3","\u0cea":"4","\u0ceb":"5","\u0cec":"6","\u0ced":"7","\u0cee":"8","\u0cef":"9","\u0ce6":"0"},Js=(M.defineLocale("kn",{months:"\u0c9c\u0ca8\u0cb5\u0cb0\u0cbf_\u0cab\u0cc6\u0cac\u0ccd\u0cb0\u0cb5\u0cb0\u0cbf_\u0cae\u0cbe\u0cb0\u0ccd\u0c9a\u0ccd_\u0c8f\u0caa\u0ccd\u0cb0\u0cbf\u0cb2\u0ccd_\u0cae\u0cc6\u0cd5_\u0c9c\u0cc2\u0ca8\u0ccd_\u0c9c\u0cc1\u0cb2\u0cc6\u0cd6_\u0c86\u0c97\u0cb8\u0ccd\u0c9f\u0ccd_\u0cb8\u0cc6\u0caa\u0ccd\u0c9f\u0cc6\u0c82\u0cac\u0cb0\u0ccd_\u0c85\u0c95\u0ccd\u0c9f\u0cc6\u0cc2\u0cd5\u0cac\u0cb0\u0ccd_\u0ca8\u0cb5\u0cc6\u0c82\u0cac\u0cb0\u0ccd_\u0ca1\u0cbf\u0cb8\u0cc6\u0c82\u0cac\u0cb0\u0ccd".split("_"),monthsShort:"\u0c9c\u0ca8_\u0cab\u0cc6\u0cac\u0ccd\u0cb0_\u0cae\u0cbe\u0cb0\u0ccd\u0c9a\u0ccd_\u0c8f\u0caa\u0ccd\u0cb0\u0cbf\u0cb2\u0ccd_\u0cae\u0cc6\u0cd5_\u0c9c\u0cc2\u0ca8\u0ccd_\u0c9c\u0cc1\u0cb2\u0cc6\u0cd6_\u0c86\u0c97\u0cb8\u0ccd\u0c9f\u0ccd_\u0cb8\u0cc6\u0caa\u0ccd\u0c9f\u0cc6\u0c82_\u0c85\u0c95\u0ccd\u0c9f\u0cc6\u0cc2\u0cd5_\u0ca8\u0cb5\u0cc6\u0c82_\u0ca1\u0cbf\u0cb8\u0cc6\u0c82".split("_"),monthsParseExact:!0,weekdays:"\u0cad\u0cbe\u0ca8\u0cc1\u0cb5\u0cbe\u0cb0_\u0cb8\u0cc6\u0cc2\u0cd5\u0cae\u0cb5\u0cbe\u0cb0_\u0cae\u0c82\u0c97\u0cb3\u0cb5\u0cbe\u0cb0_\u0cac\u0cc1\u0ca7\u0cb5\u0cbe\u0cb0_\u0c97\u0cc1\u0cb0\u0cc1\u0cb5\u0cbe\u0cb0_\u0cb6\u0cc1\u0c95\u0ccd\u0cb0\u0cb5\u0cbe\u0cb0_\u0cb6\u0ca8\u0cbf\u0cb5\u0cbe\u0cb0".split("_"),weekdaysShort:"\u0cad\u0cbe\u0ca8\u0cc1_\u0cb8\u0cc6\u0cc2\u0cd5\u0cae_\u0cae\u0c82\u0c97\u0cb3_\u0cac\u0cc1\u0ca7_\u0c97\u0cc1\u0cb0\u0cc1_\u0cb6\u0cc1\u0c95\u0ccd\u0cb0_\u0cb6\u0ca8\u0cbf".split("_"),weekdaysMin:"\u0cad\u0cbe_\u0cb8\u0cc6\u0cc2\u0cd5_\u0cae\u0c82_\u0cac\u0cc1_\u0c97\u0cc1_\u0cb6\u0cc1_\u0cb6".split("_"),longDateFormat:{LT:"A h:mm",LTS:"A h:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm",LLLL:"dddd, D MMMM YYYY, A h:mm"},calendar:{sameDay:"[\u0c87\u0c82\u0ca6\u0cc1] LT",nextDay:"[\u0ca8\u0cbe\u0cb3\u0cc6] LT",nextWeek:"dddd, LT",lastDay:"[\u0ca8\u0cbf\u0ca8\u0ccd\u0ca8\u0cc6] LT",lastWeek:"[\u0c95\u0cc6\u0cc2\u0ca8\u0cc6\u0caf] dddd, LT",sameElse:"L"},relativeTime:{future:"%s \u0ca8\u0c82\u0ca4\u0cb0",past:"%s \u0cb9\u0cbf\u0c82\u0ca6\u0cc6",s:"\u0c95\u0cc6\u0cb2\u0cb5\u0cc1 \u0c95\u0ccd\u0cb7\u0ca3\u0c97\u0cb3\u0cc1",ss:"%d \u0cb8\u0cc6\u0c95\u0cc6\u0c82\u0ca1\u0cc1\u0c97\u0cb3\u0cc1",m:"\u0c92\u0c82\u0ca6\u0cc1 \u0ca8\u0cbf\u0cae\u0cbf\u0cb7",mm:"%d \u0ca8\u0cbf\u0cae\u0cbf\u0cb7",h:"\u0c92\u0c82\u0ca6\u0cc1 \u0c97\u0c82\u0c9f\u0cc6",hh:"%d \u0c97\u0c82\u0c9f\u0cc6",d:"\u0c92\u0c82\u0ca6\u0cc1 \u0ca6\u0cbf\u0ca8",dd:"%d \u0ca6\u0cbf\u0ca8",M:"\u0c92\u0c82\u0ca6\u0cc1 \u0ca4\u0cbf\u0c82\u0c97\u0cb3\u0cc1",MM:"%d \u0ca4\u0cbf\u0c82\u0c97\u0cb3\u0cc1",y:"\u0c92\u0c82\u0ca6\u0cc1 \u0cb5\u0cb0\u0ccd\u0cb7",yy:"%d \u0cb5\u0cb0\u0ccd\u0cb7"},preparse:function(e){return e.replace(/[\u0ce7\u0ce8\u0ce9\u0cea\u0ceb\u0cec\u0ced\u0cee\u0cef\u0ce6]/g,function(e){return Ns[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return zs[e]})},meridiemParse:/\u0cb0\u0cbe\u0ca4\u0ccd\u0cb0\u0cbf|\u0cac\u0cc6\u0cb3\u0cbf\u0c97\u0ccd\u0c97\u0cc6|\u0cae\u0ca7\u0ccd\u0caf\u0cbe\u0cb9\u0ccd\u0ca8|\u0cb8\u0c82\u0c9c\u0cc6/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u0cb0\u0cbe\u0ca4\u0ccd\u0cb0\u0cbf"===a?e<4?e:e+12:"\u0cac\u0cc6\u0cb3\u0cbf\u0c97\u0ccd\u0c97\u0cc6"===a?e:"\u0cae\u0ca7\u0ccd\u0caf\u0cbe\u0cb9\u0ccd\u0ca8"===a?10<=e?e:e+12:"\u0cb8\u0c82\u0c9c\u0cc6"===a?e+12:void 0},meridiem:function(e,a,t){return e<4?"\u0cb0\u0cbe\u0ca4\u0ccd\u0cb0\u0cbf":e<10?"\u0cac\u0cc6\u0cb3\u0cbf\u0c97\u0ccd\u0c97\u0cc6":e<17?"\u0cae\u0ca7\u0ccd\u0caf\u0cbe\u0cb9\u0ccd\u0ca8":e<20?"\u0cb8\u0c82\u0c9c\u0cc6":"\u0cb0\u0cbe\u0ca4\u0ccd\u0cb0\u0cbf"},dayOfMonthOrdinalParse:/\d{1,2}(\u0ca8\u0cc6\u0cd5)/,ordinal:function(e){return e+"\u0ca8\u0cc6\u0cd5"},week:{dow:0,doy:6}}),M.defineLocale("ko",{months:"1\uc6d4_2\uc6d4_3\uc6d4_4\uc6d4_5\uc6d4_6\uc6d4_7\uc6d4_8\uc6d4_9\uc6d4_10\uc6d4_11\uc6d4_12\uc6d4".split("_"),monthsShort:"1\uc6d4_2\uc6d4_3\uc6d4_4\uc6d4_5\uc6d4_6\uc6d4_7\uc6d4_8\uc6d4_9\uc6d4_10\uc6d4_11\uc6d4_12\uc6d4".split("_"),weekdays:"\uc77c\uc694\uc77c_\uc6d4\uc694\uc77c_\ud654\uc694\uc77c_\uc218\uc694\uc77c_\ubaa9\uc694\uc77c_\uae08\uc694\uc77c_\ud1a0\uc694\uc77c".split("_"),weekdaysShort:"\uc77c_\uc6d4_\ud654_\uc218_\ubaa9_\uae08_\ud1a0".split("_"),weekdaysMin:"\uc77c_\uc6d4_\ud654_\uc218_\ubaa9_\uae08_\ud1a0".split("_"),longDateFormat:{LT:"A h:mm",LTS:"A h:mm:ss",L:"YYYY.MM.DD.",LL:"YYYY\ub144 MMMM D\uc77c",LLL:"YYYY\ub144 MMMM D\uc77c A h:mm",LLLL:"YYYY\ub144 MMMM D\uc77c dddd A h:mm",l:"YYYY.MM.DD.",ll:"YYYY\ub144 MMMM D\uc77c",lll:"YYYY\ub144 MMMM D\uc77c A h:mm",llll:"YYYY\ub144 MMMM D\uc77c dddd A h:mm"},calendar:{sameDay:"\uc624\ub298 LT",nextDay:"\ub0b4\uc77c LT",nextWeek:"dddd LT",lastDay:"\uc5b4\uc81c LT",lastWeek:"\uc9c0\ub09c\uc8fc dddd LT",sameElse:"L"},relativeTime:{future:"%s \ud6c4",past:"%s \uc804",s:"\uba87 \ucd08",ss:"%d\ucd08",m:"1\ubd84",mm:"%d\ubd84",h:"\ud55c \uc2dc\uac04",hh:"%d\uc2dc\uac04",d:"\ud558\ub8e8",dd:"%d\uc77c",M:"\ud55c \ub2ec",MM:"%d\ub2ec",y:"\uc77c \ub144",yy:"%d\ub144"},dayOfMonthOrdinalParse:/\d{1,2}(\uc77c|\uc6d4|\uc8fc)/,ordinal:function(e,a){switch(a){case"d":case"D":case"DDD":return e+"\uc77c";case"M":return e+"\uc6d4";case"w":case"W":return e+"\uc8fc";default:return e}},meridiemParse:/\uc624\uc804|\uc624\ud6c4/,isPM:function(e){return"\uc624\ud6c4"===e},meridiem:function(e,a,t){return e<12?"\uc624\uc804":"\uc624\ud6c4"}}),{1:"\u0661",2:"\u0662",3:"\u0663",4:"\u0664",5:"\u0665",6:"\u0666",7:"\u0667",8:"\u0668",9:"\u0669",0:"\u0660"}),Rs={"\u0661":"1","\u0662":"2","\u0663":"3","\u0664":"4","\u0665":"5","\u0666":"6","\u0667":"7","\u0668":"8","\u0669":"9","\u0660":"0"},ta=["\u06a9\u0627\u0646\u0648\u0646\u06cc \u062f\u0648\u0648\u06d5\u0645","\u0634\u0648\u0628\u0627\u062a","\u0626\u0627\u0632\u0627\u0631","\u0646\u06cc\u0633\u0627\u0646","\u0626\u0627\u06cc\u0627\u0631","\u062d\u0648\u0632\u06d5\u06cc\u0631\u0627\u0646","\u062a\u06d5\u0645\u0645\u0648\u0632","\u0626\u0627\u0628","\u0626\u06d5\u06cc\u0644\u0648\u0648\u0644","\u062a\u0634\u0631\u06cc\u0646\u06cc \u06cc\u06d5\u0643\u06d5\u0645","\u062a\u0634\u0631\u06cc\u0646\u06cc \u062f\u0648\u0648\u06d5\u0645","\u0643\u0627\u0646\u0648\u0646\u06cc \u06cc\u06d5\u06a9\u06d5\u0645"],Cs=(M.defineLocale("ku",{months:ta,monthsShort:ta,weekdays:"\u06cc\u0647\u200c\u0643\u0634\u0647\u200c\u0645\u0645\u0647\u200c_\u062f\u0648\u0648\u0634\u0647\u200c\u0645\u0645\u0647\u200c_\u0633\u06ce\u0634\u0647\u200c\u0645\u0645\u0647\u200c_\u0686\u0648\u0627\u0631\u0634\u0647\u200c\u0645\u0645\u0647\u200c_\u067e\u06ce\u0646\u062c\u0634\u0647\u200c\u0645\u0645\u0647\u200c_\u0647\u0647\u200c\u06cc\u0646\u06cc_\u0634\u0647\u200c\u0645\u0645\u0647\u200c".split("_"),weekdaysShort:"\u06cc\u0647\u200c\u0643\u0634\u0647\u200c\u0645_\u062f\u0648\u0648\u0634\u0647\u200c\u0645_\u0633\u06ce\u0634\u0647\u200c\u0645_\u0686\u0648\u0627\u0631\u0634\u0647\u200c\u0645_\u067e\u06ce\u0646\u062c\u0634\u0647\u200c\u0645_\u0647\u0647\u200c\u06cc\u0646\u06cc_\u0634\u0647\u200c\u0645\u0645\u0647\u200c".split("_"),weekdaysMin:"\u06cc_\u062f_\u0633_\u0686_\u067e_\u0647_\u0634".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},meridiemParse:/\u0626\u06ce\u0648\u0627\u0631\u0647\u200c|\u0628\u0647\u200c\u06cc\u0627\u0646\u06cc/,isPM:function(e){return/\u0626\u06ce\u0648\u0627\u0631\u0647\u200c/.test(e)},meridiem:function(e,a,t){return e<12?"\u0628\u0647\u200c\u06cc\u0627\u0646\u06cc":"\u0626\u06ce\u0648\u0627\u0631\u0647\u200c"},calendar:{sameDay:"[\u0626\u0647\u200c\u0645\u0631\u06c6 \u0643\u0627\u062a\u0698\u0645\u06ce\u0631] LT",nextDay:"[\u0628\u0647\u200c\u06cc\u0627\u0646\u06cc \u0643\u0627\u062a\u0698\u0645\u06ce\u0631] LT",nextWeek:"dddd [\u0643\u0627\u062a\u0698\u0645\u06ce\u0631] LT",lastDay:"[\u062f\u0648\u06ce\u0646\u06ce \u0643\u0627\u062a\u0698\u0645\u06ce\u0631] LT",lastWeek:"dddd [\u0643\u0627\u062a\u0698\u0645\u06ce\u0631] LT",sameElse:"L"},relativeTime:{future:"\u0644\u0647\u200c %s",past:"%s",s:"\u0686\u0647\u200c\u0646\u062f \u0686\u0631\u0643\u0647\u200c\u06cc\u0647\u200c\u0643",ss:"\u0686\u0631\u0643\u0647\u200c %d",m:"\u06cc\u0647\u200c\u0643 \u062e\u0648\u0644\u0647\u200c\u0643",mm:"%d \u062e\u0648\u0644\u0647\u200c\u0643",h:"\u06cc\u0647\u200c\u0643 \u0643\u0627\u062a\u0698\u0645\u06ce\u0631",hh:"%d \u0643\u0627\u062a\u0698\u0645\u06ce\u0631",d:"\u06cc\u0647\u200c\u0643 \u0695\u06c6\u0698",dd:"%d \u0695\u06c6\u0698",M:"\u06cc\u0647\u200c\u0643 \u0645\u0627\u0646\u06af",MM:"%d \u0645\u0627\u0646\u06af",y:"\u06cc\u0647\u200c\u0643 \u0633\u0627\u06b5",yy:"%d \u0633\u0627\u06b5"},preparse:function(e){return e.replace(/[\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u0660]/g,function(e){return Rs[e]}).replace(/\u060c/g,",")},postformat:function(e){return e.replace(/\d/g,function(e){return Js[e]}).replace(/,/g,"\u060c")},week:{dow:6,doy:12}}),{0:"-\u0447\u04af",1:"-\u0447\u0438",2:"-\u0447\u0438",3:"-\u0447\u04af",4:"-\u0447\u04af",5:"-\u0447\u0438",6:"-\u0447\u044b",7:"-\u0447\u0438",8:"-\u0447\u0438",9:"-\u0447\u0443",10:"-\u0447\u0443",20:"-\u0447\u044b",30:"-\u0447\u0443",40:"-\u0447\u044b",50:"-\u0447\u04af",60:"-\u0447\u044b",70:"-\u0447\u0438",80:"-\u0447\u0438",90:"-\u0447\u0443",100:"-\u0447\u04af"});function Is(e,a,t,s){var n={m:["eng Minutt","enger Minutt"],h:["eng Stonn","enger Stonn"],d:["een Dag","engem Dag"],M:["ee Mount","engem Mount"],y:["ee Joer","engem Joer"]};return a?n[t][0]:n[t][1]}function Us(e){if(e=parseInt(e,10),isNaN(e))return!1;if(e<0)return!0;if(e<10)return 4<=e&&e<=7;var a;if(e<100)return Us(0==(a=e%10)?e/10:a);if(e<1e4){for(;10<=e;)e/=10;return Us(e)}return Us(e/=1e3)}M.defineLocale("ky",{months:"\u044f\u043d\u0432\u0430\u0440\u044c_\u0444\u0435\u0432\u0440\u0430\u043b\u044c_\u043c\u0430\u0440\u0442_\u0430\u043f\u0440\u0435\u043b\u044c_\u043c\u0430\u0439_\u0438\u044e\u043d\u044c_\u0438\u044e\u043b\u044c_\u0430\u0432\u0433\u0443\u0441\u0442_\u0441\u0435\u043d\u0442\u044f\u0431\u0440\u044c_\u043e\u043a\u0442\u044f\u0431\u0440\u044c_\u043d\u043e\u044f\u0431\u0440\u044c_\u0434\u0435\u043a\u0430\u0431\u0440\u044c".split("_"),monthsShort:"\u044f\u043d\u0432_\u0444\u0435\u0432_\u043c\u0430\u0440\u0442_\u0430\u043f\u0440_\u043c\u0430\u0439_\u0438\u044e\u043d\u044c_\u0438\u044e\u043b\u044c_\u0430\u0432\u0433_\u0441\u0435\u043d_\u043e\u043a\u0442_\u043d\u043e\u044f_\u0434\u0435\u043a".split("_"),weekdays:"\u0416\u0435\u043a\u0448\u0435\u043c\u0431\u0438_\u0414\u04af\u0439\u0448\u04e9\u043c\u0431\u04af_\u0428\u0435\u0439\u0448\u0435\u043c\u0431\u0438_\u0428\u0430\u0440\u0448\u0435\u043c\u0431\u0438_\u0411\u0435\u0439\u0448\u0435\u043c\u0431\u0438_\u0416\u0443\u043c\u0430_\u0418\u0448\u0435\u043c\u0431\u0438".split("_"),weekdaysShort:"\u0416\u0435\u043a_\u0414\u04af\u0439_\u0428\u0435\u0439_\u0428\u0430\u0440_\u0411\u0435\u0439_\u0416\u0443\u043c_\u0418\u0448\u0435".split("_"),weekdaysMin:"\u0416\u043a_\u0414\u0439_\u0428\u0439_\u0428\u0440_\u0411\u0439_\u0416\u043c_\u0418\u0448".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[\u0411\u04af\u0433\u04af\u043d \u0441\u0430\u0430\u0442] LT",nextDay:"[\u042d\u0440\u0442\u0435\u04a3 \u0441\u0430\u0430\u0442] LT",nextWeek:"dddd [\u0441\u0430\u0430\u0442] LT",lastDay:"[\u041a\u0435\u0447\u044d\u044d \u0441\u0430\u0430\u0442] LT",lastWeek:"[\u04e8\u0442\u043a\u04e9\u043d \u0430\u043f\u0442\u0430\u043d\u044b\u043d] dddd [\u043a\u04af\u043d\u04af] [\u0441\u0430\u0430\u0442] LT",sameElse:"L"},relativeTime:{future:"%s \u0438\u0447\u0438\u043d\u0434\u0435",past:"%s \u043c\u0443\u0440\u0443\u043d",s:"\u0431\u0438\u0440\u043d\u0435\u0447\u0435 \u0441\u0435\u043a\u0443\u043d\u0434",ss:"%d \u0441\u0435\u043a\u0443\u043d\u0434",m:"\u0431\u0438\u0440 \u043c\u04af\u043d\u04e9\u0442",mm:"%d \u043c\u04af\u043d\u04e9\u0442",h:"\u0431\u0438\u0440 \u0441\u0430\u0430\u0442",hh:"%d \u0441\u0430\u0430\u0442",d:"\u0431\u0438\u0440 \u043a\u04af\u043d",dd:"%d \u043a\u04af\u043d",M:"\u0431\u0438\u0440 \u0430\u0439",MM:"%d \u0430\u0439",y:"\u0431\u0438\u0440 \u0436\u044b\u043b",yy:"%d \u0436\u044b\u043b"},dayOfMonthOrdinalParse:/\d{1,2}-(\u0447\u0438|\u0447\u044b|\u0447\u04af|\u0447\u0443)/,ordinal:function(e){return e+(Cs[e]||Cs[e%10]||Cs[100<=e?100:null])},week:{dow:1,doy:7}}),M.defineLocale("lb",{months:"Januar_Februar_M\xe4erz_Abr\xebll_Mee_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jan._Febr._Mrz._Abr._Mee_Jun._Jul._Aug._Sept._Okt._Nov._Dez.".split("_"),monthsParseExact:!0,weekdays:"Sonndeg_M\xe9indeg_D\xebnschdeg_M\xebttwoch_Donneschdeg_Freideg_Samschdeg".split("_"),weekdaysShort:"So._M\xe9._D\xeb._M\xeb._Do._Fr._Sa.".split("_"),weekdaysMin:"So_M\xe9_D\xeb_M\xeb_Do_Fr_Sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm [Auer]",LTS:"H:mm:ss [Auer]",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm [Auer]",LLLL:"dddd, D. MMMM YYYY H:mm [Auer]"},calendar:{sameDay:"[Haut um] LT",sameElse:"L",nextDay:"[Muer um] LT",nextWeek:"dddd [um] LT",lastDay:"[G\xebschter um] LT",lastWeek:function(){switch(this.day()){case 2:case 4:return"[Leschten] dddd [um] LT";default:return"[Leschte] dddd [um] LT"}}},relativeTime:{future:function(e){return Us(e.substr(0,e.indexOf(" ")))?"a "+e:"an "+e},past:function(e){return Us(e.substr(0,e.indexOf(" ")))?"viru "+e:"virun "+e},s:"e puer Sekonnen",ss:"%d Sekonnen",m:Is,mm:"%d Minutten",h:Is,hh:"%d Stonnen",d:Is,dd:"%d Deeg",M:Is,MM:"%d M\xe9int",y:Is,yy:"%d Joer"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),M.defineLocale("lo",{months:"\u0ea1\u0eb1\u0e87\u0e81\u0ead\u0e99_\u0e81\u0eb8\u0ea1\u0e9e\u0eb2_\u0ea1\u0eb5\u0e99\u0eb2_\u0ec0\u0ea1\u0eaa\u0eb2_\u0e9e\u0eb6\u0e94\u0eaa\u0eb0\u0e9e\u0eb2_\u0ea1\u0eb4\u0e96\u0eb8\u0e99\u0eb2_\u0e81\u0ecd\u0ea5\u0eb0\u0e81\u0ebb\u0e94_\u0eaa\u0eb4\u0e87\u0eab\u0eb2_\u0e81\u0eb1\u0e99\u0e8d\u0eb2_\u0e95\u0eb8\u0ea5\u0eb2_\u0e9e\u0eb0\u0e88\u0eb4\u0e81_\u0e97\u0eb1\u0e99\u0ea7\u0eb2".split("_"),monthsShort:"\u0ea1\u0eb1\u0e87\u0e81\u0ead\u0e99_\u0e81\u0eb8\u0ea1\u0e9e\u0eb2_\u0ea1\u0eb5\u0e99\u0eb2_\u0ec0\u0ea1\u0eaa\u0eb2_\u0e9e\u0eb6\u0e94\u0eaa\u0eb0\u0e9e\u0eb2_\u0ea1\u0eb4\u0e96\u0eb8\u0e99\u0eb2_\u0e81\u0ecd\u0ea5\u0eb0\u0e81\u0ebb\u0e94_\u0eaa\u0eb4\u0e87\u0eab\u0eb2_\u0e81\u0eb1\u0e99\u0e8d\u0eb2_\u0e95\u0eb8\u0ea5\u0eb2_\u0e9e\u0eb0\u0e88\u0eb4\u0e81_\u0e97\u0eb1\u0e99\u0ea7\u0eb2".split("_"),weekdays:"\u0ead\u0eb2\u0e97\u0eb4\u0e94_\u0e88\u0eb1\u0e99_\u0ead\u0eb1\u0e87\u0e84\u0eb2\u0e99_\u0e9e\u0eb8\u0e94_\u0e9e\u0eb0\u0eab\u0eb1\u0e94_\u0eaa\u0eb8\u0e81_\u0ec0\u0eaa\u0ebb\u0eb2".split("_"),weekdaysShort:"\u0e97\u0eb4\u0e94_\u0e88\u0eb1\u0e99_\u0ead\u0eb1\u0e87\u0e84\u0eb2\u0e99_\u0e9e\u0eb8\u0e94_\u0e9e\u0eb0\u0eab\u0eb1\u0e94_\u0eaa\u0eb8\u0e81_\u0ec0\u0eaa\u0ebb\u0eb2".split("_"),weekdaysMin:"\u0e97_\u0e88_\u0ead\u0e84_\u0e9e_\u0e9e\u0eab_\u0eaa\u0e81_\u0eaa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"\u0ea7\u0eb1\u0e99dddd D MMMM YYYY HH:mm"},meridiemParse:/\u0e95\u0ead\u0e99\u0ec0\u0e8a\u0ebb\u0ec9\u0eb2|\u0e95\u0ead\u0e99\u0ec1\u0ea5\u0e87/,isPM:function(e){return"\u0e95\u0ead\u0e99\u0ec1\u0ea5\u0e87"===e},meridiem:function(e,a,t){return e<12?"\u0e95\u0ead\u0e99\u0ec0\u0e8a\u0ebb\u0ec9\u0eb2":"\u0e95\u0ead\u0e99\u0ec1\u0ea5\u0e87"},calendar:{sameDay:"[\u0ea1\u0eb7\u0ec9\u0e99\u0eb5\u0ec9\u0ec0\u0ea7\u0ea5\u0eb2] LT",nextDay:"[\u0ea1\u0eb7\u0ec9\u0ead\u0eb7\u0ec8\u0e99\u0ec0\u0ea7\u0ea5\u0eb2] LT",nextWeek:"[\u0ea7\u0eb1\u0e99]dddd[\u0edc\u0ec9\u0eb2\u0ec0\u0ea7\u0ea5\u0eb2] LT",lastDay:"[\u0ea1\u0eb7\u0ec9\u0ea7\u0eb2\u0e99\u0e99\u0eb5\u0ec9\u0ec0\u0ea7\u0ea5\u0eb2] LT",lastWeek:"[\u0ea7\u0eb1\u0e99]dddd[\u0ec1\u0ea5\u0ec9\u0ea7\u0e99\u0eb5\u0ec9\u0ec0\u0ea7\u0ea5\u0eb2] LT",sameElse:"L"},relativeTime:{future:"\u0ead\u0eb5\u0e81 %s",past:"%s\u0e9c\u0ec8\u0eb2\u0e99\u0ea1\u0eb2",s:"\u0e9a\u0ecd\u0ec8\u0ec0\u0e97\u0ebb\u0ec8\u0eb2\u0ec3\u0e94\u0ea7\u0eb4\u0e99\u0eb2\u0e97\u0eb5",ss:"%d \u0ea7\u0eb4\u0e99\u0eb2\u0e97\u0eb5",m:"1 \u0e99\u0eb2\u0e97\u0eb5",mm:"%d \u0e99\u0eb2\u0e97\u0eb5",h:"1 \u0e8a\u0ebb\u0ec8\u0ea7\u0ec2\u0ea1\u0e87",hh:"%d \u0e8a\u0ebb\u0ec8\u0ea7\u0ec2\u0ea1\u0e87",d:"1 \u0ea1\u0eb7\u0ec9",dd:"%d \u0ea1\u0eb7\u0ec9",M:"1 \u0ec0\u0e94\u0eb7\u0ead\u0e99",MM:"%d \u0ec0\u0e94\u0eb7\u0ead\u0e99",y:"1 \u0e9b\u0eb5",yy:"%d \u0e9b\u0eb5"},dayOfMonthOrdinalParse:/(\u0e97\u0eb5\u0ec8)\d{1,2}/,ordinal:function(e){return"\u0e97\u0eb5\u0ec8"+e}});var Gs={ss:"sekund\u0117_sekund\u017ei\u0173_sekundes",m:"minut\u0117_minut\u0117s_minut\u0119",mm:"minut\u0117s_minu\u010di\u0173_minutes",h:"valanda_valandos_valand\u0105",hh:"valandos_valand\u0173_valandas",d:"diena_dienos_dien\u0105",dd:"dienos_dien\u0173_dienas",M:"m\u0117nuo_m\u0117nesio_m\u0117nes\u012f",MM:"m\u0117nesiai_m\u0117nesi\u0173_m\u0117nesius",y:"metai_met\u0173_metus",yy:"metai_met\u0173_metus"};function Vs(e,a,t,s){return a?Ks(t)[0]:s?Ks(t)[1]:Ks(t)[2]}function Bs(e){return e%10==0||10<e&&e<20}function Ks(e){return Gs[e].split("_")}function qs(e,a,t,s){var n=e+" ";return 1===e?n+Vs(0,a,t[0],s):a?n+(Bs(e)?Ks(t)[1]:Ks(t)[0]):s?n+Ks(t)[1]:n+(Bs(e)?Ks(t)[1]:Ks(t)[2])}M.defineLocale("lt",{months:{format:"sausio_vasario_kovo_baland\u017eio_gegu\u017e\u0117s_bir\u017eelio_liepos_rugpj\u016b\u010dio_rugs\u0117jo_spalio_lapkri\u010dio_gruod\u017eio".split("_"),standalone:"sausis_vasaris_kovas_balandis_gegu\u017e\u0117_bir\u017eelis_liepa_rugpj\u016btis_rugs\u0117jis_spalis_lapkritis_gruodis".split("_"),isFormat:/D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|MMMM?(\[[^\[\]]*\]|\s)+D[oD]?/},monthsShort:"sau_vas_kov_bal_geg_bir_lie_rgp_rgs_spa_lap_grd".split("_"),weekdays:{format:"sekmadien\u012f_pirmadien\u012f_antradien\u012f_tre\u010diadien\u012f_ketvirtadien\u012f_penktadien\u012f_\u0161e\u0161tadien\u012f".split("_"),standalone:"sekmadienis_pirmadienis_antradienis_tre\u010diadienis_ketvirtadienis_penktadienis_\u0161e\u0161tadienis".split("_"),isFormat:/dddd HH:mm/},weekdaysShort:"Sek_Pir_Ant_Tre_Ket_Pen_\u0160e\u0161".split("_"),weekdaysMin:"S_P_A_T_K_Pn_\u0160".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"YYYY [m.] MMMM D [d.]",LLL:"YYYY [m.] MMMM D [d.], HH:mm [val.]",LLLL:"YYYY [m.] MMMM D [d.], dddd, HH:mm [val.]",l:"YYYY-MM-DD",ll:"YYYY [m.] MMMM D [d.]",lll:"YYYY [m.] MMMM D [d.], HH:mm [val.]",llll:"YYYY [m.] MMMM D [d.], ddd, HH:mm [val.]"},calendar:{sameDay:"[\u0160iandien] LT",nextDay:"[Rytoj] LT",nextWeek:"dddd LT",lastDay:"[Vakar] LT",lastWeek:"[Pra\u0117jus\u012f] dddd LT",sameElse:"L"},relativeTime:{future:"po %s",past:"prie\u0161 %s",s:function(e,a,t,s){return a?"kelios sekund\u0117s":s?"keli\u0173 sekund\u017ei\u0173":"kelias sekundes"},ss:qs,m:Vs,mm:qs,h:Vs,hh:qs,d:Vs,dd:qs,M:Vs,MM:qs,y:Vs,yy:qs},dayOfMonthOrdinalParse:/\d{1,2}-oji/,ordinal:function(e){return e+"-oji"},week:{dow:1,doy:4}});var Zs={ss:"sekundes_sekund\u0113m_sekunde_sekundes".split("_"),m:"min\u016btes_min\u016bt\u0113m_min\u016bte_min\u016btes".split("_"),mm:"min\u016btes_min\u016bt\u0113m_min\u016bte_min\u016btes".split("_"),h:"stundas_stund\u0101m_stunda_stundas".split("_"),hh:"stundas_stund\u0101m_stunda_stundas".split("_"),d:"dienas_dien\u0101m_diena_dienas".split("_"),dd:"dienas_dien\u0101m_diena_dienas".split("_"),M:"m\u0113ne\u0161a_m\u0113ne\u0161iem_m\u0113nesis_m\u0113ne\u0161i".split("_"),MM:"m\u0113ne\u0161a_m\u0113ne\u0161iem_m\u0113nesis_m\u0113ne\u0161i".split("_"),y:"gada_gadiem_gads_gadi".split("_"),yy:"gada_gadiem_gads_gadi".split("_")};function $s(e,a,t){return t?a%10==1&&a%100!=11?e[2]:e[3]:a%10==1&&a%100!=11?e[0]:e[1]}function Qs(e,a,t){return e+" "+$s(Zs[t],e,a)}function Xs(e,a,t){return $s(Zs[t],e,a)}M.defineLocale("lv",{months:"janv\u0101ris_febru\u0101ris_marts_apr\u012blis_maijs_j\u016bnijs_j\u016blijs_augusts_septembris_oktobris_novembris_decembris".split("_"),monthsShort:"jan_feb_mar_apr_mai_j\u016bn_j\u016bl_aug_sep_okt_nov_dec".split("_"),weekdays:"sv\u0113tdiena_pirmdiena_otrdiena_tre\u0161diena_ceturtdiena_piektdiena_sestdiena".split("_"),weekdaysShort:"Sv_P_O_T_C_Pk_S".split("_"),weekdaysMin:"Sv_P_O_T_C_Pk_S".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY.",LL:"YYYY. [gada] D. MMMM",LLL:"YYYY. [gada] D. MMMM, HH:mm",LLLL:"YYYY. [gada] D. MMMM, dddd, HH:mm"},calendar:{sameDay:"[\u0160odien pulksten] LT",nextDay:"[R\u012bt pulksten] LT",nextWeek:"dddd [pulksten] LT",lastDay:"[Vakar pulksten] LT",lastWeek:"[Pag\u0101ju\u0161\u0101] dddd [pulksten] LT",sameElse:"L"},relativeTime:{future:"p\u0113c %s",past:"pirms %s",s:function(e,a){return a?"da\u017eas sekundes":"da\u017e\u0101m sekund\u0113m"},ss:Qs,m:Xs,mm:Qs,h:Xs,hh:Qs,d:Xs,dd:Qs,M:Xs,MM:Qs,y:Xs,yy:Qs},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}});var en={words:{ss:["sekund","sekunda","sekundi"],m:["jedan minut","jednog minuta"],mm:["minut","minuta","minuta"],h:["jedan sat","jednog sata"],hh:["sat","sata","sati"],dd:["dan","dana","dana"],MM:["mjesec","mjeseca","mjeseci"],yy:["godina","godine","godina"]},correctGrammaticalCase:function(e,a){return 1===e?a[0]:2<=e&&e<=4?a[1]:a[2]},translate:function(e,a,t){var s=en.words[t];return 1===t.length?a?s[0]:s[1]:e+" "+en.correctGrammaticalCase(e,s)}};function an(e,a,t,s){switch(t){case"s":return a?"\u0445\u044d\u0434\u0445\u044d\u043d \u0441\u0435\u043a\u0443\u043d\u0434":"\u0445\u044d\u0434\u0445\u044d\u043d \u0441\u0435\u043a\u0443\u043d\u0434\u044b\u043d";case"ss":return e+(a?" \u0441\u0435\u043a\u0443\u043d\u0434":" \u0441\u0435\u043a\u0443\u043d\u0434\u044b\u043d");case"m":case"mm":return e+(a?" \u043c\u0438\u043d\u0443\u0442":" \u043c\u0438\u043d\u0443\u0442\u044b\u043d");case"h":case"hh":return e+(a?" \u0446\u0430\u0433":" \u0446\u0430\u0433\u0438\u0439\u043d");case"d":case"dd":return e+(a?" \u04e9\u0434\u04e9\u0440":" \u04e9\u0434\u0440\u0438\u0439\u043d");case"M":case"MM":return e+(a?" \u0441\u0430\u0440":" \u0441\u0430\u0440\u044b\u043d");case"y":case"yy":return e+(a?" \u0436\u0438\u043b":" \u0436\u0438\u043b\u0438\u0439\u043d");default:return e}}M.defineLocale("me",{months:"januar_februar_mart_april_maj_jun_jul_avgust_septembar_oktobar_novembar_decembar".split("_"),monthsShort:"jan._feb._mar._apr._maj_jun_jul_avg._sep._okt._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"nedjelja_ponedjeljak_utorak_srijeda_\u010detvrtak_petak_subota".split("_"),weekdaysShort:"ned._pon._uto._sri._\u010det._pet._sub.".split("_"),weekdaysMin:"ne_po_ut_sr_\u010de_pe_su".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[danas u] LT",nextDay:"[sjutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedjelju] [u] LT";case 3:return"[u] [srijedu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[ju\u010de u] LT",lastWeek:function(){return["[pro\u0161le] [nedjelje] [u] LT","[pro\u0161log] [ponedjeljka] [u] LT","[pro\u0161log] [utorka] [u] LT","[pro\u0161le] [srijede] [u] LT","[pro\u0161log] [\u010detvrtka] [u] LT","[pro\u0161log] [petka] [u] LT","[pro\u0161le] [subote] [u] LT"][this.day()]},sameElse:"L"},relativeTime:{future:"za %s",past:"prije %s",s:"nekoliko sekundi",ss:en.translate,m:en.translate,mm:en.translate,h:en.translate,hh:en.translate,d:"dan",dd:en.translate,M:"mjesec",MM:en.translate,y:"godinu",yy:en.translate},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}}),M.defineLocale("mi",{months:"Kohi-t\u0101te_Hui-tanguru_Pout\u016b-te-rangi_Paenga-wh\u0101wh\u0101_Haratua_Pipiri_H\u014dngoingoi_Here-turi-k\u014dk\u0101_Mahuru_Whiringa-\u0101-nuku_Whiringa-\u0101-rangi_Hakihea".split("_"),monthsShort:"Kohi_Hui_Pou_Pae_Hara_Pipi_H\u014dngoi_Here_Mahu_Whi-nu_Whi-ra_Haki".split("_"),monthsRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i,monthsStrictRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i,monthsShortRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i,monthsShortStrictRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,2}/i,weekdays:"R\u0101tapu_Mane_T\u016brei_Wenerei_T\u0101ite_Paraire_H\u0101tarei".split("_"),weekdaysShort:"Ta_Ma_T\u016b_We_T\u0101i_Pa_H\u0101".split("_"),weekdaysMin:"Ta_Ma_T\u016b_We_T\u0101i_Pa_H\u0101".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [i] HH:mm",LLLL:"dddd, D MMMM YYYY [i] HH:mm"},calendar:{sameDay:"[i teie mahana, i] LT",nextDay:"[apopo i] LT",nextWeek:"dddd [i] LT",lastDay:"[inanahi i] LT",lastWeek:"dddd [whakamutunga i] LT",sameElse:"L"},relativeTime:{future:"i roto i %s",past:"%s i mua",s:"te h\u0113kona ruarua",ss:"%d h\u0113kona",m:"he meneti",mm:"%d meneti",h:"te haora",hh:"%d haora",d:"he ra",dd:"%d ra",M:"he marama",MM:"%d marama",y:"he tau",yy:"%d tau"},dayOfMonthOrdinalParse:/\d{1,2}\xba/,ordinal:"%d\xba",week:{dow:1,doy:4}}),M.defineLocale("mk",{months:"\u0458\u0430\u043d\u0443\u0430\u0440\u0438_\u0444\u0435\u0432\u0440\u0443\u0430\u0440\u0438_\u043c\u0430\u0440\u0442_\u0430\u043f\u0440\u0438\u043b_\u043c\u0430\u0458_\u0458\u0443\u043d\u0438_\u0458\u0443\u043b\u0438_\u0430\u0432\u0433\u0443\u0441\u0442_\u0441\u0435\u043f\u0442\u0435\u043c\u0432\u0440\u0438_\u043e\u043a\u0442\u043e\u043c\u0432\u0440\u0438_\u043d\u043e\u0435\u043c\u0432\u0440\u0438_\u0434\u0435\u043a\u0435\u043c\u0432\u0440\u0438".split("_"),monthsShort:"\u0458\u0430\u043d_\u0444\u0435\u0432_\u043c\u0430\u0440_\u0430\u043f\u0440_\u043c\u0430\u0458_\u0458\u0443\u043d_\u0458\u0443\u043b_\u0430\u0432\u0433_\u0441\u0435\u043f_\u043e\u043a\u0442_\u043d\u043e\u0435_\u0434\u0435\u043a".split("_"),weekdays:"\u043d\u0435\u0434\u0435\u043b\u0430_\u043f\u043e\u043d\u0435\u0434\u0435\u043b\u043d\u0438\u043a_\u0432\u0442\u043e\u0440\u043d\u0438\u043a_\u0441\u0440\u0435\u0434\u0430_\u0447\u0435\u0442\u0432\u0440\u0442\u043e\u043a_\u043f\u0435\u0442\u043e\u043a_\u0441\u0430\u0431\u043e\u0442\u0430".split("_"),weekdaysShort:"\u043d\u0435\u0434_\u043f\u043e\u043d_\u0432\u0442\u043e_\u0441\u0440\u0435_\u0447\u0435\u0442_\u043f\u0435\u0442_\u0441\u0430\u0431".split("_"),weekdaysMin:"\u043de_\u043fo_\u0432\u0442_\u0441\u0440_\u0447\u0435_\u043f\u0435_\u0441a".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"D.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY H:mm",LLLL:"dddd, D MMMM YYYY H:mm"},calendar:{sameDay:"[\u0414\u0435\u043d\u0435\u0441 \u0432\u043e] LT",nextDay:"[\u0423\u0442\u0440\u0435 \u0432\u043e] LT",nextWeek:"[\u0412\u043e] dddd [\u0432\u043e] LT",lastDay:"[\u0412\u0447\u0435\u0440\u0430 \u0432\u043e] LT",lastWeek:function(){switch(this.day()){case 0:case 3:case 6:return"[\u0418\u0437\u043c\u0438\u043d\u0430\u0442\u0430\u0442\u0430] dddd [\u0432\u043e] LT";case 1:case 2:case 4:case 5:return"[\u0418\u0437\u043c\u0438\u043d\u0430\u0442\u0438\u043e\u0442] dddd [\u0432\u043e] LT"}},sameElse:"L"},relativeTime:{future:"\u0437\u0430 %s",past:"\u043f\u0440\u0435\u0434 %s",s:"\u043d\u0435\u043a\u043e\u043b\u043a\u0443 \u0441\u0435\u043a\u0443\u043d\u0434\u0438",ss:"%d \u0441\u0435\u043a\u0443\u043d\u0434\u0438",m:"\u0435\u0434\u043d\u0430 \u043c\u0438\u043d\u0443\u0442\u0430",mm:"%d \u043c\u0438\u043d\u0443\u0442\u0438",h:"\u0435\u0434\u0435\u043d \u0447\u0430\u0441",hh:"%d \u0447\u0430\u0441\u0430",d:"\u0435\u0434\u0435\u043d \u0434\u0435\u043d",dd:"%d \u0434\u0435\u043d\u0430",M:"\u0435\u0434\u0435\u043d \u043c\u0435\u0441\u0435\u0446",MM:"%d \u043c\u0435\u0441\u0435\u0446\u0438",y:"\u0435\u0434\u043d\u0430 \u0433\u043e\u0434\u0438\u043d\u0430",yy:"%d \u0433\u043e\u0434\u0438\u043d\u0438"},dayOfMonthOrdinalParse:/\d{1,2}-(\u0435\u0432|\u0435\u043d|\u0442\u0438|\u0432\u0438|\u0440\u0438|\u043c\u0438)/,ordinal:function(e){var a=e%10,t=e%100;return 0===e?e+"-\u0435\u0432":0==t?e+"-\u0435\u043d":10<t&&t<20?e+"-\u0442\u0438":1==a?e+"-\u0432\u0438":2==a?e+"-\u0440\u0438":7==a||8==a?e+"-\u043c\u0438":e+"-\u0442\u0438"},week:{dow:1,doy:7}}),M.defineLocale("ml",{months:"\u0d1c\u0d28\u0d41\u0d35\u0d30\u0d3f_\u0d2b\u0d46\u0d2c\u0d4d\u0d30\u0d41\u0d35\u0d30\u0d3f_\u0d2e\u0d3e\u0d7c\u0d1a\u0d4d\u0d1a\u0d4d_\u0d0f\u0d2a\u0d4d\u0d30\u0d3f\u0d7d_\u0d2e\u0d47\u0d2f\u0d4d_\u0d1c\u0d42\u0d7a_\u0d1c\u0d42\u0d32\u0d48_\u0d13\u0d17\u0d38\u0d4d\u0d31\u0d4d\u0d31\u0d4d_\u0d38\u0d46\u0d2a\u0d4d\u0d31\u0d4d\u0d31\u0d02\u0d2c\u0d7c_\u0d12\u0d15\u0d4d\u0d1f\u0d4b\u0d2c\u0d7c_\u0d28\u0d35\u0d02\u0d2c\u0d7c_\u0d21\u0d3f\u0d38\u0d02\u0d2c\u0d7c".split("_"),monthsShort:"\u0d1c\u0d28\u0d41._\u0d2b\u0d46\u0d2c\u0d4d\u0d30\u0d41._\u0d2e\u0d3e\u0d7c._\u0d0f\u0d2a\u0d4d\u0d30\u0d3f._\u0d2e\u0d47\u0d2f\u0d4d_\u0d1c\u0d42\u0d7a_\u0d1c\u0d42\u0d32\u0d48._\u0d13\u0d17._\u0d38\u0d46\u0d2a\u0d4d\u0d31\u0d4d\u0d31._\u0d12\u0d15\u0d4d\u0d1f\u0d4b._\u0d28\u0d35\u0d02._\u0d21\u0d3f\u0d38\u0d02.".split("_"),monthsParseExact:!0,weekdays:"\u0d1e\u0d3e\u0d2f\u0d31\u0d3e\u0d34\u0d4d\u0d1a_\u0d24\u0d3f\u0d19\u0d4d\u0d15\u0d33\u0d3e\u0d34\u0d4d\u0d1a_\u0d1a\u0d4a\u0d35\u0d4d\u0d35\u0d3e\u0d34\u0d4d\u0d1a_\u0d2c\u0d41\u0d27\u0d28\u0d3e\u0d34\u0d4d\u0d1a_\u0d35\u0d4d\u0d2f\u0d3e\u0d34\u0d3e\u0d34\u0d4d\u0d1a_\u0d35\u0d46\u0d33\u0d4d\u0d33\u0d3f\u0d2f\u0d3e\u0d34\u0d4d\u0d1a_\u0d36\u0d28\u0d3f\u0d2f\u0d3e\u0d34\u0d4d\u0d1a".split("_"),weekdaysShort:"\u0d1e\u0d3e\u0d2f\u0d7c_\u0d24\u0d3f\u0d19\u0d4d\u0d15\u0d7e_\u0d1a\u0d4a\u0d35\u0d4d\u0d35_\u0d2c\u0d41\u0d27\u0d7b_\u0d35\u0d4d\u0d2f\u0d3e\u0d34\u0d02_\u0d35\u0d46\u0d33\u0d4d\u0d33\u0d3f_\u0d36\u0d28\u0d3f".split("_"),weekdaysMin:"\u0d1e\u0d3e_\u0d24\u0d3f_\u0d1a\u0d4a_\u0d2c\u0d41_\u0d35\u0d4d\u0d2f\u0d3e_\u0d35\u0d46_\u0d36".split("_"),longDateFormat:{LT:"A h:mm -\u0d28\u0d41",LTS:"A h:mm:ss -\u0d28\u0d41",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm -\u0d28\u0d41",LLLL:"dddd, D MMMM YYYY, A h:mm -\u0d28\u0d41"},calendar:{sameDay:"[\u0d07\u0d28\u0d4d\u0d28\u0d4d] LT",nextDay:"[\u0d28\u0d3e\u0d33\u0d46] LT",nextWeek:"dddd, LT",lastDay:"[\u0d07\u0d28\u0d4d\u0d28\u0d32\u0d46] LT",lastWeek:"[\u0d15\u0d34\u0d3f\u0d1e\u0d4d\u0d1e] dddd, LT",sameElse:"L"},relativeTime:{future:"%s \u0d15\u0d34\u0d3f\u0d1e\u0d4d\u0d1e\u0d4d",past:"%s \u0d2e\u0d41\u0d7b\u0d2a\u0d4d",s:"\u0d05\u0d7d\u0d2a \u0d28\u0d3f\u0d2e\u0d3f\u0d37\u0d19\u0d4d\u0d19\u0d7e",ss:"%d \u0d38\u0d46\u0d15\u0d4d\u0d15\u0d7b\u0d21\u0d4d",m:"\u0d12\u0d30\u0d41 \u0d2e\u0d3f\u0d28\u0d3f\u0d31\u0d4d\u0d31\u0d4d",mm:"%d \u0d2e\u0d3f\u0d28\u0d3f\u0d31\u0d4d\u0d31\u0d4d",h:"\u0d12\u0d30\u0d41 \u0d2e\u0d23\u0d3f\u0d15\u0d4d\u0d15\u0d42\u0d7c",hh:"%d \u0d2e\u0d23\u0d3f\u0d15\u0d4d\u0d15\u0d42\u0d7c",d:"\u0d12\u0d30\u0d41 \u0d26\u0d3f\u0d35\u0d38\u0d02",dd:"%d \u0d26\u0d3f\u0d35\u0d38\u0d02",M:"\u0d12\u0d30\u0d41 \u0d2e\u0d3e\u0d38\u0d02",MM:"%d \u0d2e\u0d3e\u0d38\u0d02",y:"\u0d12\u0d30\u0d41 \u0d35\u0d7c\u0d37\u0d02",yy:"%d \u0d35\u0d7c\u0d37\u0d02"},meridiemParse:/\u0d30\u0d3e\u0d24\u0d4d\u0d30\u0d3f|\u0d30\u0d3e\u0d35\u0d3f\u0d32\u0d46|\u0d09\u0d1a\u0d4d\u0d1a \u0d15\u0d34\u0d3f\u0d1e\u0d4d\u0d1e\u0d4d|\u0d35\u0d48\u0d15\u0d41\u0d28\u0d4d\u0d28\u0d47\u0d30\u0d02|\u0d30\u0d3e\u0d24\u0d4d\u0d30\u0d3f/i,meridiemHour:function(e,a){return 12===e&&(e=0),"\u0d30\u0d3e\u0d24\u0d4d\u0d30\u0d3f"===a&&4<=e||"\u0d09\u0d1a\u0d4d\u0d1a \u0d15\u0d34\u0d3f\u0d1e\u0d4d\u0d1e\u0d4d"===a||"\u0d35\u0d48\u0d15\u0d41\u0d28\u0d4d\u0d28\u0d47\u0d30\u0d02"===a?e+12:e},meridiem:function(e,a,t){return e<4?"\u0d30\u0d3e\u0d24\u0d4d\u0d30\u0d3f":e<12?"\u0d30\u0d3e\u0d35\u0d3f\u0d32\u0d46":e<17?"\u0d09\u0d1a\u0d4d\u0d1a \u0d15\u0d34\u0d3f\u0d1e\u0d4d\u0d1e\u0d4d":e<20?"\u0d35\u0d48\u0d15\u0d41\u0d28\u0d4d\u0d28\u0d47\u0d30\u0d02":"\u0d30\u0d3e\u0d24\u0d4d\u0d30\u0d3f"}}),M.defineLocale("mn",{months:"\u041d\u044d\u0433\u0434\u04af\u0433\u044d\u044d\u0440 \u0441\u0430\u0440_\u0425\u043e\u0451\u0440\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440_\u0413\u0443\u0440\u0430\u0432\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440_\u0414\u04e9\u0440\u04e9\u0432\u0434\u04af\u0433\u044d\u044d\u0440 \u0441\u0430\u0440_\u0422\u0430\u0432\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440_\u0417\u0443\u0440\u0433\u0430\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440_\u0414\u043e\u043b\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440_\u041d\u0430\u0439\u043c\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440_\u0415\u0441\u0434\u04af\u0433\u044d\u044d\u0440 \u0441\u0430\u0440_\u0410\u0440\u0430\u0432\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440_\u0410\u0440\u0432\u0430\u043d \u043d\u044d\u0433\u0434\u04af\u0433\u044d\u044d\u0440 \u0441\u0430\u0440_\u0410\u0440\u0432\u0430\u043d \u0445\u043e\u0451\u0440\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440".split("_"),monthsShort:"1 \u0441\u0430\u0440_2 \u0441\u0430\u0440_3 \u0441\u0430\u0440_4 \u0441\u0430\u0440_5 \u0441\u0430\u0440_6 \u0441\u0430\u0440_7 \u0441\u0430\u0440_8 \u0441\u0430\u0440_9 \u0441\u0430\u0440_10 \u0441\u0430\u0440_11 \u0441\u0430\u0440_12 \u0441\u0430\u0440".split("_"),monthsParseExact:!0,weekdays:"\u041d\u044f\u043c_\u0414\u0430\u0432\u0430\u0430_\u041c\u044f\u0433\u043c\u0430\u0440_\u041b\u0445\u0430\u0433\u0432\u0430_\u041f\u04af\u0440\u044d\u0432_\u0411\u0430\u0430\u0441\u0430\u043d_\u0411\u044f\u043c\u0431\u0430".split("_"),weekdaysShort:"\u041d\u044f\u043c_\u0414\u0430\u0432_\u041c\u044f\u0433_\u041b\u0445\u0430_\u041f\u04af\u0440_\u0411\u0430\u0430_\u0411\u044f\u043c".split("_"),weekdaysMin:"\u041d\u044f_\u0414\u0430_\u041c\u044f_\u041b\u0445_\u041f\u04af_\u0411\u0430_\u0411\u044f".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"YYYY \u043e\u043d\u044b MMMM\u044b\u043d D",LLL:"YYYY \u043e\u043d\u044b MMMM\u044b\u043d D HH:mm",LLLL:"dddd, YYYY \u043e\u043d\u044b MMMM\u044b\u043d D HH:mm"},meridiemParse:/\u04ae\u04e8|\u04ae\u0425/i,isPM:function(e){return"\u04ae\u0425"===e},meridiem:function(e,a,t){return e<12?"\u04ae\u04e8":"\u04ae\u0425"},calendar:{sameDay:"[\u04e8\u043d\u04e9\u04e9\u0434\u04e9\u0440] LT",nextDay:"[\u041c\u0430\u0440\u0433\u0430\u0430\u0448] LT",nextWeek:"[\u0418\u0440\u044d\u0445] dddd LT",lastDay:"[\u04e8\u0447\u0438\u0433\u0434\u04e9\u0440] LT",lastWeek:"[\u04e8\u043d\u0433\u04e9\u0440\u0441\u04e9\u043d] dddd LT",sameElse:"L"},relativeTime:{future:"%s \u0434\u0430\u0440\u0430\u0430",past:"%s \u04e9\u043c\u043d\u04e9",s:an,ss:an,m:an,mm:an,h:an,hh:an,d:an,dd:an,M:an,MM:an,y:an,yy:an},dayOfMonthOrdinalParse:/\d{1,2} \u04e9\u0434\u04e9\u0440/,ordinal:function(e,a){switch(a){case"d":case"D":case"DDD":return e+" \u04e9\u0434\u04e9\u0440";default:return e}}});var tn={1:"\u0967",2:"\u0968",3:"\u0969",4:"\u096a",5:"\u096b",6:"\u096c",7:"\u096d",8:"\u096e",9:"\u096f",0:"\u0966"},sn={"\u0967":"1","\u0968":"2","\u0969":"3","\u096a":"4","\u096b":"5","\u096c":"6","\u096d":"7","\u096e":"8","\u096f":"9","\u0966":"0"};function nn(e,a,t,s){var n="";if(a)switch(t){case"s":n="\u0915\u093e\u0939\u0940 \u0938\u0947\u0915\u0902\u0926";break;case"ss":n="%d \u0938\u0947\u0915\u0902\u0926";break;case"m":n="\u090f\u0915 \u092e\u093f\u0928\u093f\u091f";break;case"mm":n="%d \u092e\u093f\u0928\u093f\u091f\u0947";break;case"h":n="\u090f\u0915 \u0924\u093e\u0938";break;case"hh":n="%d \u0924\u093e\u0938";break;case"d":n="\u090f\u0915 \u0926\u093f\u0935\u0938";break;case"dd":n="%d \u0926\u093f\u0935\u0938";break;case"M":n="\u090f\u0915 \u092e\u0939\u093f\u0928\u093e";break;case"MM":n="%d \u092e\u0939\u093f\u0928\u0947";break;case"y":n="\u090f\u0915 \u0935\u0930\u094d\u0937";break;case"yy":n="%d \u0935\u0930\u094d\u0937\u0947";break}else switch(t){case"s":n="\u0915\u093e\u0939\u0940 \u0938\u0947\u0915\u0902\u0926\u093e\u0902";break;case"ss":n="%d \u0938\u0947\u0915\u0902\u0926\u093e\u0902";break;case"m":n="\u090f\u0915\u093e \u092e\u093f\u0928\u093f\u091f\u093e";break;case"mm":n="%d \u092e\u093f\u0928\u093f\u091f\u093e\u0902";break;case"h":n="\u090f\u0915\u093e \u0924\u093e\u0938\u093e";break;case"hh":n="%d \u0924\u093e\u0938\u093e\u0902";break;case"d":n="\u090f\u0915\u093e \u0926\u093f\u0935\u0938\u093e";break;case"dd":n="%d \u0926\u093f\u0935\u0938\u093e\u0902";break;case"M":n="\u090f\u0915\u093e \u092e\u0939\u093f\u0928\u094d\u092f\u093e";break;case"MM":n="%d \u092e\u0939\u093f\u0928\u094d\u092f\u093e\u0902";break;case"y":n="\u090f\u0915\u093e \u0935\u0930\u094d\u0937\u093e";break;case"yy":n="%d \u0935\u0930\u094d\u0937\u093e\u0902";break}return n.replace(/%d/i,e)}M.defineLocale("mr",{months:"\u091c\u093e\u0928\u0947\u0935\u093e\u0930\u0940_\u092b\u0947\u092c\u094d\u0930\u0941\u0935\u093e\u0930\u0940_\u092e\u093e\u0930\u094d\u091a_\u090f\u092a\u094d\u0930\u093f\u0932_\u092e\u0947_\u091c\u0942\u0928_\u091c\u0941\u0932\u0948_\u0911\u0917\u0938\u094d\u091f_\u0938\u092a\u094d\u091f\u0947\u0902\u092c\u0930_\u0911\u0915\u094d\u091f\u094b\u092c\u0930_\u0928\u094b\u0935\u094d\u0939\u0947\u0902\u092c\u0930_\u0921\u093f\u0938\u0947\u0902\u092c\u0930".split("_"),monthsShort:"\u091c\u093e\u0928\u0947._\u092b\u0947\u092c\u094d\u0930\u0941._\u092e\u093e\u0930\u094d\u091a._\u090f\u092a\u094d\u0930\u093f._\u092e\u0947._\u091c\u0942\u0928._\u091c\u0941\u0932\u0948._\u0911\u0917._\u0938\u092a\u094d\u091f\u0947\u0902._\u0911\u0915\u094d\u091f\u094b._\u0928\u094b\u0935\u094d\u0939\u0947\u0902._\u0921\u093f\u0938\u0947\u0902.".split("_"),monthsParseExact:!0,weekdays:"\u0930\u0935\u093f\u0935\u093e\u0930_\u0938\u094b\u092e\u0935\u093e\u0930_\u092e\u0902\u0917\u0933\u0935\u093e\u0930_\u092c\u0941\u0927\u0935\u093e\u0930_\u0917\u0941\u0930\u0942\u0935\u093e\u0930_\u0936\u0941\u0915\u094d\u0930\u0935\u093e\u0930_\u0936\u0928\u093f\u0935\u093e\u0930".split("_"),weekdaysShort:"\u0930\u0935\u093f_\u0938\u094b\u092e_\u092e\u0902\u0917\u0933_\u092c\u0941\u0927_\u0917\u0941\u0930\u0942_\u0936\u0941\u0915\u094d\u0930_\u0936\u0928\u093f".split("_"),weekdaysMin:"\u0930_\u0938\u094b_\u092e\u0902_\u092c\u0941_\u0917\u0941_\u0936\u0941_\u0936".split("_"),longDateFormat:{LT:"A h:mm \u0935\u093e\u091c\u0924\u093e",LTS:"A h:mm:ss \u0935\u093e\u091c\u0924\u093e",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm \u0935\u093e\u091c\u0924\u093e",LLLL:"dddd, D MMMM YYYY, A h:mm \u0935\u093e\u091c\u0924\u093e"},calendar:{sameDay:"[\u0906\u091c] LT",nextDay:"[\u0909\u0926\u094d\u092f\u093e] LT",nextWeek:"dddd, LT",lastDay:"[\u0915\u093e\u0932] LT",lastWeek:"[\u092e\u093e\u0917\u0940\u0932] dddd, LT",sameElse:"L"},relativeTime:{future:"%s\u092e\u0927\u094d\u092f\u0947",past:"%s\u092a\u0942\u0930\u094d\u0935\u0940",s:nn,ss:nn,m:nn,mm:nn,h:nn,hh:nn,d:nn,dd:nn,M:nn,MM:nn,y:nn,yy:nn},preparse:function(e){return e.replace(/[\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f\u0966]/g,function(e){return sn[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return tn[e]})},meridiemParse:/\u092a\u0939\u093e\u091f\u0947|\u0938\u0915\u093e\u0933\u0940|\u0926\u0941\u092a\u093e\u0930\u0940|\u0938\u093e\u092f\u0902\u0915\u093e\u0933\u0940|\u0930\u093e\u0924\u094d\u0930\u0940/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u092a\u0939\u093e\u091f\u0947"===a||"\u0938\u0915\u093e\u0933\u0940"===a?e:"\u0926\u0941\u092a\u093e\u0930\u0940"===a||"\u0938\u093e\u092f\u0902\u0915\u093e\u0933\u0940"===a||"\u0930\u093e\u0924\u094d\u0930\u0940"===a?12<=e?e:e+12:void 0},meridiem:function(e,a,t){return 0<=e&&e<6?"\u092a\u0939\u093e\u091f\u0947":e<12?"\u0938\u0915\u093e\u0933\u0940":e<17?"\u0926\u0941\u092a\u093e\u0930\u0940":e<20?"\u0938\u093e\u092f\u0902\u0915\u093e\u0933\u0940":"\u0930\u093e\u0924\u094d\u0930\u0940"},week:{dow:0,doy:6}}),M.defineLocale("ms-my",{months:"Januari_Februari_Mac_April_Mei_Jun_Julai_Ogos_September_Oktober_November_Disember".split("_"),monthsShort:"Jan_Feb_Mac_Apr_Mei_Jun_Jul_Ogs_Sep_Okt_Nov_Dis".split("_"),weekdays:"Ahad_Isnin_Selasa_Rabu_Khamis_Jumaat_Sabtu".split("_"),weekdaysShort:"Ahd_Isn_Sel_Rab_Kha_Jum_Sab".split("_"),weekdaysMin:"Ah_Is_Sl_Rb_Km_Jm_Sb".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [pukul] HH.mm",LLLL:"dddd, D MMMM YYYY [pukul] HH.mm"},meridiemParse:/pagi|tengahari|petang|malam/,meridiemHour:function(e,a){return 12===e&&(e=0),"pagi"===a?e:"tengahari"===a?11<=e?e:e+12:"petang"===a||"malam"===a?e+12:void 0},meridiem:function(e,a,t){return e<11?"pagi":e<15?"tengahari":e<19?"petang":"malam"},calendar:{sameDay:"[Hari ini pukul] LT",nextDay:"[Esok pukul] LT",nextWeek:"dddd [pukul] LT",lastDay:"[Kelmarin pukul] LT",lastWeek:"dddd [lepas pukul] LT",sameElse:"L"},relativeTime:{future:"dalam %s",past:"%s yang lepas",s:"beberapa saat",ss:"%d saat",m:"seminit",mm:"%d minit",h:"sejam",hh:"%d jam",d:"sehari",dd:"%d hari",M:"sebulan",MM:"%d bulan",y:"setahun",yy:"%d tahun"},week:{dow:1,doy:7}}),M.defineLocale("ms",{months:"Januari_Februari_Mac_April_Mei_Jun_Julai_Ogos_September_Oktober_November_Disember".split("_"),monthsShort:"Jan_Feb_Mac_Apr_Mei_Jun_Jul_Ogs_Sep_Okt_Nov_Dis".split("_"),weekdays:"Ahad_Isnin_Selasa_Rabu_Khamis_Jumaat_Sabtu".split("_"),weekdaysShort:"Ahd_Isn_Sel_Rab_Kha_Jum_Sab".split("_"),weekdaysMin:"Ah_Is_Sl_Rb_Km_Jm_Sb".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [pukul] HH.mm",LLLL:"dddd, D MMMM YYYY [pukul] HH.mm"},meridiemParse:/pagi|tengahari|petang|malam/,meridiemHour:function(e,a){return 12===e&&(e=0),"pagi"===a?e:"tengahari"===a?11<=e?e:e+12:"petang"===a||"malam"===a?e+12:void 0},meridiem:function(e,a,t){return e<11?"pagi":e<15?"tengahari":e<19?"petang":"malam"},calendar:{sameDay:"[Hari ini pukul] LT",nextDay:"[Esok pukul] LT",nextWeek:"dddd [pukul] LT",lastDay:"[Kelmarin pukul] LT",lastWeek:"dddd [lepas pukul] LT",sameElse:"L"},relativeTime:{future:"dalam %s",past:"%s yang lepas",s:"beberapa saat",ss:"%d saat",m:"seminit",mm:"%d minit",h:"sejam",hh:"%d jam",d:"sehari",dd:"%d hari",M:"sebulan",MM:"%d bulan",y:"setahun",yy:"%d tahun"},week:{dow:1,doy:7}}),M.defineLocale("mt",{months:"Jannar_Frar_Marzu_April_Mejju_\u0120unju_Lulju_Awwissu_Settembru_Ottubru_Novembru_Di\u010bembru".split("_"),monthsShort:"Jan_Fra_Mar_Apr_Mej_\u0120un_Lul_Aww_Set_Ott_Nov_Di\u010b".split("_"),weekdays:"Il-\u0126add_It-Tnejn_It-Tlieta_L-Erbg\u0127a_Il-\u0126amis_Il-\u0120img\u0127a_Is-Sibt".split("_"),weekdaysShort:"\u0126ad_Tne_Tli_Erb_\u0126am_\u0120im_Sib".split("_"),weekdaysMin:"\u0126a_Tn_Tl_Er_\u0126a_\u0120i_Si".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Illum fil-]LT",nextDay:"[G\u0127ada fil-]LT",nextWeek:"dddd [fil-]LT",lastDay:"[Il-biera\u0127 fil-]LT",lastWeek:"dddd [li g\u0127adda] [fil-]LT",sameElse:"L"},relativeTime:{future:"f\u2019 %s",past:"%s ilu",s:"ftit sekondi",ss:"%d sekondi",m:"minuta",mm:"%d minuti",h:"sieg\u0127a",hh:"%d sieg\u0127at",d:"\u0121urnata",dd:"%d \u0121ranet",M:"xahar",MM:"%d xhur",y:"sena",yy:"%d sni"},dayOfMonthOrdinalParse:/\d{1,2}\xba/,ordinal:"%d\xba",week:{dow:1,doy:4}});var rn={1:"\u1041",2:"\u1042",3:"\u1043",4:"\u1044",5:"\u1045",6:"\u1046",7:"\u1047",8:"\u1048",9:"\u1049",0:"\u1040"},dn={"\u1041":"1","\u1042":"2","\u1043":"3","\u1044":"4","\u1045":"5","\u1046":"6","\u1047":"7","\u1048":"8","\u1049":"9","\u1040":"0"},_n=(M.defineLocale("my",{months:"\u1007\u1014\u103a\u1014\u101d\u102b\u101b\u102e_\u1016\u1031\u1016\u1031\u102c\u103a\u101d\u102b\u101b\u102e_\u1019\u1010\u103a_\u1027\u1015\u103c\u102e_\u1019\u1031_\u1007\u103d\u1014\u103a_\u1007\u1030\u101c\u102d\u102f\u1004\u103a_\u101e\u103c\u1002\u102f\u1010\u103a_\u1005\u1000\u103a\u1010\u1004\u103a\u1018\u102c_\u1021\u1031\u102c\u1000\u103a\u1010\u102d\u102f\u1018\u102c_\u1014\u102d\u102f\u101d\u1004\u103a\u1018\u102c_\u1012\u102e\u1007\u1004\u103a\u1018\u102c".split("_"),monthsShort:"\u1007\u1014\u103a_\u1016\u1031_\u1019\u1010\u103a_\u1015\u103c\u102e_\u1019\u1031_\u1007\u103d\u1014\u103a_\u101c\u102d\u102f\u1004\u103a_\u101e\u103c_\u1005\u1000\u103a_\u1021\u1031\u102c\u1000\u103a_\u1014\u102d\u102f_\u1012\u102e".split("_"),weekdays:"\u1010\u1014\u1004\u103a\u1039\u1002\u1014\u103d\u1031_\u1010\u1014\u1004\u103a\u1039\u101c\u102c_\u1021\u1004\u103a\u1039\u1002\u102b_\u1017\u102f\u1012\u1039\u1013\u101f\u1030\u1038_\u1000\u103c\u102c\u101e\u1015\u1010\u1031\u1038_\u101e\u1031\u102c\u1000\u103c\u102c_\u1005\u1014\u1031".split("_"),weekdaysShort:"\u1014\u103d\u1031_\u101c\u102c_\u1002\u102b_\u101f\u1030\u1038_\u1000\u103c\u102c_\u101e\u1031\u102c_\u1014\u1031".split("_"),weekdaysMin:"\u1014\u103d\u1031_\u101c\u102c_\u1002\u102b_\u101f\u1030\u1038_\u1000\u103c\u102c_\u101e\u1031\u102c_\u1014\u1031".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[\u101a\u1014\u1031.] LT [\u1019\u103e\u102c]",nextDay:"[\u1019\u1014\u1000\u103a\u1016\u103c\u1014\u103a] LT [\u1019\u103e\u102c]",nextWeek:"dddd LT [\u1019\u103e\u102c]",lastDay:"[\u1019\u1014\u1031.\u1000] LT [\u1019\u103e\u102c]",lastWeek:"[\u1015\u103c\u102e\u1038\u1001\u1032\u1037\u101e\u1031\u102c] dddd LT [\u1019\u103e\u102c]",sameElse:"L"},relativeTime:{future:"\u101c\u102c\u1019\u100a\u103a\u1037 %s \u1019\u103e\u102c",past:"\u101c\u103d\u1014\u103a\u1001\u1032\u1037\u101e\u1031\u102c %s \u1000",s:"\u1005\u1000\u1039\u1000\u1014\u103a.\u1021\u1014\u100a\u103a\u1038\u1004\u101a\u103a",ss:"%d \u1005\u1000\u1039\u1000\u1014\u1037\u103a",m:"\u1010\u1005\u103a\u1019\u102d\u1014\u1005\u103a",mm:"%d \u1019\u102d\u1014\u1005\u103a",h:"\u1010\u1005\u103a\u1014\u102c\u101b\u102e",hh:"%d \u1014\u102c\u101b\u102e",d:"\u1010\u1005\u103a\u101b\u1000\u103a",dd:"%d \u101b\u1000\u103a",M:"\u1010\u1005\u103a\u101c",MM:"%d \u101c",y:"\u1010\u1005\u103a\u1014\u103e\u1005\u103a",yy:"%d \u1014\u103e\u1005\u103a"},preparse:function(e){return e.replace(/[\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049\u1040]/g,function(e){return dn[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return rn[e]})},week:{dow:1,doy:4}}),M.defineLocale("nb",{months:"januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember".split("_"),monthsShort:"jan._feb._mars_apr._mai_juni_juli_aug._sep._okt._nov._des.".split("_"),monthsParseExact:!0,weekdays:"s\xf8ndag_mandag_tirsdag_onsdag_torsdag_fredag_l\xf8rdag".split("_"),weekdaysShort:"s\xf8._ma._ti._on._to._fr._l\xf8.".split("_"),weekdaysMin:"s\xf8_ma_ti_on_to_fr_l\xf8".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY [kl.] HH:mm",LLLL:"dddd D. MMMM YYYY [kl.] HH:mm"},calendar:{sameDay:"[i dag kl.] LT",nextDay:"[i morgen kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[i g\xe5r kl.] LT",lastWeek:"[forrige] dddd [kl.] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"%s siden",s:"noen sekunder",ss:"%d sekunder",m:"ett minutt",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dager",w:"en uke",ww:"%d uker",M:"en m\xe5ned",MM:"%d m\xe5neder",y:"ett \xe5r",yy:"%d \xe5r"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),{1:"\u0967",2:"\u0968",3:"\u0969",4:"\u096a",5:"\u096b",6:"\u096c",7:"\u096d",8:"\u096e",9:"\u096f",0:"\u0966"}),on={"\u0967":"1","\u0968":"2","\u0969":"3","\u096a":"4","\u096b":"5","\u096c":"6","\u096d":"7","\u096e":"8","\u096f":"9","\u0966":"0"},mn=(M.defineLocale("ne",{months:"\u091c\u0928\u0935\u0930\u0940_\u092b\u0947\u092c\u094d\u0930\u0941\u0935\u0930\u0940_\u092e\u093e\u0930\u094d\u091a_\u0905\u092a\u094d\u0930\u093f\u0932_\u092e\u0908_\u091c\u0941\u0928_\u091c\u0941\u0932\u093e\u0908_\u0905\u0917\u0937\u094d\u091f_\u0938\u0947\u092a\u094d\u091f\u0947\u092e\u094d\u092c\u0930_\u0905\u0915\u094d\u091f\u094b\u092c\u0930_\u0928\u094b\u092d\u0947\u092e\u094d\u092c\u0930_\u0921\u093f\u0938\u0947\u092e\u094d\u092c\u0930".split("_"),monthsShort:"\u091c\u0928._\u092b\u0947\u092c\u094d\u0930\u0941._\u092e\u093e\u0930\u094d\u091a_\u0905\u092a\u094d\u0930\u093f._\u092e\u0908_\u091c\u0941\u0928_\u091c\u0941\u0932\u093e\u0908._\u0905\u0917._\u0938\u0947\u092a\u094d\u091f._\u0905\u0915\u094d\u091f\u094b._\u0928\u094b\u092d\u0947._\u0921\u093f\u0938\u0947.".split("_"),monthsParseExact:!0,weekdays:"\u0906\u0907\u0924\u092c\u093e\u0930_\u0938\u094b\u092e\u092c\u093e\u0930_\u092e\u0919\u094d\u0917\u0932\u092c\u093e\u0930_\u092c\u0941\u0927\u092c\u093e\u0930_\u092c\u093f\u0939\u093f\u092c\u093e\u0930_\u0936\u0941\u0915\u094d\u0930\u092c\u093e\u0930_\u0936\u0928\u093f\u092c\u093e\u0930".split("_"),weekdaysShort:"\u0906\u0907\u0924._\u0938\u094b\u092e._\u092e\u0919\u094d\u0917\u0932._\u092c\u0941\u0927._\u092c\u093f\u0939\u093f._\u0936\u0941\u0915\u094d\u0930._\u0936\u0928\u093f.".split("_"),weekdaysMin:"\u0906._\u0938\u094b._\u092e\u0902._\u092c\u0941._\u092c\u093f._\u0936\u0941._\u0936.".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"A\u0915\u094b h:mm \u092c\u091c\u0947",LTS:"A\u0915\u094b h:mm:ss \u092c\u091c\u0947",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A\u0915\u094b h:mm \u092c\u091c\u0947",LLLL:"dddd, D MMMM YYYY, A\u0915\u094b h:mm \u092c\u091c\u0947"},preparse:function(e){return e.replace(/[\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f\u0966]/g,function(e){return on[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return _n[e]})},meridiemParse:/\u0930\u093e\u0924\u093f|\u092c\u093f\u0939\u093e\u0928|\u0926\u093f\u0909\u0901\u0938\u094b|\u0938\u093e\u0901\u091d/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u0930\u093e\u0924\u093f"===a?e<4?e:e+12:"\u092c\u093f\u0939\u093e\u0928"===a?e:"\u0926\u093f\u0909\u0901\u0938\u094b"===a?10<=e?e:e+12:"\u0938\u093e\u0901\u091d"===a?e+12:void 0},meridiem:function(e,a,t){return e<3?"\u0930\u093e\u0924\u093f":e<12?"\u092c\u093f\u0939\u093e\u0928":e<16?"\u0926\u093f\u0909\u0901\u0938\u094b":e<20?"\u0938\u093e\u0901\u091d":"\u0930\u093e\u0924\u093f"},calendar:{sameDay:"[\u0906\u091c] LT",nextDay:"[\u092d\u094b\u0932\u093f] LT",nextWeek:"[\u0906\u0909\u0901\u0926\u094b] dddd[,] LT",lastDay:"[\u0939\u093f\u091c\u094b] LT",lastWeek:"[\u0917\u090f\u0915\u094b] dddd[,] LT",sameElse:"L"},relativeTime:{future:"%s\u092e\u093e",past:"%s \u0905\u0917\u093e\u0921\u093f",s:"\u0915\u0947\u0939\u0940 \u0915\u094d\u0937\u0923",ss:"%d \u0938\u0947\u0915\u0947\u0923\u094d\u0921",m:"\u090f\u0915 \u092e\u093f\u0928\u0947\u091f",mm:"%d \u092e\u093f\u0928\u0947\u091f",h:"\u090f\u0915 \u0918\u0923\u094d\u091f\u093e",hh:"%d \u0918\u0923\u094d\u091f\u093e",d:"\u090f\u0915 \u0926\u093f\u0928",dd:"%d \u0926\u093f\u0928",M:"\u090f\u0915 \u092e\u0939\u093f\u0928\u093e",MM:"%d \u092e\u0939\u093f\u0928\u093e",y:"\u090f\u0915 \u092c\u0930\u094d\u0937",yy:"%d \u092c\u0930\u094d\u0937"},week:{dow:0,doy:6}}),"jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.".split("_")),un="jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec".split("_"),c=[/^jan/i,/^feb/i,/^maart|mrt.?$/i,/^apr/i,/^mei$/i,/^jun[i.]?$/i,/^jul[i.]?$/i,/^aug/i,/^sep/i,/^okt/i,/^nov/i,/^dec/i],L=/^(januari|februari|maart|april|mei|ju[nl]i|augustus|september|oktober|november|december|jan\.?|feb\.?|mrt\.?|apr\.?|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i,ln=(M.defineLocale("nl-be",{months:"januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december".split("_"),monthsShort:function(e,a){return e?(/-MMM-/.test(a)?un:mn)[e.month()]:mn},monthsRegex:L,monthsShortRegex:L,monthsStrictRegex:/^(januari|februari|maart|april|mei|ju[nl]i|augustus|september|oktober|november|december)/i,monthsShortStrictRegex:/^(jan\.?|feb\.?|mrt\.?|apr\.?|mei|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i,monthsParse:c,longMonthsParse:c,shortMonthsParse:c,weekdays:"zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag".split("_"),weekdaysShort:"zo._ma._di._wo._do._vr._za.".split("_"),weekdaysMin:"zo_ma_di_wo_do_vr_za".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[vandaag om] LT",nextDay:"[morgen om] LT",nextWeek:"dddd [om] LT",lastDay:"[gisteren om] LT",lastWeek:"[afgelopen] dddd [om] LT",sameElse:"L"},relativeTime:{future:"over %s",past:"%s geleden",s:"een paar seconden",ss:"%d seconden",m:"\xe9\xe9n minuut",mm:"%d minuten",h:"\xe9\xe9n uur",hh:"%d uur",d:"\xe9\xe9n dag",dd:"%d dagen",M:"\xe9\xe9n maand",MM:"%d maanden",y:"\xe9\xe9n jaar",yy:"%d jaar"},dayOfMonthOrdinalParse:/\d{1,2}(ste|de)/,ordinal:function(e){return e+(1===e||8===e||20<=e?"ste":"de")},week:{dow:1,doy:4}}),"jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.".split("_")),Mn="jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec".split("_"),f=[/^jan/i,/^feb/i,/^maart|mrt.?$/i,/^apr/i,/^mei$/i,/^jun[i.]?$/i,/^jul[i.]?$/i,/^aug/i,/^sep/i,/^okt/i,/^nov/i,/^dec/i],ke=/^(januari|februari|maart|april|mei|ju[nl]i|augustus|september|oktober|november|december|jan\.?|feb\.?|mrt\.?|apr\.?|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i,hn=(M.defineLocale("nl",{months:"januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december".split("_"),monthsShort:function(e,a){return e?(/-MMM-/.test(a)?Mn:ln)[e.month()]:ln},monthsRegex:ke,monthsShortRegex:ke,monthsStrictRegex:/^(januari|februari|maart|april|mei|ju[nl]i|augustus|september|oktober|november|december)/i,monthsShortStrictRegex:/^(jan\.?|feb\.?|mrt\.?|apr\.?|mei|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i,monthsParse:f,longMonthsParse:f,shortMonthsParse:f,weekdays:"zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag".split("_"),weekdaysShort:"zo._ma._di._wo._do._vr._za.".split("_"),weekdaysMin:"zo_ma_di_wo_do_vr_za".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD-MM-YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[vandaag om] LT",nextDay:"[morgen om] LT",nextWeek:"dddd [om] LT",lastDay:"[gisteren om] LT",lastWeek:"[afgelopen] dddd [om] LT",sameElse:"L"},relativeTime:{future:"over %s",past:"%s geleden",s:"een paar seconden",ss:"%d seconden",m:"\xe9\xe9n minuut",mm:"%d minuten",h:"\xe9\xe9n uur",hh:"%d uur",d:"\xe9\xe9n dag",dd:"%d dagen",w:"\xe9\xe9n week",ww:"%d weken",M:"\xe9\xe9n maand",MM:"%d maanden",y:"\xe9\xe9n jaar",yy:"%d jaar"},dayOfMonthOrdinalParse:/\d{1,2}(ste|de)/,ordinal:function(e){return e+(1===e||8===e||20<=e?"ste":"de")},week:{dow:1,doy:4}}),M.defineLocale("nn",{months:"januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember".split("_"),monthsShort:"jan._feb._mars_apr._mai_juni_juli_aug._sep._okt._nov._des.".split("_"),monthsParseExact:!0,weekdays:"sundag_m\xe5ndag_tysdag_onsdag_torsdag_fredag_laurdag".split("_"),weekdaysShort:"su._m\xe5._ty._on._to._fr._lau.".split("_"),weekdaysMin:"su_m\xe5_ty_on_to_fr_la".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY [kl.] H:mm",LLLL:"dddd D. MMMM YYYY [kl.] HH:mm"},calendar:{sameDay:"[I dag klokka] LT",nextDay:"[I morgon klokka] LT",nextWeek:"dddd [klokka] LT",lastDay:"[I g\xe5r klokka] LT",lastWeek:"[F\xf8reg\xe5ande] dddd [klokka] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"%s sidan",s:"nokre sekund",ss:"%d sekund",m:"eit minutt",mm:"%d minutt",h:"ein time",hh:"%d timar",d:"ein dag",dd:"%d dagar",w:"ei veke",ww:"%d veker",M:"ein m\xe5nad",MM:"%d m\xe5nader",y:"eit \xe5r",yy:"%d \xe5r"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),M.defineLocale("oc-lnc",{months:{standalone:"geni\xe8r_febri\xe8r_mar\xe7_abril_mai_junh_julhet_agost_setembre_oct\xf2bre_novembre_decembre".split("_"),format:"de geni\xe8r_de febri\xe8r_de mar\xe7_d'abril_de mai_de junh_de julhet_d'agost_de setembre_d'oct\xf2bre_de novembre_de decembre".split("_"),isFormat:/D[oD]?(\s)+MMMM/},monthsShort:"gen._febr._mar\xe7_abr._mai_junh_julh._ago._set._oct._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"dimenge_diluns_dimars_dim\xe8cres_dij\xf2us_divendres_dissabte".split("_"),weekdaysShort:"dg._dl._dm._dc._dj._dv._ds.".split("_"),weekdaysMin:"dg_dl_dm_dc_dj_dv_ds".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM [de] YYYY",ll:"D MMM YYYY",LLL:"D MMMM [de] YYYY [a] H:mm",lll:"D MMM YYYY, H:mm",LLLL:"dddd D MMMM [de] YYYY [a] H:mm",llll:"ddd D MMM YYYY, H:mm"},calendar:{sameDay:"[u\xe8i a] LT",nextDay:"[deman a] LT",nextWeek:"dddd [a] LT",lastDay:"[i\xe8r a] LT",lastWeek:"dddd [passat a] LT",sameElse:"L"},relativeTime:{future:"d'aqu\xed %s",past:"fa %s",s:"unas segondas",ss:"%d segondas",m:"una minuta",mm:"%d minutas",h:"una ora",hh:"%d oras",d:"un jorn",dd:"%d jorns",M:"un mes",MM:"%d meses",y:"un an",yy:"%d ans"},dayOfMonthOrdinalParse:/\d{1,2}(r|n|t|\xe8|a)/,ordinal:function(e,a){return e+("w"!==a&&"W"!==a?1===e?"r":2===e?"n":3===e?"r":4===e?"t":"\xe8":"a")},week:{dow:1,doy:4}}),{1:"\u0a67",2:"\u0a68",3:"\u0a69",4:"\u0a6a",5:"\u0a6b",6:"\u0a6c",7:"\u0a6d",8:"\u0a6e",9:"\u0a6f",0:"\u0a66"}),cn={"\u0a67":"1","\u0a68":"2","\u0a69":"3","\u0a6a":"4","\u0a6b":"5","\u0a6c":"6","\u0a6d":"7","\u0a6e":"8","\u0a6f":"9","\u0a66":"0"},Ln=(M.defineLocale("pa-in",{months:"\u0a1c\u0a28\u0a35\u0a30\u0a40_\u0a2b\u0a3c\u0a30\u0a35\u0a30\u0a40_\u0a2e\u0a3e\u0a30\u0a1a_\u0a05\u0a2a\u0a4d\u0a30\u0a48\u0a32_\u0a2e\u0a08_\u0a1c\u0a42\u0a28_\u0a1c\u0a41\u0a32\u0a3e\u0a08_\u0a05\u0a17\u0a38\u0a24_\u0a38\u0a24\u0a70\u0a2c\u0a30_\u0a05\u0a15\u0a24\u0a42\u0a2c\u0a30_\u0a28\u0a35\u0a70\u0a2c\u0a30_\u0a26\u0a38\u0a70\u0a2c\u0a30".split("_"),monthsShort:"\u0a1c\u0a28\u0a35\u0a30\u0a40_\u0a2b\u0a3c\u0a30\u0a35\u0a30\u0a40_\u0a2e\u0a3e\u0a30\u0a1a_\u0a05\u0a2a\u0a4d\u0a30\u0a48\u0a32_\u0a2e\u0a08_\u0a1c\u0a42\u0a28_\u0a1c\u0a41\u0a32\u0a3e\u0a08_\u0a05\u0a17\u0a38\u0a24_\u0a38\u0a24\u0a70\u0a2c\u0a30_\u0a05\u0a15\u0a24\u0a42\u0a2c\u0a30_\u0a28\u0a35\u0a70\u0a2c\u0a30_\u0a26\u0a38\u0a70\u0a2c\u0a30".split("_"),weekdays:"\u0a10\u0a24\u0a35\u0a3e\u0a30_\u0a38\u0a4b\u0a2e\u0a35\u0a3e\u0a30_\u0a2e\u0a70\u0a17\u0a32\u0a35\u0a3e\u0a30_\u0a2c\u0a41\u0a27\u0a35\u0a3e\u0a30_\u0a35\u0a40\u0a30\u0a35\u0a3e\u0a30_\u0a38\u0a3c\u0a41\u0a71\u0a15\u0a30\u0a35\u0a3e\u0a30_\u0a38\u0a3c\u0a28\u0a40\u0a1a\u0a30\u0a35\u0a3e\u0a30".split("_"),weekdaysShort:"\u0a10\u0a24_\u0a38\u0a4b\u0a2e_\u0a2e\u0a70\u0a17\u0a32_\u0a2c\u0a41\u0a27_\u0a35\u0a40\u0a30_\u0a38\u0a3c\u0a41\u0a15\u0a30_\u0a38\u0a3c\u0a28\u0a40".split("_"),weekdaysMin:"\u0a10\u0a24_\u0a38\u0a4b\u0a2e_\u0a2e\u0a70\u0a17\u0a32_\u0a2c\u0a41\u0a27_\u0a35\u0a40\u0a30_\u0a38\u0a3c\u0a41\u0a15\u0a30_\u0a38\u0a3c\u0a28\u0a40".split("_"),longDateFormat:{LT:"A h:mm \u0a35\u0a1c\u0a47",LTS:"A h:mm:ss \u0a35\u0a1c\u0a47",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm \u0a35\u0a1c\u0a47",LLLL:"dddd, D MMMM YYYY, A h:mm \u0a35\u0a1c\u0a47"},calendar:{sameDay:"[\u0a05\u0a1c] LT",nextDay:"[\u0a15\u0a32] LT",nextWeek:"[\u0a05\u0a17\u0a32\u0a3e] dddd, LT",lastDay:"[\u0a15\u0a32] LT",lastWeek:"[\u0a2a\u0a3f\u0a1b\u0a32\u0a47] dddd, LT",sameElse:"L"},relativeTime:{future:"%s \u0a35\u0a3f\u0a71\u0a1a",past:"%s \u0a2a\u0a3f\u0a1b\u0a32\u0a47",s:"\u0a15\u0a41\u0a1d \u0a38\u0a15\u0a3f\u0a70\u0a1f",ss:"%d \u0a38\u0a15\u0a3f\u0a70\u0a1f",m:"\u0a07\u0a15 \u0a2e\u0a3f\u0a70\u0a1f",mm:"%d \u0a2e\u0a3f\u0a70\u0a1f",h:"\u0a07\u0a71\u0a15 \u0a18\u0a70\u0a1f\u0a3e",hh:"%d \u0a18\u0a70\u0a1f\u0a47",d:"\u0a07\u0a71\u0a15 \u0a26\u0a3f\u0a28",dd:"%d \u0a26\u0a3f\u0a28",M:"\u0a07\u0a71\u0a15 \u0a2e\u0a39\u0a40\u0a28\u0a3e",MM:"%d \u0a2e\u0a39\u0a40\u0a28\u0a47",y:"\u0a07\u0a71\u0a15 \u0a38\u0a3e\u0a32",yy:"%d \u0a38\u0a3e\u0a32"},preparse:function(e){return e.replace(/[\u0a67\u0a68\u0a69\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e\u0a6f\u0a66]/g,function(e){return cn[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return hn[e]})},meridiemParse:/\u0a30\u0a3e\u0a24|\u0a38\u0a35\u0a47\u0a30|\u0a26\u0a41\u0a2a\u0a39\u0a3f\u0a30|\u0a38\u0a3c\u0a3e\u0a2e/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u0a30\u0a3e\u0a24"===a?e<4?e:e+12:"\u0a38\u0a35\u0a47\u0a30"===a?e:"\u0a26\u0a41\u0a2a\u0a39\u0a3f\u0a30"===a?10<=e?e:e+12:"\u0a38\u0a3c\u0a3e\u0a2e"===a?e+12:void 0},meridiem:function(e,a,t){return e<4?"\u0a30\u0a3e\u0a24":e<10?"\u0a38\u0a35\u0a47\u0a30":e<17?"\u0a26\u0a41\u0a2a\u0a39\u0a3f\u0a30":e<20?"\u0a38\u0a3c\u0a3e\u0a2e":"\u0a30\u0a3e\u0a24"},week:{dow:0,doy:6}}),"stycze\u0144_luty_marzec_kwiecie\u0144_maj_czerwiec_lipiec_sierpie\u0144_wrzesie\u0144_pa\u017adziernik_listopad_grudzie\u0144".split("_")),Yn="stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_wrze\u015bnia_pa\u017adziernika_listopada_grudnia".split("_"),_=[/^sty/i,/^lut/i,/^mar/i,/^kwi/i,/^maj/i,/^cze/i,/^lip/i,/^sie/i,/^wrz/i,/^pa\u017a/i,/^lis/i,/^gru/i];function yn(e){return e%10<5&&1<e%10&&~~(e/10)%10!=1}function fn(e,a,t){var s=e+" ";switch(t){case"ss":return s+(yn(e)?"sekundy":"sekund");case"m":return a?"minuta":"minut\u0119";case"mm":return s+(yn(e)?"minuty":"minut");case"h":return a?"godzina":"godzin\u0119";case"hh":return s+(yn(e)?"godziny":"godzin");case"ww":return s+(yn(e)?"tygodnie":"tygodni");case"MM":return s+(yn(e)?"miesi\u0105ce":"miesi\u0119cy");case"yy":return s+(yn(e)?"lata":"lat")}}function pn(e,a,t){return e+(20<=e%100||100<=e&&e%100==0?" de ":" ")+{ss:"secunde",mm:"minute",hh:"ore",dd:"zile",ww:"s\u0103pt\u0103m\xe2ni",MM:"luni",yy:"ani"}[t]}function kn(e,a,t){return"m"===t?a?"\u043c\u0438\u043d\u0443\u0442\u0430":"\u043c\u0438\u043d\u0443\u0442\u0443":e+" "+(e=+e,a=(a={ss:a?"\u0441\u0435\u043a\u0443\u043d\u0434\u0430_\u0441\u0435\u043a\u0443\u043d\u0434\u044b_\u0441\u0435\u043a\u0443\u043d\u0434":"\u0441\u0435\u043a\u0443\u043d\u0434\u0443_\u0441\u0435\u043a\u0443\u043d\u0434\u044b_\u0441\u0435\u043a\u0443\u043d\u0434",mm:a?"\u043c\u0438\u043d\u0443\u0442\u0430_\u043c\u0438\u043d\u0443\u0442\u044b_\u043c\u0438\u043d\u0443\u0442":"\u043c\u0438\u043d\u0443\u0442\u0443_\u043c\u0438\u043d\u0443\u0442\u044b_\u043c\u0438\u043d\u0443\u0442",hh:"\u0447\u0430\u0441_\u0447\u0430\u0441\u0430_\u0447\u0430\u0441\u043e\u0432",dd:"\u0434\u0435\u043d\u044c_\u0434\u043d\u044f_\u0434\u043d\u0435\u0439",ww:"\u043d\u0435\u0434\u0435\u043b\u044f_\u043d\u0435\u0434\u0435\u043b\u0438_\u043d\u0435\u0434\u0435\u043b\u044c",MM:"\u043c\u0435\u0441\u044f\u0446_\u043c\u0435\u0441\u044f\u0446\u0430_\u043c\u0435\u0441\u044f\u0446\u0435\u0432",yy:"\u0433\u043e\u0434_\u0433\u043e\u0434\u0430_\u043b\u0435\u0442"}[t]).split("_"),e%10==1&&e%100!=11?a[0]:2<=e%10&&e%10<=4&&(e%100<10||20<=e%100)?a[1]:a[2])}M.defineLocale("pl",{months:function(e,a){return e?(/D MMMM/.test(a)?Yn:Ln)[e.month()]:Ln},monthsShort:"sty_lut_mar_kwi_maj_cze_lip_sie_wrz_pa\u017a_lis_gru".split("_"),monthsParse:_,longMonthsParse:_,shortMonthsParse:_,weekdays:"niedziela_poniedzia\u0142ek_wtorek_\u015broda_czwartek_pi\u0105tek_sobota".split("_"),weekdaysShort:"ndz_pon_wt_\u015br_czw_pt_sob".split("_"),weekdaysMin:"Nd_Pn_Wt_\u015ar_Cz_Pt_So".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Dzi\u015b o] LT",nextDay:"[Jutro o] LT",nextWeek:function(){switch(this.day()){case 0:return"[W niedziel\u0119 o] LT";case 2:return"[We wtorek o] LT";case 3:return"[W \u015brod\u0119 o] LT";case 6:return"[W sobot\u0119 o] LT";default:return"[W] dddd [o] LT"}},lastDay:"[Wczoraj o] LT",lastWeek:function(){switch(this.day()){case 0:return"[W zesz\u0142\u0105 niedziel\u0119 o] LT";case 3:return"[W zesz\u0142\u0105 \u015brod\u0119 o] LT";case 6:return"[W zesz\u0142\u0105 sobot\u0119 o] LT";default:return"[W zesz\u0142y] dddd [o] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"%s temu",s:"kilka sekund",ss:fn,m:fn,mm:fn,h:fn,hh:fn,d:"1 dzie\u0144",dd:"%d dni",w:"tydzie\u0144",ww:fn,M:"miesi\u0105c",MM:fn,y:"rok",yy:fn},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),M.defineLocale("pt-br",{months:"janeiro_fevereiro_mar\xe7o_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),weekdays:"domingo_segunda-feira_ter\xe7a-feira_quarta-feira_quinta-feira_sexta-feira_s\xe1bado".split("_"),weekdaysShort:"dom_seg_ter_qua_qui_sex_s\xe1b".split("_"),weekdaysMin:"do_2\xaa_3\xaa_4\xaa_5\xaa_6\xaa_s\xe1".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY [\xe0s] HH:mm",LLLL:"dddd, D [de] MMMM [de] YYYY [\xe0s] HH:mm"},calendar:{sameDay:"[Hoje \xe0s] LT",nextDay:"[Amanh\xe3 \xe0s] LT",nextWeek:"dddd [\xe0s] LT",lastDay:"[Ontem \xe0s] LT",lastWeek:function(){return 0===this.day()||6===this.day()?"[\xdaltimo] dddd [\xe0s] LT":"[\xdaltima] dddd [\xe0s] LT"},sameElse:"L"},relativeTime:{future:"em %s",past:"h\xe1 %s",s:"poucos segundos",ss:"%d segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",M:"um m\xeas",MM:"%d meses",y:"um ano",yy:"%d anos"},dayOfMonthOrdinalParse:/\d{1,2}\xba/,ordinal:"%d\xba",invalidDate:"Data inv\xe1lida"}),M.defineLocale("pt",{months:"janeiro_fevereiro_mar\xe7o_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),weekdays:"Domingo_Segunda-feira_Ter\xe7a-feira_Quarta-feira_Quinta-feira_Sexta-feira_S\xe1bado".split("_"),weekdaysShort:"Dom_Seg_Ter_Qua_Qui_Sex_S\xe1b".split("_"),weekdaysMin:"Do_2\xaa_3\xaa_4\xaa_5\xaa_6\xaa_S\xe1".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY HH:mm",LLLL:"dddd, D [de] MMMM [de] YYYY HH:mm"},calendar:{sameDay:"[Hoje \xe0s] LT",nextDay:"[Amanh\xe3 \xe0s] LT",nextWeek:"dddd [\xe0s] LT",lastDay:"[Ontem \xe0s] LT",lastWeek:function(){return 0===this.day()||6===this.day()?"[\xdaltimo] dddd [\xe0s] LT":"[\xdaltima] dddd [\xe0s] LT"},sameElse:"L"},relativeTime:{future:"em %s",past:"h\xe1 %s",s:"segundos",ss:"%d segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",w:"uma semana",ww:"%d semanas",M:"um m\xeas",MM:"%d meses",y:"um ano",yy:"%d anos"},dayOfMonthOrdinalParse:/\d{1,2}\xba/,ordinal:"%d\xba",week:{dow:1,doy:4}}),M.defineLocale("ro",{months:"ianuarie_februarie_martie_aprilie_mai_iunie_iulie_august_septembrie_octombrie_noiembrie_decembrie".split("_"),monthsShort:"ian._feb._mart._apr._mai_iun._iul._aug._sept._oct._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"duminic\u0103_luni_mar\u021bi_miercuri_joi_vineri_s\xe2mb\u0103t\u0103".split("_"),weekdaysShort:"Dum_Lun_Mar_Mie_Joi_Vin_S\xe2m".split("_"),weekdaysMin:"Du_Lu_Ma_Mi_Jo_Vi_S\xe2".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY H:mm",LLLL:"dddd, D MMMM YYYY H:mm"},calendar:{sameDay:"[azi la] LT",nextDay:"[m\xe2ine la] LT",nextWeek:"dddd [la] LT",lastDay:"[ieri la] LT",lastWeek:"[fosta] dddd [la] LT",sameElse:"L"},relativeTime:{future:"peste %s",past:"%s \xeen urm\u0103",s:"c\xe2teva secunde",ss:pn,m:"un minut",mm:pn,h:"o or\u0103",hh:pn,d:"o zi",dd:pn,w:"o s\u0103pt\u0103m\xe2n\u0103",ww:pn,M:"o lun\u0103",MM:pn,y:"un an",yy:pn},week:{dow:1,doy:7}});y=[/^\u044f\u043d\u0432/i,/^\u0444\u0435\u0432/i,/^\u043c\u0430\u0440/i,/^\u0430\u043f\u0440/i,/^\u043c\u0430[\u0439\u044f]/i,/^\u0438\u044e\u043d/i,/^\u0438\u044e\u043b/i,/^\u0430\u0432\u0433/i,/^\u0441\u0435\u043d/i,/^\u043e\u043a\u0442/i,/^\u043d\u043e\u044f/i,/^\u0434\u0435\u043a/i],M.defineLocale("ru",{months:{format:"\u044f\u043d\u0432\u0430\u0440\u044f_\u0444\u0435\u0432\u0440\u0430\u043b\u044f_\u043c\u0430\u0440\u0442\u0430_\u0430\u043f\u0440\u0435\u043b\u044f_\u043c\u0430\u044f_\u0438\u044e\u043d\u044f_\u0438\u044e\u043b\u044f_\u0430\u0432\u0433\u0443\u0441\u0442\u0430_\u0441\u0435\u043d\u0442\u044f\u0431\u0440\u044f_\u043e\u043a\u0442\u044f\u0431\u0440\u044f_\u043d\u043e\u044f\u0431\u0440\u044f_\u0434\u0435\u043a\u0430\u0431\u0440\u044f".split("_"),standalone:"\u044f\u043d\u0432\u0430\u0440\u044c_\u0444\u0435\u0432\u0440\u0430\u043b\u044c_\u043c\u0430\u0440\u0442_\u0430\u043f\u0440\u0435\u043b\u044c_\u043c\u0430\u0439_\u0438\u044e\u043d\u044c_\u0438\u044e\u043b\u044c_\u0430\u0432\u0433\u0443\u0441\u0442_\u0441\u0435\u043d\u0442\u044f\u0431\u0440\u044c_\u043e\u043a\u0442\u044f\u0431\u0440\u044c_\u043d\u043e\u044f\u0431\u0440\u044c_\u0434\u0435\u043a\u0430\u0431\u0440\u044c".split("_")},monthsShort:{format:"\u044f\u043d\u0432._\u0444\u0435\u0432\u0440._\u043c\u0430\u0440._\u0430\u043f\u0440._\u043c\u0430\u044f_\u0438\u044e\u043d\u044f_\u0438\u044e\u043b\u044f_\u0430\u0432\u0433._\u0441\u0435\u043d\u0442._\u043e\u043a\u0442._\u043d\u043e\u044f\u0431._\u0434\u0435\u043a.".split("_"),standalone:"\u044f\u043d\u0432._\u0444\u0435\u0432\u0440._\u043c\u0430\u0440\u0442_\u0430\u043f\u0440._\u043c\u0430\u0439_\u0438\u044e\u043d\u044c_\u0438\u044e\u043b\u044c_\u0430\u0432\u0433._\u0441\u0435\u043d\u0442._\u043e\u043a\u0442._\u043d\u043e\u044f\u0431._\u0434\u0435\u043a.".split("_")},weekdays:{standalone:"\u0432\u043e\u0441\u043a\u0440\u0435\u0441\u0435\u043d\u044c\u0435_\u043f\u043e\u043d\u0435\u0434\u0435\u043b\u044c\u043d\u0438\u043a_\u0432\u0442\u043e\u0440\u043d\u0438\u043a_\u0441\u0440\u0435\u0434\u0430_\u0447\u0435\u0442\u0432\u0435\u0440\u0433_\u043f\u044f\u0442\u043d\u0438\u0446\u0430_\u0441\u0443\u0431\u0431\u043e\u0442\u0430".split("_"),format:"\u0432\u043e\u0441\u043a\u0440\u0435\u0441\u0435\u043d\u044c\u0435_\u043f\u043e\u043d\u0435\u0434\u0435\u043b\u044c\u043d\u0438\u043a_\u0432\u0442\u043e\u0440\u043d\u0438\u043a_\u0441\u0440\u0435\u0434\u0443_\u0447\u0435\u0442\u0432\u0435\u0440\u0433_\u043f\u044f\u0442\u043d\u0438\u0446\u0443_\u0441\u0443\u0431\u0431\u043e\u0442\u0443".split("_"),isFormat:/\[ ?[\u0412\u0432] ?(?:\u043f\u0440\u043e\u0448\u043b\u0443\u044e|\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e|\u044d\u0442\u0443)? ?] ?dddd/},weekdaysShort:"\u0432\u0441_\u043f\u043d_\u0432\u0442_\u0441\u0440_\u0447\u0442_\u043f\u0442_\u0441\u0431".split("_"),weekdaysMin:"\u0432\u0441_\u043f\u043d_\u0432\u0442_\u0441\u0440_\u0447\u0442_\u043f\u0442_\u0441\u0431".split("_"),monthsParse:y,longMonthsParse:y,shortMonthsParse:y,monthsRegex:/^(\u044f\u043d\u0432\u0430\u0440[\u044c\u044f]|\u044f\u043d\u0432\.?|\u0444\u0435\u0432\u0440\u0430\u043b[\u044c\u044f]|\u0444\u0435\u0432\u0440?\.?|\u043c\u0430\u0440\u0442\u0430?|\u043c\u0430\u0440\.?|\u0430\u043f\u0440\u0435\u043b[\u044c\u044f]|\u0430\u043f\u0440\.?|\u043c\u0430[\u0439\u044f]|\u0438\u044e\u043d[\u044c\u044f]|\u0438\u044e\u043d\.?|\u0438\u044e\u043b[\u044c\u044f]|\u0438\u044e\u043b\.?|\u0430\u0432\u0433\u0443\u0441\u0442\u0430?|\u0430\u0432\u0433\.?|\u0441\u0435\u043d\u0442\u044f\u0431\u0440[\u044c\u044f]|\u0441\u0435\u043d\u0442?\.?|\u043e\u043a\u0442\u044f\u0431\u0440[\u044c\u044f]|\u043e\u043a\u0442\.?|\u043d\u043e\u044f\u0431\u0440[\u044c\u044f]|\u043d\u043e\u044f\u0431?\.?|\u0434\u0435\u043a\u0430\u0431\u0440[\u044c\u044f]|\u0434\u0435\u043a\.?)/i,monthsShortRegex:/^(\u044f\u043d\u0432\u0430\u0440[\u044c\u044f]|\u044f\u043d\u0432\.?|\u0444\u0435\u0432\u0440\u0430\u043b[\u044c\u044f]|\u0444\u0435\u0432\u0440?\.?|\u043c\u0430\u0440\u0442\u0430?|\u043c\u0430\u0440\.?|\u0430\u043f\u0440\u0435\u043b[\u044c\u044f]|\u0430\u043f\u0440\.?|\u043c\u0430[\u0439\u044f]|\u0438\u044e\u043d[\u044c\u044f]|\u0438\u044e\u043d\.?|\u0438\u044e\u043b[\u044c\u044f]|\u0438\u044e\u043b\.?|\u0430\u0432\u0433\u0443\u0441\u0442\u0430?|\u0430\u0432\u0433\.?|\u0441\u0435\u043d\u0442\u044f\u0431\u0440[\u044c\u044f]|\u0441\u0435\u043d\u0442?\.?|\u043e\u043a\u0442\u044f\u0431\u0440[\u044c\u044f]|\u043e\u043a\u0442\.?|\u043d\u043e\u044f\u0431\u0440[\u044c\u044f]|\u043d\u043e\u044f\u0431?\.?|\u0434\u0435\u043a\u0430\u0431\u0440[\u044c\u044f]|\u0434\u0435\u043a\.?)/i,monthsStrictRegex:/^(\u044f\u043d\u0432\u0430\u0440[\u044f\u044c]|\u0444\u0435\u0432\u0440\u0430\u043b[\u044f\u044c]|\u043c\u0430\u0440\u0442\u0430?|\u0430\u043f\u0440\u0435\u043b[\u044f\u044c]|\u043c\u0430[\u044f\u0439]|\u0438\u044e\u043d[\u044f\u044c]|\u0438\u044e\u043b[\u044f\u044c]|\u0430\u0432\u0433\u0443\u0441\u0442\u0430?|\u0441\u0435\u043d\u0442\u044f\u0431\u0440[\u044f\u044c]|\u043e\u043a\u0442\u044f\u0431\u0440[\u044f\u044c]|\u043d\u043e\u044f\u0431\u0440[\u044f\u044c]|\u0434\u0435\u043a\u0430\u0431\u0440[\u044f\u044c])/i,monthsShortStrictRegex:/^(\u044f\u043d\u0432\.|\u0444\u0435\u0432\u0440?\.|\u043c\u0430\u0440[\u0442.]|\u0430\u043f\u0440\.|\u043c\u0430[\u044f\u0439]|\u0438\u044e\u043d[\u044c\u044f.]|\u0438\u044e\u043b[\u044c\u044f.]|\u0430\u0432\u0433\.|\u0441\u0435\u043d\u0442?\.|\u043e\u043a\u0442\.|\u043d\u043e\u044f\u0431?\.|\u0434\u0435\u043a\.)/i,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY \u0433.",LLL:"D MMMM YYYY \u0433., H:mm",LLLL:"dddd, D MMMM YYYY \u0433., H:mm"},calendar:{sameDay:"[\u0421\u0435\u0433\u043e\u0434\u043d\u044f, \u0432] LT",nextDay:"[\u0417\u0430\u0432\u0442\u0440\u0430, \u0432] LT",lastDay:"[\u0412\u0447\u0435\u0440\u0430, \u0432] LT",nextWeek:function(e){if(e.week()===this.week())return 2===this.day()?"[\u0412\u043e] dddd, [\u0432] LT":"[\u0412] dddd, [\u0432] LT";switch(this.day()){case 0:return"[\u0412 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435] dddd, [\u0432] LT";case 1:case 2:case 4:return"[\u0412 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439] dddd, [\u0432] LT";case 3:case 5:case 6:return"[\u0412 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e] dddd, [\u0432] LT"}},lastWeek:function(e){if(e.week()===this.week())return 2===this.day()?"[\u0412\u043e] dddd, [\u0432] LT":"[\u0412] dddd, [\u0432] LT";switch(this.day()){case 0:return"[\u0412 \u043f\u0440\u043e\u0448\u043b\u043e\u0435] dddd, [\u0432] LT";case 1:case 2:case 4:return"[\u0412 \u043f\u0440\u043e\u0448\u043b\u044b\u0439] dddd, [\u0432] LT";case 3:case 5:case 6:return"[\u0412 \u043f\u0440\u043e\u0448\u043b\u0443\u044e] dddd, [\u0432] LT"}},sameElse:"L"},relativeTime:{future:"\u0447\u0435\u0440\u0435\u0437 %s",past:"%s \u043d\u0430\u0437\u0430\u0434",s:"\u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u0435\u043a\u0443\u043d\u0434",ss:kn,m:kn,mm:kn,h:"\u0447\u0430\u0441",hh:kn,d:"\u0434\u0435\u043d\u044c",dd:kn,w:"\u043d\u0435\u0434\u0435\u043b\u044f",ww:kn,M:"\u043c\u0435\u0441\u044f\u0446",MM:kn,y:"\u0433\u043e\u0434",yy:kn},meridiemParse:/\u043d\u043e\u0447\u0438|\u0443\u0442\u0440\u0430|\u0434\u043d\u044f|\u0432\u0435\u0447\u0435\u0440\u0430/i,isPM:function(e){return/^(\u0434\u043d\u044f|\u0432\u0435\u0447\u0435\u0440\u0430)$/.test(e)},meridiem:function(e,a,t){return e<4?"\u043d\u043e\u0447\u0438":e<12?"\u0443\u0442\u0440\u0430":e<17?"\u0434\u043d\u044f":"\u0432\u0435\u0447\u0435\u0440\u0430"},dayOfMonthOrdinalParse:/\d{1,2}-(\u0439|\u0433\u043e|\u044f)/,ordinal:function(e,a){switch(a){case"M":case"d":case"DDD":return e+"-\u0439";case"D":return e+"-\u0433\u043e";case"w":case"W":return e+"-\u044f";default:return e}},week:{dow:1,doy:4}}),Y=["\u062c\u0646\u0648\u0631\u064a","\u0641\u064a\u0628\u0631\u0648\u0631\u064a","\u0645\u0627\u0631\u0686","\u0627\u067e\u0631\u064a\u0644","\u0645\u0626\u064a","\u062c\u0648\u0646","\u062c\u0648\u0644\u0627\u0621\u0650","\u0622\u06af\u0633\u067d","\u0633\u064a\u067e\u067d\u0645\u0628\u0631","\u0622\u06aa\u067d\u0648\u0628\u0631","\u0646\u0648\u0645\u0628\u0631","\u068a\u0633\u0645\u0628\u0631"],Ke=["\u0622\u0686\u0631","\u0633\u0648\u0645\u0631","\u0627\u06b1\u0627\u0631\u0648","\u0627\u0631\u0628\u0639","\u062e\u0645\u064a\u0633","\u062c\u0645\u0639","\u0687\u0646\u0687\u0631"],M.defineLocale("sd",{months:Y,monthsShort:Y,weekdays:Ke,weekdaysShort:Ke,weekdaysMin:Ke,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd\u060c D MMMM YYYY HH:mm"},meridiemParse:/\u0635\u0628\u062d|\u0634\u0627\u0645/,isPM:function(e){return"\u0634\u0627\u0645"===e},meridiem:function(e,a,t){return e<12?"\u0635\u0628\u062d":"\u0634\u0627\u0645"},calendar:{sameDay:"[\u0627\u0684] LT",nextDay:"[\u0633\u0680\u0627\u06bb\u064a] LT",nextWeek:"dddd [\u0627\u06b3\u064a\u0646 \u0647\u0641\u062a\u064a \u062a\u064a] LT",lastDay:"[\u06aa\u0627\u0644\u0647\u0647] LT",lastWeek:"[\u06af\u0632\u0631\u064a\u0644 \u0647\u0641\u062a\u064a] dddd [\u062a\u064a] LT",sameElse:"L"},relativeTime:{future:"%s \u067e\u0648\u0621",past:"%s \u0627\u06b3",s:"\u0686\u0646\u062f \u0633\u064a\u06aa\u0646\u068a",ss:"%d \u0633\u064a\u06aa\u0646\u068a",m:"\u0647\u06aa \u0645\u0646\u067d",mm:"%d \u0645\u0646\u067d",h:"\u0647\u06aa \u06aa\u0644\u0627\u06aa",hh:"%d \u06aa\u0644\u0627\u06aa",d:"\u0647\u06aa \u068f\u064a\u0646\u0647\u0646",dd:"%d \u068f\u064a\u0646\u0647\u0646",M:"\u0647\u06aa \u0645\u0647\u064a\u0646\u0648",MM:"%d \u0645\u0647\u064a\u0646\u0627",y:"\u0647\u06aa \u0633\u0627\u0644",yy:"%d \u0633\u0627\u0644"},preparse:function(e){return e.replace(/\u060c/g,",")},postformat:function(e){return e.replace(/,/g,"\u060c")},week:{dow:1,doy:4}}),M.defineLocale("se",{months:"o\u0111\u0111ajagem\xe1nnu_guovvam\xe1nnu_njuk\u010dam\xe1nnu_cuo\u014bom\xe1nnu_miessem\xe1nnu_geassem\xe1nnu_suoidnem\xe1nnu_borgem\xe1nnu_\u010dak\u010dam\xe1nnu_golggotm\xe1nnu_sk\xe1bmam\xe1nnu_juovlam\xe1nnu".split("_"),monthsShort:"o\u0111\u0111j_guov_njuk_cuo_mies_geas_suoi_borg_\u010dak\u010d_golg_sk\xe1b_juov".split("_"),weekdays:"sotnabeaivi_vuoss\xe1rga_ma\u014b\u014beb\xe1rga_gaskavahkku_duorastat_bearjadat_l\xe1vvardat".split("_"),weekdaysShort:"sotn_vuos_ma\u014b_gask_duor_bear_l\xe1v".split("_"),weekdaysMin:"s_v_m_g_d_b_L".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"MMMM D. [b.] YYYY",LLL:"MMMM D. [b.] YYYY [ti.] HH:mm",LLLL:"dddd, MMMM D. [b.] YYYY [ti.] HH:mm"},calendar:{sameDay:"[otne ti] LT",nextDay:"[ihttin ti] LT",nextWeek:"dddd [ti] LT",lastDay:"[ikte ti] LT",lastWeek:"[ovddit] dddd [ti] LT",sameElse:"L"},relativeTime:{future:"%s gea\u017ees",past:"ma\u014bit %s",s:"moadde sekunddat",ss:"%d sekunddat",m:"okta minuhta",mm:"%d minuhtat",h:"okta diimmu",hh:"%d diimmut",d:"okta beaivi",dd:"%d beaivvit",M:"okta m\xe1nnu",MM:"%d m\xe1nut",y:"okta jahki",yy:"%d jagit"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),M.defineLocale("si",{months:"\u0da2\u0db1\u0dc0\u0dcf\u0dbb\u0dd2_\u0db4\u0dd9\u0db6\u0dbb\u0dc0\u0dcf\u0dbb\u0dd2_\u0db8\u0dcf\u0dbb\u0dca\u0dad\u0dd4_\u0d85\u0db4\u0dca\u200d\u0dbb\u0dda\u0dbd\u0dca_\u0db8\u0dd0\u0dba\u0dd2_\u0da2\u0dd6\u0db1\u0dd2_\u0da2\u0dd6\u0dbd\u0dd2_\u0d85\u0d9c\u0ddd\u0dc3\u0dca\u0dad\u0dd4_\u0dc3\u0dd0\u0db4\u0dca\u0dad\u0dd0\u0db8\u0dca\u0db6\u0dbb\u0dca_\u0d94\u0d9a\u0dca\u0dad\u0ddd\u0db6\u0dbb\u0dca_\u0db1\u0ddc\u0dc0\u0dd0\u0db8\u0dca\u0db6\u0dbb\u0dca_\u0daf\u0dd9\u0dc3\u0dd0\u0db8\u0dca\u0db6\u0dbb\u0dca".split("_"),monthsShort:"\u0da2\u0db1_\u0db4\u0dd9\u0db6_\u0db8\u0dcf\u0dbb\u0dca_\u0d85\u0db4\u0dca_\u0db8\u0dd0\u0dba\u0dd2_\u0da2\u0dd6\u0db1\u0dd2_\u0da2\u0dd6\u0dbd\u0dd2_\u0d85\u0d9c\u0ddd_\u0dc3\u0dd0\u0db4\u0dca_\u0d94\u0d9a\u0dca_\u0db1\u0ddc\u0dc0\u0dd0_\u0daf\u0dd9\u0dc3\u0dd0".split("_"),weekdays:"\u0d89\u0dbb\u0dd2\u0daf\u0dcf_\u0dc3\u0db3\u0dd4\u0daf\u0dcf_\u0d85\u0d9f\u0dc4\u0dbb\u0dd4\u0dc0\u0dcf\u0daf\u0dcf_\u0db6\u0daf\u0dcf\u0daf\u0dcf_\u0db6\u0dca\u200d\u0dbb\u0dc4\u0dc3\u0dca\u0db4\u0dad\u0dd2\u0db1\u0dca\u0daf\u0dcf_\u0dc3\u0dd2\u0d9a\u0dd4\u0dbb\u0dcf\u0daf\u0dcf_\u0dc3\u0dd9\u0db1\u0dc3\u0dd4\u0dbb\u0dcf\u0daf\u0dcf".split("_"),weekdaysShort:"\u0d89\u0dbb\u0dd2_\u0dc3\u0db3\u0dd4_\u0d85\u0d9f_\u0db6\u0daf\u0dcf_\u0db6\u0dca\u200d\u0dbb\u0dc4_\u0dc3\u0dd2\u0d9a\u0dd4_\u0dc3\u0dd9\u0db1".split("_"),weekdaysMin:"\u0d89_\u0dc3_\u0d85_\u0db6_\u0db6\u0dca\u200d\u0dbb_\u0dc3\u0dd2_\u0dc3\u0dd9".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"a h:mm",LTS:"a h:mm:ss",L:"YYYY/MM/DD",LL:"YYYY MMMM D",LLL:"YYYY MMMM D, a h:mm",LLLL:"YYYY MMMM D [\u0dc0\u0dd0\u0db1\u0dd2] dddd, a h:mm:ss"},calendar:{sameDay:"[\u0d85\u0daf] LT[\u0da7]",nextDay:"[\u0dc4\u0dd9\u0da7] LT[\u0da7]",nextWeek:"dddd LT[\u0da7]",lastDay:"[\u0d8a\u0dba\u0dda] LT[\u0da7]",lastWeek:"[\u0db4\u0dc3\u0dd4\u0d9c\u0dd2\u0dba] dddd LT[\u0da7]",sameElse:"L"},relativeTime:{future:"%s\u0d9a\u0dd2\u0db1\u0dca",past:"%s\u0d9a\u0da7 \u0db4\u0dd9\u0dbb",s:"\u0dad\u0dad\u0dca\u0db4\u0dbb \u0d9a\u0dd2\u0dc4\u0dd2\u0db4\u0dba",ss:"\u0dad\u0dad\u0dca\u0db4\u0dbb %d",m:"\u0db8\u0dd2\u0db1\u0dd2\u0dad\u0dca\u0dad\u0dd4\u0dc0",mm:"\u0db8\u0dd2\u0db1\u0dd2\u0dad\u0dca\u0dad\u0dd4 %d",h:"\u0db4\u0dd0\u0dba",hh:"\u0db4\u0dd0\u0dba %d",d:"\u0daf\u0dd2\u0db1\u0dba",dd:"\u0daf\u0dd2\u0db1 %d",M:"\u0db8\u0dcf\u0dc3\u0dba",MM:"\u0db8\u0dcf\u0dc3 %d",y:"\u0dc0\u0dc3\u0dbb",yy:"\u0dc0\u0dc3\u0dbb %d"},dayOfMonthOrdinalParse:/\d{1,2} \u0dc0\u0dd0\u0db1\u0dd2/,ordinal:function(e){return e+" \u0dc0\u0dd0\u0db1\u0dd2"},meridiemParse:/\u0db4\u0dd9\u0dbb \u0dc0\u0dbb\u0dd4|\u0db4\u0dc3\u0dca \u0dc0\u0dbb\u0dd4|\u0db4\u0dd9.\u0dc0|\u0db4.\u0dc0./,isPM:function(e){return"\u0db4.\u0dc0."===e||"\u0db4\u0dc3\u0dca \u0dc0\u0dbb\u0dd4"===e},meridiem:function(e,a,t){return 11<e?t?"\u0db4.\u0dc0.":"\u0db4\u0dc3\u0dca \u0dc0\u0dbb\u0dd4":t?"\u0db4\u0dd9.\u0dc0.":"\u0db4\u0dd9\u0dbb \u0dc0\u0dbb\u0dd4"}}),Ht="janu\xe1r_febru\xe1r_marec_apr\xedl_m\xe1j_j\xfan_j\xfal_august_september_okt\xf3ber_november_december".split("_"),r="jan_feb_mar_apr_m\xe1j_j\xfan_j\xfal_aug_sep_okt_nov_dec".split("_");function Dn(e){return 1<e&&e<5}function Tn(e,a,t,s){var n=e+" ";switch(t){case"s":return a||s?"p\xe1r sek\xfand":"p\xe1r sekundami";case"ss":return a||s?n+(Dn(e)?"sekundy":"sek\xfand"):n+"sekundami";case"m":return a?"min\xfata":s?"min\xfatu":"min\xfatou";case"mm":return a||s?n+(Dn(e)?"min\xfaty":"min\xfat"):n+"min\xfatami";case"h":return a?"hodina":s?"hodinu":"hodinou";case"hh":return a||s?n+(Dn(e)?"hodiny":"hod\xedn"):n+"hodinami";case"d":return a||s?"de\u0148":"d\u0148om";case"dd":return a||s?n+(Dn(e)?"dni":"dn\xed"):n+"d\u0148ami";case"M":return a||s?"mesiac":"mesiacom";case"MM":return a||s?n+(Dn(e)?"mesiace":"mesiacov"):n+"mesiacmi";case"y":return a||s?"rok":"rokom";case"yy":return a||s?n+(Dn(e)?"roky":"rokov"):n+"rokmi"}}function gn(e,a,t,s){var n=e+" ";switch(t){case"s":return a||s?"nekaj sekund":"nekaj sekundami";case"ss":return n+=1===e?a?"sekundo":"sekundi":2===e?a||s?"sekundi":"sekundah":e<5?a||s?"sekunde":"sekundah":"sekund";case"m":return a?"ena minuta":"eno minuto";case"mm":return n+=1===e?a?"minuta":"minuto":2===e?a||s?"minuti":"minutama":e<5?a||s?"minute":"minutami":a||s?"minut":"minutami";case"h":return a?"ena ura":"eno uro";case"hh":return n+=1===e?a?"ura":"uro":2===e?a||s?"uri":"urama":e<5?a||s?"ure":"urami":a||s?"ur":"urami";case"d":return a||s?"en dan":"enim dnem";case"dd":return n+=1===e?a||s?"dan":"dnem":2===e?a||s?"dni":"dnevoma":a||s?"dni":"dnevi";case"M":return a||s?"en mesec":"enim mesecem";case"MM":return n+=1===e?a||s?"mesec":"mesecem":2===e?a||s?"meseca":"mesecema":e<5?a||s?"mesece":"meseci":a||s?"mesecev":"meseci";case"y":return a||s?"eno leto":"enim letom";case"yy":return n+=1===e?a||s?"leto":"letom":2===e?a||s?"leti":"letoma":e<5?a||s?"leta":"leti":a||s?"let":"leti"}}M.defineLocale("sk",{months:Ht,monthsShort:r,weekdays:"nede\u013ea_pondelok_utorok_streda_\u0161tvrtok_piatok_sobota".split("_"),weekdaysShort:"ne_po_ut_st_\u0161t_pi_so".split("_"),weekdaysMin:"ne_po_ut_st_\u0161t_pi_so".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd D. MMMM YYYY H:mm"},calendar:{sameDay:"[dnes o] LT",nextDay:"[zajtra o] LT",nextWeek:function(){switch(this.day()){case 0:return"[v nede\u013eu o] LT";case 1:case 2:return"[v] dddd [o] LT";case 3:return"[v stredu o] LT";case 4:return"[vo \u0161tvrtok o] LT";case 5:return"[v piatok o] LT";case 6:return"[v sobotu o] LT"}},lastDay:"[v\u010dera o] LT",lastWeek:function(){switch(this.day()){case 0:return"[minul\xfa nede\u013eu o] LT";case 1:case 2:return"[minul\xfd] dddd [o] LT";case 3:return"[minul\xfa stredu o] LT";case 4:case 5:return"[minul\xfd] dddd [o] LT";case 6:return"[minul\xfa sobotu o] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"pred %s",s:Tn,ss:Tn,m:Tn,mm:Tn,h:Tn,hh:Tn,d:Tn,dd:Tn,M:Tn,MM:Tn,y:Tn,yy:Tn},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),M.defineLocale("sl",{months:"januar_februar_marec_april_maj_junij_julij_avgust_september_oktober_november_december".split("_"),monthsShort:"jan._feb._mar._apr._maj._jun._jul._avg._sep._okt._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"nedelja_ponedeljek_torek_sreda_\u010detrtek_petek_sobota".split("_"),weekdaysShort:"ned._pon._tor._sre._\u010det._pet._sob.".split("_"),weekdaysMin:"ne_po_to_sr_\u010de_pe_so".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD. MM. YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[danes ob] LT",nextDay:"[jutri ob] LT",nextWeek:function(){switch(this.day()){case 0:return"[v] [nedeljo] [ob] LT";case 3:return"[v] [sredo] [ob] LT";case 6:return"[v] [soboto] [ob] LT";case 1:case 2:case 4:case 5:return"[v] dddd [ob] LT"}},lastDay:"[v\u010deraj ob] LT",lastWeek:function(){switch(this.day()){case 0:return"[prej\u0161njo] [nedeljo] [ob] LT";case 3:return"[prej\u0161njo] [sredo] [ob] LT";case 6:return"[prej\u0161njo] [soboto] [ob] LT";case 1:case 2:case 4:case 5:return"[prej\u0161nji] dddd [ob] LT"}},sameElse:"L"},relativeTime:{future:"\u010dez %s",past:"pred %s",s:gn,ss:gn,m:gn,mm:gn,h:gn,hh:gn,d:gn,dd:gn,M:gn,MM:gn,y:gn,yy:gn},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}}),M.defineLocale("sq",{months:"Janar_Shkurt_Mars_Prill_Maj_Qershor_Korrik_Gusht_Shtator_Tetor_N\xebntor_Dhjetor".split("_"),monthsShort:"Jan_Shk_Mar_Pri_Maj_Qer_Kor_Gus_Sht_Tet_N\xebn_Dhj".split("_"),weekdays:"E Diel_E H\xebn\xeb_E Mart\xeb_E M\xebrkur\xeb_E Enjte_E Premte_E Shtun\xeb".split("_"),weekdaysShort:"Die_H\xebn_Mar_M\xebr_Enj_Pre_Sht".split("_"),weekdaysMin:"D_H_Ma_M\xeb_E_P_Sh".split("_"),weekdaysParseExact:!0,meridiemParse:/PD|MD/,isPM:function(e){return"M"===e.charAt(0)},meridiem:function(e,a,t){return e<12?"PD":"MD"},longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Sot n\xeb] LT",nextDay:"[Nes\xebr n\xeb] LT",nextWeek:"dddd [n\xeb] LT",lastDay:"[Dje n\xeb] LT",lastWeek:"dddd [e kaluar n\xeb] LT",sameElse:"L"},relativeTime:{future:"n\xeb %s",past:"%s m\xeb par\xeb",s:"disa sekonda",ss:"%d sekonda",m:"nj\xeb minut\xeb",mm:"%d minuta",h:"nj\xeb or\xeb",hh:"%d or\xeb",d:"nj\xeb dit\xeb",dd:"%d dit\xeb",M:"nj\xeb muaj",MM:"%d muaj",y:"nj\xeb vit",yy:"%d vite"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}});var W={words:{ss:["\u0441\u0435\u043a\u0443\u043d\u0434\u0430","\u0441\u0435\u043a\u0443\u043d\u0434\u0435","\u0441\u0435\u043a\u0443\u043d\u0434\u0438"],m:["\u0458\u0435\u0434\u0430\u043d \u043c\u0438\u043d\u0443\u0442","\u0458\u0435\u0434\u043d\u043e\u0433 \u043c\u0438\u043d\u0443\u0442\u0430"],mm:["\u043c\u0438\u043d\u0443\u0442","\u043c\u0438\u043d\u0443\u0442\u0430","\u043c\u0438\u043d\u0443\u0442\u0430"],h:["\u0458\u0435\u0434\u0430\u043d \u0441\u0430\u0442","\u0458\u0435\u0434\u043d\u043e\u0433 \u0441\u0430\u0442\u0430"],hh:["\u0441\u0430\u0442","\u0441\u0430\u0442\u0430","\u0441\u0430\u0442\u0438"],d:["\u0458\u0435\u0434\u0430\u043d \u0434\u0430\u043d","\u0458\u0435\u0434\u043d\u043e\u0433 \u0434\u0430\u043d\u0430"],dd:["\u0434\u0430\u043d","\u0434\u0430\u043d\u0430","\u0434\u0430\u043d\u0430"],M:["\u0458\u0435\u0434\u0430\u043d \u043c\u0435\u0441\u0435\u0446","\u0458\u0435\u0434\u043d\u043e\u0433 \u043c\u0435\u0441\u0435\u0446\u0430"],MM:["\u043c\u0435\u0441\u0435\u0446","\u043c\u0435\u0441\u0435\u0446\u0430","\u043c\u0435\u0441\u0435\u0446\u0438"],y:["\u0458\u0435\u0434\u043d\u0443 \u0433\u043e\u0434\u0438\u043d\u0443","\u0458\u0435\u0434\u043d\u0435 \u0433\u043e\u0434\u0438\u043d\u0435"],yy:["\u0433\u043e\u0434\u0438\u043d\u0443","\u0433\u043e\u0434\u0438\u043d\u0435","\u0433\u043e\u0434\u0438\u043d\u0430"]},correctGrammaticalCase:function(e,a){return 1<=e%10&&e%10<=4&&(e%100<10||20<=e%100)?e%10==1?a[0]:a[1]:a[2]},translate:function(e,a,t,s){var n=W.words[t];return 1===t.length?"y"===t&&a?"\u0458\u0435\u0434\u043d\u0430 \u0433\u043e\u0434\u0438\u043d\u0430":s||a?n[0]:n[1]:(s=W.correctGrammaticalCase(e,n),"yy"===t&&a&&"\u0433\u043e\u0434\u0438\u043d\u0443"===s?e+" \u0433\u043e\u0434\u0438\u043d\u0430":e+" "+s)}},A=(M.defineLocale("sr-cyrl",{months:"\u0458\u0430\u043d\u0443\u0430\u0440_\u0444\u0435\u0431\u0440\u0443\u0430\u0440_\u043c\u0430\u0440\u0442_\u0430\u043f\u0440\u0438\u043b_\u043c\u0430\u0458_\u0458\u0443\u043d_\u0458\u0443\u043b_\u0430\u0432\u0433\u0443\u0441\u0442_\u0441\u0435\u043f\u0442\u0435\u043c\u0431\u0430\u0440_\u043e\u043a\u0442\u043e\u0431\u0430\u0440_\u043d\u043e\u0432\u0435\u043c\u0431\u0430\u0440_\u0434\u0435\u0446\u0435\u043c\u0431\u0430\u0440".split("_"),monthsShort:"\u0458\u0430\u043d._\u0444\u0435\u0431._\u043c\u0430\u0440._\u0430\u043f\u0440._\u043c\u0430\u0458_\u0458\u0443\u043d_\u0458\u0443\u043b_\u0430\u0432\u0433._\u0441\u0435\u043f._\u043e\u043a\u0442._\u043d\u043e\u0432._\u0434\u0435\u0446.".split("_"),monthsParseExact:!0,weekdays:"\u043d\u0435\u0434\u0435\u0459\u0430_\u043f\u043e\u043d\u0435\u0434\u0435\u0459\u0430\u043a_\u0443\u0442\u043e\u0440\u0430\u043a_\u0441\u0440\u0435\u0434\u0430_\u0447\u0435\u0442\u0432\u0440\u0442\u0430\u043a_\u043f\u0435\u0442\u0430\u043a_\u0441\u0443\u0431\u043e\u0442\u0430".split("_"),weekdaysShort:"\u043d\u0435\u0434._\u043f\u043e\u043d._\u0443\u0442\u043e._\u0441\u0440\u0435._\u0447\u0435\u0442._\u043f\u0435\u0442._\u0441\u0443\u0431.".split("_"),weekdaysMin:"\u043d\u0435_\u043f\u043e_\u0443\u0442_\u0441\u0440_\u0447\u0435_\u043f\u0435_\u0441\u0443".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"D. M. YYYY.",LL:"D. MMMM YYYY.",LLL:"D. MMMM YYYY. H:mm",LLLL:"dddd, D. MMMM YYYY. H:mm"},calendar:{sameDay:"[\u0434\u0430\u043d\u0430\u0441 \u0443] LT",nextDay:"[\u0441\u0443\u0442\u0440\u0430 \u0443] LT",nextWeek:function(){switch(this.day()){case 0:return"[\u0443] [\u043d\u0435\u0434\u0435\u0459\u0443] [\u0443] LT";case 3:return"[\u0443] [\u0441\u0440\u0435\u0434\u0443] [\u0443] LT";case 6:return"[\u0443] [\u0441\u0443\u0431\u043e\u0442\u0443] [\u0443] LT";case 1:case 2:case 4:case 5:return"[\u0443] dddd [\u0443] LT"}},lastDay:"[\u0458\u0443\u0447\u0435 \u0443] LT",lastWeek:function(){return["[\u043f\u0440\u043e\u0448\u043b\u0435] [\u043d\u0435\u0434\u0435\u0459\u0435] [\u0443] LT","[\u043f\u0440\u043e\u0448\u043b\u043e\u0433] [\u043f\u043e\u043d\u0435\u0434\u0435\u0459\u043a\u0430] [\u0443] LT","[\u043f\u0440\u043e\u0448\u043b\u043e\u0433] [\u0443\u0442\u043e\u0440\u043a\u0430] [\u0443] LT","[\u043f\u0440\u043e\u0448\u043b\u0435] [\u0441\u0440\u0435\u0434\u0435] [\u0443] LT","[\u043f\u0440\u043e\u0448\u043b\u043e\u0433] [\u0447\u0435\u0442\u0432\u0440\u0442\u043a\u0430] [\u0443] LT","[\u043f\u0440\u043e\u0448\u043b\u043e\u0433] [\u043f\u0435\u0442\u043a\u0430] [\u0443] LT","[\u043f\u0440\u043e\u0448\u043b\u0435] [\u0441\u0443\u0431\u043e\u0442\u0435] [\u0443] LT"][this.day()]},sameElse:"L"},relativeTime:{future:"\u0437\u0430 %s",past:"\u043f\u0440\u0435 %s",s:"\u043d\u0435\u043a\u043e\u043b\u0438\u043a\u043e \u0441\u0435\u043a\u0443\u043d\u0434\u0438",ss:W.translate,m:W.translate,mm:W.translate,h:W.translate,hh:W.translate,d:W.translate,dd:W.translate,M:W.translate,MM:W.translate,y:W.translate,yy:W.translate},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}}),{words:{ss:["sekunda","sekunde","sekundi"],m:["jedan minut","jednog minuta"],mm:["minut","minuta","minuta"],h:["jedan sat","jednog sata"],hh:["sat","sata","sati"],d:["jedan dan","jednog dana"],dd:["dan","dana","dana"],M:["jedan mesec","jednog meseca"],MM:["mesec","meseca","meseci"],y:["jednu godinu","jedne godine"],yy:["godinu","godine","godina"]},correctGrammaticalCase:function(e,a){return 1<=e%10&&e%10<=4&&(e%100<10||20<=e%100)?e%10==1?a[0]:a[1]:a[2]},translate:function(e,a,t,s){var n=A.words[t];return 1===t.length?"y"===t&&a?"jedna godina":s||a?n[0]:n[1]:(s=A.correctGrammaticalCase(e,n),"yy"===t&&a&&"godinu"===s?e+" godina":e+" "+s)}}),wn=(M.defineLocale("sr",{months:"januar_februar_mart_april_maj_jun_jul_avgust_septembar_oktobar_novembar_decembar".split("_"),monthsShort:"jan._feb._mar._apr._maj_jun_jul_avg._sep._okt._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"nedelja_ponedeljak_utorak_sreda_\u010detvrtak_petak_subota".split("_"),weekdaysShort:"ned._pon._uto._sre._\u010det._pet._sub.".split("_"),weekdaysMin:"ne_po_ut_sr_\u010de_pe_su".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"D. M. YYYY.",LL:"D. MMMM YYYY.",LLL:"D. MMMM YYYY. H:mm",LLLL:"dddd, D. MMMM YYYY. H:mm"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedelju] [u] LT";case 3:return"[u] [sredu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[ju\u010de u] LT",lastWeek:function(){return["[pro\u0161le] [nedelje] [u] LT","[pro\u0161log] [ponedeljka] [u] LT","[pro\u0161log] [utorka] [u] LT","[pro\u0161le] [srede] [u] LT","[pro\u0161log] [\u010detvrtka] [u] LT","[pro\u0161log] [petka] [u] LT","[pro\u0161le] [subote] [u] LT"][this.day()]},sameElse:"L"},relativeTime:{future:"za %s",past:"pre %s",s:"nekoliko sekundi",ss:A.translate,m:A.translate,mm:A.translate,h:A.translate,hh:A.translate,d:A.translate,dd:A.translate,M:A.translate,MM:A.translate,y:A.translate,yy:A.translate},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}}),M.defineLocale("ss",{months:"Bhimbidvwane_Indlovana_Indlov'lenkhulu_Mabasa_Inkhwekhweti_Inhlaba_Kholwane_Ingci_Inyoni_Imphala_Lweti_Ingongoni".split("_"),monthsShort:"Bhi_Ina_Inu_Mab_Ink_Inh_Kho_Igc_Iny_Imp_Lwe_Igo".split("_"),weekdays:"Lisontfo_Umsombuluko_Lesibili_Lesitsatfu_Lesine_Lesihlanu_Umgcibelo".split("_"),weekdaysShort:"Lis_Umb_Lsb_Les_Lsi_Lsh_Umg".split("_"),weekdaysMin:"Li_Us_Lb_Lt_Ls_Lh_Ug".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY h:mm A",LLLL:"dddd, D MMMM YYYY h:mm A"},calendar:{sameDay:"[Namuhla nga] LT",nextDay:"[Kusasa nga] LT",nextWeek:"dddd [nga] LT",lastDay:"[Itolo nga] LT",lastWeek:"dddd [leliphelile] [nga] LT",sameElse:"L"},relativeTime:{future:"nga %s",past:"wenteka nga %s",s:"emizuzwana lomcane",ss:"%d mzuzwana",m:"umzuzu",mm:"%d emizuzu",h:"lihora",hh:"%d emahora",d:"lilanga",dd:"%d emalanga",M:"inyanga",MM:"%d tinyanga",y:"umnyaka",yy:"%d iminyaka"},meridiemParse:/ekuseni|emini|entsambama|ebusuku/,meridiem:function(e,a,t){return e<11?"ekuseni":e<15?"emini":e<19?"entsambama":"ebusuku"},meridiemHour:function(e,a){return 12===e&&(e=0),"ekuseni"===a?e:"emini"===a?11<=e?e:e+12:"entsambama"===a||"ebusuku"===a?0===e?0:e+12:void 0},dayOfMonthOrdinalParse:/\d{1,2}/,ordinal:"%d",week:{dow:1,doy:4}}),M.defineLocale("sv",{months:"januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"s\xf6ndag_m\xe5ndag_tisdag_onsdag_torsdag_fredag_l\xf6rdag".split("_"),weekdaysShort:"s\xf6n_m\xe5n_tis_ons_tor_fre_l\xf6r".split("_"),weekdaysMin:"s\xf6_m\xe5_ti_on_to_fr_l\xf6".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [kl.] HH:mm",LLLL:"dddd D MMMM YYYY [kl.] HH:mm",lll:"D MMM YYYY HH:mm",llll:"ddd D MMM YYYY HH:mm"},calendar:{sameDay:"[Idag] LT",nextDay:"[Imorgon] LT",lastDay:"[Ig\xe5r] LT",nextWeek:"[P\xe5] dddd LT",lastWeek:"[I] dddd[s] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"f\xf6r %s sedan",s:"n\xe5gra sekunder",ss:"%d sekunder",m:"en minut",mm:"%d minuter",h:"en timme",hh:"%d timmar",d:"en dag",dd:"%d dagar",M:"en m\xe5nad",MM:"%d m\xe5nader",y:"ett \xe5r",yy:"%d \xe5r"},dayOfMonthOrdinalParse:/\d{1,2}(\:e|\:a)/,ordinal:function(e){var a=e%10;return e+(1!=~~(e%100/10)&&(1==a||2==a)?":a":":e")},week:{dow:1,doy:4}}),M.defineLocale("sw",{months:"Januari_Februari_Machi_Aprili_Mei_Juni_Julai_Agosti_Septemba_Oktoba_Novemba_Desemba".split("_"),monthsShort:"Jan_Feb_Mac_Apr_Mei_Jun_Jul_Ago_Sep_Okt_Nov_Des".split("_"),weekdays:"Jumapili_Jumatatu_Jumanne_Jumatano_Alhamisi_Ijumaa_Jumamosi".split("_"),weekdaysShort:"Jpl_Jtat_Jnne_Jtan_Alh_Ijm_Jmos".split("_"),weekdaysMin:"J2_J3_J4_J5_Al_Ij_J1".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"hh:mm A",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[leo saa] LT",nextDay:"[kesho saa] LT",nextWeek:"[wiki ijayo] dddd [saat] LT",lastDay:"[jana] LT",lastWeek:"[wiki iliyopita] dddd [saat] LT",sameElse:"L"},relativeTime:{future:"%s baadaye",past:"tokea %s",s:"hivi punde",ss:"sekunde %d",m:"dakika moja",mm:"dakika %d",h:"saa limoja",hh:"masaa %d",d:"siku moja",dd:"siku %d",M:"mwezi mmoja",MM:"miezi %d",y:"mwaka mmoja",yy:"miaka %d"},week:{dow:1,doy:7}}),{1:"\u0be7",2:"\u0be8",3:"\u0be9",4:"\u0bea",5:"\u0beb",6:"\u0bec",7:"\u0bed",8:"\u0bee",9:"\u0bef",0:"\u0be6"}),bn={"\u0be7":"1","\u0be8":"2","\u0be9":"3","\u0bea":"4","\u0beb":"5","\u0bec":"6","\u0bed":"7","\u0bee":"8","\u0bef":"9","\u0be6":"0"},Sn=(M.defineLocale("ta",{months:"\u0b9c\u0ba9\u0bb5\u0bb0\u0bbf_\u0baa\u0bbf\u0baa\u0bcd\u0bb0\u0bb5\u0bb0\u0bbf_\u0bae\u0bbe\u0bb0\u0bcd\u0b9a\u0bcd_\u0b8f\u0baa\u0bcd\u0bb0\u0bb2\u0bcd_\u0bae\u0bc7_\u0b9c\u0bc2\u0ba9\u0bcd_\u0b9c\u0bc2\u0bb2\u0bc8_\u0b86\u0b95\u0bb8\u0bcd\u0b9f\u0bcd_\u0b9a\u0bc6\u0baa\u0bcd\u0b9f\u0bc6\u0bae\u0bcd\u0baa\u0bb0\u0bcd_\u0b85\u0b95\u0bcd\u0b9f\u0bc7\u0bbe\u0baa\u0bb0\u0bcd_\u0ba8\u0bb5\u0bae\u0bcd\u0baa\u0bb0\u0bcd_\u0b9f\u0bbf\u0b9a\u0bae\u0bcd\u0baa\u0bb0\u0bcd".split("_"),monthsShort:"\u0b9c\u0ba9\u0bb5\u0bb0\u0bbf_\u0baa\u0bbf\u0baa\u0bcd\u0bb0\u0bb5\u0bb0\u0bbf_\u0bae\u0bbe\u0bb0\u0bcd\u0b9a\u0bcd_\u0b8f\u0baa\u0bcd\u0bb0\u0bb2\u0bcd_\u0bae\u0bc7_\u0b9c\u0bc2\u0ba9\u0bcd_\u0b9c\u0bc2\u0bb2\u0bc8_\u0b86\u0b95\u0bb8\u0bcd\u0b9f\u0bcd_\u0b9a\u0bc6\u0baa\u0bcd\u0b9f\u0bc6\u0bae\u0bcd\u0baa\u0bb0\u0bcd_\u0b85\u0b95\u0bcd\u0b9f\u0bc7\u0bbe\u0baa\u0bb0\u0bcd_\u0ba8\u0bb5\u0bae\u0bcd\u0baa\u0bb0\u0bcd_\u0b9f\u0bbf\u0b9a\u0bae\u0bcd\u0baa\u0bb0\u0bcd".split("_"),weekdays:"\u0b9e\u0bbe\u0baf\u0bbf\u0bb1\u0bcd\u0bb1\u0bc1\u0b95\u0bcd\u0b95\u0bbf\u0bb4\u0bae\u0bc8_\u0ba4\u0bbf\u0b99\u0bcd\u0b95\u0b9f\u0bcd\u0b95\u0bbf\u0bb4\u0bae\u0bc8_\u0b9a\u0bc6\u0bb5\u0bcd\u0bb5\u0bbe\u0baf\u0bcd\u0b95\u0bbf\u0bb4\u0bae\u0bc8_\u0baa\u0bc1\u0ba4\u0ba9\u0bcd\u0b95\u0bbf\u0bb4\u0bae\u0bc8_\u0bb5\u0bbf\u0baf\u0bbe\u0bb4\u0b95\u0bcd\u0b95\u0bbf\u0bb4\u0bae\u0bc8_\u0bb5\u0bc6\u0bb3\u0bcd\u0bb3\u0bbf\u0b95\u0bcd\u0b95\u0bbf\u0bb4\u0bae\u0bc8_\u0b9a\u0ba9\u0bbf\u0b95\u0bcd\u0b95\u0bbf\u0bb4\u0bae\u0bc8".split("_"),weekdaysShort:"\u0b9e\u0bbe\u0baf\u0bbf\u0bb1\u0bc1_\u0ba4\u0bbf\u0b99\u0bcd\u0b95\u0bb3\u0bcd_\u0b9a\u0bc6\u0bb5\u0bcd\u0bb5\u0bbe\u0baf\u0bcd_\u0baa\u0bc1\u0ba4\u0ba9\u0bcd_\u0bb5\u0bbf\u0baf\u0bbe\u0bb4\u0ba9\u0bcd_\u0bb5\u0bc6\u0bb3\u0bcd\u0bb3\u0bbf_\u0b9a\u0ba9\u0bbf".split("_"),weekdaysMin:"\u0b9e\u0bbe_\u0ba4\u0bbf_\u0b9a\u0bc6_\u0baa\u0bc1_\u0bb5\u0bbf_\u0bb5\u0bc6_\u0b9a".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, HH:mm",LLLL:"dddd, D MMMM YYYY, HH:mm"},calendar:{sameDay:"[\u0b87\u0ba9\u0bcd\u0bb1\u0bc1] LT",nextDay:"[\u0ba8\u0bbe\u0bb3\u0bc8] LT",nextWeek:"dddd, LT",lastDay:"[\u0ba8\u0bc7\u0bb1\u0bcd\u0bb1\u0bc1] LT",lastWeek:"[\u0b95\u0b9f\u0ba8\u0bcd\u0ba4 \u0bb5\u0bbe\u0bb0\u0bae\u0bcd] dddd, LT",sameElse:"L"},relativeTime:{future:"%s \u0b87\u0bb2\u0bcd",past:"%s \u0bae\u0bc1\u0ba9\u0bcd",s:"\u0b92\u0bb0\u0bc1 \u0b9a\u0bbf\u0bb2 \u0bb5\u0bbf\u0ba8\u0bbe\u0b9f\u0bbf\u0b95\u0bb3\u0bcd",ss:"%d \u0bb5\u0bbf\u0ba8\u0bbe\u0b9f\u0bbf\u0b95\u0bb3\u0bcd",m:"\u0b92\u0bb0\u0bc1 \u0ba8\u0bbf\u0bae\u0bbf\u0b9f\u0bae\u0bcd",mm:"%d \u0ba8\u0bbf\u0bae\u0bbf\u0b9f\u0b99\u0bcd\u0b95\u0bb3\u0bcd",h:"\u0b92\u0bb0\u0bc1 \u0bae\u0ba3\u0bbf \u0ba8\u0bc7\u0bb0\u0bae\u0bcd",hh:"%d \u0bae\u0ba3\u0bbf \u0ba8\u0bc7\u0bb0\u0bae\u0bcd",d:"\u0b92\u0bb0\u0bc1 \u0ba8\u0bbe\u0bb3\u0bcd",dd:"%d \u0ba8\u0bbe\u0b9f\u0bcd\u0b95\u0bb3\u0bcd",M:"\u0b92\u0bb0\u0bc1 \u0bae\u0bbe\u0ba4\u0bae\u0bcd",MM:"%d \u0bae\u0bbe\u0ba4\u0b99\u0bcd\u0b95\u0bb3\u0bcd",y:"\u0b92\u0bb0\u0bc1 \u0bb5\u0bb0\u0bc1\u0b9f\u0bae\u0bcd",yy:"%d \u0b86\u0ba3\u0bcd\u0b9f\u0bc1\u0b95\u0bb3\u0bcd"},dayOfMonthOrdinalParse:/\d{1,2}\u0bb5\u0ba4\u0bc1/,ordinal:function(e){return e+"\u0bb5\u0ba4\u0bc1"},preparse:function(e){return e.replace(/[\u0be7\u0be8\u0be9\u0bea\u0beb\u0bec\u0bed\u0bee\u0bef\u0be6]/g,function(e){return bn[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return wn[e]})},meridiemParse:/\u0baf\u0bbe\u0bae\u0bae\u0bcd|\u0bb5\u0bc8\u0b95\u0bb1\u0bc8|\u0b95\u0bbe\u0bb2\u0bc8|\u0ba8\u0ba3\u0bcd\u0baa\u0b95\u0bb2\u0bcd|\u0b8e\u0bb1\u0bcd\u0baa\u0bbe\u0b9f\u0bc1|\u0bae\u0bbe\u0bb2\u0bc8/,meridiem:function(e,a,t){return e<2?" \u0baf\u0bbe\u0bae\u0bae\u0bcd":e<6?" \u0bb5\u0bc8\u0b95\u0bb1\u0bc8":e<10?" \u0b95\u0bbe\u0bb2\u0bc8":e<14?" \u0ba8\u0ba3\u0bcd\u0baa\u0b95\u0bb2\u0bcd":e<18?" \u0b8e\u0bb1\u0bcd\u0baa\u0bbe\u0b9f\u0bc1":e<22?" \u0bae\u0bbe\u0bb2\u0bc8":" \u0baf\u0bbe\u0bae\u0bae\u0bcd"},meridiemHour:function(e,a){return 12===e&&(e=0),"\u0baf\u0bbe\u0bae\u0bae\u0bcd"===a?e<2?e:e+12:"\u0bb5\u0bc8\u0b95\u0bb1\u0bc8"===a||"\u0b95\u0bbe\u0bb2\u0bc8"===a||"\u0ba8\u0ba3\u0bcd\u0baa\u0b95\u0bb2\u0bcd"===a&&10<=e?e:e+12},week:{dow:0,doy:6}}),M.defineLocale("te",{months:"\u0c1c\u0c28\u0c35\u0c30\u0c3f_\u0c2b\u0c3f\u0c2c\u0c4d\u0c30\u0c35\u0c30\u0c3f_\u0c2e\u0c3e\u0c30\u0c4d\u0c1a\u0c3f_\u0c0f\u0c2a\u0c4d\u0c30\u0c3f\u0c32\u0c4d_\u0c2e\u0c47_\u0c1c\u0c42\u0c28\u0c4d_\u0c1c\u0c41\u0c32\u0c48_\u0c06\u0c17\u0c38\u0c4d\u0c1f\u0c41_\u0c38\u0c46\u0c2a\u0c4d\u0c1f\u0c46\u0c02\u0c2c\u0c30\u0c4d_\u0c05\u0c15\u0c4d\u0c1f\u0c4b\u0c2c\u0c30\u0c4d_\u0c28\u0c35\u0c02\u0c2c\u0c30\u0c4d_\u0c21\u0c3f\u0c38\u0c46\u0c02\u0c2c\u0c30\u0c4d".split("_"),monthsShort:"\u0c1c\u0c28._\u0c2b\u0c3f\u0c2c\u0c4d\u0c30._\u0c2e\u0c3e\u0c30\u0c4d\u0c1a\u0c3f_\u0c0f\u0c2a\u0c4d\u0c30\u0c3f._\u0c2e\u0c47_\u0c1c\u0c42\u0c28\u0c4d_\u0c1c\u0c41\u0c32\u0c48_\u0c06\u0c17._\u0c38\u0c46\u0c2a\u0c4d._\u0c05\u0c15\u0c4d\u0c1f\u0c4b._\u0c28\u0c35._\u0c21\u0c3f\u0c38\u0c46.".split("_"),monthsParseExact:!0,weekdays:"\u0c06\u0c26\u0c3f\u0c35\u0c3e\u0c30\u0c02_\u0c38\u0c4b\u0c2e\u0c35\u0c3e\u0c30\u0c02_\u0c2e\u0c02\u0c17\u0c33\u0c35\u0c3e\u0c30\u0c02_\u0c2c\u0c41\u0c27\u0c35\u0c3e\u0c30\u0c02_\u0c17\u0c41\u0c30\u0c41\u0c35\u0c3e\u0c30\u0c02_\u0c36\u0c41\u0c15\u0c4d\u0c30\u0c35\u0c3e\u0c30\u0c02_\u0c36\u0c28\u0c3f\u0c35\u0c3e\u0c30\u0c02".split("_"),weekdaysShort:"\u0c06\u0c26\u0c3f_\u0c38\u0c4b\u0c2e_\u0c2e\u0c02\u0c17\u0c33_\u0c2c\u0c41\u0c27_\u0c17\u0c41\u0c30\u0c41_\u0c36\u0c41\u0c15\u0c4d\u0c30_\u0c36\u0c28\u0c3f".split("_"),weekdaysMin:"\u0c06_\u0c38\u0c4b_\u0c2e\u0c02_\u0c2c\u0c41_\u0c17\u0c41_\u0c36\u0c41_\u0c36".split("_"),longDateFormat:{LT:"A h:mm",LTS:"A h:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm",LLLL:"dddd, D MMMM YYYY, A h:mm"},calendar:{sameDay:"[\u0c28\u0c47\u0c21\u0c41] LT",nextDay:"[\u0c30\u0c47\u0c2a\u0c41] LT",nextWeek:"dddd, LT",lastDay:"[\u0c28\u0c3f\u0c28\u0c4d\u0c28] LT",lastWeek:"[\u0c17\u0c24] dddd, LT",sameElse:"L"},relativeTime:{future:"%s \u0c32\u0c4b",past:"%s \u0c15\u0c4d\u0c30\u0c3f\u0c24\u0c02",s:"\u0c15\u0c4a\u0c28\u0c4d\u0c28\u0c3f \u0c15\u0c4d\u0c37\u0c23\u0c3e\u0c32\u0c41",ss:"%d \u0c38\u0c46\u0c15\u0c28\u0c4d\u0c32\u0c41",m:"\u0c12\u0c15 \u0c28\u0c3f\u0c2e\u0c3f\u0c37\u0c02",mm:"%d \u0c28\u0c3f\u0c2e\u0c3f\u0c37\u0c3e\u0c32\u0c41",h:"\u0c12\u0c15 \u0c17\u0c02\u0c1f",hh:"%d \u0c17\u0c02\u0c1f\u0c32\u0c41",d:"\u0c12\u0c15 \u0c30\u0c4b\u0c1c\u0c41",dd:"%d \u0c30\u0c4b\u0c1c\u0c41\u0c32\u0c41",M:"\u0c12\u0c15 \u0c28\u0c46\u0c32",MM:"%d \u0c28\u0c46\u0c32\u0c32\u0c41",y:"\u0c12\u0c15 \u0c38\u0c02\u0c35\u0c24\u0c4d\u0c38\u0c30\u0c02",yy:"%d \u0c38\u0c02\u0c35\u0c24\u0c4d\u0c38\u0c30\u0c3e\u0c32\u0c41"},dayOfMonthOrdinalParse:/\d{1,2}\u0c35/,ordinal:"%d\u0c35",meridiemParse:/\u0c30\u0c3e\u0c24\u0c4d\u0c30\u0c3f|\u0c09\u0c26\u0c2f\u0c02|\u0c2e\u0c27\u0c4d\u0c2f\u0c3e\u0c39\u0c4d\u0c28\u0c02|\u0c38\u0c3e\u0c2f\u0c02\u0c24\u0c4d\u0c30\u0c02/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u0c30\u0c3e\u0c24\u0c4d\u0c30\u0c3f"===a?e<4?e:e+12:"\u0c09\u0c26\u0c2f\u0c02"===a?e:"\u0c2e\u0c27\u0c4d\u0c2f\u0c3e\u0c39\u0c4d\u0c28\u0c02"===a?10<=e?e:e+12:"\u0c38\u0c3e\u0c2f\u0c02\u0c24\u0c4d\u0c30\u0c02"===a?e+12:void 0},meridiem:function(e,a,t){return e<4?"\u0c30\u0c3e\u0c24\u0c4d\u0c30\u0c3f":e<10?"\u0c09\u0c26\u0c2f\u0c02":e<17?"\u0c2e\u0c27\u0c4d\u0c2f\u0c3e\u0c39\u0c4d\u0c28\u0c02":e<20?"\u0c38\u0c3e\u0c2f\u0c02\u0c24\u0c4d\u0c30\u0c02":"\u0c30\u0c3e\u0c24\u0c4d\u0c30\u0c3f"},week:{dow:0,doy:6}}),M.defineLocale("tet",{months:"Janeiru_Fevereiru_Marsu_Abril_Maiu_Ju\xf1u_Jullu_Agustu_Setembru_Outubru_Novembru_Dezembru".split("_"),monthsShort:"Jan_Fev_Mar_Abr_Mai_Jun_Jul_Ago_Set_Out_Nov_Dez".split("_"),weekdays:"Domingu_Segunda_Tersa_Kuarta_Kinta_Sesta_Sabadu".split("_"),weekdaysShort:"Dom_Seg_Ters_Kua_Kint_Sest_Sab".split("_"),weekdaysMin:"Do_Seg_Te_Ku_Ki_Ses_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Ohin iha] LT",nextDay:"[Aban iha] LT",nextWeek:"dddd [iha] LT",lastDay:"[Horiseik iha] LT",lastWeek:"dddd [semana kotuk] [iha] LT",sameElse:"L"},relativeTime:{future:"iha %s",past:"%s liuba",s:"segundu balun",ss:"segundu %d",m:"minutu ida",mm:"minutu %d",h:"oras ida",hh:"oras %d",d:"loron ida",dd:"loron %d",M:"fulan ida",MM:"fulan %d",y:"tinan ida",yy:"tinan %d"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var a=e%10;return e+(1==~~(e%100/10)?"th":1==a?"st":2==a?"nd":3==a?"rd":"th")},week:{dow:1,doy:4}}),{0:"-\u0443\u043c",1:"-\u0443\u043c",2:"-\u044e\u043c",3:"-\u044e\u043c",4:"-\u0443\u043c",5:"-\u0443\u043c",6:"-\u0443\u043c",7:"-\u0443\u043c",8:"-\u0443\u043c",9:"-\u0443\u043c",10:"-\u0443\u043c",12:"-\u0443\u043c",13:"-\u0443\u043c",20:"-\u0443\u043c",30:"-\u044e\u043c",40:"-\u0443\u043c",50:"-\u0443\u043c",60:"-\u0443\u043c",70:"-\u0443\u043c",80:"-\u0443\u043c",90:"-\u0443\u043c",100:"-\u0443\u043c"}),Hn=(M.defineLocale("tg",{months:{format:"\u044f\u043d\u0432\u0430\u0440\u0438_\u0444\u0435\u0432\u0440\u0430\u043b\u0438_\u043c\u0430\u0440\u0442\u0438_\u0430\u043f\u0440\u0435\u043b\u0438_\u043c\u0430\u0439\u0438_\u0438\u044e\u043d\u0438_\u0438\u044e\u043b\u0438_\u0430\u0432\u0433\u0443\u0441\u0442\u0438_\u0441\u0435\u043d\u0442\u044f\u0431\u0440\u0438_\u043e\u043a\u0442\u044f\u0431\u0440\u0438_\u043d\u043e\u044f\u0431\u0440\u0438_\u0434\u0435\u043a\u0430\u0431\u0440\u0438".split("_"),standalone:"\u044f\u043d\u0432\u0430\u0440_\u0444\u0435\u0432\u0440\u0430\u043b_\u043c\u0430\u0440\u0442_\u0430\u043f\u0440\u0435\u043b_\u043c\u0430\u0439_\u0438\u044e\u043d_\u0438\u044e\u043b_\u0430\u0432\u0433\u0443\u0441\u0442_\u0441\u0435\u043d\u0442\u044f\u0431\u0440_\u043e\u043a\u0442\u044f\u0431\u0440_\u043d\u043e\u044f\u0431\u0440_\u0434\u0435\u043a\u0430\u0431\u0440".split("_")},monthsShort:"\u044f\u043d\u0432_\u0444\u0435\u0432_\u043c\u0430\u0440_\u0430\u043f\u0440_\u043c\u0430\u0439_\u0438\u044e\u043d_\u0438\u044e\u043b_\u0430\u0432\u0433_\u0441\u0435\u043d_\u043e\u043a\u0442_\u043d\u043e\u044f_\u0434\u0435\u043a".split("_"),weekdays:"\u044f\u043a\u0448\u0430\u043d\u0431\u0435_\u0434\u0443\u0448\u0430\u043d\u0431\u0435_\u0441\u0435\u0448\u0430\u043d\u0431\u0435_\u0447\u043e\u0440\u0448\u0430\u043d\u0431\u0435_\u043f\u0430\u043d\u04b7\u0448\u0430\u043d\u0431\u0435_\u04b7\u0443\u043c\u044a\u0430_\u0448\u0430\u043d\u0431\u0435".split("_"),weekdaysShort:"\u044f\u0448\u0431_\u0434\u0448\u0431_\u0441\u0448\u0431_\u0447\u0448\u0431_\u043f\u0448\u0431_\u04b7\u0443\u043c_\u0448\u043d\u0431".split("_"),weekdaysMin:"\u044f\u0448_\u0434\u0448_\u0441\u0448_\u0447\u0448_\u043f\u0448_\u04b7\u043c_\u0448\u0431".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[\u0418\u043c\u0440\u04ef\u0437 \u0441\u043e\u0430\u0442\u0438] LT",nextDay:"[\u0424\u0430\u0440\u0434\u043e \u0441\u043e\u0430\u0442\u0438] LT",lastDay:"[\u0414\u0438\u0440\u04ef\u0437 \u0441\u043e\u0430\u0442\u0438] LT",nextWeek:"dddd[\u0438] [\u04b3\u0430\u0444\u0442\u0430\u0438 \u043e\u044f\u043d\u0434\u0430 \u0441\u043e\u0430\u0442\u0438] LT",lastWeek:"dddd[\u0438] [\u04b3\u0430\u0444\u0442\u0430\u0438 \u0433\u0443\u0437\u0430\u0448\u0442\u0430 \u0441\u043e\u0430\u0442\u0438] LT",sameElse:"L"},relativeTime:{future:"\u0431\u0430\u044a\u0434\u0438 %s",past:"%s \u043f\u0435\u0448",s:"\u044f\u043a\u0447\u0430\u043d\u0434 \u0441\u043e\u043d\u0438\u044f",m:"\u044f\u043a \u0434\u0430\u049b\u0438\u049b\u0430",mm:"%d \u0434\u0430\u049b\u0438\u049b\u0430",h:"\u044f\u043a \u0441\u043e\u0430\u0442",hh:"%d \u0441\u043e\u0430\u0442",d:"\u044f\u043a \u0440\u04ef\u0437",dd:"%d \u0440\u04ef\u0437",M:"\u044f\u043a \u043c\u043e\u04b3",MM:"%d \u043c\u043e\u04b3",y:"\u044f\u043a \u0441\u043e\u043b",yy:"%d \u0441\u043e\u043b"},meridiemParse:/\u0448\u0430\u0431|\u0441\u0443\u0431\u04b3|\u0440\u04ef\u0437|\u0431\u0435\u0433\u043e\u04b3/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u0448\u0430\u0431"===a?e<4?e:e+12:"\u0441\u0443\u0431\u04b3"===a?e:"\u0440\u04ef\u0437"===a?11<=e?e:e+12:"\u0431\u0435\u0433\u043e\u04b3"===a?e+12:void 0},meridiem:function(e,a,t){return e<4?"\u0448\u0430\u0431":e<11?"\u0441\u0443\u0431\u04b3":e<16?"\u0440\u04ef\u0437":e<19?"\u0431\u0435\u0433\u043e\u04b3":"\u0448\u0430\u0431"},dayOfMonthOrdinalParse:/\d{1,2}-(\u0443\u043c|\u044e\u043c)/,ordinal:function(e){return e+(Sn[e]||Sn[e%10]||Sn[100<=e?100:null])},week:{dow:1,doy:7}}),M.defineLocale("th",{months:"\u0e21\u0e01\u0e23\u0e32\u0e04\u0e21_\u0e01\u0e38\u0e21\u0e20\u0e32\u0e1e\u0e31\u0e19\u0e18\u0e4c_\u0e21\u0e35\u0e19\u0e32\u0e04\u0e21_\u0e40\u0e21\u0e29\u0e32\u0e22\u0e19_\u0e1e\u0e24\u0e29\u0e20\u0e32\u0e04\u0e21_\u0e21\u0e34\u0e16\u0e38\u0e19\u0e32\u0e22\u0e19_\u0e01\u0e23\u0e01\u0e0e\u0e32\u0e04\u0e21_\u0e2a\u0e34\u0e07\u0e2b\u0e32\u0e04\u0e21_\u0e01\u0e31\u0e19\u0e22\u0e32\u0e22\u0e19_\u0e15\u0e38\u0e25\u0e32\u0e04\u0e21_\u0e1e\u0e24\u0e28\u0e08\u0e34\u0e01\u0e32\u0e22\u0e19_\u0e18\u0e31\u0e19\u0e27\u0e32\u0e04\u0e21".split("_"),monthsShort:"\u0e21.\u0e04._\u0e01.\u0e1e._\u0e21\u0e35.\u0e04._\u0e40\u0e21.\u0e22._\u0e1e.\u0e04._\u0e21\u0e34.\u0e22._\u0e01.\u0e04._\u0e2a.\u0e04._\u0e01.\u0e22._\u0e15.\u0e04._\u0e1e.\u0e22._\u0e18.\u0e04.".split("_"),monthsParseExact:!0,weekdays:"\u0e2d\u0e32\u0e17\u0e34\u0e15\u0e22\u0e4c_\u0e08\u0e31\u0e19\u0e17\u0e23\u0e4c_\u0e2d\u0e31\u0e07\u0e04\u0e32\u0e23_\u0e1e\u0e38\u0e18_\u0e1e\u0e24\u0e2b\u0e31\u0e2a\u0e1a\u0e14\u0e35_\u0e28\u0e38\u0e01\u0e23\u0e4c_\u0e40\u0e2a\u0e32\u0e23\u0e4c".split("_"),weekdaysShort:"\u0e2d\u0e32\u0e17\u0e34\u0e15\u0e22\u0e4c_\u0e08\u0e31\u0e19\u0e17\u0e23\u0e4c_\u0e2d\u0e31\u0e07\u0e04\u0e32\u0e23_\u0e1e\u0e38\u0e18_\u0e1e\u0e24\u0e2b\u0e31\u0e2a_\u0e28\u0e38\u0e01\u0e23\u0e4c_\u0e40\u0e2a\u0e32\u0e23\u0e4c".split("_"),weekdaysMin:"\u0e2d\u0e32._\u0e08._\u0e2d._\u0e1e._\u0e1e\u0e24._\u0e28._\u0e2a.".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY \u0e40\u0e27\u0e25\u0e32 H:mm",LLLL:"\u0e27\u0e31\u0e19dddd\u0e17\u0e35\u0e48 D MMMM YYYY \u0e40\u0e27\u0e25\u0e32 H:mm"},meridiemParse:/\u0e01\u0e48\u0e2d\u0e19\u0e40\u0e17\u0e35\u0e48\u0e22\u0e07|\u0e2b\u0e25\u0e31\u0e07\u0e40\u0e17\u0e35\u0e48\u0e22\u0e07/,isPM:function(e){return"\u0e2b\u0e25\u0e31\u0e07\u0e40\u0e17\u0e35\u0e48\u0e22\u0e07"===e},meridiem:function(e,a,t){return e<12?"\u0e01\u0e48\u0e2d\u0e19\u0e40\u0e17\u0e35\u0e48\u0e22\u0e07":"\u0e2b\u0e25\u0e31\u0e07\u0e40\u0e17\u0e35\u0e48\u0e22\u0e07"},calendar:{sameDay:"[\u0e27\u0e31\u0e19\u0e19\u0e35\u0e49 \u0e40\u0e27\u0e25\u0e32] LT",nextDay:"[\u0e1e\u0e23\u0e38\u0e48\u0e07\u0e19\u0e35\u0e49 \u0e40\u0e27\u0e25\u0e32] LT",nextWeek:"dddd[\u0e2b\u0e19\u0e49\u0e32 \u0e40\u0e27\u0e25\u0e32] LT",lastDay:"[\u0e40\u0e21\u0e37\u0e48\u0e2d\u0e27\u0e32\u0e19\u0e19\u0e35\u0e49 \u0e40\u0e27\u0e25\u0e32] LT",lastWeek:"[\u0e27\u0e31\u0e19]dddd[\u0e17\u0e35\u0e48\u0e41\u0e25\u0e49\u0e27 \u0e40\u0e27\u0e25\u0e32] LT",sameElse:"L"},relativeTime:{future:"\u0e2d\u0e35\u0e01 %s",past:"%s\u0e17\u0e35\u0e48\u0e41\u0e25\u0e49\u0e27",s:"\u0e44\u0e21\u0e48\u0e01\u0e35\u0e48\u0e27\u0e34\u0e19\u0e32\u0e17\u0e35",ss:"%d \u0e27\u0e34\u0e19\u0e32\u0e17\u0e35",m:"1 \u0e19\u0e32\u0e17\u0e35",mm:"%d \u0e19\u0e32\u0e17\u0e35",h:"1 \u0e0a\u0e31\u0e48\u0e27\u0e42\u0e21\u0e07",hh:"%d \u0e0a\u0e31\u0e48\u0e27\u0e42\u0e21\u0e07",d:"1 \u0e27\u0e31\u0e19",dd:"%d \u0e27\u0e31\u0e19",w:"1 \u0e2a\u0e31\u0e1b\u0e14\u0e32\u0e2b\u0e4c",ww:"%d \u0e2a\u0e31\u0e1b\u0e14\u0e32\u0e2b\u0e4c",M:"1 \u0e40\u0e14\u0e37\u0e2d\u0e19",MM:"%d \u0e40\u0e14\u0e37\u0e2d\u0e19",y:"1 \u0e1b\u0e35",yy:"%d \u0e1b\u0e35"}}),{1:"'inji",5:"'inji",8:"'inji",70:"'inji",80:"'inji",2:"'nji",7:"'nji",20:"'nji",50:"'nji",3:"'\xfcnji",4:"'\xfcnji",100:"'\xfcnji",6:"'njy",9:"'unjy",10:"'unjy",30:"'unjy",60:"'ynjy",90:"'ynjy"}),vn=(M.defineLocale("tk",{months:"\xddanwar_Fewral_Mart_Aprel_Ma\xfd_I\xfdun_I\xfdul_Awgust_Sent\xfdabr_Okt\xfdabr_No\xfdabr_Dekabr".split("_"),monthsShort:"\xddan_Few_Mar_Apr_Ma\xfd_I\xfdn_I\xfdl_Awg_Sen_Okt_No\xfd_Dek".split("_"),weekdays:"\xddek\u015fenbe_Du\u015fenbe_Si\u015fenbe_\xc7ar\u015fenbe_Pen\u015fenbe_Anna_\u015eenbe".split("_"),weekdaysShort:"\xddek_Du\u015f_Si\u015f_\xc7ar_Pen_Ann_\u015een".split("_"),weekdaysMin:"\xddk_D\u015f_S\u015f_\xc7r_Pn_An_\u015en".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[bug\xfcn sagat] LT",nextDay:"[ertir sagat] LT",nextWeek:"[indiki] dddd [sagat] LT",lastDay:"[d\xfc\xfdn] LT",lastWeek:"[ge\xe7en] dddd [sagat] LT",sameElse:"L"},relativeTime:{future:"%s so\u0148",past:"%s \xf6\u0148",s:"birn\xe4\xe7e sekunt",m:"bir minut",mm:"%d minut",h:"bir sagat",hh:"%d sagat",d:"bir g\xfcn",dd:"%d g\xfcn",M:"bir a\xfd",MM:"%d a\xfd",y:"bir \xfdyl",yy:"%d \xfdyl"},ordinal:function(e,a){switch(a){case"d":case"D":case"Do":case"DD":return e;default:if(0===e)return e+"'unjy";var t=e%10;return e+(Hn[t]||Hn[e%100-t]||Hn[100<=e?100:null])}},week:{dow:1,doy:7}}),M.defineLocale("tl-ph",{months:"Enero_Pebrero_Marso_Abril_Mayo_Hunyo_Hulyo_Agosto_Setyembre_Oktubre_Nobyembre_Disyembre".split("_"),monthsShort:"Ene_Peb_Mar_Abr_May_Hun_Hul_Ago_Set_Okt_Nob_Dis".split("_"),weekdays:"Linggo_Lunes_Martes_Miyerkules_Huwebes_Biyernes_Sabado".split("_"),weekdaysShort:"Lin_Lun_Mar_Miy_Huw_Biy_Sab".split("_"),weekdaysMin:"Li_Lu_Ma_Mi_Hu_Bi_Sab".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"MM/D/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY HH:mm",LLLL:"dddd, MMMM DD, YYYY HH:mm"},calendar:{sameDay:"LT [ngayong araw]",nextDay:"[Bukas ng] LT",nextWeek:"LT [sa susunod na] dddd",lastDay:"LT [kahapon]",lastWeek:"LT [noong nakaraang] dddd",sameElse:"L"},relativeTime:{future:"sa loob ng %s",past:"%s ang nakalipas",s:"ilang segundo",ss:"%d segundo",m:"isang minuto",mm:"%d minuto",h:"isang oras",hh:"%d oras",d:"isang araw",dd:"%d araw",M:"isang buwan",MM:"%d buwan",y:"isang taon",yy:"%d taon"},dayOfMonthOrdinalParse:/\d{1,2}/,ordinal:function(e){return e},week:{dow:1,doy:4}}),"pagh_wa\u2019_cha\u2019_wej_loS_vagh_jav_Soch_chorgh_Hut".split("_"));function jn(e,a,t,s){var n=function(e){var a=Math.floor(e%1e3/100),t=Math.floor(e%100/10),e=e%10,s="";0<a&&(s+=vn[a]+"vatlh");0<t&&(s+=(""!==s?" ":"")+vn[t]+"maH");0<e&&(s+=(""!==s?" ":"")+vn[e]);return""===s?"pagh":s}(e);switch(t){case"ss":return n+" lup";case"mm":return n+" tup";case"hh":return n+" rep";case"dd":return n+" jaj";case"MM":return n+" jar";case"yy":return n+" DIS"}}M.defineLocale("tlh",{months:"tera\u2019 jar wa\u2019_tera\u2019 jar cha\u2019_tera\u2019 jar wej_tera\u2019 jar loS_tera\u2019 jar vagh_tera\u2019 jar jav_tera\u2019 jar Soch_tera\u2019 jar chorgh_tera\u2019 jar Hut_tera\u2019 jar wa\u2019maH_tera\u2019 jar wa\u2019maH wa\u2019_tera\u2019 jar wa\u2019maH cha\u2019".split("_"),monthsShort:"jar wa\u2019_jar cha\u2019_jar wej_jar loS_jar vagh_jar jav_jar Soch_jar chorgh_jar Hut_jar wa\u2019maH_jar wa\u2019maH wa\u2019_jar wa\u2019maH cha\u2019".split("_"),monthsParseExact:!0,weekdays:"lojmItjaj_DaSjaj_povjaj_ghItlhjaj_loghjaj_buqjaj_ghInjaj".split("_"),weekdaysShort:"lojmItjaj_DaSjaj_povjaj_ghItlhjaj_loghjaj_buqjaj_ghInjaj".split("_"),weekdaysMin:"lojmItjaj_DaSjaj_povjaj_ghItlhjaj_loghjaj_buqjaj_ghInjaj".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[DaHjaj] LT",nextDay:"[wa\u2019leS] LT",nextWeek:"LLL",lastDay:"[wa\u2019Hu\u2019] LT",lastWeek:"LLL",sameElse:"L"},relativeTime:{future:function(e){var a=e;return a=-1!==e.indexOf("jaj")?a.slice(0,-3)+"leS":-1!==e.indexOf("jar")?a.slice(0,-3)+"waQ":-1!==e.indexOf("DIS")?a.slice(0,-3)+"nem":a+" pIq"},past:function(e){var a=e;return a=-1!==e.indexOf("jaj")?a.slice(0,-3)+"Hu\u2019":-1!==e.indexOf("jar")?a.slice(0,-3)+"wen":-1!==e.indexOf("DIS")?a.slice(0,-3)+"ben":a+" ret"},s:"puS lup",ss:jn,m:"wa\u2019 tup",mm:jn,h:"wa\u2019 rep",hh:jn,d:"wa\u2019 jaj",dd:jn,M:"wa\u2019 jar",MM:jn,y:"wa\u2019 DIS",yy:jn},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}});var xn={1:"'inci",5:"'inci",8:"'inci",70:"'inci",80:"'inci",2:"'nci",7:"'nci",20:"'nci",50:"'nci",3:"'\xfcnc\xfc",4:"'\xfcnc\xfc",100:"'\xfcnc\xfc",6:"'nc\u0131",9:"'uncu",10:"'uncu",30:"'uncu",60:"'\u0131nc\u0131",90:"'\u0131nc\u0131"};function Pn(e,a,t,s){e={s:["viensas secunds","'iensas secunds"],ss:[e+" secunds",e+" secunds"],m:["'n m\xedut","'iens m\xedut"],mm:[e+" m\xeduts",e+" m\xeduts"],h:["'n \xfeora","'iensa \xfeora"],hh:[e+" \xfeoras",e+" \xfeoras"],d:["'n ziua","'iensa ziua"],dd:[e+" ziuas",e+" ziuas"],M:["'n mes","'iens mes"],MM:[e+" mesen",e+" mesen"],y:["'n ar","'iens ar"],yy:[e+" ars",e+" ars"]};return s||a?e[t][0]:e[t][1]}function On(e,a,t){return"m"===t?a?"\u0445\u0432\u0438\u043b\u0438\u043d\u0430":"\u0445\u0432\u0438\u043b\u0438\u043d\u0443":"h"===t?a?"\u0433\u043e\u0434\u0438\u043d\u0430":"\u0433\u043e\u0434\u0438\u043d\u0443":e+" "+(e=+e,a=(a={ss:a?"\u0441\u0435\u043a\u0443\u043d\u0434\u0430_\u0441\u0435\u043a\u0443\u043d\u0434\u0438_\u0441\u0435\u043a\u0443\u043d\u0434":"\u0441\u0435\u043a\u0443\u043d\u0434\u0443_\u0441\u0435\u043a\u0443\u043d\u0434\u0438_\u0441\u0435\u043a\u0443\u043d\u0434",mm:a?"\u0445\u0432\u0438\u043b\u0438\u043d\u0430_\u0445\u0432\u0438\u043b\u0438\u043d\u0438_\u0445\u0432\u0438\u043b\u0438\u043d":"\u0445\u0432\u0438\u043b\u0438\u043d\u0443_\u0445\u0432\u0438\u043b\u0438\u043d\u0438_\u0445\u0432\u0438\u043b\u0438\u043d",hh:a?"\u0433\u043e\u0434\u0438\u043d\u0430_\u0433\u043e\u0434\u0438\u043d\u0438_\u0433\u043e\u0434\u0438\u043d":"\u0433\u043e\u0434\u0438\u043d\u0443_\u0433\u043e\u0434\u0438\u043d\u0438_\u0433\u043e\u0434\u0438\u043d",dd:"\u0434\u0435\u043d\u044c_\u0434\u043d\u0456_\u0434\u043d\u0456\u0432",MM:"\u043c\u0456\u0441\u044f\u0446\u044c_\u043c\u0456\u0441\u044f\u0446\u0456_\u043c\u0456\u0441\u044f\u0446\u0456\u0432",yy:"\u0440\u0456\u043a_\u0440\u043e\u043a\u0438_\u0440\u043e\u043a\u0456\u0432"}[t]).split("_"),e%10==1&&e%100!=11?a[0]:2<=e%10&&e%10<=4&&(e%100<10||20<=e%100)?a[1]:a[2])}function Wn(e){return function(){return e+"\u043e"+(11===this.hours()?"\u0431":"")+"] LT"}}M.defineLocale("tr",{months:"Ocak_\u015eubat_Mart_Nisan_May\u0131s_Haziran_Temmuz_A\u011fustos_Eyl\xfcl_Ekim_Kas\u0131m_Aral\u0131k".split("_"),monthsShort:"Oca_\u015eub_Mar_Nis_May_Haz_Tem_A\u011fu_Eyl_Eki_Kas_Ara".split("_"),weekdays:"Pazar_Pazartesi_Sal\u0131_\xc7ar\u015famba_Per\u015fembe_Cuma_Cumartesi".split("_"),weekdaysShort:"Paz_Pzt_Sal_\xc7ar_Per_Cum_Cmt".split("_"),weekdaysMin:"Pz_Pt_Sa_\xc7a_Pe_Cu_Ct".split("_"),meridiem:function(e,a,t){return e<12?t?"\xf6\xf6":"\xd6\xd6":t?"\xf6s":"\xd6S"},meridiemParse:/\xf6\xf6|\xd6\xd6|\xf6s|\xd6S/,isPM:function(e){return"\xf6s"===e||"\xd6S"===e},longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[bug\xfcn saat] LT",nextDay:"[yar\u0131n saat] LT",nextWeek:"[gelecek] dddd [saat] LT",lastDay:"[d\xfcn] LT",lastWeek:"[ge\xe7en] dddd [saat] LT",sameElse:"L"},relativeTime:{future:"%s sonra",past:"%s \xf6nce",s:"birka\xe7 saniye",ss:"%d saniye",m:"bir dakika",mm:"%d dakika",h:"bir saat",hh:"%d saat",d:"bir g\xfcn",dd:"%d g\xfcn",w:"bir hafta",ww:"%d hafta",M:"bir ay",MM:"%d ay",y:"bir y\u0131l",yy:"%d y\u0131l"},ordinal:function(e,a){switch(a){case"d":case"D":case"Do":case"DD":return e;default:if(0===e)return e+"'\u0131nc\u0131";var t=e%10;return e+(xn[t]||xn[e%100-t]||xn[100<=e?100:null])}},week:{dow:1,doy:7}}),M.defineLocale("tzl",{months:"Januar_Fevraglh_Mar\xe7_Avr\xefu_Mai_G\xfcn_Julia_Guscht_Setemvar_Listop\xe4ts_Noemvar_Zecemvar".split("_"),monthsShort:"Jan_Fev_Mar_Avr_Mai_G\xfcn_Jul_Gus_Set_Lis_Noe_Zec".split("_"),weekdays:"S\xfaladi_L\xfane\xe7i_Maitzi_M\xe1rcuri_Xh\xfaadi_Vi\xe9ner\xe7i_S\xe1turi".split("_"),weekdaysShort:"S\xfal_L\xfan_Mai_M\xe1r_Xh\xfa_Vi\xe9_S\xe1t".split("_"),weekdaysMin:"S\xfa_L\xfa_Ma_M\xe1_Xh_Vi_S\xe1".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD.MM.YYYY",LL:"D. MMMM [dallas] YYYY",LLL:"D. MMMM [dallas] YYYY HH.mm",LLLL:"dddd, [li] D. MMMM [dallas] YYYY HH.mm"},meridiemParse:/d\'o|d\'a/i,isPM:function(e){return"d'o"===e.toLowerCase()},meridiem:function(e,a,t){return 11<e?t?"d'o":"D'O":t?"d'a":"D'A"},calendar:{sameDay:"[oxhi \xe0] LT",nextDay:"[dem\xe0 \xe0] LT",nextWeek:"dddd [\xe0] LT",lastDay:"[ieiri \xe0] LT",lastWeek:"[s\xfcr el] dddd [lasteu \xe0] LT",sameElse:"L"},relativeTime:{future:"osprei %s",past:"ja%s",s:Pn,ss:Pn,m:Pn,mm:Pn,h:Pn,hh:Pn,d:Pn,dd:Pn,M:Pn,MM:Pn,y:Pn,yy:Pn},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),M.defineLocale("tzm-latn",{months:"innayr_br\u02e4ayr\u02e4_mar\u02e4s\u02e4_ibrir_mayyw_ywnyw_ywlywz_\u0263w\u0161t_\u0161wtanbir_kt\u02e4wbr\u02e4_nwwanbir_dwjnbir".split("_"),monthsShort:"innayr_br\u02e4ayr\u02e4_mar\u02e4s\u02e4_ibrir_mayyw_ywnyw_ywlywz_\u0263w\u0161t_\u0161wtanbir_kt\u02e4wbr\u02e4_nwwanbir_dwjnbir".split("_"),weekdays:"asamas_aynas_asinas_akras_akwas_asimwas_asi\u1e0dyas".split("_"),weekdaysShort:"asamas_aynas_asinas_akras_akwas_asimwas_asi\u1e0dyas".split("_"),weekdaysMin:"asamas_aynas_asinas_akras_akwas_asimwas_asi\u1e0dyas".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[asdkh g] LT",nextDay:"[aska g] LT",nextWeek:"dddd [g] LT",lastDay:"[assant g] LT",lastWeek:"dddd [g] LT",sameElse:"L"},relativeTime:{future:"dadkh s yan %s",past:"yan %s",s:"imik",ss:"%d imik",m:"minu\u1e0d",mm:"%d minu\u1e0d",h:"sa\u025ba",hh:"%d tassa\u025bin",d:"ass",dd:"%d ossan",M:"ayowr",MM:"%d iyyirn",y:"asgas",yy:"%d isgasn"},week:{dow:6,doy:12}}),M.defineLocale("tzm",{months:"\u2d49\u2d4f\u2d4f\u2d30\u2d62\u2d54_\u2d31\u2d55\u2d30\u2d62\u2d55_\u2d4e\u2d30\u2d55\u2d5a_\u2d49\u2d31\u2d54\u2d49\u2d54_\u2d4e\u2d30\u2d62\u2d62\u2d53_\u2d62\u2d53\u2d4f\u2d62\u2d53_\u2d62\u2d53\u2d4d\u2d62\u2d53\u2d63_\u2d56\u2d53\u2d5b\u2d5c_\u2d5b\u2d53\u2d5c\u2d30\u2d4f\u2d31\u2d49\u2d54_\u2d3d\u2d5f\u2d53\u2d31\u2d55_\u2d4f\u2d53\u2d61\u2d30\u2d4f\u2d31\u2d49\u2d54_\u2d37\u2d53\u2d4a\u2d4f\u2d31\u2d49\u2d54".split("_"),monthsShort:"\u2d49\u2d4f\u2d4f\u2d30\u2d62\u2d54_\u2d31\u2d55\u2d30\u2d62\u2d55_\u2d4e\u2d30\u2d55\u2d5a_\u2d49\u2d31\u2d54\u2d49\u2d54_\u2d4e\u2d30\u2d62\u2d62\u2d53_\u2d62\u2d53\u2d4f\u2d62\u2d53_\u2d62\u2d53\u2d4d\u2d62\u2d53\u2d63_\u2d56\u2d53\u2d5b\u2d5c_\u2d5b\u2d53\u2d5c\u2d30\u2d4f\u2d31\u2d49\u2d54_\u2d3d\u2d5f\u2d53\u2d31\u2d55_\u2d4f\u2d53\u2d61\u2d30\u2d4f\u2d31\u2d49\u2d54_\u2d37\u2d53\u2d4a\u2d4f\u2d31\u2d49\u2d54".split("_"),weekdays:"\u2d30\u2d59\u2d30\u2d4e\u2d30\u2d59_\u2d30\u2d62\u2d4f\u2d30\u2d59_\u2d30\u2d59\u2d49\u2d4f\u2d30\u2d59_\u2d30\u2d3d\u2d54\u2d30\u2d59_\u2d30\u2d3d\u2d61\u2d30\u2d59_\u2d30\u2d59\u2d49\u2d4e\u2d61\u2d30\u2d59_\u2d30\u2d59\u2d49\u2d39\u2d62\u2d30\u2d59".split("_"),weekdaysShort:"\u2d30\u2d59\u2d30\u2d4e\u2d30\u2d59_\u2d30\u2d62\u2d4f\u2d30\u2d59_\u2d30\u2d59\u2d49\u2d4f\u2d30\u2d59_\u2d30\u2d3d\u2d54\u2d30\u2d59_\u2d30\u2d3d\u2d61\u2d30\u2d59_\u2d30\u2d59\u2d49\u2d4e\u2d61\u2d30\u2d59_\u2d30\u2d59\u2d49\u2d39\u2d62\u2d30\u2d59".split("_"),weekdaysMin:"\u2d30\u2d59\u2d30\u2d4e\u2d30\u2d59_\u2d30\u2d62\u2d4f\u2d30\u2d59_\u2d30\u2d59\u2d49\u2d4f\u2d30\u2d59_\u2d30\u2d3d\u2d54\u2d30\u2d59_\u2d30\u2d3d\u2d61\u2d30\u2d59_\u2d30\u2d59\u2d49\u2d4e\u2d61\u2d30\u2d59_\u2d30\u2d59\u2d49\u2d39\u2d62\u2d30\u2d59".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[\u2d30\u2d59\u2d37\u2d45 \u2d34] LT",nextDay:"[\u2d30\u2d59\u2d3d\u2d30 \u2d34] LT",nextWeek:"dddd [\u2d34] LT",lastDay:"[\u2d30\u2d5a\u2d30\u2d4f\u2d5c \u2d34] LT",lastWeek:"dddd [\u2d34] LT",sameElse:"L"},relativeTime:{future:"\u2d37\u2d30\u2d37\u2d45 \u2d59 \u2d62\u2d30\u2d4f %s",past:"\u2d62\u2d30\u2d4f %s",s:"\u2d49\u2d4e\u2d49\u2d3d",ss:"%d \u2d49\u2d4e\u2d49\u2d3d",m:"\u2d4e\u2d49\u2d4f\u2d53\u2d3a",mm:"%d \u2d4e\u2d49\u2d4f\u2d53\u2d3a",h:"\u2d59\u2d30\u2d44\u2d30",hh:"%d \u2d5c\u2d30\u2d59\u2d59\u2d30\u2d44\u2d49\u2d4f",d:"\u2d30\u2d59\u2d59",dd:"%d o\u2d59\u2d59\u2d30\u2d4f",M:"\u2d30\u2d62o\u2d53\u2d54",MM:"%d \u2d49\u2d62\u2d62\u2d49\u2d54\u2d4f",y:"\u2d30\u2d59\u2d33\u2d30\u2d59",yy:"%d \u2d49\u2d59\u2d33\u2d30\u2d59\u2d4f"},week:{dow:6,doy:12}}),M.defineLocale("ug-cn",{months:"\u064a\u0627\u0646\u06cb\u0627\u0631_\u0641\u06d0\u06cb\u0631\u0627\u0644_\u0645\u0627\u0631\u062a_\u0626\u0627\u067e\u0631\u06d0\u0644_\u0645\u0627\u064a_\u0626\u0649\u064a\u06c7\u0646_\u0626\u0649\u064a\u06c7\u0644_\u0626\u0627\u06cb\u063a\u06c7\u0633\u062a_\u0633\u06d0\u0646\u062a\u06d5\u0628\u0649\u0631_\u0626\u06c6\u0643\u062a\u06d5\u0628\u0649\u0631_\u0646\u0648\u064a\u0627\u0628\u0649\u0631_\u062f\u06d0\u0643\u0627\u0628\u0649\u0631".split("_"),monthsShort:"\u064a\u0627\u0646\u06cb\u0627\u0631_\u0641\u06d0\u06cb\u0631\u0627\u0644_\u0645\u0627\u0631\u062a_\u0626\u0627\u067e\u0631\u06d0\u0644_\u0645\u0627\u064a_\u0626\u0649\u064a\u06c7\u0646_\u0626\u0649\u064a\u06c7\u0644_\u0626\u0627\u06cb\u063a\u06c7\u0633\u062a_\u0633\u06d0\u0646\u062a\u06d5\u0628\u0649\u0631_\u0626\u06c6\u0643\u062a\u06d5\u0628\u0649\u0631_\u0646\u0648\u064a\u0627\u0628\u0649\u0631_\u062f\u06d0\u0643\u0627\u0628\u0649\u0631".split("_"),weekdays:"\u064a\u06d5\u0643\u0634\u06d5\u0646\u0628\u06d5_\u062f\u06c8\u0634\u06d5\u0646\u0628\u06d5_\u0633\u06d5\u064a\u0634\u06d5\u0646\u0628\u06d5_\u0686\u0627\u0631\u0634\u06d5\u0646\u0628\u06d5_\u067e\u06d5\u064a\u0634\u06d5\u0646\u0628\u06d5_\u062c\u06c8\u0645\u06d5_\u0634\u06d5\u0646\u0628\u06d5".split("_"),weekdaysShort:"\u064a\u06d5_\u062f\u06c8_\u0633\u06d5_\u0686\u0627_\u067e\u06d5_\u062c\u06c8_\u0634\u06d5".split("_"),weekdaysMin:"\u064a\u06d5_\u062f\u06c8_\u0633\u06d5_\u0686\u0627_\u067e\u06d5_\u062c\u06c8_\u0634\u06d5".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"YYYY-\u064a\u0649\u0644\u0649M-\u0626\u0627\u064a\u0646\u0649\u06adD-\u0643\u06c8\u0646\u0649",LLL:"YYYY-\u064a\u0649\u0644\u0649M-\u0626\u0627\u064a\u0646\u0649\u06adD-\u0643\u06c8\u0646\u0649\u060c HH:mm",LLLL:"dddd\u060c YYYY-\u064a\u0649\u0644\u0649M-\u0626\u0627\u064a\u0646\u0649\u06adD-\u0643\u06c8\u0646\u0649\u060c HH:mm"},meridiemParse:/\u064a\u06d0\u0631\u0649\u0645 \u0643\u06d0\u0686\u06d5|\u0633\u06d5\u06be\u06d5\u0631|\u0686\u06c8\u0634\u062a\u0649\u0646 \u0628\u06c7\u0631\u06c7\u0646|\u0686\u06c8\u0634|\u0686\u06c8\u0634\u062a\u0649\u0646 \u0643\u06d0\u064a\u0649\u0646|\u0643\u06d5\u0686/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u064a\u06d0\u0631\u0649\u0645 \u0643\u06d0\u0686\u06d5"===a||"\u0633\u06d5\u06be\u06d5\u0631"===a||"\u0686\u06c8\u0634\u062a\u0649\u0646 \u0628\u06c7\u0631\u06c7\u0646"===a||"\u0686\u06c8\u0634\u062a\u0649\u0646 \u0643\u06d0\u064a\u0649\u0646"!==a&&"\u0643\u06d5\u0686"!==a&&11<=e?e:e+12},meridiem:function(e,a,t){e=100*e+a;return e<600?"\u064a\u06d0\u0631\u0649\u0645 \u0643\u06d0\u0686\u06d5":e<900?"\u0633\u06d5\u06be\u06d5\u0631":e<1130?"\u0686\u06c8\u0634\u062a\u0649\u0646 \u0628\u06c7\u0631\u06c7\u0646":e<1230?"\u0686\u06c8\u0634":e<1800?"\u0686\u06c8\u0634\u062a\u0649\u0646 \u0643\u06d0\u064a\u0649\u0646":"\u0643\u06d5\u0686"},calendar:{sameDay:"[\u0628\u06c8\u06af\u06c8\u0646 \u0633\u0627\u0626\u06d5\u062a] LT",nextDay:"[\u0626\u06d5\u062a\u06d5 \u0633\u0627\u0626\u06d5\u062a] LT",nextWeek:"[\u0643\u06d0\u0644\u06d5\u0631\u0643\u0649] dddd [\u0633\u0627\u0626\u06d5\u062a] LT",lastDay:"[\u062a\u06c6\u0646\u06c8\u06af\u06c8\u0646] LT",lastWeek:"[\u0626\u0627\u0644\u062f\u0649\u0646\u0642\u0649] dddd [\u0633\u0627\u0626\u06d5\u062a] LT",sameElse:"L"},relativeTime:{future:"%s \u0643\u06d0\u064a\u0649\u0646",past:"%s \u0628\u06c7\u0631\u06c7\u0646",s:"\u0646\u06d5\u0686\u0686\u06d5 \u0633\u06d0\u0643\u0648\u0646\u062a",ss:"%d \u0633\u06d0\u0643\u0648\u0646\u062a",m:"\u0628\u0649\u0631 \u0645\u0649\u0646\u06c7\u062a",mm:"%d \u0645\u0649\u0646\u06c7\u062a",h:"\u0628\u0649\u0631 \u0633\u0627\u0626\u06d5\u062a",hh:"%d \u0633\u0627\u0626\u06d5\u062a",d:"\u0628\u0649\u0631 \u0643\u06c8\u0646",dd:"%d \u0643\u06c8\u0646",M:"\u0628\u0649\u0631 \u0626\u0627\u064a",MM:"%d \u0626\u0627\u064a",y:"\u0628\u0649\u0631 \u064a\u0649\u0644",yy:"%d \u064a\u0649\u0644"},dayOfMonthOrdinalParse:/\d{1,2}(-\u0643\u06c8\u0646\u0649|-\u0626\u0627\u064a|-\u06be\u06d5\u067e\u062a\u06d5)/,ordinal:function(e,a){switch(a){case"d":case"D":case"DDD":return e+"-\u0643\u06c8\u0646\u0649";case"w":case"W":return e+"-\u06be\u06d5\u067e\u062a\u06d5";default:return e}},preparse:function(e){return e.replace(/\u060c/g,",")},postformat:function(e){return e.replace(/,/g,"\u060c")},week:{dow:1,doy:7}}),M.defineLocale("uk",{months:{format:"\u0441\u0456\u0447\u043d\u044f_\u043b\u044e\u0442\u043e\u0433\u043e_\u0431\u0435\u0440\u0435\u0437\u043d\u044f_\u043a\u0432\u0456\u0442\u043d\u044f_\u0442\u0440\u0430\u0432\u043d\u044f_\u0447\u0435\u0440\u0432\u043d\u044f_\u043b\u0438\u043f\u043d\u044f_\u0441\u0435\u0440\u043f\u043d\u044f_\u0432\u0435\u0440\u0435\u0441\u043d\u044f_\u0436\u043e\u0432\u0442\u043d\u044f_\u043b\u0438\u0441\u0442\u043e\u043f\u0430\u0434\u0430_\u0433\u0440\u0443\u0434\u043d\u044f".split("_"),standalone:"\u0441\u0456\u0447\u0435\u043d\u044c_\u043b\u044e\u0442\u0438\u0439_\u0431\u0435\u0440\u0435\u0437\u0435\u043d\u044c_\u043a\u0432\u0456\u0442\u0435\u043d\u044c_\u0442\u0440\u0430\u0432\u0435\u043d\u044c_\u0447\u0435\u0440\u0432\u0435\u043d\u044c_\u043b\u0438\u043f\u0435\u043d\u044c_\u0441\u0435\u0440\u043f\u0435\u043d\u044c_\u0432\u0435\u0440\u0435\u0441\u0435\u043d\u044c_\u0436\u043e\u0432\u0442\u0435\u043d\u044c_\u043b\u0438\u0441\u0442\u043e\u043f\u0430\u0434_\u0433\u0440\u0443\u0434\u0435\u043d\u044c".split("_")},monthsShort:"\u0441\u0456\u0447_\u043b\u044e\u0442_\u0431\u0435\u0440_\u043a\u0432\u0456\u0442_\u0442\u0440\u0430\u0432_\u0447\u0435\u0440\u0432_\u043b\u0438\u043f_\u0441\u0435\u0440\u043f_\u0432\u0435\u0440_\u0436\u043e\u0432\u0442_\u043b\u0438\u0441\u0442_\u0433\u0440\u0443\u0434".split("_"),weekdays:function(e,a){var t={nominative:"\u043d\u0435\u0434\u0456\u043b\u044f_\u043f\u043e\u043d\u0435\u0434\u0456\u043b\u043e\u043a_\u0432\u0456\u0432\u0442\u043e\u0440\u043e\u043a_\u0441\u0435\u0440\u0435\u0434\u0430_\u0447\u0435\u0442\u0432\u0435\u0440_\u043f\u2019\u044f\u0442\u043d\u0438\u0446\u044f_\u0441\u0443\u0431\u043e\u0442\u0430".split("_"),accusative:"\u043d\u0435\u0434\u0456\u043b\u044e_\u043f\u043e\u043d\u0435\u0434\u0456\u043b\u043e\u043a_\u0432\u0456\u0432\u0442\u043e\u0440\u043e\u043a_\u0441\u0435\u0440\u0435\u0434\u0443_\u0447\u0435\u0442\u0432\u0435\u0440_\u043f\u2019\u044f\u0442\u043d\u0438\u0446\u044e_\u0441\u0443\u0431\u043e\u0442\u0443".split("_"),genitive:"\u043d\u0435\u0434\u0456\u043b\u0456_\u043f\u043e\u043d\u0435\u0434\u0456\u043b\u043a\u0430_\u0432\u0456\u0432\u0442\u043e\u0440\u043a\u0430_\u0441\u0435\u0440\u0435\u0434\u0438_\u0447\u0435\u0442\u0432\u0435\u0440\u0433\u0430_\u043f\u2019\u044f\u0442\u043d\u0438\u0446\u0456_\u0441\u0443\u0431\u043e\u0442\u0438".split("_")};return!0===e?t.nominative.slice(1,7).concat(t.nominative.slice(0,1)):e?t[/(\[[\u0412\u0432\u0423\u0443]\]) ?dddd/.test(a)?"accusative":/\[?(?:\u043c\u0438\u043d\u0443\u043b\u043e\u0457|\u043d\u0430\u0441\u0442\u0443\u043f\u043d\u043e\u0457)? ?\] ?dddd/.test(a)?"genitive":"nominative"][e.day()]:t.nominative},weekdaysShort:"\u043d\u0434_\u043f\u043d_\u0432\u0442_\u0441\u0440_\u0447\u0442_\u043f\u0442_\u0441\u0431".split("_"),weekdaysMin:"\u043d\u0434_\u043f\u043d_\u0432\u0442_\u0441\u0440_\u0447\u0442_\u043f\u0442_\u0441\u0431".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY \u0440.",LLL:"D MMMM YYYY \u0440., HH:mm",LLLL:"dddd, D MMMM YYYY \u0440., HH:mm"},calendar:{sameDay:Wn("[\u0421\u044c\u043e\u0433\u043e\u0434\u043d\u0456 "),nextDay:Wn("[\u0417\u0430\u0432\u0442\u0440\u0430 "),lastDay:Wn("[\u0412\u0447\u043e\u0440\u0430 "),nextWeek:Wn("[\u0423] dddd ["),lastWeek:function(){switch(this.day()){case 0:case 3:case 5:case 6:return Wn("[\u041c\u0438\u043d\u0443\u043b\u043e\u0457] dddd [").call(this);case 1:case 2:case 4:return Wn("[\u041c\u0438\u043d\u0443\u043b\u043e\u0433\u043e] dddd [").call(this)}},sameElse:"L"},relativeTime:{future:"\u0437\u0430 %s",past:"%s \u0442\u043e\u043c\u0443",s:"\u0434\u0435\u043a\u0456\u043b\u044c\u043a\u0430 \u0441\u0435\u043a\u0443\u043d\u0434",ss:On,m:On,mm:On,h:"\u0433\u043e\u0434\u0438\u043d\u0443",hh:On,d:"\u0434\u0435\u043d\u044c",dd:On,M:"\u043c\u0456\u0441\u044f\u0446\u044c",MM:On,y:"\u0440\u0456\u043a",yy:On},meridiemParse:/\u043d\u043e\u0447\u0456|\u0440\u0430\u043d\u043a\u0443|\u0434\u043d\u044f|\u0432\u0435\u0447\u043e\u0440\u0430/,isPM:function(e){return/^(\u0434\u043d\u044f|\u0432\u0435\u0447\u043e\u0440\u0430)$/.test(e)},meridiem:function(e,a,t){return e<4?"\u043d\u043e\u0447\u0456":e<12?"\u0440\u0430\u043d\u043a\u0443":e<17?"\u0434\u043d\u044f":"\u0432\u0435\u0447\u043e\u0440\u0430"},dayOfMonthOrdinalParse:/\d{1,2}-(\u0439|\u0433\u043e)/,ordinal:function(e,a){switch(a){case"M":case"d":case"DDD":case"w":case"W":return e+"-\u0439";case"D":return e+"-\u0433\u043e";default:return e}},week:{dow:1,doy:7}});jt=["\u062c\u0646\u0648\u0631\u06cc","\u0641\u0631\u0648\u0631\u06cc","\u0645\u0627\u0631\u0686","\u0627\u067e\u0631\u06cc\u0644","\u0645\u0626\u06cc","\u062c\u0648\u0646","\u062c\u0648\u0644\u0627\u0626\u06cc","\u0627\u06af\u0633\u062a","\u0633\u062a\u0645\u0628\u0631","\u0627\u06a9\u062a\u0648\u0628\u0631","\u0646\u0648\u0645\u0628\u0631","\u062f\u0633\u0645\u0628\u0631"],vt=["\u0627\u062a\u0648\u0627\u0631","\u067e\u06cc\u0631","\u0645\u0646\u06af\u0644","\u0628\u062f\u06be","\u062c\u0645\u0639\u0631\u0627\u062a","\u062c\u0645\u0639\u06c1","\u06c1\u0641\u062a\u06c1"];return M.defineLocale("ur",{months:jt,monthsShort:jt,weekdays:vt,weekdaysShort:vt,weekdaysMin:vt,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd\u060c D MMMM YYYY HH:mm"},meridiemParse:/\u0635\u0628\u062d|\u0634\u0627\u0645/,isPM:function(e){return"\u0634\u0627\u0645"===e},meridiem:function(e,a,t){return e<12?"\u0635\u0628\u062d":"\u0634\u0627\u0645"},calendar:{sameDay:"[\u0622\u062c \u0628\u0648\u0642\u062a] LT",nextDay:"[\u06a9\u0644 \u0628\u0648\u0642\u062a] LT",nextWeek:"dddd [\u0628\u0648\u0642\u062a] LT",lastDay:"[\u06af\u0630\u0634\u062a\u06c1 \u0631\u0648\u0632 \u0628\u0648\u0642\u062a] LT",lastWeek:"[\u06af\u0630\u0634\u062a\u06c1] dddd [\u0628\u0648\u0642\u062a] LT",sameElse:"L"},relativeTime:{future:"%s \u0628\u0639\u062f",past:"%s \u0642\u0628\u0644",s:"\u0686\u0646\u062f \u0633\u06cc\u06a9\u0646\u0688",ss:"%d \u0633\u06cc\u06a9\u0646\u0688",m:"\u0627\u06cc\u06a9 \u0645\u0646\u0679",mm:"%d \u0645\u0646\u0679",h:"\u0627\u06cc\u06a9 \u06af\u06be\u0646\u0679\u06c1",hh:"%d \u06af\u06be\u0646\u0679\u06d2",d:"\u0627\u06cc\u06a9 \u062f\u0646",dd:"%d \u062f\u0646",M:"\u0627\u06cc\u06a9 \u0645\u0627\u06c1",MM:"%d \u0645\u0627\u06c1",y:"\u0627\u06cc\u06a9 \u0633\u0627\u0644",yy:"%d \u0633\u0627\u0644"},preparse:function(e){return e.replace(/\u060c/g,",")},postformat:function(e){return e.replace(/,/g,"\u060c")},week:{dow:1,doy:4}}),M.defineLocale("uz-latn",{months:"Yanvar_Fevral_Mart_Aprel_May_Iyun_Iyul_Avgust_Sentabr_Oktabr_Noyabr_Dekabr".split("_"),monthsShort:"Yan_Fev_Mar_Apr_May_Iyun_Iyul_Avg_Sen_Okt_Noy_Dek".split("_"),weekdays:"Yakshanba_Dushanba_Seshanba_Chorshanba_Payshanba_Juma_Shanba".split("_"),weekdaysShort:"Yak_Dush_Sesh_Chor_Pay_Jum_Shan".split("_"),weekdaysMin:"Ya_Du_Se_Cho_Pa_Ju_Sha".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"D MMMM YYYY, dddd HH:mm"},calendar:{sameDay:"[Bugun soat] LT [da]",nextDay:"[Ertaga] LT [da]",nextWeek:"dddd [kuni soat] LT [da]",lastDay:"[Kecha soat] LT [da]",lastWeek:"[O'tgan] dddd [kuni soat] LT [da]",sameElse:"L"},relativeTime:{future:"Yaqin %s ichida",past:"Bir necha %s oldin",s:"soniya",ss:"%d soniya",m:"bir daqiqa",mm:"%d daqiqa",h:"bir soat",hh:"%d soat",d:"bir kun",dd:"%d kun",M:"bir oy",MM:"%d oy",y:"bir yil",yy:"%d yil"},week:{dow:1,doy:7}}),M.defineLocale("uz",{months:"\u044f\u043d\u0432\u0430\u0440_\u0444\u0435\u0432\u0440\u0430\u043b_\u043c\u0430\u0440\u0442_\u0430\u043f\u0440\u0435\u043b_\u043c\u0430\u0439_\u0438\u044e\u043d_\u0438\u044e\u043b_\u0430\u0432\u0433\u0443\u0441\u0442_\u0441\u0435\u043d\u0442\u044f\u0431\u0440_\u043e\u043a\u0442\u044f\u0431\u0440_\u043d\u043e\u044f\u0431\u0440_\u0434\u0435\u043a\u0430\u0431\u0440".split("_"),monthsShort:"\u044f\u043d\u0432_\u0444\u0435\u0432_\u043c\u0430\u0440_\u0430\u043f\u0440_\u043c\u0430\u0439_\u0438\u044e\u043d_\u0438\u044e\u043b_\u0430\u0432\u0433_\u0441\u0435\u043d_\u043e\u043a\u0442_\u043d\u043e\u044f_\u0434\u0435\u043a".split("_"),weekdays:"\u042f\u043a\u0448\u0430\u043d\u0431\u0430_\u0414\u0443\u0448\u0430\u043d\u0431\u0430_\u0421\u0435\u0448\u0430\u043d\u0431\u0430_\u0427\u043e\u0440\u0448\u0430\u043d\u0431\u0430_\u041f\u0430\u0439\u0448\u0430\u043d\u0431\u0430_\u0416\u0443\u043c\u0430_\u0428\u0430\u043d\u0431\u0430".split("_"),weekdaysShort:"\u042f\u043a\u0448_\u0414\u0443\u0448_\u0421\u0435\u0448_\u0427\u043e\u0440_\u041f\u0430\u0439_\u0416\u0443\u043c_\u0428\u0430\u043d".split("_"),weekdaysMin:"\u042f\u043a_\u0414\u0443_\u0421\u0435_\u0427\u043e_\u041f\u0430_\u0416\u0443_\u0428\u0430".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"D MMMM YYYY, dddd HH:mm"},calendar:{sameDay:"[\u0411\u0443\u0433\u0443\u043d \u0441\u043e\u0430\u0442] LT [\u0434\u0430]",nextDay:"[\u042d\u0440\u0442\u0430\u0433\u0430] LT [\u0434\u0430]",nextWeek:"dddd [\u043a\u0443\u043d\u0438 \u0441\u043e\u0430\u0442] LT [\u0434\u0430]",lastDay:"[\u041a\u0435\u0447\u0430 \u0441\u043e\u0430\u0442] LT [\u0434\u0430]",lastWeek:"[\u0423\u0442\u0433\u0430\u043d] dddd [\u043a\u0443\u043d\u0438 \u0441\u043e\u0430\u0442] LT [\u0434\u0430]",sameElse:"L"},relativeTime:{future:"\u042f\u043a\u0438\u043d %s \u0438\u0447\u0438\u0434\u0430",past:"\u0411\u0438\u0440 \u043d\u0435\u0447\u0430 %s \u043e\u043b\u0434\u0438\u043d",s:"\u0444\u0443\u0440\u0441\u0430\u0442",ss:"%d \u0444\u0443\u0440\u0441\u0430\u0442",m:"\u0431\u0438\u0440 \u0434\u0430\u043a\u0438\u043a\u0430",mm:"%d \u0434\u0430\u043a\u0438\u043a\u0430",h:"\u0431\u0438\u0440 \u0441\u043e\u0430\u0442",hh:"%d \u0441\u043e\u0430\u0442",d:"\u0431\u0438\u0440 \u043a\u0443\u043d",dd:"%d \u043a\u0443\u043d",M:"\u0431\u0438\u0440 \u043e\u0439",MM:"%d \u043e\u0439",y:"\u0431\u0438\u0440 \u0439\u0438\u043b",yy:"%d \u0439\u0438\u043b"},week:{dow:1,doy:7}}),M.defineLocale("vi",{months:"th\xe1ng 1_th\xe1ng 2_th\xe1ng 3_th\xe1ng 4_th\xe1ng 5_th\xe1ng 6_th\xe1ng 7_th\xe1ng 8_th\xe1ng 9_th\xe1ng 10_th\xe1ng 11_th\xe1ng 12".split("_"),monthsShort:"Thg 01_Thg 02_Thg 03_Thg 04_Thg 05_Thg 06_Thg 07_Thg 08_Thg 09_Thg 10_Thg 11_Thg 12".split("_"),monthsParseExact:!0,weekdays:"ch\u1ee7 nh\u1eadt_th\u1ee9 hai_th\u1ee9 ba_th\u1ee9 t\u01b0_th\u1ee9 n\u0103m_th\u1ee9 s\xe1u_th\u1ee9 b\u1ea3y".split("_"),weekdaysShort:"CN_T2_T3_T4_T5_T6_T7".split("_"),weekdaysMin:"CN_T2_T3_T4_T5_T6_T7".split("_"),weekdaysParseExact:!0,meridiemParse:/sa|ch/i,isPM:function(e){return/^ch$/i.test(e)},meridiem:function(e,a,t){return e<12?t?"sa":"SA":t?"ch":"CH"},longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM [n\u0103m] YYYY",LLL:"D MMMM [n\u0103m] YYYY HH:mm",LLLL:"dddd, D MMMM [n\u0103m] YYYY HH:mm",l:"DD/M/YYYY",ll:"D MMM YYYY",lll:"D MMM YYYY HH:mm",llll:"ddd, D MMM YYYY HH:mm"},calendar:{sameDay:"[H\xf4m nay l\xfac] LT",nextDay:"[Ng\xe0y mai l\xfac] LT",nextWeek:"dddd [tu\u1ea7n t\u1edbi l\xfac] LT",lastDay:"[H\xf4m qua l\xfac] LT",lastWeek:"dddd [tu\u1ea7n tr\u01b0\u1edbc l\xfac] LT",sameElse:"L"},relativeTime:{future:"%s t\u1edbi",past:"%s tr\u01b0\u1edbc",s:"v\xe0i gi\xe2y",ss:"%d gi\xe2y",m:"m\u1ed9t ph\xfat",mm:"%d ph\xfat",h:"m\u1ed9t gi\u1edd",hh:"%d gi\u1edd",d:"m\u1ed9t ng\xe0y",dd:"%d ng\xe0y",w:"m\u1ed9t tu\u1ea7n",ww:"%d tu\u1ea7n",M:"m\u1ed9t th\xe1ng",MM:"%d th\xe1ng",y:"m\u1ed9t n\u0103m",yy:"%d n\u0103m"},dayOfMonthOrdinalParse:/\d{1,2}/,ordinal:function(e){return e},week:{dow:1,doy:4}}),M.defineLocale("x-pseudo",{months:"J~\xe1\xf1\xfa\xe1~r\xfd_F~\xe9br\xfa~\xe1r\xfd_~M\xe1rc~h_\xc1p~r\xedl_~M\xe1\xfd_~J\xfa\xf1\xe9~_J\xfal~\xfd_\xc1\xfa~g\xfast~_S\xe9p~t\xe9mb~\xe9r_\xd3~ct\xf3b~\xe9r_\xd1~\xf3v\xe9m~b\xe9r_~D\xe9c\xe9~mb\xe9r".split("_"),monthsShort:"J~\xe1\xf1_~F\xe9b_~M\xe1r_~\xc1pr_~M\xe1\xfd_~J\xfa\xf1_~J\xfal_~\xc1\xfag_~S\xe9p_~\xd3ct_~\xd1\xf3v_~D\xe9c".split("_"),monthsParseExact:!0,weekdays:"S~\xfa\xf1d\xe1~\xfd_M\xf3~\xf1d\xe1\xfd~_T\xfa\xe9~sd\xe1\xfd~_W\xe9d~\xf1\xe9sd~\xe1\xfd_T~h\xfars~d\xe1\xfd_~Fr\xedd~\xe1\xfd_S~\xe1t\xfar~d\xe1\xfd".split("_"),weekdaysShort:"S~\xfa\xf1_~M\xf3\xf1_~T\xfa\xe9_~W\xe9d_~Th\xfa_~Fr\xed_~S\xe1t".split("_"),weekdaysMin:"S~\xfa_M\xf3~_T\xfa_~W\xe9_T~h_Fr~_S\xe1".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[T~\xf3d\xe1~\xfd \xe1t] LT",nextDay:"[T~\xf3m\xf3~rr\xf3~w \xe1t] LT",nextWeek:"dddd [\xe1t] LT",lastDay:"[\xdd~\xe9st~\xe9rd\xe1~\xfd \xe1t] LT",lastWeek:"[L~\xe1st] dddd [\xe1t] LT",sameElse:"L"},relativeTime:{future:"\xed~\xf1 %s",past:"%s \xe1~g\xf3",s:"\xe1 ~f\xe9w ~s\xe9c\xf3~\xf1ds",ss:"%d s~\xe9c\xf3\xf1~ds",m:"\xe1 ~m\xed\xf1~\xfat\xe9",mm:"%d m~\xed\xf1\xfa~t\xe9s",h:"\xe1~\xf1 h\xf3~\xfar",hh:"%d h~\xf3\xfars",d:"\xe1 ~d\xe1\xfd",dd:"%d d~\xe1\xfds",M:"\xe1 ~m\xf3\xf1~th",MM:"%d m~\xf3\xf1t~hs",y:"\xe1 ~\xfd\xe9\xe1r",yy:"%d \xfd~\xe9\xe1rs"},dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(e){var a=e%10;return e+(1==~~(e%100/10)?"th":1==a?"st":2==a?"nd":3==a?"rd":"th")},week:{dow:1,doy:4}}),M.defineLocale("yo",{months:"S\u1eb9\u0301r\u1eb9\u0301_E\u0300re\u0300le\u0300_\u1eb8r\u1eb9\u0300na\u0300_I\u0300gbe\u0301_E\u0300bibi_O\u0300ku\u0300du_Ag\u1eb9mo_O\u0300gu\u0301n_Owewe_\u1ecc\u0300wa\u0300ra\u0300_Be\u0301lu\u0301_\u1ecc\u0300p\u1eb9\u0300\u0300".split("_"),monthsShort:"S\u1eb9\u0301r_E\u0300rl_\u1eb8rn_I\u0300gb_E\u0300bi_O\u0300ku\u0300_Ag\u1eb9_O\u0300gu\u0301_Owe_\u1ecc\u0300wa\u0300_Be\u0301l_\u1ecc\u0300p\u1eb9\u0300\u0300".split("_"),weekdays:"A\u0300i\u0300ku\u0301_Aje\u0301_I\u0300s\u1eb9\u0301gun_\u1eccj\u1ecd\u0301ru\u0301_\u1eccj\u1ecd\u0301b\u1ecd_\u1eb8ti\u0300_A\u0300ba\u0301m\u1eb9\u0301ta".split("_"),weekdaysShort:"A\u0300i\u0300k_Aje\u0301_I\u0300s\u1eb9\u0301_\u1eccjr_\u1eccjb_\u1eb8ti\u0300_A\u0300ba\u0301".split("_"),weekdaysMin:"A\u0300i\u0300_Aj_I\u0300s_\u1eccr_\u1eccb_\u1eb8t_A\u0300b".split("_"),longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY h:mm A",LLLL:"dddd, D MMMM YYYY h:mm A"},calendar:{sameDay:"[O\u0300ni\u0300 ni] LT",nextDay:"[\u1ecc\u0300la ni] LT",nextWeek:"dddd [\u1eccs\u1eb9\u0300 to\u0301n'b\u1ecd] [ni] LT",lastDay:"[A\u0300na ni] LT",lastWeek:"dddd [\u1eccs\u1eb9\u0300 to\u0301l\u1ecd\u0301] [ni] LT",sameElse:"L"},relativeTime:{future:"ni\u0301 %s",past:"%s k\u1ecdja\u0301",s:"i\u0300s\u1eb9ju\u0301 aaya\u0301 die",ss:"aaya\u0301 %d",m:"i\u0300s\u1eb9ju\u0301 kan",mm:"i\u0300s\u1eb9ju\u0301 %d",h:"wa\u0301kati kan",hh:"wa\u0301kati %d",d:"\u1ecdj\u1ecd\u0301 kan",dd:"\u1ecdj\u1ecd\u0301 %d",M:"osu\u0300 kan",MM:"osu\u0300 %d",y:"\u1ecddu\u0301n kan",yy:"\u1ecddu\u0301n %d"},dayOfMonthOrdinalParse:/\u1ecdj\u1ecd\u0301\s\d{1,2}/,ordinal:"\u1ecdj\u1ecd\u0301 %d",week:{dow:1,doy:4}}),M.defineLocale("zh-cn",{months:"\u4e00\u6708_\u4e8c\u6708_\u4e09\u6708_\u56db\u6708_\u4e94\u6708_\u516d\u6708_\u4e03\u6708_\u516b\u6708_\u4e5d\u6708_\u5341\u6708_\u5341\u4e00\u6708_\u5341\u4e8c\u6708".split("_"),monthsShort:"1\u6708_2\u6708_3\u6708_4\u6708_5\u6708_6\u6708_7\u6708_8\u6708_9\u6708_10\u6708_11\u6708_12\u6708".split("_"),weekdays:"\u661f\u671f\u65e5_\u661f\u671f\u4e00_\u661f\u671f\u4e8c_\u661f\u671f\u4e09_\u661f\u671f\u56db_\u661f\u671f\u4e94_\u661f\u671f\u516d".split("_"),weekdaysShort:"\u5468\u65e5_\u5468\u4e00_\u5468\u4e8c_\u5468\u4e09_\u5468\u56db_\u5468\u4e94_\u5468\u516d".split("_"),weekdaysMin:"\u65e5_\u4e00_\u4e8c_\u4e09_\u56db_\u4e94_\u516d".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY/MM/DD",LL:"YYYY\u5e74M\u6708D\u65e5",LLL:"YYYY\u5e74M\u6708D\u65e5Ah\u70b9mm\u5206",LLLL:"YYYY\u5e74M\u6708D\u65e5ddddAh\u70b9mm\u5206",l:"YYYY/M/D",ll:"YYYY\u5e74M\u6708D\u65e5",lll:"YYYY\u5e74M\u6708D\u65e5 HH:mm",llll:"YYYY\u5e74M\u6708D\u65e5dddd HH:mm"},meridiemParse:/\u51cc\u6668|\u65e9\u4e0a|\u4e0a\u5348|\u4e2d\u5348|\u4e0b\u5348|\u665a\u4e0a/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u51cc\u6668"===a||"\u65e9\u4e0a"===a||"\u4e0a\u5348"===a||"\u4e0b\u5348"!==a&&"\u665a\u4e0a"!==a&&11<=e?e:e+12},meridiem:function(e,a,t){e=100*e+a;return e<600?"\u51cc\u6668":e<900?"\u65e9\u4e0a":e<1130?"\u4e0a\u5348":e<1230?"\u4e2d\u5348":e<1800?"\u4e0b\u5348":"\u665a\u4e0a"},calendar:{sameDay:"[\u4eca\u5929]LT",nextDay:"[\u660e\u5929]LT",nextWeek:function(e){return e.week()!==this.week()?"[\u4e0b]dddLT":"[\u672c]dddLT"},lastDay:"[\u6628\u5929]LT",lastWeek:function(e){return this.week()!==e.week()?"[\u4e0a]dddLT":"[\u672c]dddLT"},sameElse:"L"},dayOfMonthOrdinalParse:/\d{1,2}(\u65e5|\u6708|\u5468)/,ordinal:function(e,a){switch(a){case"d":case"D":case"DDD":return e+"\u65e5";case"M":return e+"\u6708";case"w":case"W":return e+"\u5468";default:return e}},relativeTime:{future:"%s\u540e",past:"%s\u524d",s:"\u51e0\u79d2",ss:"%d \u79d2",m:"1 \u5206\u949f",mm:"%d \u5206\u949f",h:"1 \u5c0f\u65f6",hh:"%d \u5c0f\u65f6",d:"1 \u5929",dd:"%d \u5929",w:"1 \u5468",ww:"%d \u5468",M:"1 \u4e2a\u6708",MM:"%d \u4e2a\u6708",y:"1 \u5e74",yy:"%d \u5e74"},week:{dow:1,doy:4}}),M.defineLocale("zh-hk",{months:"\u4e00\u6708_\u4e8c\u6708_\u4e09\u6708_\u56db\u6708_\u4e94\u6708_\u516d\u6708_\u4e03\u6708_\u516b\u6708_\u4e5d\u6708_\u5341\u6708_\u5341\u4e00\u6708_\u5341\u4e8c\u6708".split("_"),monthsShort:"1\u6708_2\u6708_3\u6708_4\u6708_5\u6708_6\u6708_7\u6708_8\u6708_9\u6708_10\u6708_11\u6708_12\u6708".split("_"),weekdays:"\u661f\u671f\u65e5_\u661f\u671f\u4e00_\u661f\u671f\u4e8c_\u661f\u671f\u4e09_\u661f\u671f\u56db_\u661f\u671f\u4e94_\u661f\u671f\u516d".split("_"),weekdaysShort:"\u9031\u65e5_\u9031\u4e00_\u9031\u4e8c_\u9031\u4e09_\u9031\u56db_\u9031\u4e94_\u9031\u516d".split("_"),weekdaysMin:"\u65e5_\u4e00_\u4e8c_\u4e09_\u56db_\u4e94_\u516d".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY/MM/DD",LL:"YYYY\u5e74M\u6708D\u65e5",LLL:"YYYY\u5e74M\u6708D\u65e5 HH:mm",LLLL:"YYYY\u5e74M\u6708D\u65e5dddd HH:mm",l:"YYYY/M/D",ll:"YYYY\u5e74M\u6708D\u65e5",lll:"YYYY\u5e74M\u6708D\u65e5 HH:mm",llll:"YYYY\u5e74M\u6708D\u65e5dddd HH:mm"},meridiemParse:/\u51cc\u6668|\u65e9\u4e0a|\u4e0a\u5348|\u4e2d\u5348|\u4e0b\u5348|\u665a\u4e0a/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u51cc\u6668"===a||"\u65e9\u4e0a"===a||"\u4e0a\u5348"===a?e:"\u4e2d\u5348"===a?11<=e?e:e+12:"\u4e0b\u5348"===a||"\u665a\u4e0a"===a?e+12:void 0},meridiem:function(e,a,t){e=100*e+a;return e<600?"\u51cc\u6668":e<900?"\u65e9\u4e0a":e<1200?"\u4e0a\u5348":1200===e?"\u4e2d\u5348":e<1800?"\u4e0b\u5348":"\u665a\u4e0a"},calendar:{sameDay:"[\u4eca\u5929]LT",nextDay:"[\u660e\u5929]LT",nextWeek:"[\u4e0b]ddddLT",lastDay:"[\u6628\u5929]LT",lastWeek:"[\u4e0a]ddddLT",sameElse:"L"},dayOfMonthOrdinalParse:/\d{1,2}(\u65e5|\u6708|\u9031)/,ordinal:function(e,a){switch(a){case"d":case"D":case"DDD":return e+"\u65e5";case"M":return e+"\u6708";case"w":case"W":return e+"\u9031";default:return e}},relativeTime:{future:"%s\u5f8c",past:"%s\u524d",s:"\u5e7e\u79d2",ss:"%d \u79d2",m:"1 \u5206\u9418",mm:"%d \u5206\u9418",h:"1 \u5c0f\u6642",hh:"%d \u5c0f\u6642",d:"1 \u5929",dd:"%d \u5929",M:"1 \u500b\u6708",MM:"%d \u500b\u6708",y:"1 \u5e74",yy:"%d \u5e74"}}),M.defineLocale("zh-mo",{months:"\u4e00\u6708_\u4e8c\u6708_\u4e09\u6708_\u56db\u6708_\u4e94\u6708_\u516d\u6708_\u4e03\u6708_\u516b\u6708_\u4e5d\u6708_\u5341\u6708_\u5341\u4e00\u6708_\u5341\u4e8c\u6708".split("_"),monthsShort:"1\u6708_2\u6708_3\u6708_4\u6708_5\u6708_6\u6708_7\u6708_8\u6708_9\u6708_10\u6708_11\u6708_12\u6708".split("_"),weekdays:"\u661f\u671f\u65e5_\u661f\u671f\u4e00_\u661f\u671f\u4e8c_\u661f\u671f\u4e09_\u661f\u671f\u56db_\u661f\u671f\u4e94_\u661f\u671f\u516d".split("_"),weekdaysShort:"\u9031\u65e5_\u9031\u4e00_\u9031\u4e8c_\u9031\u4e09_\u9031\u56db_\u9031\u4e94_\u9031\u516d".split("_"),weekdaysMin:"\u65e5_\u4e00_\u4e8c_\u4e09_\u56db_\u4e94_\u516d".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"YYYY\u5e74M\u6708D\u65e5",LLL:"YYYY\u5e74M\u6708D\u65e5 HH:mm",LLLL:"YYYY\u5e74M\u6708D\u65e5dddd HH:mm",l:"D/M/YYYY",ll:"YYYY\u5e74M\u6708D\u65e5",lll:"YYYY\u5e74M\u6708D\u65e5 HH:mm",llll:"YYYY\u5e74M\u6708D\u65e5dddd HH:mm"},meridiemParse:/\u51cc\u6668|\u65e9\u4e0a|\u4e0a\u5348|\u4e2d\u5348|\u4e0b\u5348|\u665a\u4e0a/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u51cc\u6668"===a||"\u65e9\u4e0a"===a||"\u4e0a\u5348"===a?e:"\u4e2d\u5348"===a?11<=e?e:e+12:"\u4e0b\u5348"===a||"\u665a\u4e0a"===a?e+12:void 0},meridiem:function(e,a,t){e=100*e+a;return e<600?"\u51cc\u6668":e<900?"\u65e9\u4e0a":e<1130?"\u4e0a\u5348":e<1230?"\u4e2d\u5348":e<1800?"\u4e0b\u5348":"\u665a\u4e0a"},calendar:{sameDay:"[\u4eca\u5929] LT",nextDay:"[\u660e\u5929] LT",nextWeek:"[\u4e0b]dddd LT",lastDay:"[\u6628\u5929] LT",lastWeek:"[\u4e0a]dddd LT",sameElse:"L"},dayOfMonthOrdinalParse:/\d{1,2}(\u65e5|\u6708|\u9031)/,ordinal:function(e,a){switch(a){case"d":case"D":case"DDD":return e+"\u65e5";case"M":return e+"\u6708";case"w":case"W":return e+"\u9031";default:return e}},relativeTime:{future:"%s\u5167",past:"%s\u524d",s:"\u5e7e\u79d2",ss:"%d \u79d2",m:"1 \u5206\u9418",mm:"%d \u5206\u9418",h:"1 \u5c0f\u6642",hh:"%d \u5c0f\u6642",d:"1 \u5929",dd:"%d \u5929",M:"1 \u500b\u6708",MM:"%d \u500b\u6708",y:"1 \u5e74",yy:"%d \u5e74"}}),M.defineLocale("zh-tw",{months:"\u4e00\u6708_\u4e8c\u6708_\u4e09\u6708_\u56db\u6708_\u4e94\u6708_\u516d\u6708_\u4e03\u6708_\u516b\u6708_\u4e5d\u6708_\u5341\u6708_\u5341\u4e00\u6708_\u5341\u4e8c\u6708".split("_"),monthsShort:"1\u6708_2\u6708_3\u6708_4\u6708_5\u6708_6\u6708_7\u6708_8\u6708_9\u6708_10\u6708_11\u6708_12\u6708".split("_"),weekdays:"\u661f\u671f\u65e5_\u661f\u671f\u4e00_\u661f\u671f\u4e8c_\u661f\u671f\u4e09_\u661f\u671f\u56db_\u661f\u671f\u4e94_\u661f\u671f\u516d".split("_"),weekdaysShort:"\u9031\u65e5_\u9031\u4e00_\u9031\u4e8c_\u9031\u4e09_\u9031\u56db_\u9031\u4e94_\u9031\u516d".split("_"),weekdaysMin:"\u65e5_\u4e00_\u4e8c_\u4e09_\u56db_\u4e94_\u516d".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY/MM/DD",LL:"YYYY\u5e74M\u6708D\u65e5",LLL:"YYYY\u5e74M\u6708D\u65e5 HH:mm",LLLL:"YYYY\u5e74M\u6708D\u65e5dddd HH:mm",l:"YYYY/M/D",ll:"YYYY\u5e74M\u6708D\u65e5",lll:"YYYY\u5e74M\u6708D\u65e5 HH:mm",llll:"YYYY\u5e74M\u6708D\u65e5dddd HH:mm"},meridiemParse:/\u51cc\u6668|\u65e9\u4e0a|\u4e0a\u5348|\u4e2d\u5348|\u4e0b\u5348|\u665a\u4e0a/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u51cc\u6668"===a||"\u65e9\u4e0a"===a||"\u4e0a\u5348"===a?e:"\u4e2d\u5348"===a?11<=e?e:e+12:"\u4e0b\u5348"===a||"\u665a\u4e0a"===a?e+12:void 0},meridiem:function(e,a,t){e=100*e+a;return e<600?"\u51cc\u6668":e<900?"\u65e9\u4e0a":e<1130?"\u4e0a\u5348":e<1230?"\u4e2d\u5348":e<1800?"\u4e0b\u5348":"\u665a\u4e0a"},calendar:{sameDay:"[\u4eca\u5929] LT",nextDay:"[\u660e\u5929] LT",nextWeek:"[\u4e0b]dddd LT",lastDay:"[\u6628\u5929] LT",lastWeek:"[\u4e0a]dddd LT",sameElse:"L"},dayOfMonthOrdinalParse:/\d{1,2}(\u65e5|\u6708|\u9031)/,ordinal:function(e,a){switch(a){case"d":case"D":case"DDD":return e+"\u65e5";case"M":return e+"\u6708";case"w":case"W":return e+"\u9031";default:return e}},relativeTime:{future:"%s\u5f8c",past:"%s\u524d",s:"\u5e7e\u79d2",ss:"%d \u79d2",m:"1 \u5206\u9418",mm:"%d \u5206\u9418",h:"1 \u5c0f\u6642",hh:"%d \u5c0f\u6642",d:"1 \u5929",dd:"%d \u5929",M:"1 \u500b\u6708",MM:"%d \u500b\u6708",y:"1 \u5e74",yy:"%d \u5e74"}}),M.locale("en"),M});
//# sourceMappingURL=moment-with-locales.min.js.map
//! AlaSQL v0.3.9 | © 2014-2016 Andrey Gershun & Mathias Rangel Wulff | License: MIT 
!function(e,t){"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?module.exports=t():e.alasql=t()}(this,function(){function e(e){return"(y="+e+",y===y?y:undefined)"}function t(e,t){return"(y="+e+',typeof y=="undefined"?undefined:'+t+")"}function r(){return!0}function n(){}function a(){var e=navigator.userAgent.toLowerCase();return e.indexOf("msie")!==-1&&parseInt(e.split("msie")[1])}function i(e,t,r){function n(e,r,a){var o,u,c,l=e[r];if(l.selid){if("PATH"===l.selid){for(var h=[{node:a,stack:[]}],d={},f=A.databases[A.useid].objects;h.length>0;){var p=h.shift(),b=p.node,E=p.stack,c=n(l.args,0,b);if(c.length>0){if(r+1+1>e.length)return E;var g=[];return E&&E.length>0&&E.forEach(function(t){g=g.concat(n(e,r+1,t))}),g}void 0===d[b.$id]&&(d[b.$id]=!0,b.$out&&b.$out.length>0&&b.$out.forEach(function(e){var t=f[e],r=E.concat(t);r.push(f[t.$out[0]]),h.push({node:f[t.$out[0]],stack:r})}))}return[]}if("NOT"===l.selid){var u=n(l.args,0,a);return u.length>0?[]:r+1+1>e.length?[a]:n(e,r+1,a)}if("DISTINCT"===l.selid){var u;if(u=void 0===l.args||0===l.args.length?q(a):n(l.args,0,a),0===u.length)return[];var m=q(u);return r+1+1>e.length?m:n(e,r+1,m)}if("AND"===l.selid){var m=!0;return l.args.forEach(function(e){m=m&&n(e,0,a).length>0}),m?r+1+1>e.length?[a]:n(e,r+1,a):[]}if("OR"===l.selid){var m=!1;return l.args.forEach(function(e){m=m||n(e,0,a).length>0}),m?r+1+1>e.length?[a]:n(e,r+1,a):[]}if("ALL"===l.selid){var u=n(l.args[0],0,a);return 0===u.length?[]:r+1+1>e.length?u:n(e,r+1,u)}if("ANY"===l.selid){var u=n(l.args[0],0,a);return 0===u.length?[]:r+1+1>e.length?[u[0]]:n(e,r+1,[u[0]])}if("UNIONALL"===l.selid){var u=[];return l.args.forEach(function(e){u=u.concat(n(e,0,a))}),0===u.length?[]:r+1+1>e.length?u:n(e,r+1,u)}if("UNION"===l.selid){var u=[];l.args.forEach(function(e){u=u.concat(n(e,0,a))});var u=q(u);return 0===u.length?[]:r+1+1>e.length?u:n(e,r+1,u)}if("IF"===l.selid){var u=n(l.args,0,a);return 0===u.length?[]:r+1+1>e.length?[a]:n(e,r+1,a)}if("REPEAT"===l.selid){var v,S,T=l.args[0].value;S=l.args[1]?l.args[1].value:T,l.args[2]&&(v=l.args[2].variable);var y=[];if(0===T&&(r+1+1>e.length?y=[a]:(v&&(A.vars[v]=0),y=y.concat(n(e,r+1,a)))),S>0)for(var N=[{value:a,lvl:1}],C=0;N.length>0;){var u=N[0];if(N.shift(),u.lvl<=S){v&&(A.vars[v]=u.lvl);var R=n(l.sels,0,u.value);R.forEach(function(e){N.push({value:e,lvl:u.lvl+1})}),u.lvl>=T&&(r+1+1>e.length?y=y.concat(R):R.forEach(function(t){y=y.concat(n(e,r+1,t))}))}if(++C>1e5)throw new Error("Security brake. Number of iterations = "+C)}return y}if("OF"===l.selid){if(r+1+1>e.length)return[a];var O=[];return Object.keys(a).forEach(function(t){A.vars[l.args[0].variable]=t,O=O.concat(n(e,r+1,a[t]))}),O}if("TO"===l.selid){var w=A.vars[l.args[0]],I=[];if(I=void 0!==w?w.slice(0):[],I.push(a),r+1+1>e.length)return[a];A.vars[l.args[0]]=I;var O=n(e,r+1,a);return A.vars[l.args[0]]=w,O}if("ARRAY"===l.selid){var u=n(l.args,0,a);return u.length>0?(o=u,r+1+1>e.length?[o]:n(e,r+1,o)):[]}if("SUM"===l.selid){var u=n(l.args,0,a);if(!(u.length>0))return[];var o=u.reduce(function(e,t){return e+t},0);return r+1+1>e.length?[o]:n(e,r+1,o)}if("AVG"===l.selid)return u=n(l.args,0,a),u.length>0?(o=u.reduce(function(e,t){return e+t},0)/u.length,r+1+1>e.length?[o]:n(e,r+1,o)):[];if("COUNT"===l.selid)return u=n(l.args,0,a),u.length>0?(o=u.length,r+1+1>e.length?[o]:n(e,r+1,o)):[];if("FIRST"===l.selid)return u=n(l.args,0,a),u.length>0?(o=u[0],r+1+1>e.length?[o]:n(e,r+1,o)):[];if("LAST"===l.selid)return u=n(l.args,0,a),u.length>0?(o=u[u.length-1],r+1+1>e.length?[o]:n(e,r+1,o)):[];if("MIN"===l.selid){if(u=n(l.args,0,a),0===u.length)return[];var o=u.reduce(function(e,t){return Math.min(e,t)},1/0);return r+1+1>e.length?[o]:n(e,r+1,o)}if("MAX"===l.selid){var u=n(l.args,0,a);if(0===u.length)return[];var o=u.reduce(function(e,t){return Math.max(e,t)},-(1/0));return r+1+1>e.length?[o]:n(e,r+1,o)}if("PLUS"===l.selid){var y=[],N=n(l.args,0,a).slice();r+1+1>e.length?y=y.concat(N):N.forEach(function(t){y=y.concat(n(e,r+1,t))});for(var C=0;N.length>0;){var u=N.shift();if(u=n(l.args,0,u),N=N.concat(u),r+1+1>e.length?y=y.concat(u):u.forEach(function(t){var a=n(e,r+1,t);y=y.concat(a)}),++C>1e5)throw new Error("Security brake. Number of iterations = "+C)}return y}if("STAR"===l.selid){var y=[];y=n(e,r+1,a);var N=n(l.args,0,a).slice();r+1+1>e.length?y=y.concat(N):N.forEach(function(t){y=y.concat(n(e,r+1,t))});for(var C=0;N.length>0;){var u=N[0];if(N.shift(),u=n(l.args,0,u),N=N.concat(u),r+1+1<=e.length&&u.forEach(function(t){y=y.concat(n(e,r+1,t))}),++C>1e5)throw new Error("Loop brake. Number of iterations = "+C)}return y}if("QUESTION"===l.selid){var y=[];y=y.concat(n(e,r+1,a));var u=n(l.args,0,a);return r+1+1<=e.length&&u.forEach(function(t){y=y.concat(n(e,r+1,t))}),y}if("WITH"!==l.selid){if("ROOT"===l.selid)return r+1+1>e.length?[a]:n(e,r+1,s);throw new Error("Wrong selector "+l.selid)}var u=n(l.args,0,a);if(0===u.length)return[];var c={status:1,values:u}}else{if(!l.srchid)throw new Error("Selector not found");var c=A.srch[l.srchid.toUpperCase()](a,l.args,i,t)}void 0===c&&(c={status:1,values:[a]});var m=[];if(1===c.status){var x=c.values;if(r+1+1>e.length)m=x;else for(var C=0;C<c.values.length;C++)m=m.concat(n(e,r+1,x[C]))}return m}var a,s,i={},o=F(this.selectors);if(void 0!==o&&o.length>0&&(o&&o[0]&&"PROP"===o[0].srchid&&o[0].args&&o[0].args[0]&&("XML"===o[0].args[0].toUpperCase()?(i.mode="XML",o.shift()):"HTML"===o[0].args[0].toUpperCase()?(i.mode="HTML",o.shift()):"JSON"===o[0].args[0].toUpperCase()&&(i.mode="JSON",o.shift())),o.length>0&&"VALUE"===o[0].srchid&&(i.value=!0,o.shift())),this.from instanceof Q.Column){var u=this.from.databaseid||e;s=A.databases[u].tables[this.from.columnid].data}else if(this.from instanceof Q.FuncValue&&A.from[this.from.funcid.toUpperCase()]){var c=this.from.args.map(function(e){var r=e.toJS();return new Function("params,alasql","var y;return "+r).bind(this)(t,A)});s=A.from[this.from.funcid.toUpperCase()].apply(this,c)}else if(void 0===this.from)s=A.databases[e].objects;else{var l=new Function("params,alasql","var y;return "+this.from.toJS());s=l(t,A),"object"==typeof Mongo&&"object"!=typeof Mongo.Collection&&s instanceof Mongo.Collection&&(s=s.find().fetch())}if(a=void 0!==o&&o.length>0?n(o,0,s):s,this.into){var h,d;void 0!==this.into.args[0]&&(h=new Function("params,alasql","var y;return "+this.into.args[0].toJS())(t,A)),void 0!==this.into.args[1]&&(d=new Function("params,alasql","var y;return "+this.into.args[1].toJS())(t,A)),a=A.into[this.into.funcid.toUpperCase()](h,d,a,[],r)}else i.value&&a.length>0&&(a=a[0]),r&&(a=r(a));return a}function o(e,t,r,n,a){e.sources.length;e.sourceslen=e.sources.length;var s=e.sourceslen;e.query=e,e.A=n,e.B=a,e.cb=r,e.oldscope=t,e.queriesfn&&(e.sourceslen+=e.queriesfn.length,s+=e.queriesfn.length,e.queriesdata=[],e.queriesfn.forEach(function(t,r){t.query.params=e.params,u([],-r-1,e)}));var i;i=t?F(t):{},e.scope=i;var o;return e.sources.forEach(function(t,r){t.query=e;var n=t.datafn(e,e.params,u,r,A);void 0!==n&&((e.intofn||e.intoallfn)&&Array.isArray(n)&&(n=n.length),o=n),t.queriesdata=e.queriesdata}),0!=e.sources.length&&0!==s||(o=c(e)),o}function u(e,t,r){if(t>=0){var n=r.sources[t];n.data=e,"function"==typeof n.data&&(n.getfn=n.data,n.dontcache=n.getfn.dontcache,"OUTER"!=n.joinmode&&"RIGHT"!=n.joinmode&&"ANTI"!=n.joinmode||(n.dontcache=!1),n.data={})}else r.queriesdata[-t-1]=V(e);if(!(--r.sourceslen>0))return c(r)}function c(e){var t,r=e.scope;ee(e),e.data=[],e.xgroups={},e.groups=[];if(d(e,r,0),e.groupfn){if(e.data=[],0===e.groups.length){var n={};e.selectGroup.length>0&&e.selectGroup.forEach(function(e){"COUNT"==e.aggregatorid||"SUM"==e.aggregatorid?n[e.nick]=0:n[e.nick]=void 0}),e.groups=[n]}if(e.aggrKeys.length>0){var a="";e.aggrKeys.forEach(function(e){a+="g['"+e.nick+"']=alasql.aggr['"+e.funcid+"'](undefined,g['"+e.nick+"'],3);"});var s=new Function("g,params,alasql","var y;"+a)}for(var i=0,o=e.groups.length;i<o;i++){var n=e.groups[i];if(s&&s(n,e.params,A),!e.havingfn||e.havingfn(n,e.params,A)){var u=e.selectgfn(n,e.params,A);e.data.push(u)}}}if(h(e),e.unionallfn){var c,f;if(e.corresponding)e.unionallfn.query.modifier||(e.unionallfn.query.modifier=void 0),c=e.unionallfn(e.params);else{e.unionallfn.query.modifier||(e.unionallfn.query.modifier="RECORDSET"),f=e.unionallfn(e.params),c=[],o=f.data.length;for(var i=0;i<o;i++){for(var p={},b=Math.min(e.columns.length,f.columns.length)-1;0<=b;b--)p[e.columns[b].columnid]=f.data[i][f.columns[b].columnid];c.push(p)}}e.data=e.data.concat(c)}else if(e.unionfn){if(e.corresponding)e.unionfn.query.modifier||(e.unionfn.query.modifier="ARRAY"),c=e.unionfn(e.params);else{e.unionfn.query.modifier||(e.unionfn.query.modifier="RECORDSET"),f=e.unionfn(e.params),c=[],o=f.data.length;for(var i=0;i<o;i++){p={},t=Math.min(e.columns.length,f.columns.length);for(var b=0;b<t;b++)p[e.columns[b].columnid]=f.data[i][f.columns[b].columnid];c.push(p)}}e.data=M(e.data,c)}else if(e.exceptfn){if(e.corresponding){e.exceptfn.query.modifier||(e.exceptfn.query.modifier="ARRAY");var c=e.exceptfn(e.params)}else{e.exceptfn.query.modifier||(e.exceptfn.query.modifier="RECORDSET");for(var f=e.exceptfn(e.params),c=[],i=0,o=f.data.length;i<o;i++){for(var p={},b=Math.min(e.columns.length,f.columns.length)-1;0<=b;b--)p[e.columns[b].columnid]=f.data[i][f.columns[b].columnid];c.push(p)}}e.data=U(e.data,c)}else if(e.intersectfn){if(e.corresponding)e.intersectfn.query.modifier||(e.intersectfn.query.modifier=void 0),c=e.intersectfn(e.params);else for(e.intersectfn.query.modifier||(e.intersectfn.query.modifier="RECORDSET"),f=e.intersectfn(e.params),c=[],o=f.data.length,i=0;i<o;i++){for(p={},t=Math.min(e.columns.length,f.columns.length),b=0;b<t;b++)p[e.columns[b].columnid]=f.data[i][f.columns[b].columnid];c.push(p)}e.data=_(e.data,c)}if(e.orderfn){if(e.explain)var E=Date.now();e.data=e.data.sort(e.orderfn),e.explain&&e.explaination.push({explid:e.explid++,description:"QUERY BY",ms:Date.now()-E})}if(l(e),"undefined"!=typeof angular&&e.removeKeys.push("$$hashKey"),e.removeKeys.length>0){var g=e.removeKeys;if((t=g.length)>0)for(o=e.data.length,i=0;i<o;i++)for(b=0;b<t;b++)delete e.data[i][g[b]];e.columns.length>0&&(e.columns=e.columns.filter(function(e){var t=!1;return g.forEach(function(r){e.columnid==r&&(t=!0)}),!t}))}if(void 0!==e.removeLikeKeys&&e.removeLikeKeys.length>0){for(var m=e.removeLikeKeys,i=0,o=e.data.length;i<o;i++){p=e.data[i];for(var v in p)for(b=0;b<e.removeLikeKeys.length;b++)A.utils.like(e.removeLikeKeys[b],v)&&delete p[v]}e.columns.length>0&&(e.columns=e.columns.filter(function(e){var t=!1;return m.forEach(function(r){A.utils.like(r,e.columnid)&&(t=!0)}),!t}))}if(e.pivotfn&&e.pivotfn(),e.unpivotfn&&e.unpivotfn(),e.intoallfn){var S=e.intoallfn(e.columns,e.cb,e.params,e.alasql);return S}if(e.intofn){for(o=e.data.length,i=0;i<o;i++)e.intofn(e.data[i],i,e.params,e.alasql);return e.cb&&e.cb(e.data.length,e.A,e.B),e.data.length}return S=e.data,e.cb&&(S=e.cb(e.data,e.A,e.B)),S}function l(e){if(e.limit){var t=0;e.offset&&(t=(0|e.offset)-1||0);var r;r=e.percent?(e.data.length*e.limit/100|0)+t:(0|e.limit)+t,e.data=e.data.slice(t,r)}}function h(e){if(e.distinct){for(var t={},r=0,n=e.data.length;r<n;r++){t[Object.keys(e.data[r]).map(function(t){return e.data[r][t]}).join("`")]=e.data[r]}e.data=[];for(var a in t)e.data.push(t[a])}}function d(e,t,r){if(r>=e.sources.length)e.wherefn(t,e.params,A)&&(e.groupfn?e.groupfn(t,e.params,A):e.data.push(e.selectfn(t,e.params,A)));else if(e.sources[r].applyselect){var n=e.sources[r];n.applyselect(e.params,function(a){if(a.length>0)for(var s=0;s<a.length;s++)t[n.alias]=a[s],d(e,t,r+1);else"OUTER"==n.applymode&&(t[n.alias]={},d(e,t,r+1))},t)}else{var n=e.sources[r],a=e.sources[r+1],s=n.alias||n.tableid,i=!1,o=n.data,u=!1;(!n.getfn||n.getfn&&!n.dontcache)&&"RIGHT"!=n.joinmode&&"OUTER"!=n.joinmode&&"ANTI"!=n.joinmode&&"ix"==n.optimization&&(o=n.ix[n.onleftfn(t,e.params,A)]||[],u=!0);var c=0;if(void 0===o)throw new Error("Data source number "+r+" in undefined");for(var l,h=o.length;(l=o[c])||!u&&n.getfn&&(l=n.getfn(c))||c<h;)u||!n.getfn||n.dontcache||(o[c]=l),t[s]=l,n.onleftfn&&n.onleftfn(t,e.params,A)!=n.onrightfn(t,e.params,A)||n.onmiddlefn(t,e.params,A)&&("SEMI"!=n.joinmode&&"ANTI"!=n.joinmode&&d(e,t,r+1),"LEFT"!=n.joinmode&&"INNER"!=n.joinmode&&(l._rightjoin=!0),i=!0),c++;if("LEFT"!=n.joinmode&&"OUTER"!=n.joinmode&&"SEMI"!=n.joinmode||i||(t[s]={},d(e,t,r+1)),r+1<e.sources.length&&("OUTER"==a.joinmode||"RIGHT"==a.joinmode||"ANTI"==a.joinmode)){t[n.alias]={};for(var l,f=0,p=a.data.length;(l=a.data[f])||a.getfn&&(l=a.getfn(f))||f<p;)a.getfn&&!a.dontcache&&(a.data[f]=l),l._rightjoin?delete l._rightjoin:0==r&&(t[a.alias]=l,d(e,t,r+2)),f++}t[s]=void 0}}function f(e,t){if(void 0===t||"number"==typeof t||"string"==typeof t||"boolean"==typeof t)return t;var r=e.modifier||A.options.modifier,n=e.columns;if(void 0===n||0==n.length)if(t.length>0){for(var a={},s=Math.min(t.length,A.options.columnlookup||10)-1;0<=s;s--)for(var i in t[s])a[i]=!0;n=Object.keys(a).map(function(e){return{columnid:e}})}else n=[];if("VALUE"===r)if(t.length>0){var i;i=n&&n.length>0?n[0].columnid:Object.keys(t[0])[0],t=t[0][i]}else t=void 0;else if("ROW"===r)if(t.length>0){var i,o=[];for(var i in t[0])o.push(t[0][i]);t=o}else t=void 0;else if("COLUMN"===r){var u=[];if(t.length>0){var i;i=n&&n.length>0?n[0].columnid:Object.keys(t[0])[0];for(var s=0,c=t.length;s<c;s++)u.push(t[s][i])}t=u}else if("MATRIX"===r){for(var u=[],s=0;s<t.length;s++){for(var o=[],l=t[s],h=0;h<n.length;h++)o.push(l[n[h].columnid]);u.push(o)}t=u}else if("INDEX"===r){var i,d,u={};if(n&&n.length>0)i=n[0].columnid,d=n[1].columnid;else{var f=Object.keys(t[0]);i=f[0],d=f[1]}for(var s=0,c=t.length;s<c;s++)u[t[s][i]]=t[s][d];t=u}else if("RECORDSET"===r)t=new A.Recordset({columns:n,data:t});else if("TEXTSTRING"===r){var i;i=n&&n.length>0?n[0].columnid:Object.keys(t[0])[0];for(var s=0,c=t.length;s<c;s++)t[s]=t[s][i];t=t.join("\n")}return t}function p(e,t,r){var n="",a=[];e.ixsources={},e.sources.forEach(function(t){e.ixsources[t.alias]=t});var s;if(e.ixsources[t])var s=e.ixsources[t].columns;return r&&"json"==A.options.joinstar&&(n+="r['"+t+"']={};"),s&&s.length>0?s.forEach(function(s){r&&"underscore"==A.options.joinstar?a.push("'"+t+"_"+s.columnid+"':p['"+t+"']['"+s.columnid+"']"):r&&"json"==A.options.joinstar?n+="r['"+t+"']['"+s.columnid+"']=p['"+t+"']['"+s.columnid+"'];":a.push("'"+s.columnid+"':p['"+t+"']['"+s.columnid+"']"),e.selectColumns[R(s.columnid)]=!0;var i={columnid:s.columnid,dbtypeid:s.dbtypeid,dbsize:s.dbsize,dbprecision:s.dbprecision,dbenum:s.dbenum};e.columns.push(i),e.xcolumns[i.columnid]=i}):(n+='var w=p["'+t+'"];for(var k in w){r[k]=w[k]};',e.dirtyColumns=!0),{s:a.join(","),sp:n}}function b(e,t){if(Array.isArray(e)){for(var r=[[]],n=0;n<e.length;n++)if(e[n]instanceof Q.Column)e[n].nick=R(e[n].columnid),t.groupColumns[e[n].nick]=e[n].nick,r=r.map(function(r){return r.concat(e[n].nick+"\t"+e[n].toJS("p",t.sources[0].alias,t.defcols))});else if(e[n]instanceof Q.FuncValue)t.groupColumns[R(e[n].toString())]=R(e[n].toString()),r=r.map(function(r){return r.concat(R(e[n].toString())+"\t"+e[n].toJS("p",t.sources[0].alias,t.defcols))});else if(e[n]instanceof Q.GroupExpression)if("ROLLUP"==e[n].type)r=ae(r,te(e[n].group,t));else if("CUBE"==e[n].type)r=ae(r,re(e[n].group,t));else{if("GROUPING SETS"!=e[n].type)throw new Error("Unknown grouping function");r=ae(r,ne(e[n].group,t))}else r=""===e[n]?[["1\t1"]]:r.map(function(r){return t.groupColumns[R(e[n].toString())]=R(e[n].toString()),r.concat(R(e[n].toString())+"\t"+e[n].toJS("p",t.sources[0].alias,t.defcols))});return r}return e instanceof Q.FuncValue?(t.groupColumns[R(e.toString())]=R(e.toString()),[e.toString()+"\t"+e.toJS("p",t.sources[0].alias,t.defcols)]):e instanceof Q.Column?(e.nick=R(e.columnid),t.groupColumns[e.nick]=e.nick,[e.nick+"\t"+e.toJS("p",t.sources[0].alias,t.defcols)]):(t.groupColumns[R(e.toString())]=R(e.toString()),[R(e.toString())+"\t"+e.toJS("p",t.sources[0].alias,t.defcols)])}function E(e,t,r,n){var a="";if("string"==typeof e)a='"'+e+'"';else if("number"==typeof e)a="("+e+")";else if("boolean"==typeof e)a=e;else{if("object"!=typeof e)throw new Error("2Can not parse JSON object "+JSON.stringify(e));if(Array.isArray(e))a+="["+e.map(function(e){return E(e,t,r,n)}).join(",")+"]";else if(!e.toJS||e instanceof Q.Json){a="{";var s=[];for(var i in e){var o="";if("string"==typeof i)o+='"'+i+'"';else if("number"==typeof i)o+=i;else{if("boolean"!=typeof i)throw new Error("THis is not ES6... no expressions on left side yet");o+=i}o+=":"+E(e[i],t,r,n),s.push(o)}a+=s.join(",")+"}"}else{if(!e.toJS)throw new Error("1Can not parse JSON object "+JSON.stringify(e));a=e.toJS(t,r,n)}}return a}function g(e){var t="";if(void 0===e)t+="undefined";else if(Array.isArray(e)){t+="<style>",t+="table {border:1px black solid; border-collapse: collapse; border-spacing: 0px;}",t+="td,th {border:1px black solid; padding-left:5px; padding-right:5px}",t+="th {background-color: #EEE}",t+="</style>",t+="<table>";var r=[];for(var n in e[0])r.push(n);t+="<tr><th>#",r.forEach(function(e){t+="<th>"+e});for(var a=0,s=e.length;a<s;a++)t+="<tr><th>"+(a+1),r.forEach(function(r){t+="<td> ",e[a][r]==+e[a][r]?(t+='<div style="text-align:right">',t+=void 0===e[a][r]?"NULL":e[a][r],t+="</div>"):t+=void 0===e[a][r]?"NULL":"string"==typeof e[a][r]?e[a][r]:ce(e[a][r])});t+="</table>"}else t+="<p>"+ce(e)+"</p>";return t}function m(e,t,r){if(!(r<=0)){var n=t-e.scrollTop,a=n/r*10;setTimeout(function(){e.scrollTop!==t&&(e.scrollTop=e.scrollTop+a,m(e,t,r-10))},10)}}function S(e,t,r,n,a,s){function i(e){for(var t="",r=0,n=10240;r<e.byteLength/n;++r)t+=String.fromCharCode.apply(null,new Uint8Array(e.slice(r*n,r*n+n)));return t+=String.fromCharCode.apply(null,new Uint8Array(e.slice(r*n)))}var o={};r=r||{},A.utils.extend(o,r),void 0===o.headers&&(o.headers=!0);t=A.utils.autoExtFilename(t,"xls",r),A.utils.loadBinaryFile(t,!!n,function(t){if(t instanceof ArrayBuffer)var r=i(t),u=e.read(btoa(r),{type:"base64"});else var u=e.read(t,{type:"binary"});var c;c=void 0===o.sheetid?u.SheetNames[0]:o.sheetid;var l;void 0===o.range?l=u.Sheets[c]["!ref"]:(l=o.range,u.Sheets[c][l]&&(l=u.Sheets[c][l]));for(var h=l.split(":"),d=h[0].match(/[A-Z]+/)[0],f=+h[0].match(/[0-9]+/)[0],p=h[1].match(/[A-Z]+/)[0],b=+h[1].match(/[0-9]+/)[0],E={},g=A.utils.xlscn(d),m=A.utils.xlscn(p),v=g;v<=m;v++){var S=A.utils.xlsnc(v);o.headers&&u.Sheets[c][S+""+f]?E[S]=u.Sheets[c][S+""+f].v:E[S]=S}var T=[];o.headers&&f++;for(var y=f;y<=b;y++){for(var N={},v=g;v<=m;v++){var S=A.utils.xlsnc(v);u.Sheets[c][S+""+y]&&(N[E[S]]=u.Sheets[c][S+""+y].v)}T.push(N)}T.length>0&&T[T.length-1]&&0==Object.keys(T[T.length-1]).length&&T.pop(),n&&(T=n(T,a,s))},function(e){throw e})}function T(e){function t(){if(i(/^<\?xml\s*/)){for(var e={attributes:{}};!o()&&!u("?>");){var t=a();if(!t)return e;e.attributes[t.name]=t.value}return i(/\?>\s*/),e}}function r(){var e=i(/^<([\w-:.]+)\s*/);if(e){for(var t={name:e[1],attributes:{},children:[]};!(o()||u(">")||u("?>")||u("/>"));){var s=a();if(!s)return t;t.attributes[s.name]=s.value}if(i(/^\s*\/>\s*/))return t;i(/\??>\s*/),t.content=n();for(var c;c=r();)t.children.push(c);return i(/^<\/[\w-:.]+>\s*/),t}}function n(){var e=i(/^([^<]*)/);return e?e[1]:""}function a(){var e=i(/([\w:-]+)\s*=\s*("[^"]*"|'[^']*'|\w+)\s*/);if(e)return{name:e[1],value:s(e[2])}}function s(e){return e.replace(/^['"]|['"]$/g,"")}function i(t){var r=e.match(t);if(r)return e=e.slice(r[0].length),r}function o(){return 0==e.length}function u(t){return 0==e.indexOf(t)}return e=e.trim(),e=e.replace(/<!--[\s\S]*?-->/g,""),function(){return{declaration:t(),root:r()}}()}var A=function(e,t,r,n){if(t=t||[],"function"!=typeof importScripts&&A.webworker){var a=A.lastid++;return A.buffer[a]=r,void A.webworker.postMessage({id:a,sql:e,params:t})}return 0===arguments.length?new Q.Select({columns:[new Q.Column({columnid:"*"})],from:[new Q.ParamValue({param:0})]}):1===arguments.length&&e.constructor===Array?A.promise(e):("function"==typeof t&&(n=r,r=t,t=[]),"object"!=typeof t&&(t=[t]),"string"==typeof e&&"#"===e[0]&&"object"==typeof document?e=document.querySelector(e).textContent:"object"==typeof e&&e instanceof HTMLElement?e=e.textContent:"function"==typeof e&&(e=e.toString().slice(14,-3)),A.exec(e,t,r,n))};A.version="0.3.9",A.debug=void 0;var y=function(){return null},N=function(){function e(){this.yy={}}var t=function(e,t,r,n){for(r=r||{},n=e.length;n--;r[e[n]]=t);return r
},r=[2,13],n=[1,104],a=[1,102],s=[1,103],i=[1,6],o=[1,42],u=[1,79],c=[1,76],l=[1,94],h=[1,93],d=[1,69],f=[1,101],p=[1,85],b=[1,64],E=[1,71],g=[1,84],m=[1,66],v=[1,70],S=[1,68],T=[1,61],y=[1,74],N=[1,62],C=[1,67],R=[1,83],O=[1,77],I=[1,86],x=[1,87],D=[1,81],k=[1,82],L=[1,80],$=[1,88],M=[1,89],U=[1,90],_=[1,91],F=[1,92],P=[1,98],q=[1,65],G=[1,78],V=[1,72],B=[1,96],j=[1,97],H=[1,63],J=[1,73],Y=[1,108],W=[1,107],X=[10,306,602,764],K=[10,306,310,602,764],Q=[1,115],z=[1,116],Z=[1,117],ee=[1,118],te=[1,119],re=[130,353,410],ne=[1,127],ae=[1,126],se=[1,134],ie=[1,164],oe=[1,175],ue=[1,178],ce=[1,173],le=[1,181],he=[1,185],de=[1,160],fe=[1,182],pe=[1,169],be=[1,171],Ee=[1,174],ge=[1,183],me=[1,166],ve=[1,193],Se=[1,188],Te=[1,189],Ae=[1,194],ye=[1,195],Ne=[1,196],Ce=[1,197],Re=[1,198],Oe=[1,199],we=[1,200],Ie=[1,201],xe=[1,202],De=[1,176],ke=[1,177],Le=[1,179],$e=[1,180],Me=[1,186],Ue=[1,192],_e=[1,184],Fe=[1,187],Pe=[1,172],qe=[1,170],Ge=[1,191],Ve=[1,203],Be=[2,4,5],je=[2,471],He=[1,206],Je=[1,211],Ye=[1,220],We=[1,216],Xe=[10,72,78,93,98,118,128,162,168,169,183,198,232,245,247,306,310,602,764],Ke=[2,4,5,10,72,76,77,78,112,115,116,118,122,123,124,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,183,185,187,198,280,281,282,283,284,285,286,287,288,306,310,420,424,602,764],Qe=[2,4,5,10,53,72,74,76,77,78,89,93,95,98,99,107,112,115,116,118,122,123,124,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,179,180,181,183,185,187,189,198,206,208,222,223,224,225,226,227,228,229,232,239,242,243,245,247,266,267,280,281,282,283,284,285,286,287,288,290,296,300,306,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,330,331,332,333,335,338,339,396,400,401,404,406,408,409,417,418,420,424,434,436,437,439,440,441,442,443,447,448,451,452,464,470,505,507,508,517,602,764],ze=[1,249],Ze=[1,256],et=[1,265],tt=[1,270],rt=[1,269],nt=[2,4,5,10,72,77,78,93,98,107,118,128,131,132,137,143,145,149,152,154,156,162,168,169,179,180,181,183,198,232,245,247,265,266,270,271,273,280,281,282,283,284,285,286,287,288,290,291,292,293,294,295,296,297,298,299,302,303,306,310,312,317,420,424,602,764],at=[2,162],st=[1,281],it=[10,74,78,306,310,505,602,764],ot=[2,4,5,10,53,72,74,76,77,78,89,93,95,98,99,107,112,115,116,118,122,123,124,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,179,180,181,183,185,187,189,193,198,206,208,222,223,224,225,226,227,228,229,230,231,232,239,242,243,245,247,266,267,280,281,282,283,284,285,286,287,288,290,296,297,300,302,306,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,330,331,332,333,335,338,339,343,344,356,368,369,370,373,374,386,389,396,400,401,402,403,404,405,406,408,409,417,418,420,424,426,433,434,436,437,439,440,441,442,443,447,448,451,452,464,470,505,507,508,514,515,516,517,602,764],ut=[2,4,5,10,53,72,89,124,146,156,189,266,267,290,306,335,338,339,396,400,401,404,406,408,409,417,418,434,436,437,439,440,441,442,443,447,448,451,452,505,507,508,517,602,764],ct=[1,562],lt=[1,564],ht=[2,503],dt=[1,569],ft=[1,580],pt=[1,583],bt=[1,584],Et=[10,78,89,132,137,146,189,296,306,310,470,602,764],gt=[10,74,306,310,602,764],mt=[2,567],vt=[1,602],St=[2,4,5,156],Tt=[1,640],At=[1,612],yt=[1,646],Nt=[1,647],Ct=[1,620],Rt=[1,631],Ot=[1,618],wt=[1,626],It=[1,619],xt=[1,627],Dt=[1,629],kt=[1,621],Lt=[1,622],$t=[1,641],Mt=[1,638],Ut=[1,639],_t=[1,615],Ft=[1,617],Pt=[1,609],qt=[1,610],Gt=[1,611],Vt=[1,613],Bt=[1,614],jt=[1,616],Ht=[1,623],Jt=[1,624],Yt=[1,628],Wt=[1,630],Xt=[1,632],Kt=[1,633],Qt=[1,634],zt=[1,635],Zt=[1,636],er=[1,642],tr=[1,643],rr=[1,644],nr=[1,645],ar=[2,287],sr=[2,4,5,10,53,72,74,76,77,78,89,93,95,98,99,107,112,115,116,118,122,123,124,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,179,180,181,183,185,187,189,198,206,208,222,223,224,225,226,227,228,229,230,231,232,239,242,243,245,247,266,267,280,281,282,283,284,285,286,287,288,290,296,297,300,306,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,330,331,332,333,335,338,339,343,356,368,369,373,374,396,400,401,404,406,408,409,417,418,420,424,426,434,436,437,439,440,441,442,443,447,448,451,452,464,470,505,507,508,517,602,764],ir=[2,359],or=[1,668],ur=[1,678],cr=[2,4,5,10,53,72,74,76,77,78,89,93,95,98,99,107,112,115,116,118,122,123,124,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,179,180,181,183,185,187,189,198,206,208,222,223,224,225,226,227,228,229,230,231,232,239,242,243,245,247,266,267,280,281,282,283,284,285,286,287,288,290,296,300,306,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,330,331,332,333,335,338,339,396,400,401,404,406,408,409,417,418,420,424,426,434,436,437,439,440,441,442,443,447,448,451,452,464,470,505,507,508,517,602,764],lr=[1,694],hr=[1,703],dr=[1,702],fr=[2,4,5,10,72,74,78,93,98,118,128,162,168,169,206,208,222,223,224,225,226,227,228,229,230,231,232,245,247,306,310,602,764],pr=[10,72,74,78,93,98,118,128,162,168,169,206,208,222,223,224,225,226,227,228,229,230,231,232,245,247,306,310,602,764],br=[2,202],Er=[1,725],gr=[10,72,78,93,98,118,128,162,168,169,183,232,245,247,306,310,602,764],mr=[2,163],vr=[1,728],Sr=[2,4,5,112],Tr=[1,741],Ar=[1,760],yr=[1,740],Nr=[1,739],Cr=[1,734],Rr=[1,735],Or=[1,737],wr=[1,738],Ir=[1,742],xr=[1,743],Dr=[1,744],kr=[1,745],Lr=[1,746],$r=[1,747],Mr=[1,748],Ur=[1,749],_r=[1,750],Fr=[1,751],Pr=[1,752],qr=[1,753],Gr=[1,754],Vr=[1,755],Br=[1,756],jr=[1,757],Hr=[1,759],Jr=[1,761],Yr=[1,762],Wr=[1,763],Xr=[1,764],Kr=[1,765],Qr=[1,766],zr=[1,767],Zr=[1,770],en=[1,771],tn=[1,772],rn=[1,773],nn=[1,774],an=[1,775],sn=[1,776],on=[1,777],un=[1,778],cn=[1,779],ln=[1,780],hn=[1,781],dn=[74,89,189],fn=[10,74,78,154,187,230,297,306,310,343,356,368,369,373,374,602,764],pn=[1,798],bn=[10,74,78,300,306,310,602,764],En=[1,799],gn=[1,805],mn=[1,806],vn=[1,810],Sn=[10,74,78,306,310,602,764],Tn=[2,4,5,77,131,132,137,143,145,149,152,154,156,179,180,181,265,266,270,271,273,280,281,282,283,284,285,286,287,288,290,291,292,293,294,295,296,297,298,299,302,303,312,317,420,424],An=[10,72,78,93,98,107,118,128,162,168,169,183,198,232,245,247,306,310,602,764],yn=[2,4,5,10,72,77,78,93,98,107,118,128,131,132,137,143,145,149,152,154,156,162,164,168,169,179,180,181,183,185,187,195,198,232,245,247,265,266,270,271,273,280,281,282,283,284,285,286,287,288,290,291,292,293,294,295,296,297,298,299,302,303,306,310,312,317,420,424,602,764],Nn=[2,4,5,132,296],Cn=[1,844],Rn=[10,74,76,78,306,310,602,764],On=[2,738],wn=[10,74,76,78,132,139,141,145,152,306,310,420,424,602,764],In=[2,1161],xn=[10,74,76,78,139,141,145,152,306,310,420,424,602,764],Dn=[10,74,76,78,139,141,145,306,310,420,424,602,764],kn=[10,74,78,139,141,306,310,602,764],Ln=[10,78,89,132,146,189,296,306,310,470,602,764],$n=[335,338,339],Mn=[2,764],Un=[1,869],_n=[1,870],Fn=[1,871],Pn=[1,872],qn=[1,881],Gn=[1,880],Vn=[164,166,334],Bn=[2,444],jn=[1,936],Hn=[2,4,5,77,131,156,290,291,292,293],Jn=[1,951],Yn=[2,4,5,10,53,72,74,76,77,78,89,93,95,98,99,107,112,118,122,124,128,129,130,131,132,134,135,137,139,140,141,142,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,179,181,183,185,187,189,198,206,208,222,223,224,225,226,227,228,229,232,239,242,243,245,247,266,267,280,281,282,283,284,285,286,287,288,290,296,300,306,308,309,310,311,313,314,315,317,318,319,320,321,322,323,324,325,326,330,331,332,333,335,338,339,396,400,401,404,406,408,409,417,418,420,424,434,436,437,439,440,441,442,443,447,448,451,452,464,470,505,507,508,517,602,764],Wn=[2,4,5,10,53,72,74,76,77,78,89,93,95,98,99,107,112,115,116,118,122,123,124,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,179,180,181,183,185,187,189,198,206,208,222,223,224,225,226,227,228,229,232,239,242,243,245,247,266,267,280,281,282,283,284,285,286,287,288,290,296,300,306,308,309,310,311,312,313,314,315,317,318,319,320,321,322,323,324,325,326,330,331,332,333,335,338,339,396,400,401,404,406,408,409,417,418,420,424,434,436,437,439,440,441,442,443,447,448,451,452,464,470,505,507,508,517,602,764],Xn=[2,375],Kn=[1,958],Qn=[306,308,310],zn=[74,300],Zn=[74,300,426],ea=[1,965],ta=[2,4,5,10,53,72,74,76,78,89,93,95,98,99,107,112,115,116,118,122,123,124,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,179,180,181,183,185,187,189,198,206,208,222,223,224,225,226,227,228,229,232,239,242,243,245,247,266,267,280,281,282,283,284,285,286,287,288,290,296,300,306,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,330,331,332,333,335,338,339,396,400,401,404,406,408,409,417,418,420,424,434,436,437,439,440,441,442,443,447,448,451,452,464,470,505,507,508,517,602,764],ra=[74,426],na=[1,978],aa=[1,977],sa=[1,984],ia=[10,72,78,93,98,118,128,162,168,169,232,245,247,306,310,602,764],oa=[1,1010],ua=[10,72,78,306,310,602,764],ca=[1,1016],la=[1,1017],ha=[1,1018],da=[2,4,5,10,72,74,76,77,78,112,115,116,118,122,123,124,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,179,180,181,183,185,187,198,280,281,282,283,284,285,286,287,288,306,310,420,424,602,764],fa=[1,1068],pa=[1,1067],ba=[1,1081],Ea=[1,1080],ga=[1,1088],ma=[10,72,74,78,93,98,107,118,128,162,168,169,183,198,232,245,247,306,310,602,764],va=[1,1119],Sa=[10,78,89,146,189,306,310,470,602,764],Ta=[1,1139],Aa=[1,1138],ya=[1,1137],Na=[2,4,5,10,53,72,74,76,77,78,89,93,95,98,99,107,112,115,116,118,122,123,124,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,179,180,181,183,185,187,189,198,206,208,222,223,224,225,226,227,228,229,230,232,239,242,243,245,247,266,267,280,281,282,283,284,285,286,287,288,290,296,297,300,306,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,330,331,332,333,335,338,339,343,356,368,369,373,374,396,400,401,404,406,408,409,417,418,420,424,434,436,437,439,440,441,442,443,447,448,451,452,464,470,505,507,508,517,602,764],Ca=[1,1153],Ra=[2,4,5,10,53,72,74,76,77,78,89,93,95,98,99,107,112,118,122,124,128,129,130,131,132,134,135,137,139,140,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,181,183,185,187,189,198,206,208,222,223,224,225,226,227,228,229,232,239,242,243,245,247,266,267,280,281,282,283,284,285,286,287,288,290,296,300,306,308,309,310,311,313,314,315,320,321,322,323,324,325,326,330,331,332,333,335,338,339,396,400,401,404,406,408,409,417,418,420,424,434,436,437,439,440,441,442,443,447,448,451,452,464,470,505,507,508,517,602,764],Oa=[2,4,5,10,53,72,74,76,77,78,89,93,95,98,99,107,112,118,122,124,128,129,130,131,132,134,135,137,139,140,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,181,183,185,187,189,198,206,208,222,223,224,225,226,227,228,229,232,239,242,243,245,247,266,267,280,281,282,283,284,285,286,287,288,290,296,300,306,308,309,310,311,313,315,320,321,322,323,324,325,326,330,331,332,333,335,338,339,396,400,401,404,406,408,409,417,418,420,424,434,436,437,439,440,441,442,443,447,448,451,452,464,470,505,507,508,517,602,764],wa=[2,4,5,10,53,72,74,76,77,78,89,93,95,98,99,107,112,118,122,124,128,129,130,131,132,133,134,135,137,138,139,140,141,142,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,179,180,181,183,185,187,189,198,206,208,222,223,224,225,226,227,228,229,232,239,242,243,245,247,266,267,280,281,282,283,284,285,286,287,288,290,296,300,306,308,309,310,311,313,314,315,317,318,319,320,321,322,323,324,325,326,330,331,332,333,335,338,339,396,400,401,404,406,408,409,417,418,420,424,434,436,437,439,440,441,442,443,447,448,451,452,464,470,505,507,508,517,602,764],Ia=[2,4,5,10,53,72,74,76,77,78,89,93,95,98,99,107,112,118,122,124,128,129,130,131,132,134,135,137,139,140,141,142,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,181,183,185,187,189,198,206,208,222,223,224,225,226,227,228,229,232,239,242,243,245,247,266,267,280,281,282,283,284,285,286,287,288,290,296,300,306,308,309,310,311,313,314,315,318,319,320,321,322,323,324,325,326,330,331,332,333,335,338,339,396,400,401,404,406,408,409,417,418,420,424,434,436,437,439,440,441,442,443,447,448,451,452,464,470,505,507,508,517,602,764],xa=[2,4,5,10,53,72,74,76,77,78,89,93,95,98,99,107,118,122,124,128,129,130,131,132,134,135,137,139,140,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,181,183,185,187,189,198,206,208,222,223,224,225,226,227,228,229,232,239,242,243,245,247,266,267,280,281,282,283,284,285,286,287,288,290,296,300,306,308,309,310,314,320,321,322,323,324,325,326,330,331,333,335,338,339,396,400,401,404,406,408,409,417,418,420,424,434,436,437,439,440,441,442,443,447,448,451,452,464,470,505,507,508,517,602,764],Da=[2,406],ka=[2,4,5,10,53,72,74,76,77,78,89,93,95,98,107,118,122,128,129,130,131,132,134,135,137,143,145,146,148,149,150,152,156,162,164,166,168,169,170,171,172,173,175,181,183,185,187,189,198,206,208,222,223,224,225,226,227,228,229,232,239,242,243,245,247,266,267,280,281,282,283,284,285,286,287,288,290,296,300,306,308,309,310,314,330,331,333,335,338,339,396,400,401,404,406,408,409,417,418,420,424,434,436,437,439,440,441,442,443,447,448,451,452,464,470,505,507,508,517,602,764],La=[2,285],$a=[2,4,5,10,53,72,74,76,77,78,89,93,95,98,99,107,112,115,116,118,122,123,124,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,179,180,181,183,185,187,189,198,206,208,222,223,224,225,226,227,228,229,232,239,242,243,245,247,266,267,280,281,282,283,284,285,286,287,288,290,296,300,306,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,330,331,332,333,335,338,339,396,400,401,404,406,408,409,417,418,420,424,426,434,436,437,439,440,441,442,443,447,448,451,452,464,470,505,507,508,517,602,764],Ma=[10,78,306,310,602,764],Ua=[1,1189],_a=[10,77,78,143,145,152,181,302,306,310,420,424,602,764],Fa=[10,74,78,306,308,310,464,602,764],Pa=[1,1200],qa=[10,72,78,118,128,162,168,169,232,245,247,306,310,602,764],Ga=[10,72,74,78,93,98,118,128,162,168,169,183,198,232,245,247,306,310,602,764],Va=[2,4,5,72,76,77,78,112,115,116,118,122,123,124,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,185,187,280,281,282,283,284,285,286,287,288,420,424],Ba=[2,4,5,72,74,76,77,78,112,115,116,118,122,123,124,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,185,187,280,281,282,283,284,285,286,287,288,420,424],ja=[2,1085],Ha=[2,4,5,72,74,76,77,112,115,116,118,122,123,124,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,185,187,280,281,282,283,284,285,286,287,288,420,424],Ja=[1,1252],Ya=[10,74,78,128,306,308,310,464,602,764],Wa=[115,116,124],Xa=[2,584],Ka=[1,1280],Qa=[76,139],za=[2,724],Za=[1,1297],es=[1,1298],ts=[2,4,5,10,53,72,76,89,124,146,156,189,230,266,267,290,306,310,335,338,339,396,400,401,404,406,408,409,417,418,434,436,437,439,440,441,442,443,447,448,451,452,505,507,508,517,602,764],rs=[2,330],ns=[1,1322],as=[1,1336],ss=[1,1338],is=[2,487],os=[74,78],us=[10,306,308,310,464,602,764],cs=[10,72,78,118,162,168,169,232,245,247,306,310,602,764],ls=[1,1354],hs=[1,1358],ds=[1,1359],fs=[1,1361],ps=[1,1362],bs=[1,1363],Es=[1,1364],gs=[1,1365],ms=[1,1366],vs=[1,1367],Ss=[1,1368],Ts=[10,72,74,78,93,98,118,128,162,168,169,206,208,222,223,224,225,226,227,228,229,232,245,247,306,310,602,764],As=[1,1393],ys=[10,72,78,118,162,168,169,245,247,306,310,602,764],Ns=[10,72,78,93,98,118,128,162,168,169,206,208,222,223,224,225,226,227,228,229,232,245,247,306,310,602,764],Cs=[1,1490],Rs=[1,1492],Os=[2,4,5,77,143,145,152,156,181,290,291,292,293,302,420,424],ws=[1,1506],Is=[10,72,74,78,162,168,169,245,247,306,310,602,764],xs=[1,1524],Ds=[1,1526],ks=[1,1527],Ls=[1,1523],$s=[1,1522],Ms=[1,1521],Us=[1,1528],_s=[1,1518],Fs=[1,1519],Ps=[1,1520],qs=[1,1545],Gs=[2,4,5,10,53,72,89,124,146,156,189,266,267,290,306,310,335,338,339,396,400,401,404,406,408,409,417,418,434,436,437,439,440,441,442,443,447,448,451,452,505,507,508,517,602,764],Vs=[1,1556],Bs=[1,1564],js=[1,1563],Hs=[10,72,78,162,168,169,245,247,306,310,602,764],Js=[10,72,78,93,98,118,128,162,168,169,206,208,222,223,224,225,226,227,228,229,230,231,232,245,247,306,310,602,764],Ys=[2,4,5,10,72,78,93,98,118,128,162,168,169,206,208,222,223,224,225,226,227,228,229,230,231,232,245,247,306,310,602,764],Ws=[1,1621],Xs=[1,1623],Ks=[1,1620],Qs=[1,1622],zs=[187,193,368,369,370,373],Zs=[2,515],ei=[1,1628],ti=[1,1647],ri=[10,72,78,162,168,169,306,310,602,764],ni=[1,1657],ai=[1,1658],si=[1,1659],ii=[1,1678],oi=[4,10,243,306,310,343,356,602,764],ui=[1,1726],ci=[10,72,74,78,118,162,168,169,239,245,247,306,310,602,764],li=[2,4,5,77],hi=[1,1820],di=[1,1832],fi=[1,1851],pi=[10,72,78,162,168,169,306,310,415,602,764],bi=[10,74,78,230,306,310,602,764],Ei={trace:function(){},yy:{},symbols_:{error:2,Literal:3,LITERAL:4,BRALITERAL:5,NonReserved:6,LiteralWithSpaces:7,main:8,Statements:9,EOF:10,Statements_group0:11,AStatement:12,ExplainStatement:13,EXPLAIN:14,QUERY:15,PLAN:16,Statement:17,AlterTable:18,AttachDatabase:19,Call:20,CreateDatabase:21,CreateIndex:22,CreateGraph:23,CreateTable:24,CreateView:25,CreateEdge:26,CreateVertex:27,Declare:28,Delete:29,DetachDatabase:30,DropDatabase:31,DropIndex:32,DropTable:33,DropView:34,If:35,Insert:36,Merge:37,Reindex:38,RenameTable:39,Select:40,ShowCreateTable:41,ShowColumns:42,ShowDatabases:43,ShowIndex:44,ShowTables:45,TruncateTable:46,WithSelect:47,CreateTrigger:48,DropTrigger:49,BeginTransaction:50,CommitTransaction:51,RollbackTransaction:52,EndTransaction:53,UseDatabase:54,Update:55,JavaScript:56,Source:57,Assert:58,While:59,Continue:60,Break:61,BeginEnd:62,Print:63,Require:64,SetVariable:65,ExpressionStatement:66,AddRule:67,Query:68,Echo:69,CreateFunction:70,CreateAggregate:71,WITH:72,WithTablesList:73,COMMA:74,WithTable:75,AS:76,LPAR:77,RPAR:78,SelectClause:79,Select_option0:80,IntoClause:81,FromClause:82,Select_option1:83,WhereClause:84,GroupClause:85,OrderClause:86,LimitClause:87,UnionClause:88,SEARCH:89,Select_repetition0:90,Select_option2:91,PivotClause:92,PIVOT:93,Expression:94,FOR:95,PivotClause_option0:96,PivotClause_option1:97,UNPIVOT:98,IN:99,ColumnsList:100,PivotClause_option2:101,PivotClause2:102,AsList:103,AsLiteral:104,AsPart:105,RemoveClause:106,REMOVE:107,RemoveClause_option0:108,RemoveColumnsList:109,RemoveColumn:110,Column:111,LIKE:112,StringValue:113,ArrowDot:114,ARROW:115,DOT:116,SearchSelector:117,ORDER:118,BY:119,OrderExpressionsList:120,SearchSelector_option0:121,DOTDOT:122,CARET:123,EQ:124,SearchSelector_repetition_plus0:125,SearchSelector_repetition_plus1:126,SearchSelector_option1:127,WHERE:128,OF:129,CLASS:130,NUMBER:131,STRING:132,SLASH:133,VERTEX:134,EDGE:135,EXCLAMATION:136,SHARP:137,MODULO:138,GT:139,LT:140,GTGT:141,LTLT:142,DOLLAR:143,Json:144,AT:145,SET:146,SetColumnsList:147,TO:148,VALUE:149,ROW:150,ExprList:151,COLON:152,PlusStar:153,NOT:154,SearchSelector_repetition2:155,IF:156,SearchSelector_repetition3:157,Aggregator:158,SearchSelector_repetition4:159,SearchSelector_group0:160,SearchSelector_repetition5:161,UNION:162,SearchSelectorList:163,ALL:164,SearchSelector_repetition6:165,ANY:166,SearchSelector_repetition7:167,INTERSECT:168,EXCEPT:169,AND:170,OR:171,PATH:172,RETURN:173,ResultColumns:174,REPEAT:175,SearchSelector_repetition8:176,SearchSelectorList_repetition0:177,SearchSelectorList_repetition1:178,PLUS:179,STAR:180,QUESTION:181,SearchFrom:182,FROM:183,SelectModifier:184,DISTINCT:185,TopClause:186,UNIQUE:187,SelectClause_option0:188,SELECT:189,COLUMN:190,MATRIX:191,TEXTSTRING:192,INDEX:193,RECORDSET:194,TOP:195,NumValue:196,TopClause_option0:197,INTO:198,Table:199,FuncValue:200,ParamValue:201,VarValue:202,FromTablesList:203,JoinTablesList:204,ApplyClause:205,CROSS:206,APPLY:207,OUTER:208,FromTable:209,FromTable_option0:210,FromTable_option1:211,INDEXED:212,INSERTED:213,FromString:214,JoinTable:215,JoinMode:216,JoinTableAs:217,OnClause:218,JoinTableAs_option0:219,JoinTableAs_option1:220,JoinModeMode:221,NATURAL:222,JOIN:223,INNER:224,LEFT:225,RIGHT:226,FULL:227,SEMI:228,ANTI:229,ON:230,USING:231,GROUP:232,GroupExpressionsList:233,HavingClause:234,GroupExpression:235,GROUPING:236,ROLLUP:237,CUBE:238,HAVING:239,CORRESPONDING:240,OrderExpression:241,DIRECTION:242,COLLATE:243,NOCASE:244,LIMIT:245,OffsetClause:246,OFFSET:247,LimitClause_option0:248,FETCH:249,LimitClause_option1:250,LimitClause_option2:251,LimitClause_option3:252,ResultColumn:253,Star:254,AggrValue:255,Op:256,LogicValue:257,NullValue:258,ExistsValue:259,CaseValue:260,CastClause:261,ArrayValue:262,NewClause:263,Expression_group0:264,CURRENT_TIMESTAMP:265,JAVASCRIPT:266,CREATE:267,FUNCTION:268,AGGREGATE:269,NEW:270,CAST:271,ColumnType:272,CONVERT:273,PrimitiveValue:274,OverClause:275,OVER:276,OverPartitionClause:277,OverOrderByClause:278,PARTITION:279,SUM:280,COUNT:281,MIN:282,MAX:283,AVG:284,FIRST:285,LAST:286,AGGR:287,ARRAY:288,FuncValue_option0:289,REPLACE:290,DATEADD:291,DATEDIFF:292,INTERVAL:293,TRUE:294,FALSE:295,NSTRING:296,NULL:297,EXISTS:298,ARRAYLBRA:299,RBRA:300,ParamValue_group0:301,BRAQUESTION:302,CASE:303,WhensList:304,ElseClause:305,END:306,When:307,WHEN:308,THEN:309,ELSE:310,REGEXP:311,TILDA:312,GLOB:313,ESCAPE:314,NOT_LIKE:315,BARBAR:316,MINUS:317,AMPERSAND:318,BAR:319,GE:320,LE:321,EQEQ:322,EQEQEQ:323,NE:324,NEEQEQ:325,NEEQEQEQ:326,CondOp:327,AllSome:328,ColFunc:329,BETWEEN:330,NOT_BETWEEN:331,IS:332,DOUBLECOLON:333,SOME:334,UPDATE:335,SetColumn:336,SetColumn_group0:337,DELETE:338,INSERT:339,Into:340,Values:341,ValuesListsList:342,DEFAULT:343,VALUES:344,ValuesList:345,Value:346,DateValue:347,TemporaryClause:348,TableClass:349,IfNotExists:350,CreateTableDefClause:351,CreateTableOptionsClause:352,TABLE:353,CreateTableOptions:354,CreateTableOption:355,IDENTITY:356,TEMP:357,ColumnDefsList:358,ConstraintsList:359,Constraint:360,ConstraintName:361,PrimaryKey:362,ForeignKey:363,UniqueKey:364,IndexKey:365,Check:366,CONSTRAINT:367,CHECK:368,PRIMARY:369,KEY:370,PrimaryKey_option0:371,ColsList:372,FOREIGN:373,REFERENCES:374,ForeignKey_option0:375,OnForeignKeyClause:376,ParColsList:377,OnDeleteClause:378,OnUpdateClause:379,NO:380,ACTION:381,UniqueKey_option0:382,UniqueKey_option1:383,ColumnDef:384,ColumnConstraintsClause:385,ColumnConstraints:386,SingularColumnType:387,NumberMax:388,ENUM:389,MAXNUM:390,ColumnConstraintsList:391,ColumnConstraint:392,ParLiteral:393,ColumnConstraint_option0:394,ColumnConstraint_option1:395,DROP:396,DropTable_group0:397,IfExists:398,TablesList:399,ALTER:400,RENAME:401,ADD:402,MODIFY:403,ATTACH:404,DATABASE:405,DETACH:406,AsClause:407,USE:408,SHOW:409,VIEW:410,CreateView_option0:411,CreateView_option1:412,SubqueryRestriction:413,READ:414,ONLY:415,OPTION:416,SOURCE:417,ASSERT:418,JsonObject:419,ATLBRA:420,JsonArray:421,JsonValue:422,JsonPrimitiveValue:423,LCUR:424,JsonPropertiesList:425,RCUR:426,JsonElementsList:427,JsonProperty:428,OnOff:429,SetPropsList:430,AtDollar:431,SetProp:432,OFF:433,COMMIT:434,TRANSACTION:435,ROLLBACK:436,BEGIN:437,ElseStatement:438,WHILE:439,CONTINUE:440,BREAK:441,PRINT:442,REQUIRE:443,StringValuesList:444,PluginsList:445,Plugin:446,ECHO:447,DECLARE:448,DeclaresList:449,DeclareItem:450,TRUNCATE:451,MERGE:452,MergeInto:453,MergeUsing:454,MergeOn:455,MergeMatchedList:456,OutputClause:457,MergeMatched:458,MergeNotMatched:459,MATCHED:460,MergeMatchedAction:461,MergeNotMatchedAction:462,TARGET:463,OUTPUT:464,CreateVertex_option0:465,CreateVertex_option1:466,CreateVertex_option2:467,CreateVertexSet:468,SharpValue:469,CONTENT:470,CreateEdge_option0:471,GRAPH:472,GraphList:473,GraphVertexEdge:474,GraphElement:475,GraphVertexEdge_option0:476,GraphVertexEdge_option1:477,GraphElementVar:478,GraphVertexEdge_option2:479,GraphVertexEdge_option3:480,GraphVertexEdge_option4:481,GraphVar:482,GraphAsClause:483,GraphAtClause:484,GraphElement2:485,GraphElement2_option0:486,GraphElement2_option1:487,GraphElement2_option2:488,GraphElement2_option3:489,GraphElement_option0:490,GraphElement_option1:491,GraphElement_option2:492,SharpLiteral:493,GraphElement_option3:494,GraphElement_option4:495,GraphElement_option5:496,ColonLiteral:497,DeleteVertex:498,DeleteVertex_option0:499,DeleteEdge:500,DeleteEdge_option0:501,DeleteEdge_option1:502,DeleteEdge_option2:503,Term:504,COLONDASH:505,TermsList:506,QUESTIONDASH:507,CALL:508,TRIGGER:509,BeforeAfter:510,InsertDeleteUpdate:511,CreateTrigger_option0:512,CreateTrigger_option1:513,BEFORE:514,AFTER:515,INSTEAD:516,REINDEX:517,A:518,ABSENT:519,ABSOLUTE:520,ACCORDING:521,ADA:522,ADMIN:523,ALWAYS:524,ASC:525,ASSERTION:526,ASSIGNMENT:527,ATTRIBUTE:528,ATTRIBUTES:529,BASE64:530,BERNOULLI:531,BLOCKED:532,BOM:533,BREADTH:534,C:535,CASCADE:536,CATALOG:537,CATALOG_NAME:538,CHAIN:539,CHARACTERISTICS:540,CHARACTERS:541,CHARACTER_SET_CATALOG:542,CHARACTER_SET_NAME:543,CHARACTER_SET_SCHEMA:544,CLASS_ORIGIN:545,COBOL:546,COLLATION:547,COLLATION_CATALOG:548,COLLATION_NAME:549,COLLATION_SCHEMA:550,COLUMNS:551,COLUMN_NAME:552,COMMAND_FUNCTION:553,COMMAND_FUNCTION_CODE:554,COMMITTED:555,CONDITION_NUMBER:556,CONNECTION:557,CONNECTION_NAME:558,CONSTRAINTS:559,CONSTRAINT_CATALOG:560,CONSTRAINT_NAME:561,CONSTRAINT_SCHEMA:562,CONSTRUCTOR:563,CONTROL:564,CURSOR_NAME:565,DATA:566,DATETIME_INTERVAL_CODE:567,DATETIME_INTERVAL_PRECISION:568,DB:569,DEFAULTS:570,DEFERRABLE:571,DEFERRED:572,DEFINED:573,DEFINER:574,DEGREE:575,DEPTH:576,DERIVED:577,DESC:578,DESCRIPTOR:579,DIAGNOSTICS:580,DISPATCH:581,DOCUMENT:582,DOMAIN:583,DYNAMIC_FUNCTION:584,DYNAMIC_FUNCTION_CODE:585,EMPTY:586,ENCODING:587,ENFORCED:588,EXCLUDE:589,EXCLUDING:590,EXPRESSION:591,FILE:592,FINAL:593,FLAG:594,FOLLOWING:595,FORTRAN:596,FOUND:597,FS:598,G:599,GENERAL:600,GENERATED:601,GO:602,GOTO:603,GRANTED:604,HEX:605,HIERARCHY:606,ID:607,IGNORE:608,IMMEDIATE:609,IMMEDIATELY:610,IMPLEMENTATION:611,INCLUDING:612,INCREMENT:613,INDENT:614,INITIALLY:615,INPUT:616,INSTANCE:617,INSTANTIABLE:618,INTEGRITY:619,INVOKER:620,ISOLATION:621,K:622,KEY_MEMBER:623,KEY_TYPE:624,LENGTH:625,LEVEL:626,LIBRARY:627,LINK:628,LOCATION:629,LOCATOR:630,M:631,MAP:632,MAPPING:633,MAXVALUE:634,MESSAGE_LENGTH:635,MESSAGE_OCTET_LENGTH:636,MESSAGE_TEXT:637,MINVALUE:638,MORE:639,MUMPS:640,NAME:641,NAMES:642,NAMESPACE:643,NESTING:644,NEXT:645,NFC:646,NFD:647,NFKC:648,NFKD:649,NIL:650,NORMALIZED:651,NULLABLE:652,NULLS:653,OBJECT:654,OCTETS:655,OPTIONS:656,ORDERING:657,ORDINALITY:658,OTHERS:659,OVERRIDING:660,P:661,PAD:662,PARAMETER_MODE:663,PARAMETER_NAME:664,PARAMETER_ORDINAL_POSITION:665,PARAMETER_SPECIFIC_CATALOG:666,PARAMETER_SPECIFIC_NAME:667,PARAMETER_SPECIFIC_SCHEMA:668,PARTIAL:669,PASCAL:670,PASSING:671,PASSTHROUGH:672,PERMISSION:673,PLACING:674,PLI:675,PRECEDING:676,PRESERVE:677,PRIOR:678,PRIVILEGES:679,PUBLIC:680,RECOVERY:681,RELATIVE:682,REPEATABLE:683,REQUIRING:684,RESPECT:685,RESTART:686,RESTORE:687,RESTRICT:688,RETURNED_CARDINALITY:689,RETURNED_LENGTH:690,RETURNED_OCTET_LENGTH:691,RETURNED_SQLSTATE:692,RETURNING:693,ROLE:694,ROUTINE:695,ROUTINE_CATALOG:696,ROUTINE_NAME:697,ROUTINE_SCHEMA:698,ROW_COUNT:699,SCALE:700,SCHEMA:701,SCHEMA_NAME:702,SCOPE_CATALOG:703,SCOPE_NAME:704,SCOPE_SCHEMA:705,SECTION:706,SECURITY:707,SELECTIVE:708,SELF:709,SEQUENCE:710,SERIALIZABLE:711,SERVER:712,SERVER_NAME:713,SESSION:714,SETS:715,SIMPLE:716,SIZE:717,SPACE:718,SPECIFIC_NAME:719,STANDALONE:720,STATE:721,STATEMENT:722,STRIP:723,STRUCTURE:724,STYLE:725,SUBCLASS_ORIGIN:726,T:727,TABLE_NAME:728,TEMPORARY:729,TIES:730,TOKEN:731,TOP_LEVEL_COUNT:732,TRANSACTIONS_COMMITTED:733,TRANSACTIONS_ROLLED_BACK:734,TRANSACTION_ACTIVE:735,TRANSFORM:736,TRANSFORMS:737,TRIGGER_CATALOG:738,TRIGGER_NAME:739,TRIGGER_SCHEMA:740,TYPE:741,UNBOUNDED:742,UNCOMMITTED:743,UNDER:744,UNLINK:745,UNNAMED:746,UNTYPED:747,URI:748,USAGE:749,USER_DEFINED_TYPE_CATALOG:750,USER_DEFINED_TYPE_CODE:751,USER_DEFINED_TYPE_NAME:752,USER_DEFINED_TYPE_SCHEMA:753,VALID:754,VERSION:755,WHITESPACE:756,WORK:757,WRAPPER:758,WRITE:759,XMLDECLARATION:760,XMLSCHEMA:761,YES:762,ZONE:763,SEMICOLON:764,PERCENT:765,ROWS:766,FuncValue_option0_group0:767,$accept:0,$end:1},terminals_:{2:"error",4:"LITERAL",5:"BRALITERAL",10:"EOF",14:"EXPLAIN",15:"QUERY",16:"PLAN",53:"EndTransaction",72:"WITH",74:"COMMA",76:"AS",77:"LPAR",78:"RPAR",89:"SEARCH",93:"PIVOT",95:"FOR",98:"UNPIVOT",99:"IN",107:"REMOVE",112:"LIKE",115:"ARROW",116:"DOT",118:"ORDER",119:"BY",122:"DOTDOT",123:"CARET",124:"EQ",128:"WHERE",129:"OF",130:"CLASS",131:"NUMBER",132:"STRING",133:"SLASH",134:"VERTEX",135:"EDGE",136:"EXCLAMATION",137:"SHARP",138:"MODULO",139:"GT",140:"LT",141:"GTGT",142:"LTLT",143:"DOLLAR",145:"AT",146:"SET",148:"TO",149:"VALUE",150:"ROW",152:"COLON",154:"NOT",156:"IF",162:"UNION",164:"ALL",166:"ANY",168:"INTERSECT",169:"EXCEPT",170:"AND",171:"OR",172:"PATH",173:"RETURN",175:"REPEAT",179:"PLUS",180:"STAR",181:"QUESTION",183:"FROM",185:"DISTINCT",187:"UNIQUE",189:"SELECT",190:"COLUMN",191:"MATRIX",192:"TEXTSTRING",193:"INDEX",194:"RECORDSET",195:"TOP",198:"INTO",206:"CROSS",207:"APPLY",208:"OUTER",212:"INDEXED",213:"INSERTED",222:"NATURAL",223:"JOIN",224:"INNER",225:"LEFT",226:"RIGHT",227:"FULL",228:"SEMI",229:"ANTI",230:"ON",231:"USING",232:"GROUP",236:"GROUPING",237:"ROLLUP",238:"CUBE",239:"HAVING",240:"CORRESPONDING",242:"DIRECTION",243:"COLLATE",244:"NOCASE",245:"LIMIT",247:"OFFSET",249:"FETCH",265:"CURRENT_TIMESTAMP",266:"JAVASCRIPT",267:"CREATE",268:"FUNCTION",269:"AGGREGATE",270:"NEW",271:"CAST",273:"CONVERT",276:"OVER",279:"PARTITION",280:"SUM",281:"COUNT",282:"MIN",283:"MAX",284:"AVG",285:"FIRST",286:"LAST",287:"AGGR",288:"ARRAY",290:"REPLACE",291:"DATEADD",292:"DATEDIFF",293:"INTERVAL",294:"TRUE",295:"FALSE",296:"NSTRING",297:"NULL",298:"EXISTS",299:"ARRAYLBRA",300:"RBRA",302:"BRAQUESTION",303:"CASE",306:"END",308:"WHEN",309:"THEN",310:"ELSE",311:"REGEXP",312:"TILDA",313:"GLOB",314:"ESCAPE",315:"NOT_LIKE",316:"BARBAR",317:"MINUS",318:"AMPERSAND",319:"BAR",320:"GE",321:"LE",322:"EQEQ",323:"EQEQEQ",324:"NE",325:"NEEQEQ",326:"NEEQEQEQ",330:"BETWEEN",331:"NOT_BETWEEN",332:"IS",333:"DOUBLECOLON",334:"SOME",335:"UPDATE",338:"DELETE",339:"INSERT",343:"DEFAULT",344:"VALUES",347:"DateValue",353:"TABLE",356:"IDENTITY",357:"TEMP",367:"CONSTRAINT",368:"CHECK",369:"PRIMARY",370:"KEY",373:"FOREIGN",374:"REFERENCES",380:"NO",381:"ACTION",386:"ColumnConstraints",389:"ENUM",390:"MAXNUM",396:"DROP",400:"ALTER",401:"RENAME",402:"ADD",403:"MODIFY",404:"ATTACH",405:"DATABASE",406:"DETACH",408:"USE",409:"SHOW",410:"VIEW",414:"READ",415:"ONLY",416:"OPTION",417:"SOURCE",418:"ASSERT",420:"ATLBRA",424:"LCUR",426:"RCUR",433:"OFF",434:"COMMIT",435:"TRANSACTION",436:"ROLLBACK",437:"BEGIN",439:"WHILE",440:"CONTINUE",441:"BREAK",442:"PRINT",443:"REQUIRE",447:"ECHO",448:"DECLARE",451:"TRUNCATE",452:"MERGE",460:"MATCHED",463:"TARGET",464:"OUTPUT",470:"CONTENT",472:"GRAPH",505:"COLONDASH",507:"QUESTIONDASH",508:"CALL",509:"TRIGGER",514:"BEFORE",515:"AFTER",516:"INSTEAD",517:"REINDEX",518:"A",519:"ABSENT",520:"ABSOLUTE",521:"ACCORDING",522:"ADA",523:"ADMIN",524:"ALWAYS",525:"ASC",526:"ASSERTION",527:"ASSIGNMENT",528:"ATTRIBUTE",529:"ATTRIBUTES",530:"BASE64",531:"BERNOULLI",532:"BLOCKED",533:"BOM",534:"BREADTH",535:"C",536:"CASCADE",537:"CATALOG",538:"CATALOG_NAME",539:"CHAIN",540:"CHARACTERISTICS",541:"CHARACTERS",542:"CHARACTER_SET_CATALOG",543:"CHARACTER_SET_NAME",544:"CHARACTER_SET_SCHEMA",545:"CLASS_ORIGIN",546:"COBOL",547:"COLLATION",548:"COLLATION_CATALOG",549:"COLLATION_NAME",550:"COLLATION_SCHEMA",551:"COLUMNS",552:"COLUMN_NAME",
553:"COMMAND_FUNCTION",554:"COMMAND_FUNCTION_CODE",555:"COMMITTED",556:"CONDITION_NUMBER",557:"CONNECTION",558:"CONNECTION_NAME",559:"CONSTRAINTS",560:"CONSTRAINT_CATALOG",561:"CONSTRAINT_NAME",562:"CONSTRAINT_SCHEMA",563:"CONSTRUCTOR",564:"CONTROL",565:"CURSOR_NAME",566:"DATA",567:"DATETIME_INTERVAL_CODE",568:"DATETIME_INTERVAL_PRECISION",569:"DB",570:"DEFAULTS",571:"DEFERRABLE",572:"DEFERRED",573:"DEFINED",574:"DEFINER",575:"DEGREE",576:"DEPTH",577:"DERIVED",578:"DESC",579:"DESCRIPTOR",580:"DIAGNOSTICS",581:"DISPATCH",582:"DOCUMENT",583:"DOMAIN",584:"DYNAMIC_FUNCTION",585:"DYNAMIC_FUNCTION_CODE",586:"EMPTY",587:"ENCODING",588:"ENFORCED",589:"EXCLUDE",590:"EXCLUDING",591:"EXPRESSION",592:"FILE",593:"FINAL",594:"FLAG",595:"FOLLOWING",596:"FORTRAN",597:"FOUND",598:"FS",599:"G",600:"GENERAL",601:"GENERATED",602:"GO",603:"GOTO",604:"GRANTED",605:"HEX",606:"HIERARCHY",607:"ID",608:"IGNORE",609:"IMMEDIATE",610:"IMMEDIATELY",611:"IMPLEMENTATION",612:"INCLUDING",613:"INCREMENT",614:"INDENT",615:"INITIALLY",616:"INPUT",617:"INSTANCE",618:"INSTANTIABLE",619:"INTEGRITY",620:"INVOKER",621:"ISOLATION",622:"K",623:"KEY_MEMBER",624:"KEY_TYPE",625:"LENGTH",626:"LEVEL",627:"LIBRARY",628:"LINK",629:"LOCATION",630:"LOCATOR",631:"M",632:"MAP",633:"MAPPING",634:"MAXVALUE",635:"MESSAGE_LENGTH",636:"MESSAGE_OCTET_LENGTH",637:"MESSAGE_TEXT",638:"MINVALUE",639:"MORE",640:"MUMPS",641:"NAME",642:"NAMES",643:"NAMESPACE",644:"NESTING",645:"NEXT",646:"NFC",647:"NFD",648:"NFKC",649:"NFKD",650:"NIL",651:"NORMALIZED",652:"NULLABLE",653:"NULLS",654:"OBJECT",655:"OCTETS",656:"OPTIONS",657:"ORDERING",658:"ORDINALITY",659:"OTHERS",660:"OVERRIDING",661:"P",662:"PAD",663:"PARAMETER_MODE",664:"PARAMETER_NAME",665:"PARAMETER_ORDINAL_POSITION",666:"PARAMETER_SPECIFIC_CATALOG",667:"PARAMETER_SPECIFIC_NAME",668:"PARAMETER_SPECIFIC_SCHEMA",669:"PARTIAL",670:"PASCAL",671:"PASSING",672:"PASSTHROUGH",673:"PERMISSION",674:"PLACING",675:"PLI",676:"PRECEDING",677:"PRESERVE",678:"PRIOR",679:"PRIVILEGES",680:"PUBLIC",681:"RECOVERY",682:"RELATIVE",683:"REPEATABLE",684:"REQUIRING",685:"RESPECT",686:"RESTART",687:"RESTORE",688:"RESTRICT",689:"RETURNED_CARDINALITY",690:"RETURNED_LENGTH",691:"RETURNED_OCTET_LENGTH",692:"RETURNED_SQLSTATE",693:"RETURNING",694:"ROLE",695:"ROUTINE",696:"ROUTINE_CATALOG",697:"ROUTINE_NAME",698:"ROUTINE_SCHEMA",699:"ROW_COUNT",700:"SCALE",701:"SCHEMA",702:"SCHEMA_NAME",703:"SCOPE_CATALOG",704:"SCOPE_NAME",705:"SCOPE_SCHEMA",706:"SECTION",707:"SECURITY",708:"SELECTIVE",709:"SELF",710:"SEQUENCE",711:"SERIALIZABLE",712:"SERVER",713:"SERVER_NAME",714:"SESSION",715:"SETS",716:"SIMPLE",717:"SIZE",718:"SPACE",719:"SPECIFIC_NAME",720:"STANDALONE",721:"STATE",722:"STATEMENT",723:"STRIP",724:"STRUCTURE",725:"STYLE",726:"SUBCLASS_ORIGIN",727:"T",728:"TABLE_NAME",729:"TEMPORARY",730:"TIES",731:"TOKEN",732:"TOP_LEVEL_COUNT",733:"TRANSACTIONS_COMMITTED",734:"TRANSACTIONS_ROLLED_BACK",735:"TRANSACTION_ACTIVE",736:"TRANSFORM",737:"TRANSFORMS",738:"TRIGGER_CATALOG",739:"TRIGGER_NAME",740:"TRIGGER_SCHEMA",741:"TYPE",742:"UNBOUNDED",743:"UNCOMMITTED",744:"UNDER",745:"UNLINK",746:"UNNAMED",747:"UNTYPED",748:"URI",749:"USAGE",750:"USER_DEFINED_TYPE_CATALOG",751:"USER_DEFINED_TYPE_CODE",752:"USER_DEFINED_TYPE_NAME",753:"USER_DEFINED_TYPE_SCHEMA",754:"VALID",755:"VERSION",756:"WHITESPACE",757:"WORK",758:"WRAPPER",759:"WRITE",760:"XMLDECLARATION",761:"XMLSCHEMA",762:"YES",763:"ZONE",764:"SEMICOLON",765:"PERCENT",766:"ROWS"},productions_:[0,[3,1],[3,1],[3,2],[7,1],[7,2],[8,2],[9,3],[9,1],[9,1],[13,2],[13,4],[12,1],[17,0],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[47,3],[73,3],[73,1],[75,5],[40,10],[40,4],[92,8],[92,11],[102,4],[104,2],[104,1],[103,3],[103,1],[105,1],[105,3],[106,3],[109,3],[109,1],[110,1],[110,2],[114,1],[114,1],[117,1],[117,5],[117,5],[117,1],[117,2],[117,1],[117,2],[117,2],[117,3],[117,4],[117,4],[117,4],[117,4],[117,4],[117,1],[117,1],[117,1],[117,1],[117,1],[117,1],[117,2],[117,2],[117,2],[117,1],[117,1],[117,1],[117,1],[117,1],[117,1],[117,2],[117,3],[117,4],[117,3],[117,1],[117,4],[117,2],[117,2],[117,4],[117,4],[117,4],[117,4],[117,4],[117,5],[117,4],[117,4],[117,4],[117,4],[117,4],[117,4],[117,4],[117,4],[117,6],[163,3],[163,1],[153,1],[153,1],[153,1],[182,2],[79,4],[79,4],[79,4],[79,3],[184,1],[184,2],[184,2],[184,2],[184,2],[184,2],[184,2],[184,2],[186,3],[186,4],[186,0],[81,0],[81,2],[81,2],[81,2],[81,2],[81,2],[82,2],[82,3],[82,5],[82,0],[205,6],[205,7],[205,6],[205,7],[203,1],[203,3],[209,4],[209,5],[209,3],[209,3],[209,2],[209,3],[209,1],[209,3],[209,2],[209,3],[209,1],[209,1],[209,2],[209,3],[209,1],[209,1],[209,2],[209,3],[209,1],[209,2],[209,3],[214,1],[199,3],[199,1],[204,2],[204,2],[204,1],[204,1],[215,3],[217,1],[217,2],[217,3],[217,3],[217,2],[217,3],[217,4],[217,5],[217,1],[217,2],[217,3],[217,1],[217,2],[217,3],[216,1],[216,2],[221,1],[221,2],[221,2],[221,3],[221,2],[221,3],[221,2],[221,3],[221,2],[221,2],[221,2],[218,2],[218,2],[218,0],[84,0],[84,2],[85,0],[85,4],[233,1],[233,3],[235,5],[235,4],[235,4],[235,1],[234,0],[234,2],[88,0],[88,2],[88,3],[88,2],[88,2],[88,3],[88,4],[88,3],[88,3],[86,0],[86,3],[120,1],[120,3],[241,1],[241,2],[241,3],[241,4],[87,0],[87,3],[87,8],[246,0],[246,2],[174,3],[174,1],[253,3],[253,2],[253,3],[253,2],[253,3],[253,2],[253,1],[254,5],[254,3],[254,1],[111,5],[111,3],[111,3],[111,1],[94,1],[94,1],[94,1],[94,1],[94,1],[94,1],[94,1],[94,1],[94,1],[94,1],[94,1],[94,1],[94,1],[94,1],[94,1],[94,1],[94,1],[94,1],[94,3],[94,3],[94,3],[94,1],[94,1],[56,1],[70,5],[71,5],[263,2],[263,2],[261,6],[261,8],[261,6],[261,8],[274,1],[274,1],[274,1],[274,1],[274,1],[274,1],[274,1],[255,5],[255,6],[255,6],[275,0],[275,4],[275,4],[275,5],[277,3],[278,3],[158,1],[158,1],[158,1],[158,1],[158,1],[158,1],[158,1],[158,1],[158,1],[200,5],[200,3],[200,4],[200,4],[200,8],[200,8],[200,8],[200,8],[200,3],[151,1],[151,3],[196,1],[257,1],[257,1],[113,1],[113,1],[258,1],[202,2],[259,4],[262,3],[201,2],[201,2],[201,1],[201,1],[260,5],[260,4],[304,2],[304,1],[307,4],[305,2],[305,0],[256,3],[256,3],[256,3],[256,3],[256,5],[256,3],[256,5],[256,3],[256,3],[256,3],[256,3],[256,3],[256,3],[256,3],[256,3],[256,3],[256,3],[256,3],[256,3],[256,3],[256,5],[256,3],[256,3],[256,3],[256,5],[256,3],[256,3],[256,3],[256,3],[256,3],[256,3],[256,3],[256,3],[256,3],[256,3],[256,3],[256,6],[256,6],[256,3],[256,3],[256,2],[256,2],[256,2],[256,2],[256,2],[256,3],[256,5],[256,6],[256,5],[256,6],[256,4],[256,5],[256,3],[256,4],[256,3],[256,4],[256,3],[256,3],[256,3],[256,3],[256,3],[329,1],[329,1],[329,4],[327,1],[327,1],[327,1],[327,1],[327,1],[327,1],[328,1],[328,1],[328,1],[55,6],[55,4],[147,1],[147,3],[336,3],[336,4],[29,5],[29,3],[36,5],[36,4],[36,7],[36,6],[36,5],[36,4],[36,5],[36,8],[36,7],[36,4],[36,6],[36,7],[341,1],[341,1],[340,0],[340,1],[342,3],[342,1],[342,1],[342,5],[342,3],[342,3],[345,1],[345,3],[346,1],[346,1],[346,1],[346,1],[346,1],[346,1],[100,1],[100,3],[24,9],[24,5],[349,1],[349,1],[352,0],[352,1],[354,2],[354,1],[355,1],[355,3],[355,3],[355,3],[348,0],[348,1],[350,0],[350,3],[351,3],[351,1],[351,2],[359,1],[359,3],[360,2],[360,2],[360,2],[360,2],[360,2],[361,0],[361,2],[366,4],[362,6],[363,9],[377,3],[376,0],[376,2],[378,4],[379,4],[364,6],[365,5],[365,5],[372,1],[372,1],[372,3],[372,3],[358,1],[358,3],[384,3],[384,2],[384,1],[387,6],[387,4],[387,1],[387,4],[272,2],[272,1],[388,1],[388,1],[385,0],[385,1],[391,2],[391,1],[393,3],[392,2],[392,5],[392,3],[392,6],[392,1],[392,2],[392,4],[392,2],[392,1],[392,2],[392,1],[392,1],[392,3],[392,5],[33,4],[399,3],[399,1],[398,0],[398,2],[18,6],[18,6],[18,6],[18,8],[18,6],[39,5],[19,4],[19,7],[19,6],[19,9],[30,3],[21,4],[21,6],[21,9],[21,6],[407,0],[407,2],[54,3],[54,2],[31,4],[31,5],[31,5],[22,8],[22,9],[32,3],[43,2],[43,4],[43,3],[43,5],[45,2],[45,4],[45,4],[45,6],[42,4],[42,6],[44,4],[44,6],[41,4],[41,6],[25,11],[25,8],[413,3],[413,3],[413,5],[34,4],[66,2],[57,2],[58,2],[58,2],[58,4],[144,4],[144,2],[144,2],[144,2],[144,2],[144,1],[144,2],[144,2],[422,1],[422,1],[423,1],[423,1],[423,1],[423,1],[423,1],[423,1],[423,1],[423,3],[419,3],[419,4],[419,2],[421,2],[421,3],[421,1],[425,3],[425,1],[428,3],[428,3],[428,3],[427,3],[427,1],[65,4],[65,3],[65,4],[65,5],[65,5],[65,6],[431,1],[431,1],[430,3],[430,2],[432,1],[432,1],[432,3],[429,1],[429,1],[51,2],[52,2],[50,2],[35,4],[35,3],[438,2],[59,3],[60,1],[61,1],[62,3],[63,2],[63,2],[64,2],[64,2],[446,1],[446,1],[69,2],[444,3],[444,1],[445,3],[445,1],[28,2],[449,1],[449,3],[450,3],[450,4],[450,5],[450,6],[46,3],[37,6],[453,1],[453,2],[454,2],[455,2],[456,2],[456,2],[456,1],[456,1],[458,4],[458,6],[461,1],[461,3],[459,5],[459,7],[459,7],[459,9],[459,7],[459,9],[462,3],[462,6],[462,3],[462,6],[457,0],[457,2],[457,5],[457,4],[457,7],[27,6],[469,2],[468,0],[468,2],[468,2],[468,1],[26,8],[23,3],[23,4],[473,3],[473,1],[474,3],[474,7],[474,6],[474,3],[474,4],[478,1],[478,1],[482,2],[483,3],[484,2],[485,4],[475,4],[475,3],[475,2],[475,1],[497,2],[493,2],[493,2],[498,4],[500,6],[67,3],[67,2],[506,3],[506,1],[504,1],[504,4],[68,2],[20,2],[48,9],[48,8],[48,9],[510,0],[510,1],[510,1],[510,1],[510,2],[511,1],[511,1],[511,1],[49,3],[38,2],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[11,1],[11,1],[80,0],[80,1],[83,0],[83,1],[90,0],[90,2],[91,0],[91,1],[96,0],[96,1],[97,0],[97,1],[101,0],[101,1],[108,0],[108,1],[121,0],[121,1],[125,1],[125,2],[126,1],[126,2],[127,0],[127,1],[155,0],[155,2],[157,0],[157,2],[159,0],[159,2],[160,1],[160,1],[161,0],[161,2],[165,0],[165,2],[167,0],[167,2],[176,0],[176,2],[177,0],[177,2],[178,0],[178,2],[188,0],[188,1],[197,0],[197,1],[210,0],[210,1],[211,0],[211,1],[219,0],[219,1],[220,0],[220,1],[248,0],[248,1],[250,0],[250,1],[251,0],[251,1],[252,0],[252,1],[264,1],[264,1],[767,1],[767,1],[289,0],[289,1],[301,1],[301,1],[337,1],[337,1],[371,0],[371,1],[375,0],[375,1],[382,0],[382,1],[383,0],[383,1],[394,0],[394,1],[395,0],[395,1],[397,1],[397,1],[411,0],[411,1],[412,0],[412,1],[465,0],[465,1],[466,0],[466,1],[467,0],[467,1],[471,0],[471,1],[476,0],[476,1],[477,0],[477,1],[479,0],[479,1],[480,0],[480,1],[481,0],[481,1],[486,0],[486,1],[487,0],[487,1],[488,0],[488,1],[489,0],[489,1],[490,0],[490,1],[491,0],[491,1],[492,0],[492,1],[494,0],[494,1],[495,0],[495,1],[496,0],[496,1],[499,0],[499,2],[501,0],[501,2],[502,0],[502,2],[503,0],[503,2],[512,0],[512,1],[513,0],[513,1]],performAction:function(e,t,r,n,a,s,i){var o=s.length-1;switch(a){case 1:n.casesensitive?this.$=s[o]:this.$=s[o].toLowerCase();break;case 2:this.$=w(s[o].substr(1,s[o].length-2));break;case 3:this.$=s[o].toLowerCase();break;case 4:this.$=s[o];break;case 5:this.$=s[o]?s[o-1]+" "+s[o]:s[o-1];break;case 6:return new n.Statements({statements:s[o-1]});case 7:this.$=s[o-2],s[o]&&s[o-2].push(s[o]);break;case 8:case 9:case 70:case 80:case 85:case 143:case 177:case 205:case 206:case 242:case 261:case 273:case 354:case 372:case 451:case 474:case 475:case 479:case 487:case 528:case 529:case 566:case 649:case 659:case 683:case 685:case 687:case 701:case 702:case 732:case 756:this.$=[s[o]];break;case 10:this.$=s[o],s[o].explain=!0;break;case 11:this.$=s[o],s[o].explain=!0;break;case 12:this.$=s[o],n.exists&&(this.$.exists=n.exists),delete n.exists,n.queries&&(this.$.queries=n.queries),delete n.queries;break;case 13:case 162:case 172:case 237:case 238:case 240:case 248:case 250:case 259:case 267:case 270:case 375:case 491:case 501:case 503:case 515:case 521:case 522:case 567:this.$=void 0;break;case 68:this.$=new n.WithSelect({withs:s[o-1],select:s[o]});break;case 69:case 565:s[o-2].push(s[o]),this.$=s[o-2];break;case 71:this.$={name:s[o-4],select:s[o-1]};break;case 72:n.extend(this.$,s[o-9]),n.extend(this.$,s[o-8]),n.extend(this.$,s[o-7]),n.extend(this.$,s[o-6]),n.extend(this.$,s[o-5]),n.extend(this.$,s[o-4]),n.extend(this.$,s[o-3]),n.extend(this.$,s[o-2]),n.extend(this.$,s[o-1]),n.extend(this.$,s[o]),this.$=s[o-9];break;case 73:this.$=new n.Search({selectors:s[o-2],from:s[o]}),n.extend(this.$,s[o-1]);break;case 74:this.$={pivot:{expr:s[o-5],columnid:s[o-3],inlist:s[o-2],as:s[o]}};break;case 75:this.$={unpivot:{tocolumnid:s[o-8],forcolumnid:s[o-6],inlist:s[o-3],as:s[o]}};break;case 76:case 520:case 549:case 585:case 619:case 636:case 637:case 640:case 662:this.$=s[o-1];break;case 77:case 78:case 86:case 147:case 185:case 247:case 280:case 288:case 289:case 290:case 291:case 292:case 293:case 294:case 295:case 296:case 297:case 298:case 299:case 300:case 301:case 304:case 305:case 320:case 321:case 322:case 323:case 324:case 325:case 374:case 440:case 441:case 442:case 443:case 444:case 445:case 516:case 542:case 546:case 548:case 623:case 624:case 625:case 626:case 627:case 628:case 632:case 634:case 635:case 644:case 660:case 661:case 723:case 738:case 739:case 741:case 742:case 748:case 749:this.$=s[o];break;case 79:case 84:case 731:case 755:this.$=s[o-2],this.$.push(s[o]);break;case 81:this.$={expr:s[o]};break;case 82:this.$={expr:s[o-2],as:s[o]};break;case 83:this.$={removecolumns:s[o]};break;case 87:this.$={like:s[o]};break;case 90:case 104:this.$={srchid:"PROP",args:[s[o]]};break;case 91:this.$={srchid:"ORDERBY",args:s[o-1]};break;case 92:var u=s[o-1];u||(u="ASC"),this.$={srchid:"ORDERBY",args:[{expression:new n.Column({columnid:"_"}),direction:u}]};break;case 93:this.$={srchid:"PARENT"};break;case 94:this.$={srchid:"APROP",args:[s[o]]};break;case 95:this.$={selid:"ROOT"};break;case 96:this.$={srchid:"EQ",args:[s[o]]};break;case 97:this.$={srchid:"LIKE",args:[s[o]]};break;case 98:case 99:this.$={selid:"WITH",args:s[o-1]};break;case 100:this.$={srchid:s[o-3].toUpperCase(),args:s[o-1]};break;case 101:this.$={srchid:"WHERE",args:[s[o-1]]};break;case 102:this.$={selid:"OF",args:[s[o-1]]};break;case 103:this.$={srchid:"CLASS",args:[s[o-1]]};break;case 105:this.$={srchid:"NAME",args:[s[o].substr(1,s[o].length-2)]};break;case 106:this.$={srchid:"CHILD"};break;case 107:this.$={srchid:"VERTEX"};break;case 108:this.$={srchid:"EDGE"};break;case 109:this.$={srchid:"REF"};break;case 110:this.$={srchid:"SHARP",args:[s[o]]};break;case 111:this.$={srchid:"ATTR",args:void 0===s[o]?void 0:[s[o]]};break;case 112:this.$={srchid:"ATTR"};break;case 113:this.$={srchid:"OUT"};break;case 114:this.$={srchid:"IN"};break;case 115:this.$={srchid:"OUTOUT"};break;case 116:this.$={srchid:"ININ"};break;case 117:this.$={srchid:"CONTENT"};break;case 118:this.$={srchid:"EX",args:[new n.Json({value:s[o]})]};break;case 119:this.$={srchid:"AT",args:[s[o]]};break;case 120:this.$={srchid:"AS",args:[s[o]]};break;case 121:this.$={srchid:"SET",args:s[o-1]};break;case 122:this.$={selid:"TO",args:[s[o]]};break;case 123:this.$={srchid:"VALUE"};break;case 124:this.$={srchid:"ROW",args:s[o-1]};break;case 125:this.$={srchid:"CLASS",args:[s[o]]};break;case 126:this.$={selid:s[o],args:[s[o-1]]};break;case 127:this.$={selid:"NOT",args:s[o-1]};break;case 128:this.$={selid:"IF",args:s[o-1]};break;case 129:this.$={selid:s[o-3],args:s[o-1]};break;case 130:this.$={selid:"DISTINCT",args:s[o-1]};break;case 131:this.$={selid:"UNION",args:s[o-1]};break;case 132:this.$={selid:"UNIONALL",args:s[o-1]};break;case 133:this.$={selid:"ALL",args:[s[o-1]]};break;case 134:this.$={selid:"ANY",args:[s[o-1]]};break;case 135:this.$={selid:"INTERSECT",args:s[o-1]};break;case 136:this.$={selid:"EXCEPT",args:s[o-1]};break;case 137:this.$={selid:"AND",args:s[o-1]};break;case 138:this.$={selid:"OR",args:s[o-1]};break;case 139:this.$={selid:"PATH",args:[s[o-1]]};break;case 140:this.$={srchid:"RETURN",args:s[o-1]};break;case 141:this.$={selid:"REPEAT",sels:s[o-3],args:s[o-1]};break;case 142:this.$=s[o-2],this.$.push(s[o]);break;case 144:this.$="PLUS";break;case 145:this.$="STAR";break;case 146:this.$="QUESTION";break;case 148:this.$=new n.Select({columns:s[o],distinct:!0}),n.extend(this.$,s[o-3]),n.extend(this.$,s[o-1]);break;case 149:this.$=new n.Select({columns:s[o],distinct:!0}),n.extend(this.$,s[o-3]),n.extend(this.$,s[o-1]);break;case 150:this.$=new n.Select({columns:s[o],all:!0}),n.extend(this.$,s[o-3]),n.extend(this.$,s[o-1]);break;case 151:s[o]?(this.$=new n.Select({columns:s[o]}),n.extend(this.$,s[o-2]),n.extend(this.$,s[o-1])):this.$=new n.Select({columns:[new n.Column({columnid:"_"})],modifier:"COLUMN"});break;case 152:"SELECT"==s[o]?this.$=void 0:this.$={modifier:s[o]};break;case 153:this.$={modifier:"VALUE"};break;case 154:this.$={modifier:"ROW"};break;case 155:this.$={modifier:"COLUMN"};break;case 156:this.$={modifier:"MATRIX"};break;case 157:this.$={modifier:"TEXTSTRING"};break;case 158:this.$={modifier:"INDEX"};break;case 159:this.$={modifier:"RECORDSET"};break;case 160:this.$={top:s[o-1],percent:void 0!==s[o]||void 0};break;case 161:this.$={top:s[o-1]};break;case 163:case 330:case 523:case 524:case 724:this.$=void 0;break;case 164:case 165:case 166:case 167:this.$={into:s[o]};break;case 168:var c=s[o];c=c.substr(1,c.length-2);var l=c.substr(-3).toUpperCase(),h=c.substr(-4).toUpperCase();"#"==c[0]?this.$={into:new n.FuncValue({funcid:"HTML",args:[new n.StringValue({value:c}),new n.Json({value:{headers:!0}})]})}:"XLS"==l||"CSV"==l||"TAB"==l?this.$={into:new n.FuncValue({funcid:l,args:[new n.StringValue({value:c}),new n.Json({value:{headers:!0}})]})}:"XLSX"!=h&&"JSON"!=h||(this.$={into:new n.FuncValue({funcid:h,args:[new n.StringValue({value:c}),new n.Json({value:{headers:!0}})]})});break;case 169:this.$={from:s[o]};break;case 170:this.$={from:s[o-1],joins:s[o]};break;case 171:this.$={from:s[o-2],joins:s[o-1]};break;case 173:this.$=new n.Apply({select:s[o-2],applymode:"CROSS",as:s[o]});break;case 174:this.$=new n.Apply({select:s[o-3],applymode:"CROSS",as:s[o]});break;case 175:this.$=new n.Apply({select:s[o-2],applymode:"OUTER",as:s[o]});break;case 176:this.$=new n.Apply({select:s[o-3],applymode:"OUTER",as:s[o]});break;case 178:case 243:case 452:case 530:case 531:this.$=s[o-2],s[o-2].push(s[o]);break;case 179:this.$=s[o-2],this.$.as=s[o];break;case 180:this.$=s[o-3],this.$.as=s[o];break;case 181:this.$=s[o-1],this.$.as="default";break;case 182:this.$=new n.Json({value:s[o-2]}),s[o-2].as=s[o];break;case 183:this.$=s[o-1],s[o-1].as=s[o];break;case 184:this.$=s[o-2],s[o-2].as=s[o];break;case 186:case 638:case 641:this.$=s[o-2];break;case 187:case 191:case 195:case 198:this.$=s[o-1],s[o-1].as=s[o];break;case 188:case 192:case 196:case 199:this.$=s[o-2],s[o-2].as=s[o];break;case 189:case 190:case 194:case 197:this.$=s[o],s[o].as="default";break;case 193:this.$={inserted:!0},s[o].as="default";break;case 200:var c=s[o];c=c.substr(1,c.length-2);var d,l=c.substr(-3).toUpperCase(),h=c.substr(-4).toUpperCase();if("#"==c[0])d=new n.FuncValue({funcid:"HTML",args:[new n.StringValue({value:c}),new n.Json({value:{headers:!0}})]});else if("XLS"==l||"CSV"==l||"TAB"==l)d=new n.FuncValue({funcid:l,args:[new n.StringValue({value:c}),new n.Json({value:{headers:!0}})]});else{if("XLSX"!=h&&"JSON"!=h)throw new Error("Unknown string in FROM clause");d=new n.FuncValue({funcid:h,args:[new n.StringValue({value:c}),new n.Json({value:{headers:!0}})]})}this.$=d;break;case 201:"INFORMATION_SCHEMA"==s[o-2]?this.$=new n.FuncValue({funcid:s[o-2],args:[new n.StringValue({value:s[o]})]}):this.$=new n.Table({databaseid:s[o-2],tableid:s[o]});break;case 202:this.$=new n.Table({tableid:s[o]});break;case 203:case 204:this.$=s[o-1],s[o-1].push(s[o]);break;case 207:this.$=new n.Join(s[o-2]),n.extend(this.$,s[o-1]),n.extend(this.$,s[o]);break;case 208:this.$={table:s[o]};break;case 209:this.$={table:s[o-1],as:s[o]};break;case 210:this.$={table:s[o-2],as:s[o]};break;case 211:this.$={json:new n.Json({value:s[o-2],as:s[o]})};break;case 212:this.$={param:s[o-1],as:s[o]};break;case 213:this.$={param:s[o-2],as:s[o]};break;case 214:this.$={select:s[o-2],as:s[o]};break;case 215:this.$={select:s[o-3],as:s[o]};break;case 216:this.$={funcid:s[o],as:"default"};break;case 217:this.$={funcid:s[o-1],as:s[o]};break;case 218:this.$={funcid:s[o-2],as:s[o]};break;case 219:this.$={variable:s[o],as:"default"};break;case 220:this.$={variable:s[o-1],as:s[o]};break;case 221:this.$={variable:s[o-2],as:s[o]};break;case 222:this.$={joinmode:s[o]};break;case 223:this.$={joinmode:s[o-1],natural:!0};break;case 224:case 225:this.$="INNER";break;case 226:case 227:this.$="LEFT";break;case 228:case 229:this.$="RIGHT";break;case 230:case 231:this.$="OUTER";break;case 232:this.$="SEMI";break;case 233:this.$="ANTI";break;case 234:this.$="CROSS";break;case 235:this.$={on:s[o]};break;case 236:case 697:this.$={using:s[o]};break;case 239:this.$={where:new n.Expression({expression:s[o]})};break;case 241:this.$={group:s[o-1]},n.extend(this.$,s[o]);break;case 244:this.$=new n.GroupExpression({type:"GROUPING SETS",group:s[o-1]});break;case 245:this.$=new n.GroupExpression({type:"ROLLUP",group:s[o-1]});break;case 246:this.$=new n.GroupExpression({type:"CUBE",group:s[o-1]});break;case 249:this.$={having:s[o]};break;case 251:this.$={union:s[o]};break;case 252:this.$={unionall:s[o]};break;case 253:this.$={except:s[o]};break;case 254:this.$={intersect:s[o]};break;case 255:this.$={union:s[o],corresponding:!0};break;case 256:this.$={unionall:s[o],corresponding:!0};break;case 257:this.$={except:s[o],corresponding:!0};break;case 258:this.$={intersect:s[o],corresponding:!0};break;case 260:this.$={order:s[o]};break;case 262:this.$=s[o-2],s[o-2].push(s[o]);break;case 263:this.$=new n.Expression({expression:s[o],direction:"ASC"});break;case 264:this.$=new n.Expression({expression:s[o-1],direction:s[o].toUpperCase()});break;case 265:this.$=new n.Expression({expression:s[o-2],direction:"ASC",nocase:!0});break;case 266:this.$=new n.Expression({expression:s[o-3],direction:s[o].toUpperCase(),nocase:!0});break;case 268:this.$={limit:s[o-1]},n.extend(this.$,s[o]);break;case 269:this.$={limit:s[o-2],offset:s[o-6]};break;case 271:this.$={offset:s[o]};break;case 272:case 509:case 533:case 648:case 658:case 682:case 684:case 688:s[o-2].push(s[o]),this.$=s[o-2];break;case 274:case 276:case 278:s[o-2].as=s[o],this.$=s[o-2];break;case 275:case 277:case 279:s[o-1].as=s[o],this.$=s[o-1];break;case 281:this.$=new n.Column({columid:s[o],tableid:s[o-2],databaseid:s[o-4]});break;case 282:this.$=new n.Column({columnid:s[o],tableid:s[o-2]});break;case 283:this.$=new n.Column({columnid:s[o]});break;case 284:this.$=new n.Column({columnid:s[o],tableid:s[o-2],databaseid:s[o-4]});break;case 285:case 286:this.$=new n.Column({columnid:s[o],tableid:s[o-2]});break;case 287:this.$=new n.Column({columnid:s[o]});break;case 302:this.$=new n.DomainValueValue;break;case 303:this.$=new n.Json({value:s[o]});break;case 306:case 307:case 308:n.queries||(n.queries=[]),n.queries.push(s[o-1]),s[o-1].queriesidx=n.queries.length,this.$=s[o-1];break;case 309:this.$=s[o];break;case 310:this.$=new n.FuncValue({funcid:"CURRENT_TIMESTAMP"});break;case 311:this.$=new n.JavaScript({value:s[o].substr(2,s[o].length-4)});break;case 312:this.$=new n.JavaScript({value:'alasql.fn["'+s[o-2]+'"] = '+s[o].substr(2,s[o].length-4)});break;case 313:this.$=new n.JavaScript({value:'alasql.aggr["'+s[o-2]+'"] = '+s[o].substr(2,s[o].length-4)});break;case 314:this.$=new n.FuncValue({funcid:s[o],newid:!0});break;case 315:this.$=s[o],n.extend(this.$,{newid:!0});break;case 316:this.$=new n.Convert({expression:s[o-3]}),n.extend(this.$,s[o-1]);break;case 317:this.$=new n.Convert({expression:s[o-5],style:s[o-1]}),n.extend(this.$,s[o-3]);break;case 318:this.$=new n.Convert({expression:s[o-1]}),n.extend(this.$,s[o-3]);break;case 319:this.$=new n.Convert({expression:s[o-3],style:s[o-1]}),n.extend(this.$,s[o-5]);break;case 326:this.$=new n.FuncValue({funcid:"CURRENT_TIMESTAMP"});break;case 327:s[o-2].length>1&&("MAX"==s[o-4].toUpperCase()||"MIN"==s[o-4].toUpperCase())?this.$=new n.FuncValue({funcid:s[o-4],args:s[o-2]}):this.$=new n.AggrValue({aggregatorid:s[o-4].toUpperCase(),expression:s[o-2].pop(),over:s[o]});break;case 328:this.$=new n.AggrValue({aggregatorid:s[o-5].toUpperCase(),expression:s[o-2],distinct:!0,over:s[o]});break;case 329:this.$=new n.AggrValue({aggregatorid:s[o-5].toUpperCase(),expression:s[o-2],over:s[o]});break;case 331:case 332:this.$=new n.Over,n.extend(this.$,s[o-1]);break;case 333:this.$=new n.Over,n.extend(this.$,s[o-2]),n.extend(this.$,s[o-1]);break;case 334:this.$={partition:s[o]};break;case 335:this.$={order:s[o]};break;case 336:this.$="SUM";break;case 337:this.$="COUNT";break;case 338:this.$="MIN";break;case 339:case 544:this.$="MAX";break;case 340:this.$="AVG";break;case 341:this.$="FIRST";break;case 342:this.$="LAST";break;case 343:this.$="AGGR";break;case 344:this.$="ARRAY";break;case 345:var f=s[o-4],p=s[o-1];p.length>1&&("MIN"==f.toUpperCase()||"MAX"==f.toUpperCase())?this.$=new n.FuncValue({funcid:f,args:p}):A.aggr[s[o-4]]?this.$=new n.AggrValue({aggregatorid:"REDUCE",funcid:f,expression:p.pop(),distinct:"DISTINCT"==s[o-2]}):this.$=new n.FuncValue({funcid:f,args:p});break;case 346:this.$=new n.FuncValue({funcid:s[o-2]});break;case 347:this.$=new n.FuncValue({funcid:"IIF",args:s[o-1]});break;case 348:this.$=new n.FuncValue({funcid:"REPLACE",args:s[o-1]});break;case 349:this.$=new n.FuncValue({funcid:"DATEADD",args:[new n.StringValue({value:s[o-5]}),s[o-3],s[o-1]]});break;case 350:this.$=new n.FuncValue({funcid:"DATEADD",args:[s[o-5],s[o-3],s[o-1]]});break;case 351:this.$=new n.FuncValue({funcid:"DATEDIFF",args:[new n.StringValue({value:s[o-5]}),s[o-3],s[o-1]]});break;case 352:this.$=new n.FuncValue({funcid:"DATEDIFF",args:[s[o-5],s[o-3],s[o-1]]});break;case 353:this.$=new n.FuncValue({funcid:"INTERVAL",args:[s[o-1],new n.StringValue({value:s[o].toLowerCase()})]});break;case 355:s[o-2].push(s[o]),this.$=s[o-2];break;case 356:this.$=new n.NumValue({value:+s[o]});break;case 357:this.$=new n.LogicValue({value:!0});break;case 358:this.$=new n.LogicValue({value:!1});break;case 359:this.$=new n.StringValue({value:s[o].substr(1,s[o].length-2).replace(/(\\\')/g,"'").replace(/(\'\')/g,"'")});break;case 360:this.$=new n.StringValue({value:s[o].substr(2,s[o].length-3).replace(/(\\\')/g,"'").replace(/(\'\')/g,"'")});break;case 361:this.$=new n.NullValue({value:void 0});break;case 362:this.$=new n.VarValue({variable:s[o]});break;case 363:n.exists||(n.exists=[]),this.$=new n.ExistsValue({value:s[o-1],existsidx:n.exists.length}),n.exists.push(s[o-1]);break;case 364:this.$=new n.ArrayValue({value:s[o-1]});break;case 365:case 366:this.$=new n.ParamValue({param:s[o]});break;case 367:void 0===n.question&&(n.question=0),this.$=new n.ParamValue({param:n.question++});break;case 368:void 0===n.question&&(n.question=0),this.$=new n.ParamValue({param:n.question++,array:!0});break;case 369:this.$=new n.CaseValue({expression:s[o-3],whens:s[o-2],elses:s[o-1]});break;case 370:this.$=new n.CaseValue({whens:s[o-2],elses:s[o-1]});break;case 371:case 699:case 700:this.$=s[o-1],this.$.push(s[o]);break;case 373:this.$={when:s[o-2],then:s[o]};break;case 376:case 377:this.$=new n.Op({left:s[o-2],op:"REGEXP",right:s[o]});break;case 378:this.$=new n.Op({left:s[o-2],op:"GLOB",right:s[o]});break;case 379:this.$=new n.Op({left:s[o-2],op:"LIKE",right:s[o]});break;case 380:this.$=new n.Op({left:s[o-4],op:"LIKE",right:s[o-2],escape:s[o]});break;case 381:this.$=new n.Op({left:s[o-2],op:"NOT LIKE",right:s[o]});break;case 382:this.$=new n.Op({left:s[o-4],op:"NOT LIKE",right:s[o-2],escape:s[o]});break;case 383:this.$=new n.Op({left:s[o-2],op:"||",right:s[o]});break;case 384:this.$=new n.Op({left:s[o-2],op:"+",right:s[o]});break;case 385:this.$=new n.Op({left:s[o-2],op:"-",right:s[o]});break;case 386:this.$=new n.Op({left:s[o-2],op:"*",right:s[o]});break;case 387:this.$=new n.Op({left:s[o-2],op:"/",right:s[o]});break;case 388:this.$=new n.Op({left:s[o-2],op:"%",right:s[o]});break;case 389:this.$=new n.Op({left:s[o-2],op:"^",right:s[o]});break;case 390:this.$=new n.Op({left:s[o-2],op:">>",right:s[o]});break;case 391:this.$=new n.Op({left:s[o-2],op:"<<",right:s[o]});break;case 392:this.$=new n.Op({left:s[o-2],op:"&",right:s[o]});break;case 393:this.$=new n.Op({left:s[o-2],op:"|",right:s[o]});break;case 394:case 395:case 397:this.$=new n.Op({left:s[o-2],op:"->",right:s[o]});break;case 396:this.$=new n.Op({left:s[o-4],op:"->",right:s[o-1]});break;case 398:case 399:case 401:this.$=new n.Op({left:s[o-2],op:"!",right:s[o]});break;case 400:this.$=new n.Op({left:s[o-4],op:"!",right:s[o-1]});break;case 402:this.$=new n.Op({left:s[o-2],op:">",right:s[o]});break;case 403:this.$=new n.Op({left:s[o-2],op:">=",right:s[o]});break;case 404:this.$=new n.Op({left:s[o-2],op:"<",right:s[o]});break;case 405:this.$=new n.Op({left:s[o-2],op:"<=",right:s[o]});break;case 406:this.$=new n.Op({left:s[o-2],op:"=",right:s[o]});break;case 407:this.$=new n.Op({left:s[o-2],op:"==",right:s[o]});break;case 408:this.$=new n.Op({left:s[o-2],op:"===",right:s[o]});break;case 409:this.$=new n.Op({left:s[o-2],op:"!=",right:s[o]});break;case 410:this.$=new n.Op({left:s[o-2],op:"!==",right:s[o]});break;case 411:this.$=new n.Op({left:s[o-2],op:"!===",right:s[o]});break;case 412:n.queries||(n.queries=[]),this.$=new n.Op({left:s[o-5],op:s[o-4],allsome:s[o-3],right:s[o-1],queriesidx:n.queries.length}),n.queries.push(s[o-1]);break;case 413:this.$=new n.Op({left:s[o-5],op:s[o-4],allsome:s[o-3],right:s[o-1]});break;case 414:"BETWEEN1"==s[o-2].op?"AND"==s[o-2].left.op?this.$=new n.Op({left:s[o-2].left.left,op:"AND",right:new n.Op({left:s[o-2].left.right,op:"BETWEEN",right1:s[o-2].right,right2:s[o]})}):this.$=new n.Op({left:s[o-2].left,op:"BETWEEN",right1:s[o-2].right,right2:s[o]}):"NOT BETWEEN1"==s[o-2].op?"AND"==s[o-2].left.op?this.$=new n.Op({left:s[o-2].left.left,op:"AND",right:new n.Op({left:s[o-2].left.right,op:"NOT BETWEEN",right1:s[o-2].right,right2:s[o]})}):this.$=new n.Op({left:s[o-2].left,op:"NOT BETWEEN",right1:s[o-2].right,right2:s[o]}):this.$=new n.Op({left:s[o-2],op:"AND",right:s[o]});break;case 415:this.$=new n.Op({left:s[o-2],op:"OR",right:s[o]});break;case 416:this.$=new n.UniOp({op:"NOT",right:s[o]});break;case 417:this.$=new n.UniOp({op:"-",right:s[o]});break;case 418:this.$=new n.UniOp({op:"+",right:s[o]});break;case 419:this.$=new n.UniOp({op:"~",right:s[o]});break;case 420:this.$=new n.UniOp({op:"#",right:s[o]});break;case 421:this.$=new n.UniOp({right:s[o-1]});break;case 422:n.queries||(n.queries=[]),this.$=new n.Op({left:s[o-4],op:"IN",right:s[o-1],queriesidx:n.queries.length
}),n.queries.push(s[o-1]);break;case 423:n.queries||(n.queries=[]),this.$=new n.Op({left:s[o-5],op:"NOT IN",right:s[o-1],queriesidx:n.queries.length}),n.queries.push(s[o-1]);break;case 424:this.$=new n.Op({left:s[o-4],op:"IN",right:s[o-1]});break;case 425:this.$=new n.Op({left:s[o-5],op:"NOT IN",right:s[o-1]});break;case 426:this.$=new n.Op({left:s[o-3],op:"IN",right:[]});break;case 427:this.$=new n.Op({left:s[o-4],op:"NOT IN",right:[]});break;case 428:case 430:this.$=new n.Op({left:s[o-2],op:"IN",right:s[o]});break;case 429:case 431:this.$=new n.Op({left:s[o-3],op:"NOT IN",right:s[o]});break;case 432:this.$=new n.Op({left:s[o-2],op:"BETWEEN1",right:s[o]});break;case 433:this.$=new n.Op({left:s[o-2],op:"NOT BETWEEN1",right:s[o]});break;case 434:this.$=new n.Op({op:"IS",left:s[o-2],right:s[o]});break;case 435:this.$=new n.Op({op:"IS",left:s[o-2],right:new n.UniOp({op:"NOT",right:new n.NullValue({value:void 0})})});break;case 436:this.$=new n.Convert({expression:s[o-2]}),n.extend(this.$,s[o]);break;case 437:case 438:this.$=s[o];break;case 439:this.$=s[o-1];break;case 446:this.$="ALL";break;case 447:this.$="SOME";break;case 448:this.$="ANY";break;case 449:this.$=new n.Update({table:s[o-4],columns:s[o-2],where:s[o]});break;case 450:this.$=new n.Update({table:s[o-2],columns:s[o]});break;case 453:this.$=new n.SetColumn({column:s[o-2],expression:s[o]});break;case 454:this.$=new n.SetColumn({variable:s[o-2],expression:s[o],method:s[o-3]});break;case 455:this.$=new n.Delete({table:s[o-2],where:s[o]});break;case 456:this.$=new n.Delete({table:s[o]});break;case 457:this.$=new n.Insert({into:s[o-2],values:s[o]});break;case 458:this.$=new n.Insert({into:s[o-1],values:s[o]});break;case 459:case 461:this.$=new n.Insert({into:s[o-2],values:s[o],orreplace:!0});break;case 460:case 462:this.$=new n.Insert({into:s[o-1],values:s[o],orreplace:!0});break;case 463:this.$=new n.Insert({into:s[o-2],default:!0});break;case 464:this.$=new n.Insert({into:s[o-5],columns:s[o-3],values:s[o]});break;case 465:this.$=new n.Insert({into:s[o-4],columns:s[o-2],values:s[o]});break;case 466:this.$=new n.Insert({into:s[o-1],select:s[o]});break;case 467:this.$=new n.Insert({into:s[o-1],select:s[o],orreplace:!0});break;case 468:this.$=new n.Insert({into:s[o-4],columns:s[o-2],select:s[o]});break;case 473:this.$=[s[o-1]];break;case 476:this.$=s[o-4],s[o-4].push(s[o-1]);break;case 477:case 478:case 480:case 488:this.$=s[o-2],s[o-2].push(s[o]);break;case 489:this.$=new n.CreateTable({table:s[o-4]}),n.extend(this.$,s[o-7]),n.extend(this.$,s[o-6]),n.extend(this.$,s[o-5]),n.extend(this.$,s[o-2]),n.extend(this.$,s[o]);break;case 490:this.$=new n.CreateTable({table:s[o]}),n.extend(this.$,s[o-3]),n.extend(this.$,s[o-2]),n.extend(this.$,s[o-1]);break;case 492:this.$={class:!0};break;case 502:this.$={temporary:!0};break;case 504:this.$={ifnotexists:!0};break;case 505:this.$={columns:s[o-2],constraints:s[o]};break;case 506:this.$={columns:s[o]};break;case 507:this.$={as:s[o]};break;case 508:case 532:this.$=[s[o]];break;case 510:case 511:case 512:case 513:case 514:s[o].constraintid=s[o-1],this.$=s[o];break;case 517:this.$={type:"CHECK",expression:s[o-1]};break;case 518:this.$={type:"PRIMARY KEY",columns:s[o-1],clustered:(s[o-3]+"").toUpperCase()};break;case 519:this.$={type:"FOREIGN KEY",columns:s[o-5],fktable:s[o-2],fkcolumns:s[o-1]};break;case 525:this.$={type:"UNIQUE",columns:s[o-1],clustered:(s[o-3]+"").toUpperCase()};break;case 534:this.$=new n.ColumnDef({columnid:s[o-2]}),n.extend(this.$,s[o-1]),n.extend(this.$,s[o]);break;case 535:this.$=new n.ColumnDef({columnid:s[o-1]}),n.extend(this.$,s[o]);break;case 536:this.$=new n.ColumnDef({columnid:s[o],dbtypeid:""});break;case 537:this.$={dbtypeid:s[o-5],dbsize:s[o-3],dbprecision:+s[o-1]};break;case 538:this.$={dbtypeid:s[o-3],dbsize:s[o-1]};break;case 539:this.$={dbtypeid:s[o]};break;case 540:this.$={dbtypeid:"ENUM",enumvalues:s[o-1]};break;case 541:this.$=s[o-1],s[o-1].dbtypeid+="["+s[o]+"]";break;case 543:case 750:this.$=+s[o];break;case 545:this.$=void 0;break;case 547:n.extend(s[o-1],s[o]),this.$=s[o-1];break;case 550:this.$={primarykey:!0};break;case 551:case 552:this.$={foreignkey:{table:s[o-1],columnid:s[o]}};break;case 553:this.$={identity:{value:s[o-3],step:s[o-1]}};break;case 554:this.$={identity:{value:1,step:1}};break;case 555:case 557:this.$={default:s[o]};break;case 556:this.$={default:s[o-1]};break;case 558:this.$={null:!0};break;case 559:this.$={notnull:!0};break;case 560:this.$={check:s[o]};break;case 561:this.$={unique:!0};break;case 562:this.$={onupdate:s[o]};break;case 563:this.$={onupdate:s[o-1]};break;case 564:this.$=new n.DropTable({tables:s[o],type:s[o-2]}),n.extend(this.$,s[o-1]);break;case 568:this.$={ifexists:!0};break;case 569:this.$=new n.AlterTable({table:s[o-3],renameto:s[o]});break;case 570:this.$=new n.AlterTable({table:s[o-3],addcolumn:s[o]});break;case 571:this.$=new n.AlterTable({table:s[o-3],modifycolumn:s[o]});break;case 572:this.$=new n.AlterTable({table:s[o-5],renamecolumn:s[o-2],to:s[o]});break;case 573:this.$=new n.AlterTable({table:s[o-3],dropcolumn:s[o]});break;case 574:this.$=new n.AlterTable({table:s[o-2],renameto:s[o]});break;case 575:this.$=new n.AttachDatabase({databaseid:s[o],engineid:s[o-2].toUpperCase()});break;case 576:this.$=new n.AttachDatabase({databaseid:s[o-3],engineid:s[o-5].toUpperCase(),args:s[o-1]});break;case 577:this.$=new n.AttachDatabase({databaseid:s[o-2],engineid:s[o-4].toUpperCase(),as:s[o]});break;case 578:this.$=new n.AttachDatabase({databaseid:s[o-5],engineid:s[o-7].toUpperCase(),as:s[o],args:s[o-3]});break;case 579:this.$=new n.DetachDatabase({databaseid:s[o]});break;case 580:this.$=new n.CreateDatabase({databaseid:s[o]}),n.extend(this.$,s[o]);break;case 581:this.$=new n.CreateDatabase({engineid:s[o-4].toUpperCase(),databaseid:s[o-1],as:s[o]}),n.extend(this.$,s[o-2]);break;case 582:this.$=new n.CreateDatabase({engineid:s[o-7].toUpperCase(),databaseid:s[o-4],args:s[o-2],as:s[o]}),n.extend(this.$,s[o-5]);break;case 583:this.$=new n.CreateDatabase({engineid:s[o-4].toUpperCase(),as:s[o],args:[s[o-1]]}),n.extend(this.$,s[o-2]);break;case 584:this.$=void 0;break;case 586:case 587:this.$=new n.UseDatabase({databaseid:s[o]});break;case 588:this.$=new n.DropDatabase({databaseid:s[o]}),n.extend(this.$,s[o-1]);break;case 589:case 590:this.$=new n.DropDatabase({databaseid:s[o],engineid:s[o-3].toUpperCase()}),n.extend(this.$,s[o-1]);break;case 591:this.$=new n.CreateIndex({indexid:s[o-5],table:s[o-3],columns:s[o-1]});break;case 592:this.$=new n.CreateIndex({indexid:s[o-5],table:s[o-3],columns:s[o-1],unique:!0});break;case 593:this.$=new n.DropIndex({indexid:s[o]});break;case 594:this.$=new n.ShowDatabases;break;case 595:this.$=new n.ShowDatabases({like:s[o]});break;case 596:this.$=new n.ShowDatabases({engineid:s[o-1].toUpperCase()});break;case 597:this.$=new n.ShowDatabases({engineid:s[o-3].toUpperCase(),like:s[o]});break;case 598:this.$=new n.ShowTables;break;case 599:this.$=new n.ShowTables({like:s[o]});break;case 600:this.$=new n.ShowTables({databaseid:s[o]});break;case 601:this.$=new n.ShowTables({like:s[o],databaseid:s[o-2]});break;case 602:this.$=new n.ShowColumns({table:s[o]});break;case 603:this.$=new n.ShowColumns({table:s[o-2],databaseid:s[o]});break;case 604:this.$=new n.ShowIndex({table:s[o]});break;case 605:this.$=new n.ShowIndex({table:s[o-2],databaseid:s[o]});break;case 606:this.$=new n.ShowCreateTable({table:s[o]});break;case 607:this.$=new n.ShowCreateTable({table:s[o-2],databaseid:s[o]});break;case 608:this.$=new n.CreateTable({table:s[o-6],view:!0,select:s[o-1],viewcolumns:s[o-4]}),n.extend(this.$,s[o-9]),n.extend(this.$,s[o-7]);break;case 609:this.$=new n.CreateTable({table:s[o-3],view:!0,select:s[o-1]}),n.extend(this.$,s[o-6]),n.extend(this.$,s[o-4]);break;case 613:this.$=new n.DropTable({tables:s[o],view:!0}),n.extend(this.$,s[o-1]);break;case 614:case 760:this.$=new n.ExpressionStatement({expression:s[o]});break;case 615:this.$=new n.Source({url:s[o].value});break;case 616:this.$=new n.Assert({value:s[o]});break;case 617:this.$=new n.Assert({value:s[o].value});break;case 618:this.$=new n.Assert({value:s[o],message:s[o-2]});break;case 620:case 631:case 633:this.$=s[o].value;break;case 621:case 629:this.$=+s[o].value;break;case 622:this.$=!!s[o].value;break;case 630:this.$=""+s[o].value;break;case 639:this.$={};break;case 642:this.$=[];break;case 643:n.extend(s[o-2],s[o]),this.$=s[o-2];break;case 645:this.$={},this.$[s[o-2].substr(1,s[o-2].length-2)]=s[o];break;case 646:case 647:this.$={},this.$[s[o-2]]=s[o];break;case 650:this.$=new n.SetVariable({variable:s[o-2].toLowerCase(),value:s[o]});break;case 651:this.$=new n.SetVariable({variable:s[o-1].toLowerCase(),value:s[o]});break;case 652:this.$=new n.SetVariable({variable:s[o-2],expression:s[o]});break;case 653:this.$=new n.SetVariable({variable:s[o-3],props:s[o-2],expression:s[o]});break;case 654:this.$=new n.SetVariable({variable:s[o-2],expression:s[o],method:s[o-3]});break;case 655:this.$=new n.SetVariable({variable:s[o-3],props:s[o-2],expression:s[o],method:s[o-4]});break;case 656:this.$="@";break;case 657:this.$="$";break;case 663:this.$=!0;break;case 664:this.$=!1;break;case 665:this.$=new n.CommitTransaction;break;case 666:this.$=new n.RollbackTransaction;break;case 667:this.$=new n.BeginTransaction;break;case 668:this.$=new n.If({expression:s[o-2],thenstat:s[o-1],elsestat:s[o]}),s[o-1].exists&&(this.$.exists=s[o-1].exists),s[o-1].queries&&(this.$.queries=s[o-1].queries);break;case 669:this.$=new n.If({expression:s[o-1],thenstat:s[o]}),s[o].exists&&(this.$.exists=s[o].exists),s[o].queries&&(this.$.queries=s[o].queries);break;case 670:this.$=s[o];break;case 671:this.$=new n.While({expression:s[o-1],loopstat:s[o]}),s[o].exists&&(this.$.exists=s[o].exists),s[o].queries&&(this.$.queries=s[o].queries);break;case 672:this.$=new n.Continue;break;case 673:this.$=new n.Break;break;case 674:this.$=new n.BeginEnd({statements:s[o-1]});break;case 675:this.$=new n.Print({exprs:s[o]});break;case 676:this.$=new n.Print({select:s[o]});break;case 677:this.$=new n.Require({paths:s[o]});break;case 678:this.$=new n.Require({plugins:s[o]});break;case 679:case 680:this.$=s[o].toUpperCase();break;case 681:this.$=new n.Echo({expr:s[o]});break;case 686:this.$=new n.Declare({declares:s[o]});break;case 689:this.$={variable:s[o-1]},n.extend(this.$,s[o]);break;case 690:this.$={variable:s[o-2]},n.extend(this.$,s[o]);break;case 691:this.$={variable:s[o-3],expression:s[o]},n.extend(this.$,s[o-2]);break;case 692:this.$={variable:s[o-4],expression:s[o]},n.extend(this.$,s[o-2]);break;case 693:this.$=new n.TruncateTable({table:s[o]});break;case 694:this.$=new n.Merge,n.extend(this.$,s[o-4]),n.extend(this.$,s[o-3]),n.extend(this.$,s[o-2]),n.extend(this.$,{matches:s[o-1]}),n.extend(this.$,s[o]);break;case 695:case 696:this.$={into:s[o]};break;case 698:this.$={on:s[o]};break;case 703:this.$={matched:!0,action:s[o]};break;case 704:this.$={matched:!0,expr:s[o-2],action:s[o]};break;case 705:this.$={delete:!0};break;case 706:this.$={update:s[o]};break;case 707:case 708:this.$={matched:!1,bytarget:!0,action:s[o]};break;case 709:case 710:this.$={matched:!1,bytarget:!0,expr:s[o-2],action:s[o]};break;case 711:this.$={matched:!1,bysource:!0,action:s[o]};break;case 712:this.$={matched:!1,bysource:!0,expr:s[o-2],action:s[o]};break;case 713:this.$={insert:!0,values:s[o]};break;case 714:this.$={insert:!0,values:s[o],columns:s[o-3]};break;case 715:this.$={insert:!0,defaultvalues:!0};break;case 716:this.$={insert:!0,defaultvalues:!0,columns:s[o-3]};break;case 718:this.$={output:{columns:s[o]}};break;case 719:this.$={output:{columns:s[o-3],intovar:s[o],method:s[o-1]}};break;case 720:this.$={output:{columns:s[o-2],intotable:s[o]}};break;case 721:this.$={output:{columns:s[o-5],intotable:s[o-3],intocolumns:s[o-1]}};break;case 722:this.$=new n.CreateVertex({class:s[o-3],sharp:s[o-2],name:s[o-1]}),n.extend(this.$,s[o]);break;case 725:this.$={sets:s[o]};break;case 726:this.$={content:s[o]};break;case 727:this.$={select:s[o]};break;case 728:this.$=new n.CreateEdge({from:s[o-3],to:s[o-1],name:s[o-5]}),n.extend(this.$,s[o]);break;case 729:this.$=new n.CreateGraph({graph:s[o]});break;case 730:this.$=new n.CreateGraph({from:s[o]});break;case 733:this.$=s[o-2],s[o-1]&&(this.$.json=new n.Json({value:s[o-1]})),s[o]&&(this.$.as=s[o]);break;case 734:this.$={source:s[o-6],target:s[o]},s[o-3]&&(this.$.json=new n.Json({value:s[o-3]})),s[o-2]&&(this.$.as=s[o-2]),n.extend(this.$,s[o-4]);break;case 735:this.$={source:s[o-5],target:s[o]},s[o-2]&&(this.$.json=new n.Json({value:s[o-3]})),s[o-1]&&(this.$.as=s[o-2]);break;case 736:this.$={source:s[o-2],target:s[o]};break;case 740:this.$={vars:s[o],method:s[o-1]};break;case 743:case 744:var b=s[o-1];this.$={prop:s[o-3],sharp:s[o-2],name:void 0===b?void 0:b.substr(1,b.length-2),class:s[o]};break;case 745:var E=s[o-1];this.$={sharp:s[o-2],name:void 0===E?void 0:E.substr(1,E.length-2),class:s[o]};break;case 746:var g=s[o-1];this.$={name:void 0===g?void 0:g.substr(1,g.length-2),class:s[o]};break;case 747:this.$={class:s[o]};break;case 753:this.$=new n.AddRule({left:s[o-2],right:s[o]});break;case 754:this.$=new n.AddRule({right:s[o]});break;case 757:this.$=new n.Term({termid:s[o]});break;case 758:this.$=new n.Term({termid:s[o-3],args:s[o-1]});break;case 761:this.$=new n.CreateTrigger({trigger:s[o-6],when:s[o-5],action:s[o-4],table:s[o-2],statement:s[o]}),s[o].exists&&(this.$.exists=s[o].exists),s[o].queries&&(this.$.queries=s[o].queries);break;case 762:this.$=new n.CreateTrigger({trigger:s[o-5],when:s[o-4],action:s[o-3],table:s[o-1],funcid:s[o]});break;case 763:this.$=new n.CreateTrigger({trigger:s[o-6],when:s[o-4],action:s[o-3],table:s[o-5],statement:s[o]}),s[o].exists&&(this.$.exists=s[o].exists),s[o].queries&&(this.$.queries=s[o].queries);break;case 764:case 765:case 767:this.$="AFTER";break;case 766:this.$="BEFORE";break;case 768:this.$="INSTEADOF";break;case 769:this.$="INSERT";break;case 770:this.$="DELETE";break;case 771:this.$="UPDATE";break;case 772:this.$=new n.DropTrigger({trigger:s[o]});break;case 773:this.$=new n.Reindex({indexid:s[o]});break;case 1047:case 1067:case 1069:case 1071:case 1075:case 1077:case 1079:case 1081:case 1083:case 1085:this.$=[];break;case 1048:case 1062:case 1064:case 1068:case 1070:case 1072:case 1076:case 1078:case 1080:case 1082:case 1084:case 1086:s[o-1].push(s[o]);break;case 1061:case 1063:this.$=[s[o]]}},table:[t([10,602,764],r,{8:1,9:2,12:3,13:4,17:5,18:7,19:8,20:9,21:10,22:11,23:12,24:13,25:14,26:15,27:16,28:17,29:18,30:19,31:20,32:21,33:22,34:23,35:24,36:25,37:26,38:27,39:28,40:29,41:30,42:31,43:32,44:33,45:34,46:35,47:36,48:37,49:38,50:39,51:40,52:41,54:43,55:44,56:45,57:46,58:47,59:48,60:49,61:50,62:51,63:52,64:53,65:54,66:55,67:56,68:57,69:58,70:59,71:60,79:75,504:95,184:99,3:100,2:n,4:a,5:s,14:i,53:o,72:u,89:c,124:l,146:h,156:d,189:f,266:p,267:b,290:E,335:g,338:m,339:v,396:S,400:T,401:y,404:N,406:C,408:R,409:O,417:I,418:x,434:D,436:k,437:L,439:$,440:M,441:U,442:_,443:F,447:P,448:q,451:G,452:V,505:B,507:j,508:H,517:J}),{1:[3]},{10:[1,105],11:106,602:Y,764:W},t(X,[2,8]),t(X,[2,9]),t(K,[2,12]),t(X,r,{17:5,18:7,19:8,20:9,21:10,22:11,23:12,24:13,25:14,26:15,27:16,28:17,29:18,30:19,31:20,32:21,33:22,34:23,35:24,36:25,37:26,38:27,39:28,40:29,41:30,42:31,43:32,44:33,45:34,46:35,47:36,48:37,49:38,50:39,51:40,52:41,54:43,55:44,56:45,57:46,58:47,59:48,60:49,61:50,62:51,63:52,64:53,65:54,66:55,67:56,68:57,69:58,70:59,71:60,79:75,504:95,184:99,3:100,12:109,2:n,4:a,5:s,15:[1,110],53:o,72:u,89:c,124:l,146:h,156:d,189:f,266:p,267:b,290:E,335:g,338:m,339:v,396:S,400:T,401:y,404:N,406:C,408:R,409:O,417:I,418:x,434:D,436:k,437:L,439:$,440:M,441:U,442:_,443:F,447:P,448:q,451:G,452:V,505:B,507:j,508:H,517:J}),t(K,[2,14]),t(K,[2,15]),t(K,[2,16]),t(K,[2,17]),t(K,[2,18]),t(K,[2,19]),t(K,[2,20]),t(K,[2,21]),t(K,[2,22]),t(K,[2,23]),t(K,[2,24]),t(K,[2,25]),t(K,[2,26]),t(K,[2,27]),t(K,[2,28]),t(K,[2,29]),t(K,[2,30]),t(K,[2,31]),t(K,[2,32]),t(K,[2,33]),t(K,[2,34]),t(K,[2,35]),t(K,[2,36]),t(K,[2,37]),t(K,[2,38]),t(K,[2,39]),t(K,[2,40]),t(K,[2,41]),t(K,[2,42]),t(K,[2,43]),t(K,[2,44]),t(K,[2,45]),t(K,[2,46]),t(K,[2,47]),t(K,[2,48]),t(K,[2,49]),t(K,[2,50]),t(K,[2,51]),t(K,[2,52]),t(K,[2,53]),t(K,[2,54]),t(K,[2,55]),t(K,[2,56]),t(K,[2,57]),t(K,[2,58]),t(K,[2,59]),t(K,[2,60]),t(K,[2,61]),t(K,[2,62]),t(K,[2,63]),t(K,[2,64]),t(K,[2,65]),t(K,[2,66]),t(K,[2,67]),{353:[1,111]},{2:n,3:112,4:a,5:s},{2:n,3:114,4:a,5:s,156:Q,200:113,290:z,291:Z,292:ee,293:te},t(re,[2,501],{3:121,348:125,2:n,4:a,5:s,134:ne,135:ae,187:[1,123],193:[1,122],268:[1,129],269:[1,130],357:[1,131],405:[1,120],472:[1,124],509:[1,128]}),{145:se,449:132,450:133},{183:[1,135]},{405:[1,136]},{2:n,3:138,4:a,5:s,130:[1,144],193:[1,139],353:[1,143],397:140,405:[1,137],410:[1,141],509:[1,142]},{2:n,3:168,4:a,5:s,56:165,77:ie,94:145,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(Be,je,{340:204,171:[1,205],198:He}),t(Be,je,{340:207,198:He}),{2:n,3:219,4:a,5:s,77:Je,132:Ye,143:le,144:212,145:he,152:fe,156:Q,181:ge,198:[1,210],199:213,200:215,201:214,202:217,209:209,213:We,214:218,290:z,291:Z,292:ee,293:te,302:_e,419:190,420:Ge,424:Ve,453:208},{2:n,3:221,4:a,5:s},{353:[1,222]},t(Xe,[2,1043],{80:223,106:224,107:[1,225]}),t(Ke,[2,1047],{90:226}),{2:n,3:230,4:a,5:s,190:[1,228],193:[1,231],267:[1,227],353:[1,232],405:[1,229]},{353:[1,233]},{2:n,3:236,4:a,5:s,73:234,75:235},t([306,602,764],r,{12:3,13:4,17:5,18:7,19:8,20:9,21:10,22:11,23:12,24:13,25:14,26:15,27:16,28:17,29:18,30:19,31:20,32:21,33:22,34:23,35:24,36:25,37:26,38:27,39:28,40:29,41:30,42:31,43:32,44:33,45:34,46:35,47:36,48:37,49:38,50:39,51:40,52:41,54:43,55:44,56:45,57:46,58:47,59:48,60:49,61:50,62:51,63:52,64:53,65:54,66:55,67:56,68:57,69:58,70:59,71:60,79:75,504:95,184:99,3:100,9:238,2:n,4:a,5:s,14:i,53:o,72:u,89:c,124:l,146:h,156:d,189:f,266:p,267:b,290:E,335:g,338:m,339:v,396:S,400:T,401:y,404:N,406:C,408:R,409:O,417:I,418:x,434:D,435:[1,237],436:k,437:L,439:$,440:M,441:U,442:_,443:F,447:P,448:q,451:G,452:V,505:B,507:j,508:H,517:J}),{435:[1,239]},{435:[1,240]},{2:n,3:242,4:a,5:s,405:[1,241]},{2:n,3:244,4:a,5:s,199:243},t(Qe,[2,311]),{113:245,132:ue,296:Le},{2:n,3:114,4:a,5:s,113:251,131:oe,132:[1,248],143:le,144:246,145:ze,152:fe,156:Q,181:ge,196:250,200:255,201:254,257:252,258:253,265:Ze,274:247,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,302:_e,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:257,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(K,[2,672]),t(K,[2,673]),{2:n,3:168,4:a,5:s,40:259,56:165,77:ie,79:75,89:c,94:260,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,151:258,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,184:99,189:f,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:266,4:a,5:s,113:263,132:ue,296:Le,444:261,445:262,446:264,447:et},{2:n,3:267,4:a,5:s,143:tt,145:rt,431:268},{2:n,3:168,4:a,5:s,56:165,77:ie,94:271,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{505:[1,272]},{2:n,3:100,4:a,5:s,504:274,506:273},{2:n,3:114,4:a,5:s,156:Q,200:275,290:z,291:Z,292:ee,293:te},{2:n,3:168,4:a,5:s,56:165,77:ie,94:276,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(nt,at,{186:280,164:[1,279],185:[1,277],187:[1,278],195:st}),t(it,[2,757],{77:[1,282]}),t([2,4,5,10,72,77,78,93,98,107,118,128,131,132,137,143,145,152,154,156,162,164,168,169,179,180,181,183,185,187,195,198,232,245,247,265,266,270,271,273,280,281,282,283,284,285,286,287,288,290,291,292,293,294,295,296,297,298,299,302,303,306,310,312,317,420,424,602,764],[2,152],{149:[1,283],150:[1,284],190:[1,285],191:[1,286],192:[1,287],193:[1,288],194:[1,289]}),t(ot,[2,1]),t(ot,[2,2]),{6:290,131:[1,439],172:[1,462],245:[1,411],285:[1,373],286:[1,407],370:[1,404],381:[1,295],402:[1,297],410:[1,549],414:[1,471],416:[1,443],417:[1,509],433:[1,442],435:[1,525],440:[1,342],460:[1,418],464:[1,448],470:[1,341],514:[1,307],515:[1,299],516:[1,399],518:[1,291],519:[1,292],520:[1,293],521:[1,294],522:[1,296],523:[1,298],524:[1,300],525:[1,301],526:[1,302],527:[1,303],528:[1,304],529:[1,305],530:[1,306],531:[1,308],532:[1,309],533:[1,310],534:[1,311],535:[1,312],536:[1,313],537:[1,314],538:[1,315],539:[1,316],540:[1,317],541:[1,318],542:[1,319],543:[1,320],544:[1,321],545:[1,322],546:[1,323],547:[1,324],548:[1,325],549:[1,326],550:[1,327],551:[1,328],552:[1,329],553:[1,330],554:[1,331],555:[1,332],556:[1,333],557:[1,334],558:[1,335],559:[1,336],560:[1,337],561:[1,338],562:[1,339],563:[1,340],564:[1,343],565:[1,344],566:[1,345],567:[1,346],568:[1,347],569:[1,348],570:[1,349],571:[1,350],572:[1,351],573:[1,352],574:[1,353],575:[1,354],576:[1,355],577:[1,356],578:[1,357],579:[1,358],580:[1,359],581:[1,360],582:[1,361],583:[1,362],584:[1,363],585:[1,364],586:[1,365],587:[1,366],588:[1,367],589:[1,368],590:[1,369],591:[1,370],592:[1,371],593:[1,372],594:[1,374],595:[1,375],596:[1,376],597:[1,377],598:[1,378],599:[1,379],600:[1,380],601:[1,381],602:[1,382],603:[1,383],604:[1,384],605:[1,385],606:[1,386],607:[1,387],608:[1,388],609:[1,389],610:[1,390],611:[1,391],612:[1,392],613:[1,393],614:[1,394],615:[1,395],616:[1,396],617:[1,397],618:[1,398],619:[1,400],620:[1,401],621:[1,402],622:[1,403],623:[1,405],624:[1,406],625:[1,408],626:[1,409],627:[1,410],628:[1,412],629:[1,413],630:[1,414],631:[1,415],632:[1,416],633:[1,417],634:[1,419],635:[1,420],636:[1,421],637:[1,422],638:[1,423],639:[1,424],640:[1,425],641:[1,426],642:[1,427],643:[1,428],644:[1,429],645:[1,430],646:[1,431],647:[1,432],648:[1,433],649:[1,434],650:[1,435],651:[1,436],652:[1,437],653:[1,438],654:[1,440],655:[1,441],656:[1,444],657:[1,445],658:[1,446],659:[1,447],660:[1,449],661:[1,450],662:[1,451],663:[1,452],664:[1,453],665:[1,454],666:[1,455],667:[1,456],668:[1,457],669:[1,458],670:[1,459],671:[1,460],672:[1,461],673:[1,463],674:[1,464],675:[1,465],676:[1,466],677:[1,467],678:[1,468],679:[1,469],680:[1,470],681:[1,472],682:[1,473],683:[1,474],684:[1,475],685:[1,476],686:[1,477],687:[1,478],688:[1,479],689:[1,480],690:[1,481],691:[1,482],692:[1,483],693:[1,484],694:[1,485],695:[1,486],696:[1,487],697:[1,488],698:[1,489],699:[1,490],700:[1,491],701:[1,492],702:[1,493],703:[1,494],704:[1,495],705:[1,496],706:[1,497],707:[1,498],708:[1,499],709:[1,500],710:[1,501],711:[1,502],712:[1,503],713:[1,504],714:[1,505],715:[1,506],716:[1,507],717:[1,508],718:[1,510],719:[1,511],720:[1,512],721:[1,513],722:[1,514],723:[1,515],724:[1,516],725:[1,517],726:[1,518],727:[1,519],728:[1,520],729:[1,521],730:[1,522],731:[1,523],732:[1,524],733:[1,526],734:[1,527],735:[1,528],736:[1,529],737:[1,530],738:[1,531],739:[1,532],740:[1,533],741:[1,534],742:[1,535],743:[1,536],744:[1,537],745:[1,538],746:[1,539],747:[1,540],748:[1,541],749:[1,542],750:[1,543],751:[1,544],752:[1,545],753:[1,546],754:[1,547],755:[1,548],756:[1,550],757:[1,551],758:[1,552],759:[1,553],760:[1,554],761:[1,555],762:[1,556],763:[1,557]},{1:[2,6]},t(X,r,{17:5,18:7,19:8,20:9,21:10,22:11,23:12,24:13,25:14,26:15,27:16,28:17,29:18,30:19,31:20,32:21,33:22,34:23,35:24,36:25,37:26,38:27,39:28,40:29,41:30,42:31,43:32,44:33,45:34,46:35,47:36,48:37,49:38,50:39,51:40,52:41,54:43,55:44,56:45,57:46,58:47,59:48,60:49,61:50,62:51,63:52,64:53,65:54,66:55,67:56,68:57,69:58,70:59,71:60,79:75,504:95,184:99,3:100,12:558,2:n,4:a,5:s,53:o,72:u,89:c,124:l,146:h,156:d,189:f,266:p,267:b,290:E,335:g,338:m,339:v,396:S,400:T,401:y,404:N,406:C,408:R,409:O,417:I,418:x,434:D,436:k,437:L,439:$,440:M,441:U,442:_,443:F,447:P,448:q,451:G,452:V,505:B,507:j,508:H,517:J}),t(ut,[2,1041]),t(ut,[2,1042]),t(X,[2,10]),{16:[1,559]},{2:n,3:244,4:a,5:s,199:560},{405:[1,561]},t(K,[2,760]),{77:ct},{77:[1,563]},{77:lt},{77:[1,565]},{77:[1,566]},{2:n,3:168,4:a,5:s,56:165,77:ie,94:567,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(Be,ht,{350:568,156:dt}),{405:[1,570]},{2:n,3:571,4:a,5:s},{193:[1,572]},{2:n,3:578,4:a,5:s,132:ft,137:pt,143:tt,145:rt,152:bt,183:[1,574],431:585,473:573,474:575,475:576,478:577,482:582,493:579,497:581},{130:[1,589],349:586,353:[1,588],410:[1,587]},{113:591,132:ue,183:[2,1141],296:Le,471:590},t(Et,[2,1135],{465:592,3:593,2:n,4:a,5:s}),{2:n,3:594,4:a,5:s},{4:[1,595]},{4:[1,596]},t(re,[2,502]),t(K,[2,686],{74:[1,597]}),t(gt,[2,687]),{2:n,3:598,4:a,5:s},{2:n,3:244,4:a,5:s,199:599},{2:n,3:600,4:a,5:s},t(Be,mt,{398:601,156:vt}),{405:[1,603]},{2:n,3:604,4:a,5:s},t(Be,mt,{398:605,156:vt}),t(Be,mt,{398:606,156:vt}),{2:n,3:607,4:a,5:s},t(St,[2,1129]),t(St,[2,1130]),t(K,r,{17:5,18:7,19:8,20:9,21:10,22:11,23:12,24:13,25:14,26:15,27:16,28:17,29:18,30:19,31:20,32:21,33:22,34:23,35:24,36:25,37:26,38:27,39:28,40:29,41:30,42:31,43:32,44:33,45:34,46:35,47:36,48:37,49:38,50:39,51:40,52:41,54:43,55:44,56:45,57:46,58:47,59:48,60:49,61:50,62:51,63:52,64:53,65:54,66:55,67:56,68:57,69:58,70:59,71:60,79:75,504:95,184:99,3:100,12:608,114:625,327:637,2:n,4:a,5:s,53:o,72:u,89:c,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:Rt,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,146:h,154:$t,156:d,170:Mt,171:Ut,179:_t,180:Ft,189:f,266:p,267:b,290:E,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr,335:g,338:m,339:v,396:S,400:T,401:y,404:N,406:C,408:R,409:O,417:I,418:x,434:D,436:k,437:L,439:$,440:M,441:U,442:_,443:F,447:P,448:q,451:G,452:V,505:B,507:j,508:H,517:J}),t(Qe,[2,288]),t(Qe,[2,289]),t(Qe,[2,290]),t(Qe,[2,291]),t(Qe,[2,292]),t(Qe,[2,293]),t(Qe,[2,294]),t(Qe,[2,295]),t(Qe,[2,296]),t(Qe,[2,297]),t(Qe,[2,298]),t(Qe,[2,299]),t(Qe,[2,300]),t(Qe,[2,301]),t(Qe,[2,302]),t(Qe,[2,303]),t(Qe,[2,304]),t(Qe,[2,305]),{2:n,3:168,4:a,5:s,26:654,27:653,36:649,40:648,56:165,77:ie,79:75,89:c,94:651,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,184:99,189:f,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,264:650,265:me,266:p,267:[1,655],270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:[1,652],291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,339:v,419:190,420:Ge,424:Ve},t(Qe,[2,309]),t(Qe,[2,310]),{77:[1,656]},t([2,4,5,10,53,72,74,76,78,89,93,95,98,99,107,112,115,118,122,123,124,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,179,180,181,183,185,187,189,198,206,208,222,223,224,225,226,227,228,229,232,239,242,243,245,247,266,267,280,281,282,283,284,285,286,287,288,290,296,300,306,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,330,331,332,333,335,338,339,396,400,401,404,406,408,409,417,418,420,424,434,436,437,439,440,441,442,443,447,448,451,452,464,470,505,507,508,517,602,764],ar,{77:ct,116:[1,657]}),{2:n,3:168,4:a,5:s,56:165,77:ie,94:658,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:659,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:660,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:661,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:662,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve
},t(Qe,[2,283]),t([2,4,5,10,53,72,74,76,77,78,89,93,95,98,99,107,112,115,116,118,122,123,124,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,179,180,181,183,185,187,189,198,206,208,222,223,224,225,226,227,228,229,230,231,232,239,242,243,245,247,249,265,266,267,270,271,273,280,281,282,283,284,285,286,287,288,290,291,292,293,294,295,296,297,298,299,300,302,303,306,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,330,331,332,333,335,338,339,343,356,368,369,373,374,396,400,401,404,406,408,409,415,417,418,420,424,426,434,436,437,439,440,441,442,443,447,448,451,452,464,470,505,507,508,517,602,764,765,766],[2,356]),t(sr,[2,357]),t(sr,[2,358]),t(sr,ir),t(sr,[2,360]),t([2,4,5,10,53,72,74,76,77,78,89,93,95,98,99,107,112,115,116,118,122,123,124,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,179,180,181,183,185,187,189,198,206,208,222,223,224,225,226,227,228,229,230,232,239,242,243,245,247,266,267,280,281,282,283,284,285,286,287,288,290,296,297,300,306,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,330,331,332,333,335,338,339,343,356,368,369,373,374,396,400,401,404,406,408,409,417,418,420,424,426,434,436,437,439,440,441,442,443,447,448,451,452,464,470,505,507,508,517,602,764],[2,361]),{2:n,3:664,4:a,5:s,131:[1,665],301:663},{2:n,3:666,4:a,5:s},t(sr,[2,367]),t(sr,[2,368]),{2:n,3:667,4:a,5:s,77:or,113:669,131:oe,132:ue,143:le,152:fe,181:ge,196:670,201:672,257:671,294:De,295:ke,296:Le,302:_e,419:673,424:Ve},{77:[1,674]},{2:n,3:168,4:a,5:s,56:165,77:ie,94:675,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,304:676,307:677,308:ur,312:Pe,317:qe,419:190,420:Ge,424:Ve},{77:[1,679]},{77:[1,680]},t(cr,[2,624]),{2:n,3:695,4:a,5:s,77:lr,111:690,113:688,131:oe,132:ue,143:le,144:685,145:ze,152:fe,156:Q,181:ge,196:687,200:693,201:692,257:689,258:691,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,300:[1,683],302:_e,419:190,420:Ge,421:681,422:684,423:686,424:Ve,427:682},{2:n,3:168,4:a,5:s,56:165,77:ie,94:260,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,151:696,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:697,4:a,5:s,156:Q,200:698,290:z,291:Z,292:ee,293:te},{77:[2,336]},{77:[2,337]},{77:[2,338]},{77:[2,339]},{77:[2,340]},{77:[2,341]},{77:[2,342]},{77:[2,343]},{77:[2,344]},{2:n,3:704,4:a,5:s,131:hr,132:dr,425:699,426:[1,700],428:701},{2:n,3:244,4:a,5:s,199:705},{290:[1,706]},t(Be,[2,472]),{2:n,3:244,4:a,5:s,199:707},{231:[1,709],454:708},{231:[2,695]},{2:n,3:219,4:a,5:s,77:Je,132:Ye,143:le,144:212,145:he,152:fe,156:Q,181:ge,199:213,200:215,201:214,202:217,209:710,213:We,214:218,290:z,291:Z,292:ee,293:te,302:_e,419:190,420:Ge,424:Ve},{40:711,79:75,89:c,184:99,189:f},t(fr,[2,1091],{210:712,76:[1,713]}),t(pr,[2,185],{3:714,2:n,4:a,5:s,76:[1,715],154:[1,716]}),t(pr,[2,189],{3:717,2:n,4:a,5:s,76:[1,718]}),t(pr,[2,190],{3:719,2:n,4:a,5:s,76:[1,720]}),t(pr,[2,193]),t(pr,[2,194],{3:721,2:n,4:a,5:s,76:[1,722]}),t(pr,[2,197],{3:723,2:n,4:a,5:s,76:[1,724]}),t([2,4,5,10,72,74,76,78,93,98,118,128,154,162,168,169,183,206,208,222,223,224,225,226,227,228,229,230,231,232,245,247,306,310,602,764],br,{77:ct,116:Er}),t([2,4,5,10,72,74,76,78,93,98,118,128,162,168,169,206,208,222,223,224,225,226,227,228,229,230,231,232,245,247,306,310,602,764],[2,200]),t(K,[2,773]),{2:n,3:244,4:a,5:s,199:726},t(gr,mr,{81:727,198:vr}),t(Xe,[2,1044]),t(Sr,[2,1057],{108:729,190:[1,730]}),t([10,78,183,306,310,602,764],mr,{419:190,81:731,117:732,3:733,114:736,144:758,158:768,160:769,2:n,4:a,5:s,72:Tr,76:Ar,77:yr,112:Nr,115:yt,116:Nt,118:Cr,122:Rr,123:Or,124:wr,128:Ir,129:xr,130:Dr,131:kr,132:Lr,133:$r,134:Mr,135:Ur,136:_r,137:Fr,138:Pr,139:qr,140:Gr,141:Vr,142:Br,143:jr,145:Hr,146:Jr,148:Yr,149:Wr,150:Xr,152:Kr,154:Qr,156:zr,162:Zr,164:en,166:tn,168:rn,169:nn,170:an,171:sn,172:on,173:un,175:cn,185:ln,187:hn,198:vr,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,420:Ge,424:Ve}),{353:[1,782]},{183:[1,783]},t(K,[2,594],{112:[1,784]}),{405:[1,785]},{183:[1,786]},t(K,[2,598],{112:[1,787],183:[1,788]}),{2:n,3:244,4:a,5:s,199:789},{40:790,74:[1,791],79:75,89:c,184:99,189:f},t(dn,[2,70]),{76:[1,792]},t(K,[2,667]),{11:106,306:[1,793],602:Y,764:W},t(K,[2,665]),t(K,[2,666]),{2:n,3:794,4:a,5:s},t(K,[2,587]),{146:[1,795]},t([2,4,5,10,53,72,74,76,77,78,89,95,124,128,143,145,146,148,149,152,154,156,181,183,187,189,230,266,267,290,297,302,306,310,335,338,339,343,344,356,368,369,373,374,396,400,401,402,403,404,406,408,409,417,418,420,424,434,436,437,439,440,441,442,443,447,448,451,452,505,507,508,514,515,516,517,602,764],br,{116:Er}),t(K,[2,615]),t(K,[2,616]),t(K,[2,617]),t(K,ir,{74:[1,796]}),{77:or,113:669,131:oe,132:ue,143:le,152:fe,181:ge,196:670,201:672,257:671,294:De,295:ke,296:Le,302:_e,419:673,424:Ve},t(fn,[2,320]),t(fn,[2,321]),t(fn,[2,322]),t(fn,[2,323]),t(fn,[2,324]),t(fn,[2,325]),t(fn,[2,326]),t(K,r,{17:5,18:7,19:8,20:9,21:10,22:11,23:12,24:13,25:14,26:15,27:16,28:17,29:18,30:19,31:20,32:21,33:22,34:23,35:24,36:25,37:26,38:27,39:28,40:29,41:30,42:31,43:32,44:33,45:34,46:35,47:36,48:37,49:38,50:39,51:40,52:41,54:43,55:44,56:45,57:46,58:47,59:48,60:49,61:50,62:51,63:52,64:53,65:54,66:55,67:56,68:57,69:58,70:59,71:60,79:75,504:95,184:99,3:100,114:625,327:637,12:797,2:n,4:a,5:s,53:o,72:u,89:c,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:Rt,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,146:h,154:$t,156:d,170:Mt,171:Ut,179:_t,180:Ft,189:f,266:p,267:b,290:E,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr,335:g,338:m,339:v,396:S,400:T,401:y,404:N,406:C,408:R,409:O,417:I,418:x,434:D,436:k,437:L,439:$,440:M,441:U,442:_,443:F,447:P,448:q,451:G,452:V,505:B,507:j,508:H,517:J}),t(K,[2,675],{74:pn}),t(K,[2,676]),t(bn,[2,354],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),t(K,[2,677],{74:[1,800]}),t(K,[2,678],{74:[1,801]}),t(gt,[2,683]),t(gt,[2,685]),t(gt,[2,679]),t(gt,[2,680]),{114:807,115:yt,116:Nt,124:[1,802],230:gn,429:803,430:804,433:mn},{2:n,3:808,4:a,5:s},t(Be,[2,656]),t(Be,[2,657]),t(K,[2,614],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),{2:n,3:100,4:a,5:s,504:274,506:809},t(K,[2,754],{74:vn}),t(Sn,[2,756]),t(K,[2,759]),t(K,[2,681],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),t(Tn,at,{186:811,195:st}),t(Tn,at,{186:812,195:st}),t(Tn,at,{186:813,195:st}),t(An,[2,1087],{255:146,200:147,256:148,111:149,254:150,196:151,257:152,113:153,258:154,201:155,202:156,259:157,260:158,261:159,144:161,262:162,263:163,56:165,158:167,3:168,419:190,188:814,174:815,253:816,94:817,2:n,4:a,5:s,77:ie,131:oe,132:ue,137:ce,143:le,145:he,149:de,152:fe,154:pe,156:Q,179:be,180:Ee,181:ge,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,420:Ge,424:Ve}),{77:[1,819],131:oe,196:818},{2:n,3:100,4:a,5:s,504:274,506:820},t(yn,[2,153]),t(yn,[2,154]),t(yn,[2,155]),t(yn,[2,156]),t(yn,[2,157]),t(yn,[2,158]),t(yn,[2,159]),t(ot,[2,3]),t(ot,[2,774]),t(ot,[2,775]),t(ot,[2,776]),t(ot,[2,777]),t(ot,[2,778]),t(ot,[2,779]),t(ot,[2,780]),t(ot,[2,781]),t(ot,[2,782]),t(ot,[2,783]),t(ot,[2,784]),t(ot,[2,785]),t(ot,[2,786]),t(ot,[2,787]),t(ot,[2,788]),t(ot,[2,789]),t(ot,[2,790]),t(ot,[2,791]),t(ot,[2,792]),t(ot,[2,793]),t(ot,[2,794]),t(ot,[2,795]),t(ot,[2,796]),t(ot,[2,797]),t(ot,[2,798]),t(ot,[2,799]),t(ot,[2,800]),t(ot,[2,801]),t(ot,[2,802]),t(ot,[2,803]),t(ot,[2,804]),t(ot,[2,805]),t(ot,[2,806]),t(ot,[2,807]),t(ot,[2,808]),t(ot,[2,809]),t(ot,[2,810]),t(ot,[2,811]),t(ot,[2,812]),t(ot,[2,813]),t(ot,[2,814]),t(ot,[2,815]),t(ot,[2,816]),t(ot,[2,817]),t(ot,[2,818]),t(ot,[2,819]),t(ot,[2,820]),t(ot,[2,821]),t(ot,[2,822]),t(ot,[2,823]),t(ot,[2,824]),t(ot,[2,825]),t(ot,[2,826]),t(ot,[2,827]),t(ot,[2,828]),t(ot,[2,829]),t(ot,[2,830]),t(ot,[2,831]),t(ot,[2,832]),t(ot,[2,833]),t(ot,[2,834]),t(ot,[2,835]),t(ot,[2,836]),t(ot,[2,837]),t(ot,[2,838]),t(ot,[2,839]),t(ot,[2,840]),t(ot,[2,841]),t(ot,[2,842]),t(ot,[2,843]),t(ot,[2,844]),t(ot,[2,845]),t(ot,[2,846]),t(ot,[2,847]),t(ot,[2,848]),t(ot,[2,849]),t(ot,[2,850]),t(ot,[2,851]),t(ot,[2,852]),t(ot,[2,853]),t(ot,[2,854]),t(ot,[2,855]),t(ot,[2,856]),t(ot,[2,857]),t(ot,[2,858]),t(ot,[2,859]),t(ot,[2,860]),t(ot,[2,861]),t(ot,[2,862]),t(ot,[2,863]),t(ot,[2,864]),t(ot,[2,865]),t(ot,[2,866]),t(ot,[2,867]),t(ot,[2,868]),t(ot,[2,869]),t(ot,[2,870]),t(ot,[2,871]),t(ot,[2,872]),t(ot,[2,873]),t(ot,[2,874]),t(ot,[2,875]),t(ot,[2,876]),t(ot,[2,877]),t(ot,[2,878]),t(ot,[2,879]),t(ot,[2,880]),t(ot,[2,881]),t(ot,[2,882]),t(ot,[2,883]),t(ot,[2,884]),t(ot,[2,885]),t(ot,[2,886]),t(ot,[2,887]),t(ot,[2,888]),t(ot,[2,889]),t(ot,[2,890]),t(ot,[2,891]),t(ot,[2,892]),t(ot,[2,893]),t(ot,[2,894]),t(ot,[2,895]),t(ot,[2,896]),t(ot,[2,897]),t(ot,[2,898]),t(ot,[2,899]),t(ot,[2,900]),t(ot,[2,901]),t(ot,[2,902]),t(ot,[2,903]),t(ot,[2,904]),t(ot,[2,905]),t(ot,[2,906]),t(ot,[2,907]),t(ot,[2,908]),t(ot,[2,909]),t(ot,[2,910]),t(ot,[2,911]),t(ot,[2,912]),t(ot,[2,913]),t(ot,[2,914]),t(ot,[2,915]),t(ot,[2,916]),t(ot,[2,917]),t(ot,[2,918]),t(ot,[2,919]),t(ot,[2,920]),t(ot,[2,921]),t(ot,[2,922]),t(ot,[2,923]),t(ot,[2,924]),t(ot,[2,925]),t(ot,[2,926]),t(ot,[2,927]),t(ot,[2,928]),t(ot,[2,929]),t(ot,[2,930]),t(ot,[2,931]),t(ot,[2,932]),t(ot,[2,933]),t(ot,[2,934]),t(ot,[2,935]),t(ot,[2,936]),t(ot,[2,937]),t(ot,[2,938]),t(ot,[2,939]),t(ot,[2,940]),t(ot,[2,941]),t(ot,[2,942]),t(ot,[2,943]),t(ot,[2,944]),t(ot,[2,945]),t(ot,[2,946]),t(ot,[2,947]),t(ot,[2,948]),t(ot,[2,949]),t(ot,[2,950]),t(ot,[2,951]),t(ot,[2,952]),t(ot,[2,953]),t(ot,[2,954]),t(ot,[2,955]),t(ot,[2,956]),t(ot,[2,957]),t(ot,[2,958]),t(ot,[2,959]),t(ot,[2,960]),t(ot,[2,961]),t(ot,[2,962]),t(ot,[2,963]),t(ot,[2,964]),t(ot,[2,965]),t(ot,[2,966]),t(ot,[2,967]),t(ot,[2,968]),t(ot,[2,969]),t(ot,[2,970]),t(ot,[2,971]),t(ot,[2,972]),t(ot,[2,973]),t(ot,[2,974]),t(ot,[2,975]),t(ot,[2,976]),t(ot,[2,977]),t(ot,[2,978]),t(ot,[2,979]),t(ot,[2,980]),t(ot,[2,981]),t(ot,[2,982]),t(ot,[2,983]),t(ot,[2,984]),t(ot,[2,985]),t(ot,[2,986]),t(ot,[2,987]),t(ot,[2,988]),t(ot,[2,989]),t(ot,[2,990]),t(ot,[2,991]),t(ot,[2,992]),t(ot,[2,993]),t(ot,[2,994]),t(ot,[2,995]),t(ot,[2,996]),t(ot,[2,997]),t(ot,[2,998]),t(ot,[2,999]),t(ot,[2,1e3]),t(ot,[2,1001]),t(ot,[2,1002]),t(ot,[2,1003]),t(ot,[2,1004]),t(ot,[2,1005]),t(ot,[2,1006]),t(ot,[2,1007]),t(ot,[2,1008]),t(ot,[2,1009]),t(ot,[2,1010]),t(ot,[2,1011]),t(ot,[2,1012]),t(ot,[2,1013]),t(ot,[2,1014]),t(ot,[2,1015]),t(ot,[2,1016]),t(ot,[2,1017]),t(ot,[2,1018]),t(ot,[2,1019]),t(ot,[2,1020]),t(ot,[2,1021]),t(ot,[2,1022]),t(ot,[2,1023]),t(ot,[2,1024]),t(ot,[2,1025]),t(ot,[2,1026]),t(ot,[2,1027]),t(ot,[2,1028]),t(ot,[2,1029]),t(ot,[2,1030]),t(ot,[2,1031]),t(ot,[2,1032]),t(ot,[2,1033]),t(ot,[2,1034]),t(ot,[2,1035]),t(ot,[2,1036]),t(ot,[2,1037]),t(ot,[2,1038]),t(ot,[2,1039]),t(ot,[2,1040]),t(X,[2,7]),t(X,r,{17:5,18:7,19:8,20:9,21:10,22:11,23:12,24:13,25:14,26:15,27:16,28:17,29:18,30:19,31:20,32:21,33:22,34:23,35:24,36:25,37:26,38:27,39:28,40:29,41:30,42:31,43:32,44:33,45:34,46:35,47:36,48:37,49:38,50:39,51:40,52:41,54:43,55:44,56:45,57:46,58:47,59:48,60:49,61:50,62:51,63:52,64:53,65:54,66:55,67:56,68:57,69:58,70:59,71:60,79:75,504:95,184:99,3:100,12:821,2:n,4:a,5:s,53:o,72:u,89:c,124:l,146:h,156:d,189:f,266:p,267:b,290:E,335:g,338:m,339:v,396:S,400:T,401:y,404:N,406:C,408:R,409:O,417:I,418:x,434:D,436:k,437:L,439:$,440:M,441:U,442:_,443:F,447:P,448:q,451:G,452:V,505:B,507:j,508:H,517:J}),{396:[1,825],401:[1,822],402:[1,823],403:[1,824]},{2:n,3:826,4:a,5:s},t(Tn,[2,1111],{289:827,767:829,78:[1,828],164:[1,831],185:[1,830]}),{2:n,3:168,4:a,5:s,56:165,77:ie,94:260,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,151:832,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:260,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,151:833,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:834,4:a,5:s,132:[1,835]},{2:n,3:836,4:a,5:s,132:[1,837]},{2:n,3:838,4:a,5:s,99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},{2:n,3:839,4:a,5:s},{154:[1,840]},t(Nn,ht,{350:841,156:dt}),{230:[1,842]},{2:n,3:843,4:a,5:s},t(K,[2,729],{74:Cn}),{2:n,3:168,4:a,5:s,56:165,77:ie,94:845,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(Sn,[2,732]),t(Rn,[2,1143],{419:190,476:846,144:847,139:On,141:On,145:ze,420:Ge,424:Ve}),{139:[1,848],141:[1,849]},t(wn,In,{490:851,493:852,77:[1,850],137:pt}),t(xn,[2,1167],{494:853,132:[1,854]}),t(Dn,[2,1171],{496:855,497:856,152:bt}),t(Dn,[2,747]),t(kn,[2,739]),{2:n,3:857,4:a,5:s,131:[1,858]},{2:n,3:859,4:a,5:s},{2:n,3:860,4:a,5:s},t(Be,ht,{350:861,156:dt}),t(Be,ht,{350:862,156:dt}),t(St,[2,491]),t(St,[2,492]),{183:[1,863]},{183:[2,1142]},t(Ln,[2,1137],{466:864,469:865,137:[1,866]}),t(Et,[2,1136]),t($n,Mn,{510:867,95:Un,230:[1,868],514:_n,515:Fn,516:Pn}),{76:[1,873]},{76:[1,874]},{145:se,450:875},{4:qn,7:879,76:[1,877],272:876,387:878,389:Gn},t(K,[2,456],{128:[1,882]}),t(K,[2,579]),{2:n,3:883,4:a,5:s},{298:[1,884]},t(Nn,mt,{398:885,156:vt}),t(K,[2,593]),{2:n,3:244,4:a,5:s,199:887,399:886},{2:n,3:244,4:a,5:s,199:887,399:888},t(K,[2,772]),t(X,[2,669],{438:889,310:[1,890]}),{2:n,3:168,4:a,5:s,56:165,77:ie,94:891,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:892,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:893,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:894,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:895,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:896,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:897,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:898,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:899,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:900,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:901,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:902,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:903,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:904,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:905,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:906,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:907,4:a,5:s,77:[1,909],131:oe,156:Q,196:908,200:910,290:z,291:Z,292:ee,293:te},{2:n,3:911,4:a,5:s,77:[1,913],131:oe,156:Q,196:912,200:914,290:z,291:Z,292:ee,293:te},t(Vn,[2,440],{255:146,200:147,256:148,111:149,254:150,196:151,257:152,113:153,258:154,201:155,202:156,259:157,260:158,261:159,144:161,262:162,263:163,56:165,158:167,3:168,419:190,94:915,2:n,4:a,5:s,77:ie,131:oe,132:ue,137:ce,143:le,145:he,149:de,152:fe,154:pe,156:Q,179:be,180:Ee,181:ge,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,420:Ge,424:Ve}),t(Vn,[2,441],{255:146,200:147,256:148,111:149,254:150,196:151,257:152,113:153,258:154,201:155,202:156,259:157,260:158,261:159,144:161,262:162,263:163,56:165,158:167,3:168,419:190,94:916,2:n,4:a,5:s,77:ie,131:oe,132:ue,137:ce,143:le,145:he,149:de,152:fe,154:pe,156:Q,179:be,180:Ee,181:ge,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,420:Ge,424:Ve}),t(Vn,[2,442],{255:146,200:147,256:148,111:149,254:150,196:151,257:152,113:153,258:154,201:155,202:156,259:157,260:158,261:159,144:161,262:162,263:163,56:165,158:167,3:168,419:190,94:917,2:n,4:a,5:s,77:ie,131:oe,132:ue,137:ce,143:le,145:he,149:de,152:fe,154:pe,156:Q,179:be,180:Ee,181:ge,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,420:Ge,424:Ve}),t(Vn,[2,443],{255:146,200:147,256:148,111:149,254:150,196:151,257:152,113:153,258:154,201:155,202:156,259:157,260:158,261:159,144:161,262:162,263:163,56:165,158:167,3:168,419:190,94:918,2:n,4:a,5:s,77:ie,131:oe,132:ue,137:ce,143:le,145:he,149:de,152:fe,154:pe,156:Q,179:be,180:Ee,181:ge,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,420:Ge,424:Ve}),t(Vn,Bn,{255:146,200:147,256:148,111:149,254:150,196:151,257:152,113:153,258:154,201:155,202:156,259:157,260:158,261:159,144:161,262:162,263:163,56:165,158:167,3:168,419:190,94:919,2:n,4:a,5:s,77:ie,131:oe,132:ue,137:ce,143:le,145:he,149:de,152:fe,154:pe,156:Q,179:be,180:Ee,181:ge,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,420:Ge,424:Ve}),{2:n,3:168,4:a,5:s,56:165,77:ie,94:920,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:921,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(Vn,[2,445],{255:146,200:147,256:148,111:149,254:150,196:151,257:152,113:153,258:154,201:155,202:156,259:157,260:158,261:159,144:161,262:162,263:163,56:165,158:167,3:168,419:190,94:922,2:n,4:a,5:s,77:ie,131:oe,132:ue,137:ce,143:le,145:he,149:de,152:fe,154:pe,156:Q,179:be,180:Ee,181:ge,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,420:Ge,424:Ve}),{2:n,3:168,4:a,5:s,56:165,77:ie,94:923,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:924,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{164:[1,926],166:[1,928],328:925,334:[1,927]},{2:n,3:168,4:a,5:s,56:165,77:ie,94:929,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:930,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:695,4:a,5:s,77:[1,931],111:934,145:jn,156:Q,200:935,202:933,290:z,291:Z,292:ee,293:te,329:932},{99:[1,937],297:[1,938]},{2:n,3:168,4:a,5:s,56:165,77:ie,94:939,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:940,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:941,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{4:qn,7:879,272:942,387:878,389:Gn},t(Hn,[2,88]),t(Hn,[2,89]),{78:[1,943]},{78:[1,944]},{78:[1,945]},{78:[1,946],99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},t(Be,je,{340:207,77:lt,198:He}),{78:[2,1107]},{78:[2,1108]},{134:ne,135:ae},{2:n,3:168,4:a,5:s,56:165,77:ie,94:260,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,151:947,152:fe,154:pe,156:Q,158:167,164:[1,949],179:be,180:Ee,181:ge,185:[1,948],196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,
265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:950,4:a,5:s,149:Jn,180:[1,952]},t([2,4,5,10,53,72,74,76,77,78,89,93,95,98,99,107,118,122,128,129,130,131,132,134,135,137,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,181,183,185,187,189,198,206,208,222,223,224,225,226,227,228,229,232,239,242,243,245,247,266,267,280,281,282,283,284,285,286,287,288,290,296,300,306,308,309,310,314,330,331,333,335,338,339,396,400,401,404,406,408,409,417,418,420,424,434,436,437,439,440,441,442,443,447,448,451,452,464,470,505,507,508,517,602,764],[2,416],{114:625,327:637,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,332:rr}),t(Yn,[2,417],{114:625,327:637,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,180:Ft,312:qt,316:Bt}),t(Yn,[2,418],{114:625,327:637,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,180:Ft,312:qt,316:Bt}),t(Wn,[2,419],{114:625,327:637,316:Bt}),t(Wn,[2,420],{114:625,327:637,316:Bt}),t(sr,[2,365]),t(sr,[2,1113]),t(sr,[2,1114]),t(sr,[2,366]),t([2,4,5,10,53,72,74,76,77,78,89,93,95,98,99,107,112,115,116,118,122,123,124,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,179,180,181,183,185,187,189,198,206,208,222,223,224,225,226,227,228,229,230,231,232,239,242,243,245,247,266,267,280,281,282,283,284,285,286,287,288,290,296,300,306,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,330,331,332,333,335,338,339,396,400,401,404,406,408,409,417,418,420,424,434,436,437,439,440,441,442,443,447,448,451,452,464,470,505,507,508,517,602,764],[2,362]),{2:n,3:168,4:a,5:s,56:165,77:ie,94:953,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(cr,[2,620]),t(cr,[2,621]),t(cr,[2,622]),t(cr,[2,623]),t(cr,[2,625]),{40:954,79:75,89:c,184:99,189:f},{99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,304:955,307:677,308:ur,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},{305:956,306:Xn,307:957,308:ur,310:Kn},t(Qn,[2,372]),{2:n,3:168,4:a,5:s,56:165,77:ie,94:959,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:960,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{4:qn,7:879,272:961,387:878,389:Gn},t(cr,[2,626]),{74:[1,963],300:[1,962]},t(cr,[2,642]),t(zn,[2,649]),t(Zn,[2,627]),t(Zn,[2,628]),t(Zn,[2,629]),t(Zn,[2,630]),t(Zn,[2,631]),t(Zn,[2,632]),t(Zn,[2,633]),t(Zn,[2,634]),t(Zn,[2,635]),{2:n,3:168,4:a,5:s,56:165,77:ie,94:964,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t([2,4,5,10,53,72,74,76,78,89,93,95,98,99,107,112,115,118,122,123,124,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,179,180,181,183,185,187,189,198,206,208,222,223,224,225,226,227,228,229,232,239,242,243,245,247,266,267,280,281,282,283,284,285,286,287,288,290,296,300,306,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,330,331,332,333,335,338,339,396,400,401,404,406,408,409,417,418,420,424,426,434,436,437,439,440,441,442,443,447,448,451,452,464,470,505,507,508,517,602,764],ar,{77:ct,116:ea}),{74:pn,300:[1,966]},t(ta,[2,314],{77:ct}),t(Qe,[2,315]),{74:[1,968],426:[1,967]},t(cr,[2,639]),t(ra,[2,644]),{152:[1,969]},{152:[1,970]},{152:[1,971]},{40:976,77:[1,975],79:75,89:c,143:le,144:979,145:ze,149:na,152:fe,181:ge,184:99,189:f,201:980,302:_e,341:972,342:973,343:[1,974],344:aa,419:190,420:Ge,424:Ve},t(Be,je,{340:981,198:He}),{77:sa,143:le,144:979,145:ze,149:na,152:fe,181:ge,201:980,302:_e,341:982,342:983,344:aa,419:190,420:Ge,424:Ve},{230:[1,986],455:985},{2:n,3:219,4:a,5:s,77:Je,132:Ye,143:le,144:212,145:he,152:fe,156:Q,181:ge,199:213,200:215,201:214,202:217,209:987,213:We,214:218,290:z,291:Z,292:ee,293:te,302:_e,419:190,420:Ge,424:Ve},{231:[2,696]},{78:[1,988]},t(pr,[2,1093],{211:989,3:990,2:n,4:a,5:s}),t(fr,[2,1092]),t(pr,[2,183]),{2:n,3:991,4:a,5:s},{212:[1,992]},t(pr,[2,187]),{2:n,3:993,4:a,5:s},t(pr,[2,191]),{2:n,3:994,4:a,5:s},t(pr,[2,195]),{2:n,3:995,4:a,5:s},t(pr,[2,198]),{2:n,3:996,4:a,5:s},{2:n,3:997,4:a,5:s},{148:[1,998]},t(ia,[2,172],{82:999,183:[1,1e3]}),{2:n,3:219,4:a,5:s,132:[1,1005],143:le,145:[1,1006],152:fe,156:Q,181:ge,199:1001,200:1002,201:1003,202:1004,290:z,291:Z,292:ee,293:te,302:_e},{2:n,3:1011,4:a,5:s,109:1007,110:1008,111:1009,112:oa},t(Sr,[2,1058]),t(ua,[2,1049],{91:1012,182:1013,183:[1,1014]}),t(Ke,[2,1048],{153:1015,179:ca,180:la,181:ha}),t([2,4,5,10,72,74,76,78,112,115,116,118,122,123,124,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,179,180,181,183,185,187,198,280,281,282,283,284,285,286,287,288,306,310,420,424,602,764],[2,90],{77:[1,1019]}),{119:[1,1020]},t(da,[2,93]),{2:n,3:1021,4:a,5:s},t(da,[2,95]),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1022,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1023,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:733,4:a,5:s,72:Tr,76:Ar,77:yr,112:Nr,114:736,115:yt,116:Nt,117:1025,118:Cr,122:Rr,123:Or,124:wr,125:1024,128:Ir,129:xr,130:Dr,131:kr,132:Lr,133:$r,134:Mr,135:Ur,136:_r,137:Fr,138:Pr,139:qr,140:Gr,141:Vr,142:Br,143:jr,144:758,145:Hr,146:Jr,148:Yr,149:Wr,150:Xr,152:Kr,154:Qr,156:zr,158:768,160:769,162:Zr,164:en,166:tn,168:rn,169:nn,170:an,171:sn,172:on,173:un,175:cn,185:ln,187:hn,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,419:190,420:Ge,424:Ve},{77:[1,1026]},{77:[1,1027]},{77:[1,1028]},{77:[1,1029]},t(da,[2,104]),t(da,[2,105]),t(da,[2,106]),t(da,[2,107]),t(da,[2,108]),t(da,[2,109]),{2:n,3:1030,4:a,5:s},{2:n,3:1031,4:a,5:s,133:[1,1032]},t(da,[2,113]),t(da,[2,114]),t(da,[2,115]),t(da,[2,116]),t(da,[2,117]),t(da,[2,118]),{2:n,3:1033,4:a,5:s,77:or,113:669,131:oe,132:ue,143:le,152:fe,181:ge,196:670,201:672,257:671,294:De,295:ke,296:Le,302:_e,419:673,424:Ve},{145:[1,1034]},{77:[1,1035]},{145:[1,1036]},t(da,[2,123]),{77:[1,1037]},{2:n,3:1038,4:a,5:s},{77:[1,1039]},{77:[1,1040]},{77:[1,1041]},{77:[1,1042]},{77:[1,1043],164:[1,1044]},{77:[1,1045]},{77:[1,1046]},{77:[1,1047]},{77:[1,1048]},{77:[1,1049]},{77:[1,1050]},{77:[1,1051]},{77:[1,1052]},{77:[1,1053]},{77:[2,1073]},{77:[2,1074]},{2:n,3:244,4:a,5:s,199:1054},{2:n,3:244,4:a,5:s,199:1055},{113:1056,132:ue,296:Le},t(K,[2,596],{112:[1,1057]}),{2:n,3:244,4:a,5:s,199:1058},{113:1059,132:ue,296:Le},{2:n,3:1060,4:a,5:s},t(K,[2,693]),t(K,[2,68]),{2:n,3:236,4:a,5:s,75:1061},{77:[1,1062]},t(K,[2,674]),t(K,[2,586]),{2:n,3:1011,4:a,5:s,111:1065,143:fa,145:pa,147:1063,336:1064,337:1066},{144:1069,145:ze,419:190,420:Ge,424:Ve},t(K,[2,671]),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1070,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(Vn,Bn,{255:146,200:147,256:148,111:149,254:150,196:151,257:152,113:153,258:154,201:155,202:156,259:157,260:158,261:159,144:161,262:162,263:163,56:165,158:167,3:168,419:190,94:1071,2:n,4:a,5:s,77:ie,131:oe,132:ue,137:ce,143:le,145:he,149:de,152:fe,154:pe,156:Q,179:be,180:Ee,181:ge,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,420:Ge,424:Ve}),{113:1072,132:ue,296:Le},{2:n,3:266,4:a,5:s,446:1073,447:et},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1075,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,230:gn,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve,429:1074,433:mn},t(K,[2,651]),{114:1077,115:yt,116:Nt,124:[1,1076]},t(K,[2,663]),t(K,[2,664]),{2:n,3:1079,4:a,5:s,77:ba,131:Ea,432:1078},{114:807,115:yt,116:Nt,124:[1,1082],430:1083},t(K,[2,753],{74:vn}),{2:n,3:100,4:a,5:s,504:1084},{2:n,3:168,4:a,5:s,56:165,77:ie,94:817,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,174:1085,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,253:816,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:817,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,174:1086,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,253:816,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:817,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,174:1087,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,253:816,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(An,[2,151]),t(An,[2,1088],{74:ga}),t(ma,[2,273]),t(ma,[2,280],{114:625,327:637,3:1090,113:1092,2:n,4:a,5:s,76:[1,1089],99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,131:[1,1091],132:ue,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,296:Le,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),t(nt,[2,1089],{197:1093,765:[1,1094]}),{131:oe,196:1095},{74:vn,78:[1,1096]},t(X,[2,11]),{148:[1,1097],190:[1,1098]},{190:[1,1099]},{190:[1,1100]},{190:[1,1101]},t(K,[2,575],{76:[1,1103],77:[1,1102]}),{2:n,3:168,4:a,5:s,56:165,77:ie,94:260,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,151:1104,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(sr,[2,346]),t(Tn,[2,1112]),t(Tn,[2,1109]),t(Tn,[2,1110]),{74:pn,78:[1,1105]},{74:pn,78:[1,1106]},{74:[1,1107]},{74:[1,1108]},{74:[1,1109]},{74:[1,1110]},t(sr,[2,353]),t(K,[2,580]),{298:[1,1111]},{2:n,3:1112,4:a,5:s,113:1113,132:ue,296:Le},{2:n,3:244,4:a,5:s,199:1114},{230:[1,1115]},{2:n,3:578,4:a,5:s,132:ft,137:pt,143:tt,145:rt,152:bt,431:585,474:1116,475:576,478:577,482:582,493:579,497:581},t(K,[2,730],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),t(Sn,[2,1145],{477:1117,483:1118,76:va}),t(Rn,[2,1144]),{2:n,3:1122,4:a,5:s,132:ft,137:pt,144:1121,145:ze,152:bt,419:190,420:Ge,424:Ve,475:1120,493:579,497:581},{2:n,3:1122,4:a,5:s,132:ft,137:pt,143:tt,145:rt,152:bt,431:585,475:1124,478:1123,482:582,493:579,497:581},{2:n,3:578,4:a,5:s,132:ft,137:pt,143:tt,145:rt,152:bt,431:585,473:1125,474:575,475:576,478:577,482:582,493:579,497:581},t(xn,[2,1163],{491:1126,132:[1,1127]}),t(wn,[2,1162]),t(Dn,[2,1169],{495:1128,497:1129,152:bt}),t(xn,[2,1168]),t(Dn,[2,746]),t(Dn,[2,1172]),t(wn,[2,749]),t(wn,[2,750]),t(Dn,[2,748]),t(kn,[2,740]),{2:n,3:244,4:a,5:s,199:1130},{2:n,3:244,4:a,5:s,199:1131},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1132,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(Sa,[2,1139],{467:1133,113:1134,132:ue,296:Le}),t(Ln,[2,1138]),{2:n,3:1135,4:a,5:s},{335:Ta,338:Aa,339:ya,511:1136},{2:n,3:244,4:a,5:s,199:1140},t($n,[2,765]),t($n,[2,766]),t($n,[2,767]),{129:[1,1141]},{266:[1,1142]},{266:[1,1143]},t(gt,[2,688]),t(gt,[2,689],{124:[1,1144]}),{4:qn,7:879,272:1145,387:878,389:Gn},t([2,4,10,53,72,74,76,77,78,89,93,95,98,99,107,112,115,116,118,122,123,124,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,179,180,181,183,185,187,189,198,206,208,222,223,224,225,226,227,228,229,230,232,239,242,243,245,247,266,267,280,281,282,283,284,285,286,287,288,290,296,297,300,306,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,330,331,332,333,335,338,339,343,356,368,369,373,374,396,400,401,404,406,408,409,417,418,420,424,434,436,437,439,440,441,442,443,447,448,451,452,464,470,505,507,508,517,602,764],[2,542],{5:[1,1146]}),t([2,5,10,53,72,74,76,78,89,93,95,98,99,107,112,115,116,118,122,123,124,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,179,180,181,183,185,187,189,198,206,208,222,223,224,225,226,227,228,229,230,232,239,242,243,245,247,266,267,280,281,282,283,284,285,286,287,288,290,296,297,300,306,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,330,331,332,333,335,338,339,343,356,368,369,373,374,396,400,401,404,406,408,409,417,418,420,424,434,436,437,439,440,441,442,443,447,448,451,452,464,470,505,507,508,517,602,764],[2,539],{4:[1,1148],77:[1,1147]}),{77:[1,1149]},t(Na,[2,4]),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1150,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(K,[2,588]),t(Nn,[2,568]),{2:n,3:1151,4:a,5:s,113:1152,132:ue,296:Le},t(K,[2,564],{74:Ca}),t(gt,[2,566]),t(K,[2,613],{74:Ca}),t(K,[2,668]),t(K,r,{17:5,18:7,19:8,20:9,21:10,22:11,23:12,24:13,25:14,26:15,27:16,28:17,29:18,30:19,31:20,32:21,33:22,34:23,35:24,36:25,37:26,38:27,39:28,40:29,41:30,42:31,43:32,44:33,45:34,46:35,47:36,48:37,49:38,50:39,51:40,52:41,54:43,55:44,56:45,57:46,58:47,59:48,60:49,61:50,62:51,63:52,64:53,65:54,66:55,67:56,68:57,69:58,70:59,71:60,79:75,504:95,184:99,3:100,12:1154,2:n,4:a,5:s,53:o,72:u,89:c,124:l,146:h,156:d,189:f,266:p,267:b,290:E,335:g,338:m,339:v,396:S,400:T,401:y,404:N,406:C,408:R,409:O,417:I,418:x,434:D,436:k,437:L,439:$,440:M,441:U,442:_,443:F,447:P,448:q,451:G,452:V,505:B,507:j,508:H,517:J}),t(Ra,[2,376],{114:625,327:637,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,141:kt,142:Lt,179:_t,180:Ft,312:qt,316:Bt,317:jt,318:Ht,319:Jt}),t(Wn,[2,377],{114:625,327:637,316:Bt}),t(Ra,[2,378],{114:625,327:637,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,141:kt,142:Lt,179:_t,180:Ft,312:qt,316:Bt,317:jt,318:Ht,319:Jt}),t(Oa,[2,379],{114:625,327:637,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,141:kt,142:Lt,179:_t,180:Ft,312:qt,314:[1,1155],316:Bt,317:jt,318:Ht,319:Jt}),t(Oa,[2,381],{114:625,327:637,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,141:kt,142:Lt,179:_t,180:Ft,312:qt,314:[1,1156],316:Bt,317:jt,318:Ht,319:Jt}),t(Qe,[2,383],{114:625,327:637}),t(Yn,[2,384],{114:625,327:637,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,180:Ft,312:qt,316:Bt}),t(Yn,[2,385],{114:625,327:637,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,180:Ft,312:qt,316:Bt}),t(wa,[2,386],{114:625,327:637,115:yt,116:Nt,123:Ct,136:wt,312:qt,316:Bt}),t(wa,[2,387],{114:625,327:637,115:yt,116:Nt,123:Ct,136:wt,312:qt,316:Bt}),t(wa,[2,388],{114:625,327:637,115:yt,116:Nt,123:Ct,136:wt,312:qt,316:Bt}),t([2,4,5,10,53,72,74,76,77,78,89,93,95,98,99,107,112,118,122,123,124,128,129,130,131,132,133,134,135,137,138,139,140,141,142,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,179,180,181,183,185,187,189,198,206,208,222,223,224,225,226,227,228,229,232,239,242,243,245,247,266,267,280,281,282,283,284,285,286,287,288,290,296,300,306,308,309,310,311,313,314,315,317,318,319,320,321,322,323,324,325,326,330,331,332,333,335,338,339,396,400,401,404,406,408,409,417,418,420,424,434,436,437,439,440,441,442,443,447,448,451,452,464,470,505,507,508,517,602,764],[2,389],{114:625,327:637,115:yt,116:Nt,136:wt,312:qt,316:Bt}),t(Ia,[2,390],{114:625,327:637,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,179:_t,180:Ft,312:qt,316:Bt,317:jt}),t(Ia,[2,391],{114:625,327:637,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,179:_t,180:Ft,312:qt,316:Bt,317:jt}),t(Ia,[2,392],{114:625,327:637,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,179:_t,180:Ft,312:qt,316:Bt,317:jt}),t(Ia,[2,393],{114:625,327:637,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,179:_t,180:Ft,312:qt,316:Bt,317:jt}),t(ta,[2,394],{77:ct}),t(Qe,[2,395]),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1157,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(Qe,[2,397]),t(ta,[2,398],{77:ct}),t(Qe,[2,399]),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1158,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(Qe,[2,401]),t(xa,[2,402],{114:625,327:637,112:At,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,141:kt,142:Lt,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,332:rr}),t(xa,[2,403],{114:625,327:637,112:At,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,141:kt,142:Lt,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,332:rr}),t(xa,[2,404],{114:625,327:637,112:At,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,141:kt,142:Lt,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,332:rr}),t(xa,[2,405],{114:625,327:637,112:At,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,141:kt,142:Lt,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,332:rr}),t([2,4,5,10,53,72,89,99,124,139,140,146,154,156,170,171,189,266,267,290,306,310,320,321,322,323,324,325,326,330,331,333,335,338,339,396,400,401,404,406,408,409,417,418,434,436,437,439,440,441,442,443,447,448,451,452,505,507,508,517,602,764],Da,{114:625,327:637,112:At,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,141:kt,142:Lt,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,332:rr}),t(xa,[2,407],{114:625,327:637,112:At,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,141:kt,142:Lt,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,332:rr}),t(xa,[2,408],{114:625,327:637,112:At,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,141:kt,142:Lt,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,332:rr}),t(xa,[2,409],{114:625,327:637,112:At,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,141:kt,142:Lt,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,332:rr}),t(xa,[2,410],{114:625,327:637,112:At,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,141:kt,142:Lt,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,332:rr}),t(xa,[2,411],{114:625,327:637,112:At,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,141:kt,142:Lt,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,332:rr}),{77:[1,1159]},{77:[2,446]},{77:[2,447]},{77:[2,448]},t(ka,[2,414],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,332:rr}),t([2,4,5,10,53,72,74,76,77,78,89,93,95,98,107,118,122,128,129,130,131,132,134,135,137,143,145,146,148,149,150,152,156,162,164,166,168,169,171,172,173,175,181,183,185,187,189,198,206,208,222,223,224,225,226,227,228,229,232,239,242,243,245,247,266,267,280,281,282,283,284,285,286,287,288,290,296,300,306,308,309,310,314,333,335,338,339,396,400,401,404,406,408,409,417,418,420,424,434,436,437,439,440,441,442,443,447,448,451,452,464,470,505,507,508,517,602,764],[2,415],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr}),{2:n,3:168,4:a,5:s,40:1160,56:165,77:ie,78:[1,1162],79:75,89:c,94:260,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,151:1161,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,184:99,189:f,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(Qe,[2,428]),t(Qe,[2,430]),t(Qe,[2,437]),t(Qe,[2,438]),{2:n,3:667,4:a,5:s,77:[1,1163]},{2:n,3:695,4:a,5:s,77:[1,1164],111:934,145:jn,156:Q,200:935,202:1166,290:z,291:Z,292:ee,293:te,329:1165},t(Qe,[2,435]),t(ka,[2,432],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,332:rr}),t(ka,[2,433],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,332:rr}),t([2,4,5,10,53,72,74,76,77,78,89,93,95,98,99,107,118,122,124,128,129,130,131,132,134,135,137,139,140,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,181,183,185,187,189,198,206,208,222,223,224,225,226,227,228,229,232,239,242,243,245,247,266,267,280,281,282,283,284,285,286,287,288,290,296,300,306,308,309,310,314,320,321,322,323,324,325,326,330,331,332,333,335,338,339,396,400,401,404,406,408,409,417,418,420,424,434,436,437,439,440,441,442,443,447,448,451,452,464,470,505,507,508,517,602,764],[2,434],{114:625,327:637,112:At,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,141:kt,142:Lt,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt}),t(Qe,[2,436]),t(Qe,[2,306]),t(Qe,[2,307]),t(Qe,[2,308]),t(Qe,[2,421]),{74:pn,78:[1,1167]},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1168,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1169,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(Qe,La),t($a,[2,286]),t(Qe,[2,282]),{78:[1,1171],99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},{78:[1,1172]},{305:1173,306:Xn,307:957,308:ur,310:Kn},{306:[1,1174]},t(Qn,[2,371]),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1175,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,309:[1,1176],311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},{76:[1,1177],99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},{74:[1,1178]},t(cr,[2,640]),{2:n,3:695,4:a,5:s,77:lr,111:690,113:688,131:oe,132:ue,143:le,144:685,145:ze,152:fe,156:Q,181:ge,196:687,200:693,201:692,257:689,258:691,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,300:[1,1179],302:_e,419:190,420:Ge,422:1180,423:686,424:Ve},{78:[1,1181],99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},{2:n,3:1182,4:a,5:s,149:Jn},t(Qe,[2,364]),t(cr,[2,637]),{2:n,3:704,4:a,5:s,131:hr,132:dr,426:[1,1183],428:1184},{2:n,3:695,4:a,5:s,77:lr,111:690,113:688,131:oe,132:ue,143:le,144:685,145:ze,152:fe,156:Q,181:ge,196:687,200:693,201:692,257:689,258:691,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,302:_e,419:190,420:Ge,422:1185,423:686,424:Ve},{2:n,3:695,4:a,5:s,77:lr,111:690,113:688,131:oe,132:ue,143:le,144:685,145:ze,152:fe,156:Q,181:ge,196:687,200:693,201:692,257:689,258:691,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,302:_e,419:190,420:Ge,422:1186,423:686,424:Ve},{2:n,3:695,4:a,5:s,77:lr,111:690,113:688,131:oe,132:ue,143:le,144:685,145:ze,152:fe,156:Q,181:ge,196:687,200:693,201:692,257:689,258:691,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,302:_e,419:190,420:Ge,422:1187,423:686,424:Ve},{77:sa,143:le,144:979,145:ze,152:fe,181:ge,201:980,302:_e,342:1188,419:190,420:Ge,424:Ve},t(Ma,[2,458],{74:Ua}),{149:na,341:1190,344:aa},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1194,100:1191,111:1193,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,345:1192,419:190,420:Ge,424:Ve},t(Ma,[2,466]),t(_a,[2,469]),t(_a,[2,470]),t(Fa,[2,474]),t(Fa,[2,475]),{2:n,3:244,4:a,5:s,199:1195},{77:sa,143:le,144:979,145:ze,152:fe,181:ge,201:980,302:_e,342:1196,419:190,420:Ge,424:Ve},t(Ma,[2,462],{74:Ua}),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1194,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,345:1192,419:190,420:Ge,424:Ve},{308:Pa,456:1197,458:1198,459:1199},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1201,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,
196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{230:[2,697]},t(pr,[2,181],{3:1202,2:n,4:a,5:s,76:[1,1203]}),t(pr,[2,182]),t(pr,[2,1094]),t(pr,[2,184]),t(pr,[2,186]),t(pr,[2,188]),t(pr,[2,192]),t(pr,[2,196]),t(pr,[2,199]),t([2,4,5,10,53,72,74,76,77,78,89,93,95,98,118,124,128,143,145,146,148,149,152,154,156,162,168,169,181,183,187,189,206,208,222,223,224,225,226,227,228,229,230,231,232,245,247,266,267,290,297,302,306,310,335,338,339,343,344,356,368,369,373,374,396,400,401,402,403,404,406,408,409,417,418,420,424,434,436,437,439,440,441,442,443,447,448,451,452,505,507,508,514,515,516,517,602,764],[2,201]),{2:n,3:1204,4:a,5:s},t(qa,[2,1045],{83:1205,92:1206,93:[1,1207],98:[1,1208]}),{2:n,3:219,4:a,5:s,77:[1,1210],132:Ye,143:le,144:212,145:he,152:fe,156:Q,181:ge,199:213,200:215,201:214,202:217,203:1209,209:1211,213:We,214:218,290:z,291:Z,292:ee,293:te,302:_e,419:190,420:Ge,424:Ve},t(gr,[2,164]),t(gr,[2,165]),t(gr,[2,166]),t(gr,[2,167]),t(gr,[2,168]),{2:n,3:667,4:a,5:s},t(Xe,[2,83],{74:[1,1212]}),t(Ga,[2,85]),t(Ga,[2,86]),{113:1213,132:ue,296:Le},t([10,72,74,78,93,98,118,124,128,162,168,169,183,198,206,208,222,223,224,225,226,227,228,229,232,245,247,306,310,602,764],ar,{116:ea}),t(ua,[2,73]),t(ua,[2,1050]),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1214,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(da,[2,126]),t(da,[2,144]),t(da,[2,145]),t(da,[2,146]),{2:n,3:168,4:a,5:s,56:165,77:ie,78:[2,1065],94:260,111:149,113:153,127:1215,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,151:1216,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{77:[1,1217]},t(da,[2,94]),t([2,4,5,10,72,74,76,77,78,118,122,124,128,129,130,131,132,134,135,137,139,140,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,181,183,185,187,198,280,281,282,283,284,285,286,287,288,306,310,420,424,602,764],[2,96],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,141:kt,142:Lt,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),t([2,4,5,10,72,74,76,77,78,112,118,122,124,128,129,130,131,132,134,135,137,139,140,143,145,146,148,149,150,152,154,156,162,164,166,168,169,170,171,172,173,175,181,183,185,187,198,280,281,282,283,284,285,286,287,288,306,310,420,424,602,764],[2,97],{114:625,327:637,99:Tt,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,141:kt,142:Lt,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),{2:n,3:733,4:a,5:s,72:Tr,76:Ar,77:yr,78:[1,1218],112:Nr,114:736,115:yt,116:Nt,117:1219,118:Cr,122:Rr,123:Or,124:wr,128:Ir,129:xr,130:Dr,131:kr,132:Lr,133:$r,134:Mr,135:Ur,136:_r,137:Fr,138:Pr,139:qr,140:Gr,141:Vr,142:Br,143:jr,144:758,145:Hr,146:Jr,148:Yr,149:Wr,150:Xr,152:Kr,154:Qr,156:zr,158:768,160:769,162:Zr,164:en,166:tn,168:rn,169:nn,170:an,171:sn,172:on,173:un,175:cn,185:ln,187:hn,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,419:190,420:Ge,424:Ve},t(Va,[2,1061],{153:1015,179:ca,180:la,181:ha}),{2:n,3:733,4:a,5:s,72:Tr,76:Ar,77:yr,112:Nr,114:736,115:yt,116:Nt,117:1221,118:Cr,122:Rr,123:Or,124:wr,126:1220,128:Ir,129:xr,130:Dr,131:kr,132:Lr,133:$r,134:Mr,135:Ur,136:_r,137:Fr,138:Pr,139:qr,140:Gr,141:Vr,142:Br,143:jr,144:758,145:Hr,146:Jr,148:Yr,149:Wr,150:Xr,152:Kr,154:Qr,156:zr,158:768,160:769,162:Zr,164:en,166:tn,168:rn,169:nn,170:an,171:sn,172:on,173:un,175:cn,185:ln,187:hn,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1222,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1223,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:1224,4:a,5:s},t(da,[2,110]),t(da,[2,111]),t(da,[2,112]),t(da,[2,119]),{2:n,3:1225,4:a,5:s},{2:n,3:1011,4:a,5:s,111:1065,143:fa,145:pa,147:1226,336:1064,337:1066},{2:n,3:1227,4:a,5:s},{2:n,3:168,4:a,5:s,56:165,77:ie,94:260,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,151:1228,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(da,[2,125]),t(Va,[2,1067],{155:1229}),t(Va,[2,1069],{157:1230}),t(Va,[2,1071],{159:1231}),t(Va,[2,1075],{161:1232}),t(Ba,ja,{163:1233,178:1234}),{77:[1,1235]},t(Va,[2,1077],{165:1236}),t(Va,[2,1079],{167:1237}),t(Ba,ja,{178:1234,163:1238}),t(Ba,ja,{178:1234,163:1239}),t(Ba,ja,{178:1234,163:1240}),t(Ba,ja,{178:1234,163:1241}),{2:n,3:733,4:a,5:s,72:Tr,76:Ar,77:yr,112:Nr,114:736,115:yt,116:Nt,117:1242,118:Cr,122:Rr,123:Or,124:wr,128:Ir,129:xr,130:Dr,131:kr,132:Lr,133:$r,134:Mr,135:Ur,136:_r,137:Fr,138:Pr,139:qr,140:Gr,141:Vr,142:Br,143:jr,144:758,145:Hr,146:Jr,148:Yr,149:Wr,150:Xr,152:Kr,154:Qr,156:zr,158:768,160:769,162:Zr,164:en,166:tn,168:rn,169:nn,170:an,171:sn,172:on,173:un,175:cn,185:ln,187:hn,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:817,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,174:1243,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,253:816,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(Ha,[2,1081],{176:1244}),t(K,[2,606],{183:[1,1245]}),t(K,[2,602],{183:[1,1246]}),t(K,[2,595]),{113:1247,132:ue,296:Le},t(K,[2,604],{183:[1,1248]}),t(K,[2,599]),t(K,[2,600],{112:[1,1249]}),t(dn,[2,69]),{40:1250,79:75,89:c,184:99,189:f},t(K,[2,450],{74:Ja,128:[1,1251]}),t(Ya,[2,451]),{124:[1,1253]},{2:n,3:1254,4:a,5:s},t(Be,[2,1115]),t(Be,[2,1116]),t(K,[2,618]),t(bn,[2,355],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),t(xa,Da,{114:625,327:637,112:At,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,141:kt,142:Lt,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,332:rr}),t(gt,[2,682]),t(gt,[2,684]),t(K,[2,650]),t(K,[2,652],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1255,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:1079,4:a,5:s,77:ba,131:Ea,432:1256},t(Wa,[2,659]),t(Wa,[2,660]),t(Wa,[2,661]),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1257,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1258,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{114:1077,115:yt,116:Nt,124:[1,1259]},t(Sn,[2,755]),t(An,[2,148],{74:ga}),t(An,[2,149],{74:ga}),t(An,[2,150],{74:ga}),{2:n,3:168,4:a,5:s,56:165,77:ie,94:817,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,253:1260,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:1261,4:a,5:s,113:1263,131:[1,1262],132:ue,296:Le},t(ma,[2,275]),t(ma,[2,277]),t(ma,[2,279]),t(nt,[2,160]),t(nt,[2,1090]),{78:[1,1264]},t(it,[2,758]),{2:n,3:1265,4:a,5:s},{2:n,3:1266,4:a,5:s},{2:n,3:1268,4:a,5:s,384:1267},{2:n,3:1268,4:a,5:s,384:1269},{2:n,3:1270,4:a,5:s},{2:n,3:168,4:a,5:s,56:165,77:ie,94:260,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,151:1271,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:1272,4:a,5:s},{74:pn,78:[1,1273]},t(sr,[2,347]),t(sr,[2,348]),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1274,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1275,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1276,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1277,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(Nn,[2,504]),t(K,Xa,{407:1278,76:Ka,77:[1,1279]}),t(K,Xa,{407:1281,76:Ka}),{77:[1,1282]},{2:n,3:244,4:a,5:s,199:1283},t(Sn,[2,731]),t(Sn,[2,733]),t(Sn,[2,1146]),{143:tt,145:rt,431:1284},t(Qa,[2,1147],{419:190,479:1285,144:1286,145:ze,420:Ge,424:Ve}),{76:va,139:[2,1151],481:1287,483:1288},t([10,74,76,78,132,139,145,152,306,310,420,424,602,764],In,{490:851,493:852,137:pt}),t(Sn,[2,736]),t(Sn,On),{74:Cn,78:[1,1289]},t(Dn,[2,1165],{492:1290,497:1291,152:bt}),t(xn,[2,1164]),t(Dn,[2,745]),t(Dn,[2,1170]),t(K,[2,490],{77:[1,1292]}),{76:[1,1294],77:[1,1293]},{99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,148:[1,1295],154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},t(Ma,za,{79:75,184:99,468:1296,40:1299,89:c,146:Za,189:f,470:es}),t(Sa,[2,1140]),t(Ln,[2,723]),{230:[1,1300]},t(ts,[2,769]),t(ts,[2,770]),t(ts,[2,771]),t($n,Mn,{510:1301,95:Un,514:_n,515:Fn,516:Pn}),t($n,[2,768]),t(K,[2,312]),t(K,[2,313]),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1302,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(gt,[2,690],{124:[1,1303]}),t(Na,[2,541]),{131:[1,1305],388:1304,390:[1,1306]},t(Na,[2,5]),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1194,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,345:1307,419:190,420:Ge,424:Ve},t(K,[2,455],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),t(K,[2,589]),t(K,[2,590]),{2:n,3:244,4:a,5:s,199:1308},t(K,[2,670]),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1309,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1310,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{78:[1,1311],99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},{78:[1,1312],99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},{2:n,3:168,4:a,5:s,40:1313,56:165,77:ie,79:75,89:c,94:260,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,151:1314,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,184:99,189:f,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{78:[1,1315]},{74:pn,78:[1,1316]},t(Qe,[2,426]),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1317,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,40:1318,56:165,77:ie,78:[1,1320],79:75,89:c,94:260,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,151:1319,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,184:99,189:f,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(Qe,[2,429]),t(Qe,[2,431]),t(Qe,rs,{275:1321,276:ns}),{78:[1,1323],99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},{78:[1,1324],99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},{2:n,3:1325,4:a,5:s,180:[1,1326]},t(cr,[2,619]),t(Qe,[2,363]),{306:[1,1327]},t(Qe,[2,370]),{99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,306:[2,374],311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1328,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{4:qn,7:879,272:1329,387:878,389:Gn},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1330,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(cr,[2,641]),t(zn,[2,648]),t(Zn,[2,636]),t($a,La),t(cr,[2,638]),t(ra,[2,643]),t(ra,[2,645]),t(ra,[2,646]),t(ra,[2,647]),t(Ma,[2,457],{74:Ua}),{77:[1,1332],143:le,144:1333,145:ze,152:fe,181:ge,201:1334,302:_e,419:190,420:Ge,424:Ve},t(Ma,[2,463]),{74:as,78:[1,1335]},{74:ss,78:[1,1337]},t([74,78,99,112,115,116,123,124,133,136,138,139,140,141,142,154,170,171,179,180,311,312,313,315,316,317,318,319,320,321,322,323,324,325,326,330,331,332,333],is),t(os,[2,479],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),{40:1341,77:sa,79:75,89:c,143:le,144:979,145:ze,149:na,152:fe,181:ge,184:99,189:f,201:980,302:_e,341:1339,342:1340,344:aa,419:190,420:Ge,424:Ve},t(Ma,[2,461],{74:Ua}),t(K,[2,717],{457:1342,458:1343,459:1344,308:Pa,464:[1,1345]}),t(us,[2,701]),t(us,[2,702]),{154:[1,1347],460:[1,1346]},{99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,308:[2,698],311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},t(pr,[2,179]),{2:n,3:1348,4:a,5:s},t(K,[2,574]),t(cs,[2,238],{84:1349,128:[1,1350]}),t(qa,[2,1046]),{77:[1,1351]},{77:[1,1352]},t(ia,[2,169],{204:1353,215:1355,205:1356,216:1357,221:1360,74:ls,206:hs,208:ds,222:fs,223:ps,224:bs,225:Es,226:gs,227:ms,228:vs,229:Ss}),{2:n,3:219,4:a,5:s,40:711,77:Je,79:75,89:c,132:Ye,143:le,144:212,145:he,152:fe,156:Q,181:ge,184:99,189:f,199:213,200:215,201:214,202:217,203:1369,209:1211,213:We,214:218,290:z,291:Z,292:ee,293:te,302:_e,419:190,420:Ge,424:Ve},t(Ts,[2,177]),{2:n,3:1011,4:a,5:s,110:1370,111:1009,112:oa},t(Ga,[2,87]),t(ua,[2,147],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),{78:[1,1371]},{74:pn,78:[2,1066]},{2:n,3:168,4:a,5:s,56:165,77:ie,78:[2,1059],94:1376,111:149,113:153,120:1372,121:1373,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,241:1374,242:[1,1375],254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(da,[2,98]),t(Va,[2,1062],{153:1015,179:ca,180:la,181:ha}),{2:n,3:733,4:a,5:s,72:Tr,76:Ar,77:yr,78:[1,1377],112:Nr,114:736,115:yt,116:Nt,117:1378,118:Cr,122:Rr,123:Or,124:wr,128:Ir,129:xr,130:Dr,131:kr,132:Lr,133:$r,134:Mr,135:Ur,136:_r,137:Fr,138:Pr,139:qr,140:Gr,141:Vr,142:Br,143:jr,144:758,145:Hr,146:Jr,148:Yr,149:Wr,150:Xr,152:Kr,154:Qr,156:zr,158:768,160:769,162:Zr,164:en,166:tn,168:rn,169:nn,170:an,171:sn,172:on,173:un,175:cn,185:ln,187:hn,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,419:190,420:Ge,424:Ve},t(Va,[2,1063],{153:1015,179:ca,180:la,181:ha}),{78:[1,1379],99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},{78:[1,1380],99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},{78:[1,1381]},t(da,[2,120]),{74:Ja,78:[1,1382]},t(da,[2,122]),{74:pn,78:[1,1383]},{2:n,3:733,4:a,5:s,72:Tr,76:Ar,77:yr,78:[1,1384],112:Nr,114:736,115:yt,116:Nt,117:1385,118:Cr,122:Rr,123:Or,124:wr,128:Ir,129:xr,130:Dr,131:kr,132:Lr,133:$r,134:Mr,135:Ur,136:_r,137:Fr,138:Pr,139:qr,140:Gr,141:Vr,142:Br,143:jr,144:758,145:Hr,146:Jr,148:Yr,149:Wr,150:Xr,152:Kr,154:Qr,156:zr,158:768,160:769,162:Zr,164:en,166:tn,168:rn,169:nn,170:an,171:sn,172:on,173:un,175:cn,185:ln,187:hn,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,419:190,420:Ge,424:Ve},{2:n,3:733,4:a,5:s,72:Tr,76:Ar,77:yr,78:[1,1386],112:Nr,114:736,115:yt,116:Nt,117:1387,118:Cr,122:Rr,123:Or,124:wr,128:Ir,129:xr,130:Dr,131:kr,132:Lr,133:$r,134:Mr,135:Ur,136:_r,137:Fr,138:Pr,139:qr,140:Gr,141:Vr,142:Br,143:jr,144:758,145:Hr,146:Jr,148:Yr,149:Wr,150:Xr,152:Kr,154:Qr,156:zr,158:768,160:769,162:Zr,164:en,166:tn,168:rn,169:nn,170:an,171:sn,172:on,173:un,175:cn,185:ln,187:hn,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,419:190,420:Ge,424:Ve},{2:n,3:733,4:a,5:s,72:Tr,76:Ar,77:yr,78:[1,1388],112:Nr,114:736,115:yt,116:Nt,117:1389,118:Cr,122:Rr,123:Or,124:wr,128:Ir,129:xr,130:Dr,131:kr,132:Lr,133:$r,134:Mr,135:Ur,136:_r,137:Fr,138:Pr,139:qr,140:Gr,141:Vr,142:Br,143:jr,144:758,145:Hr,146:Jr,148:Yr,149:Wr,150:Xr,152:Kr,154:Qr,156:zr,158:768,160:769,162:Zr,164:en,166:tn,168:rn,169:nn,170:an,171:sn,172:on,173:un,175:cn,185:ln,187:hn,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,419:190,420:Ge,424:Ve},{2:n,3:733,4:a,5:s,72:Tr,76:Ar,77:yr,78:[1,1390],112:Nr,114:736,115:yt,116:Nt,117:1391,118:Cr,122:Rr,123:Or,124:wr,128:Ir,129:xr,130:Dr,131:kr,132:Lr,133:$r,134:Mr,135:Ur,136:_r,137:Fr,138:Pr,139:qr,140:Gr,141:Vr,142:Br,143:jr,144:758,145:Hr,146:Jr,148:Yr,149:Wr,150:Xr,152:Kr,154:Qr,156:zr,158:768,160:769,162:Zr,164:en,166:tn,168:rn,169:nn,170:an,171:sn,172:on,173:un,175:cn,185:ln,187:hn,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,419:190,420:Ge,424:Ve},{74:As,78:[1,1392]},t(os,[2,143],{419:190,3:733,114:736,144:758,158:768,160:769,117:1394,2:n,4:a,5:s,72:Tr,76:Ar,77:yr,112:Nr,115:yt,116:Nt,118:Cr,122:Rr,123:Or,124:wr,128:Ir,129:xr,130:Dr,131:kr,132:Lr,133:$r,134:Mr,135:Ur,136:_r,137:Fr,138:Pr,139:qr,140:Gr,141:Vr,142:Br,143:jr,145:Hr,146:Jr,148:Yr,149:Wr,150:Xr,152:Kr,154:Qr,156:zr,162:Zr,164:en,166:tn,168:rn,169:nn,170:an,171:sn,172:on,173:un,175:cn,185:ln,187:hn,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,420:Ge,424:Ve}),t(Ba,ja,{178:1234,163:1395}),{2:n,3:733,4:a,5:s,72:Tr,76:Ar,77:yr,78:[1,1396],112:Nr,114:736,115:yt,116:Nt,117:1397,118:Cr,122:Rr,123:Or,124:wr,128:Ir,129:xr,130:Dr,131:kr,132:Lr,133:$r,134:Mr,135:Ur,136:_r,137:Fr,138:Pr,139:qr,140:Gr,141:Vr,142:Br,143:jr,144:758,145:Hr,146:Jr,148:Yr,149:Wr,150:Xr,152:Kr,154:Qr,156:zr,158:768,160:769,162:Zr,164:en,166:tn,168:rn,169:nn,170:an,171:sn,172:on,173:un,175:cn,185:ln,187:hn,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,419:190,420:Ge,424:Ve},{2:n,3:733,4:a,5:s,72:Tr,76:Ar,77:yr,78:[1,1398],112:Nr,114:736,115:yt,116:Nt,117:1399,118:Cr,122:Rr,123:Or,124:wr,128:Ir,129:xr,130:Dr,131:kr,132:Lr,133:$r,134:Mr,135:Ur,136:_r,137:Fr,138:Pr,139:qr,140:Gr,141:Vr,142:Br,143:jr,144:758,145:Hr,146:Jr,148:Yr,149:Wr,150:Xr,152:Kr,154:Qr,156:zr,158:768,160:769,162:Zr,164:en,166:tn,168:rn,169:nn,170:an,171:sn,172:on,173:un,175:cn,185:ln,187:hn,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,419:190,420:Ge,424:Ve},{74:As,78:[1,1400]},{74:As,78:[1,1401]},{74:As,78:[1,1402]},{74:As,78:[1,1403]},{78:[1,1404],153:1015,179:ca,180:la,181:ha},{74:ga,78:[1,1405]},{2:n,3:733,4:a,5:s,72:Tr,74:[1,1406],76:Ar,77:yr,112:Nr,114:736,115:yt,116:Nt,117:1407,118:Cr,122:Rr,123:Or,124:wr,128:Ir,129:xr,130:Dr,131:kr,132:Lr,133:$r,134:Mr,135:Ur,136:_r,137:Fr,138:Pr,139:qr,140:Gr,141:Vr,142:Br,143:jr,144:758,145:Hr,146:Jr,148:Yr,149:Wr,150:Xr,152:Kr,154:Qr,156:zr,158:768,160:769,162:Zr,164:en,166:tn,168:rn,169:nn,170:an,171:sn,172:on,173:un,175:cn,185:ln,187:hn,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,419:190,420:Ge,424:Ve},{2:n,3:1408,4:a,5:s},{2:n,3:1409,4:a,5:s},t(K,[2,597]),{2:n,3:1410,4:a,5:s},{113:1411,132:ue,296:Le},{78:[1,1412]},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1413,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:1011,4:a,5:s,111:1065,143:fa,145:pa,336:1414,337:1066},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1415,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{124:[1,1416]},t(K,[2,653],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),t(Wa,[2,658]),{78:[1,1417],99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},t(K,[2,654],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1418,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(ma,[2,272]),t(ma,[2,274]),t(ma,[2,276]),t(ma,[2,278]),t(nt,[2,161]),t(K,[2,569]),{148:[1,1419]},t(K,[2,570]),t(Sn,[2,536],{387:878,7:879,272:1420,4:qn,386:[1,1421],389:Gn}),t(K,[2,571]),t(K,[2,573]),{74:pn,78:[1,1422]},t(K,[2,577]),t(sr,[2,345]),{74:[1,1423],99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,
124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},{74:[1,1424],99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},{74:[1,1425],99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},{74:[1,1426],99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},t(K,[2,581]),{2:n,3:168,4:a,5:s,56:165,77:ie,94:260,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,151:1427,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:1428,4:a,5:s},t(K,[2,583]),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1376,111:149,113:153,120:1429,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,241:1374,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{77:[1,1430]},{2:n,3:1431,4:a,5:s},{76:va,139:[2,1149],480:1432,483:1433},t(Qa,[2,1148]),{139:[1,1434]},{139:[2,1152]},t(Sn,[2,737]),t(Dn,[2,744]),t(Dn,[2,1166]),{2:n,3:1268,4:a,5:s,76:[1,1437],351:1435,358:1436,384:1438},{2:n,3:1011,4:a,5:s,100:1439,111:1440},{40:1441,79:75,89:c,184:99,189:f},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1442,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(Ma,[2,722]),{2:n,3:1011,4:a,5:s,111:1065,143:fa,145:pa,147:1443,336:1064,337:1066},{2:n,3:168,4:a,5:s,56:165,77:ie,94:260,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,151:1444,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(Ma,[2,727]),{2:n,3:244,4:a,5:s,199:1445},{335:Ta,338:Aa,339:ya,511:1446},t(gt,[2,691],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1447,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{74:[1,1448],78:[1,1449]},t(os,[2,543]),t(os,[2,544]),{74:ss,78:[1,1450]},t(gt,[2,565]),t(Ra,[2,380],{114:625,327:637,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,141:kt,142:Lt,179:_t,180:Ft,312:qt,316:Bt,317:jt,318:Ht,319:Jt}),t(Ra,[2,382],{114:625,327:637,115:yt,116:Nt,123:Ct,133:Ot,136:wt,138:It,141:kt,142:Lt,179:_t,180:Ft,312:qt,316:Bt,317:jt,318:Ht,319:Jt}),t(Qe,[2,396]),t(Qe,[2,400]),{78:[1,1451]},{74:pn,78:[1,1452]},t(Qe,[2,422]),t(Qe,[2,424]),{78:[1,1453],99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},{78:[1,1454]},{74:pn,78:[1,1455]},t(Qe,[2,427]),t(Qe,[2,327]),{77:[1,1456]},t(Qe,rs,{275:1457,276:ns}),t(Qe,rs,{275:1458,276:ns}),t($a,[2,284]),t(Qe,[2,281]),t(Qe,[2,369]),t(Qn,[2,373],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),{74:[1,1460],78:[1,1459]},{74:[1,1462],78:[1,1461],99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},{2:n,3:1325,4:a,5:s},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1194,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,345:1463,419:190,420:Ge,424:Ve},t(Fa,[2,477]),t(Fa,[2,478]),{40:1466,77:sa,79:75,89:c,143:le,144:979,145:ze,149:na,152:fe,181:ge,184:99,189:f,201:980,302:_e,341:1464,342:1465,344:aa,419:190,420:Ge,424:Ve},{2:n,3:1011,4:a,5:s,111:1467},t(Fa,[2,473]),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1468,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{77:sa,143:le,144:979,145:ze,152:fe,181:ge,201:980,302:_e,342:1469,419:190,420:Ge,424:Ve},t(Ma,[2,460],{74:Ua}),t(Ma,[2,467]),t(K,[2,694]),t(us,[2,699]),t(us,[2,700]),{2:n,3:168,4:a,5:s,56:165,77:ie,94:817,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,174:1470,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,253:816,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{170:[1,1472],309:[1,1471]},{460:[1,1473]},t(pr,[2,180]),t(ys,[2,240],{85:1474,232:[1,1475]}),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1476,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1477,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:1478,4:a,5:s},t(ia,[2,170],{216:1357,221:1360,215:1479,205:1480,206:hs,208:ds,222:fs,223:ps,224:bs,225:Es,226:gs,227:ms,228:vs,229:Ss}),{2:n,3:219,4:a,5:s,77:Je,132:Ye,143:le,144:212,145:he,152:fe,156:Q,181:ge,199:213,200:215,201:214,202:217,209:1481,213:We,214:218,290:z,291:Z,292:ee,293:te,302:_e,419:190,420:Ge,424:Ve},t(Ns,[2,205]),t(Ns,[2,206]),{2:n,3:219,4:a,5:s,77:[1,1486],143:le,144:1484,145:he,152:fe,156:Q,181:ge,199:1483,200:1487,201:1485,202:1488,217:1482,290:z,291:Z,292:ee,293:te,302:_e,419:190,420:Ge,424:Ve},{207:[1,1489],223:Cs},{207:[1,1491],223:Rs},t(Os,[2,222]),{206:[1,1495],208:[1,1494],221:1493,223:ps,224:bs,225:Es,226:gs,227:ms,228:vs,229:Ss},t(Os,[2,224]),{223:[1,1496]},{208:[1,1498],223:[1,1497]},{208:[1,1500],223:[1,1499]},{208:[1,1501]},{223:[1,1502]},{223:[1,1503]},{74:ls,204:1504,205:1356,206:hs,208:ds,215:1355,216:1357,221:1360,222:fs,223:ps,224:bs,225:Es,226:gs,227:ms,228:vs,229:Ss},t(Ga,[2,84]),t(da,[2,100]),{74:ws,78:[1,1505]},{78:[1,1507]},t(Is,[2,261]),{78:[2,1060]},t(Is,[2,263],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,242:[1,1508],243:[1,1509],311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),t(da,[2,99]),t(Va,[2,1064],{153:1015,179:ca,180:la,181:ha}),t(da,[2,101]),t(da,[2,102]),t(da,[2,103]),t(da,[2,121]),t(da,[2,124]),t(da,[2,127]),t(Va,[2,1068],{153:1015,179:ca,180:la,181:ha}),t(da,[2,128]),t(Va,[2,1070],{153:1015,179:ca,180:la,181:ha}),t(da,[2,129]),t(Va,[2,1072],{153:1015,179:ca,180:la,181:ha}),t(da,[2,130]),t(Va,[2,1076],{153:1015,179:ca,180:la,181:ha}),t(da,[2,131]),t(Ba,[2,1083],{177:1510}),t(Ba,[2,1086],{153:1015,179:ca,180:la,181:ha}),{74:As,78:[1,1511]},t(da,[2,133]),t(Va,[2,1078],{153:1015,179:ca,180:la,181:ha}),t(da,[2,134]),t(Va,[2,1080],{153:1015,179:ca,180:la,181:ha}),t(da,[2,135]),t(da,[2,136]),t(da,[2,137]),t(da,[2,138]),t(da,[2,139]),t(da,[2,140]),{2:n,3:168,4:a,5:s,56:165,77:ie,94:260,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,151:1512,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(Ha,[2,1082],{153:1015,179:ca,180:la,181:ha}),t(K,[2,607]),t(K,[2,603]),t(K,[2,605]),t(K,[2,601]),t(dn,[2,71]),t(K,[2,449],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),t(Ya,[2,452]),t(Ya,[2,453],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1513,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(Wa,[2,662]),t(K,[2,655],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),{2:n,3:1514,4:a,5:s},t(Sn,[2,545],{385:1515,391:1516,392:1517,366:1525,154:xs,187:Ds,230:ks,297:Ls,343:$s,356:Ms,368:Us,369:_s,373:Fs,374:Ps}),t(Sn,[2,535]),t(K,[2,576],{76:[1,1529]}),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1530,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1531,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1532,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1533,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{74:pn,78:[1,1534]},t(K,[2,585]),{74:ws,78:[1,1535]},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1376,111:149,113:153,120:1536,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,241:1374,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t([10,74,78,139,306,310,602,764],[2,741]),{139:[1,1537]},{139:[2,1150]},{2:n,3:1122,4:a,5:s,132:ft,137:pt,143:tt,145:rt,152:bt,431:585,475:1124,478:1538,482:582,493:579,497:581},{78:[1,1539]},{74:[1,1540],78:[2,506]},{40:1541,79:75,89:c,184:99,189:f},t(os,[2,532]),{74:as,78:[1,1542]},t(Ts,is),t(K,[2,1133],{412:1543,413:1544,72:qs}),t(Ma,za,{79:75,184:99,114:625,327:637,40:1299,468:1546,89:c,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,146:Za,154:$t,170:Mt,171:Ut,179:_t,180:Ft,189:f,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr,470:es}),t(Ma,[2,725],{74:Ja}),t(Ma,[2,726],{74:pn}),t([10,53,72,89,124,146,156,189,266,267,290,306,310,335,338,339,396,400,401,404,406,408,409,417,418,434,436,437,439,440,441,442,443,447,448,451,452,505,507,508,517,602,764],[2,1181],{512:1547,3:1548,2:n,4:a,5:s,76:[1,1549]}),t(Gs,[2,1183],{513:1550,76:[1,1551]}),t(gt,[2,692],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),{131:[1,1552]},t(Na,[2,538]),t(Na,[2,540]),t(Qe,[2,412]),t(Qe,[2,413]),t(Qe,[2,439]),t(Qe,[2,423]),t(Qe,[2,425]),{118:Vs,277:1553,278:1554,279:[1,1555]},t(Qe,[2,328]),t(Qe,[2,329]),t(Qe,[2,316]),{131:[1,1557]},t(Qe,[2,318]),{131:[1,1558]},{74:ss,78:[1,1559]},{77:sa,143:le,144:979,145:ze,152:fe,181:ge,201:980,302:_e,342:1560,419:190,420:Ge,424:Ve},t(Ma,[2,465],{74:Ua}),t(Ma,[2,468]),t(Ts,[2,488]),t(os,[2,480],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),t(Ma,[2,459],{74:Ua}),t(K,[2,718],{74:ga,198:[1,1561]}),{335:Bs,338:js,461:1562},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1565,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{119:[1,1567],170:[1,1568],309:[1,1566]},t(Hs,[2,259],{86:1569,118:[1,1570]}),{119:[1,1571]},t(cs,[2,239],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),{95:[1,1572],99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},{95:[1,1573]},t(Ns,[2,203]),t(Ns,[2,204]),t(Ts,[2,178]),t(Ns,[2,237],{218:1574,230:[1,1575],231:[1,1576]}),t(Js,[2,208],{3:1577,2:n,4:a,5:s,76:[1,1578]}),t(Ys,[2,1095],{219:1579,76:[1,1580]}),{2:n,3:1581,4:a,5:s,76:[1,1582]},{40:1583,79:75,89:c,184:99,189:f},t(Js,[2,216],{3:1584,2:n,4:a,5:s,76:[1,1585]}),t(Js,[2,219],{3:1586,2:n,4:a,5:s,76:[1,1587]}),{77:[1,1588]},t(Os,[2,234]),{77:[1,1589]},t(Os,[2,230]),t(Os,[2,223]),{223:Rs},{223:Cs},t(Os,[2,225]),t(Os,[2,226]),{223:[1,1590]},t(Os,[2,228]),{223:[1,1591]},{223:[1,1592]},t(Os,[2,232]),t(Os,[2,233]),{78:[1,1593],205:1480,206:hs,208:ds,215:1479,216:1357,221:1360,222:fs,223:ps,224:bs,225:Es,226:gs,227:ms,228:vs,229:Ss},t(da,[2,91]),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1376,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,241:1594,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(da,[2,92]),t(Is,[2,264]),{244:[1,1595]},t(os,[2,142],{419:190,3:733,114:736,144:758,158:768,160:769,117:1596,2:n,4:a,5:s,72:Tr,76:Ar,77:yr,112:Nr,115:yt,116:Nt,118:Cr,122:Rr,123:Or,124:wr,128:Ir,129:xr,130:Dr,131:kr,132:Lr,133:$r,134:Mr,135:Ur,136:_r,137:Fr,138:Pr,139:qr,140:Gr,141:Vr,142:Br,143:jr,145:Hr,146:Jr,148:Yr,149:Wr,150:Xr,152:Kr,154:Qr,156:zr,162:Zr,164:en,166:tn,168:rn,169:nn,170:an,171:sn,172:on,173:un,175:cn,185:ln,187:hn,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,420:Ge,424:Ve}),t(da,[2,132]),{74:pn,78:[1,1597]},t(Ya,[2,454],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),t(K,[2,572]),t(Sn,[2,534]),t(Sn,[2,546],{366:1525,392:1598,154:xs,187:Ds,230:ks,297:Ls,343:$s,356:Ms,368:Us,369:_s,373:Fs,374:Ps}),t(fn,[2,548]),{370:[1,1599]},{370:[1,1600]},{2:n,3:244,4:a,5:s,199:1601},t(fn,[2,554],{77:[1,1602]}),{2:n,3:114,4:a,5:s,77:[1,1604],113:251,131:oe,132:ue,143:le,152:fe,156:Q,181:ge,196:250,200:1605,201:254,257:252,258:253,265:Ze,274:1603,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,302:_e},t(fn,[2,558]),{297:[1,1606]},t(fn,[2,560]),t(fn,[2,561]),{335:[1,1607]},{77:[1,1608]},{2:n,3:1609,4:a,5:s},{78:[1,1610],99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},{78:[1,1611],99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},{78:[1,1612],99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},{78:[1,1613],99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},t(K,Xa,{407:1614,76:Ka}),t(K,[2,591]),{74:ws,78:[1,1615]},{2:n,3:1122,4:a,5:s,132:ft,137:pt,143:tt,145:rt,152:bt,431:585,475:1124,478:1616,482:582,493:579,497:581},t(Sn,[2,735]),t(K,[2,493],{352:1617,354:1618,355:1619,4:Ws,243:Xs,343:Ks,356:Qs}),t(zs,Zs,{3:1268,359:1624,384:1625,360:1626,361:1627,2:n,4:a,5:s,367:ei}),{78:[2,507]},{76:[1,1629]},t(K,[2,609]),t(K,[2,1134]),{368:[1,1631],414:[1,1630]},t(Ma,[2,728]),t(K,r,{17:5,18:7,19:8,20:9,21:10,22:11,23:12,24:13,25:14,26:15,27:16,28:17,29:18,30:19,31:20,32:21,33:22,34:23,35:24,36:25,37:26,38:27,39:28,40:29,41:30,42:31,43:32,44:33,45:34,46:35,47:36,48:37,49:38,50:39,51:40,52:41,54:43,55:44,56:45,57:46,58:47,59:48,60:49,61:50,62:51,63:52,64:53,65:54,66:55,67:56,68:57,69:58,70:59,71:60,79:75,504:95,184:99,3:100,12:1632,2:n,4:a,5:s,53:o,72:u,89:c,124:l,146:h,156:d,189:f,266:p,267:b,290:E,335:g,338:m,339:v,396:S,400:T,401:y,404:N,406:C,408:R,409:O,417:I,418:x,434:D,436:k,437:L,439:$,440:M,441:U,442:_,443:F,447:P,448:q,451:G,452:V,505:B,507:j,508:H,517:J}),t(K,[2,762]),t(Gs,[2,1182]),t(K,r,{17:5,18:7,19:8,20:9,21:10,22:11,23:12,24:13,25:14,26:15,27:16,28:17,29:18,30:19,31:20,32:21,33:22,34:23,35:24,36:25,37:26,38:27,39:28,40:29,41:30,42:31,43:32,44:33,45:34,46:35,47:36,48:37,49:38,50:39,51:40,52:41,54:43,55:44,56:45,57:46,58:47,59:48,60:49,61:50,62:51,63:52,64:53,65:54,66:55,67:56,68:57,69:58,70:59,71:60,79:75,504:95,184:99,3:100,12:1633,2:n,4:a,5:s,53:o,72:u,89:c,124:l,146:h,156:d,189:f,266:p,267:b,290:E,335:g,338:m,339:v,396:S,400:T,401:y,404:N,406:C,408:R,409:O,417:I,418:x,434:D,436:k,437:L,439:$,440:M,441:U,442:_,443:F,447:P,448:q,451:G,452:V,505:B,507:j,508:H,517:J}),t(Gs,[2,1184]),{78:[1,1634]},{78:[1,1635],118:Vs,278:1636},{78:[1,1637]},{119:[1,1638]},{119:[1,1639]},{78:[1,1640]},{78:[1,1641]},t(Fa,[2,476]),t(Ma,[2,464],{74:Ua}),{2:n,3:244,4:a,5:s,143:tt,145:rt,199:1643,431:1642},t(us,[2,703]),t(us,[2,705]),{146:[1,1644]},{99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,309:[1,1645],311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},{339:ti,462:1646},{417:[1,1649],463:[1,1648]},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1650,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(ri,[2,267],{87:1651,245:[1,1652],247:[1,1653]}),{119:[1,1654]},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1660,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,233:1655,235:1656,236:ni,237:ai,238:si,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:1661,4:a,5:s},{2:n,3:1662,4:a,5:s},t(Ns,[2,207]),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1663,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:1011,4:a,5:s,100:1664,111:1440},t(Js,[2,209]),{2:n,3:1665,4:a,5:s},t(Js,[2,1097],{220:1666,3:1667,2:n,4:a,5:s}),t(Ys,[2,1096]),t(Js,[2,212]),{2:n,3:1668,4:a,5:s},{78:[1,1669]},t(Js,[2,217]),{2:n,3:1670,4:a,5:s},t(Js,[2,220]),{2:n,3:1671,4:a,5:s},{40:1672,79:75,89:c,184:99,189:f},{40:1673,79:75,89:c,184:99,189:f},t(Os,[2,227]),t(Os,[2,229]),t(Os,[2,231]),t(ia,[2,171]),t(Is,[2,262]),t(Is,[2,265],{242:[1,1674]}),t(Ba,[2,1084],{153:1015,179:ca,180:la,181:ha}),t(da,[2,141]),t(fn,[2,547]),t(fn,[2,550]),{374:[1,1675]},t(fn,[2,1127],{395:1676,393:1677,77:ii}),{131:oe,196:1679},t(fn,[2,555]),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1680,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(fn,[2,557]),t(fn,[2,559]),{2:n,3:114,4:a,5:s,77:[1,1682],113:251,131:oe,132:ue,143:le,152:fe,156:Q,181:ge,196:250,200:255,201:254,257:252,258:253,265:Ze,274:1681,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,302:_e},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1683,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(K,[2,578]),t(sr,[2,349]),t(sr,[2,350]),t(sr,[2,351]),t(sr,[2,352]),t(K,[2,582]),t(K,[2,592]),t(Sn,[2,734]),t(K,[2,489]),t(K,[2,494],{355:1684,4:Ws,243:Xs,343:Ks,356:Qs}),t(oi,[2,496]),t(oi,[2,497]),{124:[1,1685]},{124:[1,1686]},{124:[1,1687]},{74:[1,1688],78:[2,505]},t(os,[2,533]),t(os,[2,508]),{187:[1,1696],193:[1,1697],362:1689,363:1690,364:1691,365:1692,366:1693,368:Us,369:[1,1694],370:[1,1698],373:[1,1695]},{2:n,3:1699,4:a,5:s},{40:1700,79:75,89:c,184:99,189:f},{415:[1,1701]},{416:[1,1702]},t(K,[2,761]),t(K,[2,763]),t(Na,[2,537]),t(Qe,[2,331]),{78:[1,1703]},t(Qe,[2,332]),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1660,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,233:1704,235:1656,236:ni,237:ai,238:si,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1376,111:149,113:153,120:1705,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,241:1374,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(Qe,[2,317]),t(Qe,[2,319]),{2:n,3:1706,4:a,5:s},t(K,[2,720],{77:[1,1707]}),{2:n,3:1011,4:a,5:s,111:1065,143:fa,145:pa,147:1708,336:1064,337:1066},{335:Bs,338:js,461:1709},t(us,[2,707]),{77:[1,1711],343:[1,1712],344:[1,1710]},{170:[1,1714],309:[1,1713]},{170:[1,1716],309:[1,1715]},{99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,309:[1,1717],311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},t(ua,[2,250],{88:1718,162:[1,1719],168:[1,1721],169:[1,1720]}),{131:oe,196:1722},{131:oe,196:1723},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1376,111:149,113:153,120:1724,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,241:1374,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},t(ys,[2,248],{234:1725,74:ui,239:[1,1727]}),t(ci,[2,242]),{146:[1,1728]},{77:[1,1729]},{77:[1,1730]},t(ci,[2,247],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),{78:[2,1051],96:1731,99:[1,1733],102:1732},{99:[1,1734]},t(Ns,[2,235],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),t(Ns,[2,236],{74:as}),t(Js,[2,210]),t(Js,[2,211]),t(Js,[2,1098]),t(Js,[2,213]),{2:n,3:1735,4:a,5:s,76:[1,1736]},t(Js,[2,218]),t(Js,[2,221]),{78:[1,1737]},{78:[1,1738]},t(Is,[2,266]),{2:n,3:244,4:a,5:s,199:1739},t(fn,[2,552]),t(fn,[2,1128]),{2:n,3:1740,4:a,5:s},{74:[1,1741]},{78:[1,1742],99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,
317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},t(fn,[2,562]),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1743,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{78:[1,1744],99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},t(oi,[2,495]),{2:n,3:1745,4:a,5:s},{131:oe,196:1746},{2:n,3:1747,4:a,5:s},t(zs,Zs,{361:1627,360:1748,367:ei}),t(Sn,[2,510]),t(Sn,[2,511]),t(Sn,[2,512]),t(Sn,[2,513]),t(Sn,[2,514]),{370:[1,1749]},{370:[1,1750]},t(li,[2,1121],{382:1751,370:[1,1752]}),{2:n,3:1753,4:a,5:s},{2:n,3:1754,4:a,5:s},t(zs,[2,516]),t(K,[2,1131],{411:1755,413:1756,72:qs}),t(K,[2,610]),t(K,[2,611],{367:[1,1757]}),t(Qe,[2,333]),t([78,118],[2,334],{74:ui}),{74:ws,78:[2,335]},t(K,[2,719]),{2:n,3:1011,4:a,5:s,100:1758,111:1440},t(us,[2,706],{74:Ja}),t(us,[2,704]),{77:sa,143:le,144:979,145:ze,152:fe,181:ge,201:980,302:_e,342:1759,419:190,420:Ge,424:Ve},{2:n,3:1011,4:a,5:s,100:1760,111:1440},{344:[1,1761]},{339:ti,462:1762},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1763,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{339:ti,462:1764},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1765,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{339:ti,462:1766},t(ua,[2,72]),{40:1767,79:75,89:c,164:[1,1768],184:99,189:f,240:[1,1769]},{40:1770,79:75,89:c,184:99,189:f,240:[1,1771]},{40:1772,79:75,89:c,184:99,189:f,240:[1,1773]},t(ri,[2,270],{246:1774,247:[1,1775]}),{248:1776,249:[2,1099],766:[1,1777]},t(Hs,[2,260],{74:ws}),t(ys,[2,241]),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1660,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,235:1778,236:ni,237:ai,238:si,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1779,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{77:[1,1780]},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1660,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,233:1781,235:1656,236:ni,237:ai,238:si,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1660,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,233:1782,235:1656,236:ni,237:ai,238:si,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{78:[1,1783]},{78:[2,1052]},{77:[1,1784]},{77:[1,1785]},t(Js,[2,214]),{2:n,3:1786,4:a,5:s},{2:n,3:1787,4:a,5:s,76:[1,1788]},{2:n,3:1789,4:a,5:s,76:[1,1790]},t(fn,[2,1125],{394:1791,393:1792,77:ii}),{78:[1,1793]},{131:oe,196:1794},t(fn,[2,556]),{78:[1,1795],99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},t(fn,[2,517]),t(oi,[2,498]),t(oi,[2,499]),t(oi,[2,500]),t(os,[2,509]),{2:n,3:1797,4:a,5:s,77:[2,1117],371:1796},{77:[1,1798]},{2:n,3:1800,4:a,5:s,77:[2,1123],383:1799},t(li,[2,1122]),{77:[1,1801]},{77:[1,1802]},t(K,[2,608]),t(K,[2,1132]),t(zs,Zs,{361:1627,360:1803,367:ei}),{74:as,78:[1,1804]},t(us,[2,713],{74:Ua}),{74:as,78:[1,1805]},t(us,[2,715]),t(us,[2,708]),{99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,309:[1,1806],311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},t(us,[2,711]),{99:Tt,112:At,114:625,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,309:[1,1807],311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,327:637,330:er,331:tr,332:rr,333:nr},t(us,[2,709]),t(ua,[2,251]),{40:1808,79:75,89:c,184:99,189:f,240:[1,1809]},{40:1810,79:75,89:c,184:99,189:f},t(ua,[2,253]),{40:1811,79:75,89:c,184:99,189:f},t(ua,[2,254]),{40:1812,79:75,89:c,184:99,189:f},t(ri,[2,268]),{131:oe,196:1813},{249:[1,1814]},{249:[2,1100]},t(ci,[2,243]),t(ys,[2,249],{114:625,327:637,99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1660,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,233:1815,235:1656,236:ni,237:ai,238:si,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{74:ui,78:[1,1816]},{74:ui,78:[1,1817]},t(qa,[2,1053],{97:1818,104:1819,3:1821,2:n,4:a,5:s,76:hi}),{2:n,3:168,4:a,5:s,56:165,77:ie,94:1824,103:1822,105:1823,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:1011,4:a,5:s,100:1825,111:1440},t(Js,[2,215]),t(Ns,[2,173]),{2:n,3:1826,4:a,5:s},t(Ns,[2,175]),{2:n,3:1827,4:a,5:s},t(fn,[2,551]),t(fn,[2,1126]),t(fn,[2,549]),{78:[1,1828]},t(fn,[2,563]),{77:[1,1829]},{77:[2,1118]},{2:n,3:1831,4:a,5:s,132:di,372:1830},{77:[1,1833]},{77:[2,1124]},{2:n,3:1011,4:a,5:s,100:1834,111:1440},{2:n,3:1011,4:a,5:s,100:1835,111:1440},t(K,[2,612]),t(K,[2,721]),{343:[1,1837],344:[1,1836]},{339:ti,462:1838},{335:Bs,338:js,461:1839},t(ua,[2,252]),{40:1840,79:75,89:c,184:99,189:f},t(ua,[2,255]),t(ua,[2,257]),t(ua,[2,258]),t(ri,[2,271]),{131:[2,1101],250:1841,645:[1,1842]},{74:ui,78:[1,1843]},t(ci,[2,245]),t(ci,[2,246]),t(qa,[2,74]),t(qa,[2,1054]),{2:n,3:1844,4:a,5:s},t(qa,[2,78]),{74:[1,1846],78:[1,1845]},t(os,[2,80]),t(os,[2,81],{114:625,327:637,76:[1,1847],99:Tt,112:At,115:yt,116:Nt,123:Ct,124:En,133:Ot,136:wt,138:It,139:xt,140:Dt,141:kt,142:Lt,154:$t,170:Mt,171:Ut,179:_t,180:Ft,311:Pt,312:qt,313:Gt,315:Vt,316:Bt,317:jt,318:Ht,319:Jt,320:Yt,321:Wt,322:Xt,323:Kt,324:Qt,325:zt,326:Zt,330:er,331:tr,332:rr,333:nr}),{74:as,78:[1,1848]},t(Ns,[2,174]),t(Ns,[2,176]),t(fn,[2,553]),{2:n,3:1831,4:a,5:s,132:di,372:1849},{74:fi,78:[1,1850]},t(os,[2,528]),t(os,[2,529]),{2:n,3:1011,4:a,5:s,100:1852,111:1440},{74:as,78:[1,1853]},{74:as,78:[1,1854]},{77:sa,143:le,144:979,145:ze,152:fe,181:ge,201:980,302:_e,342:1855,419:190,420:Ge,424:Ve},{344:[1,1856]},t(us,[2,710]),t(us,[2,712]),t(ua,[2,256]),{131:oe,196:1857},{131:[2,1102]},t(ci,[2,244]),t(qa,[2,77]),{78:[2,76]},{2:n,3:168,4:a,5:s,56:165,77:ie,94:1824,105:1858,111:149,113:153,131:oe,132:ue,137:ce,143:le,144:161,145:he,149:de,152:fe,154:pe,156:Q,158:167,179:be,180:Ee,181:ge,196:151,200:147,201:155,202:156,254:150,255:146,256:148,257:152,258:154,259:157,260:158,261:159,262:162,263:163,265:me,266:p,270:ve,271:Se,273:Te,280:Ae,281:ye,282:Ne,283:Ce,284:Re,285:Oe,286:we,287:Ie,288:xe,290:z,291:Z,292:ee,293:te,294:De,295:ke,296:Le,297:$e,298:Me,299:Ue,302:_e,303:Fe,312:Pe,317:qe,419:190,420:Ge,424:Ve},{2:n,3:1859,4:a,5:s},{78:[1,1860]},{74:fi,78:[1,1861]},{374:[1,1862]},{2:n,3:1863,4:a,5:s,132:[1,1864]},{74:as,78:[1,1865]},t(Sn,[2,526]),t(Sn,[2,527]),t(us,[2,714],{74:Ua}),t(us,[2,716]),t(pi,[2,1103],{251:1866,766:[1,1867]}),t(os,[2,79]),t(os,[2,82]),t(qa,[2,1055],{3:1821,101:1868,104:1869,2:n,4:a,5:s,76:hi}),t(Sn,[2,518]),{2:n,3:244,4:a,5:s,199:1870},t(os,[2,530]),t(os,[2,531]),t(Sn,[2,525]),t(ri,[2,1105],{252:1871,415:[1,1872]}),t(pi,[2,1104]),t(qa,[2,75]),t(qa,[2,1056]),t(bi,[2,1119],{375:1873,377:1874,77:[1,1875]}),t(ri,[2,269]),t(ri,[2,1106]),t(Sn,[2,521],{376:1876,378:1877,230:[1,1878]}),t(bi,[2,1120]),{2:n,3:1831,4:a,5:s,132:di,372:1879},t(Sn,[2,519]),{230:[1,1881],379:1880},{338:[1,1882]},{74:fi,78:[1,1883]},t(Sn,[2,522]),{335:[1,1884]},{380:[1,1885]},t(bi,[2,520]),{380:[1,1886]},{381:[1,1887]},{381:[1,1888]},{230:[2,523]},t(Sn,[2,524])],defaultActions:{105:[2,6],194:[2,336],195:[2,337],196:[2,338],197:[2,339],198:[2,340],199:[2,341],200:[2,342],201:[2,343],202:[2,344],209:[2,695],591:[2,1142],653:[2,1107],654:[2,1108],710:[2,696],780:[2,1073],781:[2,1074],926:[2,446],927:[2,447],928:[2,448],987:[2,697],1288:[2,1152],1375:[2,1060],1433:[2,1150],1541:[2,507],1732:[2,1052],1777:[2,1100],1797:[2,1118],1800:[2,1124],1842:[2,1102],1845:[2,76],1887:[2,523]},parseError:function(e,t){function r(e,t){this.message=e,this.hash=t}if(!t.recoverable)throw r.prototype=Error,new r(e,t);this.trace(e)},parse:function(e){function t(e){for(var t=n.length-1,r=0;;){if(h.toString()in i[e])return r;if(0===e||t<2)return!1;t-=2,e=n[t],++r}}var r=this,n=[0],a=[null],s=[],i=this.table,o="",u=0,c=0,l=0,h=2,d=s.slice.call(arguments,1),f=Object.create(this.lexer),p={yy:{}};for(var b in this.yy)Object.prototype.hasOwnProperty.call(this.yy,b)&&(p.yy[b]=this.yy[b]);f.setInput(e,p.yy),p.yy.lexer=f,p.yy.parser=this,void 0===f.yylloc&&(f.yylloc={});var E=f.yylloc;s.push(E);var g=f.options&&f.options.ranges;"function"==typeof p.yy.parseError?this.parseError=p.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var m,v,S,T,A,y,N,C,R,O=function(){var e;return e=f.lex()||1,"number"!=typeof e&&(e=r.symbols_[e]||e),e},w={};;){if(S=n[n.length-1],this.defaultActions[S]?T=this.defaultActions[S]:(null!==m&&void 0!==m||(m=O()),T=i[S]&&i[S][m]),void 0===T||!T.length||!T[0]){var I,x="";if(l)1!==v&&(I=t(S));else{I=t(S),R=[];for(y in i[S])this.terminals_[y]&&y>h&&R.push("'"+this.terminals_[y]+"'");x=f.showPosition?"Parse error on line "+(u+1)+":\n"+f.showPosition()+"\nExpecting "+R.join(", ")+", got '"+(this.terminals_[m]||m)+"'":"Parse error on line "+(u+1)+": Unexpected "+(1==m?"end of input":"'"+(this.terminals_[m]||m)+"'"),this.parseError(x,{text:f.match,token:this.terminals_[m]||m,line:f.yylineno,loc:E,expected:R,recoverable:I!==!1})}if(3==l){if(1===m||1===v)throw new Error(x||"Parsing halted while starting to recover from another error.");c=f.yyleng,o=f.yytext,u=f.yylineno,E=f.yylloc,m=O()}if(I===!1)throw new Error(x||"Parsing halted. No suitable error recovery rule available.");!function(e){n.length=n.length-2*e,a.length=a.length-e,s.length=s.length-e}(I),v=m==h?null:m,m=h,S=n[n.length-1],T=i[S]&&i[S][h],l=3}if(T[0]instanceof Array&&T.length>1)throw new Error("Parse Error: multiple actions possible at state: "+S+", token: "+m);switch(T[0]){case 1:n.push(m),a.push(f.yytext),s.push(f.yylloc),n.push(T[1]),m=null,v?(m=v,v=null):(c=f.yyleng,o=f.yytext,u=f.yylineno,E=f.yylloc,l>0&&l--);break;case 2:if(N=this.productions_[T[1]][1],w.$=a[a.length-N],w._$={first_line:s[s.length-(N||1)].first_line,last_line:s[s.length-1].last_line,first_column:s[s.length-(N||1)].first_column,last_column:s[s.length-1].last_column},g&&(w._$.range=[s[s.length-(N||1)].range[0],s[s.length-1].range[1]]),void 0!==(A=this.performAction.apply(w,[o,c,u,p.yy,T[1],a,s].concat(d))))return A;N&&(n=n.slice(0,-1*N*2),a=a.slice(0,-1*N),s=s.slice(0,-1*N)),n.push(this.productions_[T[1]][0]),a.push(w.$),s.push(w._$),C=i[n[n.length-2]][n[n.length-1]],n.push(C);break;case 3:return!0}}return!0}},gi=["A","ABSENT","ABSOLUTE","ACCORDING","ACTION","ADA","ADD","ADMIN","AFTER","ALWAYS","ASC","ASSERTION","ASSIGNMENT","ATTRIBUTE","ATTRIBUTES","BASE64","BEFORE","BERNOULLI","BLOCKED","BOM","BREADTH","C","CASCADE","CATALOG","CATALOG_NAME","CHAIN","CHARACTERISTICS","CHARACTERS","CHARACTER_SET_CATALOG","CHARACTER_SET_NAME","CHARACTER_SET_SCHEMA","CLASS_ORIGIN","COBOL","COLLATION","COLLATION_CATALOG","COLLATION_NAME","COLLATION_SCHEMA","COLUMNS","COLUMN_NAME","COMMAND_FUNCTION","COMMAND_FUNCTION_CODE","COMMITTED","CONDITION_NUMBER","CONNECTION","CONNECTION_NAME","CONSTRAINTS","CONSTRAINT_CATALOG","CONSTRAINT_NAME","CONSTRAINT_SCHEMA","CONSTRUCTOR","CONTENT","CONTINUE","CONTROL","CURSOR_NAME","DATA","DATETIME_INTERVAL_CODE","DATETIME_INTERVAL_PRECISION","DB","DEFAULTS","DEFERRABLE","DEFERRED","DEFINED","DEFINER","DEGREE","DEPTH","DERIVED","DESC","DESCRIPTOR","DIAGNOSTICS","DISPATCH","DOCUMENT","DOMAIN","DYNAMIC_FUNCTION","DYNAMIC_FUNCTION_CODE","EMPTY","ENCODING","ENFORCED","EXCLUDE","EXCLUDING","EXPRESSION","FILE","FINAL","FIRST","FLAG","FOLLOWING","FORTRAN","FOUND","FS","G","GENERAL","GENERATED","GO","GOTO","GRANTED","HEX","HIERARCHY","ID","IGNORE","IMMEDIATE","IMMEDIATELY","IMPLEMENTATION","INCLUDING","INCREMENT","INDENT","INITIALLY","INPUT","INSTANCE","INSTANTIABLE","INSTEAD","INTEGRITY","INVOKER","ISOLATION","K","KEY","KEY_MEMBER","KEY_TYPE","LAST","LENGTH","LEVEL","LIBRARY","LIMIT","LINK","LOCATION","LOCATOR","M","MAP","MAPPING","MATCHED","MAXVALUE","MESSAGE_LENGTH","MESSAGE_OCTET_LENGTH","MESSAGE_TEXT","MINVALUE","MORE","MUMPS","NAME","NAMES","NAMESPACE","NESTING","NEXT","NFC","NFD","NFKC","NFKD","NIL","NORMALIZED","NULLABLE","NULLS","NUMBER","OBJECT","OCTETS","OFF","OPTION","OPTIONS","ORDERING","ORDINALITY","OTHERS","OUTPUT","OVERRIDING","P","PAD","PARAMETER_MODE","PARAMETER_NAME","PARAMETER_ORDINAL_POSITION","PARAMETER_SPECIFIC_CATALOG","PARAMETER_SPECIFIC_NAME","PARAMETER_SPECIFIC_SCHEMA","PARTIAL","PASCAL","PASSING","PASSTHROUGH","PATH","PERMISSION","PLACING","PLI","PRECEDING","PRESERVE","PRIOR","PRIVILEGES","PUBLIC","READ","RECOVERY","RELATIVE","REPEATABLE","REQUIRING","RESPECT","RESTART","RESTORE","RESTRICT","RETURNED_CARDINALITY","RETURNED_LENGTH","RETURNED_OCTET_LENGTH","RETURNED_SQLSTATE","RETURNING","ROLE","ROUTINE","ROUTINE_CATALOG","ROUTINE_NAME","ROUTINE_SCHEMA","ROW_COUNT","SCALE","SCHEMA","SCHEMA_NAME","SCOPE_CATALOG","SCOPE_NAME","SCOPE_SCHEMA","SECTION","SECURITY","SELECTIVE","SELF","SEQUENCE","SERIALIZABLE","SERVER","SERVER_NAME","SESSION","SETS","SIMPLE","SIZE","SOURCE","SPACE","SPECIFIC_NAME","STANDALONE","STATE","STATEMENT","STRIP","STRUCTURE","STYLE","SUBCLASS_ORIGIN","T","TABLE_NAME","TEMPORARY","TIES","TOKEN","TOP_LEVEL_COUNT","TRANSACTION","TRANSACTIONS_COMMITTED","TRANSACTIONS_ROLLED_BACK","TRANSACTION_ACTIVE","TRANSFORM","TRANSFORMS","TRIGGER_CATALOG","TRIGGER_NAME","TRIGGER_SCHEMA","TYPE","UNBOUNDED","UNCOMMITTED","UNDER","UNLINK","UNNAMED","UNTYPED","URI","USAGE","USER_DEFINED_TYPE_CATALOG","USER_DEFINED_TYPE_CODE","USER_DEFINED_TYPE_NAME","USER_DEFINED_TYPE_SCHEMA","VALID","VERSION","VIEW","WHITESPACE","WORK","WRAPPER","WRITE","XMLDECLARATION","XMLSCHEMA","YES","ZONE"];Ei.parseError=function(e,t){if(!(t.expected&&t.expected.indexOf("'LITERAL'")>-1&&/[a-zA-Z_][a-zA-Z_0-9]*/.test(t.token)&&gi.indexOf(t.token)>-1))throw new SyntaxError(e)};var mi=function(){return{EOF:1,parseError:function(e,t){if(!this.yy.parser)throw new Error(e);this.yy.parser.parseError(e,t)},setInput:function(e,t){return this.yy=t||this.yy||{},this._input=e,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var e=this._input[0];return this.yytext+=e,this.yyleng++,this.offset++,this.match+=e,this.matched+=e,e.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),e},unput:function(e){var t=e.length,r=e.split(/(?:\r\n?|\n)/g);this._input=e+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-t),this.offset-=t;var n=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),r.length-1&&(this.yylineno-=r.length-1);var a=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:r?(r.length===n.length?this.yylloc.first_column:0)+n[n.length-r.length].length-r[0].length:this.yylloc.first_column-t},this.options.ranges&&(this.yylloc.range=[a[0],a[0]+this.yyleng-t]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(e){this.unput(this.match.slice(e))},pastInput:function(){var e=this.matched.substr(0,this.matched.length-this.match.length);return(e.length>20?"...":"")+e.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var e=this.match;return e.length<20&&(e+=this._input.substr(0,20-e.length)),(e.substr(0,20)+(e.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var e=this.pastInput(),t=new Array(e.length+1).join("-");return e+this.upcomingInput()+"\n"+t+"^"},test_match:function(e,t){var r,n,a;if(this.options.backtrack_lexer&&(a={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(a.yylloc.range=this.yylloc.range.slice(0))),n=e[0].match(/(?:\r\n?|\n).*/g),n&&(this.yylineno+=n.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:n?n[n.length-1].length-n[n.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+e[0].length},this.yytext+=e[0],this.match+=e[0],this.matches=e,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(e[0].length),this.matched+=e[0],r=this.performAction.call(this,this.yy,this,t,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),r)return r;if(this._backtrack){for(var s in a)this[s]=a[s];return!1}return!1},next:function(){if(this.done)return this.EOF;this._input||(this.done=!0);var e,t,r,n;this._more||(this.yytext="",this.match="");for(var a=this._currentRules(),s=0;s<a.length;s++)if((r=this._input.match(this.rules[a[s]]))&&(!t||r[0].length>t[0].length)){if(t=r,n=s,this.options.backtrack_lexer){if((e=this.test_match(r,a[s]))!==!1)return e;if(this._backtrack){t=!1;continue}return!1}if(!this.options.flex)break}return t?(e=this.test_match(t,a[n]))!==!1&&e:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var e=this.next();return e?e:this.lex()},begin:function(e){this.conditionStack.push(e)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(e){return e=this.conditionStack.length-1-Math.abs(e||0),e>=0?this.conditionStack[e]:"INITIAL"},pushState:function(e){this.begin(e)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(e,t,r,n){switch(r){case 0:return 266;case 1:return 302;case 2:return 420;case 3:return 299;case 4:return 5;case 5:return 5;case 6:return 296;case 7:return 296;case 8:return 132;case 9:return 132;case 10:return;case 11:break;case 12:return 316;case 13:return 319;case 14:return t.yytext="VALUE",89;case 15:return t.yytext="VALUE",189;case 16:return t.yytext="ROW",189;case 17:return t.yytext="COLUMN",189;case 18:return t.yytext="MATRIX",189;case 19:return t.yytext="INDEX",189;case 20:return t.yytext="RECORDSET",189;case 21:return t.yytext="TEXT",189;case 22:return t.yytext="SELECT",189;case 23:return 520;case 24:return 381;case 25:return 402;case 26:return 515;case 27:return 287;case 28:return 269;case 29:return 269;case 30:return 164;case 31:return 400;case 32:return 170;case 33:return 229;case 34:return 166;case 35:return 207;case 36:return 288;case 37:return 76;case 38:return 418;case 39:return 242;case 40:return 404;case 41:return 356;case 42:return 284;case 43:return 514;case 44:return 437;case 45:return 330;case 46:return 441;case 47:return 331;case 48:return 315;case 49:return 119;case 50:return 112;case 51:return 315;case 52:return 112;case 53:return 315;case 54:return 112;case 55:return 315;case 56:return 508;case 57:return 303;case 58:return 271;case 59:return 368;case 60:return 130;case 61:return"CLOSE";case 62:return 243;case 63:return 190;case 64:return 190;case 65:return 434;case 66:return 367;case 67:return 470;case 68:return 440;case 69:return 273;case 70:return 240;case 71:return 281;case 72:return 267;case 73:return 206;case 74:return 238;case 75:return 265;case 76:return"CURSOR";case 77:return 405;case 78:return 291;case 79:return 292;case 80:return 448;case 81:return 343;case 82:return 338;case 83:return"DELETED";case 84:return 242;case 85:return 406;case 86:return 185;case 87:return 396;case 88:return 447;case 89:return 135;case 90:return 306;case 91:return 389;case 92:return 310;case 93:return 314;case 94:return 169;case 95:return 508;case 96:return 508;case 97:return 298;case 98:return 14;case 99:return 295;case 100:return 249;case 101:return 285;case 102:return 95;case 103:return 373;case 104:return 183;case 105:return 227;case 106:return 268;case 107:return 313;case 108:return 602;case 109:return 472;case 110:return 232;case 111:return 236;case 112:return 239;case 113:return 156;case 114:return 356;case 115:return 332;case 116:return 99;case 117:return 193;case 118:return 212;case 119:return 224;case 120:return 516;case 121:return 339;case 122:return 213;case 123:return 168;case 124:return 293;case 125:return 198;case 126:return 223;case 127:return 370;case 128:return 286;case 129:return"LET";case 130:return 225;case 131:return 112;case 132:return 245;case 133:return 460;case 134:return 191;case 135:return 283;case 136:return 390;case 137:return 282;case 138:return 452;case 139:return 169;case 140:return 403;case 141:return 222;case 142:return 645;case 143:return 270;case 144:return 244;case 145:return 380;case 146:return 154;case 147:return 297;case 148:return 433;case 149:return 230;case 150:return 415;case 151:return 129;case 152:return 247;case 153:return"OPEN";case 154:return 416;case 155:return 171;case 156:return 118;case 157:return 208;case 158:return 276;case 159:return 172;case 160:return 279;case 161:return 765;case 162:return 93;case 163:return 16;case 164:return 369;case 165:return 442;case 166:return 678;case 167:return 15;case 168:return 414;case 169:return 194;case 170:return"REDUCE";case 171:return 374;case 172:return 311;case 173:return 517;case 174:return 682;case 175:return 107;case 176:return 401;case 177:return 175;case 178:return 290;case 179:return 443;case 180:return 687;case 181:return 173;case 182:return 173;case 183:return 226;case 184:return 436;case 185:return 237;case 186:return 150;case 187:return 766;case 188:return 405;case 189:return 89;case 190:return 228;case 191:return 146;case 192:return 146;case 193:return 409;case 194:return 334;case 195:return 417;case 196:return"STRATEGY";case 197:return"STORE";case 198:return 280;case 199:return 353;case 200:return 353;case 201:return 463;case 202:return 357;case 203:return 357;case 204:return 192;case 205:return 309;case 206:return"TIMEOUT";case 207:return 148;case 208:return 195;case 209:return 435;case 210:return 435;case 211:return 509;case 212:return 294;case 213:return 451;case 214:return 162;case 215:return 187;case 216:return 98;case 217:return 335;case 218:return 408;case 219:return 231;case 220:return 149;case 221:return 344;case 222:return 134;case 223:return 410;case 224:return 308;case 225:return 128;case 226:return 439;case 227:return 72;case 228:return 435;case 229:return 131;case 230:return 131;case 231:return 115;case 232:return 137;case 233:return 179;case 234:return 317;case 235:return 180;case 236:return 133;case 237:return 138;case 238:return 326;case 239:return 323;case 240:return 325;case 241:return 322;case 242:return 320;case 243:return 318;case 244:return 319;case 245:return 142;case 246:return 141;case 247:return 139;case 248:return 321;case 249:return 324;case 250:return 140;case 251:return 124;case 252:return 324;case 253:return 77;case 254:return 78;case 255:return 145;case 256:return 424;case 257:return 426;case 258:return 300;case 259:return 505;case 260:return 507;case 261:return 122;case 262:return 116;case 263:return 74;case 264:return 333;case 265:return 152;case 266:return 764;case 267:return 143;case 268:return 181;case 269:return 136;case 270:return 123;case 271:return 312;case 272:return 4;case 273:return 10;case 274:return"INVALID"}},
rules:[/^(?:``([^\`])+``)/i,/^(?:\[\?\])/i,/^(?:@\[)/i,/^(?:ARRAY\[)/i,/^(?:\[([^\]])*?\])/i,/^(?:`([^\`])*?`)/i,/^(?:N(['](\\.|[^']|\\')*?['])+)/i,/^(?:X(['](\\.|[^']|\\')*?['])+)/i,/^(?:(['](\\.|[^']|\\')*?['])+)/i,/^(?:(["](\\.|[^"]|\\")*?["])+)/i,/^(?:--(.*?)($|\r\n|\r|\n))/i,/^(?:\s+)/i,/^(?:\|\|)/i,/^(?:\|)/i,/^(?:VALUE\s+OF\s+SEARCH\b)/i,/^(?:VALUE\s+OF\s+SELECT\b)/i,/^(?:ROW\s+OF\s+SELECT\b)/i,/^(?:COLUMN\s+OF\s+SELECT\b)/i,/^(?:MATRIX\s+OF\s+SELECT\b)/i,/^(?:INDEX\s+OF\s+SELECT\b)/i,/^(?:RECORDSET\s+OF\s+SELECT\b)/i,/^(?:TEXT\s+OF\s+SELECT\b)/i,/^(?:SELECT\b)/i,/^(?:ABSOLUTE\b)/i,/^(?:ACTION\b)/i,/^(?:ADD\b)/i,/^(?:AFTER\b)/i,/^(?:AGGR\b)/i,/^(?:AGGREGATE\b)/i,/^(?:AGGREGATOR\b)/i,/^(?:ALL\b)/i,/^(?:ALTER\b)/i,/^(?:AND\b)/i,/^(?:ANTI\b)/i,/^(?:ANY\b)/i,/^(?:APPLY\b)/i,/^(?:ARRAY\b)/i,/^(?:AS\b)/i,/^(?:ASSERT\b)/i,/^(?:ASC\b)/i,/^(?:ATTACH\b)/i,/^(?:AUTO(_)?INCREMENT\b)/i,/^(?:AVG\b)/i,/^(?:BEFORE\b)/i,/^(?:BEGIN\b)/i,/^(?:BETWEEN\b)/i,/^(?:BREAK\b)/i,/^(?:NOT\s+BETWEEN\b)/i,/^(?:NOT\s+LIKE\b)/i,/^(?:BY\b)/i,/^(?:~~\*)/i,/^(?:!~~\*)/i,/^(?:~~)/i,/^(?:!~~)/i,/^(?:ILIKE\b)/i,/^(?:NOT\s+ILIKE\b)/i,/^(?:CALL\b)/i,/^(?:CASE\b)/i,/^(?:CAST\b)/i,/^(?:CHECK\b)/i,/^(?:CLASS\b)/i,/^(?:CLOSE\b)/i,/^(?:COLLATE\b)/i,/^(?:COLUMN\b)/i,/^(?:COLUMNS\b)/i,/^(?:COMMIT\b)/i,/^(?:CONSTRAINT\b)/i,/^(?:CONTENT\b)/i,/^(?:CONTINUE\b)/i,/^(?:CONVERT\b)/i,/^(?:CORRESPONDING\b)/i,/^(?:COUNT\b)/i,/^(?:CREATE\b)/i,/^(?:CROSS\b)/i,/^(?:CUBE\b)/i,/^(?:CURRENT_TIMESTAMP\b)/i,/^(?:CURSOR\b)/i,/^(?:DATABASE(S)?)/i,/^(?:DATEADD\b)/i,/^(?:DATEDIFF\b)/i,/^(?:DECLARE\b)/i,/^(?:DEFAULT\b)/i,/^(?:DELETE\b)/i,/^(?:DELETED\b)/i,/^(?:DESC\b)/i,/^(?:DETACH\b)/i,/^(?:DISTINCT\b)/i,/^(?:DROP\b)/i,/^(?:ECHO\b)/i,/^(?:EDGE\b)/i,/^(?:END\b)/i,/^(?:ENUM\b)/i,/^(?:ELSE\b)/i,/^(?:ESCAPE\b)/i,/^(?:EXCEPT\b)/i,/^(?:EXEC\b)/i,/^(?:EXECUTE\b)/i,/^(?:EXISTS\b)/i,/^(?:EXPLAIN\b)/i,/^(?:FALSE\b)/i,/^(?:FETCH\b)/i,/^(?:FIRST\b)/i,/^(?:FOR\b)/i,/^(?:FOREIGN\b)/i,/^(?:FROM\b)/i,/^(?:FULL\b)/i,/^(?:FUNCTION\b)/i,/^(?:GLOB\b)/i,/^(?:GO\b)/i,/^(?:GRAPH\b)/i,/^(?:GROUP\b)/i,/^(?:GROUPING\b)/i,/^(?:HAVING\b)/i,/^(?:IF\b)/i,/^(?:IDENTITY\b)/i,/^(?:IS\b)/i,/^(?:IN\b)/i,/^(?:INDEX\b)/i,/^(?:INDEXED\b)/i,/^(?:INNER\b)/i,/^(?:INSTEAD\b)/i,/^(?:INSERT\b)/i,/^(?:INSERTED\b)/i,/^(?:INTERSECT\b)/i,/^(?:INTERVAL\b)/i,/^(?:INTO\b)/i,/^(?:JOIN\b)/i,/^(?:KEY\b)/i,/^(?:LAST\b)/i,/^(?:LET\b)/i,/^(?:LEFT\b)/i,/^(?:LIKE\b)/i,/^(?:LIMIT\b)/i,/^(?:MATCHED\b)/i,/^(?:MATRIX\b)/i,/^(?:MAX(\s+)?(?=\())/i,/^(?:MAX(\s+)?(?=(,|\))))/i,/^(?:MIN(\s+)?(?=\())/i,/^(?:MERGE\b)/i,/^(?:MINUS\b)/i,/^(?:MODIFY\b)/i,/^(?:NATURAL\b)/i,/^(?:NEXT\b)/i,/^(?:NEW\b)/i,/^(?:NOCASE\b)/i,/^(?:NO\b)/i,/^(?:NOT\b)/i,/^(?:NULL\b)/i,/^(?:OFF\b)/i,/^(?:ON\b)/i,/^(?:ONLY\b)/i,/^(?:OF\b)/i,/^(?:OFFSET\b)/i,/^(?:OPEN\b)/i,/^(?:OPTION\b)/i,/^(?:OR\b)/i,/^(?:ORDER\b)/i,/^(?:OUTER\b)/i,/^(?:OVER\b)/i,/^(?:PATH\b)/i,/^(?:PARTITION\b)/i,/^(?:PERCENT\b)/i,/^(?:PIVOT\b)/i,/^(?:PLAN\b)/i,/^(?:PRIMARY\b)/i,/^(?:PRINT\b)/i,/^(?:PRIOR\b)/i,/^(?:QUERY\b)/i,/^(?:READ\b)/i,/^(?:RECORDSET\b)/i,/^(?:REDUCE\b)/i,/^(?:REFERENCES\b)/i,/^(?:REGEXP\b)/i,/^(?:REINDEX\b)/i,/^(?:RELATIVE\b)/i,/^(?:REMOVE\b)/i,/^(?:RENAME\b)/i,/^(?:REPEAT\b)/i,/^(?:REPLACE\b)/i,/^(?:REQUIRE\b)/i,/^(?:RESTORE\b)/i,/^(?:RETURN\b)/i,/^(?:RETURNS\b)/i,/^(?:RIGHT\b)/i,/^(?:ROLLBACK\b)/i,/^(?:ROLLUP\b)/i,/^(?:ROW\b)/i,/^(?:ROWS\b)/i,/^(?:SCHEMA(S)?)/i,/^(?:SEARCH\b)/i,/^(?:SEMI\b)/i,/^(?:SET\b)/i,/^(?:SETS\b)/i,/^(?:SHOW\b)/i,/^(?:SOME\b)/i,/^(?:SOURCE\b)/i,/^(?:STRATEGY\b)/i,/^(?:STORE\b)/i,/^(?:SUM\b)/i,/^(?:TABLE\b)/i,/^(?:TABLES\b)/i,/^(?:TARGET\b)/i,/^(?:TEMP\b)/i,/^(?:TEMPORARY\b)/i,/^(?:TEXTSTRING\b)/i,/^(?:THEN\b)/i,/^(?:TIMEOUT\b)/i,/^(?:TO\b)/i,/^(?:TOP\b)/i,/^(?:TRAN\b)/i,/^(?:TRANSACTION\b)/i,/^(?:TRIGGER\b)/i,/^(?:TRUE\b)/i,/^(?:TRUNCATE\b)/i,/^(?:UNION\b)/i,/^(?:UNIQUE\b)/i,/^(?:UNPIVOT\b)/i,/^(?:UPDATE\b)/i,/^(?:USE\b)/i,/^(?:USING\b)/i,/^(?:VALUE\b)/i,/^(?:VALUES\b)/i,/^(?:VERTEX\b)/i,/^(?:VIEW\b)/i,/^(?:WHEN\b)/i,/^(?:WHERE\b)/i,/^(?:WHILE\b)/i,/^(?:WITH\b)/i,/^(?:WORK\b)/i,/^(?:(\d*[.])?\d+[eE]\d+)/i,/^(?:(\d*[.])?\d+)/i,/^(?:->)/i,/^(?:#)/i,/^(?:\+)/i,/^(?:-)/i,/^(?:\*)/i,/^(?:\/)/i,/^(?:%)/i,/^(?:!===)/i,/^(?:===)/i,/^(?:!==)/i,/^(?:==)/i,/^(?:>=)/i,/^(?:&)/i,/^(?:\|)/i,/^(?:<<)/i,/^(?:>>)/i,/^(?:>)/i,/^(?:<=)/i,/^(?:<>)/i,/^(?:<)/i,/^(?:=)/i,/^(?:!=)/i,/^(?:\()/i,/^(?:\))/i,/^(?:@)/i,/^(?:\{)/i,/^(?:\})/i,/^(?:\])/i,/^(?::-)/i,/^(?:\?-)/i,/^(?:\.\.)/i,/^(?:\.)/i,/^(?:,)/i,/^(?:::)/i,/^(?::)/i,/^(?:;)/i,/^(?:\$)/i,/^(?:\?)/i,/^(?:!)/i,/^(?:\^)/i,/^(?:~)/i,/^(?:[a-zA-Z_][a-zA-Z_0-9]*)/i,/^(?:$)/i,/^(?:.)/i],conditions:{INITIAL:{rules:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274],inclusive:!0}}}}();return Ei.lexer=mi,e.prototype=Ei,Ei.Parser=e,new e}();void 0!==y&&"undefined"!=typeof exports&&(exports.parser=N,exports.Parser=N.Parser,exports.parse=function(){return N.parse.apply(N,arguments)},exports.main=function(e){e[1]||(console.log("Usage: "+e[0]+" FILE"),process.exit(1));var t=y("fs").readFileSync(y("path").normalize(e[1]),"utf8");return exports.parser.parse(t)},"undefined"!=typeof module&&y.main===module&&exports.main(process.argv.slice(1))),A.prettyflag=!1,A.pretty=function(e,t){var r=A.prettyflag;A.prettyflag=!t;var n=A.parse(e).toString();return A.prettyflag=r,n};var C=A.utils={},R=C.escapeq=function(e){return(""+e).replace(/["'\\\n\r\u2028\u2029]/g,function(e){switch(e){case'"':case"'":case"\\":return"\\"+e;case"\n":return"\\n";case"\r":return"\\r";case"\u2028":return"\\u2028";case"\u2029":return"\\u2029"}})},O=C.undoubleq=function(e){return e.replace(/(\')/g,"''")},w=C.doubleq=function(e){return e.replace(/(\'\')/g,"\\'")},I=(C.doubleqq=function(e){return e.replace(/\'/g,"'")},function(e){return e[0]===String.fromCharCode(65279)&&(e=e.substr(1)),e});C.global=function(){try{return Function("return this")()}catch(t){var e=self||window||e;if(e)return e;throw new Error("Unable to locate global object")}}();C.isNativeFunction=function(e){return"function"==typeof e&&!!~e.toString().indexOf("[native code]")};C.isWebWorker=function(){try{var e=C.global.importScripts;return C.isNativeFunction(e)}catch(e){return!1}}(),C.isNode=function(){try{return C.isNativeFunction(C.global.process.reallyExit)}catch(e){return!1}}(),C.isBrowser=function(){try{return C.isNativeFunction(C.global.location.reload)}catch(e){return!1}}(),C.isBrowserify=function(){return C.isBrowser&&"undefined"!=typeof process&&process.browser}(),C.isRequireJS=function(){return C.isBrowser&&"function"==typeof y&&"function"==typeof y.specified}(),C.isMeteor=function(){return"undefined"!=typeof Meteor&&Meteor.release}(),C.isMeteorClient=C.isMeteorClient=function(){return C.isMeteor&&Meteor.isClient}(),C.isMeteorServer=function(){return C.isMeteor&&Meteor.isServer}(),C.isCordova=function(){return"object"==typeof cordova}(),C.isReactNative=function(){return!1}(),C.hasIndexedDB=function(){return!!C.global.indexedDB}(),C.isArray=function(e){return"[object Array]"===Object.prototype.toString.call(e)};var x=C.loadFile=function(e,t,r,n){var a;if(C.isNode||C.isMeteorServer);else if(C.isCordova)C.global.requestFileSystem(LocalFileSystem.PERSISTENT,0,function(t){t.root.getFile(e,{create:!1},function(e){e.file(function(e){var t=new FileReader;t.onloadend=function(e){r(I(this.result))},t.readAsText(e)})})});else if("string"==typeof e)if("#"===e.substr(0,1)&&"undefined"!=typeof document)a=document.querySelector(e).textContent,r(a);else{var s=new XMLHttpRequest;s.onreadystatechange=function(){4===s.readyState&&(200===s.status?r&&r(I(s.responseText)):n&&n(s))},s.open("GET",e,t),s.responseType="text",s.send()}else if(e instanceof Event){var i=e.target.files,o=new FileReader;i[0].name;o.onload=function(e){r(I(e.target.result))},o.readAsText(i[0])}};C.loadBinaryFile=function(e,t,r,n){if(C.isNode||C.isMeteorServer);else if("string"==typeof e){var a=new XMLHttpRequest;a.open("GET",e,t),a.responseType="arraybuffer",a.onload=function(){for(var e=new Uint8Array(a.response),t=[],n=0;n<e.length;++n)t[n]=String.fromCharCode(e[n]);r(t.join(""))},a.send()}else if(e instanceof Event){var s=e.target.files,i=new FileReader;s[0].name;i.onload=function(e){r(e.target.result)},i.readAsArrayBuffer(s[0])}else e instanceof Blob&&r(e)},C.removeFile=function(e,t){if(!C.isNode)throw new Error("You can remove files only in Node.js and Apache Cordova")},C.deleteFile=function(e,t){};C.autoExtFilename=function(e,t,r){return r=r||{},"string"!=typeof e||e.match(/^[A-z]+:\/\/|\n|\..{2,4}$/)||0===r.autoExt||r.autoExt===!1?e:e+"."+t};var D=(C.fileExists=function(e,t){if(!C.isNode)throw new Error("You can use exists() only in Node.js or Apach Cordova")},C.saveFile=function(e,t,r,n){var s=1;if(void 0===e)s=t,r&&(s=r(s));else if(C.isNode);else if(9===a()){var i=t.replace(/\r\n/g,"&#A;&#D;");i=i.replace(/\n/g,"&#D;"),i=i.replace(/\t/g,"&#9;");var o=C.global.open("about:blank","_blank");o.document.write(i),o.document.close(),o.document.execCommand("SaveAs",!1,e),o.close()}else{var u={disableAutoBom:!1};A.utils.extend(u,n);var c=new Blob([t],{type:"text/plain;charset=utf-8"});Ee(c,e,u.disableAutoBom),r&&(s=r(s))}return s},C.hash=function(e){for(var t=5381,r=e.length;r;)t=33*t^e.charCodeAt(--r);return t}),k=C.arrayUnion=function(e,t){var r=t.slice(0);return e.forEach(function(e){r.indexOf(e)<0&&r.push(e)}),r},L=C.arrayDiff=function(e,t){return e.filter(function(e){return t.indexOf(e)<0})},$=C.arrayIntersect=function(e,t){var r=[];return e.forEach(function(e){var n=!1;t.forEach(function(t){n=n||e===t}),n&&r.push(e)}),r},M=C.arrayUnionDeep=function(e,t){var r=t.slice(0);return e.forEach(function(e){var t=!1;r.forEach(function(r){t=t||P(e,r)}),t||r.push(e)}),r},U=C.arrayExceptDeep=function(e,t){var r=[];return e.forEach(function(e){var n=!1;t.forEach(function(t){n=n||P(e,t)}),n||r.push(e)}),r},_=C.arrayIntersectDeep=function(e,t){var r=[];return e.forEach(function(e){var n=!1;t.forEach(function(t){n=n||P(e,t,!0)}),n&&r.push(e)}),r},F=C.cloneDeep=function e(t){if(null===t||"object"!=typeof t)return t;if(t instanceof Date)return new Date(t);var r=t.constructor();for(var n in t)t.hasOwnProperty(n)&&(r[n]=e(t[n]));return r},P=C.deepEqual=function(e,t){if(e===t)return!0;if("object"==typeof e&&null!==e&&"object"==typeof t&&null!==t){if(Object.keys(e).length!==Object.keys(t).length)return!1;for(var r in e)if(!P(e[r],t[r]))return!1;return!0}return!1},q=C.distinctArray=function(e){for(var t={},r=0,n=e.length;r<n;r++){var a;a="object"==typeof e[r]?Object.keys(e[r]).sort().map(function(t){return t+"`"+e[r][t]}).join("`"):e[r],t[a]=e[r]}var s=[];for(var i in t)s.push(t[i]);return s},G=C.extend=function(e,t){e=e||{};for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r]);return e},V=C.flatArray=function(e){if(!e||0===e.length)return[];if("object"==typeof e&&e instanceof A.Recordset)return e.data.map(function(t){return t[e.columns[0].columnid]});var t=Object.keys(e[0])[0];return void 0===t?[]:e.map(function(e){return e[t]})};C.arrayOfArrays=function(e){return e.map(function(e){var t=[];for(var r in e)t.push(e[r]);return t})};Array.isArray||(Array.isArray=function(e){return"[object Array]"===Object.prototype.toString.call(e)});C.xlsnc=function(e){var t=String.fromCharCode(65+e%26);return e>=26&&(e=(e/26|0)-1,t=String.fromCharCode(65+e%26)+t,e>26&&(e=(e/26|0)-1,t=String.fromCharCode(65+e%26)+t)),t},C.xlscn=function(e){var t=e.charCodeAt(0)-65;return e.length>1&&(t=26*(t+1)+e.charCodeAt(1)-65,e.length>2&&(t=26*(t+1)+e.charCodeAt(2)-65)),t},C.domEmptyChildren=function(e){for(var t=e.childNodes.length;t--;)e.removeChild(e.lastChild)},C.like=function(e,t,r){r||(r="");for(var n=0,a="^";n<e.length;){var s=e[n],i="";n<e.length-1&&(i=e[n+1]),s===r?(a+="\\"+i,n++):"["===s&&"^"===i?(a+="[^",n++):a+="["===s||"]"===s?s:"%"===s?".*":"_"===s?".":"/.*+?|(){}".indexOf(s)>-1?"\\"+s:s,n++}return a+="$",(""+(t||"")).toUpperCase().search(RegExp(a.toUpperCase()))>-1};C.glob=function(e,t){for(var r=0,n="^";r<t.length;){var a=t[r],s="";r<t.length-1&&(s=t[r+1]),"["===a&&"^"===s?(n+="[^",r++):n+="["===a||"]"===a?a:"*"===a?".*":"?"===a?".":"/.*+?|(){}".indexOf(a)>-1?"\\"+a:a,r++}return n+="$",(""+(e||"")).toUpperCase().search(RegExp(n.toUpperCase()))>-1},C.findAlaSQLPath=function(){if(C.isWebWorker)return"";if(C.isMeteorClient)return"/packages/dist/";if(C.isMeteorServer)return"assets/packages/dist/";if(C.isNode)return"";if(C.isBrowser)for(var e=document.getElementsByTagName("script"),t=0;t<e.length;t++){if("alasql-worker.js"===e[t].src.substr(-16).toLowerCase())return e[t].src.substr(0,e[t].src.length-16);if("alasql-worker.min.js"===e[t].src.substr(-20).toLowerCase())return e[t].src.substr(0,e[t].src.length-20);if("alasql.js"===e[t].src.substr(-9).toLowerCase())return e[t].src.substr(0,e[t].src.length-9);if("alasql.min.js"===e[t].src.substr(-13).toLowerCase())return e[t].src.substr(0,e[t].src.length-13)}return""};var B=function(){var e=null;if(C.isNode||C.isBrowserify||C.isMeteorServer||(e=C.global.XLSX||null),null===e)throw new Error("Please include the xlsx.js library");return e},j=function(){return B()};A.path=A.utils.findAlaSQLPath(),A.utils.uncomment=function(e){e=("__"+e+"__").split("");for(var t,r=!1,n=!1,a=!1,s=0,i=e.length;s<i;s++){var o="\\"!==e[s-1]||"\\"===e[s-2];r?e[s]===t&&o&&(r=!1):n?"*"===e[s]&&"/"===e[s+1]?(e[s]=e[s+1]="",n=!1,s++):e[s]="":a?("\n"!==e[s+1]&&"\r"!==e[s+1]||(a=!1),e[s]=""):'"'===e[s]||"'"===e[s]?(r=!0,t=e[s]):"["===e[s]&&"@"!==e[s-1]?(r=!0,t="]"):"/"===e[s]&&"*"===e[s+1]&&(e[s]="",n=!0)}return e=e.join("").slice(2,-2)},A.parser=N,A.parser.parseError=function(e,t){throw new Error("Have you used a reserved keyword without `escaping` it?\n"+e)},A.parse=function(e){return N.parse(A.utils.uncomment(e))},A.engines={},A.databases={},A.databasenum=0,A.options={},A.options.errorlog=!1,A.options.valueof=!1,A.options.dropifnotexists=!1,A.options.datetimeformat="sql",A.options.casesensitive=!0,A.options.logtarget="output",A.options.logprompt=!0,A.options.progress=!1,A.options.modifier=void 0,A.options.columnlookup=10,A.options.autovertex=!0,A.options.usedbo=!0,A.options.autocommit=!0,A.options.cache=!0,A.options.tsql=!0,A.options.mysql=!0,A.options.postgres=!0,A.options.oracle=!0,A.options.sqlite=!0,A.options.orientdb=!0,A.options.nocount=!1,A.options.nan=!1,A.options.joinstar="overwrite",A.vars={},A.declares={},A.prompthistory=[],A.plugins={},A.from={},A.into={},A.fn={},A.aggr={},A.busy=0,A.MAXSQLCACHESIZE=1e4,A.DEFAULTDATABASEID="alasql",A.lastid=0,A.buffer={},A.use=function(e){if(e||(e=A.DEFAULTDATABASEID),A.useid!==e){A.useid=e;var t=A.databases[A.useid];A.tables=t.tables,t.resetSqlCache(),A.options.usedbo&&(A.databases.dbo=t)}},A.autoval=function(e,t,r,n){var a=n?A.databases[n]:A.databases[A.useid];if(!a.tables[e])throw new Error("Tablename not found: "+e);if(!a.tables[e].identities[t])throw new Error("Colname not found: "+t);return r?a.tables[e].identities[t].value||null:a.tables[e].identities[t].value-a.tables[e].identities[t].step||null},A.exec=function(e,t,r,n){if("function"==typeof t&&(n=r,r=t,t={}),delete A.error,t=t||{},!A.options.errorlog)return A.dexec(A.useid,e,t,r,n);try{return A.dexec(A.useid,e,t,r,n)}catch(e){A.error=e,r&&r(null,A.error)}},A.dexec=function(e,t,r,n,a){var s,i=A.databases[e];if(A.options.cache){s=D(t);var o=i.sqlCache[s];if(o&&i.dbversion===o.dbversion)return o(r,n)}var u=A.parse(t);if(u.statements){if(0===u.statements.length)return 0;if(1===u.statements.length){if(u.statements[0].compile){var o=u.statements[0].compile(e,r);if(!o)return;o.sql=t,o.dbversion=i.dbversion,A.options.cache&&(i.sqlCacheSize>A.MAXSQLCACHESIZE&&i.resetSqlCache(),i.sqlCacheSize++,i.sqlCache[s]=o);var c=A.res=o(r,n,a);return c}A.precompile(u.statements[0],A.useid,r);var c=A.res=u.statements[0].execute(e,r,n,a);return c}if(!n)return A.drun(e,u,r,n,a);A.adrun(e,u,r,n,a)}},A.drun=function(e,t,r,n,a){var s=A.useid;s!==e&&A.use(e);for(var i=[],o=0,u=t.statements.length;o<u;o++)if(t.statements[o])if(t.statements[o].compile){var c=t.statements[o].compile(A.useid);i.push(A.res=c(r,null,a))}else A.precompile(t.statements[o],A.useid,r),i.push(A.res=t.statements[o].execute(A.useid,r));return s!==e&&A.use(s),n&&n(i),A.res=i,i},A.adrun=function(e,t,r,n,a){function s(l){void 0!==l&&c.push(l);var h=t.statements.shift();if(h)if(h.compile){var d=h.compile(A.useid);d(r,s,a),A.options.progress!==!1&&A.options.progress(o,i++)}else A.precompile(t.statements[0],A.useid,r),h.execute(A.useid,r,s),A.options.progress!==!1&&A.options.progress(o,i++);else u!==e&&A.use(u),n(c)}var i=0,o=t.statements.length;A.options.progress!==!1&&A.options.progress(o,i++);var u=A.useid;u!==e&&A.use(e);var c=[];s()},A.compile=function(e,t){t=t||A.useid;var r=A.parse(e);if(1===r.statements.length){var n=r.statements[0].compile(t);return n.promise=function(e){return new Promise(function(t,r){n(e,function(e,n){n?r(n):t(e)})})},n}throw new Error("Cannot compile, because number of statements in SQL is not equal to 1")},C.global.Promise||C.isNode||function(){"use strict";function e(e){return"function"==typeof e||"object"==typeof e&&null!==e}function t(e){return"function"==typeof e}function r(e){G=e}function n(e){H=e}function a(){return function(){q(i)}}function s(){return function(){setTimeout(i,1)}}function i(){for(var e=0;j>e;e+=2){(0,Q[e])(Q[e+1]),Q[e]=void 0,Q[e+1]=void 0}j=0}function o(e,t){var r=this,n=new this.constructor(c);void 0===n[ee]&&I(n);var a=r._state;if(a){var s=arguments[a-1];H(function(){R(a,n,s,r._result)})}else T(r,n,e,t);return n}function u(e){var t=this;if(e&&"object"==typeof e&&e.constructor===t)return e;var r=new t(c);return g(r,e),r}function c(){}function l(){return new TypeError("You cannot resolve a promise with itself")}function h(){return new TypeError("A promises callback cannot return that same promise.")}function d(e){try{return e.then}catch(e){return ae.error=e,ae}}function f(e,t,r,n){try{e.call(t,r,n)}catch(e){return e}}function p(e,t,r){H(function(e){var n=!1,a=f(r,t,function(r){n||(n=!0,t!==r?g(e,r):v(e,r))},function(t){n||(n=!0,S(e,t))},"Settle: "+(e._label||" unknown promise"));!n&&a&&(n=!0,S(e,a))},e)}function b(e,t){t._state===re?v(e,t._result):t._state===ne?S(e,t._result):T(t,void 0,function(t){g(e,t)},function(t){S(e,t)})}function E(e,r,n){r.constructor===e.constructor&&n===z&&constructor.resolve===Z?b(e,r):n===ae?S(e,ae.error):void 0===n?v(e,r):t(n)?p(e,r,n):v(e,r)}function g(t,r){t===r?S(t,l()):e(r)?E(t,r,d(r)):v(t,r)}function m(e){e._onerror&&e._onerror(e._result),A(e)}function v(e,t){e._state===te&&(e._result=t,e._state=re,0!==e._subscribers.length&&H(A,e))}function S(e,t){e._state===te&&(e._state=ne,e._result=t,H(m,e))}function T(e,t,r,n){var a=e._subscribers,s=a.length;e._onerror=null,a[s]=t,a[s+re]=r,a[s+ne]=n,0===s&&e._state&&H(A,e)}function A(e){var t=e._subscribers,r=e._state;if(0!==t.length){for(var n,a,s=e._result,i=0;i<t.length;i+=3)n=t[i],a=t[i+r],n?R(r,n,a,s):a(s);e._subscribers.length=0}}function N(){this.error=null}function C(e,t){try{return e(t)}catch(e){return se.error=e,se}}function R(e,r,n,a){var s,i,o,u,c=t(n);if(c){if(s=C(n,a),s===se?(u=!0,i=s.error,s=null):o=!0,r===s)return void S(r,h())}else s=a,o=!0;r._state!==te||(c&&o?g(r,s):u?S(r,i):e===re?v(r,s):e===ne&&S(r,s))}function O(e,t){try{t(function(t){g(e,t)},function(t){S(e,t)})}catch(t){S(e,t)}}function w(){return ie++}function I(e){e[ee]=ie++,e._state=void 0,e._result=void 0,e._subscribers=[]}function x(e){return new he(this,e).promise}function D(e){var t=this;return new t(B(e)?function(r,n){for(var a=e.length,s=0;a>s;s++)t.resolve(e[s]).then(r,n)}:function(e,t){t(new TypeError("You must pass an array to race."))})}function k(e){var t=this,r=new t(c);return S(r,e),r}function L(){throw new TypeError("You must pass a resolver function as the first argument to the promise constructor")}function $(){throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.")}function M(e){this[ee]=w(),this._result=this._state=void 0,this._subscribers=[],c!==e&&("function"!=typeof e&&L(),this instanceof M?O(this,e):$())}function U(e,t){this._instanceConstructor=e,this.promise=new e(c),this.promise[ee]||I(this.promise),Array.isArray(t)?(this._input=t,this.length=t.length,this._remaining=t.length,this._result=new Array(this.length),0===this.length?v(this.promise,this._result):(this.length=this.length||0,this._enumerate(),0===this._remaining&&v(this.promise,this._result))):S(this.promise,_())}function _(){return new Error("Array Methods must be provided an Array")}function F(){var e;if("undefined"!=typeof global)e=global;else if("undefined"!=typeof self)e=self;else try{e=Function("return this")()}catch(e){throw new Error("polyfill failed because global object is unavailable in this environment")}var t=e.Promise;(!t||"[object Promise]"!==Object.prototype.toString.call(t.resolve())||t.cast)&&(e.Promise=le)}var P;P=Array.isArray?Array.isArray:function(e){return"[object Array]"===Object.prototype.toString.call(e)};var q,G,V,B=P,j=0,H=function(e,t){Q[j]=e,Q[j+1]=t,2===(j+=2)&&(G?G(i):V())},J="undefined"!=typeof window?window:void 0,Y=J||{},W=Y.MutationObserver||Y.WebKitMutationObserver,X="undefined"==typeof self&&"undefined"!=typeof process&&"[object process]"==={}.toString.call(process),K="undefined"!=typeof Uint8ClampedArray&&"undefined"!=typeof importScripts&&"undefined"!=typeof MessageChannel,Q=new Array(1e3);V=X?function(){return function(){process.nextTick(i)}}():W?function(){var e=0,t=new W(i),r=document.createTextNode("");return t.observe(r,{characterData:!0}),function(){r.data=e=++e%2}}():K?function(){var e=new MessageChannel;return e.port1.onmessage=i,function(){e.port2.postMessage(0)}}():void 0===J&&"function"==typeof y?function(){try{var e=y,t=e("vertx");return q=t.runOnLoop||t.runOnContext,a()}catch(e){return s()}}():s();var z=o,Z=u,ee=Math.random().toString(36).substring(16),te=void 0,re=1,ne=2,ae=new N,se=new N,ie=0,oe=x,ue=D,ce=k,le=M;M.all=oe,M.race=ue,M.resolve=Z,M.reject=ce,M._setScheduler=r,M._setAsap=n,M._asap=H,M.prototype={constructor:M,then:z,catch:function(e){return this.then(null,e)}};var he=U;U.prototype._enumerate=function(){for(var e=this.length,t=this._input,r=0;this._state===te&&e>r;r++)this._eachEntry(t[r],r)},U.prototype._eachEntry=function(e,t){var r=this._instanceConstructor,n=r.resolve;if(n===Z){var a=d(e);if(a===z&&e._state!==te)this._settledAt(e._state,t,e._result);else if("function"!=typeof a)this._remaining--,this._result[t]=e;else if(r===le){var s=new r(c);E(s,e,a),this._willSettleAt(s,t)}else this._willSettleAt(new r(function(t){t(e)}),t)}else this._willSettleAt(n(e),t)},U.prototype._settledAt=function(e,t,r){var n=this.promise;n._state===te&&(this._remaining--,e===ne?S(n,r):this._result[t]=r),0===this._remaining&&v(n,this._result)},U.prototype._willSettleAt=function(e,t){var r=this;T(e,void 0,function(e){r._settledAt(re,t,e)},function(e){r._settledAt(ne,t,e)})};var de=F,fe={Promise:le,polyfill:de};"function"==typeof define&&define.amd?define(function(){return fe}):"undefined"!=typeof module&&module.exports?module.exports=fe:void 0!==this&&(this.ES6Promise=fe),de()}.call(this);var H=function(e,t,r,n){return new C.global.Promise(function(a,s){A(e,t,function(e,t){t?s(t):(r&&n&&A.options.progress!==!1&&A.options.progress(r,n),a(e))})})},J=function(e){if(!(e.length<1)){for(var t,r,n,a=[],s=0;s<e.length;s++){if(t=e[s],"string"==typeof t&&(t=[t]),!C.isArray(t)||t.length<1||2<t.length)throw new Error("Error in .promise parameter");r=t[0],n=t[1]||void 0,a.push(H(r,n,s,e.length))}return C.global.Promise.all(a)}};A.promise=function(e,t){if("undefined"==typeof Promise)throw new Error("Please include a Promise/A+ library");if("string"==typeof e)return H(e,t);if(!C.isArray(e)||e.length<1||void 0!==t)throw new Error("Error in .promise parameters");return J(e)};var Y=A.Database=function(e){var t=this;if(t===A)if(e){if(t=A.databases[e],A.databases[e]=t,!t)throw new Error('Database "'+e+'" not found')}else t=A.databases.alasql,A.options.tsql&&(A.databases.tempdb=A.databases.alasql);return e||(e="db"+A.databasenum++),t.databaseid=e,A.databases[e]=t,t.dbversion=0,t.tables={},t.views={},t.triggers={},t.indices={},t.objects={},t.counter=0,t.resetSqlCache(),t};Y.prototype.resetSqlCache=function(){this.sqlCache={},this.sqlCacheSize=0},Y.prototype.exec=function(e,t,r){return A.dexec(this.databaseid,e,t,r)},Y.prototype.autoval=function(e,t,r){return A.autoval(e,t,r,this.databaseid)},Y.prototype.transaction=function(e){return e(new A.Transaction(this.databaseid))};var W=A.Transaction=function(e){return this.transactionid=Date.now(),this.databaseid=e,this.commited=!1,this.dbversion=A.databases[e].dbversion,this.bank=JSON.stringify(A.databases[e]),this};W.prototype.commit=function(){this.commited=!0,A.databases[this.databaseid].dbversion=Date.now(),delete this.bank},W.prototype.rollback=function(){if(this.commited)throw new Error("Transaction already commited");A.databases[this.databaseid]=JSON.parse(this.bank),delete this.bank},W.prototype.exec=function(e,t,r){return A.dexec(this.databaseid,e,t,r)},W.prototype.executeSQL=W.prototype.exec;var X=A.Table=function(e){this.data=[],this.columns=[],this.xcolumns={},this.inddefs={},this.indices={},this.uniqs={},this.uniqdefs={},this.identities={},this.checks=[],this.checkfns=[],this.beforeinsert={},this.afterinsert={},this.insteadofinsert={},this.beforedelete={},this.afterdelete={},this.insteadofdelete={},this.beforeupdate={},this.afterupdate={},this.insteadofupdate={},G(this,e)};X.prototype.indexColumns=function(){var e=this;e.xcolumns={},e.columns.forEach(function(t){e.xcolumns[t.columnid]=t})};var K=(A.View=function(e){this.columns=[],this.xcolumns={},this.query=[],G(this,e)},A.Query=function(e){this.alasql=A,this.columns=[],this.xcolumns={},this.selectGroup=[],this.groupColumns={},G(this,e)}),Q=(A.Recordset=function(e){G(this,e)},N.yy=A.yy={});Q.extend=G,Q.casesensitive=A.options.casesensitive;var z=Q.Base=function(e){return Q.extend(this,e)};z.prototype.toString=function(){},z.prototype.toType=function(){},z.prototype.toJS=function(){},z.prototype.compile=n,z.prototype.exec=function(){},z.prototype.compile=n,z.prototype.exec=function(){},Q.Statements=function(e){return Q.extend(this,e)},Q.Statements.prototype.toString=function(){return this.statements.map(function(e){return e.toString()}).join("; ")},Q.Statements.prototype.compile=function(e){var t=this.statements.map(function(t){return t.compile(e)});return 1===t.length?t[0]:function(e,r){var n=t.map(function(t){return t(e)});return r&&r(n),n}},Q.Search=function(e){return Q.extend(this,e)},Q.Search.prototype.toString=function(){var e="SEARCH ";return this.selectors&&(e+=this.selectors.toString()),this.from&&(e+="FROM "+this.from.toString()),e},Q.Search.prototype.toJS=function(e){return"this.queriesfn["+(this.queriesidx-1)+"](this.params,null,"+e+")"},Q.Search.prototype.compile=function(e){var t=e,r=this,n=function(e,a){var s;return i.bind(r)(t,e,function(e){s=f(n.query,e),a&&(s=a(s))}),s};return n.query={},n},A.srch={},A.srch.PROP=function(e,t,r){if("XML"===r.mode){var n=[];return e.children.forEach(function(e){e.name.toUpperCase()===t[0].toUpperCase()&&n.push(e)}),n.length>0?{status:1,values:n}:{status:-1,values:[]}}return"object"!=typeof e||null===e||"object"!=typeof t||void 0===e[t[0]]?{status:-1,values:[]}:{status:1,values:[e[t[0]]]}},A.srch.APROP=function(e,t){return"object"!=typeof e||null===e||"object"!=typeof t||void 0===e[t[0]]?{status:1,values:[void 0]}:{status:1,values:[e[t[0]]]}},A.srch.EQ=function(e,t,r,n){var a=t[0].toJS("x","");return e===new Function("x,alasql,params","return "+a)(e,A,n)?{status:1,values:[e]}:{status:-1,values:[]}},A.srch.LIKE=function(e,t,r,n){var a=t[0].toJS("x",""),s=new Function("x,alasql,params","return "+a);return e.toUpperCase().match(new RegExp("^"+s(e,A,n).toUpperCase().replace(/%/g,".*").replace(/\?|_/g,".")+"$"),"g")?{status:1,values:[e]}:{status:-1,values:[]}},A.srch.ATTR=function(e,t,r){if("XML"===r.mode)return void 0===t?{status:1,values:[e.attributes]}:"object"==typeof e&&"object"==typeof e.attributes&&void 0!==e.attributes[t[0]]?{status:1,values:[e.attributes[t[0]]]}:{status:-1,values:[]};throw new Error("ATTR is not using in usual mode")},A.srch.CONTENT=function(e,t,r){if("XML"===r.mode)return{status:1,values:[e.content]};throw new Error("ATTR is not using in usual mode")},A.srch.SHARP=function(e,t){var r=A.databases[A.useid].objects[t[0]];return void 0!==e&&e===r?{status:1,values:[e]}:{status:-1,values:[]}},A.srch.PARENT=function(){return console.log("PARENT not implemented",arguments),{status:-1,values:[]}},A.srch.CHILD=function(e,t,r){return"object"==typeof e?Array.isArray(e)?{status:1,values:e}:"XML"===r.mode?{status:1,values:Object.keys(e.children).map(function(t){return e.children[t]})}:{status:1,values:Object.keys(e).map(function(t){return e[t]})}:{status:1,values:[]}},A.srch.KEYS=function(e){return"object"==typeof e&&null!==e?{status:1,values:Object.keys(e)}:{status:1,values:[]}},A.srch.WHERE=function(e,t,r,n){var a=t[0].toJS("x","");return new Function("x,alasql,params","return "+a)(e,A,n)?{status:1,values:[e]}:{status:-1,values:[]}},A.srch.NAME=function(e,t){return e.name===t[0]?{status:1,values:[e]}:{status:-1,values:[]}},A.srch.CLASS=function(e,t){return e.$class==t?{status:1,values:[e]}:{status:-1,values:[]}},A.srch.VERTEX=function(e){return"VERTEX"===e.$node?{status:1,values:[e]}:{status:-1,values:[]}},A.srch.INSTANCEOF=function(e,t){return e instanceof A.fn[t[0]]?{status:1,values:[e]}:{status:-1,values:[]}},A.srch.EDGE=function(e){return"EDGE"===e.$node?{status:1,values:[e]}:{status:-1,values:[]}},A.srch.EX=function(e,t,r,n){var a=t[0].toJS("x","");return{status:1,values:[new Function("x,alasql,params","return "+a)(e,A,n)]}},A.srch.RETURN=function(e,t,r,n){var a={};return t&&t.length>0&&t.forEach(function(t){var r=t.toJS("x",""),s=new Function("x,alasql,params","return "+r);void 0===t.as&&(t.as=t.toString()),a[t.as]=s(e,A,n)}),{status:1,values:[a]}},A.srch.REF=function(e){return{status:1,values:[A.databases[A.useid].objects[e]]}},A.srch.OUT=function(e){if(e.$out&&e.$out.length>0){return{status:1,values:e.$out.map(function(e){return A.databases[A.useid].objects[e]})}}return{status:-1,values:[]}},A.srch.OUTOUT=function(e){if(e.$out&&e.$out.length>0){var t=[];return e.$out.forEach(function(e){var r=A.databases[A.useid].objects[e];r&&r.$out&&r.$out.length>0&&r.$out.forEach(function(e){t=t.concat(A.databases[A.useid].objects[e])})}),{status:1,values:t}}return{status:-1,values:[]}},A.srch.IN=function(e){if(e.$in&&e.$in.length>0){return{status:1,values:e.$in.map(function(e){return A.databases[A.useid].objects[e]})}}return{status:-1,values:[]}},A.srch.ININ=function(e){if(e.$in&&e.$in.length>0){var t=[];return e.$in.forEach(function(e){var r=A.databases[A.useid].objects[e];r&&r.$in&&r.$in.length>0&&r.$in.forEach(function(e){t=t.concat(A.databases[A.useid].objects[e])})}),{status:1,values:t}}return{status:-1,values:[]}},A.srch.AS=function(e,t){return A.vars[t[0]]=e,{status:1,values:[e]}},A.srch.AT=function(e,t){return{status:1,values:[A.vars[t[0]]]}},A.srch.CLONEDEEP=function(e){return{status:1,values:[F(e)]}},
A.srch.SET=function(e,t,r,n){var a=t.map(function(e){return"@"===e.method?"alasql.vars['"+e.variable+"']="+e.expression.toJS("x",""):"$"===e.method?"params['"+e.variable+"']="+e.expression.toJS("x",""):"x['"+e.column.columnid+"']="+e.expression.toJS("x","")}).join(";");return new Function("x,params,alasql",a)(e,n,A),{status:1,values:[e]}},A.srch.ROW=function(e,t,r,n){var a="var y;return [";return a+=t.map(function(e){return e.toJS("x","")}).join(","),a+="]",{status:1,values:[new Function("x,params,alasql",a)(e,n,A)]}},A.srch.D3=function(e){return"VERTEX"!==e.$node&&"EDGE"===e.$node&&(e.source=e.$in[0],e.target=e.$out[0]),{status:1,values:[e]}};var Z=function(e){if(e){if(e&&1===e.length&&e[0].expression&&"function"==typeof e[0].expression){var t=e[0].expression;return function(e,r){var n=t(e),a=t(r);return n>a?1:n===a?0:-1}}var r="",n="";return e.forEach(function(e){var t="";if(e.expression instanceof Q.NumValue&&(e.expression=self.columns[e.expression.value-1]),e.expression instanceof Q.Column){var a=e.expression.columnid;A.options.valueof&&(t=".valueOf()"),e.nocase&&(t+=".toUpperCase()"),"_"===a?(r+="if(a"+t+("ASC"===e.direction?">":"<")+"b"+t+")return 1;",r+="if(a"+t+"==b"+t+"){"):(r+="if((a['"+a+"']||'')"+t+("ASC"===e.direction?">":"<")+"(b['"+a+"']||'')"+t+")return 1;",r+="if((a['"+a+"']||'')"+t+"==(b['"+a+"']||'')"+t+"){")}else t=".valueOf()",e.nocase&&(t+=".toUpperCase()"),r+="if(("+e.toJS("a","")+"||'')"+t+("ASC"===e.direction?">(":"<(")+e.toJS("b","")+"||'')"+t+")return 1;",r+="if(("+e.toJS("a","")+"||'')"+t+"==("+e.toJS("b","")+"||'')"+t+"){";n+="}"}),r+="return 0;",r+=n+"return -1",new Function("a,b",r)}};A.srch.ORDERBY=function(e,t){return{status:1,values:e.sort(Z(t))}};var ee=function(e){for(var t=0,r=e.sources.length;t<r;t++){var n=e.sources[t];if(delete n.ix,t>0&&"ix"==n.optimization&&n.onleftfn&&n.onrightfn){if(n.databaseid&&A.databases[n.databaseid].tables[n.tableid]){A.databases[n.databaseid].tables[n.tableid].indices||(e.database.tables[n.tableid].indices={});var a=A.databases[n.databaseid].tables[n.tableid].indices[D(n.onrightfns+"`"+n.srcwherefns)];!A.databases[n.databaseid].tables[n.tableid].dirty&&a&&(n.ix=a)}if(!n.ix){n.ix={};for(var s,i={},o=0,u=n.data.length;(s=n.data[o])||n.getfn&&(s=n.getfn(o))||o<u;){if(n.getfn&&!n.dontcache&&(n.data[o]=s),i[n.alias||n.tableid]=s,n.srcwherefn(i,e.params,A)){var c=n.onrightfn(i,e.params,A),l=n.ix[c];l||(l=n.ix[c]=[]),l.push(s)}o++}n.databaseid&&A.databases[n.databaseid].tables[n.tableid]&&(A.databases[n.databaseid].tables[n.tableid].indices[D(n.onrightfns+"`"+n.srcwherefns)]=n.ix)}}else if(n.wxleftfn){if(A.databases[n.databaseid].engineid||(a=A.databases[n.databaseid].tables[n.tableid].indices[D(n.wxleftfns+"`")]),!A.databases[n.databaseid].tables[n.tableid].dirty&&a)n.ix=a,n.data=n.ix[n.wxrightfn(null,e.params,A)];else{for(n.ix={},i={},o=0,u=n.data.length;(s=n.data[o])||n.getfn&&(s=n.getfn(o))||o<u;)n.getfn&&!n.dontcache&&(n.data[o]=s),i[n.alias||n.tableid]=n.data[o],c=n.wxleftfn(i,e.params,A),l=n.ix[c],l||(l=n.ix[c]=[]),l.push(n.data[o]),o++;A.databases[n.databaseid].engineid||(A.databases[n.databaseid].tables[n.tableid].indices[D(n.wxleftfns+"`")]=n.ix)}n.srcwherefns&&(n.data?(i={},n.data=n.data.filter(function(t){return i[n.alias]=t,n.srcwherefn(i,e.params,A)})):n.data=[])}else if(n.srcwherefns&&!n.dontcache)if(n.data){var i={};n.data=n.data.filter(function(t){return i[n.alias]=t,n.srcwherefn(i,e.params,A)}),i={},o=0,u=n.data.length;for(var h=[];(s=n.data[o])||n.getfn&&(s=n.getfn(o))||o<u;)n.getfn&&!n.dontcache&&(n.data[o]=s),i[n.alias]=s,n.srcwherefn(i,e.params,A)&&h.push(s),o++;n.data=h}else n.data=[];n.databaseid&&A.databases[n.databaseid].tables[n.tableid]}};Q.Select=function(e){return Q.extend(this,e)},Q.Select.prototype.toString=function(){var e;return e="",this.explain&&(e+="EXPLAIN "),e+="SELECT ",this.modifier&&(e+=this.modifier+" "),this.distinct&&(e+="DISTINCT "),this.top&&(e+="TOP "+this.top.value+" ",this.percent&&(e+="PERCENT ")),e+=this.columns.map(function(e){var t;return t=e.toString(),void 0!==e.as&&(t+=" AS "+e.as),t}).join(", "),this.from&&(e+=" FROM "+this.from.map(function(e){var t;return t=e.toString(),e.as&&(t+=" AS "+e.as),t}).join(",")),this.joins&&(e+=this.joins.map(function(e){var t;if(t=" ",e.joinmode&&(t+=e.joinmode+" "),e.table)t+="JOIN "+e.table.toString();else if(e.select)t+="JOIN ("+e.select.toString()+")";else{if(!(e instanceof A.yy.Apply))throw new Error("Wrong type in JOIN mode");t+=e.toString()}return e.as&&(t+=" AS "+e.as),e.using&&(t+=" USING "+e.using.toString()),e.on&&(t+=" ON "+e.on.toString()),t})),this.where&&(e+=" WHERE "+this.where.toString()),this.group&&this.group.length>0&&(e+=" GROUP BY "+this.group.map(function(e){return e.toString()}).join(", ")),this.having&&(e+=" HAVING "+this.having.toString()),this.order&&this.order.length>0&&(e+=" ORDER BY "+this.order.map(function(e){return e.toString()}).join(", ")),this.limit&&(e+=" LIMIT "+this.limit.value),this.offset&&(e+=" OFFSET "+this.offset.value),this.union&&(e+=" UNION "+(this.corresponding?"CORRESPONDING ":"")+this.union.toString()),this.unionall&&(e+=" UNION ALL "+(this.corresponding?"CORRESPONDING ":"")+this.unionall.toString()),this.except&&(e+=" EXCEPT "+(this.corresponding?"CORRESPONDING ":"")+this.except.toString()),this.intersect&&(e+=" INTERSECT "+(this.corresponding?"CORRESPONDING ":"")+this.intersect.toString()),e},Q.Select.prototype.toJS=function(e){return"alasql.utils.flatArray(this.queriesfn["+(this.queriesidx-1)+"](this.params,null,"+e+"))[0]"},Q.Select.prototype.compile=function(e,t){var r=A.databases[e],n=new K;if(n.removeKeys=[],n.aggrKeys=[],n.explain=this.explain,n.explaination=[],n.explid=1,n.modifier=this.modifier,n.database=r,this.compileWhereExists(n),this.compileQueries(n),n.defcols=this.compileDefCols(n,e),n.fromfn=this.compileFrom(n),this.joins&&this.compileJoins(n),n.rownums=[],this.compileSelectGroup0(n),this.group||n.selectGroup.length>0?n.selectgfns=this.compileSelectGroup1(n):n.selectfns=this.compileSelect1(n,t),this.compileRemoveColumns(n),this.where&&this.compileWhereJoins(n),n.wherefn=this.compileWhere(n),(this.group||n.selectGroup.length>0)&&(n.groupfn=this.compileGroup(n)),this.having&&(n.havingfn=this.compileHaving(n)),this.order&&(n.orderfn=this.compileOrder(n)),this.group||n.selectGroup.length>0?n.selectgfn=this.compileSelectGroup2(n):n.selectfn=this.compileSelect2(n),n.distinct=this.distinct,this.pivot&&(n.pivotfn=this.compilePivot(n)),this.unpivot&&(n.pivotfn=this.compileUnpivot(n)),this.top?n.limit=this.top.value:this.limit&&(n.limit=this.limit.value,this.offset&&(n.offset=this.offset.value)),n.percent=this.percent,n.corresponding=this.corresponding,this.union?(n.unionfn=this.union.compile(e),this.union.order?n.orderfn=this.union.compileOrder(n):n.orderfn=null):this.unionall?(n.unionallfn=this.unionall.compile(e),this.unionall.order?n.orderfn=this.unionall.compileOrder(n):n.orderfn=null):this.except?(n.exceptfn=this.except.compile(e),this.except.order?n.orderfn=this.except.compileOrder(n):n.orderfn=null):this.intersect&&(n.intersectfn=this.intersect.compile(e),this.intersect.order?n.intersectfn=this.intersect.compileOrder(n):n.orderfn=null),this.into){if(this.into instanceof Q.Table)A.options.autocommit&&A.databases[this.into.databaseid||e].engineid?n.intoallfns='return alasql.engines["'+A.databases[this.into.databaseid||e].engineid+'"].intoTable("'+(this.into.databaseid||e)+'","'+this.into.tableid+'",this.data, columns, cb);':n.intofns="alasql.databases['"+(this.into.databaseid||e)+"'].tables['"+this.into.tableid+"'].data.push(r);";else if(this.into instanceof Q.VarValue)n.intoallfns='alasql.vars["'+this.into.variable+'"]=this.data;res=this.data.length;if(cb)res=cb(res);return res;';else if(this.into instanceof Q.FuncValue){var a="return alasql.into['"+this.into.funcid.toUpperCase()+"'](";this.into.args&&this.into.args.length>0?(a+=this.into.args[0].toJS()+",",a+=this.into.args.length>1?this.into.args[1].toJS()+",":"undefined,"):a+="undefined, undefined,",n.intoallfns=a+"this.data,columns,cb)"}else this.into instanceof Q.ParamValue&&(n.intofns="params['"+this.into.param+"'].push(r)");n.intofns?n.intofn=new Function("r,i,params,alasql","var y;"+n.intofns):n.intoallfns&&(n.intoallfn=new Function("columns,cb,params,alasql","var y;"+n.intoallfns))}var s=function(e,t,r){return n.params=e,o(n,r,function(e){if(n.rownums.length>0)for(var r=0,a=e.length;r<a;r++)for(var s=0,i=n.rownums.length;s<i;s++)e[r][n.rownums[s]]=r+1;var o=f(n,e);return t&&t(o),o})};return s.query=n,s},Q.Select.prototype.execute=function(e,t,r){return this.compile(e)(t,r)},Q.ExistsValue=function(e){return Q.extend(this,e)},Q.ExistsValue.prototype.toString=function(){return"EXISTS("+this.value.toString()+")"},Q.ExistsValue.prototype.toType=function(){return"boolean"},Q.ExistsValue.prototype.toJS=function(e,t,r){return"this.existsfn["+this.existsidx+"](params,null,"+e+").data.length"},Q.Select.prototype.compileWhereExists=function(e){this.exists&&(e.existsfn=this.exists.map(function(t){var r=t.compile(e.database.databaseid);return r.query.modifier="RECORDSET",r}))},Q.Select.prototype.compileQueries=function(e){this.queries&&(e.queriesfn=this.queries.map(function(t){var r=t.compile(e.database.databaseid);return r.query.modifier="RECORDSET",r}))},A.precompile=function(e,t,r){e&&(e.params=r,e.queries&&(e.queriesfn=e.queries.map(function(r){var n=r.compile(t||e.database.databaseid);return n.query.modifier="RECORDSET",n})),e.exists&&(e.existsfn=e.exists.map(function(r){var n=r.compile(t||e.database.databaseid);return n.query.modifier="RECORDSET",n})))},Q.Select.prototype.compileFrom=function(e){var t=this;e.sources=[],e.aliases={},t.from&&(t.from.forEach(function(t){var n=t.as||t.tableid;if(t instanceof Q.Table)e.aliases[n]={tableid:t.tableid,databaseid:t.databaseid||e.database.databaseid,type:"table"};else if(t instanceof Q.Select)e.aliases[n]={type:"subquery"};else if(t instanceof Q.Search)e.aliases[n]={type:"subsearch"};else if(t instanceof Q.ParamValue)e.aliases[n]={type:"paramvalue"};else if(t instanceof Q.FuncValue)e.aliases[n]={type:"funcvalue"};else if(t instanceof Q.VarValue)e.aliases[n]={type:"varvalue"};else if(t instanceof Q.FromData)e.aliases[n]={type:"fromdata"};else if(t instanceof Q.Json)e.aliases[n]={type:"json"};else{if(!t.inserted)throw new Error("Wrong table at FROM");e.aliases[n]={type:"inserted"}}var a={alias:n,databaseid:t.databaseid||e.database.databaseid,tableid:t.tableid,joinmode:"INNER",onmiddlefn:r,srcwherefns:"",srcwherefn:r};if(t instanceof Q.Table)a.columns=A.databases[a.databaseid].tables[a.tableid].columns,A.options.autocommit&&A.databases[a.databaseid].engineid&&!A.databases[a.databaseid].tables[a.tableid].view?a.datafn=function(e,t,r,n,s){return s.engines[s.databases[a.databaseid].engineid].fromTable(a.databaseid,a.tableid,r,n,e)}:A.databases[a.databaseid].tables[a.tableid].view?a.datafn=function(e,t,r,n,s){var i=s.databases[a.databaseid].tables[a.tableid].select(t);return r&&(i=r(i,n,e)),i}:a.datafn=function(e,t,r,n,s){var i=s.databases[a.databaseid].tables[a.tableid].data;return r&&(i=r(i,n,e)),i};else if(t instanceof Q.Select)a.subquery=t.compile(e.database.databaseid),void 0===a.subquery.query.modifier&&(a.subquery.query.modifier="RECORDSET"),a.columns=a.subquery.query.columns,a.datafn=function(e,t,r,n,s){var i;return a.subquery(e.params,function(t){return i=t.data,r&&(i=r(i,n,e)),i}),i};else if(t instanceof Q.Search)a.subsearch=t,a.columns=[],a.datafn=function(e,t,r,n,s){var i;return a.subsearch.execute(e.database.databaseid,e.params,function(t){return i=t,r&&(i=r(i,n,e)),i}),i};else if(t instanceof Q.ParamValue){var s="var res = alasql.prepareFromData(params['"+t.param+"']";t.array&&(s+=",true"),s+=");if(cb)res=cb(res,idx,query);return res",a.datafn=new Function("query,params,cb,idx,alasql",s)}else if(t.inserted){var s="var res = alasql.prepareFromData(alasql.inserted";t.array&&(s+=",true"),s+=");if(cb)res=cb(res,idx,query);return res",a.datafn=new Function("query,params,cb,idx,alasql",s)}else if(t instanceof Q.Json){var s="var res = alasql.prepareFromData("+t.toJS();t.array&&(s+=",true"),s+=");if(cb)res=cb(res,idx,query);return res",a.datafn=new Function("query,params,cb,idx,alasql",s)}else if(t instanceof Q.VarValue){var s="var res = alasql.prepareFromData(alasql.vars['"+t.variable+"']";t.array&&(s+=",true"),s+=");if(cb)res=cb(res,idx,query);return res",a.datafn=new Function("query,params,cb,idx,alasql",s)}else if(t instanceof Q.FuncValue){var i="var res=alasql.from['"+t.funcid.toUpperCase()+"'](";t.args&&t.args.length>0?(i+=t.args[0]?t.args[0].toJS("query.oldscope")+",":"null,",i+=t.args[1]?t.args[1].toJS("query.oldscope")+",":"null,"):i+="null,null,",i+="cb,idx,query",i+=");/*if(cb)res=cb(res,idx,query);*/return res",a.datafn=new Function("query, params, cb, idx, alasql",i)}else{if(!(t instanceof Q.FromData))throw new Error("Wrong table at FROM");a.datafn=function(e,r,n,a,s){var i=t.data;return n&&(i=n(i,a,e)),i}}e.sources.push(a)}),e.defaultTableid=e.sources[0].alias)},A.prepareFromData=function(e,t){var r=e;if("string"==typeof e){if(r=e.split(/\r?\n/),t)for(var n=0,a=r.length;n<a;n++)r[n]=[r[n]]}else if(t){r=[];for(var n=0,a=e.length;n<a;n++)r.push([e[n]])}else if("object"==typeof e&&!Array.isArray(e))if("undefined"!=typeof Mongo&&void 0!==Mongo.Collection&&e instanceof Mongo.Collection)r=e.find().fetch();else{r=[];for(var s in e)e.hasOwnProperty(s)&&r.push([s,e[s]])}return r},Q.Select.prototype.compileJoins=function(e){this.joins.forEach(function(t){if("CROSS"==t.joinmode){if(t.using||t.on)throw new Error("CROSS JOIN cannot have USING or ON clauses");t.joinmode}var n,a;if(t instanceof Q.Apply)n={alias:t.as,applymode:t.applymode,onmiddlefn:r,srcwherefns:"",srcwherefn:r,columns:[]},n.applyselect=t.select.compile(e.database.databaseid),n.columns=n.applyselect.query.columns,n.datafn=function(e,t,r,n,a){var s;return r&&(s=r(s,n,e)),s},e.sources.push(n);else{if(t.table){if(a=t.table,n={alias:t.as||a.tableid,databaseid:a.databaseid||e.database.databaseid,tableid:a.tableid,joinmode:t.joinmode,onmiddlefn:r,srcwherefns:"",srcwherefn:r,columns:[]},!A.databases[n.databaseid].tables[n.tableid])throw new Error("Table '"+n.tableid+"' is not exists in database '"+n.databaseid)+"'";n.columns=A.databases[n.databaseid].tables[n.tableid].columns,A.options.autocommit&&A.databases[n.databaseid].engineid?n.datafn=function(e,t,r,a,s){return s.engines[s.databases[n.databaseid].engineid].fromTable(n.databaseid,n.tableid,r,a,e)}:A.databases[n.databaseid].tables[n.tableid].view?n.datafn=function(e,t,r,a,s){var i=s.databases[n.databaseid].tables[n.tableid].select(t);return r&&(i=r(i,a,e)),i}:n.datafn=function(e,t,r,a,s){var i=s.databases[n.databaseid].tables[n.tableid].data;return r&&(i=r(i,a,e)),i},e.aliases[n.alias]={tableid:a.tableid,databaseid:a.databaseid||e.database.databaseid}}else if(t.select){var a=t.select;n={alias:t.as,joinmode:t.joinmode,onmiddlefn:r,srcwherefns:"",srcwherefn:r,columns:[]},n.subquery=a.compile(e.database.databaseid),void 0===n.subquery.query.modifier&&(n.subquery.query.modifier="RECORDSET"),n.columns=n.subquery.query.columns,n.datafn=function(e,t,r,a,s){return n.subquery(e.params,null,r,a).data},e.aliases[n.alias]={type:"subquery"}}else if(t.param){n={alias:t.as,joinmode:t.joinmode,onmiddlefn:r,srcwherefns:"",srcwherefn:r};var s=t.param.param,i="var res=alasql.prepareFromData(params['"+s+"']";t.array&&(i+=",true"),i+=");if(cb)res=cb(res, idx, query);return res",n.datafn=new Function("query,params,cb,idx, alasql",i),e.aliases[n.alias]={type:"paramvalue"}}else if(t.variable){n={alias:t.as,joinmode:t.joinmode,onmiddlefn:r,srcwherefns:"",srcwherefn:r};var i="var res=alasql.prepareFromData(alasql.vars['"+t.variable+"']";t.array&&(i+=",true"),i+=");if(cb)res=cb(res, idx, query);return res",n.datafn=new Function("query,params,cb,idx, alasql",i),e.aliases[n.alias]={type:"varvalue"}}else if(t.funcid){n={alias:t.as,joinmode:t.joinmode,onmiddlefn:r,srcwherefns:"",srcwherefn:r};var o="var res=alasql.from['"+js.funcid.toUpperCase()+"'](";t.args&&t.args.length>0?(o+=t.args[0]?t.args[0].toJS("query.oldscope")+",":"null,",o+=t.args[1]?t.args[1].toJS("query.oldscope")+",":"null,"):o+="null,null,",o+="cb,idx,query",o+=");/*if(cb)res=cb(res,idx,query);*/return res",n.datafn=new Function("query, params, cb, idx, alasql",o),e.aliases[n.alias]={type:"funcvalue"}}var u=n.alias;if(t.natural){if(t.using||t.on)throw new Error("NATURAL JOIN cannot have USING or ON clauses");if(e.sources.length>0){var c=e.sources[e.sources.length-1],l=A.databases[c.databaseid].tables[c.tableid],h=A.databases[n.databaseid].tables[n.tableid];if(!l||!h)throw new Error("In this version of Alasql NATURAL JOIN works for tables with predefined columns only");var d=l.columns.map(function(e){return e.columnid}),f=h.columns.map(function(e){return e.columnid});t.using=$(d,f).map(function(e){return{columnid:e}})}}if(t.using){var c=e.sources[e.sources.length-1];n.onleftfns=t.using.map(function(e){return"p['"+(c.alias||c.tableid)+"']['"+e.columnid+"']"}).join('+"`"+'),n.onleftfn=new Function("p,params,alasql","var y;return "+n.onleftfns),n.onrightfns=t.using.map(function(e){return"p['"+(n.alias||n.tableid)+"']['"+e.columnid+"']"}).join('+"`"+'),n.onrightfn=new Function("p,params,alasql","var y;return "+n.onrightfns),n.optimization="ix"}else if(t.on)if(t.on instanceof Q.Op&&"="==t.on.op&&!t.on.allsome){n.optimization="ix";var p="",b="",E="",g=!1,m=t.on.left.toJS("p",e.defaultTableid,e.defcols),v=t.on.right.toJS("p",e.defaultTableid,e.defcols);m.indexOf("p['"+u+"']")>-1&&!(v.indexOf("p['"+u+"']")>-1)?(m.match(/p\[\'.*?\'\]/g)||[]).every(function(e){return e=="p['"+u+"']"})?b=m:g=!0:!(m.indexOf("p['"+u+"']")>-1)&&v.indexOf("p['"+u+"']")>-1&&(v.match(/p\[\'.*?\'\]/g)||[]).every(function(e){return e=="p['"+u+"']"})?p=m:g=!0,v.indexOf("p['"+u+"']")>-1&&!(m.indexOf("p['"+u+"']")>-1)?(v.match(/p\[\'.*?\'\]/g)||[]).every(function(e){return e=="p['"+u+"']"})?b=v:g=!0:!(v.indexOf("p['"+u+"']")>-1)&&m.indexOf("p['"+u+"']")>-1&&(m.match(/p\[\'.*?\'\]/g)||[]).every(function(e){return e=="p['"+u+"']"})?p=v:g=!0,g&&(b="",p="",E=t.on.toJS("p",e.defaultTableid,e.defcols),n.optimization="no"),n.onleftfns=p,n.onrightfns=b,n.onmiddlefns=E||"true",n.onleftfn=new Function("p,params,alasql","var y;return "+n.onleftfns),n.onrightfn=new Function("p,params,alasql","var y;return "+n.onrightfns),n.onmiddlefn=new Function("p,params,alasql","var y;return "+n.onmiddlefns)}else n.optimization="no",n.onmiddlefns=t.on.toJS("p",e.defaultTableid,e.defcols),n.onmiddlefn=new Function("p,params,alasql","var y;return "+t.on.toJS("p",e.defaultTableid,e.defcols));e.sources.push(n)}})},Q.Select.prototype.compileWhere=function(e){if(this.where){if("function"==typeof this.where)return this.where;var t=this.where.toJS("p",e.defaultTableid,e.defcols);return e.wherefns=t,new Function("p,params,alasql","var y;return "+t)}return function(){return!0}},Q.Select.prototype.compileWhereJoins=function(e){},Q.Select.prototype.compileGroup=function(e){if(e.sources.length>0)var t=e.sources[0].alias;else var t="";var r=e.defcols,n=[[]];this.group&&(n=b(this.group,e));var a=[];n.forEach(function(e){a=k(a,e)}),e.allgroups=a,e.ingroup=[];var s="";return n.forEach(function(n){s+="var g=this.xgroups[";var i=n.map(function(t){var r=t.split("\t")[0],n=t.split("\t")[1];return""===r?"1":(e.ingroup.push(r),n)});0===i.length&&(i=["''"]),s+=i.join('+"`"+'),s+="];if(!g) {this.groups.push((g=this.xgroups[",s+=i.join('+"`"+'),s+="] = {",s+=n.map(function(e){var t=e.split("\t")[0],r=e.split("\t")[1];return""===t?"":"'"+t+"':"+r+","}).join(""),s+=L(a,n).map(function(e){return"'"+e.split("\t")[0]+"':null,"}).join("");var o="",u="";void 0!==e.groupStar&&(u+="for(var f in p['"+e.groupStar+"']) {g[f]=p['"+e.groupStar+"'][f];};"),s+=e.selectGroup.map(function(n){var a=n.expression.toJS("p",t,r),s=n.nick;return n instanceof Q.AggrValue?(n.distinct&&(o+=",g['$$_VALUES_"+s+"']={},g['$$_VALUES_"+s+"']["+a+"]=true"),"SUM"===n.aggregatorid?"'"+s+"':("+a+")||0,":"MIN"===n.aggregatorid||"MAX"===n.aggregatorid||"FIRST"===n.aggregatorid||"LAST"===n.aggregatorid?"'"+s+"':"+a+",":"ARRAY"===n.aggregatorid?"'"+s+"':["+a+"],":"COUNT"===n.aggregatorid?"*"===n.expression.columnid?"'"+s+"':1,":"'"+s+"':(typeof "+a+' != "undefined")?1:0,':"AVG"===n.aggregatorid?(e.removeKeys.push("_SUM_"+s),e.removeKeys.push("_COUNT_"+s),"'"+s+"':"+a+",'_SUM_"+s+"':("+a+")||0,'_COUNT_"+s+"':(typeof "+a+' != "undefined")?1:0,'):"AGGR"===n.aggregatorid?(o+=",g['"+s+"']="+n.expression.toJS("g",-1),""):"REDUCE"===n.aggregatorid?(e.aggrKeys.push(n),"'"+s+"':alasql.aggr['"+n.funcid+"']("+a+",undefined,1),"):""):""}).join(""),s+="}"+o+",g));"+u+"} else {",s+=e.selectGroup.map(function(e){var n=e.nick,a=e.expression.toJS("p",t,r);if(e instanceof Q.AggrValue){var s="",i="";if(e.distinct)var s="if(typeof "+a+'!="undefined" && (!g[\'$$_VALUES_'+n+"']["+a+"])) \t\t\t\t \t\t {",i="g['$$_VALUES_"+n+"']["+a+"]=true;}";return"SUM"===e.aggregatorid?s+"g['"+n+"']+=("+a+"||0);"+i:"COUNT"===e.aggregatorid?"*"===e.expression.columnid?s+"g['"+n+"']++;"+i:s+"if(typeof "+a+'!="undefined") g[\''+n+"']++;"+i:"ARRAY"===e.aggregatorid?s+"g['"+n+"'].push("+a+");"+i:"MIN"===e.aggregatorid?s+"g['"+n+"']=Math.min(g['"+n+"'],"+a+");"+i:"MAX"===e.aggregatorid?s+"g['"+n+"']=Math.max(g['"+n+"'],"+a+");"+i:"FIRST"===e.aggregatorid?"":"LAST"===e.aggregatorid?s+"g['"+n+"']="+a+";"+i:"AVG"===e.aggregatorid?s+"g['_SUM_"+n+"']+=(y="+a+")||0;g['_COUNT_"+n+"']+=(typeof y!=\"undefined\")?1:0;g['"+n+"']=g['_SUM_"+n+"']/g['_COUNT_"+n+"'];"+i:"AGGR"===e.aggregatorid?s+"g['"+n+"']="+e.expression.toJS("g",-1)+";"+i:"REDUCE"===e.aggregatorid?s+"g['"+n+"']=alasql.aggr."+e.funcid+"("+a+",g['"+n+"'],2);"+i:""}return""}).join(""),s+="}"}),new Function("p,params,alasql","var y;"+s)},Q.Select.prototype.compileSelect1=function(t,r){var n=this;t.columns=[],t.xcolumns={},t.selectColumns={},t.dirtyColumns=!1;var a="var r={",s="",i=[];return this.columns.forEach(function(a){if(a instanceof Q.Column)if("*"===a.columnid)if(a.func)s+="r=params['"+a.param+"'](p['"+t.sources[0].alias+"'],p,params,alasql);";else if(a.tableid){var o=p(t,a.tableid,!1);o.s&&(i=i.concat(o.s)),s+=o.sp}else for(var u in t.aliases){var o=p(t,u,!0);o.s&&(i=i.concat(o.s)),s+=o.sp}else{var c=a.tableid,l=a.databaseid||t.sources[0].databaseid||t.database.databaseid;if(c||(c=t.defcols[a.columnid]),c||(c=t.defaultTableid),"_"!==a.columnid){var h=r&&r.length>1&&Array.isArray(r[0])&&r[0].length>=1&&r[0][0].hasOwnProperty("sheetid");h?s='var r={};var w=p["'+c+'"];var cols=['+n.columns.map(function(e){return"'"+e.columnid+"'"}).join(",")+"];var colas=["+n.columns.map(function(e){return"'"+(e.as||e.columnid)+"'"}).join(",")+"];for (var i=0;i<Object.keys(p['"+c+"']).length;i++) for(var k=0;k<cols.length;k++){if (!r.hasOwnProperty(i)) r[i]={}; r[i][colas[k]]=w[i][cols[k]];}":i.push("'"+R(a.as||a.columnid)+"':p['"+c+"']['"+a.columnid+"']")}else i.push("'"+R(a.as||a.columnid)+"':p['"+c+"']");if(t.selectColumns[R(a.as||a.columnid)]=!0,t.aliases[c]&&"table"===t.aliases[c].type){if(!A.databases[l].tables[t.aliases[c].tableid])throw new Error("Table '"+c+"' does not exists in database");var d=A.databases[l].tables[t.aliases[c].tableid].columns,f=A.databases[l].tables[t.aliases[c].tableid].xcolumns;if(f&&d.length>0){var b=f[a.columnid];if(void 0===b)throw new Error("Column does not exists: "+a.columnid);var E={columnid:a.as||a.columnid,dbtypeid:b.dbtypeid,dbsize:b.dbsize,dbpecision:b.dbprecision,dbenum:b.dbenum};t.columns.push(E),t.xcolumns[E.columnid]=E}else{var E={columnid:a.as||a.columnid};t.columns.push(E),t.xcolumns[E.columnid]=E,t.dirtyColumns=!0}}else{var E={columnid:a.as||a.columnid};t.columns.push(E),t.xcolumns[E.columnid]=E}}else if(a instanceof Q.AggrValue){n.group||(n.group=[""]),a.as||(a.as=R(a.toString())),"SUM"===a.aggregatorid||"MAX"===a.aggregatorid||"MIN"===a.aggregatorid||"FIRST"===a.aggregatorid||"LAST"===a.aggregatorid||"AVG"===a.aggregatorid||"ARRAY"===a.aggregatorid||"REDUCE"===a.aggregatorid?i.push("'"+R(a.as)+"':"+e(a.expression.toJS("p",t.defaultTableid,t.defcols))):"COUNT"===a.aggregatorid&&i.push("'"+R(a.as)+"':1");var E={columnid:a.as||a.columnid||a.toString()};t.columns.push(E),t.xcolumns[E.columnid]=E}else{i.push("'"+R(a.as||a.columnid||a.toString())+"':"+e(a.toJS("p",t.defaultTableid,t.defcols))),t.selectColumns[R(a.as||a.columnid||a.toString())]=!0;var E={columnid:a.as||a.columnid||a.toString()};t.columns.push(E),t.xcolumns[E.columnid]=E}}),a+=i.join(",")+"};"+s},Q.Select.prototype.compileSelect2=function(e){var t=e.selectfns;return this.orderColumns&&this.orderColumns.length>0&&this.orderColumns.forEach(function(r,n){var a="$$$"+n;t+=r instanceof Q.Column&&e.xcolumns[r.columnid]?"r['"+a+"']=r['"+r.columnid+"'];":"r['"+a+"']="+r.toJS("p",e.defaultTableid,e.defcols)+";",e.removeKeys.push(a)}),new Function("p,params,alasql","var y;"+t+"return r")},Q.Select.prototype.compileSelectGroup0=function(e){var t=this;t.columns.forEach(function(r,n){if(r instanceof Q.Column&&"*"===r.columnid)e.groupStar=r.tableid||"default";else{var a;a=R(r instanceof Q.Column?r.columnid:r.toString(!0));for(var s=0;s<n;s++)if(a===t.columns[s].nick){a=t.columns[s].nick+":"+n;break}r.nick=a,!r.funcid||"ROWNUM"!==r.funcid.toUpperCase()&&"ROW_NUMBER"!==r.funcid.toUpperCase()||e.rownums.push(r.as)}}),this.columns.forEach(function(t){t.findAggregator&&t.findAggregator(e)}),this.having&&this.having.findAggregator&&this.having.findAggregator(e)},Q.Select.prototype.compileSelectGroup1=function(t){var r=this,n="var r = {};";return r.columns.forEach(function(r){if(r instanceof Q.Column&&"*"===r.columnid)return n+="for(var k in g) {r[k]=g[k]};","";var a=r.as;void 0===a&&(a=r instanceof Q.Column?R(r.columnid):r.nick),t.groupColumns[a]=r.nick,n+="r['"+a+"']=",n+=e(r.toJS("g",""))+";";for(var s=0;s<t.removeKeys.length;s++)if(t.removeKeys[s]===a){t.removeKeys.splice(s,1);break}}),n},Q.Select.prototype.compileSelectGroup2=function(e){var t=this,r=e.selectgfns;return t.columns.forEach(function(t){e.ingroup.indexOf(t.nick)>-1&&(r+="r['"+(t.as||t.nick)+"']=g['"+t.nick+"'];")}),this.orderColumns&&this.orderColumns.length>0&&this.orderColumns.forEach(function(t,n){var a="$$$"+n;r+=t instanceof Q.Column&&e.groupColumns[t.columnid]?"r['"+a+"']=r['"+t.columnid+"'];":"r['"+a+"']="+t.toJS("g","")+";",e.removeKeys.push(a)}),new Function("g,params,alasql","var y;"+r+"return r")},Q.Select.prototype.compileRemoveColumns=function(e){void 0!==this.removecolumns&&(e.removeKeys=e.removeKeys.concat(this.removecolumns.filter(function(e){return void 0===e.like}).map(function(e){return e.columnid})),e.removeLikeKeys=this.removecolumns.filter(function(e){return void 0!==e.like}).map(function(e){return e.like.value}))},Q.Select.prototype.compileHaving=function(e){return this.having?(s=this.having.toJS("g",-1),e.havingfns=s,new Function("g,params,alasql","var y;return "+s)):function(){return!0}},Q.Select.prototype.compileOrder=function(e){var t=this;if(t.orderColumns=[],this.order){if(this.order&&1==this.order.length&&this.order[0].expression&&"function"==typeof this.order[0].expression){var r=this.order[0].expression;return function(e,t){var n=r(e),a=r(t);return n>a?1:n==a?0:-1}}var n="",a="";return this.order.forEach(function(r,s){if(r.expression instanceof Q.NumValue)var i=t.columns[r.expression.value-1];else var i=r.expression;t.orderColumns.push(i);var o="$$$"+s,u="";if(r.expression instanceof Q.Column){var c=r.expression.columnid;if(e.xcolumns[c]){var l=e.xcolumns[c].dbtypeid;"DATE"!=l&&"DATETIME"!=l&&"DATETIME2"!=l||(u=".valueOf()")}else A.options.valueof&&(u=".valueOf()")}r.nocase&&(u+=".toUpperCase()"),n+="if((a['"+o+"']||'')"+u+("ASC"==r.direction?">":"<")+"(b['"+o+"']||'')"+u+")return 1;",n+="if((a['"+o+"']||'')"+u+"==(b['"+o+"']||'')"+u+"){",a+="}"}),n+="return 0;",n+=a+"return -1",e.orderfns=n,new Function("a,b","var y;"+n)}},Q.Select.prototype.compilePivot=function(e){var t=this,r=t.pivot.columnid,n=t.pivot.expr.expression.columnid,a=t.pivot.expr.aggregatorid,s=t.pivot.inlist;return s&&(s=s.map(function(e){return e.expr.columnid})),function(){var e=this,t=e.columns.filter(function(e){return e.columnid!=r&&e.columnid!=n}).map(function(e){return e.columnid}),i=[],o={},u={},c={},l=[];if(e.data.forEach(function(e){if(!s||s.indexOf(e[r])>-1){var h=t.map(function(t){return e[t]}).join("`"),d=u[h];if(d||(d={},u[h]=d,l.push(d),t.forEach(function(t){d[t]=e[t]})),c[h]||(c[h]={}),c[h][e[r]]?c[h][e[r]]++:c[h][e[r]]=1,o[e[r]]||(o[e[r]]=!0,i.push(e[r])),"SUM"==a||"AVG"==a)void 0===d[e[r]]&&(d[e[r]]=0),d[e[r]]+=e[n];else if("COUNT"==a)void 0===d[e[r]]&&(d[e[r]]=0),d[e[r]]++;else if("MIN"==a)void 0===d[e[r]]&&(d[e[r]]=1/0),e[n]<d[e[r]]&&(d[e[r]]=e[n]);else if("MAX"==a)void 0===d[e[r]]&&(d[e[r]]=-(1/0)),e[n]>d[e[r]]&&(d[e[r]]=e[n]);else if("FIRST"==a)void 0===d[e[r]]&&(d[e[r]]=e[n]);else if("LAST"==a)d[e[r]]=e[n];else{if(!A.aggr[a])throw new Error("Wrong aggregator in PIVOT clause");A.aggr[a](d[e[r]],e[n])}}}),"AVG"==a)for(var h in u){var d=u[h];for(var f in d)t.indexOf(f)==-1&&f!=n&&(d[f]=d[f]/c[h][f])}e.data=l,s&&(i=s);var p=e.columns.filter(function(e){return e.columnid==n})[0];e.columns=e.columns.filter(function(e){return!(e.columnid==r||e.columnid==n)}),i.forEach(function(t){var r=F(p);r.columnid=t,e.columns.push(r)})}},Q.Select.prototype.compileUnpivot=function(e){var t=this,r=t.unpivot.tocolumnid,n=t.unpivot.forcolumnid,a=t.unpivot.inlist.map(function(e){return e.columnid});return function(){var t=[],s=e.columns.map(function(e){return e.columnid}).filter(function(e){return a.indexOf(e)==-1&&e!=n&&e!=r});e.data.forEach(function(e){a.forEach(function(a){var i={};s.forEach(function(t){i[t]=e[t]}),i[n]=a,i[r]=e[a],t.push(i)})}),e.data=t}};var te=function(e,t){for(var r=[],n=0,a=e.length,s=0;s<a+1;s++){for(var i=[],o=0;o<a;o++){if(e[o]instanceof Q.Column){e[o].nick=R(e[o].columnid),t.groupColumns[R(e[o].columnid)]=e[o].nick;var u=e[o].nick+"\t"+e[o].toJS("p",t.sources[0].alias,t.defcols)}else{t.groupColumns[R(e[o].toString())]=R(e[o].toString());var u=R(e[o].toString())+"\t"+e[o].toJS("p",t.sources[0].alias,t.defcols)}n&1<<o&&i.push(u)}r.push(i),n=1+(n<<1)}return r},re=function(e,t){for(var r=[],n=e.length,a=1<<n,s=0;s<a;s++){for(var i=[],o=0;o<n;o++)s&1<<o&&(i=i.concat(b(e[o],t)));r.push(i)}return r},ne=function(e,t){return e.reduce(function(e,r){return e=e.concat(b(r,t))},[])},ae=function(e,t){for(var r=[],n=0;n<e.length;n++)for(var a=0;a<t.length;a++)r.push(e[n].concat(t[a]));return r};Q.Select.prototype.compileDefCols=function(e,t){var r={".":{}};return this.from&&this.from.forEach(function(e){if(r["."][e.as||e.tableid]=!0,e instanceof Q.Table){var n=e.as||e.tableid,a=A.databases[e.databaseid||t].tables[e.tableid];if(void 0===a)throw new Error("Table does not exists: "+e.tableid);a.columns&&a.columns.forEach(function(e){r[e.columnid]?r[e.columnid]="-":r[e.columnid]=n})}else if(e instanceof Q.Select);else if(e instanceof Q.Search);else if(e instanceof Q.ParamValue);else if(e instanceof Q.VarValue);else if(e instanceof Q.FuncValue);else if(e instanceof Q.FromData);else if(e instanceof Q.Json);else if(!e.inserted)throw new Error("Unknown type of FROM clause")}),this.joins&&this.joins.forEach(function(e){if(r["."][e.as||e.table.tableid]=!0,e.table){var n=e.table.tableid;e.as&&(n=e.as);var n=e.as||e.table.tableid,a=A.databases[e.table.databaseid||t].tables[e.table.tableid];a.columns&&a.columns.forEach(function(e){r[e.columnid]?r[e.columnid]="-":r[e.columnid]=n})}else if(e.select);else if(e.param);else if(!e.func)throw new Error("Unknown type of FROM clause")}),r},Q.Union=function(e){return Q.extend(this,e)},Q.Union.prototype.toString=function(){return"UNION"},Q.Union.prototype.compile=function(e){return null},Q.Apply=function(e){return Q.extend(this,e)},Q.Apply.prototype.toString=function(){var e=this.applymode+" APPLY ("+this.select.toString()+")";return this.as&&(e+=" AS "+this.as),e},Q.Over=function(e){return Q.extend(this,e)},Q.Over.prototype.toString=function(){var e="OVER ("
;return this.partition&&(e+="PARTITION BY "+this.partition.toString(),this.order&&(e+=" ")),this.order&&(e+="ORDER BY "+this.order.toString()),e+=")"},Q.ExpressionStatement=function(e){return Q.extend(this,e)},Q.ExpressionStatement.prototype.toString=function(){return this.expression.toString()},Q.ExpressionStatement.prototype.execute=function(e,t,r){if(this.expression){A.precompile(this,e,t);var n=new Function("params,alasql,p","var y;return "+this.expression.toJS("({})","",null)).bind(this),a=n(t,A);return r&&(a=r(a)),a}},Q.Expression=function(e){return Q.extend(this,e)},Q.Expression.prototype.toString=function(e){var t=this.expression.toString(e);return this.order&&(t+=" "+this.order.toString()),this.nocase&&(t+=" COLLATE NOCASE"),t},Q.Expression.prototype.findAggregator=function(e){this.expression.findAggregator&&this.expression.findAggregator(e)},Q.Expression.prototype.toJS=function(e,t,r){return this.expression.reduced?"true":this.expression.toJS(e,t,r)},Q.Expression.prototype.compile=function(e,t,r){return!!this.reduced||new Function("p","var y;return "+this.toJS(e,t,r))},Q.JavaScript=function(e){return Q.extend(this,e)},Q.JavaScript.prototype.toString=function(){return"``"+this.value+"``"},Q.JavaScript.prototype.toJS=function(){return"("+this.value+")"},Q.JavaScript.prototype.execute=function(e,t,r){var n=1;return new Function("params,alasql,p",this.value)(t,A),r&&(n=r(n)),n},Q.Literal=function(e){return Q.extend(this,e)},Q.Literal.prototype.toString=function(e){var t=this.value;return this.value1&&(t=this.value1+"."+t),this.alias&&!e&&(t+=" AS "+this.alias),t},Q.Join=function(e){return Q.extend(this,e)},Q.Join.prototype.toString=function(){var e=" ";return this.joinmode&&(e+=this.joinmode+" "),e+="JOIN "+this.table.toString()},Q.Table=function(e){return Q.extend(this,e)},Q.Table.prototype.toString=function(){var e=this.tableid;return this.databaseid&&(e=this.databaseid+"."+e),e},Q.View=function(e){return Q.extend(this,e)},Q.View.prototype.toString=function(){var e=this.viewid;return this.databaseid&&(e=this.databaseid+"."+e),e},Q.Op=function(e){return Q.extend(this,e)},Q.Op.prototype.toString=function(){if("IN"===this.op||"NOT IN"===this.op)return this.left.toString()+" "+this.op+" ("+this.right.toString()+")";if(this.allsome)return this.left.toString()+" "+this.op+" "+this.allsome+" ("+this.right.toString()+")";if("->"===this.op||"!"===this.op){var e=this.left.toString()+this.op;return"string"!=typeof this.right&&"number"!=typeof this.right&&(e+="("),e+=this.right.toString(),"string"!=typeof this.right&&"number"!=typeof this.right&&(e+=")"),e}return this.left.toString()+" "+this.op+" "+(this.allsome?this.allsome+" ":"")+this.right.toString()},Q.Op.prototype.findAggregator=function(e){this.left&&this.left.findAggregator&&this.left.findAggregator(e),this.right&&this.right.findAggregator&&!this.allsome&&this.right.findAggregator(e)},Q.Op.prototype.toType=function(e){if(["-","*","/","%","^"].indexOf(this.op)>-1)return"number";if(["||"].indexOf(this.op)>-1)return"string";if("+"===this.op){if("string"===this.left.toType(e)||"string"===this.right.toType(e))return"string";if("number"===this.left.toType(e)||"number"===this.right.toType(e))return"number"}return["AND","OR","NOT","=","==","===","!=","!==","!===",">",">=","<","<=","IN","NOT IN","LIKE","NOT LIKE","REGEXP","GLOB"].indexOf(this.op)>-1?"boolean":"BETWEEN"===this.op||"NOT BETWEEN"===this.op||"IS NULL"===this.op||"IS NOT NULL"===this.op?"boolean":this.allsome?"boolean":this.op?"unknown":this.left.toType()},Q.Op.prototype.toJS=function(e,t,r){var n,a=[],s=this.op,i=this,o=function(n){return n.toJS&&(n=n.toJS(e,t,r)),"y["+(a.push(n)-1)+"]"},u=function(){return o(i.left)},c=function(){return o(i.right)};if("="===this.op?s="===":"<>"===this.op?s="!=":"OR"===this.op&&(s="||"),"->"===this.op){var l="("+u()+"||{})";if("string"==typeof this.right)n=l+'["'+this.right+'"]';else if("number"==typeof this.right)n=l+"["+this.right+"]";else if(this.right instanceof Q.FuncValue){var h=[];if(this.right.args&&0!==this.right.args.length)var h=this.right.args.map(o);n=l+"['"+this.right.funcid+"']("+h.join(",")+")"}else n=l+"["+c()+"]"}if("!"===this.op&&"string"==typeof this.right&&(n="alasql.databases[alasql.useid].objects["+u()+']["'+this.right+'"]'),"IS"===this.op&&(n="(("+u()+"==null) === ("+c()+"==null))"),"=="===this.op&&(n="alasql.utils.deepEqual("+u()+","+c()+")"),"==="!==this.op&&"!==="!==this.op||(n="("+("!==="===this.op?"!":"")+"(("+u()+").valueOf()===("+c()+").valueOf()))"),"!=="===this.op&&(n="(!alasql.utils.deepEqual("+u()+","+c()+"))"),"||"===this.op&&(n="(''+("+u()+"||'')+("+c()+'||""))'),"LIKE"===this.op||"NOT LIKE"===this.op){var n="("+("NOT LIKE"===this.op?"!":"")+"alasql.utils.like("+c()+","+u();this.escape&&(n+=","+o(this.escape)),n+="))"}if("REGEXP"===this.op&&(n="alasql.stdfn.REGEXP_LIKE("+u()+","+c()+")"),"GLOB"===this.op&&(n="alasql.utils.glob("+u()+","+c()+")"),"BETWEEN"===this.op||"NOT BETWEEN"===this.op){var d=u();n="("+("NOT BETWEEN"===this.op?"!":"")+"(("+o(this.right1)+"<="+d+") && ("+d+"<="+o(this.right2)+")))"}if("IN"===this.op&&(this.right instanceof Q.Select?(n="(",n+="alasql.utils.flatArray(this.queriesfn["+this.queriesidx+"](params,null,"+e+"))",n+=".indexOf(",n+=u()+")>-1)"):n=Array.isArray(this.right)?"(["+this.right.map(o).join(",")+"].indexOf("+u()+")>-1)":"("+c()+".indexOf("+u()+")>-1)"),"NOT IN"===this.op&&(this.right instanceof Q.Select?(n="(",n+="alasql.utils.flatArray(this.queriesfn["+this.queriesidx+"](params,null,p))",n+=".indexOf(",n+=u()+")<0)"):Array.isArray(this.right)?(n="(["+this.right.map(o).join(",")+"].indexOf(",n+=u()+")<0)"):(n="("+c()+".indexOf(",n+=u()+")==-1)")),"ALL"===this.allsome){var n;if(this.right instanceof Q.Select)n="alasql.utils.flatArray(this.query.queriesfn["+this.queriesidx+"](params,null,p))",n+=".every(function(b){return (",n+=u()+")"+s+"b})";else{if(!Array.isArray(this.right))throw new Error("NOT IN operator without SELECT");n=""+(1==this.right.length?o(this.right[0]):"["+this.right.map(o).join(",")+"]"),n+=".every(function(b){return (",n+=u()+")"+s+"b})"}}if("SOME"===this.allsome||"ANY"===this.allsome){var n;if(this.right instanceof Q.Select)n="alasql.utils.flatArray(this.query.queriesfn["+this.queriesidx+"](params,null,p))",n+=".some(function(b){return (",n+=u()+")"+s+"b})";else{if(!Array.isArray(this.right))throw new Error("SOME/ANY operator without SELECT");n=""+(1==this.right.length?o(this.right[0]):"["+this.right.map(o).join(",")+"]"),n+=".some(function(b){return (",n+=u()+")"+s+"b})"}}if("AND"===this.op){if(this.left.reduced){if(this.right.reduced)return"true";n=c()}else this.right.reduced&&(n=u());s="&&"}var f=n||"("+u()+s+c()+")",p="y=[("+a.join("), (")+")]";return"&&"==s||"||"==s||"IS"==s||"IS NULL"==s||"IS NOT NULL"==s?"("+p+", "+f+")":"("+p+", y.some(function(e){return e == null}) ? void 0 : "+f+")"},Q.VarValue=function(e){return Q.extend(this,e)},Q.VarValue.prototype.toString=function(){return"@"+this.variable},Q.VarValue.prototype.toType=function(){return"unknown"},Q.VarValue.prototype.toJS=function(){return"alasql.vars['"+this.variable+"']"},Q.NumValue=function(e){return Q.extend(this,e)},Q.NumValue.prototype.toString=function(){return this.value.toString()},Q.NumValue.prototype.toType=function(){return"number"},Q.NumValue.prototype.toJS=function(){return""+this.value},Q.StringValue=function(e){return Q.extend(this,e)},Q.StringValue.prototype.toString=function(){return"'"+this.value.toString()+"'"},Q.StringValue.prototype.toType=function(){return"string"},Q.StringValue.prototype.toJS=function(){return"'"+R(this.value)+"'"},Q.DomainValueValue=function(e){return Q.extend(this,e)},Q.DomainValueValue.prototype.toString=function(){return"VALUE"},Q.DomainValueValue.prototype.toType=function(){return"object"},Q.DomainValueValue.prototype.toJS=function(e,t,r){return e},Q.ArrayValue=function(e){return Q.extend(this,e)},Q.ArrayValue.prototype.toString=function(){return"ARRAY[]"},Q.ArrayValue.prototype.toType=function(){return"object"},Q.ArrayValue.prototype.toJS=function(e,t,r){return"[("+this.value.map(function(n){return n.toJS(e,t,r)}).join("), (")+")]"},Q.LogicValue=function(e){return Q.extend(this,e)},Q.LogicValue.prototype.toString=function(){return this.value?"TRUE":"FALSE"},Q.LogicValue.prototype.toType=function(){return"boolean"},Q.LogicValue.prototype.toJS=function(){return this.value?"true":"false"},Q.NullValue=function(e){return Q.extend(this,e)},Q.NullValue.prototype.toString=function(){return"NULL"},Q.NullValue.prototype.toJS=function(){return"undefined"},Q.ParamValue=function(e){return Q.extend(this,e)},Q.ParamValue.prototype.toString=function(){return"$"+this.param},Q.ParamValue.prototype.toJS=function(){return"string"==typeof this.param?"params['"+this.param+"']":"params["+this.param+"]"},Q.UniOp=function(e){return Q.extend(this,e)},Q.UniOp.prototype.toString=function(){var e;return e=void 0,"~"===this.op&&(e=this.op+this.right.toString()),"-"===this.op&&(e=this.op+this.right.toString()),"+"===this.op&&(e=this.op+this.right.toString()),"#"===this.op&&(e=this.op+this.right.toString()),"NOT"===this.op&&(e=this.op+"("+this.right.toString()+")"),null===this.op&&(e="("+this.right.toString()+")"),e||(e="("+this.right.toString()+")"),e},Q.UniOp.prototype.findAggregator=function(e){this.right.findAggregator&&this.right.findAggregator(e)},Q.UniOp.prototype.toType=function(){return"-"===this.op?"number":"+"===this.op?"number":"NOT"===this.op?"boolean":void 0},Q.UniOp.prototype.toJS=function(e,t,r){return"~"===this.op?"(~("+this.right.toJS(e,t,r)+"))":"-"===this.op?"(-("+this.right.toJS(e,t,r)+"))":"+"===this.op?"("+this.right.toJS(e,t,r)+")":"NOT"===this.op?"!("+this.right.toJS(e,t,r)+")":"#"===this.op?this.right instanceof Q.Column?"(alasql.databases[alasql.useid].objects['"+this.right.columnid+"'])":"(alasql.databases[alasql.useid].objects["+this.right.toJS(e,t,r)+"])":null==this.op?"("+this.right.toJS(e,t,r)+")":void 0},Q.Column=function(e){return Q.extend(this,e)},Q.Column.prototype.toString=function(e){var t;return t=this.columnid==+this.columnid?"["+this.columnid+"]":this.columnid,this.tableid&&(t=+this.columnid===this.columnid?this.tableid+t:this.tableid+"."+t,this.databaseid&&(t=this.databaseid+"."+t)),this.alias&&!e&&(t+=" AS "+this.alias),t},Q.Column.prototype.toJS=function(e,t,r){var n="";if(this.tableid||""!==t||r)if("g"===e)n="g['"+this.nick+"']";else if(this.tableid)n="_"!==this.columnid?e+"['"+this.tableid+"']['"+this.columnid+"']":"g"===e?"g['_']":e+"['"+this.tableid+"']";else if(r){var a=r[this.columnid];if("-"===a)throw new Error('Cannot resolve column "'+this.columnid+'" because it exists in two source tables');n=a?"_"!==this.columnid?e+"['"+a+"']['"+this.columnid+"']":e+"['"+a+"']":"_"!==this.columnid?e+"['"+(this.tableid||t)+"']['"+this.columnid+"']":e+"['"+(this.tableid||t)+"']"}else n=t===-1?e+"['"+this.columnid+"']":"_"!==this.columnid?e+"['"+(this.tableid||t)+"']['"+this.columnid+"']":e+"['"+(this.tableid||t)+"']";else n="_"!==this.columnid?e+"['"+this.columnid+"']":"g"===e?"g['_']":e;return n},Q.AggrValue=function(e){return Q.extend(this,e)},Q.AggrValue.prototype.toString=function(e){var t="";return t+="REDUCE"===this.aggregatorid?this.funcid+"(":this.aggregatorid+"(",this.distinct&&(t+="DISTINCT "),this.expression&&(t+=this.expression.toString()),t+=")",this.over&&(t+=" "+this.over.toString()),this.alias&&!e&&(t+=" AS "+this.alias),t},Q.AggrValue.prototype.findAggregator=function(e){var t=R(this.toString())+":"+e.selectGroup.length,r=!1;if(!r){if(!this.nick){this.nick=t;for(var r=!1,n=0;n<e.removeKeys.length;n++)if(e.removeKeys[n]===t){r=!0;break}r||e.removeKeys.push(t)}e.selectGroup.push(this)}},Q.AggrValue.prototype.toType=function(){return["SUM","COUNT","AVG","MIN","MAX","AGGR","VAR","STDDEV"].indexOf(this.aggregatorid)>-1?"number":["ARRAY"].indexOf(this.aggregatorid)>-1?"array":["FIRST","LAST"].indexOf(this.aggregatorid)>-1?this.expression.toType():void 0},Q.AggrValue.prototype.toJS=function(){var e=this.nick;return void 0===e&&(e=this.toString()),"g['"+e+"']"},Q.OrderExpression=function(e){return Q.extend(this,e)},Q.OrderExpression.prototype.toString=Q.Expression.prototype.toString,Q.GroupExpression=function(e){return Q.extend(this,e)},Q.GroupExpression.prototype.toString=function(){return this.type+"("+this.group.toString()+")"},Q.FromData=function(e){return Q.extend(this,e)},Q.FromData.prototype.toString=function(){return this.data?"DATA("+(1e16*Math.random()|0)+")":"?"},Q.FromData.prototype.toJS=function(){},Q.Select.prototype.exec=function(e,t){this.preparams&&(e=this.preparams.concat(e));var r=A.useid;db=A.databases[r];var n=this.toString(),a=D(n),s=this.compile(r);if(s){s.sql=n,s.dbversion=db.dbversion,db.sqlCacheSize>A.MAXSQLCACHESIZE&&db.resetSqlCache(),db.sqlCacheSize++,db.sqlCache[a]=s;return A.res=s(e,t)}},Q.Select.prototype.Select=function(){var e=this;if(arguments.length>1)args=Array.prototype.slice.call(arguments);else{if(1!=arguments.length)throw new Error("Wrong number of arguments of Select() function");Array.isArray(arguments[0])?args=arguments[0]:args=[arguments[0]]}return e.columns=[],args.forEach(function(t){if("string"==typeof t)e.columns.push(new Q.Column({columnid:t}));else if("function"==typeof t){var r=0;e.preparams?r=e.preparams.length:e.preparams=[],e.preparams.push(t),e.columns.push(new Q.Column({columnid:"*",func:t,param:r}))}}),e},Q.Select.prototype.From=function(e){var t=this;if(t.from||(t.from=[]),Array.isArray(e)){var r=0;t.preparams?r=t.preparams.length:t.preparams=[],t.preparams.push(e),t.from.push(new Q.ParamValue({param:r}))}else{if("string"!=typeof e)throw new Error("Unknown arguments in From() function");t.from.push(new Q.Table({tableid:e}))}return t},Q.Select.prototype.OrderBy=function(){var e=this;if(e.order=[],0==arguments.length)args=["_"];else if(arguments.length>1)args=Array.prototype.slice.call(arguments);else{if(1!=arguments.length)throw new Error("Wrong number of arguments of Select() function");Array.isArray(arguments[0])?args=arguments[0]:args=[arguments[0]]}return args.length>0&&args.forEach(function(t){var r=new Q.Column({columnid:t});"function"==typeof t&&(r=t),e.order.push(new Q.OrderExpression({expression:r,direction:"ASC"}))}),e},Q.Select.prototype.Top=function(e){var t=this;return t.top=new Q.NumValue({value:e}),t},Q.Select.prototype.GroupBy=function(){var e=this;if(arguments.length>1)args=Array.prototype.slice.call(arguments);else{if(1!=arguments.length)throw new Error("Wrong number of arguments of Select() function");Array.isArray(arguments[0])?args=arguments[0]:args=[arguments[0]]}return e.group=[],args.forEach(function(t){var r=new Q.Column({columnid:t});e.group.push(r)}),e},Q.Select.prototype.Where=function(e){var t=this;return"function"==typeof e&&(t.where=e),t},Q.FuncValue=function(e){return Q.extend(this,e)},Q.FuncValue.prototype.toString=function(e){var t="";return A.fn[this.funcid]?t+=this.funcid:A.aggr[this.funcid]?t+=this.funcid:(A.stdlib[this.funcid.toUpperCase()]||A.stdfn[this.funcid.toUpperCase()])&&(t+=this.funcid.toUpperCase()),t+="(",this.args&&this.args.length>0&&(t+=this.args.map(function(e){return e.toString()}).join(",")),t+=")",this.as&&!e&&(t+=" AS "+this.as.toString()),t},Q.FuncValue.prototype.execute=function(e,t,r){var n=1;return A.precompile(this,e,t),new Function("params,alasql","var y;return "+this.toJS("","",null))(t,A),r&&(n=r(n)),n},Q.FuncValue.prototype.findAggregator=function(e){this.args&&this.args.length>0&&this.args.forEach(function(t){t.findAggregator&&t.findAggregator(e)})},Q.FuncValue.prototype.toJS=function(e,t,r){var n="",a=this.funcid;return!A.fn[a]&&A.stdlib[a.toUpperCase()]?n+=this.args&&this.args.length>0?A.stdlib[a.toUpperCase()].apply(this,this.args.map(function(r){return r.toJS(e,t)})):A.stdlib[a.toUpperCase()]():!A.fn[a]&&A.stdfn[a.toUpperCase()]?(this.newid&&(n+="new "),n+="alasql.stdfn."+this.funcid.toUpperCase()+"(",this.args&&this.args.length>0&&(n+=this.args.map(function(n){return n.toJS(e,t,r)}).join(",")),n+=")"):(this.newid&&(n+="new "),n+="alasql.fn."+this.funcid+"(",this.args&&this.args.length>0&&(n+=this.args.map(function(n){return n.toJS(e,t,r)}).join(",")),n+=")"),n};var se=A.stdlib={},ie=A.stdfn={};se.ABS=function(e){return"Math.abs("+e+")"},se.CLONEDEEP=function(e){return"alasql.utils.cloneDeep("+e+")"},ie.CONCAT=function(){return Array.prototype.slice.call(arguments).join("")},se.EXP=function(e){return"Math.pow(Math.E,"+e+")"},se.IIF=function(e,t,r){if(3==arguments.length)return"(("+e+")?("+t+"):("+r+"))";throw new Error("Number of arguments of IFF is not equals to 3")},se.IFNULL=function(e,t){return"("+e+"||"+t+")"},se.INSTR=function(e,t){return"(("+e+").indexOf("+t+")+1)"},se.LEN=se.LENGTH=function(e){return t(e,"y.length")},se.LOWER=se.LCASE=function(e){return t(e,"String(y).toLowerCase()")},se.MAX=se.GREATEST=function(){return"Math.max("+Array.prototype.join.call(arguments,",")+")"},se.MIN=se.LEAST=function(){return"Math.min("+Array.prototype.join.call(arguments,",")+")"},se.SUBSTRING=se.SUBSTR=se.MID=function(e,r,n){return 2==arguments.length?t(e,"y.substr("+r+"-1)"):3==arguments.length?t(e,"y.substr("+r+"-1,"+n+")"):void 0},ie.REGEXP_LIKE=function(e,t,r){return(e||"").search(RegExp(t,r))>-1},se.ISNULL=se.NULLIF=function(e,t){return"("+e+"=="+t+"?undefined:"+e+")"},se.POWER=function(e,t){return"Math.pow("+e+","+t+")"},se.RANDOM=function(e){return 0==arguments.length?"Math.random()":"(Math.random()*("+e+")|0)"},se.ROUND=function(e,t){return 2==arguments.length?"Math.round(("+e+")*Math.pow(10,("+t+")))/Math.pow(10,("+t+"))":"Math.round("+e+")"},se.CEIL=se.CEILING=function(e){return"Math.ceil("+e+")"},se.FLOOR=function(e){return"Math.floor("+e+")"},se.ROWNUM=function(){return"1"},se.ROW_NUMBER=function(){return"1"},se.SQRT=function(e){return"Math.sqrt("+e+")"},se.TRIM=function(e){return t(e,"y.trim()")},se.UPPER=se.UCASE=function(e){return t(e,"String(y).toUpperCase()")},ie.CONCAT_WS=function(){return args=Array.prototype.slice.call(arguments),args.slice(1,args.length).join(args[0])},A.aggr.GROUP_CONCAT=function(e,t,r){return 1==r?e:2==r?t+","+e:void 0},A.aggr.MEDIAN=function(e,t,r){if(2===r)return null===e?t:(t.push(e),t);if(1===r)return null===e?[]:[e];var n=t.sort();return n[n.length/2|0]},A.aggr.VAR=function(e,t,r){if(1===r)return null===e?{arr:[],sum:0}:{arr:[e],sum:e};if(2===r)return null===e?t:(t.arr.push(e),t.sum+=e,t);for(var n=t.arr.length,a=t.sum/n,s=0,i=0;i<n;i++)s+=(t.arr[i]-a)*(t.arr[i]-a);return s/=n-1},A.aggr.STDEV=function(e,t,r){return 1===r||2===r?A.aggr.VAR(e,t,r):Math.sqrt(A.aggr.VAR(e,t,r))},A.aggr.VARP=function(e,t,r){if(1==r)return{arr:[e],sum:e};if(2==r)return t.arr.push(e),t.sum+=e,t;for(var n=t.arr.length,a=t.sum/n,s=0,i=0;i<n;i++)s+=(t.arr[i]-a)*(t.arr[i]-a);return s/=n},A.aggr.STD=A.aggr.STDDEV=A.aggr.STDEVP=function(e,t,r){return 1==r||2==r?A.aggr.VARP(e,t,r):Math.sqrt(A.aggr.VARP(e,t,r))},ie.REPLACE=function(e,t,r){return(e||"").split(t).join(r)};for(var oe=[],ue=0;ue<256;ue++)oe[ue]=(ue<16?"0":"")+ue.toString(16);ie.NEWID=ie.UUID=ie.GEN_RANDOM_UUID=function(){var e=4294967295*Math.random()|0,t=4294967295*Math.random()|0,r=4294967295*Math.random()|0,n=4294967295*Math.random()|0;return oe[255&e]+oe[e>>8&255]+oe[e>>16&255]+oe[e>>24&255]+"-"+oe[255&t]+oe[t>>8&255]+"-"+oe[t>>16&15|64]+oe[t>>24&255]+"-"+oe[63&r|128]+oe[r>>8&255]+"-"+oe[r>>16&255]+oe[r>>24&255]+oe[255&n]+oe[n>>8&255]+oe[n>>16&255]+oe[n>>24&255]},Q.CaseValue=function(e){return Q.extend(this,e)},Q.CaseValue.prototype.toString=function(){var e="CASE ";return this.expression&&(e+=this.expression.toString()),this.whens&&(e+=this.whens.map(function(e){return" WHEN "+e.when.toString()+" THEN "+e.then.toString()}).join()),e+=" END"},Q.CaseValue.prototype.findAggregator=function(e){this.expression&&this.expression.findAggregator&&this.expression.findAggregator(e),this.whens&&this.whens.length>0&&this.whens.forEach(function(t){t.when.findAggregator&&t.when.findAggregator(e),t.then.findAggregator&&t.then.findAggregator(e)}),this.elses&&this.elses.findAggregator&&this.elses.findAggregator(e)},Q.CaseValue.prototype.toJS=function(e,t,r){var n="((function("+e+",params,alasql){var y,r;";return this.expression?(n+="v="+this.expression.toJS(e,t,r)+";",n+=(this.whens||[]).map(function(n){return" if(v=="+n.when.toJS(e,t,r)+") {r="+n.then.toJS(e,t,r)+"}"}).join(" else "),this.elses&&(n+=" else {r="+this.elses.toJS(e,t,r)+"}")):(n+=(this.whens||[]).map(function(n){return" if("+n.when.toJS(e,t,r)+") {r="+n.then.toJS(e,t,r)+"}"}).join(" else "),this.elses&&(n+=" else {r="+this.elses.toJS(e,t,r)+"}")),n+=";return r;}).bind(this))("+e+",params,alasql)"},Q.Json=function(e){return Q.extend(this,e)},Q.Json.prototype.toString=function(){var e="";return e+=ce(this.value),e+=""};var ce=A.utils.JSONtoString=function(e){var t="";if("string"==typeof e)t='"'+e+'"';else if("number"==typeof e)t=e;else if("boolean"==typeof e)t=e;else{if("object"!=typeof e)throw new Error("2Can not show JSON object "+JSON.stringify(e));if(Array.isArray(e))t+="["+e.map(function(e){return ce(e)}).join(",")+"]";else if(!e.toJS||e instanceof Q.Json){t="{";var r=[];for(var n in e){var a="";if("string"==typeof n)a+='"'+n+'"';else if("number"==typeof n)a+=n;else{if("boolean"!=typeof n)throw new Error("THis is not ES6... no expressions on left side yet");a+=n}a+=":"+ce(e[n]),r.push(a)}t+=r.join(",")+"}"}else{if(!e.toString)throw new Error("1Can not show JSON object "+JSON.stringify(e));t=e.toString()}}return t};Q.Json.prototype.toJS=function(e,t,r){return E(this.value,e,t,r)},Q.Convert=function(e){return Q.extend(this,e)},Q.Convert.prototype.toString=function(){var e="CONVERT(";return e+=this.dbtypeid,void 0!==this.dbsize&&(e+="("+this.dbsize,this.dbprecision&&(e+=","+dbprecision),e+=")"),e+=","+this.expression.toString(),this.style&&(e+=","+this.style),e+=")"},Q.Convert.prototype.toJS=function(e,t,r){return"alasql.stdfn.CONVERT("+this.expression.toJS(e,t,r)+',{dbtypeid:"'+this.dbtypeid+'",dbsize:'+this.dbsize+",style:"+this.style+"})"},A.stdfn.CONVERT=function(e,t){var r=e;if(t.style){var n;switch(n=/\d{8}/.test(r)?new Date(+r.substr(0,4),+r.substr(4,2)-1,+r.substr(6,2)):new Date(r),t.style){case 1:r=("0"+(n.getMonth()+1)).substr(-2)+"/"+("0"+n.getDate()).substr(-2)+"/"+("0"+n.getYear()).substr(-2);break;case 2:r=("0"+n.getYear()).substr(-2)+"."+("0"+(n.getMonth()+1)).substr(-2)+"."+("0"+n.getDate()).substr(-2);break;case 3:r=("0"+n.getDate()).substr(-2)+"/"+("0"+(n.getMonth()+1)).substr(-2)+"/"+("0"+n.getYear()).substr(-2);break;case 4:r=("0"+n.getDate()).substr(-2)+"."+("0"+(n.getMonth()+1)).substr(-2)+"."+("0"+n.getYear()).substr(-2);break;case 5:r=("0"+n.getDate()).substr(-2)+"-"+("0"+(n.getMonth()+1)).substr(-2)+"-"+("0"+n.getYear()).substr(-2);break;case 6:r=("0"+n.getDate()).substr(-2)+" "+n.toString().substr(4,3).toLowerCase()+" "+("0"+n.getYear()).substr(-2);break;case 7:r=n.toString().substr(4,3)+" "+("0"+n.getDate()).substr(-2)+","+("0"+n.getYear()).substr(-2);break;case 8:case 108:r=("0"+n.getHours()).substr(-2)+":"+("0"+n.getMinutes()).substr(-2)+":"+("0"+n.getSeconds()).substr(-2);break;case 10:r=("0"+(n.getMonth()+1)).substr(-2)+"-"+("0"+n.getDate()).substr(-2)+"-"+("0"+n.getYear()).substr(-2);break;case 11:r=("0"+n.getYear()).substr(-2)+"/"+("0"+(n.getMonth()+1)).substr(-2)+"/"+("0"+n.getDate()).substr(-2);break;case 12:r=("0"+n.getYear()).substr(-2)+("0"+(n.getMonth()+1)).substr(-2)+("0"+n.getDate()).substr(-2);break;case 101:r=("0"+(n.getMonth()+1)).substr(-2)+"/"+("0"+n.getDate()).substr(-2)+"/"+n.getFullYear();break;case 102:r=n.getFullYear()+"."+("0"+(n.getMonth()+1)).substr(-2)+"."+("0"+n.getDate()).substr(-2);break;case 103:r=("0"+n.getDate()).substr(-2)+"/"+("0"+(n.getMonth()+1)).substr(-2)+"/"+n.getFullYear();break;case 104:r=("0"+n.getDate()).substr(-2)+"."+("0"+(n.getMonth()+1)).substr(-2)+"."+n.getFullYear();break;case 105:r=("0"+n.getDate()).substr(-2)+"-"+("0"+(n.getMonth()+1)).substr(-2)+"-"+n.getFullYear();break;case 106:r=("0"+n.getDate()).substr(-2)+" "+n.toString().substr(4,3).toLowerCase()+" "+n.getFullYear();break;case 107:r=n.toString().substr(4,3)+" "+("0"+n.getDate()).substr(-2)+","+n.getFullYear();break;case 110:r=("0"+(n.getMonth()+1)).substr(-2)+"-"+("0"+n.getDate()).substr(-2)+"-"+n.getFullYear();break;case 111:r=n.getFullYear()+"/"+("0"+(n.getMonth()+1)).substr(-2)+"/"+("0"+n.getDate()).substr(-2);break;case 112:r=n.getFullYear()+("0"+(n.getMonth()+1)).substr(-2)+("0"+n.getDate()).substr(-2);break;default:throw new Error("The CONVERT style "+t.style+" is not realized yet.")}}var a=t.dbtypeid.toUpperCase();if("Date"==t.dbtypeid)return new Date(r);if("DATE"==a){var s=new Date(r),i=s.getFullYear()+"."+("0"+(s.getMonth()+1)).substr(-2)+"."+("0"+s.getDate()).substr(-2);return i}if("DATETIME"==a||"DATETIME2"==a){var s=new Date(r),i=s.getFullYear()+"."+("0"+(s.getMonth()+1)).substr(-2)+"."+("0"+s.getDate()).substr(-2);return i+=" "+("0"+s.getHours()).substr(-2)+":"+("0"+s.getMinutes()).substr(-2)+":"+("0"+s.getSeconds()).substr(-2),i+="."+("00"+s.getMilliseconds()).substr(-3)}if(["MONEY"].indexOf(a)>-1){var o=+r;return(0|o)+100*o%100/100}if(["BOOLEAN"].indexOf(a)>-1)return!!r;if(["INT","INTEGER","SMALLINT","BIGINT","SERIAL","SMALLSERIAL","BIGSERIAL"].indexOf(t.dbtypeid.toUpperCase())>-1)return 0|r;if(["STRING","VARCHAR","NVARCHAR","CHARACTER VARIABLE"].indexOf(t.dbtypeid.toUpperCase())>-1)return t.dbsize?(""+r).substr(0,t.dbsize):""+r;if(["CHAR","CHARACTER","NCHAR"].indexOf(a)>-1)return(r+new Array(t.dbsize+1).join(" ")).substr(0,t.dbsize);if(["NUMBER","FLOAT"].indexOf(a)>-1){if(void 0!==t.dbprecision){var o=+r,u=Math.pow(10,t.dbprecision);return(0|o)+o*u%u/u}return+r}if(["DECIMAL","NUMERIC"].indexOf(a)>-1){var o=+r,u=Math.pow(10,t.dbprecision);return(0|o)+o*u%u/u}if(["JSON"].indexOf(a)>-1){if("object"==typeof r)return r;try{return JSON.parse(r)}catch(e){throw new Error("Cannot convert string to JSON")}}return r},Q.ColumnDef=function(e){return Q.extend(this,e)},Q.ColumnDef.prototype.toString=function(){var e=this.columnid;return this.dbtypeid&&(e+=" "+this.dbtypeid),this.dbsize&&(e+="("+this.dbsize,this.dbprecision&&(e+=","+this.dbprecision),e+=")"),this.primarykey&&(e+=" PRIMARY KEY"),this.notnull&&(e+=" NOT NULL"),e},Q.CreateTable=function(e){return Q.extend(this,e)},Q.CreateTable.prototype.toString=function(){var e="CREATE";if(this.temporary&&(e+=" TEMPORARY"),e+=this.view?" VIEW":" "+(this.class?"CLASS":"TABLE"),this.ifnotexists&&(e+=" IF  NOT EXISTS"),e+=" "+this.table.toString(),this.viewcolumns&&(e+="("+this.viewcolumns.map(function(e){return e.toString()}).join(",")+")"),this.as)e+=" AS "+this.as;else{e+=" ("+this.columns.map(function(e){return e.toString()}).join(",")+")"}return this.view&&this.select&&(e+=" AS "+this.select.toString()),e},Q.CreateTable.prototype.execute=function(e,t,r){var n=A.databases[this.table.databaseid||e],a=this.table.tableid;if(!a)throw new Error("Table name is not defined");var s=this.columns,i=this.constraints||[];if(this.ifnotexists&&n.tables[a])return r?r(0):0;if(n.tables[a])throw new Error("Can not create table '"+a+"', because it already exists in the database '"+n.databaseid+"'");var o=n.tables[a]=new A.Table;this.class&&(o.isclass=!0);var u=[],c=[];if(s&&s.forEach(function(e){var t=e.dbtypeid;A.fn[t]||(t=t.toUpperCase()),["SERIAL","SMALLSERIAL","BIGSERIAL"].indexOf(t)>-1&&(e.identity={value:1,step:1});var r={columnid:e.columnid,dbtypeid:t,dbsize:e.dbsize,dbprecision:e.dbprecision,notnull:e.notnull,identity:e.identity};if(e.identity&&(o.identities[e.columnid]={value:+e.identity.value,step:+e.identity.step}),e.check&&o.checks.push({id:e.check.constrantid,fn:new Function("r","var y;return "+e.check.expression.toJS("r",""))}),e.default&&u.push("'"+e.columnid+"':"+e.default.toJS("r","")),e.primarykey){var n=o.pk={};n.columns=[e.columnid],n.onrightfns="r['"+e.columnid+"']",n.onrightfn=new Function("r","var y;return "+n.onrightfns),n.hh=D(n.onrightfns),o.uniqs[n.hh]={}}if(e.unique){var a={};o.uk=o.uk||[],o.uk.push(a),a.columns=[e.columnid],a.onrightfns="r['"+e.columnid+"']",a.onrightfn=new Function("r","var y;return "+a.onrightfns),a.hh=D(a.onrightfns),o.uniqs[a.hh]={}}if(e.foreignkey){var s=e.foreignkey.table,i=A.databases[s.databaseid||A.useid].tables[s.tableid];if(void 0===s.columnid){if(!(i.pk.columns&&i.pk.columns.length>0))throw new Error("FOREIGN KEY allowed only to tables with PRIMARY KEYs");s.columnid=i.pk.columns[0]}var l=function(t){var r={};if(void 0===t[e.columnid])return!0;r[s.columnid]=t[e.columnid];var n=i.pk.onrightfn(r);if(!i.uniqs[i.pk.hh][n])throw new Error('Foreign key "'+t[e.columnid]+'" is not found in table '+i.tableid);return!0};o.checks.push({fn:l})}e.onupdate&&c.push("r['"+e.columnid+"']="+e.onupdate.toJS("r","")),o.columns.push(r),o.xcolumns[r.columnid]=r}),o.defaultfns=u.join(","),o.onupdatefns=c.join(";"),i.forEach(function(e){var t;if("PRIMARY KEY"===e.type){if(o.pk)throw new Error("Primary key already exists");var r=o.pk={};r.columns=e.columns,r.onrightfns=r.columns.map(function(e){return"r['"+e+"']"}).join("+'`'+"),r.onrightfn=new Function("r","var y;return "+r.onrightfns),r.hh=D(r.onrightfns),o.uniqs[r.hh]={}}else if("CHECK"===e.type)t=new Function("r","var y;return "+e.expression.toJS("r",""));else if("UNIQUE"===e.type){var n={};o.uk=o.uk||[],o.uk.push(n),n.columns=e.columns,n.onrightfns=n.columns.map(function(e){return"r['"+e+"']"}).join("+'`'+"),n.onrightfn=new Function("r","var y;return "+n.onrightfns),n.hh=D(n.onrightfns),o.uniqs[n.hh]={}}else if("FOREIGN KEY"===e.type){var a=o.xcolumns[e.columns[0]],s=e.fktable;e.fkcolumns&&e.fkcolumns.length>0&&(s.columnid=e.fkcolumns[0]);var i=A.databases[s.databaseid||A.useid].tables[s.tableid];void 0===s.columnid&&(s.columnid=i.pk.columns[0]),t=function(e){var t={};if(void 0===e[a.columnid])return!0;t[s.columnid]=e[a.columnid];var r=i.pk.onrightfn(t);if(!i.uniqs[i.pk.hh][r])throw new Error('Foreign key "'+e[a.columnid]+'" is not found in table '+i.tableid);return!0}}t&&o.checks.push({fn:t,id:e.constraintid,fk:"FOREIGN KEY"===e.type})}),this.view&&this.viewcolumns){var l=this;this.viewcolumns.forEach(function(e,t){l.select.columns[t].as=e.columnid})}if(this.view&&this.select&&(o.view=!0,o.select=this.select.compile(this.table.databaseid||e)),n.engineid)return A.engines[n.engineid].createTable(this.table.databaseid||e,a,this.ifnotexists,r);o.insert=function(r,n){var a=A.inserted;A.inserted=[r];var s=this,i=!1,o=!1;for(var u in s.beforeinsert){var c=s.beforeinsert[u];c&&(c.funcid?A.fn[c.funcid](r)===!1&&(o=o||!0):c.statement&&c.statement.execute(e)===!1&&(o=o||!0))}if(!o){var l=!1;for(var u in s.insteadofinsert){l=!0;var c=s.insteadofinsert[u];c&&(c.funcid?A.fn[c.funcid](r):c.statement&&c.statement.execute(e))}if(!l){for(var h in s.identities){var d=s.identities[h];r[h]=d.value}if(s.checks&&s.checks.length>0&&s.checks.forEach(function(e){if(!e.fn(r))throw new Error("Violation of CHECK constraint "+(e.id||""))}),s.columns.forEach(function(e){if(e.notnull&&void 0===r[e.columnid])throw new Error("Wrong NULL value in NOT NULL column "+e.columnid)}),s.pk){var f=s.pk,p=f.onrightfn(r);if(void 0!==s.uniqs[f.hh][p]){if(!n)throw new Error("Cannot insert record, because it already exists in primary key index");i=s.uniqs[f.hh][p]}}if(s.uk&&s.uk.length&&s.uk.forEach(function(e){var t=e.onrightfn(r);if(void 0!==s.uniqs[e.hh][t]){if(!n)throw new Error("Cannot insert record, because it already exists in unique index");i=s.uniqs[e.hh][t]}}),i)s.update(function(e){for(var t in r)e[t]=r[t]},s.data.indexOf(i),t);else{s.data.push(r);for(var h in s.identities){var d=s.identities[h];d.value+=d.step}if(s.pk){var f=s.pk,p=f.onrightfn(r);s.uniqs[f.hh][p]=r}s.uk&&s.uk.length&&s.uk.forEach(function(e){var t=e.onrightfn(r);s.uniqs[e.hh][t]=r})}for(var u in s.afterinsert){var c=s.afterinsert[u];c&&(c.funcid?A.fn[c.funcid](r):c.statement&&c.statement.execute(e))}A.inserted=a}}},o.delete=function(t){var r=this,n=r.data[t],a=!1;for(var s in r.beforedelete){var i=r.beforedelete[s]
;i&&(i.funcid?A.fn[i.funcid](n)===!1&&(a=a||!0):i.statement&&i.statement.execute(e)===!1&&(a=a||!0))}if(a)return!1;var o=!1;for(var s in r.insteadofdelete){o=!0;var i=r.insteadofdelete[s];i&&(i.funcid?A.fn[i.funcid](n):i.statement&&i.statement.execute(e))}if(!o){if(this.pk){var u=this.pk,c=u.onrightfn(n);if(void 0===this.uniqs[u.hh][c])throw new Error("Something wrong with primary key index on table");this.uniqs[u.hh][c]=void 0}r.uk&&r.uk.length&&r.uk.forEach(function(e){var t=e.onrightfn(n);if(void 0===r.uniqs[e.hh][t])throw new Error("Something wrong with unique index on table");r.uniqs[e.hh][t]=void 0})}},o.deleteall=function(){this.data.length=0,this.pk&&(this.uniqs[this.pk.hh]={}),o.uk&&o.uk.length&&o.uk.forEach(function(e){o.uniqs[e.hh]={}})},o.update=function(t,r,n){var a,s=F(this.data[r]);if(this.pk&&(a=this.pk,a.pkaddr=a.onrightfn(s,n),void 0===this.uniqs[a.hh][a.pkaddr]))throw new Error("Something wrong with index on table");o.uk&&o.uk.length&&o.uk.forEach(function(e){if(e.ukaddr=e.onrightfn(s),void 0===o.uniqs[e.hh][e.ukaddr])throw new Error("Something wrong with unique index on table")}),t(s,n,A);var i=!1;for(var u in o.beforeupdate){var c=o.beforeupdate[u];c&&(c.funcid?A.fn[c.funcid](this.data[r],s)===!1&&(i=i||!0):c.statement&&c.statement.execute(e)===!1&&(i=i||!0))}if(i)return!1;var l=!1;for(var u in o.insteadofupdate){l=!0;var c=o.insteadofupdate[u];c&&(c.funcid?A.fn[c.funcid](this.data[r],s):c.statement&&c.statement.execute(e))}if(!l){if(o.checks&&o.checks.length>0&&o.checks.forEach(function(e){if(!e.fn(s))throw new Error("Violation of CHECK constraint "+(e.id||""))}),o.columns.forEach(function(e){if(e.notnull&&void 0===s[e.columnid])throw new Error("Wrong NULL value in NOT NULL column "+e.columnid)}),this.pk&&(a.newpkaddr=a.onrightfn(s),void 0!==this.uniqs[a.hh][a.newpkaddr]&&a.newpkaddr!==a.pkaddr))throw new Error("Record already exists");o.uk&&o.uk.length&&o.uk.forEach(function(e){if(e.newukaddr=e.onrightfn(s),void 0!==o.uniqs[e.hh][e.newukaddr]&&e.newukaddr!==e.ukaddr)throw new Error("Record already exists")}),this.pk&&(this.uniqs[a.hh][a.pkaddr]=void 0,this.uniqs[a.hh][a.newpkaddr]=s),o.uk&&o.uk.length&&o.uk.forEach(function(e){o.uniqs[e.hh][e.ukaddr]=void 0,o.uniqs[e.hh][e.newukaddr]=s}),this.data[r]=s;for(var u in o.afterupdate){var c=o.afterupdate[u];c&&(c.funcid?A.fn[c.funcid](this.data[r],s):c.statement&&c.statement.execute(e))}}};var h;return A.options.nocount||(h=1),r&&(h=r(h)),h},A.fn.Date=Object,A.fn.Date=Date,A.fn.Number=Number,A.fn.String=String,A.fn.Boolean=Boolean,ie.EXTEND=A.utils.extend,ie.CHAR=String.fromCharCode.bind(String),ie.ASCII=function(e){return e.charCodeAt(0)},ie.COALESCE=function(){for(var e=0;e<arguments.length;e++)if(void 0!==arguments[e]&&("number"!=typeof arguments[e]||!isNaN(arguments[e])))return arguments[e]},ie.USER=function(){return"alasql"},ie.OBJECT_ID=function(e){return!!A.tables[e]},ie.DATE=function(e){return/\d{8}/.test(e)?new Date(+e.substr(0,4),+e.substr(4,2)-1,+e.substr(6,2)):new Date(e)},ie.NOW=function(){var e=new Date,t=e.getFullYear()+"."+("0"+(e.getMonth()+1)).substr(-2)+"."+("0"+e.getDate()).substr(-2);return t+=" "+("0"+e.getHours()).substr(-2)+":"+("0"+e.getMinutes()).substr(-2)+":"+("0"+e.getSeconds()).substr(-2),t+="."+("00"+e.getMilliseconds()).substr(-3)},ie.GETDATE=ie.NOW,ie.CURRENT_TIMESTAMP=ie.NOW,ie.SECOND=function(e){var e=new Date(e);return e.getSeconds()},ie.MINUTE=function(e){var e=new Date(e);return e.getMinutes()},ie.HOUR=function(e){var e=new Date(e);return e.getHours()},ie.DAYOFWEEK=ie.WEEKDAY=function(e){var e=new Date(e);return e.getDay()},ie.DAY=ie.DAYOFMONTH=function(e){var e=new Date(e);return e.getDate()},ie.MONTH=function(e){var e=new Date(e);return e.getMonth()+1},ie.YEAR=function(e){var e=new Date(e);return e.getFullYear()};var le={year:31536e6,quarter:7884e6,month:2592e6,week:6048e5,day:864e5,dayofyear:864e5,weekday:864e5,hour:36e5,minute:6e4,second:1e3,millisecond:1,microsecond:.001};A.stdfn.DATEDIFF=function(e,t,r){return(new Date(r).getTime()-new Date(t).getTime())/le[e.toLowerCase()]},A.stdfn.DATEADD=function(e,t,r){var n=new Date(r).getTime()+t*le[e.toLowerCase()];return new Date(n)},A.stdfn.INTERVAL=function(e,t){return e*le[t.toLowerCase()]},A.stdfn.DATE_ADD=A.stdfn.ADDDATE=function(e,t){var r=new Date(e).getTime()+t;return new Date(r)},A.stdfn.DATE_SUB=A.stdfn.SUBDATE=function(e,t){var r=new Date(e).getTime()-t;return new Date(r)},Q.DropTable=function(e){return Q.extend(this,e)},Q.DropTable.prototype.toString=function(){var e="DROP ";return e+=this.view?"VIEW":"TABLE",this.ifexists&&(e+=" IF EXISTS"),e+=" "+this.tables.toString()},Q.DropTable.prototype.execute=function(e,t,r){var n=this.ifexists,a=0,s=0,i=this.tables.length;return this.tables.forEach(function(t){var o=A.databases[t.databaseid||e],u=t.tableid;if(!n||n&&o.tables[u]){if(o.tables[u])o.engineid?A.engines[o.engineid].dropTable(t.databaseid||e,u,n,function(e){delete o.tables[u],a+=e,++s==i&&r&&r(a)}):(delete o.tables[u],a++,++s==i&&r&&r(a));else if(!A.options.dropifnotexists)throw new Error("Can not drop table '"+t.tableid+"', because it does not exist in the database.")}else++s==i&&r&&r(a)}),a},Q.TruncateTable=function(e){return Q.extend(this,e)},Q.TruncateTable.prototype.toString=function(){var e="TRUNCATE TABLE";return e+=" "+this.table.toString()},Q.TruncateTable.prototype.execute=function(e,t,r){var n=A.databases[this.table.databaseid||e],a=this.table.tableid;if(n.engineid)return A.engines[n.engineid].truncateTable(this.table.databaseid||e,a,this.ifexists,r);if(!n.tables[a])throw new Error("Cannot truncate table becaues it does not exist");return n.tables[a].data=[],r?r(0):0},Q.CreateVertex=function(e){return Q.extend(this,e)},Q.CreateVertex.prototype.toString=function(){var e="CREATE VERTEX ";return this.class&&(e+=this.class+" "),this.sharp&&(e+="#"+this.sharp+" "),this.sets?e+=this.sets.toString():this.content?e+=this.content.toString():this.select&&(e+=this.select.toString()),e},Q.CreateVertex.prototype.toJS=function(e){return"this.queriesfn["+(this.queriesidx-1)+"](this.params,null,"+e+")"},Q.CreateVertex.prototype.compile=function(e){var t=e,r=this.sharp;if(void 0!==this.name)var n="x.name="+this.name.toJS(),a=new Function("x",n);if(this.sets&&this.sets.length>0)var n=this.sets.map(function(e){return"x['"+e.column.columnid+"']="+e.expression.toJS("x","")}).join(";"),s=new Function("x,params,alasql",n);return function(e,n){var i,o,u=A.databases[t];o=void 0!==r?r:u.counter++;var c={$id:o,$node:"VERTEX"};return u.objects[c.$id]=c,i=c,a&&a(c),s&&s(c,e,A),n&&(i=n(i)),i}},Q.CreateEdge=function(e){return Q.extend(this,e)},Q.CreateEdge.prototype.toString=function(){var e="CREATE EDGE ";return this.class&&(e+=this.class+" "),e},Q.CreateEdge.prototype.toJS=function(e){return"this.queriesfn["+(this.queriesidx-1)+"](this.params,null,"+e+")"},Q.CreateEdge.prototype.compile=function(e){var t=e,r=new Function("params,alasql","var y;return "+this.from.toJS()),n=new Function("params,alasql","var y;return "+this.to.toJS());if(void 0!==this.name)var a="x.name="+this.name.toJS(),s=new Function("x",a);if(this.sets&&this.sets.length>0)var a=this.sets.map(function(e){return"x['"+e.column.columnid+"']="+e.expression.toJS("x","")}).join(";"),i=new Function("x,params,alasql","var y;"+a);return function(e,a){var o=0,u=A.databases[t],c={$id:u.counter++,$node:"EDGE"},l=r(e,A),h=n(e,A);return c.$in=[l.$id],c.$out=[h.$id],void 0===l.$out&&(l.$out=[]),l.$out.push(c.$id),void 0===typeof h.$in&&(h.$in=[]),h.$in.push(c.$id),u.objects[c.$id]=c,o=c,s&&s(c),i&&i(c,e,A),a&&(o=a(o)),o}},Q.CreateGraph=function(e){return Q.extend(this,e)},Q.CreateGraph.prototype.toString=function(){var e="CREATE GRAPH ";return this.class&&(e+=this.class+" "),e},Q.CreateGraph.prototype.execute=function(e,t,r){function n(e){var t=A.databases[A.useid].objects;for(var r in t)if(t[r].name===e)return t[r]}function a(r){var n={};void 0!==r.as&&(A.vars[r.as]=n),void 0!==r.prop&&(n.$id=r.prop,n.name=r.prop),void 0!==r.sharp&&(n.$id=r.sharp),void 0!==r.name&&(n.name=r.name),void 0!==r.class&&(n.$class=r.class);var a=A.databases[e];if(void 0===n.$id&&(n.$id=a.counter++),n.$node="VERTEX",void 0!==r.json&&G(n,new Function("params,alasql","var y;return "+r.json.toJS())(t,A)),a.objects[n.$id]=n,void 0!==n.$class){if(void 0===A.databases[e].tables[n.$class])throw new Error("No such class. Pleace use CREATE CLASS");A.databases[e].tables[n.$class].data.push(n)}return s.push(n.$id),n}var s=[];return this.from&&A.from[this.from.funcid]&&(this.graph=A.from[this.from.funcid.toUpperCase()]),this.graph.forEach(function(r){if(r.source){var i={};void 0!==r.as&&(A.vars[r.as]=i),void 0!==r.prop&&(i.name=r.prop),void 0!==r.sharp&&(i.$id=r.sharp),void 0!==r.name&&(i.name=r.name),void 0!==r.class&&(i.$class=r.class);var o=A.databases[e];void 0===i.$id&&(i.$id=o.counter++),i.$node="EDGE",void 0!==r.json&&G(i,new Function("params,alasql","var y;return "+r.json.toJS())(t,A));var u;if(r.source.vars){var c=A.vars[r.source.vars];u="object"==typeof c?c:o.objects[c]}else{var l=r.source.sharp;void 0===l&&(l=r.source.prop),u=A.databases[e].objects[l],void 0!==u||!A.options.autovertex||void 0===r.source.prop&&void 0===r.source.name||void 0===(u=n(r.source.prop||r.source.name))&&(u=a(r.source))}var h;if(r.source.vars){var c=A.vars[r.target.vars];h="object"==typeof c?c:o.objects[c]}else{var d=r.target.sharp;void 0===d&&(d=r.target.prop),h=A.databases[e].objects[d],void 0!==h||!A.options.autovertex||void 0===r.target.prop&&void 0===r.target.name||void 0===(h=n(r.target.prop||r.target.name))&&(h=a(r.target))}if(i.$in=[u.$id],i.$out=[h.$id],void 0===u.$out&&(u.$out=[]),u.$out.push(i.$id),void 0===h.$in&&(h.$in=[]),h.$in.push(i.$id),o.objects[i.$id]=i,void 0!==i.$class){if(void 0===A.databases[e].tables[i.$class])throw new Error("No such class. Pleace use CREATE CLASS");A.databases[e].tables[i.$class].data.push(i)}s.push(i.$id)}else a(r)}),r&&(s=r(s)),s},Q.CreateGraph.prototype.compile1=function(e){var t=e,r=new Function("params,alasql","var y;return "+this.from.toJS()),n=new Function("params,alasql","var y;return "+this.to.toJS());if(void 0!==this.name)var a="x.name="+this.name.toJS(),s=new Function("x",a);if(this.sets&&this.sets.length>0)var a=this.sets.map(function(e){return"x['"+e.column.columnid+"']="+e.expression.toJS("x","")}).join(";"),i=new Function("x,params,alasql","var y;"+a);return function(e,a){var o=0,u=A.databases[t],c={$id:u.counter++,$node:"EDGE"},l=r(e,A),h=n(e,A);return c.$in=[l.$id],c.$out=[h.$id],void 0===l.$out&&(l.$out=[]),l.$out.push(c.$id),void 0===h.$in&&(h.$in=[]),h.$in.push(c.$id),u.objects[c.$id]=c,o=c,s&&s(c),i&&i(c,e,A),a&&(o=a(o)),o}},Q.AlterTable=function(e){return Q.extend(this,e)},Q.AlterTable.prototype.toString=function(){var e="ALTER TABLE "+this.table.toString();return this.renameto&&(e+=" RENAME TO "+this.renameto),e},Q.AlterTable.prototype.execute=function(e,t,r){var n=A.databases[e];if(n.dbversion=Date.now(),this.renameto){var a=this.table.tableid,s=this.renameto,i=1;if(n.tables[s])throw new Error("Can not rename a table '"+a+"' to '"+s+"', because the table with this name already exists");if(s==a)throw new Error("Can not rename a table '"+a+"' to itself");return n.tables[s]=n.tables[a],delete n.tables[a],i=1,r&&r(i),i}if(this.addcolumn){var n=A.databases[this.table.databaseid||e];n.dbversion++;var o=this.table.tableid,u=n.tables[o],c=this.addcolumn.columnid;if(u.xcolumns[c])throw new Error('Cannot add column "'+c+'", because it already exists in the table "'+o+'"');var l={columnid:c,dbtypeid:this.dbtypeid,dbsize:this.dbsize,dbprecision:this.dbprecision,dbenum:this.dbenum,defaultfns:null};u.columns.push(l),u.xcolumns[c]=l;for(var h=0,d=u.data.length;h<d;h++)u.data[h][c]=void 0;return r?r(1):1}if(this.modifycolumn){var n=A.databases[this.table.databaseid||e];n.dbversion++;var o=this.table.tableid,u=n.tables[o],c=this.modifycolumn.columnid;if(!u.xcolumns[c])throw new Error('Cannot modify column "'+c+'", because it was not found in the table "'+o+'"');var l=u.xcolumns[c];return l.dbtypeid=this.dbtypeid,l.dbsize=this.dbsize,l.dbprecision=this.dbprecision,l.dbenum=this.dbenum,r?r(1):1}if(this.renamecolumn){var n=A.databases[this.table.databaseid||e];n.dbversion++;var l,o=this.table.tableid,u=n.tables[o],c=this.renamecolumn,f=this.to;if(!u.xcolumns[c])throw new Error('Column "'+c+'" is not found in the table "'+o+'"');if(u.xcolumns[f])throw new Error('Column "'+f+'" already exists in the table "'+o+'"');if(c!=f){for(var p=0;p<u.columns.length;p++)u.columns[p].columnid==c&&(u.columns[p].columnid=f);u.xcolumns[f]=u.xcolumns[c],delete u.xcolumns[c];for(var h=0,d=u.data.length;h<d;h++)u.data[h][f]=u.data[h][c],delete u.data[h][c];return u.data.length}return r?r(0):0}if(this.dropcolumn){var n=A.databases[this.table.databaseid||e];n.dbversion++;for(var o=this.table.tableid,u=n.tables[o],c=this.dropcolumn,b=!1,p=0;p<u.columns.length;p++)if(u.columns[p].columnid==c){b=!0,u.columns.splice(p,1);break}if(!b)throw new Error('Cannot drop column "'+c+'", because it was not found in the table "'+o+'"');delete u.xcolumns[c];for(var h=0,d=u.data.length;h<d;h++)delete u.data[h][c];return r?r(u.data.length):u.data.length}throw Error("Unknown ALTER TABLE method")},Q.CreateIndex=function(e){return Q.extend(this,e)},Q.CreateIndex.prototype.toString=function(){var e="CREATE";return this.unique&&(e+=" UNIQUE"),e+=" INDEX "+this.indexid+" ON "+this.table.toString(),e+="("+this.columns.toString()+")"},Q.CreateIndex.prototype.execute=function(e,t,r){var n=A.databases[e],a=this.table.tableid,s=n.tables[a],i=this.indexid;n.indices[i]=a;var o=this.columns.map(function(e){return e.expression.toJS("r","")}).join("+'`'+"),u=new Function("r,params,alasql","return "+o);if(this.unique){s.uniqdefs[i]={rightfns:o};var c=s.uniqs[i]={};if(s.data.length>0)for(var l=0,h=s.data.length;l<h;l++){var d=o(s.data[l]);c[d]||(c[d]={num:0}),c[d].num++}}else{var f=D(o);s.inddefs[i]={rightfns:o,hh:f},s.indices[f]={};var p=s.indices[f]={};if(s.data.length>0)for(var l=0,h=s.data.length;l<h;l++){var d=u(s.data[l],t,A);p[d]||(p[d]=[]),p[d].push(s.data[l])}}var b=1;return r&&(b=r(b)),b},Q.Reindex=function(e){return Q.extend(this,e)},Q.Reindex.prototype.toString=function(){return"REINDEX "+this.indexid},Q.Reindex.prototype.execute=function(e,t,r){var n=A.databases[e],a=this.indexid,s=n.indices[a];n.tables[s].indexColumns();var i=1;return r&&(i=r(i)),i},Q.DropIndex=function(e){return Q.extend(this,e)},Q.DropIndex.prototype.toString=function(){return"DROP INDEX"+this.indexid},Q.DropIndex.prototype.compile=function(e){this.indexid;return function(){return 1}},Q.WithSelect=function(e){return Q.extend(this,e)},Q.WithSelect.prototype.toString=function(){var e="WITH ";return e+=this.withs.map(function(e){return e.name+" AS ("+e.select.toString()+")"}).join(",")+" ",e+=this.select.toString()},Q.WithSelect.prototype.execute=function(e,t,r){var n=this,a=[];n.withs.forEach(function(r){a.push(A.databases[e].tables[r.name]),(A.databases[e].tables[r.name]=new X({tableid:r.name})).data=r.select.execute(e,t)});return this.select.execute(e,t,function(t){return n.withs.forEach(function(t,r){a[r]?A.databases[e].tables[t.name]=a[r]:delete A.databases[e].tables[t.name]}),r&&(t=r(t)),t})},Q.If=function(e){return Q.extend(this,e)},Q.If.prototype.toString=function(){var e="IF ";return e+=this.expression.toString(),e+=" "+this.thenstat.toString(),this.elsestat&&(e+=" ELSE "+this.thenstat.toString()),e},Q.If.prototype.execute=function(e,t,r){var n;return new Function("params,alasql,p","var y;return "+this.expression.toJS("({})","",null)).bind(this)(t,A)?n=this.thenstat.execute(e,t,r):this.elsestat?n=this.elsestat.execute(e,t,r):r&&(n=r(n)),n},Q.While=function(e){return Q.extend(this,e)},Q.While.prototype.toString=function(){var e="WHILE ";return e+=this.expression.toString(),e+=" "+this.loopstat.toString()},Q.While.prototype.execute=function(e,t,r){function n(u){o?s.push(u):o=!0,setTimeout(function(){i(t,A)?a.loopstat.execute(e,t,n):s=r(s)},0)}var a=this,s=[],i=new Function("params,alasql,p","var y;return "+this.expression.toJS());if(r){var o=!1;n()}else for(;i(t,A);){var u=a.loopstat.execute(e,t);s.push(u)}return s},Q.Break=function(e){return Q.extend(this,e)},Q.Break.prototype.toString=function(){return"BREAK"},Q.Break.prototype.execute=function(e,t,r,n){var a=1;return r&&(a=r(a)),a},Q.Continue=function(e){return Q.extend(this,e)},Q.Continue.prototype.toString=function(){return"CONTINUE"},Q.Continue.prototype.execute=function(e,t,r,n){var a=1;return r&&(a=r(a)),a},Q.BeginEnd=function(e){return Q.extend(this,e)},Q.BeginEnd.prototype.toString=function(){return"BEGIN "+this.statements.toString()+" END"},Q.BeginEnd.prototype.execute=function(e,t,r,n){function a(){s.statements[o].execute(e,t,function(e){if(i.push(e),++o<s.statements.length)return a();r&&(i=r(i))})}var s=this,i=[],o=0;return a(),i},Q.Insert=function(e){return Q.extend(this,e)},Q.Insert.prototype.toString=function(){var e="INSERT ";return this.orreplace&&(e+="OR REPLACE "),this.replaceonly&&(e="REPLACE "),e+="INTO "+this.into.toString(),this.columns&&(e+="("+this.columns.toString()+")"),this.values&&(e+=" VALUES "+this.values.toString()),this.select&&(e+=" "+this.select.toString()),e},Q.Insert.prototype.toJS=function(e,t,r){return"this.queriesfn["+(this.queriesidx-1)+"](this.params,null,"+e+")"},Q.Insert.prototype.compile=function(e){var t=this;e=t.into.databaseid||e;var r=A.databases[e],n=t.into.tableid,a=r.tables[n];if(!a)throw"Table '"+n+"' could not be found";var s,i="",o="",i="db.tables['"+n+"'].dirty=true;",u="var a,aa=[],x;";if(this.values){this.exists&&(this.existsfn=this.exists.map(function(t){var r=t.compile(e);return r.query.modifier="RECORDSET",r})),this.queries&&(this.queriesfn=this.queries.map(function(t){var r=t.compile(e);return r.query.modifier="RECORDSET",r})),t.values.forEach(function(s){var u=[];t.columns?t.columns.forEach(function(e,t){var r="'"+e.columnid+"':";a.xcolumns&&a.xcolumns[e.columnid]?["INT","FLOAT","NUMBER","MONEY"].indexOf(a.xcolumns[e.columnid].dbtypeid)>=0?r+="(x="+s[t].toJS()+",x==undefined?undefined:+x)":A.fn[a.xcolumns[e.columnid].dbtypeid]?(r+="(new "+a.xcolumns[e.columnid].dbtypeid+"(",r+=s[t].toJS(),r+="))"):r+=s[t].toJS():r+=s[t].toJS(),u.push(r)}):Array.isArray(s)&&a.columns&&a.columns.length>0?a.columns.forEach(function(e,t){var r="'"+e.columnid+"':";["INT","FLOAT","NUMBER","MONEY"].indexOf(e.dbtypeid)>=0?r+="+"+s[t].toJS():A.fn[e.dbtypeid]?(r+="(new "+e.dbtypeid+"(",r+=s[t].toJS(),r+="))"):r+=s[t].toJS(),u.push(r)}):o=E(s),r.tables[n].defaultfns&&u.unshift(r.tables[n].defaultfns),i+=o?"a="+o+";":"a={"+u.join(",")+"};",r.tables[n].isclass&&(i+="var db=alasql.databases['"+e+"'];",i+='a.$class="'+n+'";',i+="a.$id=db.counter++;",i+="db.objects[a.$id]=a;"),r.tables[n].insert?(i+="var db=alasql.databases['"+e+"'];",i+="db.tables['"+n+"'].insert(a,"+(t.orreplace?"true":"false")+");"):i+="aa.push(a);"}),s=u+i,r.tables[n].insert||(i+="alasql.databases['"+e+"'].tables['"+n+"'].data=alasql.databases['"+e+"'].tables['"+n+"'].data.concat(aa);"),i+=r.tables[n].insert&&r.tables[n].isclass?"return a.$id;":"return "+t.values.length;var c=new Function("db, params, alasql","var y;"+u+i).bind(this)}else if(this.select){this.select.modifier="RECORDSET";var l=this.select.compile(e);if(r.engineid&&A.engines[r.engineid].intoTable){var h=function(e,t){var a=l(e);return A.engines[r.engineid].intoTable(r.databaseid,n,a.data,null,t)};return h}var d="return alasql.utils.extend(r,{"+a.defaultfns+"})",f=new Function("r,db,params,alasql",d),c=function(e,r,a){var s=l(r).data;if(e.tables[n].insert)for(var i=0,o=s.length;i<o;i++){var u=F(s[i]);f(u,e,r,a),e.tables[n].insert(u,t.orreplace)}else e.tables[n].data=e.tables[n].data.concat(s);return a.options.nocount?void 0:s.length}}else{if(!this.default)throw new Error("Wrong INSERT parameters");var p="db.tables['"+n+"'].data.push({"+a.defaultfns+"});return 1;",c=new Function("db,params,alasql",p)}if(r.engineid&&A.engines[r.engineid].intoTable&&A.options.autocommit)var h=function(e,t){var a=new Function("db,params","var y;"+s+"return aa;")(r,e);return A.engines[r.engineid].intoTable(r.databaseid,n,a,null,t)};else var h=function(t,r){var a=A.databases[e];A.options.autocommit&&a.engineid&&A.engines[a.engineid].loadTableData(e,n);var s=c(a,t,A);return A.options.autocommit&&a.engineid&&A.engines[a.engineid].saveTableData(e,n),A.options.nocount&&(s=void 0),r&&r(s),s};return h},Q.Insert.prototype.execute=function(e,t,r){return this.compile(e)(t,r)},Q.CreateTrigger=function(e){return Q.extend(this,e)},Q.CreateTrigger.prototype.toString=function(){var e="CREATE TRIGGER "+this.trigger+" ";return this.when&&(e+=this.when+" "),e+=this.action+" ON ",this.table.databaseid&&(e+=this.table.databaseid+"."),e+=this.table.tableid+" ",e+=this.statement.toString()},Q.CreateTrigger.prototype.execute=function(e,t,r){var n=1,a=this.trigger;e=this.table.databaseid||e;var s=A.databases[e],i=this.table.tableid,o={action:this.action,when:this.when,statement:this.statement,funcid:this.funcid};return s.triggers[a]=o,"INSERT"==o.action&&"BEFORE"==o.when?s.tables[i].beforeinsert[a]=o:"INSERT"==o.action&&"AFTER"==o.when?s.tables[i].afterinsert[a]=o:"INSERT"==o.action&&"INSTEADOF"==o.when?s.tables[i].insteadofinsert[a]=o:"DELETE"==o.action&&"BEFORE"==o.when?s.tables[i].beforedelete[a]=o:"DELETE"==o.action&&"AFTER"==o.when?s.tables[i].afterdelete[a]=o:"DELETE"==o.action&&"INSTEADOF"==o.when?s.tables[i].insteadofdelete[a]=o:"UPDATE"==o.action&&"BEFORE"==o.when?s.tables[i].beforeupdate[a]=o:"UPDATE"==o.action&&"AFTER"==o.when?s.tables[i].afterupdate[a]=o:"UPDATE"==o.action&&"INSTEADOF"==o.when&&(s.tables[i].insteadofupdate[a]=o),r&&(n=r(n)),n},Q.DropTrigger=function(e){return Q.extend(this,e)},Q.DropTrigger.prototype.toString=function(){return"DROP TRIGGER "+this.trigger},Q.DropTrigger.prototype.execute=function(e,t,r){var n=0,a=A.databases[e],s=this.trigger,i=a.triggers[s];if(!i)throw new Error("Trigger not found");return n=1,delete a.tables[i].beforeinsert[s],delete a.tables[i].afterinsert[s],delete a.tables[i].insteadofinsert[s],delete a.tables[i].beforedelte[s],delete a.tables[i].afterdelete[s],delete a.tables[i].insteadofdelete[s],delete a.tables[i].beforeupdate[s],delete a.tables[i].afterupdate[s],delete a.tables[i].insteadofupdate[s],delete a.triggers[s],r&&(n=r(n)),n},Q.Delete=function(e){return Q.extend(this,e)},Q.Delete.prototype.toString=function(){var e="DELETE FROM "+this.table.toString();return this.where&&(e+=" WHERE "+this.where.toString()),e},Q.Delete.prototype.compile=function(e){e=this.table.databaseid||e;var t,r=this.table.tableid,n=A.databases[e];if(this.where){this.exists&&(this.existsfn=this.exists.map(function(t){var r=t.compile(e);return r.query.modifier="RECORDSET",r})),this.queries&&(this.queriesfn=this.queries.map(function(t){var r=t.compile(e);return r.query.modifier="RECORDSET",r}));var a=new Function("r,params,alasql","var y;return ("+this.where.toJS("r","")+")").bind(this);t=function(t,s){if(n.engineid&&A.engines[n.engineid].deleteFromTable)return A.engines[n.engineid].deleteFromTable(e,r,a,t,s);A.options.autocommit&&n.engineid&&"LOCALSTORAGE"==n.engineid&&A.engines[n.engineid].loadTableData(e,r);for(var i=n.tables[r],o=i.data.length,u=[],c=0,l=i.data.length;c<l;c++)a(i.data[c],t,A)?i.delete&&i.delete(c,t,A):u.push(i.data[c]);i.data=u;for(var h in i.afterdelete){var d=i.afterdelete[h];d&&(d.funcid?A.fn[d.funcid]():d.statement&&d.statement.execute(e))}var f=o-i.data.length;return A.options.autocommit&&n.engineid&&"LOCALSTORAGE"==n.engineid&&A.engines[n.engineid].saveTableData(e,r),s&&s(f),f}}else t=function(t,a){A.options.autocommit&&n.engineid&&A.engines[n.engineid].loadTableData(e,r),n.tables[r].dirty=!0;var s=n.tables[r].data.length;n.tables[r].data.length=0;for(var i in n.tables[r].uniqs)n.tables[r].uniqs[i]={};for(var i in n.tables[r].indices)n.tables[r].indices[i]={};return A.options.autocommit&&n.engineid&&A.engines[n.engineid].saveTableData(e,r),a&&a(s),s};return t},Q.Delete.prototype.execute=function(e,t,r){return this.compile(e)(t,r)},Q.Update=function(e){return Q.extend(this,e)},Q.Update.prototype.toString=function(){var e="UPDATE "+this.table.toString();return this.columns&&(e+=" SET "+this.columns.toString()),this.where&&(e+=" WHERE "+this.where.toString()),e},Q.SetColumn=function(e){return Q.extend(this,e)},Q.SetColumn.prototype.toString=function(){return this.column.toString()+"="+this.expression.toString()},Q.Update.prototype.compile=function(e){e=this.table.databaseid||e;var t=this.table.tableid;if(this.where){this.exists&&(this.existsfn=this.exists.map(function(t){var r=t.compile(e);return r.query.modifier="RECORDSET",r})),this.queries&&(this.queriesfn=this.queries.map(function(t){var r=t.compile(e);return r.query.modifier="RECORDSET",r}));var r=new Function("r,params,alasql","var y;return "+this.where.toJS("r","")).bind(this)}var n=A.databases[e].tables[t].onupdatefns||"";n+=";",this.columns.forEach(function(e){n+="r['"+e.column.columnid+"']="+e.expression.toJS("r","")+";"});var a=new Function("r,params,alasql","var y;"+n);return function(n,s){var i=A.databases[e];if(i.engineid&&A.engines[i.engineid].updateTable)return A.engines[i.engineid].updateTable(e,t,a,r,n,s);A.options.autocommit&&i.engineid&&A.engines[i.engineid].loadTableData(e,t);var o=i.tables[t];if(!o)throw new Error("Table '"+t+"' not exists");for(var u=0,c=0,l=o.data.length;c<l;c++)r&&!r(o.data[c],n,A)||(o.update?o.update(a,c,n):a(o.data[c],n,A),u++);return A.options.autocommit&&i.engineid&&A.engines[i.engineid].saveTableData(e,t),s&&s(u),u}},Q.Update.prototype.execute=function(e,t,r){return this.compile(e)(t,r)},Q.Merge=function(e){return Q.extend(this,e)},Q.Merge.prototype.toString=function(){var e="MERGE ";return e+=this.into.tableid+" ",this.into.as&&(e+="AS "+this.into.as+" "),e+="USING "+this.using.tableid+" ",this.using.as&&(e+="AS "+this.using.as+" "),e+="ON "+this.on.toString()+" ",this.matches.forEach(function(t){e+="WHEN ",t.matched||(e+="NOT "),e+="MATCHED ",t.bytarget&&(e+="BY TARGET "),t.bysource&&(e+="BY SOURCE "),t.expr&&(e+="AND "+t.expr.toString()+" "),e+="THEN ",t.action.delete&&(e+="DELETE "),t.action.insert&&(e+="INSERT ",t.action.columns&&(e+="("+t.action.columns.toString()+") "),t.action.values&&(e+="VALUES ("+t.action.values.toString()+") "),t.action.defaultvalues&&(e+="DEFAULT VALUES ")),t.action.update&&(e+="UPDATE ",e+=t.action.update.map(function(e){return e.toString()}).join(",")+" ")}),e},Q.Merge.prototype.execute=function(e,t,r){var n=1;return r&&(n=r(n)),n},Q.CreateDatabase=function(e){return Q.extend(this,e)},Q.CreateDatabase.prototype.toString=function(){var e="CREATE";return this.engineid&&(e+=" "+this.engineid),e+=" DATABASE",this.ifnotexists&&(e+=" IF NOT EXISTS"),e+=" "+this.databaseid,this.args&&this.args.length>0&&(e+="("+this.args.map(function(e){return e.toString()}).join(", ")+")"),this.as&&(e+=" AS "+this.as),e},Q.CreateDatabase.prototype.execute=function(e,t,r){if(this.args&&this.args.length>0&&this.args.map(function(e){return new Function("params,alasql","var y;return "+e.toJS())(t,A)}),this.engineid){var n=A.engines[this.engineid].createDatabase(this.databaseid,this.args,this.ifnotexists,this.as,r);return n}var a=this.databaseid;if(A.databases[a])throw new Error("Database '"+a+"' already exists");var n=(new A.Database(a),1);return r?r(n):n},Q.AttachDatabase=function(e){return Q.extend(this,e)},Q.AttachDatabase.prototype.toString=function(){var e="ATTACH";return this.engineid&&(e+=" "+this.engineid),e+=" DATABASE "+this.databaseid,args&&(e+="(",args.length>0&&(e+=args.map(function(e){return e.toString()}).join(", ")),e+=")"),this.as&&(e+=" AS "+this.as),e},Q.AttachDatabase.prototype.execute=function(e,t,r){if(!A.engines[this.engineid])throw new Error('Engine "'+this.engineid+'" is not defined.');return A.engines[this.engineid].attachDatabase(this.databaseid,this.as,this.args,t,r)},Q.DetachDatabase=function(e){return Q.extend(this,e)},Q.DetachDatabase.prototype.toString=function(){var e="DETACH";return e+=" DATABASE "+this.databaseid},Q.DetachDatabase.prototype.execute=function(e,t,r){if(!A.databases[this.databaseid].engineid)throw new Error('Cannot detach database "'+this.engineid+'", because it was not attached.');var n,a=this.databaseid;if(a==A.DEFAULTDATABASEID)throw new Error("Drop of default database is prohibited");if(A.databases[a])delete A.databases[a],a==A.useid&&A.use(),n=1;else{if(!this.ifexists)throw new Error("Database '"+a+"' does not exist");n=0}return r&&r(n),n},Q.UseDatabase=function(e){return Q.extend(this,e)},Q.UseDatabase.prototype.toString=function(){return"USE DATABASE "+this.databaseid},Q.UseDatabase.prototype.execute=function(e,t,r){var n=this.databaseid;if(!A.databases[n])throw new Error("Database '"+n+"' does not exist");A.use(n);return r&&r(1),1},Q.DropDatabase=function(e){return Q.extend(this,e)},Q.DropDatabase.prototype.toString=function(){var e="DROP";return this.ifexists&&(e+=" IF EXISTS"),e+=" DATABASE "+this.databaseid},Q.DropDatabase.prototype.execute=function(e,t,r){if(this.engineid)return A.engines[this.engineid].dropDatabase(this.databaseid,this.ifexists,r);var n,a=this.databaseid;if(a==A.DEFAULTDATABASEID)throw new Error("Drop of default database is prohibited");if(A.databases[a]){if(A.databases[a].engineid)throw new Error("Cannot drop database '"+a+"', because it is attached. Detach it.");delete A.databases[a],a==A.useid&&A.use(),n=1}else{if(!this.ifexists)throw new Error("Database '"+a+"' does not exist");n=0}return r&&r(n),n},Q.Declare=function(e){return Q.extend(this,e)},Q.Declare.prototype.toString=function(){var e="DECLARE ";return this.declares&&this.declares.length>0&&(e=this.declares.map(function(e){var t="";return t+="@"+e.variable+" ",t+=e.dbtypeid,this.dbsize&&(t+="("+this.dbsize,this.dbprecision&&(t+=","+this.dbprecision),t+=")"),e.expression&&(t+=" = "+e.expression.toString()),t}).join(",")),e},Q.Declare.prototype.execute=function(e,t,r){var n=1;return this.declares&&this.declares.length>0&&this.declares.map(function(e){var r=e.dbtypeid;A.fn[r]||(r=r.toUpperCase()),A.declares[e.variable]={dbtypeid:r,dbsize:e.dbsize,dbprecision:e.dbprecision},e.expression&&(A.vars[e.variable]=new Function("params,alasql","return "+e.expression.toJS("({})","",null))(t,A),A.declares[e.variable]&&(A.vars[e.variable]=A.stdfn.CONVERT(A.vars[e.variable],A.declares[e.variable])))}),r&&(n=r(n)),n},Q.ShowDatabases=function(e){return Q.extend(this,e)},Q.ShowDatabases.prototype.toString=function(){var e="SHOW DATABASES";return this.like&&(e+="LIKE "+this.like.toString()),e},Q.ShowDatabases.prototype.execute=function(e,t,r){if(this.engineid)return A.engines[this.engineid].showDatabases(this.like,r);var n=this,a=[];for(dbid in A.databases)a.push({databaseid:dbid});return n.like&&a&&a.length>0&&(a=a.filter(function(e){return A.utils.like(n.like.value,e.databaseid)})),r&&r(a),a},Q.ShowTables=function(e){return Q.extend(this,e)},Q.ShowTables.prototype.toString=function(){var e="SHOW TABLES";return this.databaseid&&(e+=" FROM "+this.databaseid),this.like&&(e+=" LIKE "+this.like.toString()),e},Q.ShowTables.prototype.execute=function(e,t,r){var n=A.databases[this.databaseid||e],a=this,s=[];for(tableid in n.tables)s.push({tableid:tableid});return a.like&&s&&s.length>0&&(s=s.filter(function(e){return A.utils.like(a.like.value,e.tableid)})),r&&r(s),s},Q.ShowColumns=function(e){return Q.extend(this,e)},Q.ShowColumns.prototype.toString=function(){var e="SHOW COLUMNS";return this.table.tableid&&(e+=" FROM "+this.table.tableid),this.databaseid&&(e+=" FROM "+this.databaseid),e},Q.ShowColumns.prototype.execute=function(e){var t=A.databases[this.databaseid||e],r=t.tables[this.table.tableid];if(r&&r.columns){return r.columns.map(function(e){return{columnid:e.columnid,dbtypeid:e.dbtypeid,dbsize:e.dbsize}})}return[]},Q.ShowIndex=function(e){return Q.extend(this,e)},Q.ShowIndex.prototype.toString=function(){var e="SHOW INDEX";return this.table.tableid&&(e+=" FROM "+this.table.tableid),
this.databaseid&&(e+=" FROM "+this.databaseid),e},Q.ShowIndex.prototype.execute=function(e){var t=A.databases[this.databaseid||e],r=t.tables[this.table.tableid],n=[];if(r&&r.indices)for(var a in r.indices)n.push({hh:a,len:Object.keys(r.indices[a]).length});return n},Q.ShowCreateTable=function(e){return Q.extend(this,e)},Q.ShowCreateTable.prototype.toString=function(){var e="SHOW CREATE TABLE "+this.table.tableid;return this.databaseid&&(e+=" FROM "+this.databaseid),e},Q.ShowCreateTable.prototype.execute=function(e){var t=A.databases[this.databaseid||e],r=t.tables[this.table.tableid];if(r){var n="CREATE TABLE "+this.table.tableid+" (",a=[];return r.columns&&(r.columns.forEach(function(e){var t=e.columnid+" "+e.dbtypeid;e.dbsize&&(t+="("+e.dbsize+")"),e.primarykey&&(t+=" PRIMARY KEY"),a.push(t)}),n+=a.join(", ")),n+=")"}throw new Error('There is no such table "'+this.table.tableid+'"')},Q.SetVariable=function(e){return Q.extend(this,e)},Q.SetVariable.prototype.toString=function(){var e="SET ";return void 0!==this.value&&(e+=this.variable.toUpperCase()+" "+(this.value?"ON":"OFF")),this.expression&&(e+=this.method+this.variable+" = "+this.expression.toString()),e},Q.SetVariable.prototype.execute=function(e,t,r){if(void 0!==this.value){var n=this.value;"ON"==n?n=!0:"OFF"==n&&(n=!1),A.options[this.variable]=n}else if(this.expression){this.exists&&(this.existsfn=this.exists.map(function(t){var r=t.compile(e);return r.query&&!r.query.modifier&&(r.query.modifier="RECORDSET"),r})),this.queries&&(this.queriesfn=this.queries.map(function(t){var r=t.compile(e);return r.query&&!r.query.modifier&&(r.query.modifier="RECORDSET"),r}));var a=new Function("params,alasql","return "+this.expression.toJS("({})","",null)).bind(this)(t,A);if(A.declares[this.variable]&&(a=A.stdfn.CONVERT(a,A.declares[this.variable])),this.props&&this.props.length>0){if("@"==this.method)var s="alasql.vars['"+this.variable+"']";else var s="params['"+this.variable+"']";s+=this.props.map(function(e){return"string"==typeof e?"['"+e+"']":"number"==typeof e?"["+e+"]":"["+e.toJS()+"]"}).join(),new Function("value,params,alasql","var y;"+s+"=value")(a,t,A)}else"@"==this.method?A.vars[this.variable]=a:t[this.variable]=a}var a=1;return r&&(a=r(a)),a},A.test=function(e,t,r){if(0===arguments.length)return void A.log(A.con.results);if(1===arguments.length){var n=Date.now();return r(),void A.con.log(Date.now()-n)}2===arguments.length&&(r=t,t=1);for(var n=Date.now(),a=0;a<t;a++)r();A.con.results[e]=Date.now()-n},A.log=function(e,t){var r=A.useid,n=A.options.logtarget;C.isNode&&(n="console");var a;if(a="string"==typeof e?A(e,t):e,"console"===n||C.isNode)"string"==typeof e&&A.options.logprompt&&console.log(r+">",e),Array.isArray(a)&&console.table?console.table(a):console.log(ce(a));else{var s;s="output"===n?document.getElementsByTagName("output")[0]:"string"==typeof n?document.getElementById(n):n;var i="";if("string"==typeof e&&A.options.logprompt&&(i+="<pre><code>"+A.pretty(e)+"</code></pre>"),Array.isArray(a))if(0===a.length)i+="<p>[ ]</p>";else if("object"!=typeof a[0]||Array.isArray(a[0]))for(var o=0,u=a.length;o<u;o++)i+="<p>"+g(a[o])+"</p>";else i+=g(a);else i+=g(a);s.innerHTML+=i}},A.clear=function(){var e=A.options.logtarget;if(C.isNode||C.isMeteorServer)console.clear&&console.clear();else{var t;t="output"===e?document.getElementsByTagName("output")[0]:"string"==typeof e?document.getElementById(e):e,t.innerHTML=""}},A.write=function(e){var t=A.options.logtarget;if(C.isNode||C.isMeteorServer)console.log&&console.log(e);else{var r;r="output"===t?document.getElementsByTagName("output")[0]:"string"==typeof t?document.getElementById(t):t,r.innerHTML+=e}},A.prompt=function(e,t,r){if(C.isNode)throw new Error("The prompt not realized for Node.js");var n=0;if("string"==typeof e&&(e=document.getElementById(e)),"string"==typeof t&&(t=document.getElementById(t)),t.textContent=A.useid,r){A.prompthistory.push(r),n=A.prompthistory.length;try{var a=Date.now();A.log(r),A.write('<p style="color:blue">'+(Date.now()-a)+" ms</p>")}catch(e){A.write("<p>"+olduseid+"&gt;&nbsp;<b>"+sql+"</b></p>"),A.write('<p style="color:red">'+e+"<p>")}}var s=e.getBoundingClientRect().top+document.getElementsByTagName("body")[0].scrollTop;m(document.getElementsByTagName("body")[0],s,500),e.onkeydown=function(r){if(13===r.which){var a=e.value,s=A.useid;e.value="",A.prompthistory.push(a),n=A.prompthistory.length;try{var i=Date.now();A.log(a),A.write('<p style="color:blue">'+(Date.now()-i)+" ms</p>")}catch(e){A.write("<p>"+s+"&gt;&nbsp;"+A.pretty(a,!1)+"</p>"),A.write('<p style="color:red">'+e+"<p>")}e.focus(),t.textContent=A.useid;var o=e.getBoundingClientRect().top+document.getElementsByTagName("body")[0].scrollTop;m(document.getElementsByTagName("body")[0],o,500)}else 38===r.which?(n--,n<0&&(n=0),A.prompthistory[n]&&(e.value=A.prompthistory[n],r.preventDefault())):40===r.which&&(n++,n>=A.prompthistory.length?(n=A.prompthistory.length,e.value=""):A.prompthistory[n]&&(e.value=A.prompthistory[n],r.preventDefault()))}},Q.BeginTransaction=function(e){return Q.extend(this,e)},Q.BeginTransaction.prototype.toString=function(){return"BEGIN TRANSACTION"},Q.BeginTransaction.prototype.execute=function(e,t,r){return A.databases[e].engineid?A.engines[A.databases[A.useid].engineid].begin(e,r):(r&&r(1),1)},Q.CommitTransaction=function(e){return Q.extend(this,e)},Q.CommitTransaction.prototype.toString=function(){return"COMMIT TRANSACTION"},Q.CommitTransaction.prototype.execute=function(e,t,r){return A.databases[e].engineid?A.engines[A.databases[A.useid].engineid].commit(e,r):(r&&r(1),1)},Q.RollbackTransaction=function(e){return Q.extend(this,e)},Q.RollbackTransaction.prototype.toString=function(){return"ROLLBACK TRANSACTION"},Q.RollbackTransaction.prototype.execute=function(e,t,r){return A.databases[e].engineid?A.engines[A.databases[e].engineid].rollback(e,r):(r&&r(1),1)},A.options.tsql&&(A.stdfn.OBJECT_ID=function(e,t){void 0===t&&(t="T"),t=t.toUpperCase();var r=e.split("."),n=A.useid,a=r[0];2==r.length&&(n=r[0],a=r[1]);var s=A.databases[n].tables;n=A.databases[n].databaseid;for(var i in s)if(i==a){if(s[i].view&&"V"==t)return n+"."+i;if(!s[i].view&&"T"==t)return n+"."+i;return}}),A.options.mysql,(A.options.mysql||A.options.sqlite)&&(A.from.INFORMATION_SCHEMA=function(e,t,r,n,a){if("VIEWS"==e||"TABLES"==e){var s=[];for(var i in A.databases){var o=A.databases[i].tables;for(var u in o)(o[u].view&&"VIEWS"==e||!o[u].view&&"TABLES"==e)&&s.push({TABLE_CATALOG:i,TABLE_NAME:u})}return r&&(s=r(s,n,a)),s}throw new Error("Unknown INFORMATION_SCHEMA table")}),A.options.postgres,A.options.oracle,A.options.sqlite,A.into.SQL=function(e,t,r,n,a){var s;"object"==typeof e&&(t=e,e=void 0);var i={};if(A.utils.extend(i,t),void 0===i.tableid)throw new Error("Table for INSERT TO is not defined.");var o="";0===n.length&&"object"==typeof r[0]&&(n=Object.keys(r[0]).map(function(e){return{columnid:e}}));for(var u=0,c=r.length;u<c;u++)o+="INSERT INTO "+t.tableid+"(",o+=n.map(function(e){return e.columnid}).join(","),o+=") VALUES (",o+=n.map(function(e){var t=r[u][e.columnid];return e.typeid?"STRING"!==e.typeid&&"VARCHAR"!==e.typeid&&"NVARCHAR"!==e.typeid&&"CHAR"!==e.typeid&&"NCHAR"!==e.typeid||(t="'"+O(t)+"'"):"string"==typeof t&&(t="'"+O(t)+"'"),t}),o+=");\n";return e=A.utils.autoExtFilename(e,"sql",t),s=A.utils.saveFile(e,o),a&&(s=a(s)),s},A.into.HTML=function(e,t,r,n,a){var s=1;if("object"!=typeof exports){var i={headers:!0};A.utils.extend(i,t);var o=document.querySelector(e);if(!o)throw new Error("Selected HTML element is not found");0===n.length&&"object"==typeof r[0]&&(n=Object.keys(r[0]).map(function(e){return{columnid:e}}));var u=document.createElement("table"),c=document.createElement("thead");if(u.appendChild(c),i.headers){for(var l=document.createElement("tr"),h=0;h<n.length;h++){var d=document.createElement("th");d.textContent=n[h].columnid,l.appendChild(d)}c.appendChild(l)}var f=document.createElement("tbody");u.appendChild(f);for(var p=0;p<r.length;p++){for(var l=document.createElement("tr"),h=0;h<n.length;h++){var d=document.createElement("td");d.textContent=r[p][n[h].columnid],l.appendChild(d)}f.appendChild(l)}A.utils.domEmptyChildren(o),o.appendChild(u)}return a&&(s=a(s)),s},A.into.JSON=function(e,t,r,n,a){var s=1;"object"==typeof e&&(t=e,e=void 0);var i=JSON.stringify(r);return e=A.utils.autoExtFilename(e,"json",t),s=A.utils.saveFile(e,i),a&&(s=a(s)),s},A.into.TXT=function(e,t,r,n,a){0===n.length&&r.length>0&&(n=Object.keys(r[0]).map(function(e){return{columnid:e}})),"object"==typeof e&&(t=e,e=void 0);var s=r.length,i="";if(r.length>0){var o=n[0].columnid;i+=r.map(function(e){return e[o]}).join("\n")}return e=A.utils.autoExtFilename(e,"txt",t),s=A.utils.saveFile(e,i),a&&(s=a(s)),s},A.into.TAB=A.into.TSV=function(e,t,r,n,a){var s={};return A.utils.extend(s,t),s.separator="\t",e=A.utils.autoExtFilename(e,"tab",t),s.autoExt=!1,A.into.CSV(e,s,r,n,a)},A.into.CSV=function(e,t,r,n,a){0===n.length&&r.length>0&&(n=Object.keys(r[0]).map(function(e){return{columnid:e}})),"object"==typeof e&&(t=e,e=void 0);var s={headers:!0};s.separator=";",s.quote='"',s.utf8Bom=!0,t&&!t.headers&&void 0!==t.headers&&(s.utf8Bom=!1),A.utils.extend(s,t);var i=r.length,o=s.utf8Bom?"\ufeff":"";return s.headers&&(o+=s.quote+n.map(function(e){return e.columnid.trim()}).join(s.quote+s.separator+s.quote)+s.quote+"\r\n"),r.forEach(function(e){o+=n.map(function(t){var r=e[t.columnid];return""!==s.quote&&(r=(r+"").replace(new RegExp("\\"+s.quote,"g"),s.quote+s.quote)),+r!=r&&(r=s.quote+r+s.quote),r}).join(s.separator)+"\r\n"}),e=A.utils.autoExtFilename(e,"csv",t),i=A.utils.saveFile(e,o,null,{disableAutoBom:!0}),a&&(i=a(i)),i},A.into.XLS=function(e,t,r,n,a){"object"==typeof e&&(t=e,e=void 0);var s={};t&&t.sheets&&(s=t.sheets);var i={headers:!0};void 0!==s.Sheet1?i=s[0]:void 0!==t&&(i=t),void 0===i.sheetid&&(i.sheetid="Sheet1");var o=function(){var e='<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" \t\txmlns="http://www.w3.org/TR/REC-html40"><head> \t\t<meta charset="utf-8" /> \t\t<!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets> ';if(e+=" <x:ExcelWorksheet><x:Name>"+i.sheetid+"</x:Name><x:WorksheetOptions><x:DisplayGridlines/>     </x:WorksheetOptions> \t\t</x:ExcelWorksheet>",e+="</x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--></head>",e+="<body",void 0!==i.style&&(e+=' style="',e+="function"==typeof i.style?i.style(i):i.style,e+='"'),e+=">",e+="<table>",void 0!==i.caption){var a=i.caption;"string"==typeof a&&(a={title:a}),e+="<caption",void 0!==a.style&&(e+=' style="',e+="function"==typeof a.style?a.style(i,a):a.style,e+='" '),e+=">",e+=a.title,e+="</caption>"}return void 0!==i.columns?n=i.columns:0==n.length&&r.length>0&&"object"==typeof r[0]&&(n=Array.isArray(r[0])?r[0].map(function(e,t){return{columnid:t}}):Object.keys(r[0]).map(function(e){return{columnid:e}})),n.forEach(function(e,t){void 0!==i.column&&G(e,i.column),void 0===e.width&&(i.column&&"undefined"!=i.column.width?e.width=i.column.width:e.width="120px"),"number"==typeof e.width&&(e.width=e.width+"px"),void 0===e.columnid&&(e.columnid=t),void 0===e.title&&(e.title=""+e.columnid.trim()),i.headers&&Array.isArray(i.headers)&&(e.title=i.headers[t])}),e+="<colgroups>",n.forEach(function(t){e+='<col style="width: '+t.width+'"></col>'}),e+="</colgroups>",i.headers&&(e+="<thead>",e+="<tr>",n.forEach(function(t,r){e+="<th ",void 0!==t.style&&(e+=' style="',e+="function"==typeof t.style?t.style(i,t,r):t.style,e+='" '),e+=">",void 0!==t.title&&(e+="function"==typeof t.title?t.title(i,t,r):t.title),e+="</th>"}),e+="</tr>",e+="</thead>"),e+="<tbody>",r&&r.length>0&&r.forEach(function(r,a){if(!(a>i.limit)){e+="<tr";var s={};G(s,i.row),i.rows&&i.rows[a]&&G(s,i.rows[a]),void 0!==s&&void 0!==s.style&&(e+=' style="',e+="function"==typeof s.style?s.style(i,r,a):s.style,e+='" '),e+=">",n.forEach(function(n,o){var u={};G(u,i.cell),G(u,s.cell),void 0!==i.column&&G(u,i.column.cell),G(u,n.cell),i.cells&&i.cells[a]&&i.cells[a][o]&&G(u,i.cells[a][o]);var c=r[n.columnid];"function"==typeof u.value&&(c=u.value(c,i,r,n,u,a,o));var l=u.typeid;"function"==typeof l&&(l=l(c,i,r,n,u,a,o)),void 0===l&&("number"==typeof c?l="number":"string"==typeof c?l="string":"boolean"==typeof c?l="boolean":"object"==typeof c&&c instanceof Date&&(l="date"));var h="";"money"==l?h='mso-number-format:"\\#\\,\\#\\#0\\\\ _р_\\.";white-space:normal;':"number"==l?h=" ":"date"==l?h='mso-number-format:"Short Date";':t.types&&t.types[l]&&t.types[l].typestyle&&(h=t.types[l].typestyle),h=h||'mso-number-format:"\\@";',e+="<td style='"+h+"' ",void 0!==u.style&&(e+=' style="',e+="function"==typeof u.style?u.style(c,i,r,n,a,o):u.style,e+='" '),e+=">";var d=u.format;if(void 0===c)e+="";else if(void 0!==d)if("function"==typeof d)e+=d(c);else{if("string"!=typeof d)throw new Error("Unknown format type. Should be function or string");e+=c}else e+="number"==l||"date"==l?c.toString():"money"==l?(+c).toFixed(2):c;e+="</td>"}),e+="</tr>"}}),e+="</tbody>",e+="</table>",e+="</body>",e+="</html>"}();e=A.utils.autoExtFilename(e,"xls",t);var u=A.utils.saveFile(e,o);return a&&(u=a(u)),u},A.into.XLSXML=function(e,t,r,n,a){t=t||{},"object"==typeof e&&(t=e,e=void 0);var s={};t&&t.sheets?s=t.sheets:s.Sheet1=t,e=A.utils.autoExtFilename(e,"xls",t);var i=A.utils.saveFile(e,function(){function e(e){var t="";for(var r in e){t+="<"+r;for(var n in e[r])t+=" ",t+="x:"==n.substr(0,2)?n:"ss:",t+=n+'="'+e[r][n]+'"';t+="/>"}var s=D(t);return o[s]||(o[s]={styleid:u},a+='<Style ss:ID="s'+u+'">',a+=t,a+="</Style>",u++),"s"+o[s].styleid}var a="",i=" </Styles>",o={},u=62;for(var c in s){var l=s[c];void 0!==l.columns?n=l.columns:0==n.length&&r.length>0&&"object"==typeof r[0]&&(n=Array.isArray(r[0])?r[0].map(function(e,t){return{columnid:t}}):Object.keys(r[0]).map(function(e){return{columnid:e}})),n.forEach(function(e,t){void 0!==l.column&&G(e,l.column),void 0===e.width&&(l.column&&void 0!==l.column.width?e.width=l.column.width:e.width=120),"number"==typeof e.width&&(e.width=e.width),void 0===e.columnid&&(e.columnid=t),void 0===e.title&&(e.title=""+e.columnid.trim()),l.headers&&Array.isArray(l.headers)&&(e.title=l.headers[idx])}),i+='<Worksheet ss:Name="'+c+'"> \t  \t\t\t<Table ss:ExpandedColumnCount="'+n.length+'" ss:ExpandedRowCount="'+((l.headers?1:0)+Math.min(r.length,l.limit||r.length))+'" x:FullColumns="1" \t   \t\t\tx:FullRows="1" ss:DefaultColumnWidth="65" ss:DefaultRowHeight="15">',n.forEach(function(e,t){i+='<Column ss:Index="'+(t+1)+'" ss:AutoFitWidth="0" ss:Width="'+e.width+'"/>'}),l.headers&&(i+='<Row ss:AutoFitHeight="0">',n.forEach(function(t,r){if(i+="<Cell ",void 0!==t.style){var n={};"function"==typeof t.style?G(n,t.style(l,t,r)):G(n,t.style),i+='ss:StyleID="'+e(n)+'"'}i+='><Data ss:Type="String">',void 0!==t.title&&(i+="function"==typeof t.title?t.title(l,t,r):t.title),i+="</Data></Cell>"}),i+="</Row>"),r&&r.length>0&&r.forEach(function(r,a){if(!(a>l.limit)){var s={};if(G(s,l.row),l.rows&&l.rows[a]&&G(s,l.rows[a]),i+="<Row ",void 0!==s){var o={};void 0!==s.style&&("function"==typeof s.style?G(o,s.style(l,r,a)):G(o,s.style),i+='ss:StyleID="'+e(o)+'"')}i+=">",n.forEach(function(n,o){var u={};G(u,l.cell),G(u,s.cell),void 0!==l.column&&G(u,l.column.cell),G(u,n.cell),l.cells&&l.cells[a]&&l.cells[a][o]&&G(u,l.cells[a][o]);var c=r[n.columnid];"function"==typeof u.value&&(c=u.value(c,l,r,n,u,a,o));var h=u.typeid;"function"==typeof h&&(h=h(c,l,r,n,u,a,o)),void 0===h&&("number"==typeof c?h="number":"string"==typeof c?h="string":"boolean"==typeof c?h="boolean":"object"==typeof c&&c instanceof Date&&(h="date"));var d="String";"number"==h?d="Number":"date"==h&&(d="Date");var f="";"money"==h?f='mso-number-format:"\\#\\,\\#\\#0\\\\ _р_\\.";white-space:normal;':"number"==h?f=" ":"date"==h?f='mso-number-format:"Short Date";':t.types&&t.types[h]&&t.types[h].typestyle&&(f=t.types[h].typestyle),f=f||'mso-number-format:"\\@";',i+="<Cell ";var p={};void 0!==u.style&&("function"==typeof u.style?G(p,u.style(c,l,r,n,a,o)):G(p,u.style),i+='ss:StyleID="'+e(p)+'"'),i+=">",i+='<Data ss:Type="'+d+'">';var b=u.format;if(void 0===c)i+="";else if(void 0!==b)if("function"==typeof b)i+=b(c);else{if("string"!=typeof b)throw new Error("Unknown format type. Should be function or string");i+=c}else i+="number"==h||"date"==h?c.toString():"money"==h?(+c).toFixed(2):c;i+="</Data></Cell>"}),i+="</Row>"}}),i+="</Table></Worksheet>"}return i+="</Workbook>",'<?xml version="1.0"?> \t\t<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" \t\t xmlns:o="urn:schemas-microsoft-com:office:office" \t\t xmlns:x="urn:schemas-microsoft-com:office:excel" \t\t xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" \t\t xmlns:html="http://www.w3.org/TR/REC-html40"> \t\t <DocumentProperties xmlns="urn:schemas-microsoft-com:office:office"> \t\t </DocumentProperties> \t\t <OfficeDocumentSettings xmlns="urn:schemas-microsoft-com:office:office"> \t\t  <AllowPNG/> \t\t </OfficeDocumentSettings> \t\t <ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel"> \t\t  <ActiveSheet>0</ActiveSheet> \t\t </ExcelWorkbook> \t\t <Styles> \t\t  <Style ss:ID="Default" ss:Name="Normal"> \t\t   <Alignment ss:Vertical="Bottom"/> \t\t   <Borders/> \t\t   <Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="12" ss:Color="#000000"/> \t\t   <Interior/> \t\t   <NumberFormat/> \t\t   <Protection/> \t\t  </Style>'+a+i}());return a&&(i=a(i)),i},A.into.XLSX=function(e,t,r,n,s){function i(){"object"==typeof t&&Array.isArray(t)?r&&r.length>0&&r.forEach(function(e,r){o(t[r],e,void 0,r+1)}):o(t,r,n,1),u(s)}function o(e,t,r,n){var a={sheetid:"Sheet "+n,headers:!0};A.utils.extend(a,e);var s=Object.keys(t).length;(!r||0==r.length)&&s>0&&(r=Object.keys(t[0]).map(function(e){return{columnid:e}}));var i={};h.SheetNames.indexOf(a.sheetid)>-1?i=h.Sheets[a.sheetid]:(h.SheetNames.push(a.sheetid),h.Sheets[a.sheetid]={},i=h.Sheets[a.sheetid]);var o="A1";a.range&&(o=a.range);var u=A.utils.xlscn(o.match(/[A-Z]+/)[0]),c=+o.match(/[0-9]+/)[0]-1;if(h.Sheets[a.sheetid]["!ref"])var l=h.Sheets[a.sheetid]["!ref"],d=A.utils.xlscn(l.match(/[A-Z]+/)[0]),f=+l.match(/[0-9]+/)[0]-1;else var d=1,f=1;var p=Math.max(u+r.length,d),b=Math.max(c+s+2,f),E=c+1;h.Sheets[a.sheetid]["!ref"]="A1:"+A.utils.xlsnc(p)+b,a.headers&&(r.forEach(function(e,t){i[A.utils.xlsnc(u+t)+""+E]={v:e.columnid.trim()}}),E++);for(var g=0;g<s;g++)r.forEach(function(e,r){var n={v:t[g][e.columnid]};"number"==typeof t[g][e.columnid]?n.t="n":"string"==typeof t[g][e.columnid]?n.t="s":"boolean"==typeof t[g][e.columnid]?n.t="b":"object"==typeof t[g][e.columnid]&&t[g][e.columnid]instanceof Date&&(n.t="d"),i[A.utils.xlsnc(u+r)+""+E]=n}),E++}function u(t){var r;if(void 0===e)c=h;else if(r=B(),C.isNode||C.isMeteorServer)r.writeFile(h,e);else{var n={bookType:"xlsx",bookSST:!1,type:"binary"},s=r.write(h,n);if(9==a())throw new Error("Cannot save XLSX files in IE9. Please use XLS() export function");Ee(new Blob([function(e){for(var t=new ArrayBuffer(e.length),r=new Uint8Array(t),n=0;n!=e.length;++n)r[n]=255&e.charCodeAt(n);return t}(s)],{type:"application/octet-stream"}),e)}}var c=1;P(n,[{columnid:"_"}])&&(r=r.map(function(e){return e._}),n=void 0),e=A.utils.autoExtFilename(e,"xlsx",t);var l=B();"object"==typeof e&&(t=e,e=void 0);var h={SheetNames:[],Sheets:{}};return t.sourcefilename?A.utils.loadBinaryFile(t.sourcefilename,!!s,function(e){h=l.read(e,{type:"binary"}),i()}):i(),s&&(c=s(c)),c},A.from.METEOR=function(e,t,r,n,a){var s=e.find(t).fetch();return r&&(s=r(s,n,a)),s},A.from.TABLETOP=function(e,t,r,n,a){var s=[],i={headers:!0,simpleSheet:!0,key:e};return A.utils.extend(i,t),i.callback=function(e){for(var t=0;t<e.length;t++)for(var i in e[t])e[t][i]==+e[t][i]&&e[t].hasOwnProperty(i)&&(e[t][i]=+e[t][i]);s=e,r&&(s=r(s,n,a))},Tabletop.init(i),null},A.from.HTML=function(e,t,r,n,a){var s={};A.utils.extend(s,t);var i=document.querySelector(e);if(!i&&"TABLE"!==i.tagName)throw new Error("Selected HTML element is not a TABLE");var o=[],u=s.headers;if(u&&!Array.isArray(u)){u=[];for(var c=i.querySelector("thead tr").children,l=0;l<c.length;l++)c.item(l).style&&"none"===c.item(l).style.display&&s.skipdisplaynone?u.push(void 0):u.push(c.item(l).textContent)}for(var h=i.querySelectorAll("tbody tr"),d=0;d<h.length;d++){for(var f=h.item(d).children,p={},l=0;l<f.length;l++)f.item(l).style&&"none"===f.item(l).style.display&&s.skipdisplaynone||(u?p[u[l]]=f.item(l).textContent:p[l]=f.item(l).textContent);o.push(p)}return r&&(o=r(o,n,a)),o},A.from.RANGE=function(e,t,r,n,a){for(var s=[],i=e;i<=t;i++)s.push(i);return r&&(s=r(s,n,a)),s},A.from.FILE=function(e,t,r,n,a){var s;if("string"==typeof e)s=e;else{if(!(e instanceof Event))throw new Error("Wrong usage of FILE() function");s=e.target.files[0].name}var i=s.split("."),o=i[i.length-1].toUpperCase();if(A.from[o])return A.from[o](e,t,r,n,a);throw new Error("Cannot recognize file type for loading")},A.from.JSON=function(e,t,r,n,a){var s;return e=A.utils.autoExtFilename(e,"json",t),A.utils.loadFile(e,!!r,function(e){s=JSON.parse(e),r&&(s=r(s,n,a))}),s},A.from.TXT=function(e,t,r,n,a){var s;return e=A.utils.autoExtFilename(e,"txt",t),A.utils.loadFile(e,!!r,function(e){s=e.split(/\r?\n/),""===s[s.length-1]&&s.pop();for(var t=0,i=s.length;t<i;t++)s[t]==+s[t]&&(s[t]=+s[t]),s[t]=[s[t]];r&&(s=r(s,n,a))}),s},A.from.TAB=A.from.TSV=function(e,t,r,n,a){return t=t||{},t.separator="\t",e=A.utils.autoExtFilename(e,"tab",t),t.autoext=!1,A.from.CSV(e,t,r,n,a)},A.from.CSV=function(e,t,r,n,a){function s(e){function t(){if(E>=b)return f;if(c)return c=!1,d;var t=E;if(e.charCodeAt(t)===h){for(var r=t;r++<b;)if(e.charCodeAt(r)===h){if(e.charCodeAt(r+1)!==h)break;++r}E=r+2;var n=e.charCodeAt(r+1);return 13===n?(c=!0,10===e.charCodeAt(r+2)&&++E):10===n&&(c=!0),e.substring(t+1,r).replace(/""/g,'"')}for(;E<b;){var n=e.charCodeAt(E++),a=1;if(10===n)c=!0;else if(13===n)c=!0,10===e.charCodeAt(E)&&(++E,++a);else if(n!==l)continue;return e.substring(t,E-a)}return e.substring(t)}for(var s,c,l=i.separator.charCodeAt(0),h=i.quote.charCodeAt(0),d={},f={},p=[],b=e.length,E=0,g=0;(s=t())!==f;){for(var m=[];s!==d&&s!==f;)m.push(s.trim()),s=t();if(i.headers){if(0===g){if("boolean"==typeof i.headers)u=m;else if(Array.isArray(i.headers)){u=i.headers;var v={};u.forEach(function(e,t){v[e]=m[t],void 0!==v[e]&&0!==v[e].length&&v[e].trim()==+v[e]&&(v[e]=+v[e])}),p.push(v)}}else{var v={};u.forEach(function(e,t){v[e]=m[t],void 0!==v[e]&&0!==v[e].length&&v[e].trim()==+v[e]&&(v[e]=+v[e])}),p.push(v)}g++}else p.push(m)}if(o=p,i.headers&&a&&a.sources&&a.sources[n]){var S=a.sources[n].columns=[];u.forEach(function(e){S.push({columnid:e})})}r&&(o=r(o,n,a))}var i={separator:",",quote:'"',headers:!0};A.utils.extend(i,t);var o,u=[];return new RegExp("\n").test(e)?s(e):(e=A.utils.autoExtFilename(e,"csv",t),A.utils.loadFile(e,!!r,s)),o},A.from.XLS=function(e,t,r,n,a){return t=t||{},e=A.utils.autoExtFilename(e,"xls",t),t.autoExt=!1,S(j(),e,t,r,n,a)},A.from.XLSX=function(e,t,r,n,a){return t=t||{},e=A.utils.autoExtFilename(e,"xlsx",t),t.autoExt=!1,S(B(),e,t,r,n,a)},A.from.XML=function(e,t,r,n,a){var s;return A.utils.loadFile(e,!!r,function(e){s=T(e).root,r&&(s=r(s,n,a))}),s},A.from.GEXF=function(e,t,r,n,a){var s;return A("SEARCH FROM XML("+e+")",[],function(e){s=e,console.log(s),r&&(s=r(s))}),s},Q.Print=function(e){return Q.extend(this,e)},Q.Print.prototype.toString=function(){var e="PRINT";return this.statement&&(e+=" "+this.statement.toString()),e},Q.Print.prototype.execute=function(e,t,r){var n=this,a=1;if(A.precompile(this,e,t),this.exprs&&this.exprs.length>0){var s=this.exprs.map(function(e){return ce(new Function("params,alasql,p","var y;return "+e.toJS("({})","",null)).bind(n)(t,A))});console.log.apply(console,s)}else if(this.select){var i=this.select.execute(e,t);console.log(ce(i))}else console.log();return r&&(a=r(a)),a},Q.Source=function(e){return Q.extend(this,e)},Q.Source.prototype.toString=function(){var e="SOURCE";return this.url&&(e+=" '"+this.url+" '"),e},Q.Source.prototype.execute=function(e,t,r){var n;return x(this.url,!!r,function(e){return n=A(e),r&&(n=r(n)),n},function(e){throw e}),n},Q.Require=function(e){return Q.extend(this,e)},Q.Require.prototype.toString=function(){var e="REQUIRE";return this.paths&&this.paths.length>0&&(e+=this.paths.map(function(e){return e.toString()}).join(",")),this.plugins&&this.plugins.length>0&&(e+=this.plugins.map(function(e){return e.toUpperCase()}).join(",")),e},Q.Require.prototype.execute=function(e,t,r){var n=this,a=0,s="";return this.paths&&this.paths.length>0?this.paths.forEach(function(e){x(e.value,!!r,function(e){a++,s+=e,a<n.paths.length||(new Function("params,alasql",s)(t,A),r&&(a=r(a)))})}):this.plugins&&this.plugins.length>0?this.plugins.forEach(function(e){A.plugins[e]||x(A.path+"/alasql-"+e.toLowerCase()+".js",!!r,function(i){a++,s+=i,a<n.plugins.length||(new Function("params,alasql",s)(t,A),A.plugins[e]=!0,r&&(a=r(a)))})}):r&&(a=r(a)),a},Q.Assert=function(e){return Q.extend(this,e)},Q.Source.prototype.toString=function(){var e="ASSERT";return this.value&&(e+=" "+JSON.stringify(this.value)),e},Q.Assert.prototype.execute=function(e){if(!P(A.res,this.value))throw new Error((this.message||"Assert wrong")+": "+JSON.stringify(A.res)+" == "+JSON.stringify(this.value));return 1};var he=A.engines.WEBSQL=function(){};he.createDatabase=function(e,t,r,n){var a=openDatabase(e,t[0],t[1],t[2]);if(this.dbid){var s=A.createDatabase(this.dbid);s.engineid="WEBSQL",s.wdbid=e,sb.wdb=s}if(!a)throw new Error('Cannot create WebSQL database "'+databaseid+'"');return n&&n(1),1},he.dropDatabase=function(e){throw new Error("This is impossible to drop WebSQL database.")},he.attachDatabase=function(e,t,r,n,a){if(A.databases[t])throw new Error('Unable to attach database as "'+t+'" because it already exists');return alasqlopenDatabase(e,r[0],r[1],r[2]),1};var de=A.engines.INDEXEDDB=function(){};C.hasIndexedDB&&("function"==typeof C.global.indexedDB.webkitGetDatabaseNames?de.getDatabaseNames=C.global.indexedDB.webkitGetDatabaseNames.bind(C.global.indexedDB):(de.getDatabaseNames=function(){var e={},t={contains:function(e){return!0},notsupported:!0};return setTimeout(function(){var r={target:{result:t}};e.onsuccess(r)},0),e},de.getDatabaseNamesNotSupported=!0)),de.showDatabases=function(e,t){de.getDatabaseNames().onsuccess=function(r){var n=r.target.result;if(de.getDatabaseNamesNotSupported)throw new Error("SHOW DATABASE is not supported in this browser");var a=[];if(e)var s=new RegExp(e.value.replace(/\%/g,".*"),"g");for(var i=0;i<n.length;i++)e&&!n[i].match(s)||a.push({databaseid:n[i]});t(a)}},de.createDatabase=function(e,t,r,n,a){console.log(arguments);var s=C.global.indexedDB;if(r){s.open(e,1).onsuccess=function(e){e.target.result.close(),a&&a(1)}}else{var i=s.open(e,1);i.onupgradeneeded=function(e){console.log("abort"),e.target.transaction.abort()},i.onsuccess=function(t){if(console.log("success"),!r)throw new Error('IndexedDB: Cannot create new database "'+e+'" because it already exists');a&&a(0)}}},de.createDatabase=function(e,t,r,n,a){var s=C.global.indexedDB;if(de.getDatabaseNamesNotSupported)if(r){var i=!0,o=s.open(e);o.onupgradeneeded=function(e){i=!1},o.onsuccess=function(e){e.target.result.close(),i?a&&a(0):a&&a(1)}}else{var u=s.open(e);u.onupgradeneeded=function(e){e.target.transaction.abort()},u.onabort=function(e){a&&a(1)},u.onsuccess=function(t){throw t.target.result.close(),new Error('IndexedDB: Cannot create new database "'+e+'" because it already exists')}}else{var u=de.getDatabaseNames();u.onsuccess=function(t){if(t.target.result.contains(e)){if(r)return void(a&&a(0));throw new Error('IndexedDB: Cannot create new database "'+e+'" because it already exists')}s.open(e,1).onsuccess=function(e){e.target.result.close(),a&&a(1)}}}},de.dropDatabase=function(e,t,r){var n=C.global.indexedDB;de.getDatabaseNames().onsuccess=function(a){if(!a.target.result.contains(e)){if(t)return void(r&&r(0));throw new Error('IndexedDB: Cannot drop new database "'+e+'" because it does not exist')}n.deleteDatabase(e).onsuccess=function(e){r&&r(1)}}},de.attachDatabase=function(e,t,r,n,a){if(!C.hasIndexedDB)throw new Error("The current browser does not support IndexedDB");var s=C.global.indexedDB;de.getDatabaseNames().onsuccess=function(r){if(!r.target.result.contains(e))throw new Error('IndexedDB: Cannot attach database "'+e+'" because it does not exist');s.open(e).onsuccess=function(r){var n=r.target.result,s=new A.Database(t||e);s.engineid="INDEXEDDB",s.ixdbid=e,s.tables=[];for(var i=n.objectStoreNames,o=0;o<i.length;o++)s.tables[i[o]]={};r.target.result.close(),a&&a(1)}}},de.createTable=function(e,t,r,n){var a=C.global.indexedDB,s=A.databases[e].ixdbid;de.getDatabaseNames().onsuccess=function(r){if(!r.target.result.contains(s))throw new Error('IndexedDB: Cannot create table in database "'+s+'" because it does not exist');var i=a.open(s);i.onversionchange=function(e){e.target.result.close()},i.onsuccess=function(r){var i=r.target.result.version;r.target.result.close();var o=a.open(s,i+1);o.onupgradeneeded=function(e){e.target.result.createObjectStore(t,{autoIncrement:!0})},o.onsuccess=function(e){e.target.result.close(),n&&n(1)},o.onerror=function(e){throw e},o.onblocked=function(r){throw new Error('Cannot create table "'+t+'" because database "'+e+'"  is blocked')}}}},de.dropTable=function(e,t,r,n){var a=C.global.indexedDB,s=A.databases[e].ixdbid;de.getDatabaseNames().onsuccess=function(i){if(!i.target.result.contains(s))throw new Error('IndexedDB: Cannot drop table in database "'+s+'" because it does not exist');var o=a.open(s);o.onversionchange=function(e){e.target.result.close()},o.onsuccess=function(i){var o=i.target.result.version;i.target.result.close();var u=a.open(s,o+1);u.onupgradeneeded=function(n){var a=n.target.result;if(a.objectStoreNames.contains(t))a.deleteObjectStore(t),delete A.databases[e].tables[t];else if(!r)throw new Error('IndexedDB: Cannot drop table "'+t+'" because it does not exist')},u.onsuccess=function(e){e.target.result.close(),n&&n(1)},u.onerror=function(e){throw e},u.onblocked=function(r){throw new Error('Cannot drop table "'+t+'" because database "'+e+'" is blocked')}}}},de.intoTable=function(e,t,r,n,a){var s=C.global.indexedDB,i=A.databases[e].ixdbid;s.open(i).onsuccess=function(e){for(var n=e.target.result,s=n.transaction([t],"readwrite"),i=s.objectStore(t),o=0,u=r.length;o<u;o++)i.add(r[o]);s.oncomplete=function(){n.close(),a&&a(u)}}},de.fromTable=function(e,t,r,n,a){var s=C.global.indexedDB,i=A.databases[e].ixdbid;s.open(i).onsuccess=function(e){var s=[],i=e.target.result,o=i.transaction([t]),u=o.objectStore(t),c=u.openCursor();c.onblocked=function(e){},c.onerror=function(e){},c.onsuccess=function(e){var t=e.target.result;t?(s.push(t.value),t.continue()):(i.close(),r&&r(s,n,a))}}},de.deleteFromTable=function(e,t,r,n,a){var s=C.global.indexedDB,i=A.databases[e].ixdbid;s.open(i).onsuccess=function(e){var s=e.target.result,i=s.transaction([t],"readwrite"),o=i.objectStore(t),u=o.openCursor(),c=0;u.onblocked=function(e){},u.onerror=function(e){},u.onsuccess=function(e){var t=e.target.result;t?(r&&!r(t.value,n)||(t.delete(),c++),t.continue()):(s.close(),a&&a(c))}}},de.updateTable=function(e,t,r,n,a,s){var i=C.global.indexedDB,o=A.databases[e].ixdbid;i.open(o).onsuccess=function(e){var i=e.target.result,o=i.transaction([t],"readwrite"),u=o.objectStore(t),c=u.openCursor(),l=0;c.onblocked=function(e){},c.onerror=function(e){},c.onsuccess=function(e){var t=e.target.result;if(t){if(!n||n(t.value,a)){var o=t.value;r(o,a),t.update(o),l++}t.continue()}else i.close(),s&&s(l)}}};var fe=A.engines.LOCALSTORAGE=function(){};fe.get=function(e){var t=localStorage.getItem(e);if(void 0!==t){var r=void 0;try{r=JSON.parse(t)}catch(e){throw new Error("Cannot parse JSON object from localStorage"+t)}return r}},fe.set=function(e,t){void 0===t?localStorage.removeItem(e):localStorage.setItem(e,JSON.stringify(t))},fe.storeTable=function(e,t){var r=A.databases[e],n=r.tables[t],a={};a.columns=n.columns,a.data=n.data,
a.identities=n.identities,fe.set(r.lsdbid+"."+t,a)},fe.restoreTable=function(e,t){var r=A.databases[e],n=fe.get(r.lsdbid+"."+t),a=new A.Table;for(var s in n)a[s]=n[s];return r.tables[t]=a,a.indexColumns(),a},fe.removeTable=function(e,t){var r=A.databases[e];localStorage.removeItem(r.lsdbid+"."+t)},fe.createDatabase=function(e,t,r,n,a){var s=1,i=fe.get("alasql");if(r&&i&&i.databases&&i.databases[e])s=0;else{if(i||(i={databases:{}}),i.databases&&i.databases[e])throw new Error('localStorage: Cannot create new database "'+e+'" because it already exists');i.databases[e]=!0,fe.set("alasql",i),fe.set(e,{databaseid:e,tables:{}})}return a&&(s=a(s)),s},fe.dropDatabase=function(e,t,r){var n=1,a=fe.get("alasql");if(t&&a&&a.databases&&!a.databases[e])n=0;else{if(!a){if(t)return r?r(0):0;throw new Error("There is no any AlaSQL databases in localStorage")}if(a.databases&&!a.databases[e])throw new Error('localStorage: Cannot drop database "'+e+'" because there is no such database');delete a.databases[e],fe.set("alasql",a);var s=fe.get(e);for(var i in s.tables)localStorage.removeItem(e+"."+i);localStorage.removeItem(e)}return r&&(n=r(n)),n},fe.attachDatabase=function(e,t,r,n,a){var s=1;if(A.databases[t])throw new Error('Unable to attach database as "'+t+'" because it already exists');t||(t=e);var i=new A.Database(t);if(i.engineid="LOCALSTORAGE",i.lsdbid=e,i.tables=fe.get(e).tables,!A.options.autocommit&&i.tables)for(var o in i.tables)fe.restoreTable(t,o);return a&&(s=a(s)),s},fe.showDatabases=function(e,t){var r=[],n=fe.get("alasql");if(e)var a=new RegExp(e.value.replace(/\%/g,".*"),"g");if(n&&n.databases){for(dbid in n.databases)r.push({databaseid:dbid});e&&r&&r.length>0&&(r=r.filter(function(e){return e.databaseid.match(a)}))}return t&&(r=t(r)),r},fe.createTable=function(e,t,r,n){var a=1,s=A.databases[e].lsdbid;if(fe.get(s+"."+t)&&!r)throw new Error('Table "'+t+'" alsready exists in localStorage database "'+s+'"');var i=fe.get(s);A.databases[e].tables[t];return i.tables[t]=!0,fe.set(s,i),fe.storeTable(e,t),n&&(a=n(a)),a},fe.truncateTable=function(e,t,r,n){var a=1,s=A.databases[e].lsdbid;if(A.options.autocommit)var i=fe.get(s);else var i=A.databases[e];if(!r&&!i.tables[t])throw new Error('Cannot truncate table "'+t+'" in localStorage, because it does not exist');return fe.restoreTable(e,t).data=[],fe.storeTable(e,t),n&&(a=n(a)),a},fe.dropTable=function(e,t,r,n){var a=1,s=A.databases[e].lsdbid;if(A.options.autocommit)var i=fe.get(s);else var i=A.databases[e];if(!r&&!i.tables[t])throw new Error('Cannot drop table "'+t+'" in localStorage, because it does not exist');return delete i.tables[t],fe.set(s,i),fe.removeTable(e,t),n&&(a=n(a)),a},fe.fromTable=function(e,t,r,n,a){var s=(A.databases[e].lsdbid,fe.restoreTable(e,t).data);return r&&(s=r(s,n,a)),s},fe.intoTable=function(e,t,r,n,a){var s=(A.databases[e].lsdbid,r.length),i=fe.restoreTable(e,t);return i.data||(i.data=[]),i.data=i.data.concat(r),fe.storeTable(e,t),a&&(s=a(s)),s},fe.loadTableData=function(e,t){A.databases[e],A.databases[e].lsdbid;fe.restoreTable(e,t)},fe.saveTableData=function(e,t){var r=A.databases[e],n=A.databases[e].lsdbid;fe.storeTable(n,t),r.tables[t].data=void 0},fe.commit=function(e,t){var r=A.databases[e],n=A.databases[e].lsdbid,a={databaseid:n,tables:{}};if(r.tables)for(var s in r.tables)a.tables[s]=!0,fe.storeTable(e,s);return fe.set(n,a),t?t(1):1},fe.begin=fe.commit,fe.rollback=function(e,t){return};var pe=A.engines.SQLITE=function(){};pe.createDatabase=function(e,t,r,n,a){throw new Error("Connot create SQLITE database in memory. Attach it.")},pe.dropDatabase=function(e){throw new Error("This is impossible to drop SQLite database. Detach it.")},pe.attachDatabase=function(e,t,r,n,a){if(A.databases[t])throw new Error('Unable to attach database as "'+t+'" because it already exists');if(r[0]&&r[0]instanceof Q.StringValue||r[0]instanceof Q.ParamValue){if(r[0]instanceof Q.StringValue)var s=r[0].value;else if(r[0]instanceof Q.ParamValue)var s=n[r[0].param];return A.utils.loadBinaryFile(s,!0,function(r){var n=new A.Database(t||e);n.engineid="SQLITE",n.sqldbid=e;var s=n.sqldb=new SQL.Database(r);n.tables=[],s.exec("SELECT * FROM sqlite_master WHERE type='table'")[0].values.forEach(function(e){n.tables[e[1]]={};var t=n.tables[e[1]].columns=[],r=A.parse(e[4]),a=r.statements[0].columns;a&&a.length>0&&a.forEach(function(e){t.push(e)})}),a(1)},function(e){throw new Error('Cannot open SQLite database file "'+r[0].value+'"')}),1}throw new Error("Cannot attach SQLite database without a file")},pe.fromTable=function(e,t,r,n,a){var s=A.databases[e].sqldb.exec("SELECT * FROM "+t),i=a.sources[n].columns=[];s[0].columns.length>0&&s[0].columns.forEach(function(e){i.push({columnid:e})});var o=[];s[0].values.length>0&&s[0].values.forEach(function(e){var t={};i.forEach(function(r,n){t[r.columnid]=e[n]}),o.push(t)}),r&&r(o,n,a)},pe.intoTable=function(e,t,r,n,a){for(var s=A.databases[e].sqldb,i=0,o=r.length;i<o;i++){var u="INSERT INTO "+t+" (",c=r[i],l=Object.keys(c);u+=l.join(","),u+=") VALUES (",u+=l.map(function(e){return v=c[e],"string"==typeof v&&(v="'"+v+"'"),v}).join(","),u+=")",s.exec(u)}var h=o;return a&&a(h),h};var be=A.engines.FILESTORAGE=A.engines.FILE=function(){};if(be.createDatabase=function(e,t,r,n,a){var s=1,i=t[0].value;return A.utils.fileExists(i,function(e){if(e){if(r)return s=0,a&&(s=a(s)),s;throw new Error("Cannot create new database file, because it alreagy exists")}var t={tables:{}};A.utils.saveFile(i,JSON.stringify(t),function(e){a&&(s=a(s))})}),s},be.dropDatabase=function(e,t,r){var n,a=e.value;return A.utils.fileExists(a,function(e){if(e)n=1,A.utils.deleteFile(a,function(){n=1,r&&(n=r(n))});else{if(!t)throw new Error("Cannot drop database file, because it does not exist");n=0,r&&(n=r(n))}}),n},be.attachDatabase=function(e,t,r,n,a){var s=1;if(A.databases[t])throw new Error('Unable to attach database as "'+t+'" because it already exists');var i=new A.Database(t||e);return i.engineid="FILESTORAGE",i.filename=r[0].value,x(i.filename,!!a,function(e){try{i.data=JSON.parse(e)}catch(e){throw new Error("Data in FileStorage database are corrupted")}if(i.tables=i.data.tables,!A.options.autocommit&&i.tables)for(var t in i.tables)i.tables[t].data=i.data[t];a&&(s=a(s))}),s},be.createTable=function(e,t,r,n){var a=A.databases[e],s=a.data[t];if(s&&!r)throw new Error('Table "'+t+'" alsready exists in the database "'+fsdbid+'"');var i=A.databases[e].tables[t];return a.data.tables[t]={columns:i.columns},a.data[t]=[],be.updateFile(e),n&&n(1),1},be.updateFile=function(e){var t=A.databases[e];if(t.issaving)return void(t.postsave=!0);t.issaving=!0,t.postsave=!1,A.utils.saveFile(t.filename,JSON.stringify(t.data),function(){t.issaving=!1,t.postsave&&setTimeout(function(){be.updateFile(e)},50)})},be.dropTable=function(e,t,r,n){var a=A.databases[e];if(!r&&!a.tables[t])throw new Error('Cannot drop table "'+t+'" in fileStorage, because it does not exist');return delete a.tables[t],delete a.data.tables[t],delete a.data[t],be.updateFile(e),n&&n(1),1},be.fromTable=function(e,t,r,n,a){var s=A.databases[e],i=s.data[t];return r&&(i=r(i,n,a)),i},be.intoTable=function(e,t,r,n,a){var s=A.databases[e],i=r.length,o=s.data[t];return o||(o=[]),s.data[t]=o.concat(r),be.updateFile(e),a&&a(i),i},be.loadTableData=function(e,t){var r=A.databases[e];r.tables[t].data=r.data[t]},be.saveTableData=function(e,t){var r=A.databases[e];r.data[t]=r.tables[t].data,r.tables[t].data=null,be.updateFile(e)},be.commit=function(e,t){var r=A.databases[e];if(r.tables)for(var n in r.tables)r.data.tables[n]={columns:r.tables[n].columns},r.data[n]=r.tables[n].data;return be.updateFile(e),t?t(1):1},be.begin=be.commit,be.rollback=function(e,t){function r(){setTimeout(function(){if(a.issaving)return r();A.loadFile(a.filename,!!t,function(r){a.data=r,a.tables={};for(var s in a.data.tables){var i=new A.Table({columns:a.data.tables[s].columns});G(i,a.data.tables[s]),a.tables[s]=i,A.options.autocommit||(a.tables[s].data=a.data[s]),a.tables[s].indexColumns()}delete A.databases[e],A.databases[e]=new A.Database(e),G(A.databases[e],a),A.databases[e].engineid="FILESTORAGE",A.databases[e].filename=a.filename,t&&(n=t(n))})},100)}var n=1,a=A.databases[e];a.dbversion++,r()},C.isBrowser&&!C.isWebWorker){if(!(A=A||!1))throw new Error("alasql was not found");A.worker=function(){throw new Error("Can find webworker in this enviroment")},"undefined"!=typeof Worker&&(A.worker=function(e,t,r){if(e===!0&&(e=void 0),void 0===e)for(var n=document.getElementsByTagName("script"),a=0;a<n.length;a++){if("alasql-worker.js"===n[a].src.substr(-16).toLowerCase()){e=n[a].src.substr(0,n[a].src.length-16)+"alasql.js";break}if("alasql-worker.min.js"===n[a].src.substr(-20).toLowerCase()){e=n[a].src.substr(0,n[a].src.length-20)+"alasql.min.js";break}if("alasql.js"===n[a].src.substr(-9).toLowerCase()){e=n[a].src;break}if("alasql.min.js"===n[a].src.substr(-13).toLowerCase()){e=n[a].src.substr(0,n[a].src.length-13)+"alasql.min.js";break}}if(void 0===e)throw new Error("Path to alasql.js is not specified");if(e!==!1){var s="importScripts('";s+=e,s+="');self.onmessage = function(event) {alasql(event.data.sql,event.data.params, function(data){postMessage({id:event.data.id, data:data});});}";var i=new Blob([s],{type:"text/plain"});if(A.webworker=new Worker(URL.createObjectURL(i)),A.webworker.onmessage=function(e){var t=e.data.id;A.buffer[t](e.data.data),delete A.buffer[t]},A.webworker.onerror=function(e){throw e},arguments.length>1){A("REQUIRE "+t.map(function(e){return'"'+e+'"'}).join(","),[],r)}}else if(e===!1)return void delete A.webworker});var Ee=Ee||function(e){"use strict";if(!(void 0===e||"undefined"!=typeof navigator&&/MSIE [1-9]\./.test(navigator.userAgent))){var t=e.document,r=function(){return e.URL||e.webkitURL||e},n=t.createElementNS("http://www.w3.org/1999/xhtml","a"),a="download"in n,s=function(e){var t=new MouseEvent("click");e.dispatchEvent(t)},i=/constructor/i.test(e.HTMLElement)||e.safari,o=/CriOS\/[\d]+/.test(navigator.userAgent),u=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},c=function(e){var t=function(){"string"==typeof e?r().revokeObjectURL(e):e.remove()};setTimeout(t,4e4)},l=function(e,t,r){t=[].concat(t);for(var n=t.length;n--;){var a=e["on"+t[n]];if("function"==typeof a)try{a.call(e,r||e)}catch(e){u(e)}}},h=function(e){return/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)?new Blob([String.fromCharCode(65279),e],{type:e.type}):e},d=function(t,u,d){d||(t=h(t));var f,p=this,b=t.type,E="application/octet-stream"===b,g=function(){l(p,"writestart progress write writeend".split(" "))};if(p.readyState=p.INIT,a)return f=r().createObjectURL(t),void setTimeout(function(){n.href=f,n.download=u,s(n),g(),c(f),p.readyState=p.DONE});!function(){if((o||E&&i)&&e.FileReader){var n=new FileReader;return n.onloadend=function(){var t=o?n.result:n.result.replace(/^data:[^;]*;/,"data:attachment/file;");e.open(t,"_blank")||(e.location.href=t),t=void 0,p.readyState=p.DONE,g()},n.readAsDataURL(t),void(p.readyState=p.INIT)}if(f||(f=r().createObjectURL(t)),E)e.location.href=f;else{e.open(f,"_blank")||(e.location.href=f)}p.readyState=p.DONE,g(),c(f)}()},f=d.prototype,p=function(e,t,r){return new d(e,t||e.name||"download",r)};return"undefined"!=typeof navigator&&navigator.msSaveOrOpenBlob?function(e,t,r){return t=t||e.name||"download",r||(e=h(e)),navigator.msSaveOrOpenBlob(e,t)}:(f.abort=function(){},f.readyState=f.INIT=0,f.WRITING=1,f.DONE=2,f.error=f.onwritestart=f.onprogress=f.onwrite=f.onabort=f.onerror=f.onwriteend=null,p)}}("undefined"!=typeof self&&self||"undefined"!=typeof window&&window||this.content);"undefined"!=typeof module&&module.exports?module.exports.saveAs=Ee:"undefined"!=typeof define&&null!==define&&null!==define.amd&&define("FileSaver.js",function(){return Ee}),(C.isCordova||C.isMeteorServer||C.isNode)&&console.warn("It looks like you are using the browser version of AlaSQL. Please use the alasql.fs.js file instead."),A.utils.saveAs=Ee}return new Y("alasql"),A.use("alasql"),A});
// =========
// = humps =
// =========
// Underscore-to-camelCase converter (and vice versa)
// for strings and object keys

// humps is copyright © 2012+ Dom Christie
// Released under the MIT license.


;(function(global) {

  var _processKeys = function(convert, obj, options) {
    if(!_isObject(obj) || _isDate(obj) || _isRegExp(obj) || _isBoolean(obj) || _isFunction(obj)) {
      return obj;
    }

    var output,
        i = 0,
        l = 0;

    if(_isArray(obj)) {
      output = [];
      for(l=obj.length; i<l; i++) {
        output.push(_processKeys(convert, obj[i], options));
      }
    }
    else {
      output = {};
      for(var key in obj) {
        if(obj.hasOwnProperty(key)) {
          output[convert(key, options)] = _processKeys(convert, obj[key], options);
        }
      }
    }
    return output;
  };

  // String conversion methods

  var separateWords = function(string, options) {
    options = options || {};
    var separator = options.separator || '_';
    var split = options.split || /(?=[A-Z])/;

    return string.split(split).join(separator);
  };

  var camelize = function(string) {
    if (_isNumerical(string)) {
      return string;
    }
    string = string.replace(/[\-_\s]+(.)?/g, function(match, chr) {
      return chr ? chr.toUpperCase() : '';
    });
    // Ensure 1st char is always lowercase
    return string.substr(0, 1).toLowerCase() + string.substr(1);
  };

  var pascalize = function(string) {
    var camelized = camelize(string);
    // Ensure 1st char is always uppercase
    return camelized.substr(0, 1).toUpperCase() + camelized.substr(1);
  };

  var decamelize = function(string, options) {
    return separateWords(string, options).toLowerCase();
  };

  // Utilities
  // Taken from Underscore.js

  var toString = Object.prototype.toString;

  var _isFunction = function(obj) {
    return typeof(obj) === 'function';
  };
  var _isObject = function(obj) {
    return obj === Object(obj);
  };
  var _isArray = function(obj) {
    return toString.call(obj) == '[object Array]';
  };
  var _isDate = function(obj) {
    return toString.call(obj) == '[object Date]';
  };
  var _isRegExp = function(obj) {
    return toString.call(obj) == '[object RegExp]';
  };
  var _isBoolean = function(obj) {
    return toString.call(obj) == '[object Boolean]';
  };

  // Performant way to determine if obj coerces to a number
  var _isNumerical = function(obj) {
    obj = obj - 0;
    return obj === obj;
  };

  // Sets up function which handles processing keys
  // allowing the convert function to be modified by a callback
  var _processor = function(convert, options) {
    var callback = options && 'process' in options ? options.process : options;

    if(typeof(callback) !== 'function') {
      return convert;
    }

    return function(string, options) {
      return callback(string, convert, options);
    }
  };

  var humps = {
    camelize: camelize,
    decamelize: decamelize,
    pascalize: pascalize,
    depascalize: decamelize,
    camelizeKeys: function(object, options) {
      return _processKeys(_processor(camelize, options), object);
    },
    decamelizeKeys: function(object, options) {
      return _processKeys(_processor(decamelize, options), object, options);
    },
    pascalizeKeys: function(object, options) {
      return _processKeys(_processor(pascalize, options), object);
    },
    depascalizeKeys: function () {
      return this.decamelizeKeys.apply(this, arguments);
    }
  };

  if (typeof define === 'function' && define.amd) {
    define(humps);
  } else if (typeof module !== 'undefined' && module.exports) {
    module.exports = humps;
  } else {
    global.humps = humps;
  }

})(this);

;(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define([], factory);
    } else if (typeof exports === 'object') {
        module.exports = factory();
    } else {
        root.simpleObjectQuery = factory();
    }
}(this, function (undefined) {

    search.helpers = {
        equal: function (a, b) {
            return a === b;
        },
        regexp: function (val, rule) {
            return val === rule || rule.test(val);
        },
        callback: function (val, rule) {
            return val === rule || rule.call(this, val);
        }
    };

    return {
        find: find,
        search: search,
        match: match,
        get: get,
        where: where,
        replace: replace,
        flatten: flatten
    };

    function find(obj, query) {
        return search({source: obj, query: query}).map(itemTarget);
    }

    function where(arr, query) {
        if (arr.length === 0) return arr;

        if (isArray(query)) {
            var q;
            for (var i = 0, len = query.length; i < len; i++) {
                q = query[i];

                switch (typeof q) {
                    case 'string':
                        arr = arr.map(function (obj) {
                            return get(obj, q);
                        }).filter(notUndefined);
                        break;
                    case 'function':
                        arr = q(arr);
                        break;
                    default:
                        arr = where(arr, q);
                }
            }

            return arr;
        }

        return arr.filter(function (obj) {
            return search({source: obj, query: query, recursion: false}).length > 0;
        });
    }

    function search(ops) {
        var parent = ops.parent,
            obj = ops.source,
            queries = ops.query || {},
            list = [],
            callback = ops.callback || function (item) {
                list.push(item);
            },
            helpers = search.helpers;

        var rules = keys(queries).map(function (query) {
            var path = arrayQuery(query),
                value = queries[query],
                validator = helpers.equal;

            if (isRegExp(value)) {
                validator = helpers.regexp;
            }
            else if (typeof value === 'function') {
                validator = helpers.callback;
            }

            return {
                path: path,
                value: value,
                validator: validator
            }
        });

        var zeroRules = rules.length === 0;

        var res = match({
            parent: parent,
            source: obj,
            exclude: ops.exclude,
            include: ops.include,
            query: zeroRules ? ['*'] : rules[0].path,
            recursion: ops.recursion,
            callback: function (item) {
                var valid = zeroRules || rules.every(function (rule) {
                    return rule.validator(get(item.target, rule.path), rule.value);
                });

                if (valid) return callback(item);
            }
        });

        return ops.callback ? res : list;
    }

    function match(ops) {
        var list = [],
            parent = ops.parent,
            field = ops.field,
            obj = ops.source,
            query = ops.query,
            path = ops.path || [],
            exclude = ops.exclude,
            include = ops.include,
            recursion = ops.recursion,
            callback = ops.callback || function (item) {
                list.push(item);
            };

        var res = get(obj, query);

        if (res !== undefined) {
            res = callback({
                parent: parent,
                path: path,
                field: field,
                target: obj
            });
            
            if (res !== undefined) return res;
        }

        if (recursion === false) return list;

        if (isArray(exclude)) {
            exclude = function (item) {
                return ops.exclude.indexOf(item.field) > -1;
            };
        }

        if (isArray(include)) {
            include = function (item) {
                return ops.include.indexOf(item.field) > -1;
            };
        }

        var item = {
            parent: parent,
            path: path,
            target: obj
        };

        res = each(obj, function (val, name) {
            item.field = name;
            
            if (exclude && exclude(item)) return;
            if (include && !include(item)) return;

            if (!isObject(val) || val === parent) return;

            return match({
                parent: obj,
                field: name,
                path: add(path, name),
                exclude: exclude,
                include: include,
                source: val,
                query: query,
                callback: callback
            });
        });

        return ops.callback ? res : list;
    }

    function get(obj, path) {
        if (typeof path === 'string') {
            path = arrayQuery(path);
        }

        var name;

        for (var i = 0, len = path.length; i < len; i++) {
            if (!isObject(obj)) return;

            name = path[i];

            if (name === '*' && !has(obj, name)) {
                var restPath = path.slice(i + 1);

                if (restPath.length === 0) {
                    return first(obj);
                }

                return each(obj, function (item) {
                    return get(item, restPath);
                });
            }

            if (!has(obj, name)) return;

            obj = obj[name];
        }

        return obj;
    }

    function replace(obj, query, cb) {
        var ops;

        if (arguments.length === 1) {
            ops = obj;
            cb = ops.callback;
        }
        else {
            ops = {
                source: obj,
                query: query
            };
        }

        if (typeof cb !== 'function') {
            var funcVal = cb;
            cb = function () {
                return funcVal;
            };
        }

        ops.callback = function (item) {
            var target = cb(item.target, item.parent, item.field, item.path);

            if (target === undefined) {
                if (isArray(item.parent)) {
                    item.parent.splice(item.field, 1);
                }
                else {
                    delete item.parent[item.field];
                }
            }
            else {
                item.parent[item.field] = target;
            }
        };

        search(ops);
    }

    function flatten(list) {
        return Array.prototype.concat.apply([], list);
    }

    function arrayQuery(str) {
        return typeof str === 'string' ? str.split('.') : str;
    }

    function keys(obj) {
        return Object.keys(obj);
    }

    function each(obj, cb) {
        var res;

        if (isArray(obj)) {
            for (var i = 0, len = obj.length; i < len;) {
                res = cb(obj[i], i);
                
                if (res !== undefined) {
                    return res;
                }
                
                if (obj.length < len) {
                    len = obj.length;
                }
                else {
                    i++;
                }
            }
        }
        else {
            for (var field in obj) {
                if (!obj.hasOwnProperty(field)) continue;

                res = cb(obj[field], field);
                if (res !== undefined) {
                    return res;
                }
            }
        }
    }

    function first(obj) {
        return each(obj, function (val) {
            return val;
        });
    }

    function isObject(value) {
        return value !== null && typeof value === 'object';
    }

    function isRegExp(value) {
        return value instanceof RegExp;
    }

    function isArray(value) {
        return Array.isArray(value);
    }

    function has(obj, field) {
        return Object.prototype.hasOwnProperty.call(obj, field);
    }

    function add(list, field) {
        return [].concat(list, field);
    }

    function itemTarget(item) {
        return item.target;
    }

    function notUndefined(item) {
        return item !== undefined;
    }

}));
/**
 * @license
 * Lodash <https://lodash.com/>
 * Copyright OpenJS Foundation and other contributors <https://openjsf.org/>
 * Released under MIT license <https://lodash.com/license>
 * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
 * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
 */
(function(){function n(n,t,r){switch(r.length){case 0:return n.call(t);case 1:return n.call(t,r[0]);case 2:return n.call(t,r[0],r[1]);case 3:return n.call(t,r[0],r[1],r[2])}return n.apply(t,r)}function t(n,t,r,e){for(var u=-1,i=null==n?0:n.length;++u<i;){var o=n[u];t(e,o,r(o),n)}return e}function r(n,t){for(var r=-1,e=null==n?0:n.length;++r<e&&t(n[r],r,n)!==!1;);return n}function e(n,t){for(var r=null==n?0:n.length;r--&&t(n[r],r,n)!==!1;);return n}function u(n,t){for(var r=-1,e=null==n?0:n.length;++r<e;)if(!t(n[r],r,n))return!1;
return!0}function i(n,t){for(var r=-1,e=null==n?0:n.length,u=0,i=[];++r<e;){var o=n[r];t(o,r,n)&&(i[u++]=o)}return i}function o(n,t){return!!(null==n?0:n.length)&&y(n,t,0)>-1}function f(n,t,r){for(var e=-1,u=null==n?0:n.length;++e<u;)if(r(t,n[e]))return!0;return!1}function c(n,t){for(var r=-1,e=null==n?0:n.length,u=Array(e);++r<e;)u[r]=t(n[r],r,n);return u}function a(n,t){for(var r=-1,e=t.length,u=n.length;++r<e;)n[u+r]=t[r];return n}function l(n,t,r,e){var u=-1,i=null==n?0:n.length;for(e&&i&&(r=n[++u]);++u<i;)r=t(r,n[u],u,n);
return r}function s(n,t,r,e){var u=null==n?0:n.length;for(e&&u&&(r=n[--u]);u--;)r=t(r,n[u],u,n);return r}function h(n,t){for(var r=-1,e=null==n?0:n.length;++r<e;)if(t(n[r],r,n))return!0;return!1}function p(n){return n.split("")}function _(n){return n.match($t)||[]}function v(n,t,r){var e;return r(n,function(n,r,u){if(t(n,r,u))return e=r,!1}),e}function g(n,t,r,e){for(var u=n.length,i=r+(e?1:-1);e?i--:++i<u;)if(t(n[i],i,n))return i;return-1}function y(n,t,r){return t===t?Z(n,t,r):g(n,b,r)}function d(n,t,r,e){
for(var u=r-1,i=n.length;++u<i;)if(e(n[u],t))return u;return-1}function b(n){return n!==n}function w(n,t){var r=null==n?0:n.length;return r?k(n,t)/r:Cn}function m(n){return function(t){return null==t?X:t[n]}}function x(n){return function(t){return null==n?X:n[t]}}function j(n,t,r,e,u){return u(n,function(n,u,i){r=e?(e=!1,n):t(r,n,u,i)}),r}function A(n,t){var r=n.length;for(n.sort(t);r--;)n[r]=n[r].value;return n}function k(n,t){for(var r,e=-1,u=n.length;++e<u;){var i=t(n[e]);i!==X&&(r=r===X?i:r+i);
}return r}function O(n,t){for(var r=-1,e=Array(n);++r<n;)e[r]=t(r);return e}function I(n,t){return c(t,function(t){return[t,n[t]]})}function R(n){return n?n.slice(0,H(n)+1).replace(Lt,""):n}function z(n){return function(t){return n(t)}}function E(n,t){return c(t,function(t){return n[t]})}function S(n,t){return n.has(t)}function W(n,t){for(var r=-1,e=n.length;++r<e&&y(t,n[r],0)>-1;);return r}function L(n,t){for(var r=n.length;r--&&y(t,n[r],0)>-1;);return r}function C(n,t){for(var r=n.length,e=0;r--;)n[r]===t&&++e;
return e}function U(n){return"\\"+Yr[n]}function B(n,t){return null==n?X:n[t]}function T(n){return Nr.test(n)}function $(n){return Pr.test(n)}function D(n){for(var t,r=[];!(t=n.next()).done;)r.push(t.value);return r}function M(n){var t=-1,r=Array(n.size);return n.forEach(function(n,e){r[++t]=[e,n]}),r}function F(n,t){return function(r){return n(t(r))}}function N(n,t){for(var r=-1,e=n.length,u=0,i=[];++r<e;){var o=n[r];o!==t&&o!==cn||(n[r]=cn,i[u++]=r)}return i}function P(n){var t=-1,r=Array(n.size);
return n.forEach(function(n){r[++t]=n}),r}function q(n){var t=-1,r=Array(n.size);return n.forEach(function(n){r[++t]=[n,n]}),r}function Z(n,t,r){for(var e=r-1,u=n.length;++e<u;)if(n[e]===t)return e;return-1}function K(n,t,r){for(var e=r+1;e--;)if(n[e]===t)return e;return e}function V(n){return T(n)?J(n):_e(n)}function G(n){return T(n)?Y(n):p(n)}function H(n){for(var t=n.length;t--&&Ct.test(n.charAt(t)););return t}function J(n){for(var t=Mr.lastIndex=0;Mr.test(n);)++t;return t}function Y(n){return n.match(Mr)||[];
}function Q(n){return n.match(Fr)||[]}var X,nn="4.17.21",tn=200,rn="Unsupported core-js use. Try https://npms.io/search?q=ponyfill.",en="Expected a function",un="Invalid `variable` option passed into `_.template`",on="__lodash_hash_undefined__",fn=500,cn="__lodash_placeholder__",an=1,ln=2,sn=4,hn=1,pn=2,_n=1,vn=2,gn=4,yn=8,dn=16,bn=32,wn=64,mn=128,xn=256,jn=512,An=30,kn="...",On=800,In=16,Rn=1,zn=2,En=3,Sn=1/0,Wn=9007199254740991,Ln=1.7976931348623157e308,Cn=NaN,Un=4294967295,Bn=Un-1,Tn=Un>>>1,$n=[["ary",mn],["bind",_n],["bindKey",vn],["curry",yn],["curryRight",dn],["flip",jn],["partial",bn],["partialRight",wn],["rearg",xn]],Dn="[object Arguments]",Mn="[object Array]",Fn="[object AsyncFunction]",Nn="[object Boolean]",Pn="[object Date]",qn="[object DOMException]",Zn="[object Error]",Kn="[object Function]",Vn="[object GeneratorFunction]",Gn="[object Map]",Hn="[object Number]",Jn="[object Null]",Yn="[object Object]",Qn="[object Promise]",Xn="[object Proxy]",nt="[object RegExp]",tt="[object Set]",rt="[object String]",et="[object Symbol]",ut="[object Undefined]",it="[object WeakMap]",ot="[object WeakSet]",ft="[object ArrayBuffer]",ct="[object DataView]",at="[object Float32Array]",lt="[object Float64Array]",st="[object Int8Array]",ht="[object Int16Array]",pt="[object Int32Array]",_t="[object Uint8Array]",vt="[object Uint8ClampedArray]",gt="[object Uint16Array]",yt="[object Uint32Array]",dt=/\b__p \+= '';/g,bt=/\b(__p \+=) '' \+/g,wt=/(__e\(.*?\)|\b__t\)) \+\n'';/g,mt=/&(?:amp|lt|gt|quot|#39);/g,xt=/[&<>"']/g,jt=RegExp(mt.source),At=RegExp(xt.source),kt=/<%-([\s\S]+?)%>/g,Ot=/<%([\s\S]+?)%>/g,It=/<%=([\s\S]+?)%>/g,Rt=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,zt=/^\w*$/,Et=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,St=/[\\^$.*+?()[\]{}|]/g,Wt=RegExp(St.source),Lt=/^\s+/,Ct=/\s/,Ut=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,Bt=/\{\n\/\* \[wrapped with (.+)\] \*/,Tt=/,? & /,$t=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,Dt=/[()=,{}\[\]\/\s]/,Mt=/\\(\\)?/g,Ft=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,Nt=/\w*$/,Pt=/^[-+]0x[0-9a-f]+$/i,qt=/^0b[01]+$/i,Zt=/^\[object .+?Constructor\]$/,Kt=/^0o[0-7]+$/i,Vt=/^(?:0|[1-9]\d*)$/,Gt=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,Ht=/($^)/,Jt=/['\n\r\u2028\u2029\\]/g,Yt="\\ud800-\\udfff",Qt="\\u0300-\\u036f",Xt="\\ufe20-\\ufe2f",nr="\\u20d0-\\u20ff",tr=Qt+Xt+nr,rr="\\u2700-\\u27bf",er="a-z\\xdf-\\xf6\\xf8-\\xff",ur="\\xac\\xb1\\xd7\\xf7",ir="\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf",or="\\u2000-\\u206f",fr=" \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",cr="A-Z\\xc0-\\xd6\\xd8-\\xde",ar="\\ufe0e\\ufe0f",lr=ur+ir+or+fr,sr="['\u2019]",hr="["+Yt+"]",pr="["+lr+"]",_r="["+tr+"]",vr="\\d+",gr="["+rr+"]",yr="["+er+"]",dr="[^"+Yt+lr+vr+rr+er+cr+"]",br="\\ud83c[\\udffb-\\udfff]",wr="(?:"+_r+"|"+br+")",mr="[^"+Yt+"]",xr="(?:\\ud83c[\\udde6-\\uddff]){2}",jr="[\\ud800-\\udbff][\\udc00-\\udfff]",Ar="["+cr+"]",kr="\\u200d",Or="(?:"+yr+"|"+dr+")",Ir="(?:"+Ar+"|"+dr+")",Rr="(?:"+sr+"(?:d|ll|m|re|s|t|ve))?",zr="(?:"+sr+"(?:D|LL|M|RE|S|T|VE))?",Er=wr+"?",Sr="["+ar+"]?",Wr="(?:"+kr+"(?:"+[mr,xr,jr].join("|")+")"+Sr+Er+")*",Lr="\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])",Cr="\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])",Ur=Sr+Er+Wr,Br="(?:"+[gr,xr,jr].join("|")+")"+Ur,Tr="(?:"+[mr+_r+"?",_r,xr,jr,hr].join("|")+")",$r=RegExp(sr,"g"),Dr=RegExp(_r,"g"),Mr=RegExp(br+"(?="+br+")|"+Tr+Ur,"g"),Fr=RegExp([Ar+"?"+yr+"+"+Rr+"(?="+[pr,Ar,"$"].join("|")+")",Ir+"+"+zr+"(?="+[pr,Ar+Or,"$"].join("|")+")",Ar+"?"+Or+"+"+Rr,Ar+"+"+zr,Cr,Lr,vr,Br].join("|"),"g"),Nr=RegExp("["+kr+Yt+tr+ar+"]"),Pr=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,qr=["Array","Buffer","DataView","Date","Error","Float32Array","Float64Array","Function","Int8Array","Int16Array","Int32Array","Map","Math","Object","Promise","RegExp","Set","String","Symbol","TypeError","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","WeakMap","_","clearTimeout","isFinite","parseInt","setTimeout"],Zr=-1,Kr={};
Kr[at]=Kr[lt]=Kr[st]=Kr[ht]=Kr[pt]=Kr[_t]=Kr[vt]=Kr[gt]=Kr[yt]=!0,Kr[Dn]=Kr[Mn]=Kr[ft]=Kr[Nn]=Kr[ct]=Kr[Pn]=Kr[Zn]=Kr[Kn]=Kr[Gn]=Kr[Hn]=Kr[Yn]=Kr[nt]=Kr[tt]=Kr[rt]=Kr[it]=!1;var Vr={};Vr[Dn]=Vr[Mn]=Vr[ft]=Vr[ct]=Vr[Nn]=Vr[Pn]=Vr[at]=Vr[lt]=Vr[st]=Vr[ht]=Vr[pt]=Vr[Gn]=Vr[Hn]=Vr[Yn]=Vr[nt]=Vr[tt]=Vr[rt]=Vr[et]=Vr[_t]=Vr[vt]=Vr[gt]=Vr[yt]=!0,Vr[Zn]=Vr[Kn]=Vr[it]=!1;var Gr={"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a","\xe3":"a","\xe4":"a","\xe5":"a",
"\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I","\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y","\xfd":"y","\xff":"y","\xc6":"Ae",
"\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss","\u0100":"A","\u0102":"A","\u0104":"A","\u0101":"a","\u0103":"a","\u0105":"a","\u0106":"C","\u0108":"C","\u010a":"C","\u010c":"C","\u0107":"c","\u0109":"c","\u010b":"c","\u010d":"c","\u010e":"D","\u0110":"D","\u010f":"d","\u0111":"d","\u0112":"E","\u0114":"E","\u0116":"E","\u0118":"E","\u011a":"E","\u0113":"e","\u0115":"e","\u0117":"e","\u0119":"e","\u011b":"e","\u011c":"G","\u011e":"G","\u0120":"G","\u0122":"G","\u011d":"g","\u011f":"g","\u0121":"g",
"\u0123":"g","\u0124":"H","\u0126":"H","\u0125":"h","\u0127":"h","\u0128":"I","\u012a":"I","\u012c":"I","\u012e":"I","\u0130":"I","\u0129":"i","\u012b":"i","\u012d":"i","\u012f":"i","\u0131":"i","\u0134":"J","\u0135":"j","\u0136":"K","\u0137":"k","\u0138":"k","\u0139":"L","\u013b":"L","\u013d":"L","\u013f":"L","\u0141":"L","\u013a":"l","\u013c":"l","\u013e":"l","\u0140":"l","\u0142":"l","\u0143":"N","\u0145":"N","\u0147":"N","\u014a":"N","\u0144":"n","\u0146":"n","\u0148":"n","\u014b":"n","\u014c":"O",
"\u014e":"O","\u0150":"O","\u014d":"o","\u014f":"o","\u0151":"o","\u0154":"R","\u0156":"R","\u0158":"R","\u0155":"r","\u0157":"r","\u0159":"r","\u015a":"S","\u015c":"S","\u015e":"S","\u0160":"S","\u015b":"s","\u015d":"s","\u015f":"s","\u0161":"s","\u0162":"T","\u0164":"T","\u0166":"T","\u0163":"t","\u0165":"t","\u0167":"t","\u0168":"U","\u016a":"U","\u016c":"U","\u016e":"U","\u0170":"U","\u0172":"U","\u0169":"u","\u016b":"u","\u016d":"u","\u016f":"u","\u0171":"u","\u0173":"u","\u0174":"W","\u0175":"w",
"\u0176":"Y","\u0177":"y","\u0178":"Y","\u0179":"Z","\u017b":"Z","\u017d":"Z","\u017a":"z","\u017c":"z","\u017e":"z","\u0132":"IJ","\u0133":"ij","\u0152":"Oe","\u0153":"oe","\u0149":"'n","\u017f":"s"},Hr={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"},Jr={"&amp;":"&","&lt;":"<","&gt;":">","&quot;":'"',"&#39;":"'"},Yr={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},Qr=parseFloat,Xr=parseInt,ne="object"==typeof global&&global&&global.Object===Object&&global,te="object"==typeof self&&self&&self.Object===Object&&self,re=ne||te||Function("return this")(),ee="object"==typeof exports&&exports&&!exports.nodeType&&exports,ue=ee&&"object"==typeof module&&module&&!module.nodeType&&module,ie=ue&&ue.exports===ee,oe=ie&&ne.process,fe=function(){
try{var n=ue&&ue.require&&ue.require("util").types;return n?n:oe&&oe.binding&&oe.binding("util")}catch(n){}}(),ce=fe&&fe.isArrayBuffer,ae=fe&&fe.isDate,le=fe&&fe.isMap,se=fe&&fe.isRegExp,he=fe&&fe.isSet,pe=fe&&fe.isTypedArray,_e=m("length"),ve=x(Gr),ge=x(Hr),ye=x(Jr),de=function p(x){function Z(n){if(cc(n)&&!bh(n)&&!(n instanceof Ct)){if(n instanceof Y)return n;if(bl.call(n,"__wrapped__"))return eo(n)}return new Y(n)}function J(){}function Y(n,t){this.__wrapped__=n,this.__actions__=[],this.__chain__=!!t,
this.__index__=0,this.__values__=X}function Ct(n){this.__wrapped__=n,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=Un,this.__views__=[]}function $t(){var n=new Ct(this.__wrapped__);return n.__actions__=Tu(this.__actions__),n.__dir__=this.__dir__,n.__filtered__=this.__filtered__,n.__iteratees__=Tu(this.__iteratees__),n.__takeCount__=this.__takeCount__,n.__views__=Tu(this.__views__),n}function Yt(){if(this.__filtered__){var n=new Ct(this);n.__dir__=-1,
n.__filtered__=!0}else n=this.clone(),n.__dir__*=-1;return n}function Qt(){var n=this.__wrapped__.value(),t=this.__dir__,r=bh(n),e=t<0,u=r?n.length:0,i=Oi(0,u,this.__views__),o=i.start,f=i.end,c=f-o,a=e?f:o-1,l=this.__iteratees__,s=l.length,h=0,p=Hl(c,this.__takeCount__);if(!r||!e&&u==c&&p==c)return wu(n,this.__actions__);var _=[];n:for(;c--&&h<p;){a+=t;for(var v=-1,g=n[a];++v<s;){var y=l[v],d=y.iteratee,b=y.type,w=d(g);if(b==zn)g=w;else if(!w){if(b==Rn)continue n;break n}}_[h++]=g}return _}function Xt(n){
var t=-1,r=null==n?0:n.length;for(this.clear();++t<r;){var e=n[t];this.set(e[0],e[1])}}function nr(){this.__data__=is?is(null):{},this.size=0}function tr(n){var t=this.has(n)&&delete this.__data__[n];return this.size-=t?1:0,t}function rr(n){var t=this.__data__;if(is){var r=t[n];return r===on?X:r}return bl.call(t,n)?t[n]:X}function er(n){var t=this.__data__;return is?t[n]!==X:bl.call(t,n)}function ur(n,t){var r=this.__data__;return this.size+=this.has(n)?0:1,r[n]=is&&t===X?on:t,this}function ir(n){
var t=-1,r=null==n?0:n.length;for(this.clear();++t<r;){var e=n[t];this.set(e[0],e[1])}}function or(){this.__data__=[],this.size=0}function fr(n){var t=this.__data__,r=Wr(t,n);return!(r<0)&&(r==t.length-1?t.pop():Ll.call(t,r,1),--this.size,!0)}function cr(n){var t=this.__data__,r=Wr(t,n);return r<0?X:t[r][1]}function ar(n){return Wr(this.__data__,n)>-1}function lr(n,t){var r=this.__data__,e=Wr(r,n);return e<0?(++this.size,r.push([n,t])):r[e][1]=t,this}function sr(n){var t=-1,r=null==n?0:n.length;for(this.clear();++t<r;){
var e=n[t];this.set(e[0],e[1])}}function hr(){this.size=0,this.__data__={hash:new Xt,map:new(ts||ir),string:new Xt}}function pr(n){var t=xi(this,n).delete(n);return this.size-=t?1:0,t}function _r(n){return xi(this,n).get(n)}function vr(n){return xi(this,n).has(n)}function gr(n,t){var r=xi(this,n),e=r.size;return r.set(n,t),this.size+=r.size==e?0:1,this}function yr(n){var t=-1,r=null==n?0:n.length;for(this.__data__=new sr;++t<r;)this.add(n[t])}function dr(n){return this.__data__.set(n,on),this}function br(n){
return this.__data__.has(n)}function wr(n){this.size=(this.__data__=new ir(n)).size}function mr(){this.__data__=new ir,this.size=0}function xr(n){var t=this.__data__,r=t.delete(n);return this.size=t.size,r}function jr(n){return this.__data__.get(n)}function Ar(n){return this.__data__.has(n)}function kr(n,t){var r=this.__data__;if(r instanceof ir){var e=r.__data__;if(!ts||e.length<tn-1)return e.push([n,t]),this.size=++r.size,this;r=this.__data__=new sr(e)}return r.set(n,t),this.size=r.size,this}function Or(n,t){
var r=bh(n),e=!r&&dh(n),u=!r&&!e&&mh(n),i=!r&&!e&&!u&&Oh(n),o=r||e||u||i,f=o?O(n.length,hl):[],c=f.length;for(var a in n)!t&&!bl.call(n,a)||o&&("length"==a||u&&("offset"==a||"parent"==a)||i&&("buffer"==a||"byteLength"==a||"byteOffset"==a)||Ci(a,c))||f.push(a);return f}function Ir(n){var t=n.length;return t?n[tu(0,t-1)]:X}function Rr(n,t){return Xi(Tu(n),Mr(t,0,n.length))}function zr(n){return Xi(Tu(n))}function Er(n,t,r){(r===X||Gf(n[t],r))&&(r!==X||t in n)||Br(n,t,r)}function Sr(n,t,r){var e=n[t];
bl.call(n,t)&&Gf(e,r)&&(r!==X||t in n)||Br(n,t,r)}function Wr(n,t){for(var r=n.length;r--;)if(Gf(n[r][0],t))return r;return-1}function Lr(n,t,r,e){return ys(n,function(n,u,i){t(e,n,r(n),i)}),e}function Cr(n,t){return n&&$u(t,Pc(t),n)}function Ur(n,t){return n&&$u(t,qc(t),n)}function Br(n,t,r){"__proto__"==t&&Tl?Tl(n,t,{configurable:!0,enumerable:!0,value:r,writable:!0}):n[t]=r}function Tr(n,t){for(var r=-1,e=t.length,u=il(e),i=null==n;++r<e;)u[r]=i?X:Mc(n,t[r]);return u}function Mr(n,t,r){return n===n&&(r!==X&&(n=n<=r?n:r),
t!==X&&(n=n>=t?n:t)),n}function Fr(n,t,e,u,i,o){var f,c=t&an,a=t&ln,l=t&sn;if(e&&(f=i?e(n,u,i,o):e(n)),f!==X)return f;if(!fc(n))return n;var s=bh(n);if(s){if(f=zi(n),!c)return Tu(n,f)}else{var h=zs(n),p=h==Kn||h==Vn;if(mh(n))return Iu(n,c);if(h==Yn||h==Dn||p&&!i){if(f=a||p?{}:Ei(n),!c)return a?Mu(n,Ur(f,n)):Du(n,Cr(f,n))}else{if(!Vr[h])return i?n:{};f=Si(n,h,c)}}o||(o=new wr);var _=o.get(n);if(_)return _;o.set(n,f),kh(n)?n.forEach(function(r){f.add(Fr(r,t,e,r,n,o))}):jh(n)&&n.forEach(function(r,u){
f.set(u,Fr(r,t,e,u,n,o))});var v=l?a?di:yi:a?qc:Pc,g=s?X:v(n);return r(g||n,function(r,u){g&&(u=r,r=n[u]),Sr(f,u,Fr(r,t,e,u,n,o))}),f}function Nr(n){var t=Pc(n);return function(r){return Pr(r,n,t)}}function Pr(n,t,r){var e=r.length;if(null==n)return!e;for(n=ll(n);e--;){var u=r[e],i=t[u],o=n[u];if(o===X&&!(u in n)||!i(o))return!1}return!0}function Gr(n,t,r){if("function"!=typeof n)throw new pl(en);return Ws(function(){n.apply(X,r)},t)}function Hr(n,t,r,e){var u=-1,i=o,a=!0,l=n.length,s=[],h=t.length;
if(!l)return s;r&&(t=c(t,z(r))),e?(i=f,a=!1):t.length>=tn&&(i=S,a=!1,t=new yr(t));n:for(;++u<l;){var p=n[u],_=null==r?p:r(p);if(p=e||0!==p?p:0,a&&_===_){for(var v=h;v--;)if(t[v]===_)continue n;s.push(p)}else i(t,_,e)||s.push(p)}return s}function Jr(n,t){var r=!0;return ys(n,function(n,e,u){return r=!!t(n,e,u)}),r}function Yr(n,t,r){for(var e=-1,u=n.length;++e<u;){var i=n[e],o=t(i);if(null!=o&&(f===X?o===o&&!bc(o):r(o,f)))var f=o,c=i}return c}function ne(n,t,r,e){var u=n.length;for(r=kc(r),r<0&&(r=-r>u?0:u+r),
e=e===X||e>u?u:kc(e),e<0&&(e+=u),e=r>e?0:Oc(e);r<e;)n[r++]=t;return n}function te(n,t){var r=[];return ys(n,function(n,e,u){t(n,e,u)&&r.push(n)}),r}function ee(n,t,r,e,u){var i=-1,o=n.length;for(r||(r=Li),u||(u=[]);++i<o;){var f=n[i];t>0&&r(f)?t>1?ee(f,t-1,r,e,u):a(u,f):e||(u[u.length]=f)}return u}function ue(n,t){return n&&bs(n,t,Pc)}function oe(n,t){return n&&ws(n,t,Pc)}function fe(n,t){return i(t,function(t){return uc(n[t])})}function _e(n,t){t=ku(t,n);for(var r=0,e=t.length;null!=n&&r<e;)n=n[no(t[r++])];
return r&&r==e?n:X}function de(n,t,r){var e=t(n);return bh(n)?e:a(e,r(n))}function we(n){return null==n?n===X?ut:Jn:Bl&&Bl in ll(n)?ki(n):Ki(n)}function me(n,t){return n>t}function xe(n,t){return null!=n&&bl.call(n,t)}function je(n,t){return null!=n&&t in ll(n)}function Ae(n,t,r){return n>=Hl(t,r)&&n<Gl(t,r)}function ke(n,t,r){for(var e=r?f:o,u=n[0].length,i=n.length,a=i,l=il(i),s=1/0,h=[];a--;){var p=n[a];a&&t&&(p=c(p,z(t))),s=Hl(p.length,s),l[a]=!r&&(t||u>=120&&p.length>=120)?new yr(a&&p):X}p=n[0];
var _=-1,v=l[0];n:for(;++_<u&&h.length<s;){var g=p[_],y=t?t(g):g;if(g=r||0!==g?g:0,!(v?S(v,y):e(h,y,r))){for(a=i;--a;){var d=l[a];if(!(d?S(d,y):e(n[a],y,r)))continue n}v&&v.push(y),h.push(g)}}return h}function Oe(n,t,r,e){return ue(n,function(n,u,i){t(e,r(n),u,i)}),e}function Ie(t,r,e){r=ku(r,t),t=Gi(t,r);var u=null==t?t:t[no(jo(r))];return null==u?X:n(u,t,e)}function Re(n){return cc(n)&&we(n)==Dn}function ze(n){return cc(n)&&we(n)==ft}function Ee(n){return cc(n)&&we(n)==Pn}function Se(n,t,r,e,u){
return n===t||(null==n||null==t||!cc(n)&&!cc(t)?n!==n&&t!==t:We(n,t,r,e,Se,u))}function We(n,t,r,e,u,i){var o=bh(n),f=bh(t),c=o?Mn:zs(n),a=f?Mn:zs(t);c=c==Dn?Yn:c,a=a==Dn?Yn:a;var l=c==Yn,s=a==Yn,h=c==a;if(h&&mh(n)){if(!mh(t))return!1;o=!0,l=!1}if(h&&!l)return i||(i=new wr),o||Oh(n)?pi(n,t,r,e,u,i):_i(n,t,c,r,e,u,i);if(!(r&hn)){var p=l&&bl.call(n,"__wrapped__"),_=s&&bl.call(t,"__wrapped__");if(p||_){var v=p?n.value():n,g=_?t.value():t;return i||(i=new wr),u(v,g,r,e,i)}}return!!h&&(i||(i=new wr),vi(n,t,r,e,u,i));
}function Le(n){return cc(n)&&zs(n)==Gn}function Ce(n,t,r,e){var u=r.length,i=u,o=!e;if(null==n)return!i;for(n=ll(n);u--;){var f=r[u];if(o&&f[2]?f[1]!==n[f[0]]:!(f[0]in n))return!1}for(;++u<i;){f=r[u];var c=f[0],a=n[c],l=f[1];if(o&&f[2]){if(a===X&&!(c in n))return!1}else{var s=new wr;if(e)var h=e(a,l,c,n,t,s);if(!(h===X?Se(l,a,hn|pn,e,s):h))return!1}}return!0}function Ue(n){return!(!fc(n)||Di(n))&&(uc(n)?kl:Zt).test(to(n))}function Be(n){return cc(n)&&we(n)==nt}function Te(n){return cc(n)&&zs(n)==tt;
}function $e(n){return cc(n)&&oc(n.length)&&!!Kr[we(n)]}function De(n){return"function"==typeof n?n:null==n?La:"object"==typeof n?bh(n)?Ze(n[0],n[1]):qe(n):Fa(n)}function Me(n){if(!Mi(n))return Vl(n);var t=[];for(var r in ll(n))bl.call(n,r)&&"constructor"!=r&&t.push(r);return t}function Fe(n){if(!fc(n))return Zi(n);var t=Mi(n),r=[];for(var e in n)("constructor"!=e||!t&&bl.call(n,e))&&r.push(e);return r}function Ne(n,t){return n<t}function Pe(n,t){var r=-1,e=Hf(n)?il(n.length):[];return ys(n,function(n,u,i){
e[++r]=t(n,u,i)}),e}function qe(n){var t=ji(n);return 1==t.length&&t[0][2]?Ni(t[0][0],t[0][1]):function(r){return r===n||Ce(r,n,t)}}function Ze(n,t){return Bi(n)&&Fi(t)?Ni(no(n),t):function(r){var e=Mc(r,n);return e===X&&e===t?Nc(r,n):Se(t,e,hn|pn)}}function Ke(n,t,r,e,u){n!==t&&bs(t,function(i,o){if(u||(u=new wr),fc(i))Ve(n,t,o,r,Ke,e,u);else{var f=e?e(Ji(n,o),i,o+"",n,t,u):X;f===X&&(f=i),Er(n,o,f)}},qc)}function Ve(n,t,r,e,u,i,o){var f=Ji(n,r),c=Ji(t,r),a=o.get(c);if(a)return Er(n,r,a),X;var l=i?i(f,c,r+"",n,t,o):X,s=l===X;
if(s){var h=bh(c),p=!h&&mh(c),_=!h&&!p&&Oh(c);l=c,h||p||_?bh(f)?l=f:Jf(f)?l=Tu(f):p?(s=!1,l=Iu(c,!0)):_?(s=!1,l=Wu(c,!0)):l=[]:gc(c)||dh(c)?(l=f,dh(f)?l=Rc(f):fc(f)&&!uc(f)||(l=Ei(c))):s=!1}s&&(o.set(c,l),u(l,c,e,i,o),o.delete(c)),Er(n,r,l)}function Ge(n,t){var r=n.length;if(r)return t+=t<0?r:0,Ci(t,r)?n[t]:X}function He(n,t,r){t=t.length?c(t,function(n){return bh(n)?function(t){return _e(t,1===n.length?n[0]:n)}:n}):[La];var e=-1;return t=c(t,z(mi())),A(Pe(n,function(n,r,u){return{criteria:c(t,function(t){
return t(n)}),index:++e,value:n}}),function(n,t){return Cu(n,t,r)})}function Je(n,t){return Ye(n,t,function(t,r){return Nc(n,r)})}function Ye(n,t,r){for(var e=-1,u=t.length,i={};++e<u;){var o=t[e],f=_e(n,o);r(f,o)&&fu(i,ku(o,n),f)}return i}function Qe(n){return function(t){return _e(t,n)}}function Xe(n,t,r,e){var u=e?d:y,i=-1,o=t.length,f=n;for(n===t&&(t=Tu(t)),r&&(f=c(n,z(r)));++i<o;)for(var a=0,l=t[i],s=r?r(l):l;(a=u(f,s,a,e))>-1;)f!==n&&Ll.call(f,a,1),Ll.call(n,a,1);return n}function nu(n,t){for(var r=n?t.length:0,e=r-1;r--;){
var u=t[r];if(r==e||u!==i){var i=u;Ci(u)?Ll.call(n,u,1):yu(n,u)}}return n}function tu(n,t){return n+Nl(Ql()*(t-n+1))}function ru(n,t,r,e){for(var u=-1,i=Gl(Fl((t-n)/(r||1)),0),o=il(i);i--;)o[e?i:++u]=n,n+=r;return o}function eu(n,t){var r="";if(!n||t<1||t>Wn)return r;do t%2&&(r+=n),t=Nl(t/2),t&&(n+=n);while(t);return r}function uu(n,t){return Ls(Vi(n,t,La),n+"")}function iu(n){return Ir(ra(n))}function ou(n,t){var r=ra(n);return Xi(r,Mr(t,0,r.length))}function fu(n,t,r,e){if(!fc(n))return n;t=ku(t,n);
for(var u=-1,i=t.length,o=i-1,f=n;null!=f&&++u<i;){var c=no(t[u]),a=r;if("__proto__"===c||"constructor"===c||"prototype"===c)return n;if(u!=o){var l=f[c];a=e?e(l,c,f):X,a===X&&(a=fc(l)?l:Ci(t[u+1])?[]:{})}Sr(f,c,a),f=f[c]}return n}function cu(n){return Xi(ra(n))}function au(n,t,r){var e=-1,u=n.length;t<0&&(t=-t>u?0:u+t),r=r>u?u:r,r<0&&(r+=u),u=t>r?0:r-t>>>0,t>>>=0;for(var i=il(u);++e<u;)i[e]=n[e+t];return i}function lu(n,t){var r;return ys(n,function(n,e,u){return r=t(n,e,u),!r}),!!r}function su(n,t,r){
var e=0,u=null==n?e:n.length;if("number"==typeof t&&t===t&&u<=Tn){for(;e<u;){var i=e+u>>>1,o=n[i];null!==o&&!bc(o)&&(r?o<=t:o<t)?e=i+1:u=i}return u}return hu(n,t,La,r)}function hu(n,t,r,e){var u=0,i=null==n?0:n.length;if(0===i)return 0;t=r(t);for(var o=t!==t,f=null===t,c=bc(t),a=t===X;u<i;){var l=Nl((u+i)/2),s=r(n[l]),h=s!==X,p=null===s,_=s===s,v=bc(s);if(o)var g=e||_;else g=a?_&&(e||h):f?_&&h&&(e||!p):c?_&&h&&!p&&(e||!v):!p&&!v&&(e?s<=t:s<t);g?u=l+1:i=l}return Hl(i,Bn)}function pu(n,t){for(var r=-1,e=n.length,u=0,i=[];++r<e;){
var o=n[r],f=t?t(o):o;if(!r||!Gf(f,c)){var c=f;i[u++]=0===o?0:o}}return i}function _u(n){return"number"==typeof n?n:bc(n)?Cn:+n}function vu(n){if("string"==typeof n)return n;if(bh(n))return c(n,vu)+"";if(bc(n))return vs?vs.call(n):"";var t=n+"";return"0"==t&&1/n==-Sn?"-0":t}function gu(n,t,r){var e=-1,u=o,i=n.length,c=!0,a=[],l=a;if(r)c=!1,u=f;else if(i>=tn){var s=t?null:ks(n);if(s)return P(s);c=!1,u=S,l=new yr}else l=t?[]:a;n:for(;++e<i;){var h=n[e],p=t?t(h):h;if(h=r||0!==h?h:0,c&&p===p){for(var _=l.length;_--;)if(l[_]===p)continue n;
t&&l.push(p),a.push(h)}else u(l,p,r)||(l!==a&&l.push(p),a.push(h))}return a}function yu(n,t){return t=ku(t,n),n=Gi(n,t),null==n||delete n[no(jo(t))]}function du(n,t,r,e){return fu(n,t,r(_e(n,t)),e)}function bu(n,t,r,e){for(var u=n.length,i=e?u:-1;(e?i--:++i<u)&&t(n[i],i,n););return r?au(n,e?0:i,e?i+1:u):au(n,e?i+1:0,e?u:i)}function wu(n,t){var r=n;return r instanceof Ct&&(r=r.value()),l(t,function(n,t){return t.func.apply(t.thisArg,a([n],t.args))},r)}function mu(n,t,r){var e=n.length;if(e<2)return e?gu(n[0]):[];
for(var u=-1,i=il(e);++u<e;)for(var o=n[u],f=-1;++f<e;)f!=u&&(i[u]=Hr(i[u]||o,n[f],t,r));return gu(ee(i,1),t,r)}function xu(n,t,r){for(var e=-1,u=n.length,i=t.length,o={};++e<u;){r(o,n[e],e<i?t[e]:X)}return o}function ju(n){return Jf(n)?n:[]}function Au(n){return"function"==typeof n?n:La}function ku(n,t){return bh(n)?n:Bi(n,t)?[n]:Cs(Ec(n))}function Ou(n,t,r){var e=n.length;return r=r===X?e:r,!t&&r>=e?n:au(n,t,r)}function Iu(n,t){if(t)return n.slice();var r=n.length,e=zl?zl(r):new n.constructor(r);
return n.copy(e),e}function Ru(n){var t=new n.constructor(n.byteLength);return new Rl(t).set(new Rl(n)),t}function zu(n,t){return new n.constructor(t?Ru(n.buffer):n.buffer,n.byteOffset,n.byteLength)}function Eu(n){var t=new n.constructor(n.source,Nt.exec(n));return t.lastIndex=n.lastIndex,t}function Su(n){return _s?ll(_s.call(n)):{}}function Wu(n,t){return new n.constructor(t?Ru(n.buffer):n.buffer,n.byteOffset,n.length)}function Lu(n,t){if(n!==t){var r=n!==X,e=null===n,u=n===n,i=bc(n),o=t!==X,f=null===t,c=t===t,a=bc(t);
if(!f&&!a&&!i&&n>t||i&&o&&c&&!f&&!a||e&&o&&c||!r&&c||!u)return 1;if(!e&&!i&&!a&&n<t||a&&r&&u&&!e&&!i||f&&r&&u||!o&&u||!c)return-1}return 0}function Cu(n,t,r){for(var e=-1,u=n.criteria,i=t.criteria,o=u.length,f=r.length;++e<o;){var c=Lu(u[e],i[e]);if(c){if(e>=f)return c;return c*("desc"==r[e]?-1:1)}}return n.index-t.index}function Uu(n,t,r,e){for(var u=-1,i=n.length,o=r.length,f=-1,c=t.length,a=Gl(i-o,0),l=il(c+a),s=!e;++f<c;)l[f]=t[f];for(;++u<o;)(s||u<i)&&(l[r[u]]=n[u]);for(;a--;)l[f++]=n[u++];return l;
}function Bu(n,t,r,e){for(var u=-1,i=n.length,o=-1,f=r.length,c=-1,a=t.length,l=Gl(i-f,0),s=il(l+a),h=!e;++u<l;)s[u]=n[u];for(var p=u;++c<a;)s[p+c]=t[c];for(;++o<f;)(h||u<i)&&(s[p+r[o]]=n[u++]);return s}function Tu(n,t){var r=-1,e=n.length;for(t||(t=il(e));++r<e;)t[r]=n[r];return t}function $u(n,t,r,e){var u=!r;r||(r={});for(var i=-1,o=t.length;++i<o;){var f=t[i],c=e?e(r[f],n[f],f,r,n):X;c===X&&(c=n[f]),u?Br(r,f,c):Sr(r,f,c)}return r}function Du(n,t){return $u(n,Is(n),t)}function Mu(n,t){return $u(n,Rs(n),t);
}function Fu(n,r){return function(e,u){var i=bh(e)?t:Lr,o=r?r():{};return i(e,n,mi(u,2),o)}}function Nu(n){return uu(function(t,r){var e=-1,u=r.length,i=u>1?r[u-1]:X,o=u>2?r[2]:X;for(i=n.length>3&&"function"==typeof i?(u--,i):X,o&&Ui(r[0],r[1],o)&&(i=u<3?X:i,u=1),t=ll(t);++e<u;){var f=r[e];f&&n(t,f,e,i)}return t})}function Pu(n,t){return function(r,e){if(null==r)return r;if(!Hf(r))return n(r,e);for(var u=r.length,i=t?u:-1,o=ll(r);(t?i--:++i<u)&&e(o[i],i,o)!==!1;);return r}}function qu(n){return function(t,r,e){
for(var u=-1,i=ll(t),o=e(t),f=o.length;f--;){var c=o[n?f:++u];if(r(i[c],c,i)===!1)break}return t}}function Zu(n,t,r){function e(){return(this&&this!==re&&this instanceof e?i:n).apply(u?r:this,arguments)}var u=t&_n,i=Gu(n);return e}function Ku(n){return function(t){t=Ec(t);var r=T(t)?G(t):X,e=r?r[0]:t.charAt(0),u=r?Ou(r,1).join(""):t.slice(1);return e[n]()+u}}function Vu(n){return function(t){return l(Ra(ca(t).replace($r,"")),n,"")}}function Gu(n){return function(){var t=arguments;switch(t.length){
case 0:return new n;case 1:return new n(t[0]);case 2:return new n(t[0],t[1]);case 3:return new n(t[0],t[1],t[2]);case 4:return new n(t[0],t[1],t[2],t[3]);case 5:return new n(t[0],t[1],t[2],t[3],t[4]);case 6:return new n(t[0],t[1],t[2],t[3],t[4],t[5]);case 7:return new n(t[0],t[1],t[2],t[3],t[4],t[5],t[6])}var r=gs(n.prototype),e=n.apply(r,t);return fc(e)?e:r}}function Hu(t,r,e){function u(){for(var o=arguments.length,f=il(o),c=o,a=wi(u);c--;)f[c]=arguments[c];var l=o<3&&f[0]!==a&&f[o-1]!==a?[]:N(f,a);
return o-=l.length,o<e?oi(t,r,Qu,u.placeholder,X,f,l,X,X,e-o):n(this&&this!==re&&this instanceof u?i:t,this,f)}var i=Gu(t);return u}function Ju(n){return function(t,r,e){var u=ll(t);if(!Hf(t)){var i=mi(r,3);t=Pc(t),r=function(n){return i(u[n],n,u)}}var o=n(t,r,e);return o>-1?u[i?t[o]:o]:X}}function Yu(n){return gi(function(t){var r=t.length,e=r,u=Y.prototype.thru;for(n&&t.reverse();e--;){var i=t[e];if("function"!=typeof i)throw new pl(en);if(u&&!o&&"wrapper"==bi(i))var o=new Y([],!0)}for(e=o?e:r;++e<r;){
i=t[e];var f=bi(i),c="wrapper"==f?Os(i):X;o=c&&$i(c[0])&&c[1]==(mn|yn|bn|xn)&&!c[4].length&&1==c[9]?o[bi(c[0])].apply(o,c[3]):1==i.length&&$i(i)?o[f]():o.thru(i)}return function(){var n=arguments,e=n[0];if(o&&1==n.length&&bh(e))return o.plant(e).value();for(var u=0,i=r?t[u].apply(this,n):e;++u<r;)i=t[u].call(this,i);return i}})}function Qu(n,t,r,e,u,i,o,f,c,a){function l(){for(var y=arguments.length,d=il(y),b=y;b--;)d[b]=arguments[b];if(_)var w=wi(l),m=C(d,w);if(e&&(d=Uu(d,e,u,_)),i&&(d=Bu(d,i,o,_)),
y-=m,_&&y<a){return oi(n,t,Qu,l.placeholder,r,d,N(d,w),f,c,a-y)}var x=h?r:this,j=p?x[n]:n;return y=d.length,f?d=Hi(d,f):v&&y>1&&d.reverse(),s&&c<y&&(d.length=c),this&&this!==re&&this instanceof l&&(j=g||Gu(j)),j.apply(x,d)}var s=t&mn,h=t&_n,p=t&vn,_=t&(yn|dn),v=t&jn,g=p?X:Gu(n);return l}function Xu(n,t){return function(r,e){return Oe(r,n,t(e),{})}}function ni(n,t){return function(r,e){var u;if(r===X&&e===X)return t;if(r!==X&&(u=r),e!==X){if(u===X)return e;"string"==typeof r||"string"==typeof e?(r=vu(r),
e=vu(e)):(r=_u(r),e=_u(e)),u=n(r,e)}return u}}function ti(t){return gi(function(r){return r=c(r,z(mi())),uu(function(e){var u=this;return t(r,function(t){return n(t,u,e)})})})}function ri(n,t){t=t===X?" ":vu(t);var r=t.length;if(r<2)return r?eu(t,n):t;var e=eu(t,Fl(n/V(t)));return T(t)?Ou(G(e),0,n).join(""):e.slice(0,n)}function ei(t,r,e,u){function i(){for(var r=-1,c=arguments.length,a=-1,l=u.length,s=il(l+c),h=this&&this!==re&&this instanceof i?f:t;++a<l;)s[a]=u[a];for(;c--;)s[a++]=arguments[++r];
return n(h,o?e:this,s)}var o=r&_n,f=Gu(t);return i}function ui(n){return function(t,r,e){return e&&"number"!=typeof e&&Ui(t,r,e)&&(r=e=X),t=Ac(t),r===X?(r=t,t=0):r=Ac(r),e=e===X?t<r?1:-1:Ac(e),ru(t,r,e,n)}}function ii(n){return function(t,r){return"string"==typeof t&&"string"==typeof r||(t=Ic(t),r=Ic(r)),n(t,r)}}function oi(n,t,r,e,u,i,o,f,c,a){var l=t&yn,s=l?o:X,h=l?X:o,p=l?i:X,_=l?X:i;t|=l?bn:wn,t&=~(l?wn:bn),t&gn||(t&=~(_n|vn));var v=[n,t,u,p,s,_,h,f,c,a],g=r.apply(X,v);return $i(n)&&Ss(g,v),g.placeholder=e,
Yi(g,n,t)}function fi(n){var t=al[n];return function(n,r){if(n=Ic(n),r=null==r?0:Hl(kc(r),292),r&&Zl(n)){var e=(Ec(n)+"e").split("e");return e=(Ec(t(e[0]+"e"+(+e[1]+r)))+"e").split("e"),+(e[0]+"e"+(+e[1]-r))}return t(n)}}function ci(n){return function(t){var r=zs(t);return r==Gn?M(t):r==tt?q(t):I(t,n(t))}}function ai(n,t,r,e,u,i,o,f){var c=t&vn;if(!c&&"function"!=typeof n)throw new pl(en);var a=e?e.length:0;if(a||(t&=~(bn|wn),e=u=X),o=o===X?o:Gl(kc(o),0),f=f===X?f:kc(f),a-=u?u.length:0,t&wn){var l=e,s=u;
e=u=X}var h=c?X:Os(n),p=[n,t,r,e,u,l,s,i,o,f];if(h&&qi(p,h),n=p[0],t=p[1],r=p[2],e=p[3],u=p[4],f=p[9]=p[9]===X?c?0:n.length:Gl(p[9]-a,0),!f&&t&(yn|dn)&&(t&=~(yn|dn)),t&&t!=_n)_=t==yn||t==dn?Hu(n,t,f):t!=bn&&t!=(_n|bn)||u.length?Qu.apply(X,p):ei(n,t,r,e);else var _=Zu(n,t,r);return Yi((h?ms:Ss)(_,p),n,t)}function li(n,t,r,e){return n===X||Gf(n,gl[r])&&!bl.call(e,r)?t:n}function si(n,t,r,e,u,i){return fc(n)&&fc(t)&&(i.set(t,n),Ke(n,t,X,si,i),i.delete(t)),n}function hi(n){return gc(n)?X:n}function pi(n,t,r,e,u,i){
var o=r&hn,f=n.length,c=t.length;if(f!=c&&!(o&&c>f))return!1;var a=i.get(n),l=i.get(t);if(a&&l)return a==t&&l==n;var s=-1,p=!0,_=r&pn?new yr:X;for(i.set(n,t),i.set(t,n);++s<f;){var v=n[s],g=t[s];if(e)var y=o?e(g,v,s,t,n,i):e(v,g,s,n,t,i);if(y!==X){if(y)continue;p=!1;break}if(_){if(!h(t,function(n,t){if(!S(_,t)&&(v===n||u(v,n,r,e,i)))return _.push(t)})){p=!1;break}}else if(v!==g&&!u(v,g,r,e,i)){p=!1;break}}return i.delete(n),i.delete(t),p}function _i(n,t,r,e,u,i,o){switch(r){case ct:if(n.byteLength!=t.byteLength||n.byteOffset!=t.byteOffset)return!1;
n=n.buffer,t=t.buffer;case ft:return!(n.byteLength!=t.byteLength||!i(new Rl(n),new Rl(t)));case Nn:case Pn:case Hn:return Gf(+n,+t);case Zn:return n.name==t.name&&n.message==t.message;case nt:case rt:return n==t+"";case Gn:var f=M;case tt:var c=e&hn;if(f||(f=P),n.size!=t.size&&!c)return!1;var a=o.get(n);if(a)return a==t;e|=pn,o.set(n,t);var l=pi(f(n),f(t),e,u,i,o);return o.delete(n),l;case et:if(_s)return _s.call(n)==_s.call(t)}return!1}function vi(n,t,r,e,u,i){var o=r&hn,f=yi(n),c=f.length;if(c!=yi(t).length&&!o)return!1;
for(var a=c;a--;){var l=f[a];if(!(o?l in t:bl.call(t,l)))return!1}var s=i.get(n),h=i.get(t);if(s&&h)return s==t&&h==n;var p=!0;i.set(n,t),i.set(t,n);for(var _=o;++a<c;){l=f[a];var v=n[l],g=t[l];if(e)var y=o?e(g,v,l,t,n,i):e(v,g,l,n,t,i);if(!(y===X?v===g||u(v,g,r,e,i):y)){p=!1;break}_||(_="constructor"==l)}if(p&&!_){var d=n.constructor,b=t.constructor;d!=b&&"constructor"in n&&"constructor"in t&&!("function"==typeof d&&d instanceof d&&"function"==typeof b&&b instanceof b)&&(p=!1)}return i.delete(n),
i.delete(t),p}function gi(n){return Ls(Vi(n,X,_o),n+"")}function yi(n){return de(n,Pc,Is)}function di(n){return de(n,qc,Rs)}function bi(n){for(var t=n.name+"",r=fs[t],e=bl.call(fs,t)?r.length:0;e--;){var u=r[e],i=u.func;if(null==i||i==n)return u.name}return t}function wi(n){return(bl.call(Z,"placeholder")?Z:n).placeholder}function mi(){var n=Z.iteratee||Ca;return n=n===Ca?De:n,arguments.length?n(arguments[0],arguments[1]):n}function xi(n,t){var r=n.__data__;return Ti(t)?r["string"==typeof t?"string":"hash"]:r.map;
}function ji(n){for(var t=Pc(n),r=t.length;r--;){var e=t[r],u=n[e];t[r]=[e,u,Fi(u)]}return t}function Ai(n,t){var r=B(n,t);return Ue(r)?r:X}function ki(n){var t=bl.call(n,Bl),r=n[Bl];try{n[Bl]=X;var e=!0}catch(n){}var u=xl.call(n);return e&&(t?n[Bl]=r:delete n[Bl]),u}function Oi(n,t,r){for(var e=-1,u=r.length;++e<u;){var i=r[e],o=i.size;switch(i.type){case"drop":n+=o;break;case"dropRight":t-=o;break;case"take":t=Hl(t,n+o);break;case"takeRight":n=Gl(n,t-o)}}return{start:n,end:t}}function Ii(n){var t=n.match(Bt);
return t?t[1].split(Tt):[]}function Ri(n,t,r){t=ku(t,n);for(var e=-1,u=t.length,i=!1;++e<u;){var o=no(t[e]);if(!(i=null!=n&&r(n,o)))break;n=n[o]}return i||++e!=u?i:(u=null==n?0:n.length,!!u&&oc(u)&&Ci(o,u)&&(bh(n)||dh(n)))}function zi(n){var t=n.length,r=new n.constructor(t);return t&&"string"==typeof n[0]&&bl.call(n,"index")&&(r.index=n.index,r.input=n.input),r}function Ei(n){return"function"!=typeof n.constructor||Mi(n)?{}:gs(El(n))}function Si(n,t,r){var e=n.constructor;switch(t){case ft:return Ru(n);
case Nn:case Pn:return new e(+n);case ct:return zu(n,r);case at:case lt:case st:case ht:case pt:case _t:case vt:case gt:case yt:return Wu(n,r);case Gn:return new e;case Hn:case rt:return new e(n);case nt:return Eu(n);case tt:return new e;case et:return Su(n)}}function Wi(n,t){var r=t.length;if(!r)return n;var e=r-1;return t[e]=(r>1?"& ":"")+t[e],t=t.join(r>2?", ":" "),n.replace(Ut,"{\n/* [wrapped with "+t+"] */\n")}function Li(n){return bh(n)||dh(n)||!!(Cl&&n&&n[Cl])}function Ci(n,t){var r=typeof n;
return t=null==t?Wn:t,!!t&&("number"==r||"symbol"!=r&&Vt.test(n))&&n>-1&&n%1==0&&n<t}function Ui(n,t,r){if(!fc(r))return!1;var e=typeof t;return!!("number"==e?Hf(r)&&Ci(t,r.length):"string"==e&&t in r)&&Gf(r[t],n)}function Bi(n,t){if(bh(n))return!1;var r=typeof n;return!("number"!=r&&"symbol"!=r&&"boolean"!=r&&null!=n&&!bc(n))||(zt.test(n)||!Rt.test(n)||null!=t&&n in ll(t))}function Ti(n){var t=typeof n;return"string"==t||"number"==t||"symbol"==t||"boolean"==t?"__proto__"!==n:null===n}function $i(n){
var t=bi(n),r=Z[t];if("function"!=typeof r||!(t in Ct.prototype))return!1;if(n===r)return!0;var e=Os(r);return!!e&&n===e[0]}function Di(n){return!!ml&&ml in n}function Mi(n){var t=n&&n.constructor;return n===("function"==typeof t&&t.prototype||gl)}function Fi(n){return n===n&&!fc(n)}function Ni(n,t){return function(r){return null!=r&&(r[n]===t&&(t!==X||n in ll(r)))}}function Pi(n){var t=Cf(n,function(n){return r.size===fn&&r.clear(),n}),r=t.cache;return t}function qi(n,t){var r=n[1],e=t[1],u=r|e,i=u<(_n|vn|mn),o=e==mn&&r==yn||e==mn&&r==xn&&n[7].length<=t[8]||e==(mn|xn)&&t[7].length<=t[8]&&r==yn;
if(!i&&!o)return n;e&_n&&(n[2]=t[2],u|=r&_n?0:gn);var f=t[3];if(f){var c=n[3];n[3]=c?Uu(c,f,t[4]):f,n[4]=c?N(n[3],cn):t[4]}return f=t[5],f&&(c=n[5],n[5]=c?Bu(c,f,t[6]):f,n[6]=c?N(n[5],cn):t[6]),f=t[7],f&&(n[7]=f),e&mn&&(n[8]=null==n[8]?t[8]:Hl(n[8],t[8])),null==n[9]&&(n[9]=t[9]),n[0]=t[0],n[1]=u,n}function Zi(n){var t=[];if(null!=n)for(var r in ll(n))t.push(r);return t}function Ki(n){return xl.call(n)}function Vi(t,r,e){return r=Gl(r===X?t.length-1:r,0),function(){for(var u=arguments,i=-1,o=Gl(u.length-r,0),f=il(o);++i<o;)f[i]=u[r+i];
i=-1;for(var c=il(r+1);++i<r;)c[i]=u[i];return c[r]=e(f),n(t,this,c)}}function Gi(n,t){return t.length<2?n:_e(n,au(t,0,-1))}function Hi(n,t){for(var r=n.length,e=Hl(t.length,r),u=Tu(n);e--;){var i=t[e];n[e]=Ci(i,r)?u[i]:X}return n}function Ji(n,t){if(("constructor"!==t||"function"!=typeof n[t])&&"__proto__"!=t)return n[t]}function Yi(n,t,r){var e=t+"";return Ls(n,Wi(e,ro(Ii(e),r)))}function Qi(n){var t=0,r=0;return function(){var e=Jl(),u=In-(e-r);if(r=e,u>0){if(++t>=On)return arguments[0]}else t=0;
return n.apply(X,arguments)}}function Xi(n,t){var r=-1,e=n.length,u=e-1;for(t=t===X?e:t;++r<t;){var i=tu(r,u),o=n[i];n[i]=n[r],n[r]=o}return n.length=t,n}function no(n){if("string"==typeof n||bc(n))return n;var t=n+"";return"0"==t&&1/n==-Sn?"-0":t}function to(n){if(null!=n){try{return dl.call(n)}catch(n){}try{return n+""}catch(n){}}return""}function ro(n,t){return r($n,function(r){var e="_."+r[0];t&r[1]&&!o(n,e)&&n.push(e)}),n.sort()}function eo(n){if(n instanceof Ct)return n.clone();var t=new Y(n.__wrapped__,n.__chain__);
return t.__actions__=Tu(n.__actions__),t.__index__=n.__index__,t.__values__=n.__values__,t}function uo(n,t,r){t=(r?Ui(n,t,r):t===X)?1:Gl(kc(t),0);var e=null==n?0:n.length;if(!e||t<1)return[];for(var u=0,i=0,o=il(Fl(e/t));u<e;)o[i++]=au(n,u,u+=t);return o}function io(n){for(var t=-1,r=null==n?0:n.length,e=0,u=[];++t<r;){var i=n[t];i&&(u[e++]=i)}return u}function oo(){var n=arguments.length;if(!n)return[];for(var t=il(n-1),r=arguments[0],e=n;e--;)t[e-1]=arguments[e];return a(bh(r)?Tu(r):[r],ee(t,1));
}function fo(n,t,r){var e=null==n?0:n.length;return e?(t=r||t===X?1:kc(t),au(n,t<0?0:t,e)):[]}function co(n,t,r){var e=null==n?0:n.length;return e?(t=r||t===X?1:kc(t),t=e-t,au(n,0,t<0?0:t)):[]}function ao(n,t){return n&&n.length?bu(n,mi(t,3),!0,!0):[]}function lo(n,t){return n&&n.length?bu(n,mi(t,3),!0):[]}function so(n,t,r,e){var u=null==n?0:n.length;return u?(r&&"number"!=typeof r&&Ui(n,t,r)&&(r=0,e=u),ne(n,t,r,e)):[]}function ho(n,t,r){var e=null==n?0:n.length;if(!e)return-1;var u=null==r?0:kc(r);
return u<0&&(u=Gl(e+u,0)),g(n,mi(t,3),u)}function po(n,t,r){var e=null==n?0:n.length;if(!e)return-1;var u=e-1;return r!==X&&(u=kc(r),u=r<0?Gl(e+u,0):Hl(u,e-1)),g(n,mi(t,3),u,!0)}function _o(n){return(null==n?0:n.length)?ee(n,1):[]}function vo(n){return(null==n?0:n.length)?ee(n,Sn):[]}function go(n,t){return(null==n?0:n.length)?(t=t===X?1:kc(t),ee(n,t)):[]}function yo(n){for(var t=-1,r=null==n?0:n.length,e={};++t<r;){var u=n[t];e[u[0]]=u[1]}return e}function bo(n){return n&&n.length?n[0]:X}function wo(n,t,r){
var e=null==n?0:n.length;if(!e)return-1;var u=null==r?0:kc(r);return u<0&&(u=Gl(e+u,0)),y(n,t,u)}function mo(n){return(null==n?0:n.length)?au(n,0,-1):[]}function xo(n,t){return null==n?"":Kl.call(n,t)}function jo(n){var t=null==n?0:n.length;return t?n[t-1]:X}function Ao(n,t,r){var e=null==n?0:n.length;if(!e)return-1;var u=e;return r!==X&&(u=kc(r),u=u<0?Gl(e+u,0):Hl(u,e-1)),t===t?K(n,t,u):g(n,b,u,!0)}function ko(n,t){return n&&n.length?Ge(n,kc(t)):X}function Oo(n,t){return n&&n.length&&t&&t.length?Xe(n,t):n;
}function Io(n,t,r){return n&&n.length&&t&&t.length?Xe(n,t,mi(r,2)):n}function Ro(n,t,r){return n&&n.length&&t&&t.length?Xe(n,t,X,r):n}function zo(n,t){var r=[];if(!n||!n.length)return r;var e=-1,u=[],i=n.length;for(t=mi(t,3);++e<i;){var o=n[e];t(o,e,n)&&(r.push(o),u.push(e))}return nu(n,u),r}function Eo(n){return null==n?n:Xl.call(n)}function So(n,t,r){var e=null==n?0:n.length;return e?(r&&"number"!=typeof r&&Ui(n,t,r)?(t=0,r=e):(t=null==t?0:kc(t),r=r===X?e:kc(r)),au(n,t,r)):[]}function Wo(n,t){
return su(n,t)}function Lo(n,t,r){return hu(n,t,mi(r,2))}function Co(n,t){var r=null==n?0:n.length;if(r){var e=su(n,t);if(e<r&&Gf(n[e],t))return e}return-1}function Uo(n,t){return su(n,t,!0)}function Bo(n,t,r){return hu(n,t,mi(r,2),!0)}function To(n,t){if(null==n?0:n.length){var r=su(n,t,!0)-1;if(Gf(n[r],t))return r}return-1}function $o(n){return n&&n.length?pu(n):[]}function Do(n,t){return n&&n.length?pu(n,mi(t,2)):[]}function Mo(n){var t=null==n?0:n.length;return t?au(n,1,t):[]}function Fo(n,t,r){
return n&&n.length?(t=r||t===X?1:kc(t),au(n,0,t<0?0:t)):[]}function No(n,t,r){var e=null==n?0:n.length;return e?(t=r||t===X?1:kc(t),t=e-t,au(n,t<0?0:t,e)):[]}function Po(n,t){return n&&n.length?bu(n,mi(t,3),!1,!0):[]}function qo(n,t){return n&&n.length?bu(n,mi(t,3)):[]}function Zo(n){return n&&n.length?gu(n):[]}function Ko(n,t){return n&&n.length?gu(n,mi(t,2)):[]}function Vo(n,t){return t="function"==typeof t?t:X,n&&n.length?gu(n,X,t):[]}function Go(n){if(!n||!n.length)return[];var t=0;return n=i(n,function(n){
if(Jf(n))return t=Gl(n.length,t),!0}),O(t,function(t){return c(n,m(t))})}function Ho(t,r){if(!t||!t.length)return[];var e=Go(t);return null==r?e:c(e,function(t){return n(r,X,t)})}function Jo(n,t){return xu(n||[],t||[],Sr)}function Yo(n,t){return xu(n||[],t||[],fu)}function Qo(n){var t=Z(n);return t.__chain__=!0,t}function Xo(n,t){return t(n),n}function nf(n,t){return t(n)}function tf(){return Qo(this)}function rf(){return new Y(this.value(),this.__chain__)}function ef(){this.__values__===X&&(this.__values__=jc(this.value()));
var n=this.__index__>=this.__values__.length;return{done:n,value:n?X:this.__values__[this.__index__++]}}function uf(){return this}function of(n){for(var t,r=this;r instanceof J;){var e=eo(r);e.__index__=0,e.__values__=X,t?u.__wrapped__=e:t=e;var u=e;r=r.__wrapped__}return u.__wrapped__=n,t}function ff(){var n=this.__wrapped__;if(n instanceof Ct){var t=n;return this.__actions__.length&&(t=new Ct(this)),t=t.reverse(),t.__actions__.push({func:nf,args:[Eo],thisArg:X}),new Y(t,this.__chain__)}return this.thru(Eo);
}function cf(){return wu(this.__wrapped__,this.__actions__)}function af(n,t,r){var e=bh(n)?u:Jr;return r&&Ui(n,t,r)&&(t=X),e(n,mi(t,3))}function lf(n,t){return(bh(n)?i:te)(n,mi(t,3))}function sf(n,t){return ee(yf(n,t),1)}function hf(n,t){return ee(yf(n,t),Sn)}function pf(n,t,r){return r=r===X?1:kc(r),ee(yf(n,t),r)}function _f(n,t){return(bh(n)?r:ys)(n,mi(t,3))}function vf(n,t){return(bh(n)?e:ds)(n,mi(t,3))}function gf(n,t,r,e){n=Hf(n)?n:ra(n),r=r&&!e?kc(r):0;var u=n.length;return r<0&&(r=Gl(u+r,0)),
dc(n)?r<=u&&n.indexOf(t,r)>-1:!!u&&y(n,t,r)>-1}function yf(n,t){return(bh(n)?c:Pe)(n,mi(t,3))}function df(n,t,r,e){return null==n?[]:(bh(t)||(t=null==t?[]:[t]),r=e?X:r,bh(r)||(r=null==r?[]:[r]),He(n,t,r))}function bf(n,t,r){var e=bh(n)?l:j,u=arguments.length<3;return e(n,mi(t,4),r,u,ys)}function wf(n,t,r){var e=bh(n)?s:j,u=arguments.length<3;return e(n,mi(t,4),r,u,ds)}function mf(n,t){return(bh(n)?i:te)(n,Uf(mi(t,3)))}function xf(n){return(bh(n)?Ir:iu)(n)}function jf(n,t,r){return t=(r?Ui(n,t,r):t===X)?1:kc(t),
(bh(n)?Rr:ou)(n,t)}function Af(n){return(bh(n)?zr:cu)(n)}function kf(n){if(null==n)return 0;if(Hf(n))return dc(n)?V(n):n.length;var t=zs(n);return t==Gn||t==tt?n.size:Me(n).length}function Of(n,t,r){var e=bh(n)?h:lu;return r&&Ui(n,t,r)&&(t=X),e(n,mi(t,3))}function If(n,t){if("function"!=typeof t)throw new pl(en);return n=kc(n),function(){if(--n<1)return t.apply(this,arguments)}}function Rf(n,t,r){return t=r?X:t,t=n&&null==t?n.length:t,ai(n,mn,X,X,X,X,t)}function zf(n,t){var r;if("function"!=typeof t)throw new pl(en);
return n=kc(n),function(){return--n>0&&(r=t.apply(this,arguments)),n<=1&&(t=X),r}}function Ef(n,t,r){t=r?X:t;var e=ai(n,yn,X,X,X,X,X,t);return e.placeholder=Ef.placeholder,e}function Sf(n,t,r){t=r?X:t;var e=ai(n,dn,X,X,X,X,X,t);return e.placeholder=Sf.placeholder,e}function Wf(n,t,r){function e(t){var r=h,e=p;return h=p=X,d=t,v=n.apply(e,r)}function u(n){return d=n,g=Ws(f,t),b?e(n):v}function i(n){var r=n-y,e=n-d,u=t-r;return w?Hl(u,_-e):u}function o(n){var r=n-y,e=n-d;return y===X||r>=t||r<0||w&&e>=_;
}function f(){var n=fh();return o(n)?c(n):(g=Ws(f,i(n)),X)}function c(n){return g=X,m&&h?e(n):(h=p=X,v)}function a(){g!==X&&As(g),d=0,h=y=p=g=X}function l(){return g===X?v:c(fh())}function s(){var n=fh(),r=o(n);if(h=arguments,p=this,y=n,r){if(g===X)return u(y);if(w)return As(g),g=Ws(f,t),e(y)}return g===X&&(g=Ws(f,t)),v}var h,p,_,v,g,y,d=0,b=!1,w=!1,m=!0;if("function"!=typeof n)throw new pl(en);return t=Ic(t)||0,fc(r)&&(b=!!r.leading,w="maxWait"in r,_=w?Gl(Ic(r.maxWait)||0,t):_,m="trailing"in r?!!r.trailing:m),
s.cancel=a,s.flush=l,s}function Lf(n){return ai(n,jn)}function Cf(n,t){if("function"!=typeof n||null!=t&&"function"!=typeof t)throw new pl(en);var r=function(){var e=arguments,u=t?t.apply(this,e):e[0],i=r.cache;if(i.has(u))return i.get(u);var o=n.apply(this,e);return r.cache=i.set(u,o)||i,o};return r.cache=new(Cf.Cache||sr),r}function Uf(n){if("function"!=typeof n)throw new pl(en);return function(){var t=arguments;switch(t.length){case 0:return!n.call(this);case 1:return!n.call(this,t[0]);case 2:
return!n.call(this,t[0],t[1]);case 3:return!n.call(this,t[0],t[1],t[2])}return!n.apply(this,t)}}function Bf(n){return zf(2,n)}function Tf(n,t){if("function"!=typeof n)throw new pl(en);return t=t===X?t:kc(t),uu(n,t)}function $f(t,r){if("function"!=typeof t)throw new pl(en);return r=null==r?0:Gl(kc(r),0),uu(function(e){var u=e[r],i=Ou(e,0,r);return u&&a(i,u),n(t,this,i)})}function Df(n,t,r){var e=!0,u=!0;if("function"!=typeof n)throw new pl(en);return fc(r)&&(e="leading"in r?!!r.leading:e,u="trailing"in r?!!r.trailing:u),
Wf(n,t,{leading:e,maxWait:t,trailing:u})}function Mf(n){return Rf(n,1)}function Ff(n,t){return ph(Au(t),n)}function Nf(){if(!arguments.length)return[];var n=arguments[0];return bh(n)?n:[n]}function Pf(n){return Fr(n,sn)}function qf(n,t){return t="function"==typeof t?t:X,Fr(n,sn,t)}function Zf(n){return Fr(n,an|sn)}function Kf(n,t){return t="function"==typeof t?t:X,Fr(n,an|sn,t)}function Vf(n,t){return null==t||Pr(n,t,Pc(t))}function Gf(n,t){return n===t||n!==n&&t!==t}function Hf(n){return null!=n&&oc(n.length)&&!uc(n);
}function Jf(n){return cc(n)&&Hf(n)}function Yf(n){return n===!0||n===!1||cc(n)&&we(n)==Nn}function Qf(n){return cc(n)&&1===n.nodeType&&!gc(n)}function Xf(n){if(null==n)return!0;if(Hf(n)&&(bh(n)||"string"==typeof n||"function"==typeof n.splice||mh(n)||Oh(n)||dh(n)))return!n.length;var t=zs(n);if(t==Gn||t==tt)return!n.size;if(Mi(n))return!Me(n).length;for(var r in n)if(bl.call(n,r))return!1;return!0}function nc(n,t){return Se(n,t)}function tc(n,t,r){r="function"==typeof r?r:X;var e=r?r(n,t):X;return e===X?Se(n,t,X,r):!!e;
}function rc(n){if(!cc(n))return!1;var t=we(n);return t==Zn||t==qn||"string"==typeof n.message&&"string"==typeof n.name&&!gc(n)}function ec(n){return"number"==typeof n&&Zl(n)}function uc(n){if(!fc(n))return!1;var t=we(n);return t==Kn||t==Vn||t==Fn||t==Xn}function ic(n){return"number"==typeof n&&n==kc(n)}function oc(n){return"number"==typeof n&&n>-1&&n%1==0&&n<=Wn}function fc(n){var t=typeof n;return null!=n&&("object"==t||"function"==t)}function cc(n){return null!=n&&"object"==typeof n}function ac(n,t){
return n===t||Ce(n,t,ji(t))}function lc(n,t,r){return r="function"==typeof r?r:X,Ce(n,t,ji(t),r)}function sc(n){return vc(n)&&n!=+n}function hc(n){if(Es(n))throw new fl(rn);return Ue(n)}function pc(n){return null===n}function _c(n){return null==n}function vc(n){return"number"==typeof n||cc(n)&&we(n)==Hn}function gc(n){if(!cc(n)||we(n)!=Yn)return!1;var t=El(n);if(null===t)return!0;var r=bl.call(t,"constructor")&&t.constructor;return"function"==typeof r&&r instanceof r&&dl.call(r)==jl}function yc(n){
return ic(n)&&n>=-Wn&&n<=Wn}function dc(n){return"string"==typeof n||!bh(n)&&cc(n)&&we(n)==rt}function bc(n){return"symbol"==typeof n||cc(n)&&we(n)==et}function wc(n){return n===X}function mc(n){return cc(n)&&zs(n)==it}function xc(n){return cc(n)&&we(n)==ot}function jc(n){if(!n)return[];if(Hf(n))return dc(n)?G(n):Tu(n);if(Ul&&n[Ul])return D(n[Ul]());var t=zs(n);return(t==Gn?M:t==tt?P:ra)(n)}function Ac(n){if(!n)return 0===n?n:0;if(n=Ic(n),n===Sn||n===-Sn){return(n<0?-1:1)*Ln}return n===n?n:0}function kc(n){
var t=Ac(n),r=t%1;return t===t?r?t-r:t:0}function Oc(n){return n?Mr(kc(n),0,Un):0}function Ic(n){if("number"==typeof n)return n;if(bc(n))return Cn;if(fc(n)){var t="function"==typeof n.valueOf?n.valueOf():n;n=fc(t)?t+"":t}if("string"!=typeof n)return 0===n?n:+n;n=R(n);var r=qt.test(n);return r||Kt.test(n)?Xr(n.slice(2),r?2:8):Pt.test(n)?Cn:+n}function Rc(n){return $u(n,qc(n))}function zc(n){return n?Mr(kc(n),-Wn,Wn):0===n?n:0}function Ec(n){return null==n?"":vu(n)}function Sc(n,t){var r=gs(n);return null==t?r:Cr(r,t);
}function Wc(n,t){return v(n,mi(t,3),ue)}function Lc(n,t){return v(n,mi(t,3),oe)}function Cc(n,t){return null==n?n:bs(n,mi(t,3),qc)}function Uc(n,t){return null==n?n:ws(n,mi(t,3),qc)}function Bc(n,t){return n&&ue(n,mi(t,3))}function Tc(n,t){return n&&oe(n,mi(t,3))}function $c(n){return null==n?[]:fe(n,Pc(n))}function Dc(n){return null==n?[]:fe(n,qc(n))}function Mc(n,t,r){var e=null==n?X:_e(n,t);return e===X?r:e}function Fc(n,t){return null!=n&&Ri(n,t,xe)}function Nc(n,t){return null!=n&&Ri(n,t,je);
}function Pc(n){return Hf(n)?Or(n):Me(n)}function qc(n){return Hf(n)?Or(n,!0):Fe(n)}function Zc(n,t){var r={};return t=mi(t,3),ue(n,function(n,e,u){Br(r,t(n,e,u),n)}),r}function Kc(n,t){var r={};return t=mi(t,3),ue(n,function(n,e,u){Br(r,e,t(n,e,u))}),r}function Vc(n,t){return Gc(n,Uf(mi(t)))}function Gc(n,t){if(null==n)return{};var r=c(di(n),function(n){return[n]});return t=mi(t),Ye(n,r,function(n,r){return t(n,r[0])})}function Hc(n,t,r){t=ku(t,n);var e=-1,u=t.length;for(u||(u=1,n=X);++e<u;){var i=null==n?X:n[no(t[e])];
i===X&&(e=u,i=r),n=uc(i)?i.call(n):i}return n}function Jc(n,t,r){return null==n?n:fu(n,t,r)}function Yc(n,t,r,e){return e="function"==typeof e?e:X,null==n?n:fu(n,t,r,e)}function Qc(n,t,e){var u=bh(n),i=u||mh(n)||Oh(n);if(t=mi(t,4),null==e){var o=n&&n.constructor;e=i?u?new o:[]:fc(n)&&uc(o)?gs(El(n)):{}}return(i?r:ue)(n,function(n,r,u){return t(e,n,r,u)}),e}function Xc(n,t){return null==n||yu(n,t)}function na(n,t,r){return null==n?n:du(n,t,Au(r))}function ta(n,t,r,e){return e="function"==typeof e?e:X,
null==n?n:du(n,t,Au(r),e)}function ra(n){return null==n?[]:E(n,Pc(n))}function ea(n){return null==n?[]:E(n,qc(n))}function ua(n,t,r){return r===X&&(r=t,t=X),r!==X&&(r=Ic(r),r=r===r?r:0),t!==X&&(t=Ic(t),t=t===t?t:0),Mr(Ic(n),t,r)}function ia(n,t,r){return t=Ac(t),r===X?(r=t,t=0):r=Ac(r),n=Ic(n),Ae(n,t,r)}function oa(n,t,r){if(r&&"boolean"!=typeof r&&Ui(n,t,r)&&(t=r=X),r===X&&("boolean"==typeof t?(r=t,t=X):"boolean"==typeof n&&(r=n,n=X)),n===X&&t===X?(n=0,t=1):(n=Ac(n),t===X?(t=n,n=0):t=Ac(t)),n>t){
var e=n;n=t,t=e}if(r||n%1||t%1){var u=Ql();return Hl(n+u*(t-n+Qr("1e-"+((u+"").length-1))),t)}return tu(n,t)}function fa(n){return Qh(Ec(n).toLowerCase())}function ca(n){return n=Ec(n),n&&n.replace(Gt,ve).replace(Dr,"")}function aa(n,t,r){n=Ec(n),t=vu(t);var e=n.length;r=r===X?e:Mr(kc(r),0,e);var u=r;return r-=t.length,r>=0&&n.slice(r,u)==t}function la(n){return n=Ec(n),n&&At.test(n)?n.replace(xt,ge):n}function sa(n){return n=Ec(n),n&&Wt.test(n)?n.replace(St,"\\$&"):n}function ha(n,t,r){n=Ec(n),t=kc(t);
var e=t?V(n):0;if(!t||e>=t)return n;var u=(t-e)/2;return ri(Nl(u),r)+n+ri(Fl(u),r)}function pa(n,t,r){n=Ec(n),t=kc(t);var e=t?V(n):0;return t&&e<t?n+ri(t-e,r):n}function _a(n,t,r){n=Ec(n),t=kc(t);var e=t?V(n):0;return t&&e<t?ri(t-e,r)+n:n}function va(n,t,r){return r||null==t?t=0:t&&(t=+t),Yl(Ec(n).replace(Lt,""),t||0)}function ga(n,t,r){return t=(r?Ui(n,t,r):t===X)?1:kc(t),eu(Ec(n),t)}function ya(){var n=arguments,t=Ec(n[0]);return n.length<3?t:t.replace(n[1],n[2])}function da(n,t,r){return r&&"number"!=typeof r&&Ui(n,t,r)&&(t=r=X),
(r=r===X?Un:r>>>0)?(n=Ec(n),n&&("string"==typeof t||null!=t&&!Ah(t))&&(t=vu(t),!t&&T(n))?Ou(G(n),0,r):n.split(t,r)):[]}function ba(n,t,r){return n=Ec(n),r=null==r?0:Mr(kc(r),0,n.length),t=vu(t),n.slice(r,r+t.length)==t}function wa(n,t,r){var e=Z.templateSettings;r&&Ui(n,t,r)&&(t=X),n=Ec(n),t=Sh({},t,e,li);var u,i,o=Sh({},t.imports,e.imports,li),f=Pc(o),c=E(o,f),a=0,l=t.interpolate||Ht,s="__p += '",h=sl((t.escape||Ht).source+"|"+l.source+"|"+(l===It?Ft:Ht).source+"|"+(t.evaluate||Ht).source+"|$","g"),p="//# sourceURL="+(bl.call(t,"sourceURL")?(t.sourceURL+"").replace(/\s/g," "):"lodash.templateSources["+ ++Zr+"]")+"\n";
n.replace(h,function(t,r,e,o,f,c){return e||(e=o),s+=n.slice(a,c).replace(Jt,U),r&&(u=!0,s+="' +\n__e("+r+") +\n'"),f&&(i=!0,s+="';\n"+f+";\n__p += '"),e&&(s+="' +\n((__t = ("+e+")) == null ? '' : __t) +\n'"),a=c+t.length,t}),s+="';\n";var _=bl.call(t,"variable")&&t.variable;if(_){if(Dt.test(_))throw new fl(un)}else s="with (obj) {\n"+s+"\n}\n";s=(i?s.replace(dt,""):s).replace(bt,"$1").replace(wt,"$1;"),s="function("+(_||"obj")+") {\n"+(_?"":"obj || (obj = {});\n")+"var __t, __p = ''"+(u?", __e = _.escape":"")+(i?", __j = Array.prototype.join;\nfunction print() { __p += __j.call(arguments, '') }\n":";\n")+s+"return __p\n}";
var v=Xh(function(){return cl(f,p+"return "+s).apply(X,c)});if(v.source=s,rc(v))throw v;return v}function ma(n){return Ec(n).toLowerCase()}function xa(n){return Ec(n).toUpperCase()}function ja(n,t,r){if(n=Ec(n),n&&(r||t===X))return R(n);if(!n||!(t=vu(t)))return n;var e=G(n),u=G(t);return Ou(e,W(e,u),L(e,u)+1).join("")}function Aa(n,t,r){if(n=Ec(n),n&&(r||t===X))return n.slice(0,H(n)+1);if(!n||!(t=vu(t)))return n;var e=G(n);return Ou(e,0,L(e,G(t))+1).join("")}function ka(n,t,r){if(n=Ec(n),n&&(r||t===X))return n.replace(Lt,"");
if(!n||!(t=vu(t)))return n;var e=G(n);return Ou(e,W(e,G(t))).join("")}function Oa(n,t){var r=An,e=kn;if(fc(t)){var u="separator"in t?t.separator:u;r="length"in t?kc(t.length):r,e="omission"in t?vu(t.omission):e}n=Ec(n);var i=n.length;if(T(n)){var o=G(n);i=o.length}if(r>=i)return n;var f=r-V(e);if(f<1)return e;var c=o?Ou(o,0,f).join(""):n.slice(0,f);if(u===X)return c+e;if(o&&(f+=c.length-f),Ah(u)){if(n.slice(f).search(u)){var a,l=c;for(u.global||(u=sl(u.source,Ec(Nt.exec(u))+"g")),u.lastIndex=0;a=u.exec(l);)var s=a.index;
c=c.slice(0,s===X?f:s)}}else if(n.indexOf(vu(u),f)!=f){var h=c.lastIndexOf(u);h>-1&&(c=c.slice(0,h))}return c+e}function Ia(n){return n=Ec(n),n&&jt.test(n)?n.replace(mt,ye):n}function Ra(n,t,r){return n=Ec(n),t=r?X:t,t===X?$(n)?Q(n):_(n):n.match(t)||[]}function za(t){var r=null==t?0:t.length,e=mi();return t=r?c(t,function(n){if("function"!=typeof n[1])throw new pl(en);return[e(n[0]),n[1]]}):[],uu(function(e){for(var u=-1;++u<r;){var i=t[u];if(n(i[0],this,e))return n(i[1],this,e)}})}function Ea(n){
return Nr(Fr(n,an))}function Sa(n){return function(){return n}}function Wa(n,t){return null==n||n!==n?t:n}function La(n){return n}function Ca(n){return De("function"==typeof n?n:Fr(n,an))}function Ua(n){return qe(Fr(n,an))}function Ba(n,t){return Ze(n,Fr(t,an))}function Ta(n,t,e){var u=Pc(t),i=fe(t,u);null!=e||fc(t)&&(i.length||!u.length)||(e=t,t=n,n=this,i=fe(t,Pc(t)));var o=!(fc(e)&&"chain"in e&&!e.chain),f=uc(n);return r(i,function(r){var e=t[r];n[r]=e,f&&(n.prototype[r]=function(){var t=this.__chain__;
if(o||t){var r=n(this.__wrapped__);return(r.__actions__=Tu(this.__actions__)).push({func:e,args:arguments,thisArg:n}),r.__chain__=t,r}return e.apply(n,a([this.value()],arguments))})}),n}function $a(){return re._===this&&(re._=Al),this}function Da(){}function Ma(n){return n=kc(n),uu(function(t){return Ge(t,n)})}function Fa(n){return Bi(n)?m(no(n)):Qe(n)}function Na(n){return function(t){return null==n?X:_e(n,t)}}function Pa(){return[]}function qa(){return!1}function Za(){return{}}function Ka(){return"";
}function Va(){return!0}function Ga(n,t){if(n=kc(n),n<1||n>Wn)return[];var r=Un,e=Hl(n,Un);t=mi(t),n-=Un;for(var u=O(e,t);++r<n;)t(r);return u}function Ha(n){return bh(n)?c(n,no):bc(n)?[n]:Tu(Cs(Ec(n)))}function Ja(n){var t=++wl;return Ec(n)+t}function Ya(n){return n&&n.length?Yr(n,La,me):X}function Qa(n,t){return n&&n.length?Yr(n,mi(t,2),me):X}function Xa(n){return w(n,La)}function nl(n,t){return w(n,mi(t,2))}function tl(n){return n&&n.length?Yr(n,La,Ne):X}function rl(n,t){return n&&n.length?Yr(n,mi(t,2),Ne):X;
}function el(n){return n&&n.length?k(n,La):0}function ul(n,t){return n&&n.length?k(n,mi(t,2)):0}x=null==x?re:be.defaults(re.Object(),x,be.pick(re,qr));var il=x.Array,ol=x.Date,fl=x.Error,cl=x.Function,al=x.Math,ll=x.Object,sl=x.RegExp,hl=x.String,pl=x.TypeError,_l=il.prototype,vl=cl.prototype,gl=ll.prototype,yl=x["__core-js_shared__"],dl=vl.toString,bl=gl.hasOwnProperty,wl=0,ml=function(){var n=/[^.]+$/.exec(yl&&yl.keys&&yl.keys.IE_PROTO||"");return n?"Symbol(src)_1."+n:""}(),xl=gl.toString,jl=dl.call(ll),Al=re._,kl=sl("^"+dl.call(bl).replace(St,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),Ol=ie?x.Buffer:X,Il=x.Symbol,Rl=x.Uint8Array,zl=Ol?Ol.allocUnsafe:X,El=F(ll.getPrototypeOf,ll),Sl=ll.create,Wl=gl.propertyIsEnumerable,Ll=_l.splice,Cl=Il?Il.isConcatSpreadable:X,Ul=Il?Il.iterator:X,Bl=Il?Il.toStringTag:X,Tl=function(){
try{var n=Ai(ll,"defineProperty");return n({},"",{}),n}catch(n){}}(),$l=x.clearTimeout!==re.clearTimeout&&x.clearTimeout,Dl=ol&&ol.now!==re.Date.now&&ol.now,Ml=x.setTimeout!==re.setTimeout&&x.setTimeout,Fl=al.ceil,Nl=al.floor,Pl=ll.getOwnPropertySymbols,ql=Ol?Ol.isBuffer:X,Zl=x.isFinite,Kl=_l.join,Vl=F(ll.keys,ll),Gl=al.max,Hl=al.min,Jl=ol.now,Yl=x.parseInt,Ql=al.random,Xl=_l.reverse,ns=Ai(x,"DataView"),ts=Ai(x,"Map"),rs=Ai(x,"Promise"),es=Ai(x,"Set"),us=Ai(x,"WeakMap"),is=Ai(ll,"create"),os=us&&new us,fs={},cs=to(ns),as=to(ts),ls=to(rs),ss=to(es),hs=to(us),ps=Il?Il.prototype:X,_s=ps?ps.valueOf:X,vs=ps?ps.toString:X,gs=function(){
function n(){}return function(t){if(!fc(t))return{};if(Sl)return Sl(t);n.prototype=t;var r=new n;return n.prototype=X,r}}();Z.templateSettings={escape:kt,evaluate:Ot,interpolate:It,variable:"",imports:{_:Z}},Z.prototype=J.prototype,Z.prototype.constructor=Z,Y.prototype=gs(J.prototype),Y.prototype.constructor=Y,Ct.prototype=gs(J.prototype),Ct.prototype.constructor=Ct,Xt.prototype.clear=nr,Xt.prototype.delete=tr,Xt.prototype.get=rr,Xt.prototype.has=er,Xt.prototype.set=ur,ir.prototype.clear=or,ir.prototype.delete=fr,
ir.prototype.get=cr,ir.prototype.has=ar,ir.prototype.set=lr,sr.prototype.clear=hr,sr.prototype.delete=pr,sr.prototype.get=_r,sr.prototype.has=vr,sr.prototype.set=gr,yr.prototype.add=yr.prototype.push=dr,yr.prototype.has=br,wr.prototype.clear=mr,wr.prototype.delete=xr,wr.prototype.get=jr,wr.prototype.has=Ar,wr.prototype.set=kr;var ys=Pu(ue),ds=Pu(oe,!0),bs=qu(),ws=qu(!0),ms=os?function(n,t){return os.set(n,t),n}:La,xs=Tl?function(n,t){return Tl(n,"toString",{configurable:!0,enumerable:!1,value:Sa(t),
writable:!0})}:La,js=uu,As=$l||function(n){return re.clearTimeout(n)},ks=es&&1/P(new es([,-0]))[1]==Sn?function(n){return new es(n)}:Da,Os=os?function(n){return os.get(n)}:Da,Is=Pl?function(n){return null==n?[]:(n=ll(n),i(Pl(n),function(t){return Wl.call(n,t)}))}:Pa,Rs=Pl?function(n){for(var t=[];n;)a(t,Is(n)),n=El(n);return t}:Pa,zs=we;(ns&&zs(new ns(new ArrayBuffer(1)))!=ct||ts&&zs(new ts)!=Gn||rs&&zs(rs.resolve())!=Qn||es&&zs(new es)!=tt||us&&zs(new us)!=it)&&(zs=function(n){var t=we(n),r=t==Yn?n.constructor:X,e=r?to(r):"";
if(e)switch(e){case cs:return ct;case as:return Gn;case ls:return Qn;case ss:return tt;case hs:return it}return t});var Es=yl?uc:qa,Ss=Qi(ms),Ws=Ml||function(n,t){return re.setTimeout(n,t)},Ls=Qi(xs),Cs=Pi(function(n){var t=[];return 46===n.charCodeAt(0)&&t.push(""),n.replace(Et,function(n,r,e,u){t.push(e?u.replace(Mt,"$1"):r||n)}),t}),Us=uu(function(n,t){return Jf(n)?Hr(n,ee(t,1,Jf,!0)):[]}),Bs=uu(function(n,t){var r=jo(t);return Jf(r)&&(r=X),Jf(n)?Hr(n,ee(t,1,Jf,!0),mi(r,2)):[]}),Ts=uu(function(n,t){
var r=jo(t);return Jf(r)&&(r=X),Jf(n)?Hr(n,ee(t,1,Jf,!0),X,r):[]}),$s=uu(function(n){var t=c(n,ju);return t.length&&t[0]===n[0]?ke(t):[]}),Ds=uu(function(n){var t=jo(n),r=c(n,ju);return t===jo(r)?t=X:r.pop(),r.length&&r[0]===n[0]?ke(r,mi(t,2)):[]}),Ms=uu(function(n){var t=jo(n),r=c(n,ju);return t="function"==typeof t?t:X,t&&r.pop(),r.length&&r[0]===n[0]?ke(r,X,t):[]}),Fs=uu(Oo),Ns=gi(function(n,t){var r=null==n?0:n.length,e=Tr(n,t);return nu(n,c(t,function(n){return Ci(n,r)?+n:n}).sort(Lu)),e}),Ps=uu(function(n){
return gu(ee(n,1,Jf,!0))}),qs=uu(function(n){var t=jo(n);return Jf(t)&&(t=X),gu(ee(n,1,Jf,!0),mi(t,2))}),Zs=uu(function(n){var t=jo(n);return t="function"==typeof t?t:X,gu(ee(n,1,Jf,!0),X,t)}),Ks=uu(function(n,t){return Jf(n)?Hr(n,t):[]}),Vs=uu(function(n){return mu(i(n,Jf))}),Gs=uu(function(n){var t=jo(n);return Jf(t)&&(t=X),mu(i(n,Jf),mi(t,2))}),Hs=uu(function(n){var t=jo(n);return t="function"==typeof t?t:X,mu(i(n,Jf),X,t)}),Js=uu(Go),Ys=uu(function(n){var t=n.length,r=t>1?n[t-1]:X;return r="function"==typeof r?(n.pop(),
r):X,Ho(n,r)}),Qs=gi(function(n){var t=n.length,r=t?n[0]:0,e=this.__wrapped__,u=function(t){return Tr(t,n)};return!(t>1||this.__actions__.length)&&e instanceof Ct&&Ci(r)?(e=e.slice(r,+r+(t?1:0)),e.__actions__.push({func:nf,args:[u],thisArg:X}),new Y(e,this.__chain__).thru(function(n){return t&&!n.length&&n.push(X),n})):this.thru(u)}),Xs=Fu(function(n,t,r){bl.call(n,r)?++n[r]:Br(n,r,1)}),nh=Ju(ho),th=Ju(po),rh=Fu(function(n,t,r){bl.call(n,r)?n[r].push(t):Br(n,r,[t])}),eh=uu(function(t,r,e){var u=-1,i="function"==typeof r,o=Hf(t)?il(t.length):[];
return ys(t,function(t){o[++u]=i?n(r,t,e):Ie(t,r,e)}),o}),uh=Fu(function(n,t,r){Br(n,r,t)}),ih=Fu(function(n,t,r){n[r?0:1].push(t)},function(){return[[],[]]}),oh=uu(function(n,t){if(null==n)return[];var r=t.length;return r>1&&Ui(n,t[0],t[1])?t=[]:r>2&&Ui(t[0],t[1],t[2])&&(t=[t[0]]),He(n,ee(t,1),[])}),fh=Dl||function(){return re.Date.now()},ch=uu(function(n,t,r){var e=_n;if(r.length){var u=N(r,wi(ch));e|=bn}return ai(n,e,t,r,u)}),ah=uu(function(n,t,r){var e=_n|vn;if(r.length){var u=N(r,wi(ah));e|=bn;
}return ai(t,e,n,r,u)}),lh=uu(function(n,t){return Gr(n,1,t)}),sh=uu(function(n,t,r){return Gr(n,Ic(t)||0,r)});Cf.Cache=sr;var hh=js(function(t,r){r=1==r.length&&bh(r[0])?c(r[0],z(mi())):c(ee(r,1),z(mi()));var e=r.length;return uu(function(u){for(var i=-1,o=Hl(u.length,e);++i<o;)u[i]=r[i].call(this,u[i]);return n(t,this,u)})}),ph=uu(function(n,t){return ai(n,bn,X,t,N(t,wi(ph)))}),_h=uu(function(n,t){return ai(n,wn,X,t,N(t,wi(_h)))}),vh=gi(function(n,t){return ai(n,xn,X,X,X,t)}),gh=ii(me),yh=ii(function(n,t){
return n>=t}),dh=Re(function(){return arguments}())?Re:function(n){return cc(n)&&bl.call(n,"callee")&&!Wl.call(n,"callee")},bh=il.isArray,wh=ce?z(ce):ze,mh=ql||qa,xh=ae?z(ae):Ee,jh=le?z(le):Le,Ah=se?z(se):Be,kh=he?z(he):Te,Oh=pe?z(pe):$e,Ih=ii(Ne),Rh=ii(function(n,t){return n<=t}),zh=Nu(function(n,t){if(Mi(t)||Hf(t))return $u(t,Pc(t),n),X;for(var r in t)bl.call(t,r)&&Sr(n,r,t[r])}),Eh=Nu(function(n,t){$u(t,qc(t),n)}),Sh=Nu(function(n,t,r,e){$u(t,qc(t),n,e)}),Wh=Nu(function(n,t,r,e){$u(t,Pc(t),n,e);
}),Lh=gi(Tr),Ch=uu(function(n,t){n=ll(n);var r=-1,e=t.length,u=e>2?t[2]:X;for(u&&Ui(t[0],t[1],u)&&(e=1);++r<e;)for(var i=t[r],o=qc(i),f=-1,c=o.length;++f<c;){var a=o[f],l=n[a];(l===X||Gf(l,gl[a])&&!bl.call(n,a))&&(n[a]=i[a])}return n}),Uh=uu(function(t){return t.push(X,si),n(Mh,X,t)}),Bh=Xu(function(n,t,r){null!=t&&"function"!=typeof t.toString&&(t=xl.call(t)),n[t]=r},Sa(La)),Th=Xu(function(n,t,r){null!=t&&"function"!=typeof t.toString&&(t=xl.call(t)),bl.call(n,t)?n[t].push(r):n[t]=[r]},mi),$h=uu(Ie),Dh=Nu(function(n,t,r){
Ke(n,t,r)}),Mh=Nu(function(n,t,r,e){Ke(n,t,r,e)}),Fh=gi(function(n,t){var r={};if(null==n)return r;var e=!1;t=c(t,function(t){return t=ku(t,n),e||(e=t.length>1),t}),$u(n,di(n),r),e&&(r=Fr(r,an|ln|sn,hi));for(var u=t.length;u--;)yu(r,t[u]);return r}),Nh=gi(function(n,t){return null==n?{}:Je(n,t)}),Ph=ci(Pc),qh=ci(qc),Zh=Vu(function(n,t,r){return t=t.toLowerCase(),n+(r?fa(t):t)}),Kh=Vu(function(n,t,r){return n+(r?"-":"")+t.toLowerCase()}),Vh=Vu(function(n,t,r){return n+(r?" ":"")+t.toLowerCase()}),Gh=Ku("toLowerCase"),Hh=Vu(function(n,t,r){
return n+(r?"_":"")+t.toLowerCase()}),Jh=Vu(function(n,t,r){return n+(r?" ":"")+Qh(t)}),Yh=Vu(function(n,t,r){return n+(r?" ":"")+t.toUpperCase()}),Qh=Ku("toUpperCase"),Xh=uu(function(t,r){try{return n(t,X,r)}catch(n){return rc(n)?n:new fl(n)}}),np=gi(function(n,t){return r(t,function(t){t=no(t),Br(n,t,ch(n[t],n))}),n}),tp=Yu(),rp=Yu(!0),ep=uu(function(n,t){return function(r){return Ie(r,n,t)}}),up=uu(function(n,t){return function(r){return Ie(n,r,t)}}),ip=ti(c),op=ti(u),fp=ti(h),cp=ui(),ap=ui(!0),lp=ni(function(n,t){
return n+t},0),sp=fi("ceil"),hp=ni(function(n,t){return n/t},1),pp=fi("floor"),_p=ni(function(n,t){return n*t},1),vp=fi("round"),gp=ni(function(n,t){return n-t},0);return Z.after=If,Z.ary=Rf,Z.assign=zh,Z.assignIn=Eh,Z.assignInWith=Sh,Z.assignWith=Wh,Z.at=Lh,Z.before=zf,Z.bind=ch,Z.bindAll=np,Z.bindKey=ah,Z.castArray=Nf,Z.chain=Qo,Z.chunk=uo,Z.compact=io,Z.concat=oo,Z.cond=za,Z.conforms=Ea,Z.constant=Sa,Z.countBy=Xs,Z.create=Sc,Z.curry=Ef,Z.curryRight=Sf,Z.debounce=Wf,Z.defaults=Ch,Z.defaultsDeep=Uh,
Z.defer=lh,Z.delay=sh,Z.difference=Us,Z.differenceBy=Bs,Z.differenceWith=Ts,Z.drop=fo,Z.dropRight=co,Z.dropRightWhile=ao,Z.dropWhile=lo,Z.fill=so,Z.filter=lf,Z.flatMap=sf,Z.flatMapDeep=hf,Z.flatMapDepth=pf,Z.flatten=_o,Z.flattenDeep=vo,Z.flattenDepth=go,Z.flip=Lf,Z.flow=tp,Z.flowRight=rp,Z.fromPairs=yo,Z.functions=$c,Z.functionsIn=Dc,Z.groupBy=rh,Z.initial=mo,Z.intersection=$s,Z.intersectionBy=Ds,Z.intersectionWith=Ms,Z.invert=Bh,Z.invertBy=Th,Z.invokeMap=eh,Z.iteratee=Ca,Z.keyBy=uh,Z.keys=Pc,Z.keysIn=qc,
Z.map=yf,Z.mapKeys=Zc,Z.mapValues=Kc,Z.matches=Ua,Z.matchesProperty=Ba,Z.memoize=Cf,Z.merge=Dh,Z.mergeWith=Mh,Z.method=ep,Z.methodOf=up,Z.mixin=Ta,Z.negate=Uf,Z.nthArg=Ma,Z.omit=Fh,Z.omitBy=Vc,Z.once=Bf,Z.orderBy=df,Z.over=ip,Z.overArgs=hh,Z.overEvery=op,Z.overSome=fp,Z.partial=ph,Z.partialRight=_h,Z.partition=ih,Z.pick=Nh,Z.pickBy=Gc,Z.property=Fa,Z.propertyOf=Na,Z.pull=Fs,Z.pullAll=Oo,Z.pullAllBy=Io,Z.pullAllWith=Ro,Z.pullAt=Ns,Z.range=cp,Z.rangeRight=ap,Z.rearg=vh,Z.reject=mf,Z.remove=zo,Z.rest=Tf,
Z.reverse=Eo,Z.sampleSize=jf,Z.set=Jc,Z.setWith=Yc,Z.shuffle=Af,Z.slice=So,Z.sortBy=oh,Z.sortedUniq=$o,Z.sortedUniqBy=Do,Z.split=da,Z.spread=$f,Z.tail=Mo,Z.take=Fo,Z.takeRight=No,Z.takeRightWhile=Po,Z.takeWhile=qo,Z.tap=Xo,Z.throttle=Df,Z.thru=nf,Z.toArray=jc,Z.toPairs=Ph,Z.toPairsIn=qh,Z.toPath=Ha,Z.toPlainObject=Rc,Z.transform=Qc,Z.unary=Mf,Z.union=Ps,Z.unionBy=qs,Z.unionWith=Zs,Z.uniq=Zo,Z.uniqBy=Ko,Z.uniqWith=Vo,Z.unset=Xc,Z.unzip=Go,Z.unzipWith=Ho,Z.update=na,Z.updateWith=ta,Z.values=ra,Z.valuesIn=ea,
Z.without=Ks,Z.words=Ra,Z.wrap=Ff,Z.xor=Vs,Z.xorBy=Gs,Z.xorWith=Hs,Z.zip=Js,Z.zipObject=Jo,Z.zipObjectDeep=Yo,Z.zipWith=Ys,Z.entries=Ph,Z.entriesIn=qh,Z.extend=Eh,Z.extendWith=Sh,Ta(Z,Z),Z.add=lp,Z.attempt=Xh,Z.camelCase=Zh,Z.capitalize=fa,Z.ceil=sp,Z.clamp=ua,Z.clone=Pf,Z.cloneDeep=Zf,Z.cloneDeepWith=Kf,Z.cloneWith=qf,Z.conformsTo=Vf,Z.deburr=ca,Z.defaultTo=Wa,Z.divide=hp,Z.endsWith=aa,Z.eq=Gf,Z.escape=la,Z.escapeRegExp=sa,Z.every=af,Z.find=nh,Z.findIndex=ho,Z.findKey=Wc,Z.findLast=th,Z.findLastIndex=po,
Z.findLastKey=Lc,Z.floor=pp,Z.forEach=_f,Z.forEachRight=vf,Z.forIn=Cc,Z.forInRight=Uc,Z.forOwn=Bc,Z.forOwnRight=Tc,Z.get=Mc,Z.gt=gh,Z.gte=yh,Z.has=Fc,Z.hasIn=Nc,Z.head=bo,Z.identity=La,Z.includes=gf,Z.indexOf=wo,Z.inRange=ia,Z.invoke=$h,Z.isArguments=dh,Z.isArray=bh,Z.isArrayBuffer=wh,Z.isArrayLike=Hf,Z.isArrayLikeObject=Jf,Z.isBoolean=Yf,Z.isBuffer=mh,Z.isDate=xh,Z.isElement=Qf,Z.isEmpty=Xf,Z.isEqual=nc,Z.isEqualWith=tc,Z.isError=rc,Z.isFinite=ec,Z.isFunction=uc,Z.isInteger=ic,Z.isLength=oc,Z.isMap=jh,
Z.isMatch=ac,Z.isMatchWith=lc,Z.isNaN=sc,Z.isNative=hc,Z.isNil=_c,Z.isNull=pc,Z.isNumber=vc,Z.isObject=fc,Z.isObjectLike=cc,Z.isPlainObject=gc,Z.isRegExp=Ah,Z.isSafeInteger=yc,Z.isSet=kh,Z.isString=dc,Z.isSymbol=bc,Z.isTypedArray=Oh,Z.isUndefined=wc,Z.isWeakMap=mc,Z.isWeakSet=xc,Z.join=xo,Z.kebabCase=Kh,Z.last=jo,Z.lastIndexOf=Ao,Z.lowerCase=Vh,Z.lowerFirst=Gh,Z.lt=Ih,Z.lte=Rh,Z.max=Ya,Z.maxBy=Qa,Z.mean=Xa,Z.meanBy=nl,Z.min=tl,Z.minBy=rl,Z.stubArray=Pa,Z.stubFalse=qa,Z.stubObject=Za,Z.stubString=Ka,
Z.stubTrue=Va,Z.multiply=_p,Z.nth=ko,Z.noConflict=$a,Z.noop=Da,Z.now=fh,Z.pad=ha,Z.padEnd=pa,Z.padStart=_a,Z.parseInt=va,Z.random=oa,Z.reduce=bf,Z.reduceRight=wf,Z.repeat=ga,Z.replace=ya,Z.result=Hc,Z.round=vp,Z.runInContext=p,Z.sample=xf,Z.size=kf,Z.snakeCase=Hh,Z.some=Of,Z.sortedIndex=Wo,Z.sortedIndexBy=Lo,Z.sortedIndexOf=Co,Z.sortedLastIndex=Uo,Z.sortedLastIndexBy=Bo,Z.sortedLastIndexOf=To,Z.startCase=Jh,Z.startsWith=ba,Z.subtract=gp,Z.sum=el,Z.sumBy=ul,Z.template=wa,Z.times=Ga,Z.toFinite=Ac,Z.toInteger=kc,
Z.toLength=Oc,Z.toLower=ma,Z.toNumber=Ic,Z.toSafeInteger=zc,Z.toString=Ec,Z.toUpper=xa,Z.trim=ja,Z.trimEnd=Aa,Z.trimStart=ka,Z.truncate=Oa,Z.unescape=Ia,Z.uniqueId=Ja,Z.upperCase=Yh,Z.upperFirst=Qh,Z.each=_f,Z.eachRight=vf,Z.first=bo,Ta(Z,function(){var n={};return ue(Z,function(t,r){bl.call(Z.prototype,r)||(n[r]=t)}),n}(),{chain:!1}),Z.VERSION=nn,r(["bind","bindKey","curry","curryRight","partial","partialRight"],function(n){Z[n].placeholder=Z}),r(["drop","take"],function(n,t){Ct.prototype[n]=function(r){
r=r===X?1:Gl(kc(r),0);var e=this.__filtered__&&!t?new Ct(this):this.clone();return e.__filtered__?e.__takeCount__=Hl(r,e.__takeCount__):e.__views__.push({size:Hl(r,Un),type:n+(e.__dir__<0?"Right":"")}),e},Ct.prototype[n+"Right"]=function(t){return this.reverse()[n](t).reverse()}}),r(["filter","map","takeWhile"],function(n,t){var r=t+1,e=r==Rn||r==En;Ct.prototype[n]=function(n){var t=this.clone();return t.__iteratees__.push({iteratee:mi(n,3),type:r}),t.__filtered__=t.__filtered__||e,t}}),r(["head","last"],function(n,t){
var r="take"+(t?"Right":"");Ct.prototype[n]=function(){return this[r](1).value()[0]}}),r(["initial","tail"],function(n,t){var r="drop"+(t?"":"Right");Ct.prototype[n]=function(){return this.__filtered__?new Ct(this):this[r](1)}}),Ct.prototype.compact=function(){return this.filter(La)},Ct.prototype.find=function(n){return this.filter(n).head()},Ct.prototype.findLast=function(n){return this.reverse().find(n)},Ct.prototype.invokeMap=uu(function(n,t){return"function"==typeof n?new Ct(this):this.map(function(r){
return Ie(r,n,t)})}),Ct.prototype.reject=function(n){return this.filter(Uf(mi(n)))},Ct.prototype.slice=function(n,t){n=kc(n);var r=this;return r.__filtered__&&(n>0||t<0)?new Ct(r):(n<0?r=r.takeRight(-n):n&&(r=r.drop(n)),t!==X&&(t=kc(t),r=t<0?r.dropRight(-t):r.take(t-n)),r)},Ct.prototype.takeRightWhile=function(n){return this.reverse().takeWhile(n).reverse()},Ct.prototype.toArray=function(){return this.take(Un)},ue(Ct.prototype,function(n,t){var r=/^(?:filter|find|map|reject)|While$/.test(t),e=/^(?:head|last)$/.test(t),u=Z[e?"take"+("last"==t?"Right":""):t],i=e||/^find/.test(t);
u&&(Z.prototype[t]=function(){var t=this.__wrapped__,o=e?[1]:arguments,f=t instanceof Ct,c=o[0],l=f||bh(t),s=function(n){var t=u.apply(Z,a([n],o));return e&&h?t[0]:t};l&&r&&"function"==typeof c&&1!=c.length&&(f=l=!1);var h=this.__chain__,p=!!this.__actions__.length,_=i&&!h,v=f&&!p;if(!i&&l){t=v?t:new Ct(this);var g=n.apply(t,o);return g.__actions__.push({func:nf,args:[s],thisArg:X}),new Y(g,h)}return _&&v?n.apply(this,o):(g=this.thru(s),_?e?g.value()[0]:g.value():g)})}),r(["pop","push","shift","sort","splice","unshift"],function(n){
var t=_l[n],r=/^(?:push|sort|unshift)$/.test(n)?"tap":"thru",e=/^(?:pop|shift)$/.test(n);Z.prototype[n]=function(){var n=arguments;if(e&&!this.__chain__){var u=this.value();return t.apply(bh(u)?u:[],n)}return this[r](function(r){return t.apply(bh(r)?r:[],n)})}}),ue(Ct.prototype,function(n,t){var r=Z[t];if(r){var e=r.name+"";bl.call(fs,e)||(fs[e]=[]),fs[e].push({name:t,func:r})}}),fs[Qu(X,vn).name]=[{name:"wrapper",func:X}],Ct.prototype.clone=$t,Ct.prototype.reverse=Yt,Ct.prototype.value=Qt,Z.prototype.at=Qs,
Z.prototype.chain=tf,Z.prototype.commit=rf,Z.prototype.next=ef,Z.prototype.plant=of,Z.prototype.reverse=ff,Z.prototype.toJSON=Z.prototype.valueOf=Z.prototype.value=cf,Z.prototype.first=Z.prototype.head,Ul&&(Z.prototype[Ul]=uf),Z},be=de();"function"==typeof define&&"object"==typeof define.amd&&define.amd?(re._=be,define(function(){return be})):ue?((ue.exports=be)._=be,ee._=be):re._=be}).call(this);
/* Blob.js
 * A Blob, File, FileReader & URL implementation.
 * 2020-02-01
 *
 * By Eli Grey, https://eligrey.com
 * By Jimmy Wärting, https://github.com/jimmywarting
 * License: MIT
 *   See https://github.com/eligrey/Blob.js/blob/master/LICENSE.md
 */

(function(global) {
	(function (factory) {
		if (typeof define === "function" && define.amd) {
			// AMD. Register as an anonymous module.
			define(["exports"], factory);
		} else if (typeof exports === "object" && typeof exports.nodeName !== "string") {
			// CommonJS
			factory(exports);
		} else {
			// Browser globals
			factory(global);
		}
	})(function (exports) {
		"use strict";

		var BlobBuilder = global.BlobBuilder
			|| global.WebKitBlobBuilder
			|| global.MSBlobBuilder
			|| global.MozBlobBuilder;

		var URL = global.URL || global.webkitURL || function (href, a) {
			a = document.createElement("a");
			a.href = href;
			return a;
		};

		var origBlob = global.Blob;
		var createObjectURL = URL.createObjectURL;
		var revokeObjectURL = URL.revokeObjectURL;
		var strTag = global.Symbol && global.Symbol.toStringTag;
		var blobSupported = false;
		var blobSupportsArrayBufferView = false;
		var blobBuilderSupported = BlobBuilder
			&& BlobBuilder.prototype.append
			&& BlobBuilder.prototype.getBlob;

		try {
			// Check if Blob constructor is supported
			blobSupported = new Blob(["ä"]).size === 2;

			// Check if Blob constructor supports ArrayBufferViews
			// Fails in Safari 6, so we need to map to ArrayBuffers there.
			blobSupportsArrayBufferView = new Blob([new Uint8Array([1, 2])]).size === 2;
		} catch (e) {/**/}


		// Helper function that maps ArrayBufferViews to ArrayBuffers
		// Used by BlobBuilder constructor and old browsers that didn't
		// support it in the Blob constructor.
		function mapArrayBufferViews (ary) {
			return ary.map(function (chunk) {
				if (chunk.buffer instanceof ArrayBuffer) {
					var buf = chunk.buffer;

					// if this is a subarray, make a copy so we only
					// include the subarray region from the underlying buffer
					if (chunk.byteLength !== buf.byteLength) {
						var copy = new Uint8Array(chunk.byteLength);
						copy.set(new Uint8Array(buf, chunk.byteOffset, chunk.byteLength));
						buf = copy.buffer;
					}

					return buf;
				}

				return chunk;
			});
		}

		function BlobBuilderConstructor (ary, options) {
			options = options || {};

			var bb = new BlobBuilder();
			mapArrayBufferViews(ary).forEach(function (part) {
				bb.append(part);
			});

			return options.type ? bb.getBlob(options.type) : bb.getBlob();
		}

		function BlobConstructor (ary, options) {
			return new origBlob(mapArrayBufferViews(ary), options || {});
		}

		if (global.Blob) {
			BlobBuilderConstructor.prototype = Blob.prototype;
			BlobConstructor.prototype = Blob.prototype;
		}

		/********************************************************/
		/*               String Encoder fallback                */
		/********************************************************/
		function stringEncode (string) {
			var pos = 0;
			var len = string.length;
			var Arr = global.Uint8Array || Array; // Use byte array when possible

			var at = 0; // output position
			var tlen = Math.max(32, len + (len >> 1) + 7); // 1.5x size
			var target = new Arr((tlen >> 3) << 3); // ... but at 8 byte offset

			while (pos < len) {
				var value = string.charCodeAt(pos++);
				if (value >= 0xd800 && value <= 0xdbff) {
					// high surrogate
					if (pos < len) {
						var extra = string.charCodeAt(pos);
						if ((extra & 0xfc00) === 0xdc00) {
							++pos;
							value = ((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000;
						}
					}
					if (value >= 0xd800 && value <= 0xdbff) {
						continue; // drop lone surrogate
					}
				}

				// expand the buffer if we couldn't write 4 bytes
				if (at + 4 > target.length) {
					tlen += 8; // minimum extra
					tlen *= (1.0 + (pos / string.length) * 2); // take 2x the remaining
					tlen = (tlen >> 3) << 3; // 8 byte offset

					var update = new Uint8Array(tlen);
					update.set(target);
					target = update;
				}

				if ((value & 0xffffff80) === 0) { // 1-byte
					target[at++] = value; // ASCII
					continue;
				} else if ((value & 0xfffff800) === 0) { // 2-byte
					target[at++] = ((value >> 6) & 0x1f) | 0xc0;
				} else if ((value & 0xffff0000) === 0) { // 3-byte
					target[at++] = ((value >> 12) & 0x0f) | 0xe0;
					target[at++] = ((value >> 6) & 0x3f) | 0x80;
				} else if ((value & 0xffe00000) === 0) { // 4-byte
					target[at++] = ((value >> 18) & 0x07) | 0xf0;
					target[at++] = ((value >> 12) & 0x3f) | 0x80;
					target[at++] = ((value >> 6) & 0x3f) | 0x80;
				} else {
					// FIXME: do we care
					continue;
				}

				target[at++] = (value & 0x3f) | 0x80;
			}

			return target.slice(0, at);
		}

		/********************************************************/
		/*               String Decoder fallback                */
		/********************************************************/
		function stringDecode (buf) {
			var end = buf.length;
			var res = [];

			var i = 0;
			while (i < end) {
				var firstByte = buf[i];
				var codePoint = null;
				var bytesPerSequence = (firstByte > 0xEF) ? 4
					: (firstByte > 0xDF) ? 3
						: (firstByte > 0xBF) ? 2
							: 1;

				if (i + bytesPerSequence <= end) {
					var secondByte, thirdByte, fourthByte, tempCodePoint;

					switch (bytesPerSequence) {
					case 1:
						if (firstByte < 0x80) {
							codePoint = firstByte;
						}
						break;
					case 2:
						secondByte = buf[i + 1];
						if ((secondByte & 0xC0) === 0x80) {
							tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F);
							if (tempCodePoint > 0x7F) {
								codePoint = tempCodePoint;
							}
						}
						break;
					case 3:
						secondByte = buf[i + 1];
						thirdByte = buf[i + 2];
						if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) {
							tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F);
							if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) {
								codePoint = tempCodePoint;
							}
						}
						break;
					case 4:
						secondByte = buf[i + 1];
						thirdByte = buf[i + 2];
						fourthByte = buf[i + 3];
						if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) {
							tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F);
							if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) {
								codePoint = tempCodePoint;
							}
						}
					}
				}

				if (codePoint === null) {
					// we did not generate a valid codePoint so insert a
					// replacement char (U+FFFD) and advance only 1 byte
					codePoint = 0xFFFD;
					bytesPerSequence = 1;
				} else if (codePoint > 0xFFFF) {
					// encode to utf16 (surrogate pair dance)
					codePoint -= 0x10000;
					res.push(codePoint >>> 10 & 0x3FF | 0xD800);
					codePoint = 0xDC00 | codePoint & 0x3FF;
				}

				res.push(codePoint);
				i += bytesPerSequence;
			}

			var len = res.length;
			var str = "";
			var j = 0;

			while (j < len) {
				str += String.fromCharCode.apply(String, res.slice(j, j += 0x1000));
			}

			return str;
		}

		// string -> buffer
		var textEncode = typeof TextEncoder === "function"
			? TextEncoder.prototype.encode.bind(new TextEncoder())
			: stringEncode;

		// buffer -> string
		var textDecode = typeof TextDecoder === "function"
			? TextDecoder.prototype.decode.bind(new TextDecoder())
			: stringDecode;

		function FakeBlobBuilder () {
			function bufferClone (buf) {
				var view = new Array(buf.byteLength);
				var array = new Uint8Array(buf);
				var i = view.length;
				while (i--) {
					view[i] = array[i];
				}
				return view;
			}
			function array2base64 (input) {
				var byteToCharMap = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

				var output = [];

				for (var i = 0; i < input.length; i += 3) {
					var byte1 = input[i];
					var haveByte2 = i + 1 < input.length;
					var byte2 = haveByte2 ? input[i + 1] : 0;
					var haveByte3 = i + 2 < input.length;
					var byte3 = haveByte3 ? input[i + 2] : 0;

					var outByte1 = byte1 >> 2;
					var outByte2 = ((byte1 & 0x03) << 4) | (byte2 >> 4);
					var outByte3 = ((byte2 & 0x0F) << 2) | (byte3 >> 6);
					var outByte4 = byte3 & 0x3F;

					if (!haveByte3) {
						outByte4 = 64;

						if (!haveByte2) {
							outByte3 = 64;
						}
					}

					output.push(
						byteToCharMap[outByte1], byteToCharMap[outByte2],
						byteToCharMap[outByte3], byteToCharMap[outByte4]
					);
				}

				return output.join("");
			}

			var create = Object.create || function (a) {
				function c () {}
				c.prototype = a;
				return new c();
			};

			function getObjectTypeName (o) {
				return Object.prototype.toString.call(o).slice(8, -1);
			}

			function isPrototypeOf(c, o) {
				return typeof c === "object" && Object.prototype.isPrototypeOf.call(c.prototype, o);
			}

			function isDataView (o) {
				return getObjectTypeName(o) === "DataView" || isPrototypeOf(global.DataView, o);
			}

			var arrayBufferClassNames = [
				"Int8Array",
				"Uint8Array",
				"Uint8ClampedArray",
				"Int16Array",
				"Uint16Array",
				"Int32Array",
				"Uint32Array",
				"Float32Array",
				"Float64Array",
				"ArrayBuffer"
			];

			function includes(a, v) {
				return a.indexOf(v) !== -1;
			}

			function isArrayBuffer(o) {
				return includes(arrayBufferClassNames, getObjectTypeName(o)) || isPrototypeOf(global.ArrayBuffer, o);
			}

			function concatTypedarrays (chunks) {
				var size = 0;
				var j = chunks.length;
				while (j--) { size += chunks[j].length; }
				var b = new Uint8Array(size);
				var offset = 0;
				for (var i = 0; i < chunks.length; i++) {
					var chunk = chunks[i];
					b.set(chunk, offset);
					offset += chunk.byteLength || chunk.length;
				}

				return b;
			}

			/********************************************************/
			/*                   Blob constructor                   */
			/********************************************************/
			function Blob (chunks, opts) {
				chunks = chunks ? chunks.slice() : [];
				opts = opts == null ? {} : opts;
				for (var i = 0, len = chunks.length; i < len; i++) {
					var chunk = chunks[i];
					if (chunk instanceof Blob) {
						chunks[i] = chunk._buffer;
					} else if (typeof chunk === "string") {
						chunks[i] = textEncode(chunk);
					} else if (isDataView(chunk)) {
						chunks[i] = bufferClone(chunk.buffer);
					} else if (isArrayBuffer(chunk)) {
						chunks[i] = bufferClone(chunk);
					} else {
						chunks[i] = textEncode(String(chunk));
					}
				}

				this._buffer = global.Uint8Array
					? concatTypedarrays(chunks)
					: [].concat.apply([], chunks);
				this.size = this._buffer.length;

				this.type = opts.type || "";
				if (/[^\u0020-\u007E]/.test(this.type)) {
					this.type = "";
				} else {
					this.type = this.type.toLowerCase();
				}
			}

			Blob.prototype.arrayBuffer = function () {
				return Promise.resolve(this._buffer.buffer || this._buffer);
			};

			Blob.prototype.text = function () {
				return Promise.resolve(textDecode(this._buffer));
			};

			Blob.prototype.slice = function (start, end, type) {
				var slice = this._buffer.slice(start || 0, end || this._buffer.length);
				return new Blob([slice], {type: type});
			};

			Blob.prototype.toString = function () {
				return "[object Blob]";
			};

			/********************************************************/
			/*                   File constructor                   */
			/********************************************************/
			function File (chunks, name, opts) {
				opts = opts || {};
				var a = Blob.call(this, chunks, opts) || this;
				a.name = name.replace(/\//g, ":");
				a.lastModifiedDate = opts.lastModified ? new Date(opts.lastModified) : new Date();
				a.lastModified = +a.lastModifiedDate;

				return a;
			}

			File.prototype = create(Blob.prototype);
			File.prototype.constructor = File;

			if (Object.setPrototypeOf) {
				Object.setPrototypeOf(File, Blob);
			} else {
				try {
					File.__proto__ = Blob;
				} catch (e) {/**/}
			}

			File.prototype.toString = function () {
				return "[object File]";
			};

			/********************************************************/
			/*                FileReader constructor                */
			/********************************************************/
			function FileReader () {
				if (!(this instanceof FileReader)) {
					throw new TypeError("Failed to construct 'FileReader': Please use the 'new' operator, this DOM object constructor cannot be called as a function.");
				}

				var delegate = document.createDocumentFragment();
				this.addEventListener = delegate.addEventListener;
				this.dispatchEvent = function (evt) {
					var local = this["on" + evt.type];
					if (typeof local === "function") local(evt);
					delegate.dispatchEvent(evt);
				};
				this.removeEventListener = delegate.removeEventListener;
			}

			function _read (fr, blob, kind) {
				if (!(blob instanceof Blob)) {
					throw new TypeError("Failed to execute '" + kind + "' on 'FileReader': parameter 1 is not of type 'Blob'.");
				}

				fr.result = "";

				setTimeout(function () {
					this.readyState = FileReader.LOADING;
					fr.dispatchEvent(new Event("load"));
					fr.dispatchEvent(new Event("loadend"));
				});
			}

			FileReader.EMPTY = 0;
			FileReader.LOADING = 1;
			FileReader.DONE = 2;
			FileReader.prototype.error = null;
			FileReader.prototype.onabort = null;
			FileReader.prototype.onerror = null;
			FileReader.prototype.onload = null;
			FileReader.prototype.onloadend = null;
			FileReader.prototype.onloadstart = null;
			FileReader.prototype.onprogress = null;

			FileReader.prototype.readAsDataURL = function (blob) {
				_read(this, blob, "readAsDataURL");
				this.result = "data:" + blob.type + ";base64," + array2base64(blob._buffer);
			};

			FileReader.prototype.readAsText = function (blob) {
				_read(this, blob, "readAsText");
				this.result = textDecode(blob._buffer);
			};

			FileReader.prototype.readAsArrayBuffer = function (blob) {
				_read(this, blob, "readAsText");
				// return ArrayBuffer when possible
				this.result = (blob._buffer.buffer || blob._buffer).slice();
			};

			FileReader.prototype.abort = function () {};

			/********************************************************/
			/*                         URL                          */
			/********************************************************/
			URL.createObjectURL = function (blob) {
				return blob instanceof Blob
					? "data:" + blob.type + ";base64," + array2base64(blob._buffer)
					: createObjectURL.call(URL, blob);
			};

			URL.revokeObjectURL = function (url) {
				revokeObjectURL && revokeObjectURL.call(URL, url);
			};

			/********************************************************/
			/*                         XHR                          */
			/********************************************************/
			var _send = global.XMLHttpRequest && global.XMLHttpRequest.prototype.send;
			if (_send) {
				XMLHttpRequest.prototype.send = function (data) {
					if (data instanceof Blob) {
						this.setRequestHeader("Content-Type", data.type);
						_send.call(this, textDecode(data._buffer));
					} else {
						_send.call(this, data);
					}
				};
			}

			exports.Blob = Blob;
			exports.File = File;
			exports.FileReader = FileReader;
			exports.URL = URL;
		}

		function fixFileAndXHR () {
			var isIE = !!global.ActiveXObject || (
				"-ms-scroll-limit" in document.documentElement.style &&
				"-ms-ime-align" in document.documentElement.style
			);

			// Monkey patched
			// IE doesn't set Content-Type header on XHR whose body is a typed Blob
			// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/6047383
			var _send = global.XMLHttpRequest && global.XMLHttpRequest.prototype.send;
			if (isIE && _send) {
				XMLHttpRequest.prototype.send = function (data) {
					if (data instanceof Blob) {
						this.setRequestHeader("Content-Type", data.type);
						_send.call(this, data);
					} else {
						_send.call(this, data);
					}
				};
			}

			try {
				new File([], "");
				exports.File = global.File;
				exports.FileReader = global.FileReader;
			} catch (e) {
				try {
					exports.File = new Function("class File extends Blob {" +
						"constructor(chunks, name, opts) {" +
							"opts = opts || {};" +
							"super(chunks, opts || {});" +
							"this.name = name.replace(/\\//g, \":\");" +
							"this.lastModifiedDate = opts.lastModified ? new Date(opts.lastModified) : new Date();" +
							"this.lastModified = +this.lastModifiedDate;" +
						"}};" +
						"return new File([], \"\"), File"
					)();
				} catch (e) {
					exports.File = function (b, d, c) {
						var blob = new Blob(b, c);
						var t = c && void 0 !== c.lastModified ? new Date(c.lastModified) : new Date();

						blob.name = d.replace(/\//g, ":");
						blob.lastModifiedDate = t;
						blob.lastModified = +t;
						blob.toString = function () {
							return "[object File]";
						};

						if (strTag) {
							blob[strTag] = "File";
						}

						return blob;
					};
				}
			}
		}

		if (blobSupported) {
			fixFileAndXHR();
			exports.Blob = blobSupportsArrayBufferView ? global.Blob : BlobConstructor;
		} else if (blobBuilderSupported) {
			fixFileAndXHR();
			exports.Blob = BlobBuilderConstructor;
		} else {
			FakeBlobBuilder();
		}

		if (strTag) {
			if (!exports.File.prototype[strTag]) exports.File.prototype[strTag] = "File";
			if (!exports.Blob.prototype[strTag]) exports.Blob.prototype[strTag] = "Blob";
			if (!exports.FileReader.prototype[strTag]) exports.FileReader.prototype[strTag] = "FileReader";
		}

		var blob = exports.Blob.prototype;
		var stream;

		try {
			new ReadableStream({ type: "bytes" });
			stream = function stream() {
				var position = 0;
				var blob = this;

				return new ReadableStream({
					type: "bytes",
					autoAllocateChunkSize: 524288,

					pull: function (controller) {
						var v = controller.byobRequest.view;
						var chunk = blob.slice(position, position + v.byteLength);
						return chunk.arrayBuffer()
							.then(function (buffer) {
								var uint8array = new Uint8Array(buffer);
								var bytesRead = uint8array.byteLength;

								position += bytesRead;
								v.set(uint8array);
								controller.byobRequest.respond(bytesRead);

								if(position >= blob.size)
									controller.close();
							});
					}
				});
			};
		} catch (e) {
			try {
				new ReadableStream({});
				stream = function stream(blob){
					var position = 0;

					return new ReadableStream({
						pull: function (controller) {
							var chunk = blob.slice(position, position + 524288);

							return chunk.arrayBuffer().then(function (buffer) {
								position += buffer.byteLength;
								var uint8array = new Uint8Array(buffer);
								controller.enqueue(uint8array);

								if (position == blob.size)
									controller.close();
							});
						}
					});
				};
			} catch (e) {
				try {
					new Response("").body.getReader().read();
					stream = function stream() {
						return (new Response(this)).body;
					};
				} catch (e) {
					stream = function stream() {
						throw new Error("Include https://github.com/MattiasBuelens/web-streams-polyfill");
					};
				}
			}
		}

		function promisify(obj) {
			return new Promise(function(resolve, reject) {
				obj.onload = obj.onerror = function(evt) {
					obj.onload = obj.onerror = null;

					evt.type === "load" ?
						resolve(obj.result || obj) :
						reject(new Error("Failed to read the blob/file"));
				};
			});
		}

		if (!blob.arrayBuffer) {
			blob.arrayBuffer = function arrayBuffer() {
				var fr = new FileReader();
				fr.readAsArrayBuffer(this);
				return promisify(fr);
			};
		}

		if (!blob.text) {
			blob.text = function text() {
				var fr = new FileReader();
				fr.readAsText(this);
				return promisify(fr);
			};
		}

		if (!blob.stream) {
			blob.stream = stream;
		}
	});
})(
	typeof self !== "undefined" && self ||
		typeof window !== "undefined" && window ||
		typeof global !== "undefined" && global ||
		this
);

/*! VelocityJS.org (1.5.2). (C) 2014 Julian Shapiro. MIT @license: en.wikipedia.org/wiki/MIT_License */
/*! VelocityJS.org jQuery Shim (1.0.1). (C) 2014 The jQuery Foundation. MIT @license: en.wikipedia.org/wiki/MIT_License. */
!function(a){"use strict";function b(a){var b=a.length,d=c.type(a);return"function"!==d&&!c.isWindow(a)&&(!(1!==a.nodeType||!b)||("array"===d||0===b||"number"==typeof b&&b>0&&b-1 in a))}if(!a.jQuery){var c=function(a,b){return new c.fn.init(a,b)};c.isWindow=function(a){return a&&a===a.window},c.type=function(a){return a?"object"==typeof a||"function"==typeof a?e[g.call(a)]||"object":typeof a:a+""},c.isArray=Array.isArray||function(a){return"array"===c.type(a)},c.isPlainObject=function(a){var b;if(!a||"object"!==c.type(a)||a.nodeType||c.isWindow(a))return!1;try{if(a.constructor&&!f.call(a,"constructor")&&!f.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(d){return!1}for(b in a);return b===undefined||f.call(a,b)},c.each=function(a,c,d){var e=0,f=a.length,g=b(a);if(d){if(g)for(;e<f&&!1!==c.apply(a[e],d);e++);else for(e in a)if(a.hasOwnProperty(e)&&!1===c.apply(a[e],d))break}else if(g)for(;e<f&&!1!==c.call(a[e],e,a[e]);e++);else for(e in a)if(a.hasOwnProperty(e)&&!1===c.call(a[e],e,a[e]))break;return a},c.data=function(a,b,e){if(e===undefined){var f=a[c.expando],g=f&&d[f];if(b===undefined)return g;if(g&&b in g)return g[b]}else if(b!==undefined){var h=a[c.expando]||(a[c.expando]=++c.uuid);return d[h]=d[h]||{},d[h][b]=e,e}},c.removeData=function(a,b){var e=a[c.expando],f=e&&d[e];f&&(b?c.each(b,function(a,b){delete f[b]}):delete d[e])},c.extend=function(){var a,b,d,e,f,g,h=arguments[0]||{},i=1,j=arguments.length,k=!1;for("boolean"==typeof h&&(k=h,h=arguments[i]||{},i++),"object"!=typeof h&&"function"!==c.type(h)&&(h={}),i===j&&(h=this,i--);i<j;i++)if(f=arguments[i])for(e in f)f.hasOwnProperty(e)&&(a=h[e],d=f[e],h!==d&&(k&&d&&(c.isPlainObject(d)||(b=c.isArray(d)))?(b?(b=!1,g=a&&c.isArray(a)?a:[]):g=a&&c.isPlainObject(a)?a:{},h[e]=c.extend(k,g,d)):d!==undefined&&(h[e]=d)));return h},c.queue=function(a,d,e){if(a){d=(d||"fx")+"queue";var f=c.data(a,d);return e?(!f||c.isArray(e)?f=c.data(a,d,function(a,c){var d=c||[];return a&&(b(Object(a))?function(a,b){for(var c=+b.length,d=0,e=a.length;d<c;)a[e++]=b[d++];if(c!==c)for(;b[d]!==undefined;)a[e++]=b[d++];a.length=e}(d,"string"==typeof a?[a]:a):[].push.call(d,a)),d}(e)):f.push(e),f):f||[]}},c.dequeue=function(a,b){c.each(a.nodeType?[a]:a,function(a,d){b=b||"fx";var e=c.queue(d,b),f=e.shift();"inprogress"===f&&(f=e.shift()),f&&("fx"===b&&e.unshift("inprogress"),f.call(d,function(){c.dequeue(d,b)}))})},c.fn=c.prototype={init:function(a){if(a.nodeType)return this[0]=a,this;throw new Error("Not a DOM node.")},offset:function(){var b=this[0].getBoundingClientRect?this[0].getBoundingClientRect():{top:0,left:0};return{top:b.top+(a.pageYOffset||document.scrollTop||0)-(document.clientTop||0),left:b.left+(a.pageXOffset||document.scrollLeft||0)-(document.clientLeft||0)}},position:function(){var a=this[0],b=function(a){for(var b=a.offsetParent;b&&"html"!==b.nodeName.toLowerCase()&&b.style&&"static"===b.style.position.toLowerCase();)b=b.offsetParent;return b||document}(a),d=this.offset(),e=/^(?:body|html)$/i.test(b.nodeName)?{top:0,left:0}:c(b).offset();return d.top-=parseFloat(a.style.marginTop)||0,d.left-=parseFloat(a.style.marginLeft)||0,b.style&&(e.top+=parseFloat(b.style.borderTopWidth)||0,e.left+=parseFloat(b.style.borderLeftWidth)||0),{top:d.top-e.top,left:d.left-e.left}}};var d={};c.expando="velocity"+(new Date).getTime(),c.uuid=0;for(var e={},f=e.hasOwnProperty,g=e.toString,h="Boolean Number String Function Array Date RegExp Object Error".split(" "),i=0;i<h.length;i++)e["[object "+h[i]+"]"]=h[i].toLowerCase();c.fn.init.prototype=c.fn,a.Velocity={Utilities:c}}}(window),function(a){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a():"function"==typeof define&&define.amd?define(a):a()}(function(){"use strict";return function(a,b,c,d){function e(a){for(var b=-1,c=a?a.length:0,d=[];++b<c;){var e=a[b];e&&d.push(e)}return d}function f(a){return u.isWrapped(a)?a=s.call(a):u.isNode(a)&&(a=[a]),a}function g(a){var b=o.data(a,"velocity");return null===b?d:b}function h(a,b){var c=g(a);c&&c.delayTimer&&!c.delayPaused&&(c.delayRemaining=c.delay-b+c.delayBegin,c.delayPaused=!0,clearTimeout(c.delayTimer.setTimeout))}function i(a,b){var c=g(a);c&&c.delayTimer&&c.delayPaused&&(c.delayPaused=!1,c.delayTimer.setTimeout=setTimeout(c.delayTimer.next,c.delayRemaining))}function j(a){return function(b){return Math.round(b*a)*(1/a)}}function k(a,c,d,e){function f(a,b){return 1-3*b+3*a}function g(a,b){return 3*b-6*a}function h(a){return 3*a}function i(a,b,c){return((f(b,c)*a+g(b,c))*a+h(b))*a}function j(a,b,c){return 3*f(b,c)*a*a+2*g(b,c)*a+h(b)}function k(b,c){for(var e=0;e<p;++e){var f=j(c,a,d);if(0===f)return c;c-=(i(c,a,d)-b)/f}return c}function l(){for(var b=0;b<t;++b)x[b]=i(b*u,a,d)}function m(b,c,e){var f,g,h=0;do{g=c+(e-c)/2,f=i(g,a,d)-b,f>0?e=g:c=g}while(Math.abs(f)>r&&++h<s);return g}function n(b){for(var c=0,e=1,f=t-1;e!==f&&x[e]<=b;++e)c+=u;--e;var g=(b-x[e])/(x[e+1]-x[e]),h=c+g*u,i=j(h,a,d);return i>=q?k(b,h):0===i?h:m(b,c,c+u)}function o(){y=!0,a===c&&d===e||l()}var p=4,q=.001,r=1e-7,s=10,t=11,u=1/(t-1),v="Float32Array"in b;if(4!==arguments.length)return!1;for(var w=0;w<4;++w)if("number"!=typeof arguments[w]||isNaN(arguments[w])||!isFinite(arguments[w]))return!1;a=Math.min(a,1),d=Math.min(d,1),a=Math.max(a,0),d=Math.max(d,0);var x=v?new Float32Array(t):new Array(t),y=!1,z=function(b){return y||o(),a===c&&d===e?b:0===b?0:1===b?1:i(n(b),c,e)};z.getControlPoints=function(){return[{x:a,y:c},{x:d,y:e}]};var A="generateBezier("+[a,c,d,e]+")";return z.toString=function(){return A},z}function l(a,b){var c=a;return u.isString(a)?y.Easings[a]||(c=!1):c=u.isArray(a)&&1===a.length?j.apply(null,a):u.isArray(a)&&2===a.length?z.apply(null,a.concat([b])):!(!u.isArray(a)||4!==a.length)&&k.apply(null,a),!1===c&&(c=y.Easings[y.defaults.easing]?y.defaults.easing:x),c}function m(a){if(a){var b=y.timestamp&&!0!==a?a:r.now(),c=y.State.calls.length;c>1e4&&(y.State.calls=e(y.State.calls),c=y.State.calls.length);for(var f=0;f<c;f++)if(y.State.calls[f]){var h=y.State.calls[f],i=h[0],j=h[2],k=h[3],l=!k,q=null,s=h[5],t=h[6];if(k||(k=y.State.calls[f][3]=b-16),s){if(!0!==s.resume)continue;k=h[3]=Math.round(b-t-16),h[5]=null}t=h[6]=b-k;for(var v=Math.min(t/j.duration,1),w=0,x=i.length;w<x;w++){var z=i[w],B=z.element;if(g(B)){var D=!1;if(j.display!==d&&null!==j.display&&"none"!==j.display){if("flex"===j.display){var E=["-webkit-box","-moz-box","-ms-flexbox","-webkit-flex"];o.each(E,function(a,b){A.setPropertyValue(B,"display",b)})}A.setPropertyValue(B,"display",j.display)}j.visibility!==d&&"hidden"!==j.visibility&&A.setPropertyValue(B,"visibility",j.visibility);for(var F in z)if(z.hasOwnProperty(F)&&"element"!==F){var G,H=z[F],I=u.isString(H.easing)?y.Easings[H.easing]:H.easing;if(u.isString(H.pattern)){var J=1===v?function(a,b,c){var d=H.endValue[b];return c?Math.round(d):d}:function(a,b,c){var d=H.startValue[b],e=H.endValue[b]-d,f=d+e*I(v,j,e);return c?Math.round(f):f};G=H.pattern.replace(/{(\d+)(!)?}/g,J)}else if(1===v)G=H.endValue;else{var K=H.endValue-H.startValue;G=H.startValue+K*I(v,j,K)}if(!l&&G===H.currentValue)continue;if(H.currentValue=G,"tween"===F)q=G;else{var L;if(A.Hooks.registered[F]){L=A.Hooks.getRoot(F);var M=g(B).rootPropertyValueCache[L];M&&(H.rootPropertyValue=M)}var N=A.setPropertyValue(B,F,H.currentValue+(p<9&&0===parseFloat(G)?"":H.unitType),H.rootPropertyValue,H.scrollData);A.Hooks.registered[F]&&(A.Normalizations.registered[L]?g(B).rootPropertyValueCache[L]=A.Normalizations.registered[L]("extract",null,N[1]):g(B).rootPropertyValueCache[L]=N[1]),"transform"===N[0]&&(D=!0)}}j.mobileHA&&g(B).transformCache.translate3d===d&&(g(B).transformCache.translate3d="(0px, 0px, 0px)",D=!0),D&&A.flushTransformCache(B)}}j.display!==d&&"none"!==j.display&&(y.State.calls[f][2].display=!1),j.visibility!==d&&"hidden"!==j.visibility&&(y.State.calls[f][2].visibility=!1),j.progress&&j.progress.call(h[1],h[1],v,Math.max(0,k+j.duration-b),k,q),1===v&&n(f)}}y.State.isTicking&&C(m)}function n(a,b){if(!y.State.calls[a])return!1;for(var c=y.State.calls[a][0],e=y.State.calls[a][1],f=y.State.calls[a][2],h=y.State.calls[a][4],i=!1,j=0,k=c.length;j<k;j++){var l=c[j].element;b||f.loop||("none"===f.display&&A.setPropertyValue(l,"display",f.display),"hidden"===f.visibility&&A.setPropertyValue(l,"visibility",f.visibility));var m=g(l);if(!0!==f.loop&&(o.queue(l)[1]===d||!/\.velocityQueueEntryFlag/i.test(o.queue(l)[1]))&&m){m.isAnimating=!1,m.rootPropertyValueCache={};var n=!1;o.each(A.Lists.transforms3D,function(a,b){var c=/^scale/.test(b)?1:0,e=m.transformCache[b];m.transformCache[b]!==d&&new RegExp("^\\("+c+"[^.]").test(e)&&(n=!0,delete m.transformCache[b])}),f.mobileHA&&(n=!0,delete m.transformCache.translate3d),n&&A.flushTransformCache(l),A.Values.removeClass(l,"velocity-animating")}if(!b&&f.complete&&!f.loop&&j===k-1)try{f.complete.call(e,e)}catch(r){setTimeout(function(){throw r},1)}h&&!0!==f.loop&&h(e),m&&!0===f.loop&&!b&&(o.each(m.tweensContainer,function(a,b){if(/^rotate/.test(a)&&(parseFloat(b.startValue)-parseFloat(b.endValue))%360==0){var c=b.startValue;b.startValue=b.endValue,b.endValue=c}/^backgroundPosition/.test(a)&&100===parseFloat(b.endValue)&&"%"===b.unitType&&(b.endValue=0,b.startValue=100)}),y(l,"reverse",{loop:!0,delay:f.delay})),!1!==f.queue&&o.dequeue(l,f.queue)}y.State.calls[a]=!1;for(var p=0,q=y.State.calls.length;p<q;p++)if(!1!==y.State.calls[p]){i=!0;break}!1===i&&(y.State.isTicking=!1,delete y.State.calls,y.State.calls=[])}var o,p=function(){if(c.documentMode)return c.documentMode;for(var a=7;a>4;a--){var b=c.createElement("div");if(b.innerHTML="\x3c!--[if IE "+a+"]><span></span><![endif]--\x3e",b.getElementsByTagName("span").length)return b=null,a}return d}(),q=function(){var a=0;return b.webkitRequestAnimationFrame||b.mozRequestAnimationFrame||function(b){var c,d=(new Date).getTime();return c=Math.max(0,16-(d-a)),a=d+c,setTimeout(function(){b(d+c)},c)}}(),r=function(){var a=b.performance||{};if("function"!=typeof a.now){var c=a.timing&&a.timing.navigationStart?a.timing.navigationStart:(new Date).getTime();a.now=function(){return(new Date).getTime()-c}}return a}(),s=function(){var a=Array.prototype.slice;try{return a.call(c.documentElement),a}catch(b){return function(b,c){var d=this.length;if("number"!=typeof b&&(b=0),"number"!=typeof c&&(c=d),this.slice)return a.call(this,b,c);var e,f=[],g=b>=0?b:Math.max(0,d+b),h=c<0?d+c:Math.min(c,d),i=h-g;if(i>0)if(f=new Array(i),this.charAt)for(e=0;e<i;e++)f[e]=this.charAt(g+e);else for(e=0;e<i;e++)f[e]=this[g+e];return f}}}(),t=function(){return Array.prototype.includes?function(a,b){return a.includes(b)}:Array.prototype.indexOf?function(a,b){return a.indexOf(b)>=0}:function(a,b){for(var c=0;c<a.length;c++)if(a[c]===b)return!0;return!1}},u={isNumber:function(a){return"number"==typeof a},isString:function(a){return"string"==typeof a},isArray:Array.isArray||function(a){return"[object Array]"===Object.prototype.toString.call(a)},isFunction:function(a){return"[object Function]"===Object.prototype.toString.call(a)},isNode:function(a){return a&&a.nodeType},isWrapped:function(a){return a&&a!==b&&u.isNumber(a.length)&&!u.isString(a)&&!u.isFunction(a)&&!u.isNode(a)&&(0===a.length||u.isNode(a[0]))},isSVG:function(a){return b.SVGElement&&a instanceof b.SVGElement},isEmptyObject:function(a){for(var b in a)if(a.hasOwnProperty(b))return!1;return!0}},v=!1;if(a.fn&&a.fn.jquery?(o=a,v=!0):o=b.Velocity.Utilities,p<=8&&!v)throw new Error("Velocity: IE8 and below require jQuery to be loaded before Velocity.");if(p<=7)return void(jQuery.fn.velocity=jQuery.fn.animate);var w=400,x="swing",y={State:{isMobile:/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(b.navigator.userAgent),isAndroid:/Android/i.test(b.navigator.userAgent),isGingerbread:/Android 2\.3\.[3-7]/i.test(b.navigator.userAgent),isChrome:b.chrome,isFirefox:/Firefox/i.test(b.navigator.userAgent),prefixElement:c.createElement("div"),prefixMatches:{},scrollAnchor:null,scrollPropertyLeft:null,scrollPropertyTop:null,isTicking:!1,calls:[],delayedElements:{count:0}},CSS:{},Utilities:o,Redirects:{},Easings:{},Promise:b.Promise,defaults:{queue:"",duration:w,easing:x,begin:d,complete:d,progress:d,display:d,visibility:d,loop:!1,delay:!1,mobileHA:!0,_cacheValues:!0,promiseRejectEmpty:!0},init:function(a){o.data(a,"velocity",{isSVG:u.isSVG(a),isAnimating:!1,computedStyle:null,tweensContainer:null,rootPropertyValueCache:{},transformCache:{}})},hook:null,mock:!1,version:{major:1,minor:5,patch:2},debug:!1,timestamp:!0,pauseAll:function(a){var b=(new Date).getTime();o.each(y.State.calls,function(b,c){if(c){if(a!==d&&(c[2].queue!==a||!1===c[2].queue))return!0;c[5]={resume:!1}}}),o.each(y.State.delayedElements,function(a,c){c&&h(c,b)})},resumeAll:function(a){var b=(new Date).getTime();o.each(y.State.calls,function(b,c){if(c){if(a!==d&&(c[2].queue!==a||!1===c[2].queue))return!0;c[5]&&(c[5].resume=!0)}}),o.each(y.State.delayedElements,function(a,c){c&&i(c,b)})}};b.pageYOffset!==d?(y.State.scrollAnchor=b,y.State.scrollPropertyLeft="pageXOffset",y.State.scrollPropertyTop="pageYOffset"):(y.State.scrollAnchor=c.documentElement||c.body.parentNode||c.body,y.State.scrollPropertyLeft="scrollLeft",y.State.scrollPropertyTop="scrollTop");var z=function(){function a(a){return-a.tension*a.x-a.friction*a.v}function b(b,c,d){var e={x:b.x+d.dx*c,v:b.v+d.dv*c,tension:b.tension,friction:b.friction};return{dx:e.v,dv:a(e)}}function c(c,d){var e={dx:c.v,dv:a(c)},f=b(c,.5*d,e),g=b(c,.5*d,f),h=b(c,d,g),i=1/6*(e.dx+2*(f.dx+g.dx)+h.dx),j=1/6*(e.dv+2*(f.dv+g.dv)+h.dv);return c.x=c.x+i*d,c.v=c.v+j*d,c}return function d(a,b,e){var f,g,h,i={x:-1,v:0,tension:null,friction:null},j=[0],k=0;for(a=parseFloat(a)||500,b=parseFloat(b)||20,e=e||null,i.tension=a,i.friction=b,f=null!==e,f?(k=d(a,b),g=k/e*.016):g=.016;;)if(h=c(h||i,g),j.push(1+h.x),k+=16,!(Math.abs(h.x)>1e-4&&Math.abs(h.v)>1e-4))break;return f?function(a){return j[a*(j.length-1)|0]}:k}}();y.Easings={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},spring:function(a){return 1-Math.cos(4.5*a*Math.PI)*Math.exp(6*-a)}},o.each([["ease",[.25,.1,.25,1]],["ease-in",[.42,0,1,1]],["ease-out",[0,0,.58,1]],["ease-in-out",[.42,0,.58,1]],["easeInSine",[.47,0,.745,.715]],["easeOutSine",[.39,.575,.565,1]],["easeInOutSine",[.445,.05,.55,.95]],["easeInQuad",[.55,.085,.68,.53]],["easeOutQuad",[.25,.46,.45,.94]],["easeInOutQuad",[.455,.03,.515,.955]],["easeInCubic",[.55,.055,.675,.19]],["easeOutCubic",[.215,.61,.355,1]],["easeInOutCubic",[.645,.045,.355,1]],["easeInQuart",[.895,.03,.685,.22]],["easeOutQuart",[.165,.84,.44,1]],["easeInOutQuart",[.77,0,.175,1]],["easeInQuint",[.755,.05,.855,.06]],["easeOutQuint",[.23,1,.32,1]],["easeInOutQuint",[.86,0,.07,1]],["easeInExpo",[.95,.05,.795,.035]],["easeOutExpo",[.19,1,.22,1]],["easeInOutExpo",[1,0,0,1]],["easeInCirc",[.6,.04,.98,.335]],["easeOutCirc",[.075,.82,.165,1]],["easeInOutCirc",[.785,.135,.15,.86]]],function(a,b){y.Easings[b[0]]=k.apply(null,b[1])});var A=y.CSS={RegEx:{isHex:/^#([A-f\d]{3}){1,2}$/i,valueUnwrap:/^[A-z]+\((.*)\)$/i,wrappedValueAlreadyExtracted:/[0-9.]+ [0-9.]+ [0-9.]+( [0-9.]+)?/,valueSplit:/([A-z]+\(.+\))|(([A-z0-9#-.]+?)(?=\s|$))/gi},Lists:{colors:["fill","stroke","stopColor","color","backgroundColor","borderColor","borderTopColor","borderRightColor","borderBottomColor","borderLeftColor","outlineColor"],transformsBase:["translateX","translateY","scale","scaleX","scaleY","skewX","skewY","rotateZ"],transforms3D:["transformPerspective","translateZ","scaleZ","rotateX","rotateY"],units:["%","em","ex","ch","rem","vw","vh","vmin","vmax","cm","mm","Q","in","pc","pt","px","deg","grad","rad","turn","s","ms"],colorNames:{aliceblue:"240,248,255",antiquewhite:"250,235,215",aquamarine:"127,255,212",aqua:"0,255,255",azure:"240,255,255",beige:"245,245,220",bisque:"255,228,196",black:"0,0,0",blanchedalmond:"255,235,205",blueviolet:"138,43,226",blue:"0,0,255",brown:"165,42,42",burlywood:"222,184,135",cadetblue:"95,158,160",chartreuse:"127,255,0",chocolate:"210,105,30",coral:"255,127,80",cornflowerblue:"100,149,237",cornsilk:"255,248,220",crimson:"220,20,60",cyan:"0,255,255",darkblue:"0,0,139",darkcyan:"0,139,139",darkgoldenrod:"184,134,11",darkgray:"169,169,169",darkgrey:"169,169,169",darkgreen:"0,100,0",darkkhaki:"189,183,107",darkmagenta:"139,0,139",darkolivegreen:"85,107,47",darkorange:"255,140,0",darkorchid:"153,50,204",darkred:"139,0,0",darksalmon:"233,150,122",darkseagreen:"143,188,143",darkslateblue:"72,61,139",darkslategray:"47,79,79",darkturquoise:"0,206,209",darkviolet:"148,0,211",deeppink:"255,20,147",deepskyblue:"0,191,255",dimgray:"105,105,105",dimgrey:"105,105,105",dodgerblue:"30,144,255",firebrick:"178,34,34",floralwhite:"255,250,240",forestgreen:"34,139,34",fuchsia:"255,0,255",gainsboro:"220,220,220",ghostwhite:"248,248,255",gold:"255,215,0",goldenrod:"218,165,32",gray:"128,128,128",grey:"128,128,128",greenyellow:"173,255,47",green:"0,128,0",honeydew:"240,255,240",hotpink:"255,105,180",indianred:"205,92,92",indigo:"75,0,130",ivory:"255,255,240",khaki:"240,230,140",lavenderblush:"255,240,245",lavender:"230,230,250",lawngreen:"124,252,0",lemonchiffon:"255,250,205",lightblue:"173,216,230",lightcoral:"240,128,128",lightcyan:"224,255,255",lightgoldenrodyellow:"250,250,210",lightgray:"211,211,211",lightgrey:"211,211,211",lightgreen:"144,238,144",lightpink:"255,182,193",lightsalmon:"255,160,122",lightseagreen:"32,178,170",lightskyblue:"135,206,250",lightslategray:"119,136,153",lightsteelblue:"176,196,222",lightyellow:"255,255,224",limegreen:"50,205,50",lime:"0,255,0",linen:"250,240,230",magenta:"255,0,255",maroon:"128,0,0",mediumaquamarine:"102,205,170",mediumblue:"0,0,205",mediumorchid:"186,85,211",mediumpurple:"147,112,219",mediumseagreen:"60,179,113",mediumslateblue:"123,104,238",mediumspringgreen:"0,250,154",mediumturquoise:"72,209,204",mediumvioletred:"199,21,133",midnightblue:"25,25,112",mintcream:"245,255,250",mistyrose:"255,228,225",moccasin:"255,228,181",navajowhite:"255,222,173",navy:"0,0,128",oldlace:"253,245,230",olivedrab:"107,142,35",olive:"128,128,0",orangered:"255,69,0",orange:"255,165,0",orchid:"218,112,214",palegoldenrod:"238,232,170",palegreen:"152,251,152",paleturquoise:"175,238,238",palevioletred:"219,112,147",papayawhip:"255,239,213",peachpuff:"255,218,185",peru:"205,133,63",pink:"255,192,203",plum:"221,160,221",powderblue:"176,224,230",purple:"128,0,128",red:"255,0,0",rosybrown:"188,143,143",royalblue:"65,105,225",saddlebrown:"139,69,19",salmon:"250,128,114",sandybrown:"244,164,96",seagreen:"46,139,87",seashell:"255,245,238",sienna:"160,82,45",silver:"192,192,192",skyblue:"135,206,235",slateblue:"106,90,205",slategray:"112,128,144",snow:"255,250,250",springgreen:"0,255,127",steelblue:"70,130,180",tan:"210,180,140",teal:"0,128,128",thistle:"216,191,216",tomato:"255,99,71",turquoise:"64,224,208",violet:"238,130,238",wheat:"245,222,179",whitesmoke:"245,245,245",white:"255,255,255",yellowgreen:"154,205,50",yellow:"255,255,0"}},Hooks:{templates:{textShadow:["Color X Y Blur","black 0px 0px 0px"],boxShadow:["Color X Y Blur Spread","black 0px 0px 0px 0px"],clip:["Top Right Bottom Left","0px 0px 0px 0px"],backgroundPosition:["X Y","0% 0%"],transformOrigin:["X Y Z","50% 50% 0px"],perspectiveOrigin:["X Y","50% 50%"]},registered:{},register:function(){for(var a=0;a<A.Lists.colors.length;a++){var b="color"===A.Lists.colors[a]?"0 0 0 1":"255 255 255 1";A.Hooks.templates[A.Lists.colors[a]]=["Red Green Blue Alpha",b]}var c,d,e;if(p)for(c in A.Hooks.templates)if(A.Hooks.templates.hasOwnProperty(c)){d=A.Hooks.templates[c],e=d[0].split(" ");var f=d[1].match(A.RegEx.valueSplit);"Color"===e[0]&&(e.push(e.shift()),f.push(f.shift()),A.Hooks.templates[c]=[e.join(" "),f.join(" ")])}for(c in A.Hooks.templates)if(A.Hooks.templates.hasOwnProperty(c)){d=A.Hooks.templates[c],e=d[0].split(" ");for(var g in e)if(e.hasOwnProperty(g)){var h=c+e[g],i=g;A.Hooks.registered[h]=[c,i]}}},getRoot:function(a){var b=A.Hooks.registered[a];return b?b[0]:a},getUnit:function(a,b){var c=(a.substr(b||0,5).match(/^[a-z%]+/)||[])[0]||"";return c&&t(A.Lists.units,c)?c:""},fixColors:function(a){return a.replace(/(rgba?\(\s*)?(\b[a-z]+\b)/g,function(a,b,c){return A.Lists.colorNames.hasOwnProperty(c)?(b||"rgba(")+A.Lists.colorNames[c]+(b?"":",1)"):b+c})},cleanRootPropertyValue:function(a,b){return A.RegEx.valueUnwrap.test(b)&&(b=b.match(A.RegEx.valueUnwrap)[1]),A.Values.isCSSNullValue(b)&&(b=A.Hooks.templates[a][1]),b},extractValue:function(a,b){var c=A.Hooks.registered[a];if(c){var d=c[0],e=c[1];return b=A.Hooks.cleanRootPropertyValue(d,b),b.toString().match(A.RegEx.valueSplit)[e]}return b},injectValue:function(a,b,c){var d=A.Hooks.registered[a];if(d){var e,f=d[0],g=d[1];return c=A.Hooks.cleanRootPropertyValue(f,c),e=c.toString().match(A.RegEx.valueSplit),e[g]=b,e.join(" ")}return c}},Normalizations:{registered:{clip:function(a,b,c){switch(a){case"name":return"clip";case"extract":var d;return A.RegEx.wrappedValueAlreadyExtracted.test(c)?d=c:(d=c.toString().match(A.RegEx.valueUnwrap),d=d?d[1].replace(/,(\s+)?/g," "):c),d;case"inject":return"rect("+c+")"}},blur:function(a,b,c){switch(a){case"name":return y.State.isFirefox?"filter":"-webkit-filter";case"extract":var d=parseFloat(c);if(!d&&0!==d){var e=c.toString().match(/blur\(([0-9]+[A-z]+)\)/i);d=e?e[1]:0}return d;case"inject":return parseFloat(c)?"blur("+c+")":"none"}},opacity:function(a,b,c){if(p<=8)switch(a){case"name":return"filter";case"extract":var d=c.toString().match(/alpha\(opacity=(.*)\)/i);return c=d?d[1]/100:1;case"inject":return b.style.zoom=1,parseFloat(c)>=1?"":"alpha(opacity="+parseInt(100*parseFloat(c),10)+")"}else switch(a){case"name":return"opacity";case"extract":case"inject":return c}}},register:function(){function a(a,b,c){if("border-box"===A.getPropertyValue(b,"boxSizing").toString().toLowerCase()===(c||!1)){var d,e,f=0,g="width"===a?["Left","Right"]:["Top","Bottom"],h=["padding"+g[0],"padding"+g[1],"border"+g[0]+"Width","border"+g[1]+"Width"];for(d=0;d<h.length;d++)e=parseFloat(A.getPropertyValue(b,h[d])),isNaN(e)||(f+=e);return c?-f:f}return 0}function b(b,c){return function(d,e,f){switch(d){case"name":return b;case"extract":return parseFloat(f)+a(b,e,c);case"inject":return parseFloat(f)-a(b,e,c)+"px"}}}p&&!(p>9)||y.State.isGingerbread||(A.Lists.transformsBase=A.Lists.transformsBase.concat(A.Lists.transforms3D));for(var c=0;c<A.Lists.transformsBase.length;c++)!function(){var a=A.Lists.transformsBase[c];A.Normalizations.registered[a]=function(b,c,e){switch(b){case"name":return"transform";case"extract":return g(c)===d||g(c).transformCache[a]===d?/^scale/i.test(a)?1:0:g(c).transformCache[a].replace(/[()]/g,"");case"inject":var f=!1;switch(a.substr(0,a.length-1)){case"translate":f=!/(%|px|em|rem|vw|vh|\d)$/i.test(e);break;case"scal":case"scale":y.State.isAndroid&&g(c).transformCache[a]===d&&e<1&&(e=1),f=!/(\d)$/i.test(e);break;case"skew":case"rotate":f=!/(deg|\d)$/i.test(e)}return f||(g(c).transformCache[a]="("+e+")"),g(c).transformCache[a]}}}();for(var e=0;e<A.Lists.colors.length;e++)!function(){var a=A.Lists.colors[e];A.Normalizations.registered[a]=function(b,c,e){switch(b){case"name":return a;case"extract":var f;if(A.RegEx.wrappedValueAlreadyExtracted.test(e))f=e;else{var g,h={black:"rgb(0, 0, 0)",blue:"rgb(0, 0, 255)",gray:"rgb(128, 128, 128)",green:"rgb(0, 128, 0)",red:"rgb(255, 0, 0)",white:"rgb(255, 255, 255)"};/^[A-z]+$/i.test(e)?g=h[e]!==d?h[e]:h.black:A.RegEx.isHex.test(e)?g="rgb("+A.Values.hexToRgb(e).join(" ")+")":/^rgba?\(/i.test(e)||(g=h.black),f=(g||e).toString().match(A.RegEx.valueUnwrap)[1].replace(/,(\s+)?/g," ")}return(!p||p>8)&&3===f.split(" ").length&&(f+=" 1"),f;case"inject":return/^rgb/.test(e)?e:(p<=8?4===e.split(" ").length&&(e=e.split(/\s+/).slice(0,3).join(" ")):3===e.split(" ").length&&(e+=" 1"),(p<=8?"rgb":"rgba")+"("+e.replace(/\s+/g,",").replace(/\.(\d)+(?=,)/g,"")+")")}}}();A.Normalizations.registered.innerWidth=b("width",!0),A.Normalizations.registered.innerHeight=b("height",!0),A.Normalizations.registered.outerWidth=b("width"),A.Normalizations.registered.outerHeight=b("height")}},Names:{camelCase:function(a){return a.replace(/-(\w)/g,function(a,b){return b.toUpperCase()})},SVGAttribute:function(a){var b="width|height|x|y|cx|cy|r|rx|ry|x1|x2|y1|y2";return(p||y.State.isAndroid&&!y.State.isChrome)&&(b+="|transform"),new RegExp("^("+b+")$","i").test(a)},prefixCheck:function(a){if(y.State.prefixMatches[a])return[y.State.prefixMatches[a],!0];for(var b=["","Webkit","Moz","ms","O"],c=0,d=b.length;c<d;c++){var e;if(e=0===c?a:b[c]+a.replace(/^\w/,function(a){return a.toUpperCase()}),u.isString(y.State.prefixElement.style[e]))return y.State.prefixMatches[a]=e,[e,!0]}return[a,!1]}},Values:{hexToRgb:function(a){var b,c=/^#?([a-f\d])([a-f\d])([a-f\d])$/i,d=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i;return a=a.replace(c,function(a,b,c,d){return b+b+c+c+d+d}),b=d.exec(a),b?[parseInt(b[1],16),parseInt(b[2],16),parseInt(b[3],16)]:[0,0,0]},isCSSNullValue:function(a){return!a||/^(none|auto|transparent|(rgba\(0, ?0, ?0, ?0\)))$/i.test(a)},getUnitType:function(a){return/^(rotate|skew)/i.test(a)?"deg":/(^(scale|scaleX|scaleY|scaleZ|alpha|flexGrow|flexHeight|zIndex|fontWeight)$)|((opacity|red|green|blue|alpha)$)/i.test(a)?"":"px"},getDisplayType:function(a){var b=a&&a.tagName.toString().toLowerCase();return/^(b|big|i|small|tt|abbr|acronym|cite|code|dfn|em|kbd|strong|samp|var|a|bdo|br|img|map|object|q|script|span|sub|sup|button|input|label|select|textarea)$/i.test(b)?"inline":/^(li)$/i.test(b)?"list-item":/^(tr)$/i.test(b)?"table-row":/^(table)$/i.test(b)?"table":/^(tbody)$/i.test(b)?"table-row-group":"block"},addClass:function(a,b){if(a)if(a.classList)a.classList.add(b);else if(u.isString(a.className))a.className+=(a.className.length?" ":"")+b;else{var c=a.getAttribute(p<=7?"className":"class")||"";a.setAttribute("class",c+(c?" ":"")+b)}},removeClass:function(a,b){if(a)if(a.classList)a.classList.remove(b);else if(u.isString(a.className))a.className=a.className.toString().replace(new RegExp("(^|\\s)"+b.split(" ").join("|")+"(\\s|$)","gi")," ");else{var c=a.getAttribute(p<=7?"className":"class")||"";a.setAttribute("class",c.replace(new RegExp("(^|s)"+b.split(" ").join("|")+"(s|$)","gi")," "))}}},getPropertyValue:function(a,c,e,f){function h(a,c){var e=0;if(p<=8)e=o.css(a,c);else{var i=!1;/^(width|height)$/.test(c)&&0===A.getPropertyValue(a,"display")&&(i=!0,A.setPropertyValue(a,"display",A.Values.getDisplayType(a)));var j=function(){i&&A.setPropertyValue(a,"display","none")};if(!f){if("height"===c&&"border-box"!==A.getPropertyValue(a,"boxSizing").toString().toLowerCase()){var k=a.offsetHeight-(parseFloat(A.getPropertyValue(a,"borderTopWidth"))||0)-(parseFloat(A.getPropertyValue(a,"borderBottomWidth"))||0)-(parseFloat(A.getPropertyValue(a,"paddingTop"))||0)-(parseFloat(A.getPropertyValue(a,"paddingBottom"))||0);return j(),k}if("width"===c&&"border-box"!==A.getPropertyValue(a,"boxSizing").toString().toLowerCase()){var l=a.offsetWidth-(parseFloat(A.getPropertyValue(a,"borderLeftWidth"))||0)-(parseFloat(A.getPropertyValue(a,"borderRightWidth"))||0)-(parseFloat(A.getPropertyValue(a,"paddingLeft"))||0)-(parseFloat(A.getPropertyValue(a,"paddingRight"))||0);return j(),l}}var m;m=g(a)===d?b.getComputedStyle(a,null):g(a).computedStyle?g(a).computedStyle:g(a).computedStyle=b.getComputedStyle(a,null),"borderColor"===c&&(c="borderTopColor"),e=9===p&&"filter"===c?m.getPropertyValue(c):m[c],""!==e&&null!==e||(e=a.style[c]),j()}if("auto"===e&&/^(top|right|bottom|left)$/i.test(c)){var n=h(a,"position");("fixed"===n||"absolute"===n&&/top|left/i.test(c))&&(e=o(a).position()[c]+"px")}return e}var i;if(A.Hooks.registered[c]){var j=c,k=A.Hooks.getRoot(j);e===d&&(e=A.getPropertyValue(a,A.Names.prefixCheck(k)[0])),A.Normalizations.registered[k]&&(e=A.Normalizations.registered[k]("extract",a,e)),i=A.Hooks.extractValue(j,e)}else if(A.Normalizations.registered[c]){var l,m;l=A.Normalizations.registered[c]("name",a),"transform"!==l&&(m=h(a,A.Names.prefixCheck(l)[0]),A.Values.isCSSNullValue(m)&&A.Hooks.templates[c]&&(m=A.Hooks.templates[c][1])),i=A.Normalizations.registered[c]("extract",a,m)}if(!/^[\d-]/.test(i)){var n=g(a);if(n&&n.isSVG&&A.Names.SVGAttribute(c))if(/^(height|width)$/i.test(c))try{i=a.getBBox()[c]}catch(q){i=0}else i=a.getAttribute(c);else i=h(a,A.Names.prefixCheck(c)[0])}return A.Values.isCSSNullValue(i)&&(i=0),y.debug>=2&&console.log("Get "+c+": "+i),i},setPropertyValue:function(a,c,d,e,f){var h=c;if("scroll"===c)f.container?f.container["scroll"+f.direction]=d:"Left"===f.direction?b.scrollTo(d,f.alternateValue):b.scrollTo(f.alternateValue,d);else if(A.Normalizations.registered[c]&&"transform"===A.Normalizations.registered[c]("name",a))A.Normalizations.registered[c]("inject",a,d),h="transform",d=g(a).transformCache[c];else{if(A.Hooks.registered[c]){var i=c,j=A.Hooks.getRoot(c);e=e||A.getPropertyValue(a,j),d=A.Hooks.injectValue(i,d,e),c=j}if(A.Normalizations.registered[c]&&(d=A.Normalizations.registered[c]("inject",a,d),c=A.Normalizations.registered[c]("name",a)),h=A.Names.prefixCheck(c)[0],p<=8)try{a.style[h]=d}catch(l){y.debug&&console.log("Browser does not support ["+d+"] for ["+h+"]")}else{var k=g(a);k&&k.isSVG&&A.Names.SVGAttribute(c)?a.setAttribute(c,d):a.style[h]=d}y.debug>=2&&console.log("Set "+c+" ("+h+"): "+d)}return[h,d]},flushTransformCache:function(a){var b="",c=g(a);if((p||y.State.isAndroid&&!y.State.isChrome)&&c&&c.isSVG){var d=function(b){return parseFloat(A.getPropertyValue(a,b))},e={translate:[d("translateX"),d("translateY")],skewX:[d("skewX")],skewY:[d("skewY")],scale:1!==d("scale")?[d("scale"),d("scale")]:[d("scaleX"),d("scaleY")],rotate:[d("rotateZ"),0,0]};o.each(g(a).transformCache,function(a){/^translate/i.test(a)?a="translate":/^scale/i.test(a)?a="scale":/^rotate/i.test(a)&&(a="rotate"),e[a]&&(b+=a+"("+e[a].join(" ")+") ",delete e[a])})}else{var f,h;o.each(g(a).transformCache,function(c){if(f=g(a).transformCache[c],"transformPerspective"===c)return h=f,!0;9===p&&"rotateZ"===c&&(c="rotate"),b+=c+f+" "}),h&&(b="perspective"+h+" "+b)}A.setPropertyValue(a,"transform",b)}};A.Hooks.register(),A.Normalizations.register(),y.hook=function(a,b,c){var e;return a=f(a),o.each(a,function(a,f){if(g(f)===d&&y.init(f),c===d)e===d&&(e=A.getPropertyValue(f,b));else{var h=A.setPropertyValue(f,b,c);"transform"===h[0]&&y.CSS.flushTransformCache(f),e=h}}),e};var B=function(){function a(){return k?z.promise||null:p}function e(a,e){function f(f){var k,n;if(i.begin&&0===D)try{i.begin.call(r,r)}catch(V){setTimeout(function(){throw V},1)}if("scroll"===G){var p,q,w,x=/^x$/i.test(i.axis)?"Left":"Top",B=parseFloat(i.offset)||0;i.container?u.isWrapped(i.container)||u.isNode(i.container)?(i.container=i.container[0]||i.container,p=i.container["scroll"+x],w=p+o(a).position()[x.toLowerCase()]+B):i.container=null:(p=y.State.scrollAnchor[y.State["scrollProperty"+x]],q=y.State.scrollAnchor[y.State["scrollProperty"+("Left"===x?"Top":"Left")]],w=o(a).offset()[x.toLowerCase()]+B),j={scroll:{rootPropertyValue:!1,startValue:p,currentValue:p,endValue:w,unitType:"",easing:i.easing,scrollData:{container:i.container,direction:x,alternateValue:q}},element:a},y.debug&&console.log("tweensContainer (scroll): ",j.scroll,a)}else if("reverse"===G){if(!(k=g(a)))return;if(!k.tweensContainer)return void o.dequeue(a,i.queue);"none"===k.opts.display&&(k.opts.display="auto"),"hidden"===k.opts.visibility&&(k.opts.visibility="visible"),k.opts.loop=!1,k.opts.begin=null,k.opts.complete=null,v.easing||delete i.easing,v.duration||delete i.duration,i=o.extend({},k.opts,i),n=o.extend(!0,{},k?k.tweensContainer:null);for(var E in n)if(n.hasOwnProperty(E)&&"element"!==E){var F=n[E].startValue;n[E].startValue=n[E].currentValue=n[E].endValue,n[E].endValue=F,u.isEmptyObject(v)||(n[E].easing=i.easing),y.debug&&console.log("reverse tweensContainer ("+E+"): "+JSON.stringify(n[E]),a)}j=n}else if("start"===G){k=g(a),k&&k.tweensContainer&&!0===k.isAnimating&&(n=k.tweensContainer);var H=function(e,f){var g,l=A.Hooks.getRoot(e),m=!1,p=f[0],q=f[1],r=f[2]
;if(!(k&&k.isSVG||"tween"===l||!1!==A.Names.prefixCheck(l)[1]||A.Normalizations.registered[l]!==d))return void(y.debug&&console.log("Skipping ["+l+"] due to a lack of browser support."));(i.display!==d&&null!==i.display&&"none"!==i.display||i.visibility!==d&&"hidden"!==i.visibility)&&/opacity|filter/.test(e)&&!r&&0!==p&&(r=0),i._cacheValues&&n&&n[e]?(r===d&&(r=n[e].endValue+n[e].unitType),m=k.rootPropertyValueCache[l]):A.Hooks.registered[e]?r===d?(m=A.getPropertyValue(a,l),r=A.getPropertyValue(a,e,m)):m=A.Hooks.templates[l][1]:r===d&&(r=A.getPropertyValue(a,e));var s,t,v,w=!1,x=function(a,b){var c,d;return d=(b||"0").toString().toLowerCase().replace(/[%A-z]+$/,function(a){return c=a,""}),c||(c=A.Values.getUnitType(a)),[d,c]};if(r!==p&&u.isString(r)&&u.isString(p)){g="";var z=0,B=0,C=[],D=[],E=0,F=0,G=0;for(r=A.Hooks.fixColors(r),p=A.Hooks.fixColors(p);z<r.length&&B<p.length;){var H=r[z],I=p[B];if(/[\d\.-]/.test(H)&&/[\d\.-]/.test(I)){for(var J=H,K=I,L=".",N=".";++z<r.length;){if((H=r[z])===L)L="..";else if(!/\d/.test(H))break;J+=H}for(;++B<p.length;){if((I=p[B])===N)N="..";else if(!/\d/.test(I))break;K+=I}var O=A.Hooks.getUnit(r,z),P=A.Hooks.getUnit(p,B);if(z+=O.length,B+=P.length,O===P)J===K?g+=J+O:(g+="{"+C.length+(F?"!":"")+"}"+O,C.push(parseFloat(J)),D.push(parseFloat(K)));else{var Q=parseFloat(J),R=parseFloat(K);g+=(E<5?"calc":"")+"("+(Q?"{"+C.length+(F?"!":"")+"}":"0")+O+" + "+(R?"{"+(C.length+(Q?1:0))+(F?"!":"")+"}":"0")+P+")",Q&&(C.push(Q),D.push(0)),R&&(C.push(0),D.push(R))}}else{if(H!==I){E=0;break}g+=H,z++,B++,0===E&&"c"===H||1===E&&"a"===H||2===E&&"l"===H||3===E&&"c"===H||E>=4&&"("===H?E++:(E&&E<5||E>=4&&")"===H&&--E<5)&&(E=0),0===F&&"r"===H||1===F&&"g"===H||2===F&&"b"===H||3===F&&"a"===H||F>=3&&"("===H?(3===F&&"a"===H&&(G=1),F++):G&&","===H?++G>3&&(F=G=0):(G&&F<(G?5:4)||F>=(G?4:3)&&")"===H&&--F<(G?5:4))&&(F=G=0)}}z===r.length&&B===p.length||(y.debug&&console.error('Trying to pattern match mis-matched strings ["'+p+'", "'+r+'"]'),g=d),g&&(C.length?(y.debug&&console.log('Pattern found "'+g+'" -> ',C,D,"["+r+","+p+"]"),r=C,p=D,t=v=""):g=d)}g||(s=x(e,r),r=s[0],v=s[1],s=x(e,p),p=s[0].replace(/^([+-\/*])=/,function(a,b){return w=b,""}),t=s[1],r=parseFloat(r)||0,p=parseFloat(p)||0,"%"===t&&(/^(fontSize|lineHeight)$/.test(e)?(p/=100,t="em"):/^scale/.test(e)?(p/=100,t=""):/(Red|Green|Blue)$/i.test(e)&&(p=p/100*255,t="")));if(/[\/*]/.test(w))t=v;else if(v!==t&&0!==r)if(0===p)t=v;else{h=h||function(){var d={myParent:a.parentNode||c.body,position:A.getPropertyValue(a,"position"),fontSize:A.getPropertyValue(a,"fontSize")},e=d.position===M.lastPosition&&d.myParent===M.lastParent,f=d.fontSize===M.lastFontSize;M.lastParent=d.myParent,M.lastPosition=d.position,M.lastFontSize=d.fontSize;var g={};if(f&&e)g.emToPx=M.lastEmToPx,g.percentToPxWidth=M.lastPercentToPxWidth,g.percentToPxHeight=M.lastPercentToPxHeight;else{var h=k&&k.isSVG?c.createElementNS("http://www.w3.org/2000/svg","rect"):c.createElement("div");y.init(h),d.myParent.appendChild(h),o.each(["overflow","overflowX","overflowY"],function(a,b){y.CSS.setPropertyValue(h,b,"hidden")}),y.CSS.setPropertyValue(h,"position",d.position),y.CSS.setPropertyValue(h,"fontSize",d.fontSize),y.CSS.setPropertyValue(h,"boxSizing","content-box"),o.each(["minWidth","maxWidth","width","minHeight","maxHeight","height"],function(a,b){y.CSS.setPropertyValue(h,b,"100%")}),y.CSS.setPropertyValue(h,"paddingLeft","100em"),g.percentToPxWidth=M.lastPercentToPxWidth=(parseFloat(A.getPropertyValue(h,"width",null,!0))||1)/100,g.percentToPxHeight=M.lastPercentToPxHeight=(parseFloat(A.getPropertyValue(h,"height",null,!0))||1)/100,g.emToPx=M.lastEmToPx=(parseFloat(A.getPropertyValue(h,"paddingLeft"))||1)/100,d.myParent.removeChild(h)}return null===M.remToPx&&(M.remToPx=parseFloat(A.getPropertyValue(c.body,"fontSize"))||16),null===M.vwToPx&&(M.vwToPx=parseFloat(b.innerWidth)/100,M.vhToPx=parseFloat(b.innerHeight)/100),g.remToPx=M.remToPx,g.vwToPx=M.vwToPx,g.vhToPx=M.vhToPx,y.debug>=1&&console.log("Unit ratios: "+JSON.stringify(g),a),g}();var S=/margin|padding|left|right|width|text|word|letter/i.test(e)||/X$/.test(e)||"x"===e?"x":"y";switch(v){case"%":r*="x"===S?h.percentToPxWidth:h.percentToPxHeight;break;case"px":break;default:r*=h[v+"ToPx"]}switch(t){case"%":r*=1/("x"===S?h.percentToPxWidth:h.percentToPxHeight);break;case"px":break;default:r*=1/h[t+"ToPx"]}}switch(w){case"+":p=r+p;break;case"-":p=r-p;break;case"*":p*=r;break;case"/":p=r/p}j[e]={rootPropertyValue:m,startValue:r,currentValue:r,endValue:p,unitType:t,easing:q},g&&(j[e].pattern=g),y.debug&&console.log("tweensContainer ("+e+"): "+JSON.stringify(j[e]),a)};for(var I in s)if(s.hasOwnProperty(I)){var J=A.Names.camelCase(I),K=function(b,c){var d,f,g;return u.isFunction(b)&&(b=b.call(a,e,C)),u.isArray(b)?(d=b[0],!u.isArray(b[1])&&/^[\d-]/.test(b[1])||u.isFunction(b[1])||A.RegEx.isHex.test(b[1])?g=b[1]:u.isString(b[1])&&!A.RegEx.isHex.test(b[1])&&y.Easings[b[1]]||u.isArray(b[1])?(f=c?b[1]:l(b[1],i.duration),g=b[2]):g=b[1]||b[2]):d=b,c||(f=f||i.easing),u.isFunction(d)&&(d=d.call(a,e,C)),u.isFunction(g)&&(g=g.call(a,e,C)),[d||0,f,g]}(s[I]);if(t(A.Lists.colors,J)){var L=K[0],O=K[1],P=K[2];if(A.RegEx.isHex.test(L)){for(var Q=["Red","Green","Blue"],R=A.Values.hexToRgb(L),S=P?A.Values.hexToRgb(P):d,T=0;T<Q.length;T++){var U=[R[T]];O&&U.push(O),S!==d&&U.push(S[T]),H(J+Q[T],U)}continue}}H(J,K)}j.element=a}j.element&&(A.Values.addClass(a,"velocity-animating"),N.push(j),k=g(a),k&&(""===i.queue&&(k.tweensContainer=j,k.opts=i),k.isAnimating=!0),D===C-1?(y.State.calls.push([N,r,i,null,z.resolver,null,0]),!1===y.State.isTicking&&(y.State.isTicking=!0,m())):D++)}var h,i=o.extend({},y.defaults,v),j={};switch(g(a)===d&&y.init(a),parseFloat(i.delay)&&!1!==i.queue&&o.queue(a,i.queue,function(b,c){if(!0===c)return!0;y.velocityQueueEntryFlag=!0;var d=y.State.delayedElements.count++;y.State.delayedElements[d]=a;var e=function(a){return function(){y.State.delayedElements[a]=!1,b()}}(d);g(a).delayBegin=(new Date).getTime(),g(a).delay=parseFloat(i.delay),g(a).delayTimer={setTimeout:setTimeout(b,parseFloat(i.delay)),next:e}}),i.duration.toString().toLowerCase()){case"fast":i.duration=200;break;case"normal":i.duration=w;break;case"slow":i.duration=600;break;default:i.duration=parseFloat(i.duration)||1}if(!1!==y.mock&&(!0===y.mock?i.duration=i.delay=1:(i.duration*=parseFloat(y.mock)||1,i.delay*=parseFloat(y.mock)||1)),i.easing=l(i.easing,i.duration),i.begin&&!u.isFunction(i.begin)&&(i.begin=null),i.progress&&!u.isFunction(i.progress)&&(i.progress=null),i.complete&&!u.isFunction(i.complete)&&(i.complete=null),i.display!==d&&null!==i.display&&(i.display=i.display.toString().toLowerCase(),"auto"===i.display&&(i.display=y.CSS.Values.getDisplayType(a))),i.visibility!==d&&null!==i.visibility&&(i.visibility=i.visibility.toString().toLowerCase()),i.mobileHA=i.mobileHA&&y.State.isMobile&&!y.State.isGingerbread,!1===i.queue)if(i.delay){var k=y.State.delayedElements.count++;y.State.delayedElements[k]=a;var n=function(a){return function(){y.State.delayedElements[a]=!1,f()}}(k);g(a).delayBegin=(new Date).getTime(),g(a).delay=parseFloat(i.delay),g(a).delayTimer={setTimeout:setTimeout(f,parseFloat(i.delay)),next:n}}else f();else o.queue(a,i.queue,function(a,b){if(!0===b)return z.promise&&z.resolver(r),!0;y.velocityQueueEntryFlag=!0,f(a)});""!==i.queue&&"fx"!==i.queue||"inprogress"===o.queue(a)[0]||o.dequeue(a)}var j,k,p,q,r,s,v,x=arguments[0]&&(arguments[0].p||o.isPlainObject(arguments[0].properties)&&!arguments[0].properties.names||u.isString(arguments[0].properties));u.isWrapped(this)?(k=!1,q=0,r=this,p=this):(k=!0,q=1,r=x?arguments[0].elements||arguments[0].e:arguments[0]);var z={promise:null,resolver:null,rejecter:null};if(k&&y.Promise&&(z.promise=new y.Promise(function(a,b){z.resolver=a,z.rejecter=b})),x?(s=arguments[0].properties||arguments[0].p,v=arguments[0].options||arguments[0].o):(s=arguments[q],v=arguments[q+1]),!(r=f(r)))return void(z.promise&&(s&&v&&!1===v.promiseRejectEmpty?z.resolver():z.rejecter()));var C=r.length,D=0;if(!/^(stop|finish|finishAll|pause|resume)$/i.test(s)&&!o.isPlainObject(v)){var E=q+1;v={};for(var F=E;F<arguments.length;F++)u.isArray(arguments[F])||!/^(fast|normal|slow)$/i.test(arguments[F])&&!/^\d/.test(arguments[F])?u.isString(arguments[F])||u.isArray(arguments[F])?v.easing=arguments[F]:u.isFunction(arguments[F])&&(v.complete=arguments[F]):v.duration=arguments[F]}var G;switch(s){case"scroll":G="scroll";break;case"reverse":G="reverse";break;case"pause":var H=(new Date).getTime();return o.each(r,function(a,b){h(b,H)}),o.each(y.State.calls,function(a,b){var c=!1;b&&o.each(b[1],function(a,e){var f=v===d?"":v;return!0!==f&&b[2].queue!==f&&(v!==d||!1!==b[2].queue)||(o.each(r,function(a,d){if(d===e)return b[5]={resume:!1},c=!0,!1}),!c&&void 0)})}),a();case"resume":return o.each(r,function(a,b){i(b,H)}),o.each(y.State.calls,function(a,b){var c=!1;b&&o.each(b[1],function(a,e){var f=v===d?"":v;return!0!==f&&b[2].queue!==f&&(v!==d||!1!==b[2].queue)||(!b[5]||(o.each(r,function(a,d){if(d===e)return b[5].resume=!0,c=!0,!1}),!c&&void 0))})}),a();case"finish":case"finishAll":case"stop":o.each(r,function(a,b){g(b)&&g(b).delayTimer&&(clearTimeout(g(b).delayTimer.setTimeout),g(b).delayTimer.next&&g(b).delayTimer.next(),delete g(b).delayTimer),"finishAll"!==s||!0!==v&&!u.isString(v)||(o.each(o.queue(b,u.isString(v)?v:""),function(a,b){u.isFunction(b)&&b()}),o.queue(b,u.isString(v)?v:"",[]))});var I=[];return o.each(y.State.calls,function(a,b){b&&o.each(b[1],function(c,e){var f=v===d?"":v;if(!0!==f&&b[2].queue!==f&&(v!==d||!1!==b[2].queue))return!0;o.each(r,function(c,d){if(d===e)if((!0===v||u.isString(v))&&(o.each(o.queue(d,u.isString(v)?v:""),function(a,b){u.isFunction(b)&&b(null,!0)}),o.queue(d,u.isString(v)?v:"",[])),"stop"===s){var h=g(d);h&&h.tweensContainer&&(!0===f||""===f)&&o.each(h.tweensContainer,function(a,b){b.endValue=b.currentValue}),I.push(a)}else"finish"!==s&&"finishAll"!==s||(b[2].duration=1)})})}),"stop"===s&&(o.each(I,function(a,b){n(b,!0)}),z.promise&&z.resolver(r)),a();default:if(!o.isPlainObject(s)||u.isEmptyObject(s)){if(u.isString(s)&&y.Redirects[s]){j=o.extend({},v);var J=j.duration,K=j.delay||0;return!0===j.backwards&&(r=o.extend(!0,[],r).reverse()),o.each(r,function(a,b){parseFloat(j.stagger)?j.delay=K+parseFloat(j.stagger)*a:u.isFunction(j.stagger)&&(j.delay=K+j.stagger.call(b,a,C)),j.drag&&(j.duration=parseFloat(J)||(/^(callout|transition)/.test(s)?1e3:w),j.duration=Math.max(j.duration*(j.backwards?1-a/C:(a+1)/C),.75*j.duration,200)),y.Redirects[s].call(b,b,j||{},a,C,r,z.promise?z:d)}),a()}var L="Velocity: First argument ("+s+") was not a property map, a known action, or a registered redirect. Aborting.";return z.promise?z.rejecter(new Error(L)):b.console&&console.log(L),a()}G="start"}var M={lastParent:null,lastPosition:null,lastFontSize:null,lastPercentToPxWidth:null,lastPercentToPxHeight:null,lastEmToPx:null,remToPx:null,vwToPx:null,vhToPx:null},N=[];o.each(r,function(a,b){u.isNode(b)&&e(b,a)}),j=o.extend({},y.defaults,v),j.loop=parseInt(j.loop,10);var O=2*j.loop-1;if(j.loop)for(var P=0;P<O;P++){var Q={delay:j.delay,progress:j.progress};P===O-1&&(Q.display=j.display,Q.visibility=j.visibility,Q.complete=j.complete),B(r,"reverse",Q)}return a()};y=o.extend(B,y),y.animate=B;var C=b.requestAnimationFrame||q;if(!y.State.isMobile&&c.hidden!==d){var D=function(){c.hidden?(C=function(a){return setTimeout(function(){a(!0)},16)},m()):C=b.requestAnimationFrame||q};D(),c.addEventListener("visibilitychange",D)}return a.Velocity=y,a!==b&&(a.fn.velocity=B,a.fn.velocity.defaults=y.defaults),o.each(["Down","Up"],function(a,b){y.Redirects["slide"+b]=function(a,c,e,f,g,h){var i=o.extend({},c),j=i.begin,k=i.complete,l={},m={height:"",marginTop:"",marginBottom:"",paddingTop:"",paddingBottom:""};i.display===d&&(i.display="Down"===b?"inline"===y.CSS.Values.getDisplayType(a)?"inline-block":"block":"none"),i.begin=function(){0===e&&j&&j.call(g,g);for(var c in m)if(m.hasOwnProperty(c)){l[c]=a.style[c];var d=A.getPropertyValue(a,c);m[c]="Down"===b?[d,0]:[0,d]}l.overflow=a.style.overflow,a.style.overflow="hidden"},i.complete=function(){for(var b in l)l.hasOwnProperty(b)&&(a.style[b]=l[b]);e===f-1&&(k&&k.call(g,g),h&&h.resolver(g))},y(a,m,i)}}),o.each(["In","Out"],function(a,b){y.Redirects["fade"+b]=function(a,c,e,f,g,h){var i=o.extend({},c),j=i.complete,k={opacity:"In"===b?1:0};0!==e&&(i.begin=null),i.complete=e!==f-1?null:function(){j&&j.call(g,g),h&&h.resolver(g)},i.display===d&&(i.display="In"===b?"auto":"none"),y(this,k,i)}}),y}(window.jQuery||window.Zepto||window,window,window?window.document:undefined)});
!function(a){"use strict";"function"==typeof require&&"object"==typeof exports?module.exports=a():"function"==typeof define&&define.amd?define(["velocity"],a):a()}(function(){"use strict";return function(a,b,c,d){var e=a.Velocity;if(!e||!e.Utilities)return void(b.console&&console.log("Velocity UI Pack: Velocity must be loaded first. Aborting."));var f=e.Utilities,g=e.version,h={major:1,minor:1,patch:0};if(function(a,b){var c=[];return!(!a||!b)&&(f.each([a,b],function(a,b){var d=[];f.each(b,function(a,b){for(;b.toString().length<5;)b="0"+b;d.push(b)}),c.push(d.join(""))}),parseFloat(c[0])>parseFloat(c[1]))}(h,g)){var i="Velocity UI Pack: You need to update Velocity (velocity.js) to a newer version. Visit http://github.com/julianshapiro/velocity.";throw alert(i),new Error(i)}e.RegisterEffect=e.RegisterUI=function(a,b){function c(a,b,c,d){var g,h=0;f.each(a.nodeType?[a]:a,function(a,b){d&&(c+=a*d),g=b.parentNode;var i=["height","paddingTop","paddingBottom","marginTop","marginBottom"];"border-box"===e.CSS.getPropertyValue(b,"boxSizing").toString().toLowerCase()&&(i=["height"]),f.each(i,function(a,c){h+=parseFloat(e.CSS.getPropertyValue(b,c))})}),e.animate(g,{height:("In"===b?"+":"-")+"="+h},{queue:!1,easing:"ease-in-out",duration:c*("In"===b?.6:1)})}return e.Redirects[a]=function(d,g,h,i,j,k,l){var m=h===i-1,n=0;l=l||b.loop,"function"==typeof b.defaultDuration?b.defaultDuration=b.defaultDuration.call(j,j):b.defaultDuration=parseFloat(b.defaultDuration);for(var o=0;o<b.calls.length;o++)"number"==typeof(t=b.calls[o][1])&&(n+=t);var p=n>=1?0:b.calls.length?(1-n)/b.calls.length:1;for(o=0;o<b.calls.length;o++){var q=b.calls[o],r=q[0],s=1e3,t=q[1],u=q[2]||{},v={};if(void 0!==g.duration?s=g.duration:void 0!==b.defaultDuration&&(s=b.defaultDuration),v.duration=s*("number"==typeof t?t:p),v.queue=g.queue||"",v.easing=u.easing||"ease",v.delay=parseFloat(u.delay)||0,v.loop=!b.loop&&u.loop,v._cacheValues=u._cacheValues||!0,0===o){if(v.delay+=parseFloat(g.delay)||0,0===h&&(v.begin=function(){g.begin&&g.begin.call(j,j);var b=a.match(/(In|Out)$/);b&&"In"===b[0]&&void 0!==r.opacity&&f.each(j.nodeType?[j]:j,function(a,b){e.CSS.setPropertyValue(b,"opacity",0)}),g.animateParentHeight&&b&&c(j,b[0],s+v.delay,g.stagger)}),null!==g.display)if(void 0!==g.display&&"none"!==g.display)v.display=g.display;else if(/In$/.test(a)){var w=e.CSS.Values.getDisplayType(d);v.display="inline"===w?"inline-block":w}g.visibility&&"hidden"!==g.visibility&&(v.visibility=g.visibility)}if(o===b.calls.length-1){var x=function(){void 0!==g.display&&"none"!==g.display||!/Out$/.test(a)||f.each(j.nodeType?[j]:j,function(a,b){e.CSS.setPropertyValue(b,"display","none")}),g.complete&&g.complete.call(j,j),k&&k.resolver(j||d)};v.complete=function(){if(l&&e.Redirects[a](d,g,h,i,j,k,!0===l||Math.max(0,l-1)),b.reset){for(var c in b.reset)if(b.reset.hasOwnProperty(c)){var f=b.reset[c];void 0!==e.CSS.Hooks.registered[c]||"string"!=typeof f&&"number"!=typeof f||(b.reset[c]=[b.reset[c],b.reset[c]])}var n={duration:0,queue:!1};m&&(n.complete=x),e.animate(d,b.reset,n)}else m&&x()},"hidden"===g.visibility&&(v.visibility=g.visibility)}e.animate(d,r,v)}},e},e.RegisterEffect.packagedEffects={"callout.bounce":{defaultDuration:550,calls:[[{translateY:-30},.25],[{translateY:0},.125],[{translateY:-15},.125],[{translateY:0},.25]]},"callout.shake":{defaultDuration:800,calls:[[{translateX:-11}],[{translateX:11}],[{translateX:-11}],[{translateX:11}],[{translateX:-11}],[{translateX:11}],[{translateX:-11}],[{translateX:0}]]},"callout.flash":{defaultDuration:1100,calls:[[{opacity:[0,"easeInOutQuad",1]}],[{opacity:[1,"easeInOutQuad"]}],[{opacity:[0,"easeInOutQuad"]}],[{opacity:[1,"easeInOutQuad"]}]]},"callout.pulse":{defaultDuration:825,calls:[[{scaleX:1.1,scaleY:1.1},.5,{easing:"easeInExpo"}],[{scaleX:1,scaleY:1},.5]]},"callout.swing":{defaultDuration:950,calls:[[{rotateZ:15}],[{rotateZ:-10}],[{rotateZ:5}],[{rotateZ:-5}],[{rotateZ:0}]]},"callout.tada":{defaultDuration:1e3,calls:[[{scaleX:.9,scaleY:.9,rotateZ:-3},.1],[{scaleX:1.1,scaleY:1.1,rotateZ:3},.1],[{scaleX:1.1,scaleY:1.1,rotateZ:-3},.1],["reverse",.125],["reverse",.125],["reverse",.125],["reverse",.125],["reverse",.125],[{scaleX:1,scaleY:1,rotateZ:0},.2]]},"transition.fadeIn":{defaultDuration:500,calls:[[{opacity:[1,0]}]]},"transition.fadeOut":{defaultDuration:500,calls:[[{opacity:[0,1]}]]},"transition.flipXIn":{defaultDuration:700,calls:[[{opacity:[1,0],transformPerspective:[800,800],rotateY:[0,-55]}]],reset:{transformPerspective:0}},"transition.flipXOut":{defaultDuration:700,calls:[[{opacity:[0,1],transformPerspective:[800,800],rotateY:55}]],reset:{transformPerspective:0,rotateY:0}},"transition.flipYIn":{defaultDuration:800,calls:[[{opacity:[1,0],transformPerspective:[800,800],rotateX:[0,-45]}]],reset:{transformPerspective:0}},"transition.flipYOut":{defaultDuration:800,calls:[[{opacity:[0,1],transformPerspective:[800,800],rotateX:25}]],reset:{transformPerspective:0,rotateX:0}},"transition.flipBounceXIn":{defaultDuration:900,calls:[[{opacity:[.725,0],transformPerspective:[400,400],rotateY:[-10,90]},.5],[{opacity:.8,rotateY:10},.25],[{opacity:1,rotateY:0},.25]],reset:{transformPerspective:0}},"transition.flipBounceXOut":{defaultDuration:800,calls:[[{opacity:[.9,1],transformPerspective:[400,400],rotateY:-10}],[{opacity:0,rotateY:90}]],reset:{transformPerspective:0,rotateY:0}},"transition.flipBounceYIn":{defaultDuration:850,calls:[[{opacity:[.725,0],transformPerspective:[400,400],rotateX:[-10,90]},.5],[{opacity:.8,rotateX:10},.25],[{opacity:1,rotateX:0},.25]],reset:{transformPerspective:0}},"transition.flipBounceYOut":{defaultDuration:800,calls:[[{opacity:[.9,1],transformPerspective:[400,400],rotateX:-15}],[{opacity:0,rotateX:90}]],reset:{transformPerspective:0,rotateX:0}},"transition.swoopIn":{defaultDuration:850,calls:[[{opacity:[1,0],transformOriginX:["100%","50%"],transformOriginY:["100%","100%"],scaleX:[1,0],scaleY:[1,0],translateX:[0,-700],translateZ:0}]],reset:{transformOriginX:"50%",transformOriginY:"50%"}},"transition.swoopOut":{defaultDuration:850,calls:[[{opacity:[0,1],transformOriginX:["50%","100%"],transformOriginY:["100%","100%"],scaleX:0,scaleY:0,translateX:-700,translateZ:0}]],reset:{transformOriginX:"50%",transformOriginY:"50%",scaleX:1,scaleY:1,translateX:0}},"transition.whirlIn":{defaultDuration:850,calls:[[{opacity:[1,0],transformOriginX:["50%","50%"],transformOriginY:["50%","50%"],scaleX:[1,0],scaleY:[1,0],rotateY:[0,160]},1,{easing:"easeInOutSine"}]]},"transition.whirlOut":{defaultDuration:750,calls:[[{opacity:[0,"easeInOutQuint",1],transformOriginX:["50%","50%"],transformOriginY:["50%","50%"],scaleX:0,scaleY:0,rotateY:160},1,{easing:"swing"}]],reset:{scaleX:1,scaleY:1,rotateY:0}},"transition.shrinkIn":{defaultDuration:750,calls:[[{opacity:[1,0],transformOriginX:["50%","50%"],transformOriginY:["50%","50%"],scaleX:[1,1.5],scaleY:[1,1.5],translateZ:0}]]},"transition.shrinkOut":{defaultDuration:600,calls:[[{opacity:[0,1],transformOriginX:["50%","50%"],transformOriginY:["50%","50%"],scaleX:1.3,scaleY:1.3,translateZ:0}]],reset:{scaleX:1,scaleY:1}},"transition.expandIn":{defaultDuration:700,calls:[[{opacity:[1,0],transformOriginX:["50%","50%"],transformOriginY:["50%","50%"],scaleX:[1,.625],scaleY:[1,.625],translateZ:0}]]},"transition.expandOut":{defaultDuration:700,calls:[[{opacity:[0,1],transformOriginX:["50%","50%"],transformOriginY:["50%","50%"],scaleX:.5,scaleY:.5,translateZ:0}]],reset:{scaleX:1,scaleY:1}},"transition.bounceIn":{defaultDuration:800,calls:[[{opacity:[1,0],scaleX:[1.05,.3],scaleY:[1.05,.3]},.35],[{scaleX:.9,scaleY:.9,translateZ:0},.2],[{scaleX:1,scaleY:1},.45]]},"transition.bounceOut":{defaultDuration:800,calls:[[{scaleX:.95,scaleY:.95},.35],[{scaleX:1.1,scaleY:1.1,translateZ:0},.35],[{opacity:[0,1],scaleX:.3,scaleY:.3},.3]],reset:{scaleX:1,scaleY:1}},"transition.bounceUpIn":{defaultDuration:800,calls:[[{opacity:[1,0],translateY:[-30,1e3]},.6,{easing:"easeOutCirc"}],[{translateY:10},.2],[{translateY:0},.2]]},"transition.bounceUpOut":{defaultDuration:1e3,calls:[[{translateY:20},.2],[{opacity:[0,"easeInCirc",1],translateY:-1e3},.8]],reset:{translateY:0}},"transition.bounceDownIn":{defaultDuration:800,calls:[[{opacity:[1,0],translateY:[30,-1e3]},.6,{easing:"easeOutCirc"}],[{translateY:-10},.2],[{translateY:0},.2]]},"transition.bounceDownOut":{defaultDuration:1e3,calls:[[{translateY:-20},.2],[{opacity:[0,"easeInCirc",1],translateY:1e3},.8]],reset:{translateY:0}},"transition.bounceLeftIn":{defaultDuration:750,calls:[[{opacity:[1,0],translateX:[30,-1250]},.6,{easing:"easeOutCirc"}],[{translateX:-10},.2],[{translateX:0},.2]]},"transition.bounceLeftOut":{defaultDuration:750,calls:[[{translateX:30},.2],[{opacity:[0,"easeInCirc",1],translateX:-1250},.8]],reset:{translateX:0}},"transition.bounceRightIn":{defaultDuration:750,calls:[[{opacity:[1,0],translateX:[-30,1250]},.6,{easing:"easeOutCirc"}],[{translateX:10},.2],[{translateX:0},.2]]},"transition.bounceRightOut":{defaultDuration:750,calls:[[{translateX:-30},.2],[{opacity:[0,"easeInCirc",1],translateX:1250},.8]],reset:{translateX:0}},"transition.slideUpIn":{defaultDuration:900,calls:[[{opacity:[1,0],translateY:[0,20],translateZ:0}]]},"transition.slideUpOut":{defaultDuration:900,calls:[[{opacity:[0,1],translateY:-20,translateZ:0}]],reset:{translateY:0}},"transition.slideDownIn":{defaultDuration:900,calls:[[{opacity:[1,0],translateY:[0,-20],translateZ:0}]]},"transition.slideDownOut":{defaultDuration:900,calls:[[{opacity:[0,1],translateY:20,translateZ:0}]],reset:{translateY:0}},"transition.slideLeftIn":{defaultDuration:1e3,calls:[[{opacity:[1,0],translateX:[0,-20],translateZ:0}]]},"transition.slideLeftOut":{defaultDuration:1050,calls:[[{opacity:[0,1],translateX:-20,translateZ:0}]],reset:{translateX:0}},"transition.slideRightIn":{defaultDuration:1e3,calls:[[{opacity:[1,0],translateX:[0,20],translateZ:0}]]},"transition.slideRightOut":{defaultDuration:1050,calls:[[{opacity:[0,1],translateX:20,translateZ:0}]],reset:{translateX:0}},"transition.slideUpBigIn":{defaultDuration:850,calls:[[{opacity:[1,0],translateY:[0,75],translateZ:0}]]},"transition.slideUpBigOut":{defaultDuration:800,calls:[[{opacity:[0,1],translateY:-75,translateZ:0}]],reset:{translateY:0}},"transition.slideDownBigIn":{defaultDuration:850,calls:[[{opacity:[1,0],translateY:[0,-75],translateZ:0}]]},"transition.slideDownBigOut":{defaultDuration:800,calls:[[{opacity:[0,1],translateY:75,translateZ:0}]],reset:{translateY:0}},"transition.slideLeftBigIn":{defaultDuration:800,calls:[[{opacity:[1,0],translateX:[0,-75],translateZ:0}]]},"transition.slideLeftBigOut":{defaultDuration:750,calls:[[{opacity:[0,1],translateX:-75,translateZ:0}]],reset:{translateX:0}},"transition.slideRightBigIn":{defaultDuration:800,calls:[[{opacity:[1,0],translateX:[0,75],translateZ:0}]]},"transition.slideRightBigOut":{defaultDuration:750,calls:[[{opacity:[0,1],translateX:75,translateZ:0}]],reset:{translateX:0}},"transition.perspectiveUpIn":{defaultDuration:800,calls:[[{opacity:[1,0],transformPerspective:[800,800],transformOriginX:[0,0],transformOriginY:["100%","100%"],rotateX:[0,-180]}]],reset:{transformPerspective:0,transformOriginX:"50%",transformOriginY:"50%"}},"transition.perspectiveUpOut":{defaultDuration:850,calls:[[{opacity:[0,1],transformPerspective:[800,800],transformOriginX:[0,0],transformOriginY:["100%","100%"],rotateX:-180}]],reset:{transformPerspective:0,transformOriginX:"50%",transformOriginY:"50%",rotateX:0}},"transition.perspectiveDownIn":{defaultDuration:800,calls:[[{opacity:[1,0],transformPerspective:[800,800],transformOriginX:[0,0],transformOriginY:[0,0],rotateX:[0,180]}]],reset:{transformPerspective:0,transformOriginX:"50%",transformOriginY:"50%"}},"transition.perspectiveDownOut":{defaultDuration:850,calls:[[{opacity:[0,1],transformPerspective:[800,800],transformOriginX:[0,0],transformOriginY:[0,0],rotateX:180}]],reset:{transformPerspective:0,transformOriginX:"50%",transformOriginY:"50%",rotateX:0}},"transition.perspectiveLeftIn":{defaultDuration:950,calls:[[{opacity:[1,0],transformPerspective:[2e3,2e3],transformOriginX:[0,0],transformOriginY:[0,0],rotateY:[0,-180]}]],reset:{transformPerspective:0,transformOriginX:"50%",transformOriginY:"50%"}},"transition.perspectiveLeftOut":{defaultDuration:950,calls:[[{opacity:[0,1],transformPerspective:[2e3,2e3],transformOriginX:[0,0],transformOriginY:[0,0],rotateY:-180}]],reset:{transformPerspective:0,transformOriginX:"50%",transformOriginY:"50%",rotateY:0}},"transition.perspectiveRightIn":{defaultDuration:950,calls:[[{opacity:[1,0],transformPerspective:[2e3,2e3],transformOriginX:["100%","100%"],transformOriginY:[0,0],rotateY:[0,180]}]],reset:{transformPerspective:0,transformOriginX:"50%",transformOriginY:"50%"}},"transition.perspectiveRightOut":{defaultDuration:950,calls:[[{opacity:[0,1],transformPerspective:[2e3,2e3],transformOriginX:["100%","100%"],transformOriginY:[0,0],rotateY:180}]],reset:{transformPerspective:0,transformOriginX:"50%",transformOriginY:"50%",rotateY:0}}};for(var j in e.RegisterEffect.packagedEffects)e.RegisterEffect.packagedEffects.hasOwnProperty(j)&&e.RegisterEffect(j,e.RegisterEffect.packagedEffects[j]);e.RunSequence=function(a){var b=f.extend(!0,[],a);b.length>1&&(f.each(b.reverse(),function(a,c){var d=b[a+1];if(d){var g=c.o||c.options,h=d.o||d.options,i=g&&!1===g.sequenceQueue?"begin":"complete",j=h&&h[i],k={};k[i]=function(){var a=d.e||d.elements,b=a.nodeType?[a]:a;j&&j.call(b,b),e(c)},d.o?d.o=f.extend({},h,k):d.options=f.extend({},h,k)}}),b.reverse()),e(b[0])}}(window.jQuery||window.Zepto||window,window,window?window.document:undefined)});
/**
 * @license
 * Video.js 5.5.3 <http://videojs.com/>
 * Copyright Brightcove, Inc. <https://www.brightcove.com/>
 * Available under Apache License Version 2.0
 * <https://github.com/videojs/video.js/blob/master/LICENSE>
 *
 * Includes vtt.js <https://github.com/mozilla/vtt.js>
 * Available under Apache License Version 2.0
 * <https://github.com/mozilla/vtt.js/blob/master/LICENSE>
 */
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.videojs = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
(function (global){
var topLevel = typeof global !== 'undefined' ? global :
    typeof window !== 'undefined' ? window : {}
var minDoc = _dereq_('min-document');

if (typeof document !== 'undefined') {
    module.exports = document;
} else {
    var doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'];

    if (!doccy) {
        doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc;
    }

    module.exports = doccy;
}

}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
//# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9nbG9iYWwvZG9jdW1lbnQuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyJ2YXIgdG9wTGV2ZWwgPSB0eXBlb2YgZ2xvYmFsICE9PSAndW5kZWZpbmVkJyA/IGdsb2JhbCA6XG4gICAgdHlwZW9mIHdpbmRvdyAhPT0gJ3VuZGVmaW5lZCcgPyB3aW5kb3cgOiB7fVxudmFyIG1pbkRvYyA9IHJlcXVpcmUoJ21pbi1kb2N1bWVudCcpO1xuXG5pZiAodHlwZW9mIGRvY3VtZW50ICE9PSAndW5kZWZpbmVkJykge1xuICAgIG1vZHVsZS5leHBvcnRzID0gZG9jdW1lbnQ7XG59IGVsc2Uge1xuICAgIHZhciBkb2NjeSA9IHRvcExldmVsWydfX0dMT0JBTF9ET0NVTUVOVF9DQUNIRUA0J107XG5cbiAgICBpZiAoIWRvY2N5KSB7XG4gICAgICAgIGRvY2N5ID0gdG9wTGV2ZWxbJ19fR0xPQkFMX0RPQ1VNRU5UX0NBQ0hFQDQnXSA9IG1pbkRvYztcbiAgICB9XG5cbiAgICBtb2R1bGUuZXhwb3J0cyA9IGRvY2N5O1xufVxuIl19
},{"min-document":3}],2:[function(_dereq_,module,exports){
(function (global){
if (typeof window !== "undefined") {
    module.exports = window;
} else if (typeof global !== "undefined") {
    module.exports = global;
} else if (typeof self !== "undefined"){
    module.exports = self;
} else {
    module.exports = {};
}

}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
//# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9nbG9iYWwvd2luZG93LmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiJnZW5lcmF0ZWQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlc0NvbnRlbnQiOlsiaWYgKHR5cGVvZiB3aW5kb3cgIT09IFwidW5kZWZpbmVkXCIpIHtcbiAgICBtb2R1bGUuZXhwb3J0cyA9IHdpbmRvdztcbn0gZWxzZSBpZiAodHlwZW9mIGdsb2JhbCAhPT0gXCJ1bmRlZmluZWRcIikge1xuICAgIG1vZHVsZS5leHBvcnRzID0gZ2xvYmFsO1xufSBlbHNlIGlmICh0eXBlb2Ygc2VsZiAhPT0gXCJ1bmRlZmluZWRcIil7XG4gICAgbW9kdWxlLmV4cG9ydHMgPSBzZWxmO1xufSBlbHNlIHtcbiAgICBtb2R1bGUuZXhwb3J0cyA9IHt9O1xufVxuIl19
},{}],3:[function(_dereq_,module,exports){

},{}],4:[function(_dereq_,module,exports){
var getNative = _dereq_('../internal/getNative');

/* Native method references for those with the same name as other `lodash` methods. */
var nativeNow = getNative(Date, 'now');

/**
 * Gets the number of milliseconds that have elapsed since the Unix epoch
 * (1 January 1970 00:00:00 UTC).
 *
 * @static
 * @memberOf _
 * @category Date
 * @example
 *
 * _.defer(function(stamp) {
 *   console.log(_.now() - stamp);
 * }, _.now());
 * // => logs the number of milliseconds it took for the deferred function to be invoked
 */
var now = nativeNow || function() {
  return new Date().getTime();
};

module.exports = now;

},{"../internal/getNative":20}],5:[function(_dereq_,module,exports){
var isObject = _dereq_('../lang/isObject'),
    now = _dereq_('../date/now');

/** Used as the `TypeError` message for "Functions" methods. */
var FUNC_ERROR_TEXT = 'Expected a function';

/* Native method references for those with the same name as other `lodash` methods. */
var nativeMax = Math.max;

/**
 * Creates a debounced function that delays invoking `func` until after `wait`
 * milliseconds have elapsed since the last time the debounced function was
 * invoked. The debounced function comes with a `cancel` method to cancel
 * delayed invocations. Provide an options object to indicate that `func`
 * should be invoked on the leading and/or trailing edge of the `wait` timeout.
 * Subsequent calls to the debounced function return the result of the last
 * `func` invocation.
 *
 * **Note:** If `leading` and `trailing` options are `true`, `func` is invoked
 * on the trailing edge of the timeout only if the the debounced function is
 * invoked more than once during the `wait` timeout.
 *
 * See [David Corbacho's article](http://drupalmotion.com/article/debounce-and-throttle-visual-explanation)
 * for details over the differences between `_.debounce` and `_.throttle`.
 *
 * @static
 * @memberOf _
 * @category Function
 * @param {Function} func The function to debounce.
 * @param {number} [wait=0] The number of milliseconds to delay.
 * @param {Object} [options] The options object.
 * @param {boolean} [options.leading=false] Specify invoking on the leading
 *  edge of the timeout.
 * @param {number} [options.maxWait] The maximum time `func` is allowed to be
 *  delayed before it's invoked.
 * @param {boolean} [options.trailing=true] Specify invoking on the trailing
 *  edge of the timeout.
 * @returns {Function} Returns the new debounced function.
 * @example
 *
 * // avoid costly calculations while the window size is in flux
 * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
 *
 * // invoke `sendMail` when the click event is fired, debouncing subsequent calls
 * jQuery('#postbox').on('click', _.debounce(sendMail, 300, {
 *   'leading': true,
 *   'trailing': false
 * }));
 *
 * // ensure `batchLog` is invoked once after 1 second of debounced calls
 * var source = new EventSource('/stream');
 * jQuery(source).on('message', _.debounce(batchLog, 250, {
 *   'maxWait': 1000
 * }));
 *
 * // cancel a debounced call
 * var todoChanges = _.debounce(batchLog, 1000);
 * Object.observe(models.todo, todoChanges);
 *
 * Object.observe(models, function(changes) {
 *   if (_.find(changes, { 'user': 'todo', 'type': 'delete'})) {
 *     todoChanges.cancel();
 *   }
 * }, ['delete']);
 *
 * // ...at some point `models.todo` is changed
 * models.todo.completed = true;
 *
 * // ...before 1 second has passed `models.todo` is deleted
 * // which cancels the debounced `todoChanges` call
 * delete models.todo;
 */
function debounce(func, wait, options) {
  var args,
      maxTimeoutId,
      result,
      stamp,
      thisArg,
      timeoutId,
      trailingCall,
      lastCalled = 0,
      maxWait = false,
      trailing = true;

  if (typeof func != 'function') {
    throw new TypeError(FUNC_ERROR_TEXT);
  }
  wait = wait < 0 ? 0 : (+wait || 0);
  if (options === true) {
    var leading = true;
    trailing = false;
  } else if (isObject(options)) {
    leading = !!options.leading;
    maxWait = 'maxWait' in options && nativeMax(+options.maxWait || 0, wait);
    trailing = 'trailing' in options ? !!options.trailing : trailing;
  }

  function cancel() {
    if (timeoutId) {
      clearTimeout(timeoutId);
    }
    if (maxTimeoutId) {
      clearTimeout(maxTimeoutId);
    }
    lastCalled = 0;
    maxTimeoutId = timeoutId = trailingCall = undefined;
  }

  function complete(isCalled, id) {
    if (id) {
      clearTimeout(id);
    }
    maxTimeoutId = timeoutId = trailingCall = undefined;
    if (isCalled) {
      lastCalled = now();
      result = func.apply(thisArg, args);
      if (!timeoutId && !maxTimeoutId) {
        args = thisArg = undefined;
      }
    }
  }

  function delayed() {
    var remaining = wait - (now() - stamp);
    if (remaining <= 0 || remaining > wait) {
      complete(trailingCall, maxTimeoutId);
    } else {
      timeoutId = setTimeout(delayed, remaining);
    }
  }

  function maxDelayed() {
    complete(trailing, timeoutId);
  }

  function debounced() {
    args = arguments;
    stamp = now();
    thisArg = this;
    trailingCall = trailing && (timeoutId || !leading);

    if (maxWait === false) {
      var leadingCall = leading && !timeoutId;
    } else {
      if (!maxTimeoutId && !leading) {
        lastCalled = stamp;
      }
      var remaining = maxWait - (stamp - lastCalled),
          isCalled = remaining <= 0 || remaining > maxWait;

      if (isCalled) {
        if (maxTimeoutId) {
          maxTimeoutId = clearTimeout(maxTimeoutId);
        }
        lastCalled = stamp;
        result = func.apply(thisArg, args);
      }
      else if (!maxTimeoutId) {
        maxTimeoutId = setTimeout(maxDelayed, remaining);
      }
    }
    if (isCalled && timeoutId) {
      timeoutId = clearTimeout(timeoutId);
    }
    else if (!timeoutId && wait !== maxWait) {
      timeoutId = setTimeout(delayed, wait);
    }
    if (leadingCall) {
      isCalled = true;
      result = func.apply(thisArg, args);
    }
    if (isCalled && !timeoutId && !maxTimeoutId) {
      args = thisArg = undefined;
    }
    return result;
  }
  debounced.cancel = cancel;
  return debounced;
}

module.exports = debounce;

},{"../date/now":4,"../lang/isObject":33}],6:[function(_dereq_,module,exports){
/** Used as the `TypeError` message for "Functions" methods. */
var FUNC_ERROR_TEXT = 'Expected a function';

/* Native method references for those with the same name as other `lodash` methods. */
var nativeMax = Math.max;

/**
 * Creates a function that invokes `func` with the `this` binding of the
 * created function and arguments from `start` and beyond provided as an array.
 *
 * **Note:** This method is based on the [rest parameter](https://developer.mozilla.org/Web/JavaScript/Reference/Functions/rest_parameters).
 *
 * @static
 * @memberOf _
 * @category Function
 * @param {Function} func The function to apply a rest parameter to.
 * @param {number} [start=func.length-1] The start position of the rest parameter.
 * @returns {Function} Returns the new function.
 * @example
 *
 * var say = _.restParam(function(what, names) {
 *   return what + ' ' + _.initial(names).join(', ') +
 *     (_.size(names) > 1 ? ', & ' : '') + _.last(names);
 * });
 *
 * say('hello', 'fred', 'barney', 'pebbles');
 * // => 'hello fred, barney, & pebbles'
 */
function restParam(func, start) {
  if (typeof func != 'function') {
    throw new TypeError(FUNC_ERROR_TEXT);
  }
  start = nativeMax(start === undefined ? (func.length - 1) : (+start || 0), 0);
  return function() {
    var args = arguments,
        index = -1,
        length = nativeMax(args.length - start, 0),
        rest = Array(length);

    while (++index < length) {
      rest[index] = args[start + index];
    }
    switch (start) {
      case 0: return func.call(this, rest);
      case 1: return func.call(this, args[0], rest);
      case 2: return func.call(this, args[0], args[1], rest);
    }
    var otherArgs = Array(start + 1);
    index = -1;
    while (++index < start) {
      otherArgs[index] = args[index];
    }
    otherArgs[start] = rest;
    return func.apply(this, otherArgs);
  };
}

module.exports = restParam;

},{}],7:[function(_dereq_,module,exports){
var debounce = _dereq_('./debounce'),
    isObject = _dereq_('../lang/isObject');

/** Used as the `TypeError` message for "Functions" methods. */
var FUNC_ERROR_TEXT = 'Expected a function';

/**
 * Creates a throttled function that only invokes `func` at most once per
 * every `wait` milliseconds. The throttled function comes with a `cancel`
 * method to cancel delayed invocations. Provide an options object to indicate
 * that `func` should be invoked on the leading and/or trailing edge of the
 * `wait` timeout. Subsequent calls to the throttled function return the
 * result of the last `func` call.
 *
 * **Note:** If `leading` and `trailing` options are `true`, `func` is invoked
 * on the trailing edge of the timeout only if the the throttled function is
 * invoked more than once during the `wait` timeout.
 *
 * See [David Corbacho's article](http://drupalmotion.com/article/debounce-and-throttle-visual-explanation)
 * for details over the differences between `_.throttle` and `_.debounce`.
 *
 * @static
 * @memberOf _
 * @category Function
 * @param {Function} func The function to throttle.
 * @param {number} [wait=0] The number of milliseconds to throttle invocations to.
 * @param {Object} [options] The options object.
 * @param {boolean} [options.leading=true] Specify invoking on the leading
 *  edge of the timeout.
 * @param {boolean} [options.trailing=true] Specify invoking on the trailing
 *  edge of the timeout.
 * @returns {Function} Returns the new throttled function.
 * @example
 *
 * // avoid excessively updating the position while scrolling
 * jQuery(window).on('scroll', _.throttle(updatePosition, 100));
 *
 * // invoke `renewToken` when the click event is fired, but not more than once every 5 minutes
 * jQuery('.interactive').on('click', _.throttle(renewToken, 300000, {
 *   'trailing': false
 * }));
 *
 * // cancel a trailing throttled call
 * jQuery(window).on('popstate', throttled.cancel);
 */
function throttle(func, wait, options) {
  var leading = true,
      trailing = true;

  if (typeof func != 'function') {
    throw new TypeError(FUNC_ERROR_TEXT);
  }
  if (options === false) {
    leading = false;
  } else if (isObject(options)) {
    leading = 'leading' in options ? !!options.leading : leading;
    trailing = 'trailing' in options ? !!options.trailing : trailing;
  }
  return debounce(func, wait, { 'leading': leading, 'maxWait': +wait, 'trailing': trailing });
}

module.exports = throttle;

},{"../lang/isObject":33,"./debounce":5}],8:[function(_dereq_,module,exports){
/**
 * Copies the values of `source` to `array`.
 *
 * @private
 * @param {Array} source The array to copy values from.
 * @param {Array} [array=[]] The array to copy values to.
 * @returns {Array} Returns `array`.
 */
function arrayCopy(source, array) {
  var index = -1,
      length = source.length;

  array || (array = Array(length));
  while (++index < length) {
    array[index] = source[index];
  }
  return array;
}

module.exports = arrayCopy;

},{}],9:[function(_dereq_,module,exports){
/**
 * A specialized version of `_.forEach` for arrays without support for callback
 * shorthands and `this` binding.
 *
 * @private
 * @param {Array} array The array to iterate over.
 * @param {Function} iteratee The function invoked per iteration.
 * @returns {Array} Returns `array`.
 */
function arrayEach(array, iteratee) {
  var index = -1,
      length = array.length;

  while (++index < length) {
    if (iteratee(array[index], index, array) === false) {
      break;
    }
  }
  return array;
}

module.exports = arrayEach;

},{}],10:[function(_dereq_,module,exports){
/**
 * Copies properties of `source` to `object`.
 *
 * @private
 * @param {Object} source The object to copy properties from.
 * @param {Array} props The property names to copy.
 * @param {Object} [object={}] The object to copy properties to.
 * @returns {Object} Returns `object`.
 */
function baseCopy(source, props, object) {
  object || (object = {});

  var index = -1,
      length = props.length;

  while (++index < length) {
    var key = props[index];
    object[key] = source[key];
  }
  return object;
}

module.exports = baseCopy;

},{}],11:[function(_dereq_,module,exports){
var createBaseFor = _dereq_('./createBaseFor');

/**
 * The base implementation of `baseForIn` and `baseForOwn` which iterates
 * over `object` properties returned by `keysFunc` invoking `iteratee` for
 * each property. Iteratee functions may exit iteration early by explicitly
 * returning `false`.
 *
 * @private
 * @param {Object} object The object to iterate over.
 * @param {Function} iteratee The function invoked per iteration.
 * @param {Function} keysFunc The function to get the keys of `object`.
 * @returns {Object} Returns `object`.
 */
var baseFor = createBaseFor();

module.exports = baseFor;

},{"./createBaseFor":18}],12:[function(_dereq_,module,exports){
var baseFor = _dereq_('./baseFor'),
    keysIn = _dereq_('../object/keysIn');

/**
 * The base implementation of `_.forIn` without support for callback
 * shorthands and `this` binding.
 *
 * @private
 * @param {Object} object The object to iterate over.
 * @param {Function} iteratee The function invoked per iteration.
 * @returns {Object} Returns `object`.
 */
function baseForIn(object, iteratee) {
  return baseFor(object, iteratee, keysIn);
}

module.exports = baseForIn;

},{"../object/keysIn":39,"./baseFor":11}],13:[function(_dereq_,module,exports){
var arrayEach = _dereq_('./arrayEach'),
    baseMergeDeep = _dereq_('./baseMergeDeep'),
    isArray = _dereq_('../lang/isArray'),
    isArrayLike = _dereq_('./isArrayLike'),
    isObject = _dereq_('../lang/isObject'),
    isObjectLike = _dereq_('./isObjectLike'),
    isTypedArray = _dereq_('../lang/isTypedArray'),
    keys = _dereq_('../object/keys');

/**
 * The base implementation of `_.merge` without support for argument juggling,
 * multiple sources, and `this` binding `customizer` functions.
 *
 * @private
 * @param {Object} object The destination object.
 * @param {Object} source The source object.
 * @param {Function} [customizer] The function to customize merged values.
 * @param {Array} [stackA=[]] Tracks traversed source objects.
 * @param {Array} [stackB=[]] Associates values with source counterparts.
 * @returns {Object} Returns `object`.
 */
function baseMerge(object, source, customizer, stackA, stackB) {
  if (!isObject(object)) {
    return object;
  }
  var isSrcArr = isArrayLike(source) && (isArray(source) || isTypedArray(source)),
      props = isSrcArr ? undefined : keys(source);

  arrayEach(props || source, function(srcValue, key) {
    if (props) {
      key = srcValue;
      srcValue = source[key];
    }
    if (isObjectLike(srcValue)) {
      stackA || (stackA = []);
      stackB || (stackB = []);
      baseMergeDeep(object, source, key, baseMerge, customizer, stackA, stackB);
    }
    else {
      var value = object[key],
          result = customizer ? customizer(value, srcValue, key, object, source) : undefined,
          isCommon = result === undefined;

      if (isCommon) {
        result = srcValue;
      }
      if ((result !== undefined || (isSrcArr && !(key in object))) &&
          (isCommon || (result === result ? (result !== value) : (value === value)))) {
        object[key] = result;
      }
    }
  });
  return object;
}

module.exports = baseMerge;

},{"../lang/isArray":30,"../lang/isObject":33,"../lang/isTypedArray":36,"../object/keys":38,"./arrayEach":9,"./baseMergeDeep":14,"./isArrayLike":21,"./isObjectLike":26}],14:[function(_dereq_,module,exports){
var arrayCopy = _dereq_('./arrayCopy'),
    isArguments = _dereq_('../lang/isArguments'),
    isArray = _dereq_('../lang/isArray'),
    isArrayLike = _dereq_('./isArrayLike'),
    isPlainObject = _dereq_('../lang/isPlainObject'),
    isTypedArray = _dereq_('../lang/isTypedArray'),
    toPlainObject = _dereq_('../lang/toPlainObject');

/**
 * A specialized version of `baseMerge` for arrays and objects which performs
 * deep merges and tracks traversed objects enabling objects with circular
 * references to be merged.
 *
 * @private
 * @param {Object} object The destination object.
 * @param {Object} source The source object.
 * @param {string} key The key of the value to merge.
 * @param {Function} mergeFunc The function to merge values.
 * @param {Function} [customizer] The function to customize merged values.
 * @param {Array} [stackA=[]] Tracks traversed source objects.
 * @param {Array} [stackB=[]] Associates values with source counterparts.
 * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
 */
function baseMergeDeep(object, source, key, mergeFunc, customizer, stackA, stackB) {
  var length = stackA.length,
      srcValue = source[key];

  while (length--) {
    if (stackA[length] == srcValue) {
      object[key] = stackB[length];
      return;
    }
  }
  var value = object[key],
      result = customizer ? customizer(value, srcValue, key, object, source) : undefined,
      isCommon = result === undefined;

  if (isCommon) {
    result = srcValue;
    if (isArrayLike(srcValue) && (isArray(srcValue) || isTypedArray(srcValue))) {
      result = isArray(value)
        ? value
        : (isArrayLike(value) ? arrayCopy(value) : []);
    }
    else if (isPlainObject(srcValue) || isArguments(srcValue)) {
      result = isArguments(value)
        ? toPlainObject(value)
        : (isPlainObject(value) ? value : {});
    }
    else {
      isCommon = false;
    }
  }
  // Add the source value to the stack of traversed objects and associate
  // it with its merged value.
  stackA.push(srcValue);
  stackB.push(result);

  if (isCommon) {
    // Recursively merge objects and arrays (susceptible to call stack limits).
    object[key] = mergeFunc(result, srcValue, customizer, stackA, stackB);
  } else if (result === result ? (result !== value) : (value === value)) {
    object[key] = result;
  }
}

module.exports = baseMergeDeep;

},{"../lang/isArguments":29,"../lang/isArray":30,"../lang/isPlainObject":34,"../lang/isTypedArray":36,"../lang/toPlainObject":37,"./arrayCopy":8,"./isArrayLike":21}],15:[function(_dereq_,module,exports){
var toObject = _dereq_('./toObject');

/**
 * The base implementation of `_.property` without support for deep paths.
 *
 * @private
 * @param {string} key The key of the property to get.
 * @returns {Function} Returns the new function.
 */
function baseProperty(key) {
  return function(object) {
    return object == null ? undefined : toObject(object)[key];
  };
}

module.exports = baseProperty;

},{"./toObject":28}],16:[function(_dereq_,module,exports){
var identity = _dereq_('../utility/identity');

/**
 * A specialized version of `baseCallback` which only supports `this` binding
 * and specifying the number of arguments to provide to `func`.
 *
 * @private
 * @param {Function} func The function to bind.
 * @param {*} thisArg The `this` binding of `func`.
 * @param {number} [argCount] The number of arguments to provide to `func`.
 * @returns {Function} Returns the callback.
 */
function bindCallback(func, thisArg, argCount) {
  if (typeof func != 'function') {
    return identity;
  }
  if (thisArg === undefined) {
    return func;
  }
  switch (argCount) {
    case 1: return function(value) {
      return func.call(thisArg, value);
    };
    case 3: return function(value, index, collection) {
      return func.call(thisArg, value, index, collection);
    };
    case 4: return function(accumulator, value, index, collection) {
      return func.call(thisArg, accumulator, value, index, collection);
    };
    case 5: return function(value, other, key, object, source) {
      return func.call(thisArg, value, other, key, object, source);
    };
  }
  return function() {
    return func.apply(thisArg, arguments);
  };
}

module.exports = bindCallback;

},{"../utility/identity":42}],17:[function(_dereq_,module,exports){
var bindCallback = _dereq_('./bindCallback'),
    isIterateeCall = _dereq_('./isIterateeCall'),
    restParam = _dereq_('../function/restParam');

/**
 * Creates a `_.assign`, `_.defaults`, or `_.merge` function.
 *
 * @private
 * @param {Function} assigner The function to assign values.
 * @returns {Function} Returns the new assigner function.
 */
function createAssigner(assigner) {
  return restParam(function(object, sources) {
    var index = -1,
        length = object == null ? 0 : sources.length,
        customizer = length > 2 ? sources[length - 2] : undefined,
        guard = length > 2 ? sources[2] : undefined,
        thisArg = length > 1 ? sources[length - 1] : undefined;

    if (typeof customizer == 'function') {
      customizer = bindCallback(customizer, thisArg, 5);
      length -= 2;
    } else {
      customizer = typeof thisArg == 'function' ? thisArg : undefined;
      length -= (customizer ? 1 : 0);
    }
    if (guard && isIterateeCall(sources[0], sources[1], guard)) {
      customizer = length < 3 ? undefined : customizer;
      length = 1;
    }
    while (++index < length) {
      var source = sources[index];
      if (source) {
        assigner(object, source, customizer);
      }
    }
    return object;
  });
}

module.exports = createAssigner;

},{"../function/restParam":6,"./bindCallback":16,"./isIterateeCall":24}],18:[function(_dereq_,module,exports){
var toObject = _dereq_('./toObject');

/**
 * Creates a base function for `_.forIn` or `_.forInRight`.
 *
 * @private
 * @param {boolean} [fromRight] Specify iterating from right to left.
 * @returns {Function} Returns the new base function.
 */
function createBaseFor(fromRight) {
  return function(object, iteratee, keysFunc) {
    var iterable = toObject(object),
        props = keysFunc(object),
        length = props.length,
        index = fromRight ? length : -1;

    while ((fromRight ? index-- : ++index < length)) {
      var key = props[index];
      if (iteratee(iterable[key], key, iterable) === false) {
        break;
      }
    }
    return object;
  };
}

module.exports = createBaseFor;

},{"./toObject":28}],19:[function(_dereq_,module,exports){
var baseProperty = _dereq_('./baseProperty');

/**
 * Gets the "length" property value of `object`.
 *
 * **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792)
 * that affects Safari on at least iOS 8.1-8.3 ARM64.
 *
 * @private
 * @param {Object} object The object to query.
 * @returns {*} Returns the "length" value.
 */
var getLength = baseProperty('length');

module.exports = getLength;

},{"./baseProperty":15}],20:[function(_dereq_,module,exports){
var isNative = _dereq_('../lang/isNative');

/**
 * Gets the native function at `key` of `object`.
 *
 * @private
 * @param {Object} object The object to query.
 * @param {string} key The key of the method to get.
 * @returns {*} Returns the function if it's native, else `undefined`.
 */
function getNative(object, key) {
  var value = object == null ? undefined : object[key];
  return isNative(value) ? value : undefined;
}

module.exports = getNative;

},{"../lang/isNative":32}],21:[function(_dereq_,module,exports){
var getLength = _dereq_('./getLength'),
    isLength = _dereq_('./isLength');

/**
 * Checks if `value` is array-like.
 *
 * @private
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is array-like, else `false`.
 */
function isArrayLike(value) {
  return value != null && isLength(getLength(value));
}

module.exports = isArrayLike;

},{"./getLength":19,"./isLength":25}],22:[function(_dereq_,module,exports){
/**
 * Checks if `value` is a host object in IE < 9.
 *
 * @private
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a host object, else `false`.
 */
var isHostObject = (function() {
  try {
    Object({ 'toString': 0 } + '');
  } catch(e) {
    return function() { return false; };
  }
  return function(value) {
    // IE < 9 presents many host objects as `Object` objects that can coerce
    // to strings despite having improperly defined `toString` methods.
    return typeof value.toString != 'function' && typeof (value + '') == 'string';
  };
}());

module.exports = isHostObject;

},{}],23:[function(_dereq_,module,exports){
/** Used to detect unsigned integer values. */
var reIsUint = /^\d+$/;

/**
 * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer)
 * of an array-like value.
 */
var MAX_SAFE_INTEGER = 9007199254740991;

/**
 * Checks if `value` is a valid array-like index.
 *
 * @private
 * @param {*} value The value to check.
 * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
 * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
 */
function isIndex(value, length) {
  value = (typeof value == 'number' || reIsUint.test(value)) ? +value : -1;
  length = length == null ? MAX_SAFE_INTEGER : length;
  return value > -1 && value % 1 == 0 && value < length;
}

module.exports = isIndex;

},{}],24:[function(_dereq_,module,exports){
var isArrayLike = _dereq_('./isArrayLike'),
    isIndex = _dereq_('./isIndex'),
    isObject = _dereq_('../lang/isObject');

/**
 * Checks if the provided arguments are from an iteratee call.
 *
 * @private
 * @param {*} value The potential iteratee value argument.
 * @param {*} index The potential iteratee index or key argument.
 * @param {*} object The potential iteratee object argument.
 * @returns {boolean} Returns `true` if the arguments are from an iteratee call, else `false`.
 */
function isIterateeCall(value, index, object) {
  if (!isObject(object)) {
    return false;
  }
  var type = typeof index;
  if (type == 'number'
      ? (isArrayLike(object) && isIndex(index, object.length))
      : (type == 'string' && index in object)) {
    var other = object[index];
    return value === value ? (value === other) : (other !== other);
  }
  return false;
}

module.exports = isIterateeCall;

},{"../lang/isObject":33,"./isArrayLike":21,"./isIndex":23}],25:[function(_dereq_,module,exports){
/**
 * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer)
 * of an array-like value.
 */
var MAX_SAFE_INTEGER = 9007199254740991;

/**
 * Checks if `value` is a valid array-like length.
 *
 * **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).
 *
 * @private
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
 */
function isLength(value) {
  return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
}

module.exports = isLength;

},{}],26:[function(_dereq_,module,exports){
/**
 * Checks if `value` is object-like.
 *
 * @private
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
 */
function isObjectLike(value) {
  return !!value && typeof value == 'object';
}

module.exports = isObjectLike;

},{}],27:[function(_dereq_,module,exports){
var isArguments = _dereq_('../lang/isArguments'),
    isArray = _dereq_('../lang/isArray'),
    isIndex = _dereq_('./isIndex'),
    isLength = _dereq_('./isLength'),
    isString = _dereq_('../lang/isString'),
    keysIn = _dereq_('../object/keysIn');

/** Used for native method references. */
var objectProto = Object.prototype;

/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;

/**
 * A fallback implementation of `Object.keys` which creates an array of the
 * own enumerable property names of `object`.
 *
 * @private
 * @param {Object} object The object to query.
 * @returns {Array} Returns the array of property names.
 */
function shimKeys(object) {
  var props = keysIn(object),
      propsLength = props.length,
      length = propsLength && object.length;

  var allowIndexes = !!length && isLength(length) &&
    (isArray(object) || isArguments(object) || isString(object));

  var index = -1,
      result = [];

  while (++index < propsLength) {
    var key = props[index];
    if ((allowIndexes && isIndex(key, length)) || hasOwnProperty.call(object, key)) {
      result.push(key);
    }
  }
  return result;
}

module.exports = shimKeys;

},{"../lang/isArguments":29,"../lang/isArray":30,"../lang/isString":35,"../object/keysIn":39,"./isIndex":23,"./isLength":25}],28:[function(_dereq_,module,exports){
var isObject = _dereq_('../lang/isObject'),
    isString = _dereq_('../lang/isString'),
    support = _dereq_('../support');

/**
 * Converts `value` to an object if it's not one.
 *
 * @private
 * @param {*} value The value to process.
 * @returns {Object} Returns the object.
 */
function toObject(value) {
  if (support.unindexedChars && isString(value)) {
    var index = -1,
        length = value.length,
        result = Object(value);

    while (++index < length) {
      result[index] = value.charAt(index);
    }
    return result;
  }
  return isObject(value) ? value : Object(value);
}

module.exports = toObject;

},{"../lang/isObject":33,"../lang/isString":35,"../support":41}],29:[function(_dereq_,module,exports){
var isArrayLike = _dereq_('../internal/isArrayLike'),
    isObjectLike = _dereq_('../internal/isObjectLike');

/** Used for native method references. */
var objectProto = Object.prototype;

/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;

/** Native method references. */
var propertyIsEnumerable = objectProto.propertyIsEnumerable;

/**
 * Checks if `value` is classified as an `arguments` object.
 *
 * @static
 * @memberOf _
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
 * @example
 *
 * _.isArguments(function() { return arguments; }());
 * // => true
 *
 * _.isArguments([1, 2, 3]);
 * // => false
 */
function isArguments(value) {
  return isObjectLike(value) && isArrayLike(value) &&
    hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee');
}

module.exports = isArguments;

},{"../internal/isArrayLike":21,"../internal/isObjectLike":26}],30:[function(_dereq_,module,exports){
var getNative = _dereq_('../internal/getNative'),
    isLength = _dereq_('../internal/isLength'),
    isObjectLike = _dereq_('../internal/isObjectLike');

/** `Object#toString` result references. */
var arrayTag = '[object Array]';

/** Used for native method references. */
var objectProto = Object.prototype;

/**
 * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
 * of values.
 */
var objToString = objectProto.toString;

/* Native method references for those with the same name as other `lodash` methods. */
var nativeIsArray = getNative(Array, 'isArray');

/**
 * Checks if `value` is classified as an `Array` object.
 *
 * @static
 * @memberOf _
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
 * @example
 *
 * _.isArray([1, 2, 3]);
 * // => true
 *
 * _.isArray(function() { return arguments; }());
 * // => false
 */
var isArray = nativeIsArray || function(value) {
  return isObjectLike(value) && isLength(value.length) && objToString.call(value) == arrayTag;
};

module.exports = isArray;

},{"../internal/getNative":20,"../internal/isLength":25,"../internal/isObjectLike":26}],31:[function(_dereq_,module,exports){
var isObject = _dereq_('./isObject');

/** `Object#toString` result references. */
var funcTag = '[object Function]';

/** Used for native method references. */
var objectProto = Object.prototype;

/**
 * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
 * of values.
 */
var objToString = objectProto.toString;

/**
 * Checks if `value` is classified as a `Function` object.
 *
 * @static
 * @memberOf _
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
 * @example
 *
 * _.isFunction(_);
 * // => true
 *
 * _.isFunction(/abc/);
 * // => false
 */
function isFunction(value) {
  // The use of `Object#toString` avoids issues with the `typeof` operator
  // in older versions of Chrome and Safari which return 'function' for regexes
  // and Safari 8 which returns 'object' for typed array constructors.
  return isObject(value) && objToString.call(value) == funcTag;
}

module.exports = isFunction;

},{"./isObject":33}],32:[function(_dereq_,module,exports){
var isFunction = _dereq_('./isFunction'),
    isHostObject = _dereq_('../internal/isHostObject'),
    isObjectLike = _dereq_('../internal/isObjectLike');

/** Used to detect host constructors (Safari > 5). */
var reIsHostCtor = /^\[object .+?Constructor\]$/;

/** Used for native method references. */
var objectProto = Object.prototype;

/** Used to resolve the decompiled source of functions. */
var fnToString = Function.prototype.toString;

/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;

/** Used to detect if a method is native. */
var reIsNative = RegExp('^' +
  fnToString.call(hasOwnProperty).replace(/[\\^$.*+?()[\]{}|]/g, '\\$&')
  .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
);

/**
 * Checks if `value` is a native function.
 *
 * @static
 * @memberOf _
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a native function, else `false`.
 * @example
 *
 * _.isNative(Array.prototype.push);
 * // => true
 *
 * _.isNative(_);
 * // => false
 */
function isNative(value) {
  if (value == null) {
    return false;
  }
  if (isFunction(value)) {
    return reIsNative.test(fnToString.call(value));
  }
  return isObjectLike(value) && (isHostObject(value) ? reIsNative : reIsHostCtor).test(value);
}

module.exports = isNative;

},{"../internal/isHostObject":22,"../internal/isObjectLike":26,"./isFunction":31}],33:[function(_dereq_,module,exports){
/**
 * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`.
 * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
 *
 * @static
 * @memberOf _
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is an object, else `false`.
 * @example
 *
 * _.isObject({});
 * // => true
 *
 * _.isObject([1, 2, 3]);
 * // => true
 *
 * _.isObject(1);
 * // => false
 */
function isObject(value) {
  // Avoid a V8 JIT bug in Chrome 19-20.
  // See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
  var type = typeof value;
  return !!value && (type == 'object' || type == 'function');
}

module.exports = isObject;

},{}],34:[function(_dereq_,module,exports){
var baseForIn = _dereq_('../internal/baseForIn'),
    isArguments = _dereq_('./isArguments'),
    isHostObject = _dereq_('../internal/isHostObject'),
    isObjectLike = _dereq_('../internal/isObjectLike'),
    support = _dereq_('../support');

/** `Object#toString` result references. */
var objectTag = '[object Object]';

/** Used for native method references. */
var objectProto = Object.prototype;

/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;

/**
 * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
 * of values.
 */
var objToString = objectProto.toString;

/**
 * Checks if `value` is a plain object, that is, an object created by the
 * `Object` constructor or one with a `[[Prototype]]` of `null`.
 *
 * **Note:** This method assumes objects created by the `Object` constructor
 * have no inherited enumerable properties.
 *
 * @static
 * @memberOf _
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
 * @example
 *
 * function Foo() {
 *   this.a = 1;
 * }
 *
 * _.isPlainObject(new Foo);
 * // => false
 *
 * _.isPlainObject([1, 2, 3]);
 * // => false
 *
 * _.isPlainObject({ 'x': 0, 'y': 0 });
 * // => true
 *
 * _.isPlainObject(Object.create(null));
 * // => true
 */
function isPlainObject(value) {
  var Ctor;

  // Exit early for non `Object` objects.
  if (!(isObjectLike(value) && objToString.call(value) == objectTag && !isHostObject(value) && !isArguments(value)) ||
      (!hasOwnProperty.call(value, 'constructor') && (Ctor = value.constructor, typeof Ctor == 'function' && !(Ctor instanceof Ctor)))) {
    return false;
  }
  // IE < 9 iterates inherited properties before own properties. If the first
  // iterated property is an object's own property then there are no inherited
  // enumerable properties.
  var result;
  if (support.ownLast) {
    baseForIn(value, function(subValue, key, object) {
      result = hasOwnProperty.call(object, key);
      return false;
    });
    return result !== false;
  }
  // In most environments an object's own properties are iterated before
  // its inherited properties. If the last iterated property is an object's
  // own property then there are no inherited enumerable properties.
  baseForIn(value, function(subValue, key) {
    result = key;
  });
  return result === undefined || hasOwnProperty.call(value, result);
}

module.exports = isPlainObject;

},{"../internal/baseForIn":12,"../internal/isHostObject":22,"../internal/isObjectLike":26,"../support":41,"./isArguments":29}],35:[function(_dereq_,module,exports){
var isObjectLike = _dereq_('../internal/isObjectLike');

/** `Object#toString` result references. */
var stringTag = '[object String]';

/** Used for native method references. */
var objectProto = Object.prototype;

/**
 * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
 * of values.
 */
var objToString = objectProto.toString;

/**
 * Checks if `value` is classified as a `String` primitive or object.
 *
 * @static
 * @memberOf _
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
 * @example
 *
 * _.isString('abc');
 * // => true
 *
 * _.isString(1);
 * // => false
 */
function isString(value) {
  return typeof value == 'string' || (isObjectLike(value) && objToString.call(value) == stringTag);
}

module.exports = isString;

},{"../internal/isObjectLike":26}],36:[function(_dereq_,module,exports){
var isLength = _dereq_('../internal/isLength'),
    isObjectLike = _dereq_('../internal/isObjectLike');

/** `Object#toString` result references. */
var argsTag = '[object Arguments]',
    arrayTag = '[object Array]',
    boolTag = '[object Boolean]',
    dateTag = '[object Date]',
    errorTag = '[object Error]',
    funcTag = '[object Function]',
    mapTag = '[object Map]',
    numberTag = '[object Number]',
    objectTag = '[object Object]',
    regexpTag = '[object RegExp]',
    setTag = '[object Set]',
    stringTag = '[object String]',
    weakMapTag = '[object WeakMap]';

var arrayBufferTag = '[object ArrayBuffer]',
    float32Tag = '[object Float32Array]',
    float64Tag = '[object Float64Array]',
    int8Tag = '[object Int8Array]',
    int16Tag = '[object Int16Array]',
    int32Tag = '[object Int32Array]',
    uint8Tag = '[object Uint8Array]',
    uint8ClampedTag = '[object Uint8ClampedArray]',
    uint16Tag = '[object Uint16Array]',
    uint32Tag = '[object Uint32Array]';

/** Used to identify `toStringTag` values of typed arrays. */
var typedArrayTags = {};
typedArrayTags[float32Tag] = typedArrayTags[float64Tag] =
typedArrayTags[int8Tag] = typedArrayTags[int16Tag] =
typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] =
typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] =
typedArrayTags[uint32Tag] = true;
typedArrayTags[argsTag] = typedArrayTags[arrayTag] =
typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] =
typedArrayTags[dateTag] = typedArrayTags[errorTag] =
typedArrayTags[funcTag] = typedArrayTags[mapTag] =
typedArrayTags[numberTag] = typedArrayTags[objectTag] =
typedArrayTags[regexpTag] = typedArrayTags[setTag] =
typedArrayTags[stringTag] = typedArrayTags[weakMapTag] = false;

/** Used for native method references. */
var objectProto = Object.prototype;

/**
 * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
 * of values.
 */
var objToString = objectProto.toString;

/**
 * Checks if `value` is classified as a typed array.
 *
 * @static
 * @memberOf _
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
 * @example
 *
 * _.isTypedArray(new Uint8Array);
 * // => true
 *
 * _.isTypedArray([]);
 * // => false
 */
function isTypedArray(value) {
  return isObjectLike(value) && isLength(value.length) && !!typedArrayTags[objToString.call(value)];
}

module.exports = isTypedArray;

},{"../internal/isLength":25,"../internal/isObjectLike":26}],37:[function(_dereq_,module,exports){
var baseCopy = _dereq_('../internal/baseCopy'),
    keysIn = _dereq_('../object/keysIn');

/**
 * Converts `value` to a plain object flattening inherited enumerable
 * properties of `value` to own properties of the plain object.
 *
 * @static
 * @memberOf _
 * @category Lang
 * @param {*} value The value to convert.
 * @returns {Object} Returns the converted plain object.
 * @example
 *
 * function Foo() {
 *   this.b = 2;
 * }
 *
 * Foo.prototype.c = 3;
 *
 * _.assign({ 'a': 1 }, new Foo);
 * // => { 'a': 1, 'b': 2 }
 *
 * _.assign({ 'a': 1 }, _.toPlainObject(new Foo));
 * // => { 'a': 1, 'b': 2, 'c': 3 }
 */
function toPlainObject(value) {
  return baseCopy(value, keysIn(value));
}

module.exports = toPlainObject;

},{"../internal/baseCopy":10,"../object/keysIn":39}],38:[function(_dereq_,module,exports){
var getNative = _dereq_('../internal/getNative'),
    isArrayLike = _dereq_('../internal/isArrayLike'),
    isObject = _dereq_('../lang/isObject'),
    shimKeys = _dereq_('../internal/shimKeys'),
    support = _dereq_('../support');

/* Native method references for those with the same name as other `lodash` methods. */
var nativeKeys = getNative(Object, 'keys');

/**
 * Creates an array of the own enumerable property names of `object`.
 *
 * **Note:** Non-object values are coerced to objects. See the
 * [ES spec](http://ecma-international.org/ecma-262/6.0/#sec-object.keys)
 * for more details.
 *
 * @static
 * @memberOf _
 * @category Object
 * @param {Object} object The object to query.
 * @returns {Array} Returns the array of property names.
 * @example
 *
 * function Foo() {
 *   this.a = 1;
 *   this.b = 2;
 * }
 *
 * Foo.prototype.c = 3;
 *
 * _.keys(new Foo);
 * // => ['a', 'b'] (iteration order is not guaranteed)
 *
 * _.keys('hi');
 * // => ['0', '1']
 */
var keys = !nativeKeys ? shimKeys : function(object) {
  var Ctor = object == null ? undefined : object.constructor;
  if ((typeof Ctor == 'function' && Ctor.prototype === object) ||
      (typeof object == 'function' ? support.enumPrototypes : isArrayLike(object))) {
    return shimKeys(object);
  }
  return isObject(object) ? nativeKeys(object) : [];
};

module.exports = keys;

},{"../internal/getNative":20,"../internal/isArrayLike":21,"../internal/shimKeys":27,"../lang/isObject":33,"../support":41}],39:[function(_dereq_,module,exports){
var arrayEach = _dereq_('../internal/arrayEach'),
    isArguments = _dereq_('../lang/isArguments'),
    isArray = _dereq_('../lang/isArray'),
    isFunction = _dereq_('../lang/isFunction'),
    isIndex = _dereq_('../internal/isIndex'),
    isLength = _dereq_('../internal/isLength'),
    isObject = _dereq_('../lang/isObject'),
    isString = _dereq_('../lang/isString'),
    support = _dereq_('../support');

/** `Object#toString` result references. */
var arrayTag = '[object Array]',
    boolTag = '[object Boolean]',
    dateTag = '[object Date]',
    errorTag = '[object Error]',
    funcTag = '[object Function]',
    numberTag = '[object Number]',
    objectTag = '[object Object]',
    regexpTag = '[object RegExp]',
    stringTag = '[object String]';

/** Used to fix the JScript `[[DontEnum]]` bug. */
var shadowProps = [
  'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable',
  'toLocaleString', 'toString', 'valueOf'
];

/** Used for native method references. */
var errorProto = Error.prototype,
    objectProto = Object.prototype,
    stringProto = String.prototype;

/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;

/**
 * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
 * of values.
 */
var objToString = objectProto.toString;

/** Used to avoid iterating over non-enumerable properties in IE < 9. */
var nonEnumProps = {};
nonEnumProps[arrayTag] = nonEnumProps[dateTag] = nonEnumProps[numberTag] = { 'constructor': true, 'toLocaleString': true, 'toString': true, 'valueOf': true };
nonEnumProps[boolTag] = nonEnumProps[stringTag] = { 'constructor': true, 'toString': true, 'valueOf': true };
nonEnumProps[errorTag] = nonEnumProps[funcTag] = nonEnumProps[regexpTag] = { 'constructor': true, 'toString': true };
nonEnumProps[objectTag] = { 'constructor': true };

arrayEach(shadowProps, function(key) {
  for (var tag in nonEnumProps) {
    if (hasOwnProperty.call(nonEnumProps, tag)) {
      var props = nonEnumProps[tag];
      props[key] = hasOwnProperty.call(props, key);
    }
  }
});

/**
 * Creates an array of the own and inherited enumerable property names of `object`.
 *
 * **Note:** Non-object values are coerced to objects.
 *
 * @static
 * @memberOf _
 * @category Object
 * @param {Object} object The object to query.
 * @returns {Array} Returns the array of property names.
 * @example
 *
 * function Foo() {
 *   this.a = 1;
 *   this.b = 2;
 * }
 *
 * Foo.prototype.c = 3;
 *
 * _.keysIn(new Foo);
 * // => ['a', 'b', 'c'] (iteration order is not guaranteed)
 */
function keysIn(object) {
  if (object == null) {
    return [];
  }
  if (!isObject(object)) {
    object = Object(object);
  }
  var length = object.length;

  length = (length && isLength(length) &&
    (isArray(object) || isArguments(object) || isString(object)) && length) || 0;

  var Ctor = object.constructor,
      index = -1,
      proto = (isFunction(Ctor) && Ctor.prototype) || objectProto,
      isProto = proto === object,
      result = Array(length),
      skipIndexes = length > 0,
      skipErrorProps = support.enumErrorProps && (object === errorProto || object instanceof Error),
      skipProto = support.enumPrototypes && isFunction(object);

  while (++index < length) {
    result[index] = (index + '');
  }
  // lodash skips the `constructor` property when it infers it's iterating
  // over a `prototype` object because IE < 9 can't set the `[[Enumerable]]`
  // attribute of an existing property and the `constructor` property of a
  // prototype defaults to non-enumerable.
  for (var key in object) {
    if (!(skipProto && key == 'prototype') &&
        !(skipErrorProps && (key == 'message' || key == 'name')) &&
        !(skipIndexes && isIndex(key, length)) &&
        !(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) {
      result.push(key);
    }
  }
  if (support.nonEnumShadows && object !== objectProto) {
    var tag = object === stringProto ? stringTag : (object === errorProto ? errorTag : objToString.call(object)),
        nonEnums = nonEnumProps[tag] || nonEnumProps[objectTag];

    if (tag == objectTag) {
      proto = objectProto;
    }
    length = shadowProps.length;
    while (length--) {
      key = shadowProps[length];
      var nonEnum = nonEnums[key];
      if (!(isProto && nonEnum) &&
          (nonEnum ? hasOwnProperty.call(object, key) : object[key] !== proto[key])) {
        result.push(key);
      }
    }
  }
  return result;
}

module.exports = keysIn;

},{"../internal/arrayEach":9,"../internal/isIndex":23,"../internal/isLength":25,"../lang/isArguments":29,"../lang/isArray":30,"../lang/isFunction":31,"../lang/isObject":33,"../lang/isString":35,"../support":41}],40:[function(_dereq_,module,exports){
var baseMerge = _dereq_('../internal/baseMerge'),
    createAssigner = _dereq_('../internal/createAssigner');

/**
 * Recursively merges own enumerable properties of the source object(s), that
 * don't resolve to `undefined` into the destination object. Subsequent sources
 * overwrite property assignments of previous sources. If `customizer` is
 * provided it's invoked to produce the merged values of the destination and
 * source properties. If `customizer` returns `undefined` merging is handled
 * by the method instead. The `customizer` is bound to `thisArg` and invoked
 * with five arguments: (objectValue, sourceValue, key, object, source).
 *
 * @static
 * @memberOf _
 * @category Object
 * @param {Object} object The destination object.
 * @param {...Object} [sources] The source objects.
 * @param {Function} [customizer] The function to customize assigned values.
 * @param {*} [thisArg] The `this` binding of `customizer`.
 * @returns {Object} Returns `object`.
 * @example
 *
 * var users = {
 *   'data': [{ 'user': 'barney' }, { 'user': 'fred' }]
 * };
 *
 * var ages = {
 *   'data': [{ 'age': 36 }, { 'age': 40 }]
 * };
 *
 * _.merge(users, ages);
 * // => { 'data': [{ 'user': 'barney', 'age': 36 }, { 'user': 'fred', 'age': 40 }] }
 *
 * // using a customizer callback
 * var object = {
 *   'fruits': ['apple'],
 *   'vegetables': ['beet']
 * };
 *
 * var other = {
 *   'fruits': ['banana'],
 *   'vegetables': ['carrot']
 * };
 *
 * _.merge(object, other, function(a, b) {
 *   if (_.isArray(a)) {
 *     return a.concat(b);
 *   }
 * });
 * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot'] }
 */
var merge = createAssigner(baseMerge);

module.exports = merge;

},{"../internal/baseMerge":13,"../internal/createAssigner":17}],41:[function(_dereq_,module,exports){
/** Used for native method references. */
var arrayProto = Array.prototype,
    errorProto = Error.prototype,
    objectProto = Object.prototype;

/** Native method references. */
var propertyIsEnumerable = objectProto.propertyIsEnumerable,
    splice = arrayProto.splice;

/**
 * An object environment feature flags.
 *
 * @static
 * @memberOf _
 * @type Object
 */
var support = {};

(function(x) {
  var Ctor = function() { this.x = x; },
      object = { '0': x, 'length': x },
      props = [];

  Ctor.prototype = { 'valueOf': x, 'y': x };
  for (var key in new Ctor) { props.push(key); }

  /**
   * Detect if `name` or `message` properties of `Error.prototype` are
   * enumerable by default (IE < 9, Safari < 5.1).
   *
   * @memberOf _.support
   * @type boolean
   */
  support.enumErrorProps = propertyIsEnumerable.call(errorProto, 'message') ||
    propertyIsEnumerable.call(errorProto, 'name');

  /**
   * Detect if `prototype` properties are enumerable by default.
   *
   * Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1
   * (if the prototype or a property on the prototype has been set)
   * incorrectly set the `[[Enumerable]]` value of a function's `prototype`
   * property to `true`.
   *
   * @memberOf _.support
   * @type boolean
   */
  support.enumPrototypes = propertyIsEnumerable.call(Ctor, 'prototype');

  /**
   * Detect if properties shadowing those on `Object.prototype` are non-enumerable.
   *
   * In IE < 9 an object's own properties, shadowing non-enumerable ones,
   * are made non-enumerable as well (a.k.a the JScript `[[DontEnum]]` bug).
   *
   * @memberOf _.support
   * @type boolean
   */
  support.nonEnumShadows = !/valueOf/.test(props);

  /**
   * Detect if own properties are iterated after inherited properties (IE < 9).
   *
   * @memberOf _.support
   * @type boolean
   */
  support.ownLast = props[0] != 'x';

  /**
   * Detect if `Array#shift` and `Array#splice` augment array-like objects
   * correctly.
   *
   * Firefox < 10, compatibility modes of IE 8, and IE < 9 have buggy Array
   * `shift()` and `splice()` functions that fail to remove the last element,
   * `value[0]`, of array-like objects even though the "length" property is
   * set to `0`. The `shift()` method is buggy in compatibility modes of IE 8,
   * while `splice()` is buggy regardless of mode in IE < 9.
   *
   * @memberOf _.support
   * @type boolean
   */
  support.spliceObjects = (splice.call(object, 0, 1), !object[0]);

  /**
   * Detect lack of support for accessing string characters by index.
   *
   * IE < 8 can't access characters by index. IE 8 can only access characters
   * by index on string literals, not string objects.
   *
   * @memberOf _.support
   * @type boolean
   */
  support.unindexedChars = ('x'[0] + Object('x')[0]) != 'xx';
}(1, 0));

module.exports = support;

},{}],42:[function(_dereq_,module,exports){
/**
 * This method returns the first argument provided to it.
 *
 * @static
 * @memberOf _
 * @category Utility
 * @param {*} value Any value.
 * @returns {*} Returns `value`.
 * @example
 *
 * var object = { 'user': 'fred' };
 *
 * _.identity(object) === object;
 * // => true
 */
function identity(value) {
  return value;
}

module.exports = identity;

},{}],43:[function(_dereq_,module,exports){
'use strict';

var keys = _dereq_('object-keys');

module.exports = function hasSymbols() {
	if (typeof Symbol !== 'function' || typeof Object.getOwnPropertySymbols !== 'function') { return false; }
	if (typeof Symbol.iterator === 'symbol') { return true; }

	var obj = {};
	var sym = Symbol('test');
	if (typeof sym === 'string') { return false; }

	// temp disabled per https://github.com/ljharb/object.assign/issues/17
	// if (sym instanceof Symbol) { return false; }
	// temp disabled per https://github.com/WebReflection/get-own-property-symbols/issues/4
	// if (!(Object(sym) instanceof Symbol)) { return false; }

	var symVal = 42;
	obj[sym] = symVal;
	for (sym in obj) { return false; }
	if (keys(obj).length !== 0) { return false; }
	if (typeof Object.keys === 'function' && Object.keys(obj).length !== 0) { return false; }

	if (typeof Object.getOwnPropertyNames === 'function' && Object.getOwnPropertyNames(obj).length !== 0) { return false; }

	var syms = Object.getOwnPropertySymbols(obj);
	if (syms.length !== 1 || syms[0] !== sym) { return false; }

	if (!Object.prototype.propertyIsEnumerable.call(obj, sym)) { return false; }

	if (typeof Object.getOwnPropertyDescriptor === 'function') {
		var descriptor = Object.getOwnPropertyDescriptor(obj, sym);
		if (descriptor.value !== symVal || descriptor.enumerable !== true) { return false; }
	}

	return true;
};

},{"object-keys":49}],44:[function(_dereq_,module,exports){
'use strict';

// modified from https://github.com/es-shims/es6-shim
var keys = _dereq_('object-keys');
var bind = _dereq_('function-bind');
var canBeObject = function (obj) {
	return typeof obj !== 'undefined' && obj !== null;
};
var hasSymbols = _dereq_('./hasSymbols')();
var toObject = Object;
var push = bind.call(Function.call, Array.prototype.push);
var propIsEnumerable = bind.call(Function.call, Object.prototype.propertyIsEnumerable);

module.exports = function assign(target, source1) {
	if (!canBeObject(target)) { throw new TypeError('target must be an object'); }
	var objTarget = toObject(target);
	var s, source, i, props, syms, value, key;
	for (s = 1; s < arguments.length; ++s) {
		source = toObject(arguments[s]);
		props = keys(source);
		if (hasSymbols && Object.getOwnPropertySymbols) {
			syms = Object.getOwnPropertySymbols(source);
			for (i = 0; i < syms.length; ++i) {
				key = syms[i];
				if (propIsEnumerable(source, key)) {
					push(props, key);
				}
			}
		}
		for (i = 0; i < props.length; ++i) {
			key = props[i];
			value = source[key];
			if (propIsEnumerable(source, key)) {
				objTarget[key] = value;
			}
		}
	}
	return objTarget;
};

},{"./hasSymbols":43,"function-bind":48,"object-keys":49}],45:[function(_dereq_,module,exports){
'use strict';

var defineProperties = _dereq_('define-properties');

var implementation = _dereq_('./implementation');
var getPolyfill = _dereq_('./polyfill');
var shim = _dereq_('./shim');

defineProperties(implementation, {
	implementation: implementation,
	getPolyfill: getPolyfill,
	shim: shim
});

module.exports = implementation;

},{"./implementation":44,"./polyfill":51,"./shim":52,"define-properties":46}],46:[function(_dereq_,module,exports){
'use strict';

var keys = _dereq_('object-keys');
var foreach = _dereq_('foreach');
var hasSymbols = typeof Symbol === 'function' && typeof Symbol() === 'symbol';

var toStr = Object.prototype.toString;

var isFunction = function (fn) {
	return typeof fn === 'function' && toStr.call(fn) === '[object Function]';
};

var arePropertyDescriptorsSupported = function () {
	var obj = {};
	try {
		Object.defineProperty(obj, 'x', { enumerable: false, value: obj });
        /* eslint-disable no-unused-vars, no-restricted-syntax */
        for (var _ in obj) { return false; }
        /* eslint-enable no-unused-vars, no-restricted-syntax */
		return obj.x === obj;
	} catch (e) { /* this is IE 8. */
		return false;
	}
};
var supportsDescriptors = Object.defineProperty && arePropertyDescriptorsSupported();

var defineProperty = function (object, name, value, predicate) {
	if (name in object && (!isFunction(predicate) || !predicate())) {
		return;
	}
	if (supportsDescriptors) {
		Object.defineProperty(object, name, {
			configurable: true,
			enumerable: false,
			value: value,
			writable: true
		});
	} else {
		object[name] = value;
	}
};

var defineProperties = function (object, map) {
	var predicates = arguments.length > 2 ? arguments[2] : {};
	var props = keys(map);
	if (hasSymbols) {
		props = props.concat(Object.getOwnPropertySymbols(map));
	}
	foreach(props, function (name) {
		defineProperty(object, name, map[name], predicates[name]);
	});
};

defineProperties.supportsDescriptors = !!supportsDescriptors;

module.exports = defineProperties;

},{"foreach":47,"object-keys":49}],47:[function(_dereq_,module,exports){

var hasOwn = Object.prototype.hasOwnProperty;
var toString = Object.prototype.toString;

module.exports = function forEach (obj, fn, ctx) {
    if (toString.call(fn) !== '[object Function]') {
        throw new TypeError('iterator must be a function');
    }
    var l = obj.length;
    if (l === +l) {
        for (var i = 0; i < l; i++) {
            fn.call(ctx, obj[i], i, obj);
        }
    } else {
        for (var k in obj) {
            if (hasOwn.call(obj, k)) {
                fn.call(ctx, obj[k], k, obj);
            }
        }
    }
};


},{}],48:[function(_dereq_,module,exports){
var ERROR_MESSAGE = 'Function.prototype.bind called on incompatible ';
var slice = Array.prototype.slice;
var toStr = Object.prototype.toString;
var funcType = '[object Function]';

module.exports = function bind(that) {
    var target = this;
    if (typeof target !== 'function' || toStr.call(target) !== funcType) {
        throw new TypeError(ERROR_MESSAGE + target);
    }
    var args = slice.call(arguments, 1);

    var binder = function () {
        if (this instanceof bound) {
            var result = target.apply(
                this,
                args.concat(slice.call(arguments))
            );
            if (Object(result) === result) {
                return result;
            }
            return this;
        } else {
            return target.apply(
                that,
                args.concat(slice.call(arguments))
            );
        }
    };

    var boundLength = Math.max(0, target.length - args.length);
    var boundArgs = [];
    for (var i = 0; i < boundLength; i++) {
        boundArgs.push('$' + i);
    }

    // var bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this,arguments); }')(binder);

    // hack for inline new function
    var bound = function(){
      return binder.apply(this, arguments);
    };

    if (target.prototype) {
        var Empty = function Empty() {};
        Empty.prototype = target.prototype;
        bound.prototype = new Empty();
        Empty.prototype = null;
    }

    return bound;
};


},{}],49:[function(_dereq_,module,exports){
'use strict';

// modified from https://github.com/es-shims/es5-shim
var has = Object.prototype.hasOwnProperty;
var toStr = Object.prototype.toString;
var slice = Array.prototype.slice;
var isArgs = _dereq_('./isArguments');
var hasDontEnumBug = !({ toString: null }).propertyIsEnumerable('toString');
var hasProtoEnumBug = function () {}.propertyIsEnumerable('prototype');
var dontEnums = [
	'toString',
	'toLocaleString',
	'valueOf',
	'hasOwnProperty',
	'isPrototypeOf',
	'propertyIsEnumerable',
	'constructor'
];
var equalsConstructorPrototype = function (o) {
	var ctor = o.constructor;
	return ctor && ctor.prototype === o;
};
var blacklistedKeys = {
	$console: true,
	$frame: true,
	$frameElement: true,
	$frames: true,
	$parent: true,
	$self: true,
	$webkitIndexedDB: true,
	$webkitStorageInfo: true,
	$window: true
};
var hasAutomationEqualityBug = (function () {
	/* global window */
	if (typeof window === 'undefined') { return false; }
	for (var k in window) {
		try {
			if (!blacklistedKeys['$' + k] && has.call(window, k) && window[k] !== null && typeof window[k] === 'object') {
				try {
					equalsConstructorPrototype(window[k]);
				} catch (e) {
					return true;
				}
			}
		} catch (e) {
			return true;
		}
	}
	return false;
}());
var equalsConstructorPrototypeIfNotBuggy = function (o) {
	/* global window */
	if (typeof window === 'undefined' || !hasAutomationEqualityBug) {
		return equalsConstructorPrototype(o);
	}
	try {
		return equalsConstructorPrototype(o);
	} catch (e) {
		return false;
	}
};

var keysShim = function keys(object) {
	var isObject = object !== null && typeof object === 'object';
	var isFunction = toStr.call(object) === '[object Function]';
	var isArguments = isArgs(object);
	var isString = isObject && toStr.call(object) === '[object String]';
	var theKeys = [];

	if (!isObject && !isFunction && !isArguments) {
		throw new TypeError('Object.keys called on a non-object');
	}

	var skipProto = hasProtoEnumBug && isFunction;
	if (isString && object.length > 0 && !has.call(object, 0)) {
		for (var i = 0; i < object.length; ++i) {
			theKeys.push(String(i));
		}
	}

	if (isArguments && object.length > 0) {
		for (var j = 0; j < object.length; ++j) {
			theKeys.push(String(j));
		}
	} else {
		for (var name in object) {
			if (!(skipProto && name === 'prototype') && has.call(object, name)) {
				theKeys.push(String(name));
			}
		}
	}

	if (hasDontEnumBug) {
		var skipConstructor = equalsConstructorPrototypeIfNotBuggy(object);

		for (var k = 0; k < dontEnums.length; ++k) {
			if (!(skipConstructor && dontEnums[k] === 'constructor') && has.call(object, dontEnums[k])) {
				theKeys.push(dontEnums[k]);
			}
		}
	}
	return theKeys;
};

keysShim.shim = function shimObjectKeys() {
	if (Object.keys) {
		var keysWorksWithArguments = (function () {
			// Safari 5.0 bug
			return (Object.keys(arguments) || '').length === 2;
		}(1, 2));
		if (!keysWorksWithArguments) {
			var originalKeys = Object.keys;
			Object.keys = function keys(object) {
				if (isArgs(object)) {
					return originalKeys(slice.call(object));
				} else {
					return originalKeys(object);
				}
			};
		}
	} else {
		Object.keys = keysShim;
	}
	return Object.keys || keysShim;
};

module.exports = keysShim;

},{"./isArguments":50}],50:[function(_dereq_,module,exports){
'use strict';

var toStr = Object.prototype.toString;

module.exports = function isArguments(value) {
	var str = toStr.call(value);
	var isArgs = str === '[object Arguments]';
	if (!isArgs) {
		isArgs = str !== '[object Array]' &&
			value !== null &&
			typeof value === 'object' &&
			typeof value.length === 'number' &&
			value.length >= 0 &&
			toStr.call(value.callee) === '[object Function]';
	}
	return isArgs;
};

},{}],51:[function(_dereq_,module,exports){
'use strict';

var implementation = _dereq_('./implementation');

var lacksProperEnumerationOrder = function () {
	if (!Object.assign) {
		return false;
	}
	// v8, specifically in node 4.x, has a bug with incorrect property enumeration order
	// note: this does not detect the bug unless there's 20 characters
	var str = 'abcdefghijklmnopqrst';
	var letters = str.split('');
	var map = {};
	for (var i = 0; i < letters.length; ++i) {
		map[letters[i]] = letters[i];
	}
	var obj = Object.assign({}, map);
	var actual = '';
	for (var k in obj) {
		actual += k;
	}
	return str !== actual;
};

var assignHasPendingExceptions = function () {
	if (!Object.assign || !Object.preventExtensions) {
		return false;
	}
	// Firefox 37 still has "pending exception" logic in its Object.assign implementation,
	// which is 72% slower than our shim, and Firefox 40's native implementation.
	var thrower = Object.preventExtensions({ 1: 2 });
	try {
		Object.assign(thrower, 'xy');
	} catch (e) {
		return thrower[1] === 'y';
	}
};

module.exports = function getPolyfill() {
	if (!Object.assign) {
		return implementation;
	}
	if (lacksProperEnumerationOrder()) {
		return implementation;
	}
	if (assignHasPendingExceptions()) {
		return implementation;
	}
	return Object.assign;
};

},{"./implementation":44}],52:[function(_dereq_,module,exports){
'use strict';

var define = _dereq_('define-properties');
var getPolyfill = _dereq_('./polyfill');

module.exports = function shimAssign() {
	var polyfill = getPolyfill();
	define(
		Object,
		{ assign: polyfill },
		{ assign: function () { return Object.assign !== polyfill; } }
	);
	return polyfill;
};

},{"./polyfill":51,"define-properties":46}],53:[function(_dereq_,module,exports){
module.exports = SafeParseTuple

function SafeParseTuple(obj, reviver) {
    var json
    var error = null

    try {
        json = JSON.parse(obj, reviver)
    } catch (err) {
        error = err
    }

    return [error, json]
}

},{}],54:[function(_dereq_,module,exports){
function clean (s) {
  return s.replace(/\n\r?\s*/g, '')
}


module.exports = function tsml (sa) {
  var s = ''
    , i = 0

  for (; i < arguments.length; i++)
    s += clean(sa[i]) + (arguments[i + 1] || '')

  return s
}
},{}],55:[function(_dereq_,module,exports){
"use strict";
var window = _dereq_("global/window")
var once = _dereq_("once")
var isFunction = _dereq_("is-function")
var parseHeaders = _dereq_("parse-headers")
var xtend = _dereq_("xtend")

module.exports = createXHR
createXHR.XMLHttpRequest = window.XMLHttpRequest || noop
createXHR.XDomainRequest = "withCredentials" in (new createXHR.XMLHttpRequest()) ? createXHR.XMLHttpRequest : window.XDomainRequest

forEachArray(["get", "put", "post", "patch", "head", "delete"], function(method) {
    createXHR[method === "delete" ? "del" : method] = function(uri, options, callback) {
        options = initParams(uri, options, callback)
        options.method = method.toUpperCase()
        return _createXHR(options)
    }
})

function forEachArray(array, iterator) {
    for (var i = 0; i < array.length; i++) {
        iterator(array[i])
    }
}

function isEmpty(obj){
    for(var i in obj){
        if(obj.hasOwnProperty(i)) return false
    }
    return true
}

function initParams(uri, options, callback) {
    var params = uri

    if (isFunction(options)) {
        callback = options
        if (typeof uri === "string") {
            params = {uri:uri}
        }
    } else {
        params = xtend(options, {uri: uri})
    }

    params.callback = callback
    return params
}

function createXHR(uri, options, callback) {
    options = initParams(uri, options, callback)
    return _createXHR(options)
}

function _createXHR(options) {
    var callback = options.callback
    if(typeof callback === "undefined"){
        throw new Error("callback argument missing")
    }
    callback = once(callback)

    function readystatechange() {
        if (xhr.readyState === 4) {
            loadFunc()
        }
    }

    function getBody() {
        // Chrome with requestType=blob throws errors arround when even testing access to responseText
        var body = undefined

        if (xhr.response) {
            body = xhr.response
        } else if (xhr.responseType === "text" || !xhr.responseType) {
            body = xhr.responseText || xhr.responseXML
        }

        if (isJson) {
            try {
                body = JSON.parse(body)
            } catch (e) {}
        }

        return body
    }

    var failureResponse = {
                body: undefined,
                headers: {},
                statusCode: 0,
                method: method,
                url: uri,
                rawRequest: xhr
            }

    function errorFunc(evt) {
        clearTimeout(timeoutTimer)
        if(!(evt instanceof Error)){
            evt = new Error("" + (evt || "Unknown XMLHttpRequest Error") )
        }
        evt.statusCode = 0
        callback(evt, failureResponse)
    }

    // will load the data & process the response in a special response object
    function loadFunc() {
        if (aborted) return
        var status
        clearTimeout(timeoutTimer)
        if(options.useXDR && xhr.status===undefined) {
            //IE8 CORS GET successful response doesn't have a status field, but body is fine
            status = 200
        } else {
            status = (xhr.status === 1223 ? 204 : xhr.status)
        }
        var response = failureResponse
        var err = null

        if (status !== 0){
            response = {
                body: getBody(),
                statusCode: status,
                method: method,
                headers: {},
                url: uri,
                rawRequest: xhr
            }
            if(xhr.getAllResponseHeaders){ //remember xhr can in fact be XDR for CORS in IE
                response.headers = parseHeaders(xhr.getAllResponseHeaders())
            }
        } else {
            err = new Error("Internal XMLHttpRequest Error")
        }
        callback(err, response, response.body)

    }

    var xhr = options.xhr || null

    if (!xhr) {
        if (options.cors || options.useXDR) {
            xhr = new createXHR.XDomainRequest()
        }else{
            xhr = new createXHR.XMLHttpRequest()
        }
    }

    var key
    var aborted
    var uri = xhr.url = options.uri || options.url
    var method = xhr.method = options.method || "GET"
    var body = options.body || options.data || null
    var headers = xhr.headers = options.headers || {}
    var sync = !!options.sync
    var isJson = false
    var timeoutTimer

    if ("json" in options) {
        isJson = true
        headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json") //Don't override existing accept header declared by user
        if (method !== "GET" && method !== "HEAD") {
            headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json") //Don't override existing accept header declared by user
            body = JSON.stringify(options.json)
        }
    }

    xhr.onreadystatechange = readystatechange
    xhr.onload = loadFunc
    xhr.onerror = errorFunc
    // IE9 must have onprogress be set to a unique function.
    xhr.onprogress = function () {
        // IE must die
    }
    xhr.ontimeout = errorFunc
    xhr.open(method, uri, !sync, options.username, options.password)
    //has to be after open
    if(!sync) {
        xhr.withCredentials = !!options.withCredentials
    }
    // Cannot set timeout with sync request
    // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly
    // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent
    if (!sync && options.timeout > 0 ) {
        timeoutTimer = setTimeout(function(){
            aborted=true//IE9 may still call readystatechange
            xhr.abort("timeout")
            var e = new Error("XMLHttpRequest timeout")
            e.code = "ETIMEDOUT"
            errorFunc(e)
        }, options.timeout )
    }

    if (xhr.setRequestHeader) {
        for(key in headers){
            if(headers.hasOwnProperty(key)){
                xhr.setRequestHeader(key, headers[key])
            }
        }
    } else if (options.headers && !isEmpty(options.headers)) {
        throw new Error("Headers cannot be set on an XDomainRequest object")
    }

    if ("responseType" in options) {
        xhr.responseType = options.responseType
    }

    if ("beforeSend" in options &&
        typeof options.beforeSend === "function"
    ) {
        options.beforeSend(xhr)
    }

    xhr.send(body)

    return xhr


}

function noop() {}

},{"global/window":2,"is-function":56,"once":57,"parse-headers":60,"xtend":61}],56:[function(_dereq_,module,exports){
module.exports = isFunction

var toString = Object.prototype.toString

function isFunction (fn) {
  var string = toString.call(fn)
  return string === '[object Function]' ||
    (typeof fn === 'function' && string !== '[object RegExp]') ||
    (typeof window !== 'undefined' &&
     // IE8 and below
     (fn === window.setTimeout ||
      fn === window.alert ||
      fn === window.confirm ||
      fn === window.prompt))
};

},{}],57:[function(_dereq_,module,exports){
module.exports = once

once.proto = once(function () {
  Object.defineProperty(Function.prototype, 'once', {
    value: function () {
      return once(this)
    },
    configurable: true
  })
})

function once (fn) {
  var called = false
  return function () {
    if (called) return
    called = true
    return fn.apply(this, arguments)
  }
}

},{}],58:[function(_dereq_,module,exports){
var isFunction = _dereq_('is-function')

module.exports = forEach

var toString = Object.prototype.toString
var hasOwnProperty = Object.prototype.hasOwnProperty

function forEach(list, iterator, context) {
    if (!isFunction(iterator)) {
        throw new TypeError('iterator must be a function')
    }

    if (arguments.length < 3) {
        context = this
    }
    
    if (toString.call(list) === '[object Array]')
        forEachArray(list, iterator, context)
    else if (typeof list === 'string')
        forEachString(list, iterator, context)
    else
        forEachObject(list, iterator, context)
}

function forEachArray(array, iterator, context) {
    for (var i = 0, len = array.length; i < len; i++) {
        if (hasOwnProperty.call(array, i)) {
            iterator.call(context, array[i], i, array)
        }
    }
}

function forEachString(string, iterator, context) {
    for (var i = 0, len = string.length; i < len; i++) {
        // no such thing as a sparse string.
        iterator.call(context, string.charAt(i), i, string)
    }
}

function forEachObject(object, iterator, context) {
    for (var k in object) {
        if (hasOwnProperty.call(object, k)) {
            iterator.call(context, object[k], k, object)
        }
    }
}

},{"is-function":56}],59:[function(_dereq_,module,exports){

exports = module.exports = trim;

function trim(str){
  return str.replace(/^\s*|\s*$/g, '');
}

exports.left = function(str){
  return str.replace(/^\s*/, '');
};

exports.right = function(str){
  return str.replace(/\s*$/, '');
};

},{}],60:[function(_dereq_,module,exports){
var trim = _dereq_('trim')
  , forEach = _dereq_('for-each')
  , isArray = function(arg) {
      return Object.prototype.toString.call(arg) === '[object Array]';
    }

module.exports = function (headers) {
  if (!headers)
    return {}

  var result = {}

  forEach(
      trim(headers).split('\n')
    , function (row) {
        var index = row.indexOf(':')
          , key = trim(row.slice(0, index)).toLowerCase()
          , value = trim(row.slice(index + 1))

        if (typeof(result[key]) === 'undefined') {
          result[key] = value
        } else if (isArray(result[key])) {
          result[key].push(value)
        } else {
          result[key] = [ result[key], value ]
        }
      }
  )

  return result
}
},{"for-each":58,"trim":59}],61:[function(_dereq_,module,exports){
module.exports = extend

var hasOwnProperty = Object.prototype.hasOwnProperty;

function extend() {
    var target = {}

    for (var i = 0; i < arguments.length; i++) {
        var source = arguments[i]

        for (var key in source) {
            if (hasOwnProperty.call(source, key)) {
                target[key] = source[key]
            }
        }
    }

    return target
}

},{}],62:[function(_dereq_,module,exports){
/**
 * @file big-play-button.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _buttonJs = _dereq_('./button.js');

var _buttonJs2 = _interopRequireDefault(_buttonJs);

var _componentJs = _dereq_('./component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

/**
 * Initial play button. Shows before the video has played. The hiding of the
 * big play button is done via CSS and player states.
 *
 * @param {Object} player  Main Player
 * @param {Object=} options Object of option names and values
 * @extends Button
 * @class BigPlayButton
 */

var BigPlayButton = (function (_Button) {
  _inherits(BigPlayButton, _Button);

  function BigPlayButton(player, options) {
    _classCallCheck(this, BigPlayButton);

    _Button.call(this, player, options);
  }

  /**
   * Allow sub components to stack CSS class names
   *
   * @return {String} The constructed class name
   * @method buildCSSClass
   */

  BigPlayButton.prototype.buildCSSClass = function buildCSSClass() {
    return 'vjs-big-play-button';
  };

  /**
   * Handles click for play
   *
   * @method handleClick
   */

  BigPlayButton.prototype.handleClick = function handleClick() {
    this.player_.play();
  };

  return BigPlayButton;
})(_buttonJs2['default']);

BigPlayButton.prototype.controlText_ = 'Play Video';

_componentJs2['default'].registerComponent('BigPlayButton', BigPlayButton);
exports['default'] = BigPlayButton;
module.exports = exports['default'];

},{"./button.js":63,"./component.js":65}],63:[function(_dereq_,module,exports){
/**
 * @file button.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _component = _dereq_('./component');

var _component2 = _interopRequireDefault(_component);

var _utilsDomJs = _dereq_('./utils/dom.js');

var Dom = _interopRequireWildcard(_utilsDomJs);

var _utilsEventsJs = _dereq_('./utils/events.js');

var Events = _interopRequireWildcard(_utilsEventsJs);

var _utilsFnJs = _dereq_('./utils/fn.js');

var Fn = _interopRequireWildcard(_utilsFnJs);

var _globalDocument = _dereq_('global/document');

var _globalDocument2 = _interopRequireDefault(_globalDocument);

var _objectAssign = _dereq_('object.assign');

var _objectAssign2 = _interopRequireDefault(_objectAssign);

/**
 * Base class for all buttons
 *
 * @param {Object} player  Main Player
 * @param {Object=} options Object of option names and values
 * @extends Component
 * @class Button
 */

var Button = (function (_Component) {
  _inherits(Button, _Component);

  function Button(player, options) {
    _classCallCheck(this, Button);

    _Component.call(this, player, options);

    this.emitTapEvents();

    this.on('tap', this.handleClick);
    this.on('click', this.handleClick);
    this.on('focus', this.handleFocus);
    this.on('blur', this.handleBlur);
  }

  /**
   * Create the component's DOM element
   *
   * @param {String=} type Element's node type. e.g. 'div'
   * @param {Object=} props An object of element attributes that should be set on the element Tag name
   * @return {Element}
   * @method createEl
   */

  Button.prototype.createEl = function createEl() {
    var tag = arguments.length <= 0 || arguments[0] === undefined ? 'button' : arguments[0];
    var props = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
    var attributes = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];

    props = _objectAssign2['default']({
      className: this.buildCSSClass(),
      tabIndex: 0
    }, props);

    // Add standard Aria info
    attributes = _objectAssign2['default']({
      role: 'button',
      type: 'button', // Necessary since the default button type is "submit"
      'aria-live': 'polite' // let the screen reader user know that the text of the button may change
    }, attributes);

    var el = _Component.prototype.createEl.call(this, tag, props, attributes);

    this.controlTextEl_ = Dom.createEl('span', {
      className: 'vjs-control-text'
    });

    el.appendChild(this.controlTextEl_);

    this.controlText(this.controlText_);

    return el;
  };

  /**
   * Controls text - both request and localize
   *
   * @param {String} text Text for button
   * @return {String}
   * @method controlText
   */

  Button.prototype.controlText = function controlText(text) {
    if (!text) return this.controlText_ || 'Need Text';

    this.controlText_ = text;
    this.controlTextEl_.innerHTML = this.localize(this.controlText_);

    return this;
  };

  /**
   * Allows sub components to stack CSS class names
   *
   * @return {String}
   * @method buildCSSClass
   */

  Button.prototype.buildCSSClass = function buildCSSClass() {
    return 'vjs-control vjs-button ' + _Component.prototype.buildCSSClass.call(this);
  };

  /**
   * Handle Click - Override with specific functionality for button
   *
   * @method handleClick
   */

  Button.prototype.handleClick = function handleClick() {};

  /**
   * Handle Focus - Add keyboard functionality to element
   *
   * @method handleFocus
   */

  Button.prototype.handleFocus = function handleFocus() {
    Events.on(_globalDocument2['default'], 'keydown', Fn.bind(this, this.handleKeyPress));
  };

  /**
   * Handle KeyPress (document level) - Trigger click when keys are pressed
   *
   * @method handleKeyPress
   */

  Button.prototype.handleKeyPress = function handleKeyPress(event) {
    // Check for space bar (32) or enter (13) keys
    if (event.which === 32 || event.which === 13) {
      event.preventDefault();
      this.handleClick(event);
    }
  };

  /**
   * Handle Blur - Remove keyboard triggers
   *
   * @method handleBlur
   */

  Button.prototype.handleBlur = function handleBlur() {
    Events.off(_globalDocument2['default'], 'keydown', Fn.bind(this, this.handleKeyPress));
  };

  return Button;
})(_component2['default']);

_component2['default'].registerComponent('Button', Button);
exports['default'] = Button;
module.exports = exports['default'];

},{"./component":65,"./utils/dom.js":128,"./utils/events.js":129,"./utils/fn.js":130,"global/document":1,"object.assign":45}],64:[function(_dereq_,module,exports){
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _button = _dereq_('./button');

var _button2 = _interopRequireDefault(_button);

var _component = _dereq_('./component');

var _component2 = _interopRequireDefault(_component);

/**
 * The `CloseButton` component is a button which fires a "close" event
 * when it is activated.
 *
 * @extends Button
 * @class CloseButton
 */

var CloseButton = (function (_Button) {
  _inherits(CloseButton, _Button);

  function CloseButton(player, options) {
    _classCallCheck(this, CloseButton);

    _Button.call(this, player, options);
    this.controlText(options && options.controlText || this.localize('Close'));
  }

  CloseButton.prototype.buildCSSClass = function buildCSSClass() {
    return 'vjs-close-button ' + _Button.prototype.buildCSSClass.call(this);
  };

  CloseButton.prototype.handleClick = function handleClick() {
    this.trigger({ type: 'close', bubbles: false });
  };

  return CloseButton;
})(_button2['default']);

_component2['default'].registerComponent('CloseButton', CloseButton);
exports['default'] = CloseButton;
module.exports = exports['default'];

},{"./button":63,"./component":65}],65:[function(_dereq_,module,exports){
/**
 * @file component.js
 *
 * Player Component - Base class for all UI objects
 */

'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

var _globalWindow = _dereq_('global/window');

var _globalWindow2 = _interopRequireDefault(_globalWindow);

var _utilsDomJs = _dereq_('./utils/dom.js');

var Dom = _interopRequireWildcard(_utilsDomJs);

var _utilsFnJs = _dereq_('./utils/fn.js');

var Fn = _interopRequireWildcard(_utilsFnJs);

var _utilsGuidJs = _dereq_('./utils/guid.js');

var Guid = _interopRequireWildcard(_utilsGuidJs);

var _utilsEventsJs = _dereq_('./utils/events.js');

var Events = _interopRequireWildcard(_utilsEventsJs);

var _utilsLogJs = _dereq_('./utils/log.js');

var _utilsLogJs2 = _interopRequireDefault(_utilsLogJs);

var _utilsToTitleCaseJs = _dereq_('./utils/to-title-case.js');

var _utilsToTitleCaseJs2 = _interopRequireDefault(_utilsToTitleCaseJs);

var _objectAssign = _dereq_('object.assign');

var _objectAssign2 = _interopRequireDefault(_objectAssign);

var _utilsMergeOptionsJs = _dereq_('./utils/merge-options.js');

var _utilsMergeOptionsJs2 = _interopRequireDefault(_utilsMergeOptionsJs);

/**
 * Base UI Component class
 * Components are embeddable UI objects that are represented by both a
 * javascript object and an element in the DOM. They can be children of other
 * components, and can have many children themselves.
 * ```js
 *     // adding a button to the player
 *     var button = player.addChild('button');
 *     button.el(); // -> button element
 * ```
 * ```html
 *     <div class="video-js">
 *       <div class="vjs-button">Button</div>
 *     </div>
 * ```
 * Components are also event targets.
 * ```js
 *     button.on('click', function(){
 *       console.log('Button Clicked!');
 *     });
 *     button.trigger('customevent');
 * ```
 *
 * @param {Object} player  Main Player
 * @param {Object=} options Object of option names and values
 * @param {Function=} ready    Ready callback function
 * @class Component
 */

var Component = (function () {
  function Component(player, options, ready) {
    _classCallCheck(this, Component);

    // The component might be the player itself and we can't pass `this` to super
    if (!player && this.play) {
      this.player_ = player = this; // eslint-disable-line
    } else {
        this.player_ = player;
      }

    // Make a copy of prototype.options_ to protect against overriding defaults
    this.options_ = _utilsMergeOptionsJs2['default']({}, this.options_);

    // Updated options with supplied options
    options = this.options_ = _utilsMergeOptionsJs2['default'](this.options_, options);

    // Get ID from options or options element if one is supplied
    this.id_ = options.id || options.el && options.el.id;

    // If there was no ID from the options, generate one
    if (!this.id_) {
      // Don't require the player ID function in the case of mock players
      var id = player && player.id && player.id() || 'no_player';

      this.id_ = id + '_component_' + Guid.newGUID();
    }

    this.name_ = options.name || null;

    // Create element if one wasn't provided in options
    if (options.el) {
      this.el_ = options.el;
    } else if (options.createEl !== false) {
      this.el_ = this.createEl();
    }

    this.children_ = [];
    this.childIndex_ = {};
    this.childNameIndex_ = {};

    // Add any child components in options
    if (options.initChildren !== false) {
      this.initChildren();
    }

    this.ready(ready);
    // Don't want to trigger ready here or it will before init is actually
    // finished for all children that run this constructor

    if (options.reportTouchActivity !== false) {
      this.enableTouchActivity();
    }
  }

  /**
   * Dispose of the component and all child components
   *
   * @method dispose
   */

  Component.prototype.dispose = function dispose() {
    this.trigger({ type: 'dispose', bubbles: false });

    // Dispose all children.
    if (this.children_) {
      for (var i = this.children_.length - 1; i >= 0; i--) {
        if (this.children_[i].dispose) {
          this.children_[i].dispose();
        }
      }
    }

    // Delete child references
    this.children_ = null;
    this.childIndex_ = null;
    this.childNameIndex_ = null;

    // Remove all event listeners.
    this.off();

    // Remove element from DOM
    if (this.el_.parentNode) {
      this.el_.parentNode.removeChild(this.el_);
    }

    Dom.removeElData(this.el_);
    this.el_ = null;
  };

  /**
   * Return the component's player
   *
   * @return {Player}
   * @method player
   */

  Component.prototype.player = function player() {
    return this.player_;
  };

  /**
   * Deep merge of options objects
   * Whenever a property is an object on both options objects
   * the two properties will be merged using mergeOptions.
   *
   * ```js
   *     Parent.prototype.options_ = {
   *       optionSet: {
   *         'childOne': { 'foo': 'bar', 'asdf': 'fdsa' },
   *         'childTwo': {},
   *         'childThree': {}
   *       }
   *     }
   *     newOptions = {
   *       optionSet: {
   *         'childOne': { 'foo': 'baz', 'abc': '123' }
   *         'childTwo': null,
   *         'childFour': {}
   *       }
   *     }
   *
   *     this.options(newOptions);
   * ```
   * RESULT
   * ```js
   *     {
   *       optionSet: {
   *         'childOne': { 'foo': 'baz', 'asdf': 'fdsa', 'abc': '123' },
   *         'childTwo': null, // Disabled. Won't be initialized.
   *         'childThree': {},
   *         'childFour': {}
   *       }
   *     }
   * ```
   *
   * @param  {Object} obj Object of new option values
   * @return {Object}     A NEW object of this.options_ and obj merged
   * @method options
   */

  Component.prototype.options = function options(obj) {
    _utilsLogJs2['default'].warn('this.options() has been deprecated and will be moved to the constructor in 6.0');

    if (!obj) {
      return this.options_;
    }

    this.options_ = _utilsMergeOptionsJs2['default'](this.options_, obj);
    return this.options_;
  };

  /**
   * Get the component's DOM element
   * ```js
   *     var domEl = myComponent.el();
   * ```
   *
   * @return {Element}
   * @method el
   */

  Component.prototype.el = function el() {
    return this.el_;
  };

  /**
   * Create the component's DOM element
   *
   * @param  {String=} tagName  Element's node type. e.g. 'div'
   * @param  {Object=} properties An object of properties that should be set
   * @param  {Object=} attributes An object of attributes that should be set
   * @return {Element}
   * @method createEl
   */

  Component.prototype.createEl = function createEl(tagName, properties, attributes) {
    return Dom.createEl(tagName, properties, attributes);
  };

  Component.prototype.localize = function localize(string) {
    var code = this.player_.language && this.player_.language();
    var languages = this.player_.languages && this.player_.languages();

    if (!code || !languages) {
      return string;
    }

    var language = languages[code];

    if (language && language[string]) {
      return language[string];
    }

    var primaryCode = code.split('-')[0];
    var primaryLang = languages[primaryCode];

    if (primaryLang && primaryLang[string]) {
      return primaryLang[string];
    }

    return string;
  };

  /**
   * Return the component's DOM element where children are inserted.
   * Will either be the same as el() or a new element defined in createEl().
   *
   * @return {Element}
   * @method contentEl
   */

  Component.prototype.contentEl = function contentEl() {
    return this.contentEl_ || this.el_;
  };

  /**
   * Get the component's ID
   * ```js
   *     var id = myComponent.id();
   * ```
   *
   * @return {String}
   * @method id
   */

  Component.prototype.id = function id() {
    return this.id_;
  };

  /**
   * Get the component's name. The name is often used to reference the component.
   * ```js
   *     var name = myComponent.name();
   * ```
   *
   * @return {String}
   * @method name
   */

  Component.prototype.name = function name() {
    return this.name_;
  };

  /**
   * Get an array of all child components
   * ```js
   *     var kids = myComponent.children();
   * ```
   *
   * @return {Array} The children
   * @method children
   */

  Component.prototype.children = function children() {
    return this.children_;
  };

  /**
   * Returns a child component with the provided ID
   *
   * @return {Component}
   * @method getChildById
   */

  Component.prototype.getChildById = function getChildById(id) {
    return this.childIndex_[id];
  };

  /**
   * Returns a child component with the provided name
   *
   * @return {Component}
   * @method getChild
   */

  Component.prototype.getChild = function getChild(name) {
    return this.childNameIndex_[name];
  };

  /**
   * Adds a child component inside this component
   * ```js
   *     myComponent.el();
   *     // -> <div class='my-component'></div>
   *     myComponent.children();
   *     // [empty array]
   *
   *     var myButton = myComponent.addChild('MyButton');
   *     // -> <div class='my-component'><div class="my-button">myButton<div></div>
   *     // -> myButton === myComponent.children()[0];
   * ```
   * Pass in options for child constructors and options for children of the child
   * ```js
   *     var myButton = myComponent.addChild('MyButton', {
   *       text: 'Press Me',
   *       buttonChildExample: {
   *         buttonChildOption: true
   *       }
   *     });
   * ```
   *
   * @param {String|Component} child The class name or instance of a child to add
   * @param {Object=} options Options, including options to be passed to children of the child.
   * @return {Component} The child component (created by this process if a string was used)
   * @method addChild
   */

  Component.prototype.addChild = function addChild(child) {
    var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];

    var component = undefined;
    var componentName = undefined;

    // If child is a string, create nt with options
    if (typeof child === 'string') {
      componentName = child;

      // Options can also be specified as a boolean, so convert to an empty object if false.
      if (!options) {
        options = {};
      }

      // Same as above, but true is deprecated so show a warning.
      if (options === true) {
        _utilsLogJs2['default'].warn('Initializing a child component with `true` is deprecated. Children should be defined in an array when possible, but if necessary use an object instead of `true`.');
        options = {};
      }

      // If no componentClass in options, assume componentClass is the name lowercased
      // (e.g. playButton)
      var componentClassName = options.componentClass || _utilsToTitleCaseJs2['default'](componentName);

      // Set name through options
      options.name = componentName;

      // Create a new object & element for this controls set
      // If there's no .player_, this is a player
      var ComponentClass = Component.getComponent(componentClassName);

      if (!ComponentClass) {
        throw new Error('Component ' + componentClassName + ' does not exist');
      }

      // data stored directly on the videojs object may be
      // misidentified as a component to retain
      // backwards-compatibility with 4.x. check to make sure the
      // component class can be instantiated.
      if (typeof ComponentClass !== 'function') {
        return null;
      }

      component = new ComponentClass(this.player_ || this, options);

      // child is a component instance
    } else {
        component = child;
      }

    this.children_.push(component);

    if (typeof component.id === 'function') {
      this.childIndex_[component.id()] = component;
    }

    // If a name wasn't used to create the component, check if we can use the
    // name function of the component
    componentName = componentName || component.name && component.name();

    if (componentName) {
      this.childNameIndex_[componentName] = component;
    }

    // Add the UI object's element to the container div (box)
    // Having an element is not required
    if (typeof component.el === 'function' && component.el()) {
      this.contentEl().appendChild(component.el());
    }

    // Return so it can stored on parent object if desired.
    return component;
  };

  /**
   * Remove a child component from this component's list of children, and the
   * child component's element from this component's element
   *
   * @param  {Component} component Component to remove
   * @method removeChild
   */

  Component.prototype.removeChild = function removeChild(component) {
    if (typeof component === 'string') {
      component = this.getChild(component);
    }

    if (!component || !this.children_) {
      return;
    }

    var childFound = false;

    for (var i = this.children_.length - 1; i >= 0; i--) {
      if (this.children_[i] === component) {
        childFound = true;
        this.children_.splice(i, 1);
        break;
      }
    }

    if (!childFound) {
      return;
    }

    this.childIndex_[component.id()] = null;
    this.childNameIndex_[component.name()] = null;

    var compEl = component.el();

    if (compEl && compEl.parentNode === this.contentEl()) {
      this.contentEl().removeChild(component.el());
    }
  };

  /**
   * Add and initialize default child components from options
   * ```js
   *     // when an instance of MyComponent is created, all children in options
   *     // will be added to the instance by their name strings and options
   *     MyComponent.prototype.options_ = {
   *       children: [
   *         'myChildComponent'
   *       ],
   *       myChildComponent: {
   *         myChildOption: true
   *       }
   *     };
   *
   *     // Or when creating the component
   *     var myComp = new MyComponent(player, {
   *       children: [
   *         'myChildComponent'
   *       ],
   *       myChildComponent: {
   *         myChildOption: true
   *       }
   *     });
   * ```
   * The children option can also be an array of
   * child options objects (that also include a 'name' key).
   * This can be used if you have two child components of the
   * same type that need different options.
   * ```js
   *     var myComp = new MyComponent(player, {
   *       children: [
   *         'button',
   *         {
   *           name: 'button',
   *           someOtherOption: true
   *         },
   *         {
   *           name: 'button',
   *           someOtherOption: false
   *         }
   *       ]
   *     });
   * ```
   *
   * @method initChildren
   */

  Component.prototype.initChildren = function initChildren() {
    var _this = this;

    var children = this.options_.children;

    if (children) {
      (function () {
        // `this` is `parent`
        var parentOptions = _this.options_;

        var handleAdd = function handleAdd(child) {
          var name = child.name;
          var opts = child.opts;

          // Allow options for children to be set at the parent options
          // e.g. videojs(id, { controlBar: false });
          // instead of videojs(id, { children: { controlBar: false });
          if (parentOptions[name] !== undefined) {
            opts = parentOptions[name];
          }

          // Allow for disabling default components
          // e.g. options['children']['posterImage'] = false
          if (opts === false) {
            return;
          }

          // Allow options to be passed as a simple boolean if no configuration
          // is necessary.
          if (opts === true) {
            opts = {};
          }

          // We also want to pass the original player options to each component as well so they don't need to
          // reach back into the player for options later.
          opts.playerOptions = _this.options_.playerOptions;

          // Create and add the child component.
          // Add a direct reference to the child by name on the parent instance.
          // If two of the same component are used, different names should be supplied
          // for each
          var newChild = _this.addChild(name, opts);
          if (newChild) {
            _this[name] = newChild;
          }
        };

        // Allow for an array of children details to passed in the options
        var workingChildren = undefined;
        var Tech = Component.getComponent('Tech');

        if (Array.isArray(children)) {
          workingChildren = children;
        } else {
          workingChildren = Object.keys(children);
        }

        workingChildren
        // children that are in this.options_ but also in workingChildren  would
        // give us extra children we do not want. So, we want to filter them out.
        .concat(Object.keys(_this.options_).filter(function (child) {
          return !workingChildren.some(function (wchild) {
            if (typeof wchild === 'string') {
              return child === wchild;
            } else {
              return child === wchild.name;
            }
          });
        })).map(function (child) {
          var name = undefined,
              opts = undefined;

          if (typeof child === 'string') {
            name = child;
            opts = children[name] || _this.options_[name] || {};
          } else {
            name = child.name;
            opts = child;
          }

          return { name: name, opts: opts };
        }).filter(function (child) {
          // we have to make sure that child.name isn't in the techOrder since
          // techs are registerd as Components but can't aren't compatible
          // See https://github.com/videojs/video.js/issues/2772
          var c = Component.getComponent(child.opts.componentClass || _utilsToTitleCaseJs2['default'](child.name));
          return c && !Tech.isTech(c);
        }).forEach(handleAdd);
      })();
    }
  };

  /**
   * Allows sub components to stack CSS class names
   *
   * @return {String} The constructed class name
   * @method buildCSSClass
   */

  Component.prototype.buildCSSClass = function buildCSSClass() {
    // Child classes can include a function that does:
    // return 'CLASS NAME' + this._super();
    return '';
  };

  /**
   * Add an event listener to this component's element
   * ```js
   *     var myFunc = function(){
   *       var myComponent = this;
   *       // Do something when the event is fired
   *     };
   *
   *     myComponent.on('eventType', myFunc);
   * ```
   * The context of myFunc will be myComponent unless previously bound.
   * Alternatively, you can add a listener to another element or component.
   * ```js
   *     myComponent.on(otherElement, 'eventName', myFunc);
   *     myComponent.on(otherComponent, 'eventName', myFunc);
   * ```
   * The benefit of using this over `VjsEvents.on(otherElement, 'eventName', myFunc)`
   * and `otherComponent.on('eventName', myFunc)` is that this way the listeners
   * will be automatically cleaned up when either component is disposed.
   * It will also bind myComponent as the context of myFunc.
   * **NOTE**: When using this on elements in the page other than window
   * and document (both permanent), if you remove the element from the DOM
   * you need to call `myComponent.trigger(el, 'dispose')` on it to clean up
   * references to it and allow the browser to garbage collect it.
   *
   * @param  {String|Component} first   The event type or other component
   * @param  {Function|String}      second  The event handler or event type
   * @param  {Function}             third   The event handler
   * @return {Component}
   * @method on
   */

  Component.prototype.on = function on(first, second, third) {
    var _this2 = this;

    if (typeof first === 'string' || Array.isArray(first)) {
      Events.on(this.el_, first, Fn.bind(this, second));

      // Targeting another component or element
    } else {
        (function () {
          var target = first;
          var type = second;
          var fn = Fn.bind(_this2, third);

          // When this component is disposed, remove the listener from the other component
          var removeOnDispose = function removeOnDispose() {
            return _this2.off(target, type, fn);
          };

          // Use the same function ID so we can remove it later it using the ID
          // of the original listener
          removeOnDispose.guid = fn.guid;
          _this2.on('dispose', removeOnDispose);

          // If the other component is disposed first we need to clean the reference
          // to the other component in this component's removeOnDispose listener
          // Otherwise we create a memory leak.
          var cleanRemover = function cleanRemover() {
            return _this2.off('dispose', removeOnDispose);
          };

          // Add the same function ID so we can easily remove it later
          cleanRemover.guid = fn.guid;

          // Check if this is a DOM node
          if (first.nodeName) {
            // Add the listener to the other element
            Events.on(target, type, fn);
            Events.on(target, 'dispose', cleanRemover);

            // Should be a component
            // Not using `instanceof Component` because it makes mock players difficult
          } else if (typeof first.on === 'function') {
              // Add the listener to the other component
              target.on(type, fn);
              target.on('dispose', cleanRemover);
            }
        })();
      }

    return this;
  };

  /**
   * Remove an event listener from this component's element
   * ```js
   *     myComponent.off('eventType', myFunc);
   * ```
   * If myFunc is excluded, ALL listeners for the event type will be removed.
   * If eventType is excluded, ALL listeners will be removed from the component.
   * Alternatively you can use `off` to remove listeners that were added to other
   * elements or components using `myComponent.on(otherComponent...`.
   * In this case both the event type and listener function are REQUIRED.
   * ```js
   *     myComponent.off(otherElement, 'eventType', myFunc);
   *     myComponent.off(otherComponent, 'eventType', myFunc);
   * ```
   *
   * @param  {String=|Component}  first  The event type or other component
   * @param  {Function=|String}       second The listener function or event type
   * @param  {Function=}              third  The listener for other component
   * @return {Component}
   * @method off
   */

  Component.prototype.off = function off(first, second, third) {
    if (!first || typeof first === 'string' || Array.isArray(first)) {
      Events.off(this.el_, first, second);
    } else {
      var target = first;
      var type = second;
      // Ensure there's at least a guid, even if the function hasn't been used
      var fn = Fn.bind(this, third);

      // Remove the dispose listener on this component,
      // which was given the same guid as the event listener
      this.off('dispose', fn);

      if (first.nodeName) {
        // Remove the listener
        Events.off(target, type, fn);
        // Remove the listener for cleaning the dispose listener
        Events.off(target, 'dispose', fn);
      } else {
        target.off(type, fn);
        target.off('dispose', fn);
      }
    }

    return this;
  };

  /**
   * Add an event listener to be triggered only once and then removed
   * ```js
   *     myComponent.one('eventName', myFunc);
   * ```
   * Alternatively you can add a listener to another element or component
   * that will be triggered only once.
   * ```js
   *     myComponent.one(otherElement, 'eventName', myFunc);
   *     myComponent.one(otherComponent, 'eventName', myFunc);
   * ```
   *
   * @param  {String|Component}  first   The event type or other component
   * @param  {Function|String}       second  The listener function or event type
   * @param  {Function=}             third   The listener function for other component
   * @return {Component}
   * @method one
   */

  Component.prototype.one = function one(first, second, third) {
    var _this3 = this,
        _arguments = arguments;

    if (typeof first === 'string' || Array.isArray(first)) {
      Events.one(this.el_, first, Fn.bind(this, second));
    } else {
      (function () {
        var target = first;
        var type = second;
        var fn = Fn.bind(_this3, third);

        var newFunc = function newFunc() {
          _this3.off(target, type, newFunc);
          fn.apply(null, _arguments);
        };

        // Keep the same function ID so we can remove it later
        newFunc.guid = fn.guid;

        _this3.on(target, type, newFunc);
      })();
    }

    return this;
  };

  /**
   * Trigger an event on an element
   * ```js
   *     myComponent.trigger('eventName');
   *     myComponent.trigger({'type':'eventName'});
   *     myComponent.trigger('eventName', {data: 'some data'});
   *     myComponent.trigger({'type':'eventName'}, {data: 'some data'});
   * ```
   *
   * @param  {Event|Object|String} event  A string (the type) or an event object with a type attribute
   * @param  {Object} [hash] data hash to pass along with the event
   * @return {Component}       self
   * @method trigger
   */

  Component.prototype.trigger = function trigger(event, hash) {
    Events.trigger(this.el_, event, hash);
    return this;
  };

  /**
   * Bind a listener to the component's ready state.
   * Different from event listeners in that if the ready event has already happened
   * it will trigger the function immediately.
   *
   * @param  {Function} fn Ready listener
   * @param  {Boolean} sync Exec the listener synchronously if component is ready
   * @return {Component}
   * @method ready
   */

  Component.prototype.ready = function ready(fn) {
    var sync = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];

    if (fn) {
      if (this.isReady_) {
        if (sync) {
          fn.call(this);
        } else {
          // Call the function asynchronously by default for consistency
          this.setTimeout(fn, 1);
        }
      } else {
        this.readyQueue_ = this.readyQueue_ || [];
        this.readyQueue_.push(fn);
      }
    }
    return this;
  };

  /**
   * Trigger the ready listeners
   *
   * @return {Component}
   * @method triggerReady
   */

  Component.prototype.triggerReady = function triggerReady() {
    this.isReady_ = true;

    // Ensure ready is triggerd asynchronously
    this.setTimeout(function () {
      var readyQueue = this.readyQueue_;

      // Reset Ready Queue
      this.readyQueue_ = [];

      if (readyQueue && readyQueue.length > 0) {
        readyQueue.forEach(function (fn) {
          fn.call(this);
        }, this);
      }

      // Allow for using event listeners also
      this.trigger('ready');
    }, 1);
  };

  /**
   * Finds a single DOM element matching `selector` within the component's
   * `contentEl` or another custom context.
   *
   * @method $
   * @param  {String} selector
   *         A valid CSS selector, which will be passed to `querySelector`.
   *
   * @param  {Element|String} [context=document]
   *         A DOM element within which to query. Can also be a selector
   *         string in which case the first matching element will be used
   *         as context. If missing (or no element matches selector), falls
   *         back to `document`.
   *
   * @return {Element|null}
   */

  Component.prototype.$ = function $(selector, context) {
    return Dom.$(selector, context || this.contentEl());
  };

  /**
   * Finds a all DOM elements matching `selector` within the component's
   * `contentEl` or another custom context.
   *
   * @method $$
   * @param  {String} selector
   *         A valid CSS selector, which will be passed to `querySelectorAll`.
   *
   * @param  {Element|String} [context=document]
   *         A DOM element within which to query. Can also be a selector
   *         string in which case the first matching element will be used
   *         as context. If missing (or no element matches selector), falls
   *         back to `document`.
   *
   * @return {NodeList}
   */

  Component.prototype.$$ = function $$(selector, context) {
    return Dom.$$(selector, context || this.contentEl());
  };

  /**
   * Check if a component's element has a CSS class name
   *
   * @param {String} classToCheck Classname to check
   * @return {Component}
   * @method hasClass
   */

  Component.prototype.hasClass = function hasClass(classToCheck) {
    return Dom.hasElClass(this.el_, classToCheck);
  };

  /**
   * Add a CSS class name to the component's element
   *
   * @param {String} classToAdd Classname to add
   * @return {Component}
   * @method addClass
   */

  Component.prototype.addClass = function addClass(classToAdd) {
    Dom.addElClass(this.el_, classToAdd);
    return this;
  };

  /**
   * Remove a CSS class name from the component's element
   *
   * @param {String} classToRemove Classname to remove
   * @return {Component}
   * @method removeClass
   */

  Component.prototype.removeClass = function removeClass(classToRemove) {
    Dom.removeElClass(this.el_, classToRemove);
    return this;
  };

  /**
   * Add or remove a CSS class name from the component's element
   *
   * @param  {String} classToToggle
   * @param  {Boolean|Function} [predicate]
   *         Can be a function that returns a Boolean. If `true`, the class
   *         will be added; if `false`, the class will be removed. If not
   *         given, the class will be added if not present and vice versa.
   *
   * @return {Component}
   * @method toggleClass
   */

  Component.prototype.toggleClass = function toggleClass(classToToggle, predicate) {
    Dom.toggleElClass(this.el_, classToToggle, predicate);
    return this;
  };

  /**
   * Show the component element if hidden
   *
   * @return {Component}
   * @method show
   */

  Component.prototype.show = function show() {
    this.removeClass('vjs-hidden');
    return this;
  };

  /**
   * Hide the component element if currently showing
   *
   * @return {Component}
   * @method hide
   */

  Component.prototype.hide = function hide() {
    this.addClass('vjs-hidden');
    return this;
  };

  /**
   * Lock an item in its visible state
   * To be used with fadeIn/fadeOut.
   *
   * @return {Component}
   * @private
   * @method lockShowing
   */

  Component.prototype.lockShowing = function lockShowing() {
    this.addClass('vjs-lock-showing');
    return this;
  };

  /**
   * Unlock an item to be hidden
   * To be used with fadeIn/fadeOut.
   *
   * @return {Component}
   * @private
   * @method unlockShowing
   */

  Component.prototype.unlockShowing = function unlockShowing() {
    this.removeClass('vjs-lock-showing');
    return this;
  };

  /**
   * Set or get the width of the component (CSS values)
   * Setting the video tag dimension values only works with values in pixels.
   * Percent values will not work.
   * Some percents can be used, but width()/height() will return the number + %,
   * not the actual computed width/height.
   *
   * @param  {Number|String=} num   Optional width number
   * @param  {Boolean} skipListeners Skip the 'resize' event trigger
   * @return {Component} This component, when setting the width
   * @return {Number|String} The width, when getting
   * @method width
   */

  Component.prototype.width = function width(num, skipListeners) {
    return this.dimension('width', num, skipListeners);
  };

  /**
   * Get or set the height of the component (CSS values)
   * Setting the video tag dimension values only works with values in pixels.
   * Percent values will not work.
   * Some percents can be used, but width()/height() will return the number + %,
   * not the actual computed width/height.
   *
   * @param  {Number|String=} num     New component height
   * @param  {Boolean=} skipListeners Skip the resize event trigger
   * @return {Component} This component, when setting the height
   * @return {Number|String} The height, when getting
   * @method height
   */

  Component.prototype.height = function height(num, skipListeners) {
    return this.dimension('height', num, skipListeners);
  };

  /**
   * Set both width and height at the same time
   *
   * @param  {Number|String} width Width of player
   * @param  {Number|String} height Height of player
   * @return {Component} The component
   * @method dimensions
   */

  Component.prototype.dimensions = function dimensions(width, height) {
    // Skip resize listeners on width for optimization
    return this.width(width, true).height(height);
  };

  /**
   * Get or set width or height
   * This is the shared code for the width() and height() methods.
   * All for an integer, integer + 'px' or integer + '%';
   * Known issue: Hidden elements officially have a width of 0. We're defaulting
   * to the style.width value and falling back to computedStyle which has the
   * hidden element issue. Info, but probably not an efficient fix:
   * http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/
   *
   * @param  {String} widthOrHeight  'width' or 'height'
   * @param  {Number|String=} num     New dimension
   * @param  {Boolean=} skipListeners Skip resize event trigger
   * @return {Component} The component if a dimension was set
   * @return {Number|String} The dimension if nothing was set
   * @private
   * @method dimension
   */

  Component.prototype.dimension = function dimension(widthOrHeight, num, skipListeners) {
    if (num !== undefined) {
      // Set to zero if null or literally NaN (NaN !== NaN)
      if (num === null || num !== num) {
        num = 0;
      }

      // Check if using css width/height (% or px) and adjust
      if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) {
        this.el_.style[widthOrHeight] = num;
      } else if (num === 'auto') {
        this.el_.style[widthOrHeight] = '';
      } else {
        this.el_.style[widthOrHeight] = num + 'px';
      }

      // skipListeners allows us to avoid triggering the resize event when setting both width and height
      if (!skipListeners) {
        this.trigger('resize');
      }

      // Return component
      return this;
    }

    // Not setting a value, so getting it
    // Make sure element exists
    if (!this.el_) {
      return 0;
    }

    // Get dimension value from style
    var val = this.el_.style[widthOrHeight];
    var pxIndex = val.indexOf('px');

    if (pxIndex !== -1) {
      // Return the pixel value with no 'px'
      return parseInt(val.slice(0, pxIndex), 10);
    }

    // No px so using % or no style was set, so falling back to offsetWidth/height
    // If component has display:none, offset will return 0
    // TODO: handle display:none and no dimension style using px
    return parseInt(this.el_['offset' + _utilsToTitleCaseJs2['default'](widthOrHeight)], 10);
  };

  /**
   * Emit 'tap' events when touch events are supported
   * This is used to support toggling the controls through a tap on the video.
   * We're requiring them to be enabled because otherwise every component would
   * have this extra overhead unnecessarily, on mobile devices where extra
   * overhead is especially bad.
   *
   * @private
   * @method emitTapEvents
   */

  Component.prototype.emitTapEvents = function emitTapEvents() {
    // Track the start time so we can determine how long the touch lasted
    var touchStart = 0;
    var firstTouch = null;

    // Maximum movement allowed during a touch event to still be considered a tap
    // Other popular libs use anywhere from 2 (hammer.js) to 15, so 10 seems like a nice, round number.
    var tapMovementThreshold = 10;

    // The maximum length a touch can be while still being considered a tap
    var touchTimeThreshold = 200;

    var couldBeTap = undefined;

    this.on('touchstart', function (event) {
      // If more than one finger, don't consider treating this as a click
      if (event.touches.length === 1) {
        // Copy the touches object to prevent modifying the original
        firstTouch = _objectAssign2['default']({}, event.touches[0]);
        // Record start time so we can detect a tap vs. "touch and hold"
        touchStart = new Date().getTime();
        // Reset couldBeTap tracking
        couldBeTap = true;
      }
    });

    this.on('touchmove', function (event) {
      // If more than one finger, don't consider treating this as a click
      if (event.touches.length > 1) {
        couldBeTap = false;
      } else if (firstTouch) {
        // Some devices will throw touchmoves for all but the slightest of taps.
        // So, if we moved only a small distance, this could still be a tap
        var xdiff = event.touches[0].pageX - firstTouch.pageX;
        var ydiff = event.touches[0].pageY - firstTouch.pageY;
        var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);

        if (touchDistance > tapMovementThreshold) {
          couldBeTap = false;
        }
      }
    });

    var noTap = function noTap() {
      couldBeTap = false;
    };

    // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
    this.on('touchleave', noTap);
    this.on('touchcancel', noTap);

    // When the touch ends, measure how long it took and trigger the appropriate
    // event
    this.on('touchend', function (event) {
      firstTouch = null;
      // Proceed only if the touchmove/leave/cancel event didn't happen
      if (couldBeTap === true) {
        // Measure how long the touch lasted
        var touchTime = new Date().getTime() - touchStart;

        // Make sure the touch was less than the threshold to be considered a tap
        if (touchTime < touchTimeThreshold) {
          // Don't let browser turn this into a click
          event.preventDefault();
          this.trigger('tap');
          // It may be good to copy the touchend event object and change the
          // type to tap, if the other event properties aren't exact after
          // Events.fixEvent runs (e.g. event.target)
        }
      }
    });
  };

  /**
   * Report user touch activity when touch events occur
   * User activity is used to determine when controls should show/hide. It's
   * relatively simple when it comes to mouse events, because any mouse event
   * should show the controls. So we capture mouse events that bubble up to the
   * player and report activity when that happens.
   * With touch events it isn't as easy. We can't rely on touch events at the
   * player level, because a tap (touchstart + touchend) on the video itself on
   * mobile devices is meant to turn controls off (and on). User activity is
   * checked asynchronously, so what could happen is a tap event on the video
   * turns the controls off, then the touchend event bubbles up to the player,
   * which if it reported user activity, would turn the controls right back on.
   * (We also don't want to completely block touch events from bubbling up)
   * Also a touchmove, touch+hold, and anything other than a tap is not supposed
   * to turn the controls back on on a mobile device.
   * Here we're setting the default component behavior to report user activity
   * whenever touch events happen, and this can be turned off by components that
   * want touch events to act differently.
   *
   * @method enableTouchActivity
   */

  Component.prototype.enableTouchActivity = function enableTouchActivity() {
    // Don't continue if the root player doesn't support reporting user activity
    if (!this.player() || !this.player().reportUserActivity) {
      return;
    }

    // listener for reporting that the user is active
    var report = Fn.bind(this.player(), this.player().reportUserActivity);

    var touchHolding = undefined;

    this.on('touchstart', function () {
      report();
      // For as long as the they are touching the device or have their mouse down,
      // we consider them active even if they're not moving their finger or mouse.
      // So we want to continue to update that they are active
      this.clearInterval(touchHolding);
      // report at the same interval as activityCheck
      touchHolding = this.setInterval(report, 250);
    });

    var touchEnd = function touchEnd(event) {
      report();
      // stop the interval that maintains activity if the touch is holding
      this.clearInterval(touchHolding);
    };

    this.on('touchmove', report);
    this.on('touchend', touchEnd);
    this.on('touchcancel', touchEnd);
  };

  /**
   * Creates timeout and sets up disposal automatically.
   *
   * @param {Function} fn The function to run after the timeout.
   * @param {Number} timeout Number of ms to delay before executing specified function.
   * @return {Number} Returns the timeout ID
   * @method setTimeout
   */

  Component.prototype.setTimeout = function setTimeout(fn, timeout) {
    fn = Fn.bind(this, fn);

    // window.setTimeout would be preferable here, but due to some bizarre issue with Sinon and/or Phantomjs, we can't.
    var timeoutId = _globalWindow2['default'].setTimeout(fn, timeout);

    var disposeFn = function disposeFn() {
      this.clearTimeout(timeoutId);
    };

    disposeFn.guid = 'vjs-timeout-' + timeoutId;

    this.on('dispose', disposeFn);

    return timeoutId;
  };

  /**
   * Clears a timeout and removes the associated dispose listener
   *
   * @param {Number} timeoutId The id of the timeout to clear
   * @return {Number} Returns the timeout ID
   * @method clearTimeout
   */

  Component.prototype.clearTimeout = function clearTimeout(timeoutId) {
    _globalWindow2['default'].clearTimeout(timeoutId);

    var disposeFn = function disposeFn() {};

    disposeFn.guid = 'vjs-timeout-' + timeoutId;

    this.off('dispose', disposeFn);

    return timeoutId;
  };

  /**
   * Creates an interval and sets up disposal automatically.
   *
   * @param {Function} fn The function to run every N seconds.
   * @param {Number} interval Number of ms to delay before executing specified function.
   * @return {Number} Returns the interval ID
   * @method setInterval
   */

  Component.prototype.setInterval = function setInterval(fn, interval) {
    fn = Fn.bind(this, fn);

    var intervalId = _globalWindow2['default'].setInterval(fn, interval);

    var disposeFn = function disposeFn() {
      this.clearInterval(intervalId);
    };

    disposeFn.guid = 'vjs-interval-' + intervalId;

    this.on('dispose', disposeFn);

    return intervalId;
  };

  /**
   * Clears an interval and removes the associated dispose listener
   *
   * @param {Number} intervalId The id of the interval to clear
   * @return {Number} Returns the interval ID
   * @method clearInterval
   */

  Component.prototype.clearInterval = function clearInterval(intervalId) {
    _globalWindow2['default'].clearInterval(intervalId);

    var disposeFn = function disposeFn() {};

    disposeFn.guid = 'vjs-interval-' + intervalId;

    this.off('dispose', disposeFn);

    return intervalId;
  };

  /**
   * Registers a component
   *
   * @param {String} name Name of the component to register
   * @param {Object} comp The component to register
   * @static
   * @method registerComponent
   */

  Component.registerComponent = function registerComponent(name, comp) {
    if (!Component.components_) {
      Component.components_ = {};
    }

    Component.components_[name] = comp;
    return comp;
  };

  /**
   * Gets a component by name
   *
   * @param {String} name Name of the component to get
   * @return {Component}
   * @static
   * @method getComponent
   */

  Component.getComponent = function getComponent(name) {
    if (Component.components_ && Component.components_[name]) {
      return Component.components_[name];
    }

    if (_globalWindow2['default'] && _globalWindow2['default'].videojs && _globalWindow2['default'].videojs[name]) {
      _utilsLogJs2['default'].warn('The ' + name + ' component was added to the videojs object when it should be registered using videojs.registerComponent(name, component)');
      return _globalWindow2['default'].videojs[name];
    }
  };

  /**
   * Sets up the constructor using the supplied init method
   * or uses the init of the parent object
   *
   * @param {Object} props An object of properties
   * @static
   * @deprecated
   * @method extend
   */

  Component.extend = function extend(props) {
    props = props || {};

    _utilsLogJs2['default'].warn('Component.extend({}) has been deprecated, use videojs.extend(Component, {}) instead');

    // Set up the constructor using the supplied init method
    // or using the init of the parent object
    // Make sure to check the unobfuscated version for external libs
    var init = props.init || props.init || this.prototype.init || this.prototype.init || function () {};
    // In Resig's simple class inheritance (previously used) the constructor
    //  is a function that calls `this.init.apply(arguments)`
    // However that would prevent us from using `ParentObject.call(this);`
    //  in a Child constructor because the `this` in `this.init`
    //  would still refer to the Child and cause an infinite loop.
    // We would instead have to do
    //    `ParentObject.prototype.init.apply(this, arguments);`
    //  Bleh. We're not creating a _super() function, so it's good to keep
    //  the parent constructor reference simple.
    var subObj = function subObj() {
      init.apply(this, arguments);
    };

    // Inherit from this object's prototype
    subObj.prototype = Object.create(this.prototype);
    // Reset the constructor property for subObj otherwise
    // instances of subObj would have the constructor of the parent Object
    subObj.prototype.constructor = subObj;

    // Make the class extendable
    subObj.extend = Component.extend;

    // Extend subObj's prototype with functions and other properties from props
    for (var _name in props) {
      if (props.hasOwnProperty(_name)) {
        subObj.prototype[_name] = props[_name];
      }
    }

    return subObj;
  };

  return Component;
})();

Component.registerComponent('Component', Component);
exports['default'] = Component;
module.exports = exports['default'];

},{"./utils/dom.js":128,"./utils/events.js":129,"./utils/fn.js":130,"./utils/guid.js":132,"./utils/log.js":133,"./utils/merge-options.js":134,"./utils/to-title-case.js":137,"global/window":2,"object.assign":45}],66:[function(_dereq_,module,exports){
/**
 * @file control-bar.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _componentJs = _dereq_('../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

// Required children

var _playToggleJs = _dereq_('./play-toggle.js');

var _playToggleJs2 = _interopRequireDefault(_playToggleJs);

var _timeControlsCurrentTimeDisplayJs = _dereq_('./time-controls/current-time-display.js');

var _timeControlsCurrentTimeDisplayJs2 = _interopRequireDefault(_timeControlsCurrentTimeDisplayJs);

var _timeControlsDurationDisplayJs = _dereq_('./time-controls/duration-display.js');

var _timeControlsDurationDisplayJs2 = _interopRequireDefault(_timeControlsDurationDisplayJs);

var _timeControlsTimeDividerJs = _dereq_('./time-controls/time-divider.js');

var _timeControlsTimeDividerJs2 = _interopRequireDefault(_timeControlsTimeDividerJs);

var _timeControlsRemainingTimeDisplayJs = _dereq_('./time-controls/remaining-time-display.js');

var _timeControlsRemainingTimeDisplayJs2 = _interopRequireDefault(_timeControlsRemainingTimeDisplayJs);

var _liveDisplayJs = _dereq_('./live-display.js');

var _liveDisplayJs2 = _interopRequireDefault(_liveDisplayJs);

var _progressControlProgressControlJs = _dereq_('./progress-control/progress-control.js');

var _progressControlProgressControlJs2 = _interopRequireDefault(_progressControlProgressControlJs);

var _fullscreenToggleJs = _dereq_('./fullscreen-toggle.js');

var _fullscreenToggleJs2 = _interopRequireDefault(_fullscreenToggleJs);

var _volumeControlVolumeControlJs = _dereq_('./volume-control/volume-control.js');

var _volumeControlVolumeControlJs2 = _interopRequireDefault(_volumeControlVolumeControlJs);

var _volumeMenuButtonJs = _dereq_('./volume-menu-button.js');

var _volumeMenuButtonJs2 = _interopRequireDefault(_volumeMenuButtonJs);

var _muteToggleJs = _dereq_('./mute-toggle.js');

var _muteToggleJs2 = _interopRequireDefault(_muteToggleJs);

var _textTrackControlsChaptersButtonJs = _dereq_('./text-track-controls/chapters-button.js');

var _textTrackControlsChaptersButtonJs2 = _interopRequireDefault(_textTrackControlsChaptersButtonJs);

var _textTrackControlsSubtitlesButtonJs = _dereq_('./text-track-controls/subtitles-button.js');

var _textTrackControlsSubtitlesButtonJs2 = _interopRequireDefault(_textTrackControlsSubtitlesButtonJs);

var _textTrackControlsCaptionsButtonJs = _dereq_('./text-track-controls/captions-button.js');

var _textTrackControlsCaptionsButtonJs2 = _interopRequireDefault(_textTrackControlsCaptionsButtonJs);

var _playbackRateMenuPlaybackRateMenuButtonJs = _dereq_('./playback-rate-menu/playback-rate-menu-button.js');

var _playbackRateMenuPlaybackRateMenuButtonJs2 = _interopRequireDefault(_playbackRateMenuPlaybackRateMenuButtonJs);

var _spacerControlsCustomControlSpacerJs = _dereq_('./spacer-controls/custom-control-spacer.js');

var _spacerControlsCustomControlSpacerJs2 = _interopRequireDefault(_spacerControlsCustomControlSpacerJs);

/**
 * Container of main controls
 *
 * @extends Component
 * @class ControlBar
 */

var ControlBar = (function (_Component) {
  _inherits(ControlBar, _Component);

  function ControlBar() {
    _classCallCheck(this, ControlBar);

    _Component.apply(this, arguments);
  }

  /**
   * Create the component's DOM element
   *
   * @return {Element}
   * @method createEl
   */

  ControlBar.prototype.createEl = function createEl() {
    return _Component.prototype.createEl.call(this, 'div', {
      className: 'vjs-control-bar'
    });
  };

  return ControlBar;
})(_componentJs2['default']);

ControlBar.prototype.options_ = {
  loadEvent: 'play',
  children: ['playToggle', 'volumeMenuButton', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'subtitlesButton', 'captionsButton', 'fullscreenToggle']
};

_componentJs2['default'].registerComponent('ControlBar', ControlBar);
exports['default'] = ControlBar;
module.exports = exports['default'];

},{"../component.js":65,"./fullscreen-toggle.js":67,"./live-display.js":68,"./mute-toggle.js":69,"./play-toggle.js":70,"./playback-rate-menu/playback-rate-menu-button.js":71,"./progress-control/progress-control.js":76,"./spacer-controls/custom-control-spacer.js":78,"./text-track-controls/captions-button.js":81,"./text-track-controls/chapters-button.js":82,"./text-track-controls/subtitles-button.js":85,"./time-controls/current-time-display.js":88,"./time-controls/duration-display.js":89,"./time-controls/remaining-time-display.js":90,"./time-controls/time-divider.js":91,"./volume-control/volume-control.js":93,"./volume-menu-button.js":95}],67:[function(_dereq_,module,exports){
/**
 * @file fullscreen-toggle.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _buttonJs = _dereq_('../button.js');

var _buttonJs2 = _interopRequireDefault(_buttonJs);

var _componentJs = _dereq_('../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

/**
 * Toggle fullscreen video
 *
 * @extends Button
 * @class FullscreenToggle
 */

var FullscreenToggle = (function (_Button) {
  _inherits(FullscreenToggle, _Button);

  function FullscreenToggle() {
    _classCallCheck(this, FullscreenToggle);

    _Button.apply(this, arguments);
  }

  /**
   * Allow sub components to stack CSS class names
   *
   * @return {String} The constructed class name
   * @method buildCSSClass
   */

  FullscreenToggle.prototype.buildCSSClass = function buildCSSClass() {
    return 'vjs-fullscreen-control ' + _Button.prototype.buildCSSClass.call(this);
  };

  /**
   * Handles click for full screen
   *
   * @method handleClick
   */

  FullscreenToggle.prototype.handleClick = function handleClick() {
    if (!this.player_.isFullscreen()) {
      this.player_.requestFullscreen();
      this.controlText('Non-Fullscreen');
    } else {
      this.player_.exitFullscreen();
      this.controlText('Fullscreen');
    }
  };

  return FullscreenToggle;
})(_buttonJs2['default']);

FullscreenToggle.prototype.controlText_ = 'Fullscreen';

_componentJs2['default'].registerComponent('FullscreenToggle', FullscreenToggle);
exports['default'] = FullscreenToggle;
module.exports = exports['default'];

},{"../button.js":63,"../component.js":65}],68:[function(_dereq_,module,exports){
/**
 * @file live-display.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _component = _dereq_('../component');

var _component2 = _interopRequireDefault(_component);

var _utilsDomJs = _dereq_('../utils/dom.js');

var Dom = _interopRequireWildcard(_utilsDomJs);

/**
 * Displays the live indicator
 * TODO - Future make it click to snap to live
 *
 * @extends Component
 * @class LiveDisplay
 */

var LiveDisplay = (function (_Component) {
  _inherits(LiveDisplay, _Component);

  function LiveDisplay(player, options) {
    _classCallCheck(this, LiveDisplay);

    _Component.call(this, player, options);

    this.updateShowing();
    this.on(this.player(), 'durationchange', this.updateShowing);
  }

  /**
   * Create the component's DOM element
   *
   * @return {Element}
   * @method createEl
   */

  LiveDisplay.prototype.createEl = function createEl() {
    var el = _Component.prototype.createEl.call(this, 'div', {
      className: 'vjs-live-control vjs-control'
    });

    this.contentEl_ = Dom.createEl('div', {
      className: 'vjs-live-display',
      innerHTML: '<span class="vjs-control-text">' + this.localize('Stream Type') + '</span>' + this.localize('LIVE')
    }, {
      'aria-live': 'off'
    });

    el.appendChild(this.contentEl_);
    return el;
  };

  LiveDisplay.prototype.updateShowing = function updateShowing() {
    if (this.player().duration() === Infinity) {
      this.show();
    } else {
      this.hide();
    }
  };

  return LiveDisplay;
})(_component2['default']);

_component2['default'].registerComponent('LiveDisplay', LiveDisplay);
exports['default'] = LiveDisplay;
module.exports = exports['default'];

},{"../component":65,"../utils/dom.js":128}],69:[function(_dereq_,module,exports){
/**
 * @file mute-toggle.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _button = _dereq_('../button');

var _button2 = _interopRequireDefault(_button);

var _component = _dereq_('../component');

var _component2 = _interopRequireDefault(_component);

var _utilsDomJs = _dereq_('../utils/dom.js');

var Dom = _interopRequireWildcard(_utilsDomJs);

/**
 * A button component for muting the audio
 *
 * @param {Player|Object} player
 * @param {Object=} options
 * @extends Button
 * @class MuteToggle
 */

var MuteToggle = (function (_Button) {
  _inherits(MuteToggle, _Button);

  function MuteToggle(player, options) {
    _classCallCheck(this, MuteToggle);

    _Button.call(this, player, options);

    this.on(player, 'volumechange', this.update);

    // hide mute toggle if the current tech doesn't support volume control
    if (player.tech_ && player.tech_['featuresVolumeControl'] === false) {
      this.addClass('vjs-hidden');
    }

    this.on(player, 'loadstart', function () {
      this.update(); // We need to update the button to account for a default muted state.

      if (player.tech_['featuresVolumeControl'] === false) {
        this.addClass('vjs-hidden');
      } else {
        this.removeClass('vjs-hidden');
      }
    });
  }

  /**
   * Allow sub components to stack CSS class names
   *
   * @return {String} The constructed class name
   * @method buildCSSClass
   */

  MuteToggle.prototype.buildCSSClass = function buildCSSClass() {
    return 'vjs-mute-control ' + _Button.prototype.buildCSSClass.call(this);
  };

  /**
   * Handle click on mute
   *
   * @method handleClick
   */

  MuteToggle.prototype.handleClick = function handleClick() {
    this.player_.muted(this.player_.muted() ? false : true);
  };

  /**
   * Update volume
   *
   * @method update
   */

  MuteToggle.prototype.update = function update() {
    var vol = this.player_.volume(),
        level = 3;

    if (vol === 0 || this.player_.muted()) {
      level = 0;
    } else if (vol < 0.33) {
      level = 1;
    } else if (vol < 0.67) {
      level = 2;
    }

    // Don't rewrite the button text if the actual text doesn't change.
    // This causes unnecessary and confusing information for screen reader users.
    // This check is needed because this function gets called every time the volume level is changed.
    var toMute = this.player_.muted() ? 'Unmute' : 'Mute';
    var localizedMute = this.localize(toMute);
    if (this.controlText() !== localizedMute) {
      this.controlText(localizedMute);
    }

    /* TODO improve muted icon classes */
    for (var i = 0; i < 4; i++) {
      Dom.removeElClass(this.el_, 'vjs-vol-' + i);
    }
    Dom.addElClass(this.el_, 'vjs-vol-' + level);
  };

  return MuteToggle;
})(_button2['default']);

MuteToggle.prototype.controlText_ = 'Mute';

_component2['default'].registerComponent('MuteToggle', MuteToggle);
exports['default'] = MuteToggle;
module.exports = exports['default'];

},{"../button":63,"../component":65,"../utils/dom.js":128}],70:[function(_dereq_,module,exports){
/**
 * @file play-toggle.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _buttonJs = _dereq_('../button.js');

var _buttonJs2 = _interopRequireDefault(_buttonJs);

var _componentJs = _dereq_('../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

/**
 * Button to toggle between play and pause
 *
 * @param {Player|Object} player
 * @param {Object=} options
 * @extends Button
 * @class PlayToggle
 */

var PlayToggle = (function (_Button) {
  _inherits(PlayToggle, _Button);

  function PlayToggle(player, options) {
    _classCallCheck(this, PlayToggle);

    _Button.call(this, player, options);

    this.on(player, 'play', this.handlePlay);
    this.on(player, 'pause', this.handlePause);
  }

  /**
   * Allow sub components to stack CSS class names
   *
   * @return {String} The constructed class name
   * @method buildCSSClass
   */

  PlayToggle.prototype.buildCSSClass = function buildCSSClass() {
    return 'vjs-play-control ' + _Button.prototype.buildCSSClass.call(this);
  };

  /**
   * Handle click to toggle between play and pause
   *
   * @method handleClick
   */

  PlayToggle.prototype.handleClick = function handleClick() {
    if (this.player_.paused()) {
      this.player_.play();
    } else {
      this.player_.pause();
    }
  };

  /**
   * Add the vjs-playing class to the element so it can change appearance
   *
   * @method handlePlay
   */

  PlayToggle.prototype.handlePlay = function handlePlay() {
    this.removeClass('vjs-paused');
    this.addClass('vjs-playing');
    this.controlText('Pause'); // change the button text to "Pause"
  };

  /**
   * Add the vjs-paused class to the element so it can change appearance
   *
   * @method handlePause
   */

  PlayToggle.prototype.handlePause = function handlePause() {
    this.removeClass('vjs-playing');
    this.addClass('vjs-paused');
    this.controlText('Play'); // change the button text to "Play"
  };

  return PlayToggle;
})(_buttonJs2['default']);

PlayToggle.prototype.controlText_ = 'Play';

_componentJs2['default'].registerComponent('PlayToggle', PlayToggle);
exports['default'] = PlayToggle;
module.exports = exports['default'];

},{"../button.js":63,"../component.js":65}],71:[function(_dereq_,module,exports){
/**
 * @file playback-rate-menu-button.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _menuMenuButtonJs = _dereq_('../../menu/menu-button.js');

var _menuMenuButtonJs2 = _interopRequireDefault(_menuMenuButtonJs);

var _menuMenuJs = _dereq_('../../menu/menu.js');

var _menuMenuJs2 = _interopRequireDefault(_menuMenuJs);

var _playbackRateMenuItemJs = _dereq_('./playback-rate-menu-item.js');

var _playbackRateMenuItemJs2 = _interopRequireDefault(_playbackRateMenuItemJs);

var _componentJs = _dereq_('../../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

var _utilsDomJs = _dereq_('../../utils/dom.js');

var Dom = _interopRequireWildcard(_utilsDomJs);

/**
 * The component for controlling the playback rate
 *
 * @param {Player|Object} player
 * @param {Object=} options
 * @extends MenuButton
 * @class PlaybackRateMenuButton
 */

var PlaybackRateMenuButton = (function (_MenuButton) {
  _inherits(PlaybackRateMenuButton, _MenuButton);

  function PlaybackRateMenuButton(player, options) {
    _classCallCheck(this, PlaybackRateMenuButton);

    _MenuButton.call(this, player, options);

    this.updateVisibility();
    this.updateLabel();

    this.on(player, 'loadstart', this.updateVisibility);
    this.on(player, 'ratechange', this.updateLabel);
  }

  /**
   * Create the component's DOM element
   *
   * @return {Element}
   * @method createEl
   */

  PlaybackRateMenuButton.prototype.createEl = function createEl() {
    var el = _MenuButton.prototype.createEl.call(this);

    this.labelEl_ = Dom.createEl('div', {
      className: 'vjs-playback-rate-value',
      innerHTML: 1.0
    });

    el.appendChild(this.labelEl_);

    return el;
  };

  /**
   * Allow sub components to stack CSS class names
   *
   * @return {String} The constructed class name
   * @method buildCSSClass
   */

  PlaybackRateMenuButton.prototype.buildCSSClass = function buildCSSClass() {
    return 'vjs-playback-rate ' + _MenuButton.prototype.buildCSSClass.call(this);
  };

  /**
   * Create the playback rate menu
   *
   * @return {Menu} Menu object populated with items
   * @method createMenu
   */

  PlaybackRateMenuButton.prototype.createMenu = function createMenu() {
    var menu = new _menuMenuJs2['default'](this.player());
    var rates = this.playbackRates();

    if (rates) {
      for (var i = rates.length - 1; i >= 0; i--) {
        menu.addChild(new _playbackRateMenuItemJs2['default'](this.player(), { 'rate': rates[i] + 'x' }));
      }
    }

    return menu;
  };

  /**
   * Updates ARIA accessibility attributes
   *
   * @method updateARIAAttributes
   */

  PlaybackRateMenuButton.prototype.updateARIAAttributes = function updateARIAAttributes() {
    // Current playback rate
    this.el().setAttribute('aria-valuenow', this.player().playbackRate());
  };

  /**
   * Handle menu item click
   *
   * @method handleClick
   */

  PlaybackRateMenuButton.prototype.handleClick = function handleClick() {
    // select next rate option
    var currentRate = this.player().playbackRate();
    var rates = this.playbackRates();

    // this will select first one if the last one currently selected
    var newRate = rates[0];
    for (var i = 0; i < rates.length; i++) {
      if (rates[i] > currentRate) {
        newRate = rates[i];
        break;
      }
    }
    this.player().playbackRate(newRate);
  };

  /**
   * Get possible playback rates
   *
   * @return {Array} Possible playback rates
   * @method playbackRates
   */

  PlaybackRateMenuButton.prototype.playbackRates = function playbackRates() {
    return this.options_['playbackRates'] || this.options_.playerOptions && this.options_.playerOptions['playbackRates'];
  };

  /**
   * Get supported playback rates
   *
   * @return {Array} Supported playback rates
   * @method playbackRateSupported
   */

  PlaybackRateMenuButton.prototype.playbackRateSupported = function playbackRateSupported() {
    return this.player().tech_ && this.player().tech_['featuresPlaybackRate'] && this.playbackRates() && this.playbackRates().length > 0;
  };

  /**
   * Hide playback rate controls when they're no playback rate options to select
   *
   * @method updateVisibility
   */

  PlaybackRateMenuButton.prototype.updateVisibility = function updateVisibility() {
    if (this.playbackRateSupported()) {
      this.removeClass('vjs-hidden');
    } else {
      this.addClass('vjs-hidden');
    }
  };

  /**
   * Update button label when rate changed
   *
   * @method updateLabel
   */

  PlaybackRateMenuButton.prototype.updateLabel = function updateLabel() {
    if (this.playbackRateSupported()) {
      this.labelEl_.innerHTML = this.player().playbackRate() + 'x';
    }
  };

  return PlaybackRateMenuButton;
})(_menuMenuButtonJs2['default']);

PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate';

_componentJs2['default'].registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton);
exports['default'] = PlaybackRateMenuButton;
module.exports = exports['default'];

},{"../../component.js":65,"../../menu/menu-button.js":102,"../../menu/menu.js":104,"../../utils/dom.js":128,"./playback-rate-menu-item.js":72}],72:[function(_dereq_,module,exports){
/**
 * @file playback-rate-menu-item.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _menuMenuItemJs = _dereq_('../../menu/menu-item.js');

var _menuMenuItemJs2 = _interopRequireDefault(_menuMenuItemJs);

var _componentJs = _dereq_('../../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

/**
 * The specific menu item type for selecting a playback rate
 *
 * @param {Player|Object} player
 * @param {Object=} options
 * @extends MenuItem
 * @class PlaybackRateMenuItem
 */

var PlaybackRateMenuItem = (function (_MenuItem) {
  _inherits(PlaybackRateMenuItem, _MenuItem);

  function PlaybackRateMenuItem(player, options) {
    _classCallCheck(this, PlaybackRateMenuItem);

    var label = options['rate'];
    var rate = parseFloat(label, 10);

    // Modify options for parent MenuItem class's init.
    options['label'] = label;
    options['selected'] = rate === 1;
    _MenuItem.call(this, player, options);

    this.label = label;
    this.rate = rate;

    this.on(player, 'ratechange', this.update);
  }

  /**
   * Handle click on menu item
   *
   * @method handleClick
   */

  PlaybackRateMenuItem.prototype.handleClick = function handleClick() {
    _MenuItem.prototype.handleClick.call(this);
    this.player().playbackRate(this.rate);
  };

  /**
   * Update playback rate with selected rate
   *
   * @method update
   */

  PlaybackRateMenuItem.prototype.update = function update() {
    this.selected(this.player().playbackRate() === this.rate);
  };

  return PlaybackRateMenuItem;
})(_menuMenuItemJs2['default']);

PlaybackRateMenuItem.prototype.contentElType = 'button';

_componentJs2['default'].registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem);
exports['default'] = PlaybackRateMenuItem;
module.exports = exports['default'];

},{"../../component.js":65,"../../menu/menu-item.js":103}],73:[function(_dereq_,module,exports){
/**
 * @file load-progress-bar.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _componentJs = _dereq_('../../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

var _utilsDomJs = _dereq_('../../utils/dom.js');

var Dom = _interopRequireWildcard(_utilsDomJs);

/**
 * Shows load progress
 *
 * @param {Player|Object} player
 * @param {Object=} options
 * @extends Component
 * @class LoadProgressBar
 */

var LoadProgressBar = (function (_Component) {
  _inherits(LoadProgressBar, _Component);

  function LoadProgressBar(player, options) {
    _classCallCheck(this, LoadProgressBar);

    _Component.call(this, player, options);
    this.on(player, 'progress', this.update);
  }

  /**
   * Create the component's DOM element
   *
   * @return {Element}
   * @method createEl
   */

  LoadProgressBar.prototype.createEl = function createEl() {
    return _Component.prototype.createEl.call(this, 'div', {
      className: 'vjs-load-progress',
      innerHTML: '<span class="vjs-control-text"><span>' + this.localize('Loaded') + '</span>: 0%</span>'
    });
  };

  /**
   * Update progress bar
   *
   * @method update
   */

  LoadProgressBar.prototype.update = function update() {
    var buffered = this.player_.buffered();
    var duration = this.player_.duration();
    var bufferedEnd = this.player_.bufferedEnd();
    var children = this.el_.children;

    // get the percent width of a time compared to the total end
    var percentify = function percentify(time, end) {
      var percent = time / end || 0; // no NaN
      return (percent >= 1 ? 1 : percent) * 100 + '%';
    };

    // update the width of the progress bar
    this.el_.style.width = percentify(bufferedEnd, duration);

    // add child elements to represent the individual buffered time ranges
    for (var i = 0; i < buffered.length; i++) {
      var start = buffered.start(i);
      var end = buffered.end(i);
      var part = children[i];

      if (!part) {
        part = this.el_.appendChild(Dom.createEl());
      }

      // set the percent based on the width of the progress bar (bufferedEnd)
      part.style.left = percentify(start, bufferedEnd);
      part.style.width = percentify(end - start, bufferedEnd);
    }

    // remove unused buffered range elements
    for (var i = children.length; i > buffered.length; i--) {
      this.el_.removeChild(children[i - 1]);
    }
  };

  return LoadProgressBar;
})(_componentJs2['default']);

_componentJs2['default'].registerComponent('LoadProgressBar', LoadProgressBar);
exports['default'] = LoadProgressBar;
module.exports = exports['default'];

},{"../../component.js":65,"../../utils/dom.js":128}],74:[function(_dereq_,module,exports){
/**
 * @file mouse-time-display.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _componentJs = _dereq_('../../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

var _utilsDomJs = _dereq_('../../utils/dom.js');

var Dom = _interopRequireWildcard(_utilsDomJs);

var _utilsFnJs = _dereq_('../../utils/fn.js');

var Fn = _interopRequireWildcard(_utilsFnJs);

var _utilsFormatTimeJs = _dereq_('../../utils/format-time.js');

var _utilsFormatTimeJs2 = _interopRequireDefault(_utilsFormatTimeJs);

var _lodashCompatFunctionThrottle = _dereq_('lodash-compat/function/throttle');

var _lodashCompatFunctionThrottle2 = _interopRequireDefault(_lodashCompatFunctionThrottle);

/**
 * The Mouse Time Display component shows the time you will seek to
 * when hovering over the progress bar
 *
 * @param {Player|Object} player
 * @param {Object=} options
 * @extends Component
 * @class MouseTimeDisplay
 */

var MouseTimeDisplay = (function (_Component) {
  _inherits(MouseTimeDisplay, _Component);

  function MouseTimeDisplay(player, options) {
    var _this = this;

    _classCallCheck(this, MouseTimeDisplay);

    _Component.call(this, player, options);

    this.update(0, 0);

    player.on('ready', function () {
      _this.on(player.controlBar.progressControl.el(), 'mousemove', _lodashCompatFunctionThrottle2['default'](Fn.bind(_this, _this.handleMouseMove), 25));
    });
  }

  /**
   * Create the component's DOM element
   *
   * @return {Element}
   * @method createEl
   */

  MouseTimeDisplay.prototype.createEl = function createEl() {
    return _Component.prototype.createEl.call(this, 'div', {
      className: 'vjs-mouse-display'
    });
  };

  MouseTimeDisplay.prototype.handleMouseMove = function handleMouseMove(event) {
    var duration = this.player_.duration();
    var newTime = this.calculateDistance(event) * duration;
    var position = event.pageX - Dom.findElPosition(this.el().parentNode).left;

    this.update(newTime, position);
  };

  MouseTimeDisplay.prototype.update = function update(newTime, position) {
    var time = _utilsFormatTimeJs2['default'](newTime, this.player_.duration());

    this.el().style.left = position + 'px';
    this.el().setAttribute('data-current-time', time);
  };

  MouseTimeDisplay.prototype.calculateDistance = function calculateDistance(event) {
    return Dom.getPointerPosition(this.el().parentNode, event).x;
  };

  return MouseTimeDisplay;
})(_componentJs2['default']);

_componentJs2['default'].registerComponent('MouseTimeDisplay', MouseTimeDisplay);
exports['default'] = MouseTimeDisplay;
module.exports = exports['default'];

},{"../../component.js":65,"../../utils/dom.js":128,"../../utils/fn.js":130,"../../utils/format-time.js":131,"lodash-compat/function/throttle":7}],75:[function(_dereq_,module,exports){
/**
 * @file play-progress-bar.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _componentJs = _dereq_('../../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

var _utilsFnJs = _dereq_('../../utils/fn.js');

var Fn = _interopRequireWildcard(_utilsFnJs);

var _utilsFormatTimeJs = _dereq_('../../utils/format-time.js');

var _utilsFormatTimeJs2 = _interopRequireDefault(_utilsFormatTimeJs);

/**
 * Shows play progress
 *
 * @param {Player|Object} player
 * @param {Object=} options
 * @extends Component
 * @class PlayProgressBar
 */

var PlayProgressBar = (function (_Component) {
  _inherits(PlayProgressBar, _Component);

  function PlayProgressBar(player, options) {
    _classCallCheck(this, PlayProgressBar);

    _Component.call(this, player, options);
    this.updateDataAttr();
    this.on(player, 'timeupdate', this.updateDataAttr);
    player.ready(Fn.bind(this, this.updateDataAttr));
  }

  /**
   * Create the component's DOM element
   *
   * @return {Element}
   * @method createEl
   */

  PlayProgressBar.prototype.createEl = function createEl() {
    return _Component.prototype.createEl.call(this, 'div', {
      className: 'vjs-play-progress vjs-slider-bar',
      innerHTML: '<span class="vjs-control-text"><span>' + this.localize('Progress') + '</span>: 0%</span>'
    });
  };

  PlayProgressBar.prototype.updateDataAttr = function updateDataAttr() {
    var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
    this.el_.setAttribute('data-current-time', _utilsFormatTimeJs2['default'](time, this.player_.duration()));
  };

  return PlayProgressBar;
})(_componentJs2['default']);

_componentJs2['default'].registerComponent('PlayProgressBar', PlayProgressBar);
exports['default'] = PlayProgressBar;
module.exports = exports['default'];

},{"../../component.js":65,"../../utils/fn.js":130,"../../utils/format-time.js":131}],76:[function(_dereq_,module,exports){
/**
 * @file progress-control.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _componentJs = _dereq_('../../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

var _seekBarJs = _dereq_('./seek-bar.js');

var _seekBarJs2 = _interopRequireDefault(_seekBarJs);

var _mouseTimeDisplayJs = _dereq_('./mouse-time-display.js');

var _mouseTimeDisplayJs2 = _interopRequireDefault(_mouseTimeDisplayJs);

/**
 * The Progress Control component contains the seek bar, load progress,
 * and play progress
 *
 * @param {Player|Object} player
 * @param {Object=} options
 * @extends Component
 * @class ProgressControl
 */

var ProgressControl = (function (_Component) {
  _inherits(ProgressControl, _Component);

  function ProgressControl() {
    _classCallCheck(this, ProgressControl);

    _Component.apply(this, arguments);
  }

  /**
   * Create the component's DOM element
   *
   * @return {Element}
   * @method createEl
   */

  ProgressControl.prototype.createEl = function createEl() {
    return _Component.prototype.createEl.call(this, 'div', {
      className: 'vjs-progress-control vjs-control'
    });
  };

  return ProgressControl;
})(_componentJs2['default']);

ProgressControl.prototype.options_ = {
  children: ['seekBar']
};

_componentJs2['default'].registerComponent('ProgressControl', ProgressControl);
exports['default'] = ProgressControl;
module.exports = exports['default'];

},{"../../component.js":65,"./mouse-time-display.js":74,"./seek-bar.js":77}],77:[function(_dereq_,module,exports){
/**
 * @file seek-bar.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _sliderSliderJs = _dereq_('../../slider/slider.js');

var _sliderSliderJs2 = _interopRequireDefault(_sliderSliderJs);

var _componentJs = _dereq_('../../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

var _loadProgressBarJs = _dereq_('./load-progress-bar.js');

var _loadProgressBarJs2 = _interopRequireDefault(_loadProgressBarJs);

var _playProgressBarJs = _dereq_('./play-progress-bar.js');

var _playProgressBarJs2 = _interopRequireDefault(_playProgressBarJs);

var _utilsFnJs = _dereq_('../../utils/fn.js');

var Fn = _interopRequireWildcard(_utilsFnJs);

var _utilsFormatTimeJs = _dereq_('../../utils/format-time.js');

var _utilsFormatTimeJs2 = _interopRequireDefault(_utilsFormatTimeJs);

var _objectAssign = _dereq_('object.assign');

var _objectAssign2 = _interopRequireDefault(_objectAssign);

/**
 * Seek Bar and holder for the progress bars
 *
 * @param {Player|Object} player
 * @param {Object=} options
 * @extends Slider
 * @class SeekBar
 */

var SeekBar = (function (_Slider) {
  _inherits(SeekBar, _Slider);

  function SeekBar(player, options) {
    _classCallCheck(this, SeekBar);

    _Slider.call(this, player, options);
    this.on(player, 'timeupdate', this.updateARIAAttributes);
    player.ready(Fn.bind(this, this.updateARIAAttributes));
  }

  /**
   * Create the component's DOM element
   *
   * @return {Element}
   * @method createEl
   */

  SeekBar.prototype.createEl = function createEl() {
    return _Slider.prototype.createEl.call(this, 'div', {
      className: 'vjs-progress-holder'
    }, {
      'aria-label': 'video progress bar'
    });
  };

  /**
   * Update ARIA accessibility attributes
   *
   * @method updateARIAAttributes
   */

  SeekBar.prototype.updateARIAAttributes = function updateARIAAttributes() {
    // Allows for smooth scrubbing, when player can't keep up.
    var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
    this.el_.setAttribute('aria-valuenow', (this.getPercent() * 100).toFixed(2)); // machine readable value of progress bar (percentage complete)
    this.el_.setAttribute('aria-valuetext', _utilsFormatTimeJs2['default'](time, this.player_.duration())); // human readable value of progress bar (time complete)
  };

  /**
   * Get percentage of video played
   *
   * @return {Number} Percentage played
   * @method getPercent
   */

  SeekBar.prototype.getPercent = function getPercent() {
    var percent = this.player_.currentTime() / this.player_.duration();
    return percent >= 1 ? 1 : percent;
  };

  /**
   * Handle mouse down on seek bar
   *
   * @method handleMouseDown
   */

  SeekBar.prototype.handleMouseDown = function handleMouseDown(event) {
    _Slider.prototype.handleMouseDown.call(this, event);

    this.player_.scrubbing(true);

    this.videoWasPlaying = !this.player_.paused();
    this.player_.pause();
  };

  /**
   * Handle mouse move on seek bar
   *
   * @method handleMouseMove
   */

  SeekBar.prototype.handleMouseMove = function handleMouseMove(event) {
    var newTime = this.calculateDistance(event) * this.player_.duration();

    // Don't let video end while scrubbing.
    if (newTime === this.player_.duration()) {
      newTime = newTime - 0.1;
    }

    // Set new time (tell player to seek to new time)
    this.player_.currentTime(newTime);
  };

  /**
   * Handle mouse up on seek bar
   *
   * @method handleMouseUp
   */

  SeekBar.prototype.handleMouseUp = function handleMouseUp(event) {
    _Slider.prototype.handleMouseUp.call(this, event);

    this.player_.scrubbing(false);
    if (this.videoWasPlaying) {
      this.player_.play();
    }
  };

  /**
   * Move more quickly fast forward for keyboard-only users
   *
   * @method stepForward
   */

  SeekBar.prototype.stepForward = function stepForward() {
    this.player_.currentTime(this.player_.currentTime() + 5); // more quickly fast forward for keyboard-only users
  };

  /**
   * Move more quickly rewind for keyboard-only users
   *
   * @method stepBack
   */

  SeekBar.prototype.stepBack = function stepBack() {
    this.player_.currentTime(this.player_.currentTime() - 5); // more quickly rewind for keyboard-only users
  };

  return SeekBar;
})(_sliderSliderJs2['default']);

SeekBar.prototype.options_ = {
  children: ['loadProgressBar', 'mouseTimeDisplay', 'playProgressBar'],
  'barName': 'playProgressBar'
};

SeekBar.prototype.playerEvent = 'timeupdate';

_componentJs2['default'].registerComponent('SeekBar', SeekBar);
exports['default'] = SeekBar;
module.exports = exports['default'];

},{"../../component.js":65,"../../slider/slider.js":110,"../../utils/fn.js":130,"../../utils/format-time.js":131,"./load-progress-bar.js":73,"./play-progress-bar.js":75,"object.assign":45}],78:[function(_dereq_,module,exports){
/**
 * @file custom-control-spacer.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _spacerJs = _dereq_('./spacer.js');

var _spacerJs2 = _interopRequireDefault(_spacerJs);

var _componentJs = _dereq_('../../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

/**
 * Spacer specifically meant to be used as an insertion point for new plugins, etc.
 *
 * @extends Spacer
 * @class CustomControlSpacer
 */

var CustomControlSpacer = (function (_Spacer) {
  _inherits(CustomControlSpacer, _Spacer);

  function CustomControlSpacer() {
    _classCallCheck(this, CustomControlSpacer);

    _Spacer.apply(this, arguments);
  }

  /**
   * Allow sub components to stack CSS class names
   *
   * @return {String} The constructed class name
   * @method buildCSSClass
   */

  CustomControlSpacer.prototype.buildCSSClass = function buildCSSClass() {
    return 'vjs-custom-control-spacer ' + _Spacer.prototype.buildCSSClass.call(this);
  };

  /**
   * Create the component's DOM element
   *
   * @return {Element}
   * @method createEl
   */

  CustomControlSpacer.prototype.createEl = function createEl() {
    var el = _Spacer.prototype.createEl.call(this, {
      className: this.buildCSSClass()
    });

    // No-flex/table-cell mode requires there be some content
    // in the cell to fill the remaining space of the table.
    el.innerHTML = '&nbsp;';
    return el;
  };

  return CustomControlSpacer;
})(_spacerJs2['default']);

_componentJs2['default'].registerComponent('CustomControlSpacer', CustomControlSpacer);
exports['default'] = CustomControlSpacer;
module.exports = exports['default'];

},{"../../component.js":65,"./spacer.js":79}],79:[function(_dereq_,module,exports){
/**
 * @file spacer.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _componentJs = _dereq_('../../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

/**
 * Just an empty spacer element that can be used as an append point for plugins, etc.
 * Also can be used to create space between elements when necessary.
 *
 * @extends Component
 * @class Spacer
 */

var Spacer = (function (_Component) {
  _inherits(Spacer, _Component);

  function Spacer() {
    _classCallCheck(this, Spacer);

    _Component.apply(this, arguments);
  }

  /**
   * Allow sub components to stack CSS class names
   *
   * @return {String} The constructed class name
   * @method buildCSSClass
   */

  Spacer.prototype.buildCSSClass = function buildCSSClass() {
    return 'vjs-spacer ' + _Component.prototype.buildCSSClass.call(this);
  };

  /**
   * Create the component's DOM element
   *
   * @return {Element}
   * @method createEl
   */

  Spacer.prototype.createEl = function createEl() {
    return _Component.prototype.createEl.call(this, 'div', {
      className: this.buildCSSClass()
    });
  };

  return Spacer;
})(_componentJs2['default']);

_componentJs2['default'].registerComponent('Spacer', Spacer);

exports['default'] = Spacer;
module.exports = exports['default'];

},{"../../component.js":65}],80:[function(_dereq_,module,exports){
/**
 * @file caption-settings-menu-item.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _textTrackMenuItemJs = _dereq_('./text-track-menu-item.js');

var _textTrackMenuItemJs2 = _interopRequireDefault(_textTrackMenuItemJs);

var _componentJs = _dereq_('../../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

/**
 * The menu item for caption track settings menu
 *
 * @param {Player|Object} player
 * @param {Object=} options
 * @extends TextTrackMenuItem
 * @class CaptionSettingsMenuItem
 */

var CaptionSettingsMenuItem = (function (_TextTrackMenuItem) {
  _inherits(CaptionSettingsMenuItem, _TextTrackMenuItem);

  function CaptionSettingsMenuItem(player, options) {
    _classCallCheck(this, CaptionSettingsMenuItem);

    options['track'] = {
      'kind': options['kind'],
      'player': player,
      'label': options['kind'] + ' settings',
      'default': false,
      mode: 'disabled'
    };

    _TextTrackMenuItem.call(this, player, options);
    this.addClass('vjs-texttrack-settings');
  }

  /**
   * Handle click on menu item
   *
   * @method handleClick
   */

  CaptionSettingsMenuItem.prototype.handleClick = function handleClick() {
    this.player().getChild('textTrackSettings').show();
  };

  return CaptionSettingsMenuItem;
})(_textTrackMenuItemJs2['default']);

_componentJs2['default'].registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem);
exports['default'] = CaptionSettingsMenuItem;
module.exports = exports['default'];

},{"../../component.js":65,"./text-track-menu-item.js":87}],81:[function(_dereq_,module,exports){
/**
 * @file captions-button.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _textTrackButtonJs = _dereq_('./text-track-button.js');

var _textTrackButtonJs2 = _interopRequireDefault(_textTrackButtonJs);

var _componentJs = _dereq_('../../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

var _captionSettingsMenuItemJs = _dereq_('./caption-settings-menu-item.js');

var _captionSettingsMenuItemJs2 = _interopRequireDefault(_captionSettingsMenuItemJs);

/**
 * The button component for toggling and selecting captions
 *
 * @param {Object} player  Player object
 * @param {Object=} options Object of option names and values
 * @param {Function=} ready    Ready callback function
 * @extends TextTrackButton
 * @class CaptionsButton
 */

var CaptionsButton = (function (_TextTrackButton) {
  _inherits(CaptionsButton, _TextTrackButton);

  function CaptionsButton(player, options, ready) {
    _classCallCheck(this, CaptionsButton);

    _TextTrackButton.call(this, player, options, ready);
    this.el_.setAttribute('aria-label', 'Captions Menu');
  }

  /**
   * Allow sub components to stack CSS class names
   *
   * @return {String} The constructed class name
   * @method buildCSSClass
   */

  CaptionsButton.prototype.buildCSSClass = function buildCSSClass() {
    return 'vjs-captions-button ' + _TextTrackButton.prototype.buildCSSClass.call(this);
  };

  /**
   * Update caption menu items
   *
   * @method update
   */

  CaptionsButton.prototype.update = function update() {
    var threshold = 2;
    _TextTrackButton.prototype.update.call(this);

    // if native, then threshold is 1 because no settings button
    if (this.player().tech_ && this.player().tech_['featuresNativeTextTracks']) {
      threshold = 1;
    }

    if (this.items && this.items.length > threshold) {
      this.show();
    } else {
      this.hide();
    }
  };

  /**
   * Create caption menu items
   *
   * @return {Array} Array of menu items
   * @method createItems
   */

  CaptionsButton.prototype.createItems = function createItems() {
    var items = [];

    if (!(this.player().tech_ && this.player().tech_['featuresNativeTextTracks'])) {
      items.push(new _captionSettingsMenuItemJs2['default'](this.player_, { 'kind': this.kind_ }));
    }

    return _TextTrackButton.prototype.createItems.call(this, items);
  };

  return CaptionsButton;
})(_textTrackButtonJs2['default']);

CaptionsButton.prototype.kind_ = 'captions';
CaptionsButton.prototype.controlText_ = 'Captions';

_componentJs2['default'].registerComponent('CaptionsButton', CaptionsButton);
exports['default'] = CaptionsButton;
module.exports = exports['default'];

},{"../../component.js":65,"./caption-settings-menu-item.js":80,"./text-track-button.js":86}],82:[function(_dereq_,module,exports){
/**
 * @file chapters-button.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _textTrackButtonJs = _dereq_('./text-track-button.js');

var _textTrackButtonJs2 = _interopRequireDefault(_textTrackButtonJs);

var _componentJs = _dereq_('../../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

var _textTrackMenuItemJs = _dereq_('./text-track-menu-item.js');

var _textTrackMenuItemJs2 = _interopRequireDefault(_textTrackMenuItemJs);

var _chaptersTrackMenuItemJs = _dereq_('./chapters-track-menu-item.js');

var _chaptersTrackMenuItemJs2 = _interopRequireDefault(_chaptersTrackMenuItemJs);

var _menuMenuJs = _dereq_('../../menu/menu.js');

var _menuMenuJs2 = _interopRequireDefault(_menuMenuJs);

var _utilsDomJs = _dereq_('../../utils/dom.js');

var Dom = _interopRequireWildcard(_utilsDomJs);

var _utilsFnJs = _dereq_('../../utils/fn.js');

var Fn = _interopRequireWildcard(_utilsFnJs);

var _utilsToTitleCaseJs = _dereq_('../../utils/to-title-case.js');

var _utilsToTitleCaseJs2 = _interopRequireDefault(_utilsToTitleCaseJs);

var _globalWindow = _dereq_('global/window');

var _globalWindow2 = _interopRequireDefault(_globalWindow);

/**
 * The button component for toggling and selecting chapters
 * Chapters act much differently than other text tracks
 * Cues are navigation vs. other tracks of alternative languages
 *
 * @param {Object} player  Player object
 * @param {Object=} options Object of option names and values
 * @param {Function=} ready    Ready callback function
 * @extends TextTrackButton
 * @class ChaptersButton
 */

var ChaptersButton = (function (_TextTrackButton) {
  _inherits(ChaptersButton, _TextTrackButton);

  function ChaptersButton(player, options, ready) {
    _classCallCheck(this, ChaptersButton);

    _TextTrackButton.call(this, player, options, ready);
    this.el_.setAttribute('aria-label', 'Chapters Menu');
  }

  /**
   * Allow sub components to stack CSS class names
   *
   * @return {String} The constructed class name
   * @method buildCSSClass
   */

  ChaptersButton.prototype.buildCSSClass = function buildCSSClass() {
    return 'vjs-chapters-button ' + _TextTrackButton.prototype.buildCSSClass.call(this);
  };

  /**
   * Create a menu item for each text track
   *
   * @return {Array} Array of menu items
   * @method createItems
   */

  ChaptersButton.prototype.createItems = function createItems() {
    var items = [];

    var tracks = this.player_.textTracks();

    if (!tracks) {
      return items;
    }

    for (var i = 0; i < tracks.length; i++) {
      var track = tracks[i];
      if (track['kind'] === this.kind_) {
        items.push(new _textTrackMenuItemJs2['default'](this.player_, {
          'track': track
        }));
      }
    }

    return items;
  };

  /**
   * Create menu from chapter buttons
   *
   * @return {Menu} Menu of chapter buttons
   * @method createMenu
   */

  ChaptersButton.prototype.createMenu = function createMenu() {
    var _this = this;

    var tracks = this.player_.textTracks() || [];
    var chaptersTrack = undefined;
    var items = this.items = [];

    for (var i = 0, _length = tracks.length; i < _length; i++) {
      var track = tracks[i];

      if (track['kind'] === this.kind_) {
        chaptersTrack = track;

        break;
      }
    }

    var menu = this.menu;
    if (menu === undefined) {
      menu = new _menuMenuJs2['default'](this.player_);
      menu.contentEl().appendChild(Dom.createEl('li', {
        className: 'vjs-menu-title',
        innerHTML: _utilsToTitleCaseJs2['default'](this.kind_),
        tabIndex: -1
      }));
    }

    if (chaptersTrack && chaptersTrack.cues == null) {
      chaptersTrack['mode'] = 'hidden';

      var remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(chaptersTrack);

      if (remoteTextTrackEl) {
        remoteTextTrackEl.addEventListener('load', function (event) {
          return _this.update();
        });
      }
    }

    if (chaptersTrack && chaptersTrack.cues && chaptersTrack.cues.length > 0) {
      var cues = chaptersTrack['cues'],
          cue = undefined;

      for (var i = 0, l = cues.length; i < l; i++) {
        cue = cues[i];

        var mi = new _chaptersTrackMenuItemJs2['default'](this.player_, {
          'track': chaptersTrack,
          'cue': cue
        });

        items.push(mi);

        menu.addChild(mi);
      }

      this.addChild(menu);
    }

    if (this.items.length > 0) {
      this.show();
    }

    return menu;
  };

  return ChaptersButton;
})(_textTrackButtonJs2['default']);

ChaptersButton.prototype.kind_ = 'chapters';
ChaptersButton.prototype.controlText_ = 'Chapters';

_componentJs2['default'].registerComponent('ChaptersButton', ChaptersButton);
exports['default'] = ChaptersButton;
module.exports = exports['default'];

},{"../../component.js":65,"../../menu/menu.js":104,"../../utils/dom.js":128,"../../utils/fn.js":130,"../../utils/to-title-case.js":137,"./chapters-track-menu-item.js":83,"./text-track-button.js":86,"./text-track-menu-item.js":87,"global/window":2}],83:[function(_dereq_,module,exports){
/**
 * @file chapters-track-menu-item.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _menuMenuItemJs = _dereq_('../../menu/menu-item.js');

var _menuMenuItemJs2 = _interopRequireDefault(_menuMenuItemJs);

var _componentJs = _dereq_('../../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

var _utilsFnJs = _dereq_('../../utils/fn.js');

var Fn = _interopRequireWildcard(_utilsFnJs);

/**
 * The chapter track menu item
 *
 * @param {Player|Object} player
 * @param {Object=} options
 * @extends MenuItem
 * @class ChaptersTrackMenuItem
 */

var ChaptersTrackMenuItem = (function (_MenuItem) {
  _inherits(ChaptersTrackMenuItem, _MenuItem);

  function ChaptersTrackMenuItem(player, options) {
    _classCallCheck(this, ChaptersTrackMenuItem);

    var track = options['track'];
    var cue = options['cue'];
    var currentTime = player.currentTime();

    // Modify options for parent MenuItem class's init.
    options['label'] = cue.text;
    options['selected'] = cue['startTime'] <= currentTime && currentTime < cue['endTime'];
    _MenuItem.call(this, player, options);

    this.track = track;
    this.cue = cue;
    track.addEventListener('cuechange', Fn.bind(this, this.update));
  }

  /**
   * Handle click on menu item
   *
   * @method handleClick
   */

  ChaptersTrackMenuItem.prototype.handleClick = function handleClick() {
    _MenuItem.prototype.handleClick.call(this);
    this.player_.currentTime(this.cue.startTime);
    this.update(this.cue.startTime);
  };

  /**
   * Update chapter menu item
   *
   * @method update
   */

  ChaptersTrackMenuItem.prototype.update = function update() {
    var cue = this.cue;
    var currentTime = this.player_.currentTime();

    // vjs.log(currentTime, cue.startTime);
    this.selected(cue['startTime'] <= currentTime && currentTime < cue['endTime']);
  };

  return ChaptersTrackMenuItem;
})(_menuMenuItemJs2['default']);

_componentJs2['default'].registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem);
exports['default'] = ChaptersTrackMenuItem;
module.exports = exports['default'];

},{"../../component.js":65,"../../menu/menu-item.js":103,"../../utils/fn.js":130}],84:[function(_dereq_,module,exports){
/**
 * @file off-text-track-menu-item.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _textTrackMenuItemJs = _dereq_('./text-track-menu-item.js');

var _textTrackMenuItemJs2 = _interopRequireDefault(_textTrackMenuItemJs);

var _componentJs = _dereq_('../../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

/**
 * A special menu item for turning of a specific type of text track
 *
 * @param {Player|Object} player
 * @param {Object=} options
 * @extends TextTrackMenuItem
 * @class OffTextTrackMenuItem
 */

var OffTextTrackMenuItem = (function (_TextTrackMenuItem) {
  _inherits(OffTextTrackMenuItem, _TextTrackMenuItem);

  function OffTextTrackMenuItem(player, options) {
    _classCallCheck(this, OffTextTrackMenuItem);

    // Create pseudo track info
    // Requires options['kind']
    options['track'] = {
      'kind': options['kind'],
      'player': player,
      'label': options['kind'] + ' off',
      'default': false,
      'mode': 'disabled'
    };

    _TextTrackMenuItem.call(this, player, options);
    this.selected(true);
  }

  /**
   * Handle text track change
   *
   * @param {Object} event Event object
   * @method handleTracksChange
   */

  OffTextTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) {
    var tracks = this.player().textTracks();
    var selected = true;

    for (var i = 0, l = tracks.length; i < l; i++) {
      var track = tracks[i];
      if (track['kind'] === this.track['kind'] && track['mode'] === 'showing') {
        selected = false;
        break;
      }
    }

    this.selected(selected);
  };

  return OffTextTrackMenuItem;
})(_textTrackMenuItemJs2['default']);

_componentJs2['default'].registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem);
exports['default'] = OffTextTrackMenuItem;
module.exports = exports['default'];

},{"../../component.js":65,"./text-track-menu-item.js":87}],85:[function(_dereq_,module,exports){
/**
 * @file subtitles-button.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _textTrackButtonJs = _dereq_('./text-track-button.js');

var _textTrackButtonJs2 = _interopRequireDefault(_textTrackButtonJs);

var _componentJs = _dereq_('../../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

/**
 * The button component for toggling and selecting subtitles
 *
 * @param {Object} player  Player object
 * @param {Object=} options Object of option names and values
 * @param {Function=} ready    Ready callback function
 * @extends TextTrackButton
 * @class SubtitlesButton
 */

var SubtitlesButton = (function (_TextTrackButton) {
  _inherits(SubtitlesButton, _TextTrackButton);

  function SubtitlesButton(player, options, ready) {
    _classCallCheck(this, SubtitlesButton);

    _TextTrackButton.call(this, player, options, ready);
    this.el_.setAttribute('aria-label', 'Subtitles Menu');
  }

  /**
   * Allow sub components to stack CSS class names
   *
   * @return {String} The constructed class name
   * @method buildCSSClass
   */

  SubtitlesButton.prototype.buildCSSClass = function buildCSSClass() {
    return 'vjs-subtitles-button ' + _TextTrackButton.prototype.buildCSSClass.call(this);
  };

  return SubtitlesButton;
})(_textTrackButtonJs2['default']);

SubtitlesButton.prototype.kind_ = 'subtitles';
SubtitlesButton.prototype.controlText_ = 'Subtitles';

_componentJs2['default'].registerComponent('SubtitlesButton', SubtitlesButton);
exports['default'] = SubtitlesButton;
module.exports = exports['default'];

},{"../../component.js":65,"./text-track-button.js":86}],86:[function(_dereq_,module,exports){
/**
 * @file text-track-button.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _menuMenuButtonJs = _dereq_('../../menu/menu-button.js');

var _menuMenuButtonJs2 = _interopRequireDefault(_menuMenuButtonJs);

var _componentJs = _dereq_('../../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

var _utilsFnJs = _dereq_('../../utils/fn.js');

var Fn = _interopRequireWildcard(_utilsFnJs);

var _textTrackMenuItemJs = _dereq_('./text-track-menu-item.js');

var _textTrackMenuItemJs2 = _interopRequireDefault(_textTrackMenuItemJs);

var _offTextTrackMenuItemJs = _dereq_('./off-text-track-menu-item.js');

var _offTextTrackMenuItemJs2 = _interopRequireDefault(_offTextTrackMenuItemJs);

/**
 * The base class for buttons that toggle specific text track types (e.g. subtitles)
 *
 * @param {Player|Object} player
 * @param {Object=} options
 * @extends MenuButton
 * @class TextTrackButton
 */

var TextTrackButton = (function (_MenuButton) {
  _inherits(TextTrackButton, _MenuButton);

  function TextTrackButton(player, options) {
    _classCallCheck(this, TextTrackButton);

    _MenuButton.call(this, player, options);

    var tracks = this.player_.textTracks();

    if (this.items.length <= 1) {
      this.hide();
    }

    if (!tracks) {
      return;
    }

    var updateHandler = Fn.bind(this, this.update);
    tracks.addEventListener('removetrack', updateHandler);
    tracks.addEventListener('addtrack', updateHandler);

    this.player_.on('dispose', function () {
      tracks.removeEventListener('removetrack', updateHandler);
      tracks.removeEventListener('addtrack', updateHandler);
    });
  }

  // Create a menu item for each text track

  TextTrackButton.prototype.createItems = function createItems() {
    var items = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0];

    // Add an OFF menu item to turn all tracks off
    items.push(new _offTextTrackMenuItemJs2['default'](this.player_, { 'kind': this.kind_ }));

    var tracks = this.player_.textTracks();

    if (!tracks) {
      return items;
    }

    for (var i = 0; i < tracks.length; i++) {
      var track = tracks[i];

      // only add tracks that are of the appropriate kind and have a label
      if (track['kind'] === this.kind_) {
        items.push(new _textTrackMenuItemJs2['default'](this.player_, {
          'track': track
        }));
      }
    }

    return items;
  };

  return TextTrackButton;
})(_menuMenuButtonJs2['default']);

_componentJs2['default'].registerComponent('TextTrackButton', TextTrackButton);
exports['default'] = TextTrackButton;
module.exports = exports['default'];

},{"../../component.js":65,"../../menu/menu-button.js":102,"../../utils/fn.js":130,"./off-text-track-menu-item.js":84,"./text-track-menu-item.js":87}],87:[function(_dereq_,module,exports){
/**
 * @file text-track-menu-item.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _menuMenuItemJs = _dereq_('../../menu/menu-item.js');

var _menuMenuItemJs2 = _interopRequireDefault(_menuMenuItemJs);

var _componentJs = _dereq_('../../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

var _utilsFnJs = _dereq_('../../utils/fn.js');

var Fn = _interopRequireWildcard(_utilsFnJs);

var _globalWindow = _dereq_('global/window');

var _globalWindow2 = _interopRequireDefault(_globalWindow);

var _globalDocument = _dereq_('global/document');

var _globalDocument2 = _interopRequireDefault(_globalDocument);

/**
 * The specific menu item type for selecting a language within a text track kind
 *
 * @param {Player|Object} player
 * @param {Object=} options
 * @extends MenuItem
 * @class TextTrackMenuItem
 */

var TextTrackMenuItem = (function (_MenuItem) {
  _inherits(TextTrackMenuItem, _MenuItem);

  function TextTrackMenuItem(player, options) {
    var _this = this;

    _classCallCheck(this, TextTrackMenuItem);

    var track = options['track'];
    var tracks = player.textTracks();

    // Modify options for parent MenuItem class's init.
    options['label'] = track['label'] || track['language'] || 'Unknown';
    options['selected'] = track['default'] || track['mode'] === 'showing';
    _MenuItem.call(this, player, options);

    this.track = track;

    if (tracks) {
      (function () {
        var changeHandler = Fn.bind(_this, _this.handleTracksChange);

        tracks.addEventListener('change', changeHandler);
        _this.on('dispose', function () {
          tracks.removeEventListener('change', changeHandler);
        });
      })();
    }

    // iOS7 doesn't dispatch change events to TextTrackLists when an
    // associated track's mode changes. Without something like
    // Object.observe() (also not present on iOS7), it's not
    // possible to detect changes to the mode attribute and polyfill
    // the change event. As a poor substitute, we manually dispatch
    // change events whenever the controls modify the mode.
    if (tracks && tracks.onchange === undefined) {
      (function () {
        var event = undefined;

        _this.on(['tap', 'click'], function () {
          if (typeof _globalWindow2['default'].Event !== 'object') {
            // Android 2.3 throws an Illegal Constructor error for window.Event
            try {
              event = new _globalWindow2['default'].Event('change');
            } catch (err) {}
          }

          if (!event) {
            event = _globalDocument2['default'].createEvent('Event');
            event.initEvent('change', true, true);
          }

          tracks.dispatchEvent(event);
        });
      })();
    }
  }

  /**
   * Handle click on text track
   *
   * @method handleClick
   */

  TextTrackMenuItem.prototype.handleClick = function handleClick(event) {
    var kind = this.track['kind'];
    var tracks = this.player_.textTracks();

    _MenuItem.prototype.handleClick.call(this, event);

    if (!tracks) return;

    for (var i = 0; i < tracks.length; i++) {
      var track = tracks[i];

      if (track['kind'] !== kind) {
        continue;
      }

      if (track === this.track) {
        track['mode'] = 'showing';
      } else {
        track['mode'] = 'disabled';
      }
    }
  };

  /**
   * Handle text track change
   *
   * @method handleTracksChange
   */

  TextTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) {
    this.selected(this.track['mode'] === 'showing');
  };

  return TextTrackMenuItem;
})(_menuMenuItemJs2['default']);

_componentJs2['default'].registerComponent('TextTrackMenuItem', TextTrackMenuItem);
exports['default'] = TextTrackMenuItem;
module.exports = exports['default'];

},{"../../component.js":65,"../../menu/menu-item.js":103,"../../utils/fn.js":130,"global/document":1,"global/window":2}],88:[function(_dereq_,module,exports){
/**
 * @file current-time-display.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _componentJs = _dereq_('../../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

var _utilsDomJs = _dereq_('../../utils/dom.js');

var Dom = _interopRequireWildcard(_utilsDomJs);

var _utilsFormatTimeJs = _dereq_('../../utils/format-time.js');

var _utilsFormatTimeJs2 = _interopRequireDefault(_utilsFormatTimeJs);

/**
 * Displays the current time
 *
 * @param {Player|Object} player
 * @param {Object=} options
 * @extends Component
 * @class CurrentTimeDisplay
 */

var CurrentTimeDisplay = (function (_Component) {
  _inherits(CurrentTimeDisplay, _Component);

  function CurrentTimeDisplay(player, options) {
    _classCallCheck(this, CurrentTimeDisplay);

    _Component.call(this, player, options);

    this.on(player, 'timeupdate', this.updateContent);
  }

  /**
   * Create the component's DOM element
   *
   * @return {Element}
   * @method createEl
   */

  CurrentTimeDisplay.prototype.createEl = function createEl() {
    var el = _Component.prototype.createEl.call(this, 'div', {
      className: 'vjs-current-time vjs-time-control vjs-control'
    });

    this.contentEl_ = Dom.createEl('div', {
      className: 'vjs-current-time-display',
      // label the current time for screen reader users
      innerHTML: '<span class="vjs-control-text">Current Time </span>' + '0:00'
    }, {
      // tell screen readers not to automatically read the time as it changes
      'aria-live': 'off'
    });

    el.appendChild(this.contentEl_);
    return el;
  };

  /**
   * Update current time display
   *
   * @method updateContent
   */

  CurrentTimeDisplay.prototype.updateContent = function updateContent() {
    // Allows for smooth scrubbing, when player can't keep up.
    var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
    var localizedText = this.localize('Current Time');
    var formattedTime = _utilsFormatTimeJs2['default'](time, this.player_.duration());
    this.contentEl_.innerHTML = '<span class="vjs-control-text">' + localizedText + '</span> ' + formattedTime;
  };

  return CurrentTimeDisplay;
})(_componentJs2['default']);

_componentJs2['default'].registerComponent('CurrentTimeDisplay', CurrentTimeDisplay);
exports['default'] = CurrentTimeDisplay;
module.exports = exports['default'];

},{"../../component.js":65,"../../utils/dom.js":128,"../../utils/format-time.js":131}],89:[function(_dereq_,module,exports){
/**
 * @file duration-display.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _componentJs = _dereq_('../../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

var _utilsDomJs = _dereq_('../../utils/dom.js');

var Dom = _interopRequireWildcard(_utilsDomJs);

var _utilsFormatTimeJs = _dereq_('../../utils/format-time.js');

var _utilsFormatTimeJs2 = _interopRequireDefault(_utilsFormatTimeJs);

/**
 * Displays the duration
 *
 * @param {Player|Object} player
 * @param {Object=} options
 * @extends Component
 * @class DurationDisplay
 */

var DurationDisplay = (function (_Component) {
  _inherits(DurationDisplay, _Component);

  function DurationDisplay(player, options) {
    _classCallCheck(this, DurationDisplay);

    _Component.call(this, player, options);

    // this might need to be changed to 'durationchange' instead of 'timeupdate' eventually,
    // however the durationchange event fires before this.player_.duration() is set,
    // so the value cannot be written out using this method.
    // Once the order of durationchange and this.player_.duration() being set is figured out,
    // this can be updated.
    this.on(player, 'timeupdate', this.updateContent);
    this.on(player, 'loadedmetadata', this.updateContent);
  }

  /**
   * Create the component's DOM element
   *
   * @return {Element}
   * @method createEl
   */

  DurationDisplay.prototype.createEl = function createEl() {
    var el = _Component.prototype.createEl.call(this, 'div', {
      className: 'vjs-duration vjs-time-control vjs-control'
    });

    this.contentEl_ = Dom.createEl('div', {
      className: 'vjs-duration-display',
      // label the duration time for screen reader users
      innerHTML: '<span class="vjs-control-text">' + this.localize('Duration Time') + '</span> 0:00'
    }, {
      // tell screen readers not to automatically read the time as it changes
      'aria-live': 'off'
    });

    el.appendChild(this.contentEl_);
    return el;
  };

  /**
   * Update duration time display
   *
   * @method updateContent
   */

  DurationDisplay.prototype.updateContent = function updateContent() {
    var duration = this.player_.duration();
    if (duration) {
      var localizedText = this.localize('Duration Time');
      var formattedTime = _utilsFormatTimeJs2['default'](duration);
      this.contentEl_.innerHTML = '<span class="vjs-control-text">' + localizedText + '</span> ' + formattedTime; // label the duration time for screen reader users
    }
  };

  return DurationDisplay;
})(_componentJs2['default']);

_componentJs2['default'].registerComponent('DurationDisplay', DurationDisplay);
exports['default'] = DurationDisplay;
module.exports = exports['default'];

},{"../../component.js":65,"../../utils/dom.js":128,"../../utils/format-time.js":131}],90:[function(_dereq_,module,exports){
/**
 * @file remaining-time-display.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _componentJs = _dereq_('../../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

var _utilsDomJs = _dereq_('../../utils/dom.js');

var Dom = _interopRequireWildcard(_utilsDomJs);

var _utilsFormatTimeJs = _dereq_('../../utils/format-time.js');

var _utilsFormatTimeJs2 = _interopRequireDefault(_utilsFormatTimeJs);

/**
 * Displays the time left in the video
 *
 * @param {Player|Object} player
 * @param {Object=} options
 * @extends Component
 * @class RemainingTimeDisplay
 */

var RemainingTimeDisplay = (function (_Component) {
  _inherits(RemainingTimeDisplay, _Component);

  function RemainingTimeDisplay(player, options) {
    _classCallCheck(this, RemainingTimeDisplay);

    _Component.call(this, player, options);

    this.on(player, 'timeupdate', this.updateContent);
  }

  /**
   * Create the component's DOM element
   *
   * @return {Element}
   * @method createEl
   */

  RemainingTimeDisplay.prototype.createEl = function createEl() {
    var el = _Component.prototype.createEl.call(this, 'div', {
      className: 'vjs-remaining-time vjs-time-control vjs-control'
    });

    this.contentEl_ = Dom.createEl('div', {
      className: 'vjs-remaining-time-display',
      // label the remaining time for screen reader users
      innerHTML: '<span class="vjs-control-text">' + this.localize('Remaining Time') + '</span> -0:00'
    }, {
      // tell screen readers not to automatically read the time as it changes
      'aria-live': 'off'
    });

    el.appendChild(this.contentEl_);
    return el;
  };

  /**
   * Update remaining time display
   *
   * @method updateContent
   */

  RemainingTimeDisplay.prototype.updateContent = function updateContent() {
    if (this.player_.duration()) {
      var localizedText = this.localize('Remaining Time');
      var formattedTime = _utilsFormatTimeJs2['default'](this.player_.remainingTime());
      this.contentEl_.innerHTML = '<span class="vjs-control-text">' + localizedText + '</span> -' + formattedTime;
    }

    // Allows for smooth scrubbing, when player can't keep up.
    // var time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime();
    // this.contentEl_.innerHTML = vjs.formatTime(time, this.player_.duration());
  };

  return RemainingTimeDisplay;
})(_componentJs2['default']);

_componentJs2['default'].registerComponent('RemainingTimeDisplay', RemainingTimeDisplay);
exports['default'] = RemainingTimeDisplay;
module.exports = exports['default'];

},{"../../component.js":65,"../../utils/dom.js":128,"../../utils/format-time.js":131}],91:[function(_dereq_,module,exports){
/**
 * @file time-divider.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _componentJs = _dereq_('../../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

/**
 * The separator between the current time and duration.
 * Can be hidden if it's not needed in the design.
 *
 * @param {Player|Object} player
 * @param {Object=} options
 * @extends Component
 * @class TimeDivider
 */

var TimeDivider = (function (_Component) {
  _inherits(TimeDivider, _Component);

  function TimeDivider() {
    _classCallCheck(this, TimeDivider);

    _Component.apply(this, arguments);
  }

  /**
   * Create the component's DOM element
   *
   * @return {Element}
   * @method createEl
   */

  TimeDivider.prototype.createEl = function createEl() {
    return _Component.prototype.createEl.call(this, 'div', {
      className: 'vjs-time-control vjs-time-divider',
      innerHTML: '<div><span>/</span></div>'
    });
  };

  return TimeDivider;
})(_componentJs2['default']);

_componentJs2['default'].registerComponent('TimeDivider', TimeDivider);
exports['default'] = TimeDivider;
module.exports = exports['default'];

},{"../../component.js":65}],92:[function(_dereq_,module,exports){
/**
 * @file volume-bar.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _sliderSliderJs = _dereq_('../../slider/slider.js');

var _sliderSliderJs2 = _interopRequireDefault(_sliderSliderJs);

var _componentJs = _dereq_('../../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

var _utilsFnJs = _dereq_('../../utils/fn.js');

var Fn = _interopRequireWildcard(_utilsFnJs);

// Required children

var _volumeLevelJs = _dereq_('./volume-level.js');

var _volumeLevelJs2 = _interopRequireDefault(_volumeLevelJs);

/**
 * The bar that contains the volume level and can be clicked on to adjust the level
 *
 * @param {Player|Object} player
 * @param {Object=} options
 * @extends Slider
 * @class VolumeBar
 */

var VolumeBar = (function (_Slider) {
  _inherits(VolumeBar, _Slider);

  function VolumeBar(player, options) {
    _classCallCheck(this, VolumeBar);

    _Slider.call(this, player, options);
    this.on(player, 'volumechange', this.updateARIAAttributes);
    player.ready(Fn.bind(this, this.updateARIAAttributes));
  }

  /**
   * Create the component's DOM element
   *
   * @return {Element}
   * @method createEl
   */

  VolumeBar.prototype.createEl = function createEl() {
    return _Slider.prototype.createEl.call(this, 'div', {
      className: 'vjs-volume-bar vjs-slider-bar'
    }, {
      'aria-label': 'volume level'
    });
  };

  /**
   * Handle mouse move on volume bar
   *
   * @method handleMouseMove
   */

  VolumeBar.prototype.handleMouseMove = function handleMouseMove(event) {
    if (this.player_.muted()) {
      this.player_.muted(false);
    }

    this.player_.volume(this.calculateDistance(event));
  };

  /**
   * Get percent of volume level
   *
   * @retun {Number} Volume level percent
   * @method getPercent
   */

  VolumeBar.prototype.getPercent = function getPercent() {
    if (this.player_.muted()) {
      return 0;
    } else {
      return this.player_.volume();
    }
  };

  /**
   * Increase volume level for keyboard users
   *
   * @method stepForward
   */

  VolumeBar.prototype.stepForward = function stepForward() {
    this.player_.volume(this.player_.volume() + 0.1);
  };

  /**
   * Decrease volume level for keyboard users
   *
   * @method stepBack
   */

  VolumeBar.prototype.stepBack = function stepBack() {
    this.player_.volume(this.player_.volume() - 0.1);
  };

  /**
   * Update ARIA accessibility attributes
   *
   * @method updateARIAAttributes
   */

  VolumeBar.prototype.updateARIAAttributes = function updateARIAAttributes() {
    // Current value of volume bar as a percentage
    var volume = (this.player_.volume() * 100).toFixed(2);
    this.el_.setAttribute('aria-valuenow', volume);
    this.el_.setAttribute('aria-valuetext', volume + '%');
  };

  return VolumeBar;
})(_sliderSliderJs2['default']);

VolumeBar.prototype.options_ = {
  children: ['volumeLevel'],
  'barName': 'volumeLevel'
};

VolumeBar.prototype.playerEvent = 'volumechange';

_componentJs2['default'].registerComponent('VolumeBar', VolumeBar);
exports['default'] = VolumeBar;
module.exports = exports['default'];

},{"../../component.js":65,"../../slider/slider.js":110,"../../utils/fn.js":130,"./volume-level.js":94}],93:[function(_dereq_,module,exports){
/**
 * @file volume-control.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _componentJs = _dereq_('../../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

// Required children

var _volumeBarJs = _dereq_('./volume-bar.js');

var _volumeBarJs2 = _interopRequireDefault(_volumeBarJs);

/**
 * The component for controlling the volume level
 *
 * @param {Player|Object} player
 * @param {Object=} options
 * @extends Component
 * @class VolumeControl
 */

var VolumeControl = (function (_Component) {
  _inherits(VolumeControl, _Component);

  function VolumeControl(player, options) {
    _classCallCheck(this, VolumeControl);

    _Component.call(this, player, options);

    // hide volume controls when they're not supported by the current tech
    if (player.tech_ && player.tech_['featuresVolumeControl'] === false) {
      this.addClass('vjs-hidden');
    }
    this.on(player, 'loadstart', function () {
      if (player.tech_['featuresVolumeControl'] === false) {
        this.addClass('vjs-hidden');
      } else {
        this.removeClass('vjs-hidden');
      }
    });
  }

  /**
   * Create the component's DOM element
   *
   * @return {Element}
   * @method createEl
   */

  VolumeControl.prototype.createEl = function createEl() {
    return _Component.prototype.createEl.call(this, 'div', {
      className: 'vjs-volume-control vjs-control'
    });
  };

  return VolumeControl;
})(_componentJs2['default']);

VolumeControl.prototype.options_ = {
  children: ['volumeBar']
};

_componentJs2['default'].registerComponent('VolumeControl', VolumeControl);
exports['default'] = VolumeControl;
module.exports = exports['default'];

},{"../../component.js":65,"./volume-bar.js":92}],94:[function(_dereq_,module,exports){
/**
 * @file volume-level.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _componentJs = _dereq_('../../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

/**
 * Shows volume level
 *
 * @param {Player|Object} player
 * @param {Object=} options
 * @extends Component
 * @class VolumeLevel
 */

var VolumeLevel = (function (_Component) {
  _inherits(VolumeLevel, _Component);

  function VolumeLevel() {
    _classCallCheck(this, VolumeLevel);

    _Component.apply(this, arguments);
  }

  /**
   * Create the component's DOM element
   *
   * @return {Element}
   * @method createEl
   */

  VolumeLevel.prototype.createEl = function createEl() {
    return _Component.prototype.createEl.call(this, 'div', {
      className: 'vjs-volume-level',
      innerHTML: '<span class="vjs-control-text"></span>'
    });
  };

  return VolumeLevel;
})(_componentJs2['default']);

_componentJs2['default'].registerComponent('VolumeLevel', VolumeLevel);
exports['default'] = VolumeLevel;
module.exports = exports['default'];

},{"../../component.js":65}],95:[function(_dereq_,module,exports){
/**
 * @file volume-menu-button.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _buttonJs = _dereq_('../button.js');

var _buttonJs2 = _interopRequireDefault(_buttonJs);

var _utilsFnJs = _dereq_('../utils/fn.js');

var Fn = _interopRequireWildcard(_utilsFnJs);

var _componentJs = _dereq_('../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

var _menuMenuJs = _dereq_('../menu/menu.js');

var _menuMenuJs2 = _interopRequireDefault(_menuMenuJs);

var _menuMenuButtonJs = _dereq_('../menu/menu-button.js');

var _menuMenuButtonJs2 = _interopRequireDefault(_menuMenuButtonJs);

var _muteToggleJs = _dereq_('./mute-toggle.js');

var _muteToggleJs2 = _interopRequireDefault(_muteToggleJs);

var _volumeControlVolumeBarJs = _dereq_('./volume-control/volume-bar.js');

var _volumeControlVolumeBarJs2 = _interopRequireDefault(_volumeControlVolumeBarJs);

var _globalDocument = _dereq_('global/document');

var _globalDocument2 = _interopRequireDefault(_globalDocument);

/**
 * Button for volume menu
 *
 * @param {Player|Object} player
 * @param {Object=} options
 * @extends MenuButton
 * @class VolumeMenuButton
 */

var VolumeMenuButton = (function (_MenuButton) {
  _inherits(VolumeMenuButton, _MenuButton);

  function VolumeMenuButton(player) {
    var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];

    _classCallCheck(this, VolumeMenuButton);

    // Default to inline
    if (options.inline === undefined) {
      options.inline = true;
    }

    // If the vertical option isn't passed at all, default to true.
    if (options.vertical === undefined) {
      // If an inline volumeMenuButton is used, we should default to using
      // a horizontal slider for obvious reasons.
      if (options.inline) {
        options.vertical = false;
      } else {
        options.vertical = true;
      }
    }

    // The vertical option needs to be set on the volumeBar as well,
    // since that will need to be passed along to the VolumeBar constructor
    options.volumeBar = options.volumeBar || {};
    options.volumeBar.vertical = !!options.vertical;

    _MenuButton.call(this, player, options);

    // Same listeners as MuteToggle
    this.on(player, 'volumechange', this.volumeUpdate);
    this.on(player, 'loadstart', this.volumeUpdate);

    // hide mute toggle if the current tech doesn't support volume control
    function updateVisibility() {
      if (player.tech_ && player.tech_['featuresVolumeControl'] === false) {
        this.addClass('vjs-hidden');
      } else {
        this.removeClass('vjs-hidden');
      }
    }

    updateVisibility.call(this);
    this.on(player, 'loadstart', updateVisibility);

    this.on(this.volumeBar, ['slideractive', 'focus'], function () {
      this.addClass('vjs-slider-active');
    });

    this.on(this.volumeBar, ['sliderinactive', 'blur'], function () {
      this.removeClass('vjs-slider-active');
    });
  }

  /**
   * Allow sub components to stack CSS class names
   *
   * @return {String} The constructed class name
   * @method buildCSSClass
   */

  VolumeMenuButton.prototype.buildCSSClass = function buildCSSClass() {
    var orientationClass = '';
    if (!!this.options_.vertical) {
      orientationClass = 'vjs-volume-menu-button-vertical';
    } else {
      orientationClass = 'vjs-volume-menu-button-horizontal';
    }

    return 'vjs-volume-menu-button ' + _MenuButton.prototype.buildCSSClass.call(this) + ' ' + orientationClass;
  };

  /**
   * Allow sub components to stack CSS class names
   *
   * @return {Menu} The volume menu button
   * @method createMenu
   */

  VolumeMenuButton.prototype.createMenu = function createMenu() {
    var menu = new _menuMenuJs2['default'](this.player_, {
      contentElType: 'div'
    });

    var vb = new _volumeControlVolumeBarJs2['default'](this.player_, this.options_.volumeBar);

    menu.addChild(vb);

    this.volumeBar = vb;

    this.attachVolumeBarEvents();

    return menu;
  };

  /**
   * Handle click on volume menu and calls super
   *
   * @method handleClick
   */

  VolumeMenuButton.prototype.handleClick = function handleClick() {
    _muteToggleJs2['default'].prototype.handleClick.call(this);
    _MenuButton.prototype.handleClick.call(this);
  };

  VolumeMenuButton.prototype.attachVolumeBarEvents = function attachVolumeBarEvents() {
    this.on(['mousedown', 'touchdown'], this.handleMouseDown);
  };

  VolumeMenuButton.prototype.handleMouseDown = function handleMouseDown(event) {
    this.on(['mousemove', 'touchmove'], Fn.bind(this.volumeBar, this.volumeBar.handleMouseMove));
    this.on(_globalDocument2['default'], ['mouseup', 'touchend'], this.handleMouseUp);
  };

  VolumeMenuButton.prototype.handleMouseUp = function handleMouseUp(event) {
    this.off(['mousemove', 'touchmove'], Fn.bind(this.volumeBar, this.volumeBar.handleMouseMove));
  };

  return VolumeMenuButton;
})(_menuMenuButtonJs2['default']);

VolumeMenuButton.prototype.volumeUpdate = _muteToggleJs2['default'].prototype.update;
VolumeMenuButton.prototype.controlText_ = 'Mute';

_componentJs2['default'].registerComponent('VolumeMenuButton', VolumeMenuButton);
exports['default'] = VolumeMenuButton;
module.exports = exports['default'];

},{"../button.js":63,"../component.js":65,"../menu/menu-button.js":102,"../menu/menu.js":104,"../utils/fn.js":130,"./mute-toggle.js":69,"./volume-control/volume-bar.js":92,"global/document":1}],96:[function(_dereq_,module,exports){
/**
 * @file error-display.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _component = _dereq_('./component');

var _component2 = _interopRequireDefault(_component);

var _modalDialog = _dereq_('./modal-dialog');

var _modalDialog2 = _interopRequireDefault(_modalDialog);

var _utilsDom = _dereq_('./utils/dom');

var Dom = _interopRequireWildcard(_utilsDom);

var _utilsMergeOptions = _dereq_('./utils/merge-options');

var _utilsMergeOptions2 = _interopRequireDefault(_utilsMergeOptions);

/**
 * Display that an error has occurred making the video unplayable.
 *
 * @extends ModalDialog
 * @class ErrorDisplay
 */

var ErrorDisplay = (function (_ModalDialog) {
  _inherits(ErrorDisplay, _ModalDialog);

  /**
   * Constructor for error display modal.
   *
   * @param  {Player} player
   * @param  {Object} [options]
   */

  function ErrorDisplay(player, options) {
    _classCallCheck(this, ErrorDisplay);

    _ModalDialog.call(this, player, options);
    this.on(player, 'error', this.open);
  }

  /**
   * Include the old class for backward-compatibility.
   *
   * This can be removed in 6.0.
   *
   * @method buildCSSClass
   * @deprecated
   * @return {String}
   */

  ErrorDisplay.prototype.buildCSSClass = function buildCSSClass() {
    return 'vjs-error-display ' + _ModalDialog.prototype.buildCSSClass.call(this);
  };

  /**
   * Generates the modal content based on the player error.
   *
   * @return {String|Null}
   */

  ErrorDisplay.prototype.content = function content() {
    var error = this.player().error();
    return error ? this.localize(error.message) : '';
  };

  return ErrorDisplay;
})(_modalDialog2['default']);

ErrorDisplay.prototype.options_ = _utilsMergeOptions2['default'](_modalDialog2['default'].prototype.options_, {
  fillAlways: true,
  temporary: false,
  uncloseable: true
});

_component2['default'].registerComponent('ErrorDisplay', ErrorDisplay);
exports['default'] = ErrorDisplay;
module.exports = exports['default'];

},{"./component":65,"./modal-dialog":105,"./utils/dom":128,"./utils/merge-options":134}],97:[function(_dereq_,module,exports){
/**
 * @file event-target.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

var _utilsEventsJs = _dereq_('./utils/events.js');

var Events = _interopRequireWildcard(_utilsEventsJs);

var EventTarget = function EventTarget() {};

EventTarget.prototype.allowedEvents_ = {};

EventTarget.prototype.on = function (type, fn) {
  // Remove the addEventListener alias before calling Events.on
  // so we don't get into an infinite type loop
  var ael = this.addEventListener;
  this.addEventListener = Function.prototype;
  Events.on(this, type, fn);
  this.addEventListener = ael;
};
EventTarget.prototype.addEventListener = EventTarget.prototype.on;

EventTarget.prototype.off = function (type, fn) {
  Events.off(this, type, fn);
};
EventTarget.prototype.removeEventListener = EventTarget.prototype.off;

EventTarget.prototype.one = function (type, fn) {
  Events.one(this, type, fn);
};

EventTarget.prototype.trigger = function (event) {
  var type = event.type || event;

  if (typeof event === 'string') {
    event = {
      type: type
    };
  }
  event = Events.fixEvent(event);

  if (this.allowedEvents_[type] && this['on' + type]) {
    this['on' + type](event);
  }

  Events.trigger(this, event);
};
// The standard DOM EventTarget.dispatchEvent() is aliased to trigger()
EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger;

exports['default'] = EventTarget;
module.exports = exports['default'];

},{"./utils/events.js":129}],98:[function(_dereq_,module,exports){
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

var _utilsLog = _dereq_('./utils/log');

var _utilsLog2 = _interopRequireDefault(_utilsLog);

/*
 * @file extend.js
 *
 * A combination of node inherits and babel's inherits (after transpile).
 * Both work the same but node adds `super_` to the subClass
 * and Bable adds the superClass as __proto__. Both seem useful.
 */
var _inherits = function _inherits(subClass, superClass) {
  if (typeof superClass !== 'function' && superClass !== null) {
    throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass);
  }

  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: {
      value: subClass,
      enumerable: false,
      writable: true,
      configurable: true
    }
  });

  if (superClass) {
    // node
    subClass.super_ = superClass;
  }
};

/*
 * Function for subclassing using the same inheritance that
 * videojs uses internally
 * ```js
 * var Button = videojs.getComponent('Button');
 * ```
 * ```js
 * var MyButton = videojs.extend(Button, {
 *   constructor: function(player, options) {
 *     Button.call(this, player, options);
 *   },
 *   onClick: function() {
 *     // doSomething
 *   }
 * });
 * ```
 */
var extendFn = function extendFn(superClass) {
  var subClassMethods = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];

  var subClass = function subClass() {
    superClass.apply(this, arguments);
  };
  var methods = {};

  if (typeof subClassMethods === 'object') {
    if (typeof subClassMethods.init === 'function') {
      _utilsLog2['default'].warn('Constructor logic via init() is deprecated; please use constructor() instead.');
      subClassMethods.constructor = subClassMethods.init;
    }
    if (subClassMethods.constructor !== Object.prototype.constructor) {
      subClass = subClassMethods.constructor;
    }
    methods = subClassMethods;
  } else if (typeof subClassMethods === 'function') {
    subClass = subClassMethods;
  }

  _inherits(subClass, superClass);

  // Extend subObj's prototype with functions and other properties from props
  for (var name in methods) {
    if (methods.hasOwnProperty(name)) {
      subClass.prototype[name] = methods[name];
    }
  }

  return subClass;
};

exports['default'] = extendFn;
module.exports = exports['default'];

},{"./utils/log":133}],99:[function(_dereq_,module,exports){
/**
 * @file fullscreen-api.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

var _globalDocument = _dereq_('global/document');

var _globalDocument2 = _interopRequireDefault(_globalDocument);

/*
 * Store the browser-specific methods for the fullscreen API
 * @type {Object|undefined}
 * @private
 */
var FullscreenApi = {};

// browser API methods
// map approach from Screenful.js - https://github.com/sindresorhus/screenfull.js
var apiMap = [
// Spec: https://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html
['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror'],
// WebKit
['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror'],
// Old WebKit (Safari 5.1)
['webkitRequestFullScreen', 'webkitCancelFullScreen', 'webkitCurrentFullScreenElement', 'webkitCancelFullScreen', 'webkitfullscreenchange', 'webkitfullscreenerror'],
// Mozilla
['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror'],
// Microsoft
['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError']];

var specApi = apiMap[0];
var browserApi = undefined;

// determine the supported set of functions
for (var i = 0; i < apiMap.length; i++) {
  // check for exitFullscreen function
  if (apiMap[i][1] in _globalDocument2['default']) {
    browserApi = apiMap[i];
    break;
  }
}

// map the browser API names to the spec API names
if (browserApi) {
  for (var i = 0; i < browserApi.length; i++) {
    FullscreenApi[specApi[i]] = browserApi[i];
  }
}

exports['default'] = FullscreenApi;
module.exports = exports['default'];

},{"global/document":1}],100:[function(_dereq_,module,exports){
/**
 * @file loading-spinner.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _component = _dereq_('./component');

var _component2 = _interopRequireDefault(_component);

/* Loading Spinner
================================================================================ */
/**
 * Loading spinner for waiting events
 *
 * @extends Component
 * @class LoadingSpinner
 */

var LoadingSpinner = (function (_Component) {
  _inherits(LoadingSpinner, _Component);

  function LoadingSpinner() {
    _classCallCheck(this, LoadingSpinner);

    _Component.apply(this, arguments);
  }

  /**
   * Create the component's DOM element
   *
   * @method createEl
   */

  LoadingSpinner.prototype.createEl = function createEl() {
    return _Component.prototype.createEl.call(this, 'div', {
      className: 'vjs-loading-spinner'
    });
  };

  return LoadingSpinner;
})(_component2['default']);

_component2['default'].registerComponent('LoadingSpinner', LoadingSpinner);
exports['default'] = LoadingSpinner;
module.exports = exports['default'];

},{"./component":65}],101:[function(_dereq_,module,exports){
/**
 * @file media-error.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

var _objectAssign = _dereq_('object.assign');

var _objectAssign2 = _interopRequireDefault(_objectAssign);

/*
 * Custom MediaError to mimic the HTML5 MediaError
 *
 * @param {Number} code The media error code
 */
var MediaError = function MediaError(code) {
  if (typeof code === 'number') {
    this.code = code;
  } else if (typeof code === 'string') {
    // default code is zero, so this is a custom error
    this.message = code;
  } else if (typeof code === 'object') {
    // object
    _objectAssign2['default'](this, code);
  }

  if (!this.message) {
    this.message = MediaError.defaultMessages[this.code] || '';
  }
};

/*
 * The error code that refers two one of the defined
 * MediaError types
 *
 * @type {Number}
 */
MediaError.prototype.code = 0;

/*
 * An optional message to be shown with the error.
 * Message is not part of the HTML5 video spec
 * but allows for more informative custom errors.
 *
 * @type {String}
 */
MediaError.prototype.message = '';

/*
 * An optional status code that can be set by plugins
 * to allow even more detail about the error.
 * For example the HLS plugin might provide the specific
 * HTTP status code that was returned when the error
 * occurred, then allowing a custom error overlay
 * to display more information.
 *
 * @type {Array}
 */
MediaError.prototype.status = null;

MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', // = 0
'MEDIA_ERR_ABORTED', // = 1
'MEDIA_ERR_NETWORK', // = 2
'MEDIA_ERR_DECODE', // = 3
'MEDIA_ERR_SRC_NOT_SUPPORTED', // = 4
'MEDIA_ERR_ENCRYPTED' // = 5
];

MediaError.defaultMessages = {
  1: 'You aborted the media playback',
  2: 'A network error caused the media download to fail part-way.',
  3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.',
  4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.',
  5: 'The media is encrypted and we do not have the keys to decrypt it.'
};

// Add types as properties on MediaError
// e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) {
  MediaError[MediaError.errorTypes[errNum]] = errNum;
  // values should be accessible on both the class and instance
  MediaError.prototype[MediaError.errorTypes[errNum]] = errNum;
}

exports['default'] = MediaError;
module.exports = exports['default'];

},{"object.assign":45}],102:[function(_dereq_,module,exports){
/**
 * @file menu-button.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _buttonJs = _dereq_('../button.js');

var _buttonJs2 = _interopRequireDefault(_buttonJs);

var _componentJs = _dereq_('../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

var _menuJs = _dereq_('./menu.js');

var _menuJs2 = _interopRequireDefault(_menuJs);

var _utilsDomJs = _dereq_('../utils/dom.js');

var Dom = _interopRequireWildcard(_utilsDomJs);

var _utilsFnJs = _dereq_('../utils/fn.js');

var Fn = _interopRequireWildcard(_utilsFnJs);

var _utilsToTitleCaseJs = _dereq_('../utils/to-title-case.js');

var _utilsToTitleCaseJs2 = _interopRequireDefault(_utilsToTitleCaseJs);

/**
 * A button class with a popup menu
 *
 * @param {Player|Object} player
 * @param {Object=} options
 * @extends Button
 * @class MenuButton
 */

var MenuButton = (function (_Button) {
  _inherits(MenuButton, _Button);

  function MenuButton(player) {
    var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];

    _classCallCheck(this, MenuButton);

    _Button.call(this, player, options);

    this.update();

    this.on('keydown', this.handleKeyPress);
    this.el_.setAttribute('aria-haspopup', true);
    this.el_.setAttribute('role', 'button');
  }

  /**
   * Update menu
   *
   * @method update
   */

  MenuButton.prototype.update = function update() {
    var menu = this.createMenu();

    if (this.menu) {
      this.removeChild(this.menu);
    }

    this.menu = menu;
    this.addChild(menu);

    /**
     * Track the state of the menu button
     *
     * @type {Boolean}
     * @private
     */
    this.buttonPressed_ = false;

    if (this.items && this.items.length === 0) {
      this.hide();
    } else if (this.items && this.items.length > 1) {
      this.show();
    }
  };

  /**
   * Create menu
   *
   * @return {Menu} The constructed menu
   * @method createMenu
   */

  MenuButton.prototype.createMenu = function createMenu() {
    var menu = new _menuJs2['default'](this.player_);

    // Add a title list item to the top
    if (this.options_.title) {
      menu.contentEl().appendChild(Dom.createEl('li', {
        className: 'vjs-menu-title',
        innerHTML: _utilsToTitleCaseJs2['default'](this.options_.title),
        tabIndex: -1
      }));
    }

    this.items = this['createItems']();

    if (this.items) {
      // Add menu items to the menu
      for (var i = 0; i < this.items.length; i++) {
        menu.addItem(this.items[i]);
      }
    }

    return menu;
  };

  /**
   * Create the list of menu items. Specific to each subclass.
   *
   * @method createItems
   */

  MenuButton.prototype.createItems = function createItems() {};

  /**
   * Create the component's DOM element
   *
   * @return {Element}
   * @method createEl
   */

  MenuButton.prototype.createEl = function createEl() {
    return _Button.prototype.createEl.call(this, 'div', {
      className: this.buildCSSClass()
    });
  };

  /**
   * Allow sub components to stack CSS class names
   *
   * @return {String} The constructed class name
   * @method buildCSSClass
   */

  MenuButton.prototype.buildCSSClass = function buildCSSClass() {
    var menuButtonClass = 'vjs-menu-button';

    // If the inline option is passed, we want to use different styles altogether.
    if (this.options_.inline === true) {
      menuButtonClass += '-inline';
    } else {
      menuButtonClass += '-popup';
    }

    return 'vjs-menu-button ' + menuButtonClass + ' ' + _Button.prototype.buildCSSClass.call(this);
  };

  /**
   * Focus - Add keyboard functionality to element
   * This function is not needed anymore. Instead, the
   * keyboard functionality is handled by
   * treating the button as triggering a submenu.
   * When the button is pressed, the submenu
   * appears. Pressing the button again makes
   * the submenu disappear.
   *
   * @method handleFocus
   */

  MenuButton.prototype.handleFocus = function handleFocus() {};

  /**
   * Can't turn off list display that we turned
   * on with focus, because list would go away.
   *
   * @method handleBlur
   */

  MenuButton.prototype.handleBlur = function handleBlur() {};

  /**
   * When you click the button it adds focus, which
   * will show the menu indefinitely.
   * So we'll remove focus when the mouse leaves the button.
   * Focus is needed for tab navigation.
   * Allow sub components to stack CSS class names
   *
   * @method handleClick
   */

  MenuButton.prototype.handleClick = function handleClick() {
    this.one('mouseout', Fn.bind(this, function () {
      this.menu.unlockShowing();
      this.el_.blur();
    }));
    if (this.buttonPressed_) {
      this.unpressButton();
    } else {
      this.pressButton();
    }
  };

  /**
   * Handle key press on menu
   *
   * @param {Object} Key press event
   * @method handleKeyPress
   */

  MenuButton.prototype.handleKeyPress = function handleKeyPress(event) {

    // Check for space bar (32) or enter (13) keys
    if (event.which === 32 || event.which === 13) {
      if (this.buttonPressed_) {
        this.unpressButton();
      } else {
        this.pressButton();
      }
      event.preventDefault();
      // Check for escape (27) key
    } else if (event.which === 27) {
        if (this.buttonPressed_) {
          this.unpressButton();
        }
        event.preventDefault();
      }
  };

  /**
   * Makes changes based on button pressed
   *
   * @method pressButton
   */

  MenuButton.prototype.pressButton = function pressButton() {
    this.buttonPressed_ = true;
    this.menu.lockShowing();
    this.el_.setAttribute('aria-pressed', true);
    if (this.items && this.items.length > 0) {
      this.items[0].el().focus(); // set the focus to the title of the submenu
    }
  };

  /**
   * Makes changes based on button unpressed
   *
   * @method unpressButton
   */

  MenuButton.prototype.unpressButton = function unpressButton() {
    this.buttonPressed_ = false;
    this.menu.unlockShowing();
    this.el_.setAttribute('aria-pressed', false);
  };

  return MenuButton;
})(_buttonJs2['default']);

_componentJs2['default'].registerComponent('MenuButton', MenuButton);
exports['default'] = MenuButton;
module.exports = exports['default'];

},{"../button.js":63,"../component.js":65,"../utils/dom.js":128,"../utils/fn.js":130,"../utils/to-title-case.js":137,"./menu.js":104}],103:[function(_dereq_,module,exports){
/**
 * @file menu-item.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _buttonJs = _dereq_('../button.js');

var _buttonJs2 = _interopRequireDefault(_buttonJs);

var _componentJs = _dereq_('../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

var _objectAssign = _dereq_('object.assign');

var _objectAssign2 = _interopRequireDefault(_objectAssign);

/**
 * The component for a menu item. `<li>`
 *
 * @param {Player|Object} player
 * @param {Object=} options
 * @extends Button
 * @class MenuItem
 */

var MenuItem = (function (_Button) {
  _inherits(MenuItem, _Button);

  function MenuItem(player, options) {
    _classCallCheck(this, MenuItem);

    _Button.call(this, player, options);
    this.selected(options['selected']);
  }

  /**
   * Create the component's DOM element
   *
   * @param {String=} type Desc
   * @param {Object=} props Desc
   * @return {Element}
   * @method createEl
   */

  MenuItem.prototype.createEl = function createEl(type, props, attrs) {
    return _Button.prototype.createEl.call(this, 'li', _objectAssign2['default']({
      className: 'vjs-menu-item',
      innerHTML: this.localize(this.options_['label'])
    }, props), attrs);
  };

  /**
   * Handle a click on the menu item, and set it to selected
   *
   * @method handleClick
   */

  MenuItem.prototype.handleClick = function handleClick() {
    this.selected(true);
  };

  /**
   * Set this menu item as selected or not
   *
   * @param  {Boolean} selected
   * @method selected
   */

  MenuItem.prototype.selected = function selected(_selected) {
    if (_selected) {
      this.addClass('vjs-selected');
      this.el_.setAttribute('aria-selected', true);
    } else {
      this.removeClass('vjs-selected');
      this.el_.setAttribute('aria-selected', false);
    }
  };

  return MenuItem;
})(_buttonJs2['default']);

_componentJs2['default'].registerComponent('MenuItem', MenuItem);
exports['default'] = MenuItem;
module.exports = exports['default'];

},{"../button.js":63,"../component.js":65,"object.assign":45}],104:[function(_dereq_,module,exports){
/**
 * @file menu.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _componentJs = _dereq_('../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

var _utilsDomJs = _dereq_('../utils/dom.js');

var Dom = _interopRequireWildcard(_utilsDomJs);

var _utilsFnJs = _dereq_('../utils/fn.js');

var Fn = _interopRequireWildcard(_utilsFnJs);

var _utilsEventsJs = _dereq_('../utils/events.js');

var Events = _interopRequireWildcard(_utilsEventsJs);

/**
 * The Menu component is used to build pop up menus, including subtitle and
 * captions selection menus.
 *
 * @extends Component
 * @class Menu
 */

var Menu = (function (_Component) {
  _inherits(Menu, _Component);

  function Menu() {
    _classCallCheck(this, Menu);

    _Component.apply(this, arguments);
  }

  /**
   * Add a menu item to the menu
   *
   * @param {Object|String} component Component or component type to add
   * @method addItem
   */

  Menu.prototype.addItem = function addItem(component) {
    this.addChild(component);
    component.on('click', Fn.bind(this, function () {
      this.unlockShowing();
    }));
  };

  /**
   * Create the component's DOM element
   *
   * @return {Element}
   * @method createEl
   */

  Menu.prototype.createEl = function createEl() {
    var contentElType = this.options_.contentElType || 'ul';
    this.contentEl_ = Dom.createEl(contentElType, {
      className: 'vjs-menu-content'
    });
    var el = _Component.prototype.createEl.call(this, 'div', {
      append: this.contentEl_,
      className: 'vjs-menu'
    });
    el.appendChild(this.contentEl_);

    // Prevent clicks from bubbling up. Needed for Menu Buttons,
    // where a click on the parent is significant
    Events.on(el, 'click', function (event) {
      event.preventDefault();
      event.stopImmediatePropagation();
    });

    return el;
  };

  return Menu;
})(_componentJs2['default']);

_componentJs2['default'].registerComponent('Menu', Menu);
exports['default'] = Menu;
module.exports = exports['default'];

},{"../component.js":65,"../utils/dom.js":128,"../utils/events.js":129,"../utils/fn.js":130}],105:[function(_dereq_,module,exports){
/**
 * @file modal-dialog.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _globalDocument = _dereq_('global/document');

var _globalDocument2 = _interopRequireDefault(_globalDocument);

var _utilsDom = _dereq_('./utils/dom');

var Dom = _interopRequireWildcard(_utilsDom);

var _utilsFn = _dereq_('./utils/fn');

var Fn = _interopRequireWildcard(_utilsFn);

var _utilsLog = _dereq_('./utils/log');

var _utilsLog2 = _interopRequireDefault(_utilsLog);

var _component = _dereq_('./component');

var _component2 = _interopRequireDefault(_component);

var _closeButton = _dereq_('./close-button');

var _closeButton2 = _interopRequireDefault(_closeButton);

var MODAL_CLASS_NAME = 'vjs-modal-dialog';
var ESC = 27;

/**
 * The `ModalDialog` displays over the video and its controls, which blocks
 * interaction with the player until it is closed.
 *
 * Modal dialogs include a "Close" button and will close when that button
 * is activated - or when ESC is pressed anywhere.
 *
 * @extends Component
 * @class ModalDialog
 */

var ModalDialog = (function (_Component) {
  _inherits(ModalDialog, _Component);

  /**
   * Constructor for modals.
   *
   * @param  {Player} player
   * @param  {Object} [options]
   * @param  {Mixed} [options.content=undefined]
   *         Provide customized content for this modal.
   *
   * @param  {String} [options.description]
   *         A text description for the modal, primarily for accessibility.
   *
   * @param  {Boolean} [options.fillAlways=false]
   *         Normally, modals are automatically filled only the first time
   *         they open. This tells the modal to refresh its content
   *         every time it opens.
   *
   * @param  {String} [options.label]
   *         A text label for the modal, primarily for accessibility.
   *
   * @param  {Boolean} [options.temporary=true]
   *         If `true`, the modal can only be opened once; it will be
   *         disposed as soon as it's closed.
   *
   * @param  {Boolean} [options.uncloseable=false]
   *         If `true`, the user will not be able to close the modal
   *         through the UI in the normal ways. Programmatic closing is
   *         still possible.
   *
   */

  function ModalDialog(player, options) {
    _classCallCheck(this, ModalDialog);

    _Component.call(this, player, options);
    this.opened_ = this.hasBeenOpened_ = this.hasBeenFilled_ = false;

    this.closeable(!this.options_.uncloseable);
    this.content(this.options_.content);

    // Make sure the contentEl is defined AFTER any children are initialized
    // because we only want the contents of the modal in the contentEl
    // (not the UI elements like the close button).
    this.contentEl_ = Dom.createEl('div', {
      className: MODAL_CLASS_NAME + '-content'
    }, {
      role: 'document'
    });

    this.descEl_ = Dom.createEl('p', {
      className: MODAL_CLASS_NAME + '-description vjs-offscreen',
      id: this.el().getAttribute('aria-describedby')
    });

    Dom.textContent(this.descEl_, this.description());
    this.el_.appendChild(this.descEl_);
    this.el_.appendChild(this.contentEl_);
  }

  /*
   * Modal dialog default options.
   *
   * @type {Object}
   * @private
   */

  /**
   * Create the modal's DOM element
   *
   * @method createEl
   * @return {Element}
   */

  ModalDialog.prototype.createEl = function createEl() {
    return _Component.prototype.createEl.call(this, 'div', {
      className: this.buildCSSClass(),
      tabIndex: -1
    }, {
      'aria-describedby': this.id() + '_description',
      'aria-hidden': 'true',
      'aria-label': this.label(),
      role: 'dialog'
    });
  };

  /**
   * Build the modal's CSS class.
   *
   * @method buildCSSClass
   * @return {String}
   */

  ModalDialog.prototype.buildCSSClass = function buildCSSClass() {
    return MODAL_CLASS_NAME + ' vjs-hidden ' + _Component.prototype.buildCSSClass.call(this);
  };

  /**
   * Handles key presses on the document, looking for ESC, which closes
   * the modal.
   *
   * @method handleKeyPress
   * @param  {Event} e
   */

  ModalDialog.prototype.handleKeyPress = function handleKeyPress(e) {
    if (e.which === ESC && this.closeable()) {
      this.close();
    }
  };

  /**
   * Returns the label string for this modal. Primarily used for accessibility.
   *
   * @return {String}
   */

  ModalDialog.prototype.label = function label() {
    return this.options_.label || this.localize('Modal Window');
  };

  /**
   * Returns the description string for this modal. Primarily used for
   * accessibility.
   *
   * @return {String}
   */

  ModalDialog.prototype.description = function description() {
    var desc = this.options_.description || this.localize('This is a modal window.');

    // Append a universal closeability message if the modal is closeable.
    if (this.closeable()) {
      desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.');
    }

    return desc;
  };

  /**
   * Opens the modal.
   *
   * @method open
   * @return {ModalDialog}
   */

  ModalDialog.prototype.open = function open() {
    if (!this.opened_) {
      var player = this.player();

      this.trigger('beforemodalopen');
      this.opened_ = true;

      // Fill content if the modal has never opened before and
      // never been filled.
      if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) {
        this.fill();
      }

      // If the player was playing, pause it and take note of its previously
      // playing state.
      this.wasPlaying_ = !player.paused();

      if (this.wasPlaying_) {
        player.pause();
      }

      if (this.closeable()) {
        this.on(_globalDocument2['default'], 'keydown', Fn.bind(this, this.handleKeyPress));
      }

      player.controls(false);
      this.show();
      this.el().setAttribute('aria-hidden', 'false');
      this.trigger('modalopen');
      this.hasBeenOpened_ = true;
    }
    return this;
  };

  /**
   * Whether or not the modal is opened currently.
   *
   * @method opened
   * @param  {Boolean} [value]
   *         If given, it will open (`true`) or close (`false`) the modal.
   *
   * @return {Boolean}
   */

  ModalDialog.prototype.opened = function opened(value) {
    if (typeof value === 'boolean') {
      this[value ? 'open' : 'close']();
    }
    return this.opened_;
  };

  /**
   * Closes the modal.
   *
   * @method close
   * @return {ModalDialog}
   */

  ModalDialog.prototype.close = function close() {
    if (this.opened_) {
      var player = this.player();

      this.trigger('beforemodalclose');
      this.opened_ = false;

      if (this.wasPlaying_) {
        player.play();
      }

      if (this.closeable()) {
        this.off(_globalDocument2['default'], 'keydown', Fn.bind(this, this.handleKeyPress));
      }

      player.controls(true);
      this.hide();
      this.el().setAttribute('aria-hidden', 'true');
      this.trigger('modalclose');

      if (this.options_.temporary) {
        this.dispose();
      }
    }
    return this;
  };

  /**
   * Whether or not the modal is closeable via the UI.
   *
   * @method closeable
   * @param  {Boolean} [value]
   *         If given as a Boolean, it will set the `closeable` option.
   *
   * @return {Boolean}
   */

  ModalDialog.prototype.closeable = function closeable(value) {
    if (typeof value === 'boolean') {
      var closeable = this.closeable_ = !!value;
      var _close = this.getChild('closeButton');

      // If this is being made closeable and has no close button, add one.
      if (closeable && !_close) {

        // The close button should be a child of the modal - not its
        // content element, so temporarily change the content element.
        var temp = this.contentEl_;
        this.contentEl_ = this.el_;
        _close = this.addChild('closeButton');
        this.contentEl_ = temp;
        this.on(_close, 'close', this.close);
      }

      // If this is being made uncloseable and has a close button, remove it.
      if (!closeable && _close) {
        this.off(_close, 'close', this.close);
        this.removeChild(_close);
        _close.dispose();
      }
    }
    return this.closeable_;
  };

  /**
   * Fill the modal's content element with the modal's "content" option.
   *
   * The content element will be emptied before this change takes place.
   *
   * @method fill
   * @return {ModalDialog}
   */

  ModalDialog.prototype.fill = function fill() {
    return this.fillWith(this.content());
  };

  /**
   * Fill the modal's content element with arbitrary content.
   *
   * The content element will be emptied before this change takes place.
   *
   * @method fillWith
   * @param  {Mixed} [content]
   *         The same rules apply to this as apply to the `content` option.
   *
   * @return {ModalDialog}
   */

  ModalDialog.prototype.fillWith = function fillWith(content) {
    var contentEl = this.contentEl();
    var parentEl = contentEl.parentNode;
    var nextSiblingEl = contentEl.nextSibling;

    this.trigger('beforemodalfill');
    this.hasBeenFilled_ = true;

    // Detach the content element from the DOM before performing
    // manipulation to avoid modifying the live DOM multiple times.
    parentEl.removeChild(contentEl);
    this.empty();
    Dom.insertContent(contentEl, content);
    this.trigger('modalfill');

    // Re-inject the re-filled content element.
    if (nextSiblingEl) {
      parentEl.insertBefore(contentEl, nextSiblingEl);
    } else {
      parentEl.appendChild(contentEl);
    }

    return this;
  };

  /**
   * Empties the content element.
   *
   * This happens automatically anytime the modal is filled.
   *
   * @method empty
   * @return {ModalDialog}
   */

  ModalDialog.prototype.empty = function empty() {
    this.trigger('beforemodalempty');
    Dom.emptyEl(this.contentEl());
    this.trigger('modalempty');
    return this;
  };

  /**
   * Gets or sets the modal content, which gets normalized before being
   * rendered into the DOM.
   *
   * This does not update the DOM or fill the modal, but it is called during
   * that process.
   *
   * @method content
   * @param  {Mixed} [value]
   *         If defined, sets the internal content value to be used on the
   *         next call(s) to `fill`. This value is normalized before being
   *         inserted. To "clear" the internal content value, pass `null`.
   *
   * @return {Mixed}
   */

  ModalDialog.prototype.content = function content(value) {
    if (typeof value !== 'undefined') {
      this.content_ = value;
    }
    return this.content_;
  };

  return ModalDialog;
})(_component2['default']);

ModalDialog.prototype.options_ = {
  temporary: true
};

_component2['default'].registerComponent('ModalDialog', ModalDialog);
exports['default'] = ModalDialog;
module.exports = exports['default'];

},{"./close-button":64,"./component":65,"./utils/dom":128,"./utils/fn":130,"./utils/log":133,"global/document":1}],106:[function(_dereq_,module,exports){
/**
 * @file player.js
 */
// Subclasses Component
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _componentJs = _dereq_('./component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

var _globalDocument = _dereq_('global/document');

var _globalDocument2 = _interopRequireDefault(_globalDocument);

var _globalWindow = _dereq_('global/window');

var _globalWindow2 = _interopRequireDefault(_globalWindow);

var _utilsEventsJs = _dereq_('./utils/events.js');

var Events = _interopRequireWildcard(_utilsEventsJs);

var _utilsDomJs = _dereq_('./utils/dom.js');

var Dom = _interopRequireWildcard(_utilsDomJs);

var _utilsFnJs = _dereq_('./utils/fn.js');

var Fn = _interopRequireWildcard(_utilsFnJs);

var _utilsGuidJs = _dereq_('./utils/guid.js');

var Guid = _interopRequireWildcard(_utilsGuidJs);

var _utilsBrowserJs = _dereq_('./utils/browser.js');

var browser = _interopRequireWildcard(_utilsBrowserJs);

var _utilsLogJs = _dereq_('./utils/log.js');

var _utilsLogJs2 = _interopRequireDefault(_utilsLogJs);

var _utilsToTitleCaseJs = _dereq_('./utils/to-title-case.js');

var _utilsToTitleCaseJs2 = _interopRequireDefault(_utilsToTitleCaseJs);

var _utilsTimeRangesJs = _dereq_('./utils/time-ranges.js');

var _utilsBufferJs = _dereq_('./utils/buffer.js');

var _utilsStylesheetJs = _dereq_('./utils/stylesheet.js');

var stylesheet = _interopRequireWildcard(_utilsStylesheetJs);

var _fullscreenApiJs = _dereq_('./fullscreen-api.js');

var _fullscreenApiJs2 = _interopRequireDefault(_fullscreenApiJs);

var _mediaErrorJs = _dereq_('./media-error.js');

var _mediaErrorJs2 = _interopRequireDefault(_mediaErrorJs);

var _safeJsonParseTuple = _dereq_('safe-json-parse/tuple');

var _safeJsonParseTuple2 = _interopRequireDefault(_safeJsonParseTuple);

var _objectAssign = _dereq_('object.assign');

var _objectAssign2 = _interopRequireDefault(_objectAssign);

var _utilsMergeOptionsJs = _dereq_('./utils/merge-options.js');

var _utilsMergeOptionsJs2 = _interopRequireDefault(_utilsMergeOptionsJs);

var _tracksTextTrackListConverterJs = _dereq_('./tracks/text-track-list-converter.js');

var _tracksTextTrackListConverterJs2 = _interopRequireDefault(_tracksTextTrackListConverterJs);

// Include required child components (importing also registers them)

var _techLoaderJs = _dereq_('./tech/loader.js');

var _techLoaderJs2 = _interopRequireDefault(_techLoaderJs);

var _posterImageJs = _dereq_('./poster-image.js');

var _posterImageJs2 = _interopRequireDefault(_posterImageJs);

var _tracksTextTrackDisplayJs = _dereq_('./tracks/text-track-display.js');

var _tracksTextTrackDisplayJs2 = _interopRequireDefault(_tracksTextTrackDisplayJs);

var _loadingSpinnerJs = _dereq_('./loading-spinner.js');

var _loadingSpinnerJs2 = _interopRequireDefault(_loadingSpinnerJs);

var _bigPlayButtonJs = _dereq_('./big-play-button.js');

var _bigPlayButtonJs2 = _interopRequireDefault(_bigPlayButtonJs);

var _controlBarControlBarJs = _dereq_('./control-bar/control-bar.js');

var _controlBarControlBarJs2 = _interopRequireDefault(_controlBarControlBarJs);

var _errorDisplayJs = _dereq_('./error-display.js');

var _errorDisplayJs2 = _interopRequireDefault(_errorDisplayJs);

var _tracksTextTrackSettingsJs = _dereq_('./tracks/text-track-settings.js');

var _tracksTextTrackSettingsJs2 = _interopRequireDefault(_tracksTextTrackSettingsJs);

var _modalDialog = _dereq_('./modal-dialog');

var _modalDialog2 = _interopRequireDefault(_modalDialog);

// Require html5 tech, at least for disposing the original video tag

var _techTechJs = _dereq_('./tech/tech.js');

var _techTechJs2 = _interopRequireDefault(_techTechJs);

var _techHtml5Js = _dereq_('./tech/html5.js');

var _techHtml5Js2 = _interopRequireDefault(_techHtml5Js);

/**
 * An instance of the `Player` class is created when any of the Video.js setup methods are used to initialize a video.
 * ```js
 * var myPlayer = videojs('example_video_1');
 * ```
 * In the following example, the `data-setup` attribute tells the Video.js library to create a player instance when the library is ready.
 * ```html
 * <video id="example_video_1" data-setup='{}' controls>
 *   <source src="my-source.mp4" type="video/mp4">
 * </video>
 * ```
 * After an instance has been created it can be accessed globally using `Video('example_video_1')`.
 *
 * @param {Element} tag        The original video tag used for configuring options
 * @param {Object=} options    Object of option names and values
 * @param {Function=} ready    Ready callback function
 * @extends Component
 * @class Player
 */

var Player = (function (_Component) {
  _inherits(Player, _Component);

  /**
   * player's constructor function
   *
   * @constructs
   * @method init
   * @param {Element} tag        The original video tag used for configuring options
   * @param {Object=} options    Player options
   * @param {Function=} ready    Ready callback function
   */

  function Player(tag, options, ready) {
    var _this = this;

    _classCallCheck(this, Player);

    // Make sure tag ID exists
    tag.id = tag.id || 'vjs_video_' + Guid.newGUID();

    // Set Options
    // The options argument overrides options set in the video tag
    // which overrides globally set options.
    // This latter part coincides with the load order
    // (tag must exist before Player)
    options = _objectAssign2['default'](Player.getTagSettings(tag), options);

    // Delay the initialization of children because we need to set up
    // player properties first, and can't use `this` before `super()`
    options.initChildren = false;

    // Same with creating the element
    options.createEl = false;

    // we don't want the player to report touch activity on itself
    // see enableTouchActivity in Component
    options.reportTouchActivity = false;

    // Run base component initializing with new options
    _Component.call(this, null, options, ready);

    // if the global option object was accidentally blown away by
    // someone, bail early with an informative error
    if (!this.options_ || !this.options_.techOrder || !this.options_.techOrder.length) {
      throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?');
    }

    this.tag = tag; // Store the original tag used to set options

    // Store the tag attributes used to restore html5 element
    this.tagAttributes = tag && Dom.getElAttributes(tag);

    // Update current language
    this.language(this.options_.language);

    // Update Supported Languages
    if (options.languages) {
      (function () {
        // Normalise player option languages to lowercase
        var languagesToLower = {};

        Object.getOwnPropertyNames(options.languages).forEach(function (name) {
          languagesToLower[name.toLowerCase()] = options.languages[name];
        });
        _this.languages_ = languagesToLower;
      })();
    } else {
      this.languages_ = Player.prototype.options_.languages;
    }

    // Cache for video property values.
    this.cache_ = {};

    // Set poster
    this.poster_ = options.poster || '';

    // Set controls
    this.controls_ = !!options.controls;

    // Original tag settings stored in options
    // now remove immediately so native controls don't flash.
    // May be turned back on by HTML5 tech if nativeControlsForTouch is true
    tag.controls = false;

    /*
     * Store the internal state of scrubbing
     *
     * @private
     * @return {Boolean} True if the user is scrubbing
     */
    this.scrubbing_ = false;

    this.el_ = this.createEl();

    // We also want to pass the original player options to each component and plugin
    // as well so they don't need to reach back into the player for options later.
    // We also need to do another copy of this.options_ so we don't end up with
    // an infinite loop.
    var playerOptionsCopy = _utilsMergeOptionsJs2['default'](this.options_);

    // Load plugins
    if (options.plugins) {
      (function () {
        var plugins = options.plugins;

        Object.getOwnPropertyNames(plugins).forEach(function (name) {
          if (typeof this[name] === 'function') {
            this[name](plugins[name]);
          } else {
            _utilsLogJs2['default'].error('Unable to find plugin:', name);
          }
        }, _this);
      })();
    }

    this.options_.playerOptions = playerOptionsCopy;

    this.initChildren();

    // Set isAudio based on whether or not an audio tag was used
    this.isAudio(tag.nodeName.toLowerCase() === 'audio');

    // Update controls className. Can't do this when the controls are initially
    // set because the element doesn't exist yet.
    if (this.controls()) {
      this.addClass('vjs-controls-enabled');
    } else {
      this.addClass('vjs-controls-disabled');
    }

    if (this.isAudio()) {
      this.addClass('vjs-audio');
    }

    if (this.flexNotSupported_()) {
      this.addClass('vjs-no-flex');
    }

    // TODO: Make this smarter. Toggle user state between touching/mousing
    // using events, since devices can have both touch and mouse events.
    // if (browser.TOUCH_ENABLED) {
    //   this.addClass('vjs-touch-enabled');
    // }

    // Make player easily findable by ID
    Player.players[this.id_] = this;

    // When the player is first initialized, trigger activity so components
    // like the control bar show themselves if needed
    this.userActive(true);
    this.reportUserActivity();
    this.listenForUserActivity_();

    this.on('fullscreenchange', this.handleFullscreenChange_);
    this.on('stageclick', this.handleStageClick_);
  }

  /*
   * Global player list
   *
   * @type {Object}
   */

  /**
   * Destroys the video player and does any necessary cleanup
   * ```js
   *     myPlayer.dispose();
   * ```
   * This is especially helpful if you are dynamically adding and removing videos
   * to/from the DOM.
   *
   * @method dispose
   */

  Player.prototype.dispose = function dispose() {
    this.trigger('dispose');
    // prevent dispose from being called twice
    this.off('dispose');

    if (this.styleEl_ && this.styleEl_.parentNode) {
      this.styleEl_.parentNode.removeChild(this.styleEl_);
    }

    // Kill reference to this player
    Player.players[this.id_] = null;
    if (this.tag && this.tag.player) {
      this.tag.player = null;
    }
    if (this.el_ && this.el_.player) {
      this.el_.player = null;
    }

    if (this.tech_) {
      this.tech_.dispose();
    }

    _Component.prototype.dispose.call(this);
  };

  /**
   * Create the component's DOM element
   *
   * @return {Element}
   * @method createEl
   */

  Player.prototype.createEl = function createEl() {
    var el = this.el_ = _Component.prototype.createEl.call(this, 'div');
    var tag = this.tag;

    // Remove width/height attrs from tag so CSS can make it 100% width/height
    tag.removeAttribute('width');
    tag.removeAttribute('height');

    // Copy over all the attributes from the tag, including ID and class
    // ID will now reference player box, not the video tag
    var attrs = Dom.getElAttributes(tag);

    Object.getOwnPropertyNames(attrs).forEach(function (attr) {
      // workaround so we don't totally break IE7
      // http://stackoverflow.com/questions/3653444/css-styles-not-applied-on-dynamic-elements-in-internet-explorer-7
      if (attr === 'class') {
        el.className = attrs[attr];
      } else {
        el.setAttribute(attr, attrs[attr]);
      }
    });

    // Update tag id/class for use as HTML5 playback tech
    // Might think we should do this after embedding in container so .vjs-tech class
    // doesn't flash 100% width/height, but class only applies with .video-js parent
    tag.playerId = tag.id;
    tag.id += '_html5_api';
    tag.className = 'vjs-tech';

    // Make player findable on elements
    tag.player = el.player = this;
    // Default state of video is paused
    this.addClass('vjs-paused');

    // Add a style element in the player that we'll use to set the width/height
    // of the player in a way that's still overrideable by CSS, just like the
    // video element
    this.styleEl_ = stylesheet.createStyleElement('vjs-styles-dimensions');
    var defaultsStyleEl = Dom.$('.vjs-styles-defaults');
    var head = Dom.$('head');
    head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild);

    // Pass in the width/height/aspectRatio options which will update the style el
    this.width(this.options_.width);
    this.height(this.options_.height);
    this.fluid(this.options_.fluid);
    this.aspectRatio(this.options_.aspectRatio);

    // insertElFirst seems to cause the networkState to flicker from 3 to 2, so
    // keep track of the original for later so we can know if the source originally failed
    tag.initNetworkState_ = tag.networkState;

    // Wrap video tag in div (el/box) container
    if (tag.parentNode) {
      tag.parentNode.insertBefore(el, tag);
    }
    Dom.insertElFirst(tag, el); // Breaks iPhone, fixed in HTML5 setup.

    this.el_ = el;

    return el;
  };

  /**
   * Get/set player width
   *
   * @param {Number=} value Value for width
   * @return {Number} Width when getting
   * @method width
   */

  Player.prototype.width = function width(value) {
    return this.dimension('width', value);
  };

  /**
   * Get/set player height
   *
   * @param {Number=} value Value for height
   * @return {Number} Height when getting
   * @method height
   */

  Player.prototype.height = function height(value) {
    return this.dimension('height', value);
  };

  /**
   * Get/set dimension for player
   *
   * @param {String} dimension Either width or height
   * @param {Number=} value Value for dimension
   * @return {Component}
   * @method dimension
   */

  Player.prototype.dimension = function dimension(_dimension, value) {
    var privDimension = _dimension + '_';

    if (value === undefined) {
      return this[privDimension] || 0;
    }

    if (value === '') {
      // If an empty string is given, reset the dimension to be automatic
      this[privDimension] = undefined;
    } else {
      var parsedVal = parseFloat(value);

      if (isNaN(parsedVal)) {
        _utilsLogJs2['default'].error('Improper value "' + value + '" supplied for for ' + _dimension);
        return this;
      }

      this[privDimension] = parsedVal;
    }

    this.updateStyleEl_();
    return this;
  };

  /**
   * Add/remove the vjs-fluid class
   *
   * @param {Boolean} bool Value of true adds the class, value of false removes the class
   * @method fluid
   */

  Player.prototype.fluid = function fluid(bool) {
    if (bool === undefined) {
      return !!this.fluid_;
    }

    this.fluid_ = !!bool;

    if (bool) {
      this.addClass('vjs-fluid');
    } else {
      this.removeClass('vjs-fluid');
    }
  };

  /**
   * Get/Set the aspect ratio
   *
   * @param {String=} ratio Aspect ratio for player
   * @return aspectRatio
   * @method aspectRatio
   */

  Player.prototype.aspectRatio = function aspectRatio(ratio) {
    if (ratio === undefined) {
      return this.aspectRatio_;
    }

    // Check for width:height format
    if (!/^\d+\:\d+$/.test(ratio)) {
      throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.');
    }
    this.aspectRatio_ = ratio;

    // We're assuming if you set an aspect ratio you want fluid mode,
    // because in fixed mode you could calculate width and height yourself.
    this.fluid(true);

    this.updateStyleEl_();
  };

  /**
   * Update styles of the player element (height, width and aspect ratio)
   *
   * @method updateStyleEl_
   */

  Player.prototype.updateStyleEl_ = function updateStyleEl_() {
    var width = undefined;
    var height = undefined;
    var aspectRatio = undefined;
    var idClass = undefined;

    // The aspect ratio is either used directly or to calculate width and height.
    if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') {
      // Use any aspectRatio that's been specifically set
      aspectRatio = this.aspectRatio_;
    } else if (this.videoWidth()) {
      // Otherwise try to get the aspect ratio from the video metadata
      aspectRatio = this.videoWidth() + ':' + this.videoHeight();
    } else {
      // Or use a default. The video element's is 2:1, but 16:9 is more common.
      aspectRatio = '16:9';
    }

    // Get the ratio as a decimal we can use to calculate dimensions
    var ratioParts = aspectRatio.split(':');
    var ratioMultiplier = ratioParts[1] / ratioParts[0];

    if (this.width_ !== undefined) {
      // Use any width that's been specifically set
      width = this.width_;
    } else if (this.height_ !== undefined) {
      // Or calulate the width from the aspect ratio if a height has been set
      width = this.height_ / ratioMultiplier;
    } else {
      // Or use the video's metadata, or use the video el's default of 300
      width = this.videoWidth() || 300;
    }

    if (this.height_ !== undefined) {
      // Use any height that's been specifically set
      height = this.height_;
    } else {
      // Otherwise calculate the height from the ratio and the width
      height = width * ratioMultiplier;
    }

    // Ensure the CSS class is valid by starting with an alpha character
    if (/^[^a-zA-Z]/.test(this.id())) {
      idClass = 'dimensions-' + this.id();
    } else {
      idClass = this.id() + '-dimensions';
    }

    // Ensure the right class is still on the player for the style element
    this.addClass(idClass);

    stylesheet.setTextContent(this.styleEl_, '\n      .' + idClass + ' {\n        width: ' + width + 'px;\n        height: ' + height + 'px;\n      }\n\n      .' + idClass + '.vjs-fluid {\n        padding-top: ' + ratioMultiplier * 100 + '%;\n      }\n    ');
  };

  /**
   * Load the Media Playback Technology (tech)
   * Load/Create an instance of playback technology including element and API methods
   * And append playback element in player div.
   *
   * @param {String} techName Name of the playback technology
   * @param {String} source Video source
   * @method loadTech_
   * @private
   */

  Player.prototype.loadTech_ = function loadTech_(techName, source) {

    // Pause and remove current playback technology
    if (this.tech_) {
      this.unloadTech_();
    }

    // get rid of the HTML5 video tag as soon as we are using another tech
    if (techName !== 'Html5' && this.tag) {
      _techTechJs2['default'].getTech('Html5').disposeMediaElement(this.tag);
      this.tag.player = null;
      this.tag = null;
    }

    this.techName_ = techName;

    // Turn off API access because we're loading a new tech that might load asynchronously
    this.isReady_ = false;

    // Grab tech-specific options from player options and add source and parent element to use.
    var techOptions = _objectAssign2['default']({
      'nativeControlsForTouch': this.options_.nativeControlsForTouch,
      'source': source,
      'playerId': this.id(),
      'techId': this.id() + '_' + techName + '_api',
      'textTracks': this.textTracks_,
      'autoplay': this.options_.autoplay,
      'preload': this.options_.preload,
      'loop': this.options_.loop,
      'muted': this.options_.muted,
      'poster': this.poster(),
      'language': this.language(),
      'vtt.js': this.options_['vtt.js']
    }, this.options_[techName.toLowerCase()]);

    if (this.tag) {
      techOptions.tag = this.tag;
    }

    if (source) {
      this.currentType_ = source.type;
      if (source.src === this.cache_.src && this.cache_.currentTime > 0) {
        techOptions.startTime = this.cache_.currentTime;
      }

      this.cache_.src = source.src;
    }

    // Initialize tech instance
    var techComponent = _techTechJs2['default'].getTech(techName);
    // Support old behavior of techs being registered as components.
    // Remove once that deprecated behavior is removed.
    if (!techComponent) {
      techComponent = _componentJs2['default'].getComponent(techName);
    }
    this.tech_ = new techComponent(techOptions);

    // player.triggerReady is always async, so don't need this to be async
    this.tech_.ready(Fn.bind(this, this.handleTechReady_), true);

    _tracksTextTrackListConverterJs2['default'].jsonToTextTracks(this.textTracksJson_ || [], this.tech_);

    // Listen to all HTML5-defined events and trigger them on the player
    this.on(this.tech_, 'loadstart', this.handleTechLoadStart_);
    this.on(this.tech_, 'waiting', this.handleTechWaiting_);
    this.on(this.tech_, 'canplay', this.handleTechCanPlay_);
    this.on(this.tech_, 'canplaythrough', this.handleTechCanPlayThrough_);
    this.on(this.tech_, 'playing', this.handleTechPlaying_);
    this.on(this.tech_, 'ended', this.handleTechEnded_);
    this.on(this.tech_, 'seeking', this.handleTechSeeking_);
    this.on(this.tech_, 'seeked', this.handleTechSeeked_);
    this.on(this.tech_, 'play', this.handleTechPlay_);
    this.on(this.tech_, 'firstplay', this.handleTechFirstPlay_);
    this.on(this.tech_, 'pause', this.handleTechPause_);
    this.on(this.tech_, 'progress', this.handleTechProgress_);
    this.on(this.tech_, 'durationchange', this.handleTechDurationChange_);
    this.on(this.tech_, 'fullscreenchange', this.handleTechFullscreenChange_);
    this.on(this.tech_, 'error', this.handleTechError_);
    this.on(this.tech_, 'suspend', this.handleTechSuspend_);
    this.on(this.tech_, 'abort', this.handleTechAbort_);
    this.on(this.tech_, 'emptied', this.handleTechEmptied_);
    this.on(this.tech_, 'stalled', this.handleTechStalled_);
    this.on(this.tech_, 'loadedmetadata', this.handleTechLoadedMetaData_);
    this.on(this.tech_, 'loadeddata', this.handleTechLoadedData_);
    this.on(this.tech_, 'timeupdate', this.handleTechTimeUpdate_);
    this.on(this.tech_, 'ratechange', this.handleTechRateChange_);
    this.on(this.tech_, 'volumechange', this.handleTechVolumeChange_);
    this.on(this.tech_, 'texttrackchange', this.handleTechTextTrackChange_);
    this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_);
    this.on(this.tech_, 'posterchange', this.handleTechPosterChange_);

    this.usingNativeControls(this.techGet_('controls'));

    if (this.controls() && !this.usingNativeControls()) {
      this.addTechControlsListeners_();
    }

    // Add the tech element in the DOM if it was not already there
    // Make sure to not insert the original video element if using Html5
    if (this.tech_.el().parentNode !== this.el() && (techName !== 'Html5' || !this.tag)) {
      Dom.insertElFirst(this.tech_.el(), this.el());
    }

    // Get rid of the original video tag reference after the first tech is loaded
    if (this.tag) {
      this.tag.player = null;
      this.tag = null;
    }
  };

  /**
   * Unload playback technology
   *
   * @method unloadTech_
   * @private
   */

  Player.prototype.unloadTech_ = function unloadTech_() {
    // Save the current text tracks so that we can reuse the same text tracks with the next tech
    this.textTracks_ = this.textTracks();
    this.textTracksJson_ = _tracksTextTrackListConverterJs2['default'].textTracksToJson(this.tech_);

    this.isReady_ = false;

    this.tech_.dispose();

    this.tech_ = false;
  };

  /**
   * Return a reference to the current tech.
   * It will only return a reference to the tech if given an object with the
   * `IWillNotUseThisInPlugins` property on it. This is try and prevent misuse
   * of techs by plugins.
   *
   * @param {Object}
   * @return {Object} The Tech
   * @method tech
   */

  Player.prototype.tech = function tech(safety) {
    if (safety && safety.IWillNotUseThisInPlugins) {
      return this.tech_;
    }
    var errorText = '\n      Please make sure that you are not using this inside of a plugin.\n      To disable this alert and error, please pass in an object with\n      `IWillNotUseThisInPlugins` to the `tech` method. See\n      https://github.com/videojs/video.js/issues/2617 for more info.\n    ';
    _globalWindow2['default'].alert(errorText);
    throw new Error(errorText);
  };

  /**
   * Set up click and touch listeners for the playback element
   *
   * On desktops, a click on the video itself will toggle playback,
   * on a mobile device a click on the video toggles controls.
   * (toggling controls is done by toggling the user state between active and
   * inactive)
   * A tap can signal that a user has become active, or has become inactive
   * e.g. a quick tap on an iPhone movie should reveal the controls. Another
   * quick tap should hide them again (signaling the user is in an inactive
   * viewing state)
   * In addition to this, we still want the user to be considered inactive after
   * a few seconds of inactivity.
   * Note: the only part of iOS interaction we can't mimic with this setup
   * is a touch and hold on the video element counting as activity in order to
   * keep the controls showing, but that shouldn't be an issue. A touch and hold
   * on any controls will still keep the user active
   *
   * @private
   * @method addTechControlsListeners_
   */

  Player.prototype.addTechControlsListeners_ = function addTechControlsListeners_() {
    // Make sure to remove all the previous listeners in case we are called multiple times.
    this.removeTechControlsListeners_();

    // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do
    // trigger mousedown/up.
    // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object
    // Any touch events are set to block the mousedown event from happening
    this.on(this.tech_, 'mousedown', this.handleTechClick_);

    // If the controls were hidden we don't want that to change without a tap event
    // so we'll check if the controls were already showing before reporting user
    // activity
    this.on(this.tech_, 'touchstart', this.handleTechTouchStart_);
    this.on(this.tech_, 'touchmove', this.handleTechTouchMove_);
    this.on(this.tech_, 'touchend', this.handleTechTouchEnd_);

    // The tap listener needs to come after the touchend listener because the tap
    // listener cancels out any reportedUserActivity when setting userActive(false)
    this.on(this.tech_, 'tap', this.handleTechTap_);
  };

  /**
   * Remove the listeners used for click and tap controls. This is needed for
   * toggling to controls disabled, where a tap/touch should do nothing.
   *
   * @method removeTechControlsListeners_
   * @private
   */

  Player.prototype.removeTechControlsListeners_ = function removeTechControlsListeners_() {
    // We don't want to just use `this.off()` because there might be other needed
    // listeners added by techs that extend this.
    this.off(this.tech_, 'tap', this.handleTechTap_);
    this.off(this.tech_, 'touchstart', this.handleTechTouchStart_);
    this.off(this.tech_, 'touchmove', this.handleTechTouchMove_);
    this.off(this.tech_, 'touchend', this.handleTechTouchEnd_);
    this.off(this.tech_, 'mousedown', this.handleTechClick_);
  };

  /**
   * Player waits for the tech to be ready
   *
   * @method handleTechReady_
   * @private
   */

  Player.prototype.handleTechReady_ = function handleTechReady_() {
    this.triggerReady();

    // Keep the same volume as before
    if (this.cache_.volume) {
      this.techCall_('setVolume', this.cache_.volume);
    }

    // Look if the tech found a higher resolution poster while loading
    this.handleTechPosterChange_();

    // Update the duration if available
    this.handleTechDurationChange_();

    // Chrome and Safari both have issues with autoplay.
    // In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work.
    // In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays)
    // This fixes both issues. Need to wait for API, so it updates displays correctly
    if (this.src() && this.tag && this.options_.autoplay && this.paused()) {
      delete this.tag.poster; // Chrome Fix. Fixed in Chrome v16.
      this.play();
    }
  };

  /**
   * Fired when the user agent begins looking for media data
   *
   * @private
   * @method handleTechLoadStart_
   */

  Player.prototype.handleTechLoadStart_ = function handleTechLoadStart_() {
    // TODO: Update to use `emptied` event instead. See #1277.

    this.removeClass('vjs-ended');

    // reset the error state
    this.error(null);

    // If it's already playing we want to trigger a firstplay event now.
    // The firstplay event relies on both the play and loadstart events
    // which can happen in any order for a new source
    if (!this.paused()) {
      this.trigger('loadstart');
      this.trigger('firstplay');
    } else {
      // reset the hasStarted state
      this.hasStarted(false);
      this.trigger('loadstart');
    }
  };

  /**
   * Add/remove the vjs-has-started class
   *
   * @param {Boolean} hasStarted The value of true adds the class the value of false remove the class
   * @return {Boolean} Boolean value if has started
   * @private
   * @method hasStarted
   */

  Player.prototype.hasStarted = function hasStarted(_hasStarted) {
    if (_hasStarted !== undefined) {
      // only update if this is a new value
      if (this.hasStarted_ !== _hasStarted) {
        this.hasStarted_ = _hasStarted;
        if (_hasStarted) {
          this.addClass('vjs-has-started');
          // trigger the firstplay event if this newly has played
          this.trigger('firstplay');
        } else {
          this.removeClass('vjs-has-started');
        }
      }
      return this;
    }
    return !!this.hasStarted_;
  };

  /**
   * Fired whenever the media begins or resumes playback
   *
   * @private
   * @method handleTechPlay_
   */

  Player.prototype.handleTechPlay_ = function handleTechPlay_() {
    this.removeClass('vjs-ended');
    this.removeClass('vjs-paused');
    this.addClass('vjs-playing');

    // hide the poster when the user hits play
    // https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play
    this.hasStarted(true);

    this.trigger('play');
  };

  /**
   * Fired whenever the media begins waiting
   *
   * @private
   * @method handleTechWaiting_
   */

  Player.prototype.handleTechWaiting_ = function handleTechWaiting_() {
    this.addClass('vjs-waiting');
    this.trigger('waiting');
  };

  /**
   * A handler for events that signal that waiting has ended
   * which is not consistent between browsers. See #1351
   *
   * @private
   * @method handleTechCanPlay_
   */

  Player.prototype.handleTechCanPlay_ = function handleTechCanPlay_() {
    this.removeClass('vjs-waiting');
    this.trigger('canplay');
  };

  /**
   * A handler for events that signal that waiting has ended
   * which is not consistent between browsers. See #1351
   *
   * @private
   * @method handleTechCanPlayThrough_
   */

  Player.prototype.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() {
    this.removeClass('vjs-waiting');
    this.trigger('canplaythrough');
  };

  /**
   * A handler for events that signal that waiting has ended
   * which is not consistent between browsers. See #1351
   *
   * @private
   * @method handleTechPlaying_
   */

  Player.prototype.handleTechPlaying_ = function handleTechPlaying_() {
    this.removeClass('vjs-waiting');
    this.trigger('playing');
  };

  /**
   * Fired whenever the player is jumping to a new time
   *
   * @private
   * @method handleTechSeeking_
   */

  Player.prototype.handleTechSeeking_ = function handleTechSeeking_() {
    this.addClass('vjs-seeking');
    this.trigger('seeking');
  };

  /**
   * Fired when the player has finished jumping to a new time
   *
   * @private
   * @method handleTechSeeked_
   */

  Player.prototype.handleTechSeeked_ = function handleTechSeeked_() {
    this.removeClass('vjs-seeking');
    this.trigger('seeked');
  };

  /**
   * Fired the first time a video is played
   * Not part of the HLS spec, and we're not sure if this is the best
   * implementation yet, so use sparingly. If you don't have a reason to
   * prevent playback, use `myPlayer.one('play');` instead.
   *
   * @private
   * @method handleTechFirstPlay_
   */

  Player.prototype.handleTechFirstPlay_ = function handleTechFirstPlay_() {
    //If the first starttime attribute is specified
    //then we will start at the given offset in seconds
    if (this.options_.starttime) {
      this.currentTime(this.options_.starttime);
    }

    this.addClass('vjs-has-started');
    this.trigger('firstplay');
  };

  /**
   * Fired whenever the media has been paused
   *
   * @private
   * @method handleTechPause_
   */

  Player.prototype.handleTechPause_ = function handleTechPause_() {
    this.removeClass('vjs-playing');
    this.addClass('vjs-paused');
    this.trigger('pause');
  };

  /**
   * Fired while the user agent is downloading media data
   *
   * @private
   * @method handleTechProgress_
   */

  Player.prototype.handleTechProgress_ = function handleTechProgress_() {
    this.trigger('progress');
  };

  /**
   * Fired when the end of the media resource is reached (currentTime == duration)
   *
   * @private
   * @method handleTechEnded_
   */

  Player.prototype.handleTechEnded_ = function handleTechEnded_() {
    this.addClass('vjs-ended');
    if (this.options_.loop) {
      this.currentTime(0);
      this.play();
    } else if (!this.paused()) {
      this.pause();
    }

    this.trigger('ended');
  };

  /**
   * Fired when the duration of the media resource is first known or changed
   *
   * @private
   * @method handleTechDurationChange_
   */

  Player.prototype.handleTechDurationChange_ = function handleTechDurationChange_() {
    this.duration(this.techGet_('duration'));
  };

  /**
   * Handle a click on the media element to play/pause
   *
   * @param {Object=} event Event object
   * @private
   * @method handleTechClick_
   */

  Player.prototype.handleTechClick_ = function handleTechClick_(event) {
    // We're using mousedown to detect clicks thanks to Flash, but mousedown
    // will also be triggered with right-clicks, so we need to prevent that
    if (event.button !== 0) return;

    // When controls are disabled a click should not toggle playback because
    // the click is considered a control
    if (this.controls()) {
      if (this.paused()) {
        this.play();
      } else {
        this.pause();
      }
    }
  };

  /**
   * Handle a tap on the media element. It will toggle the user
   * activity state, which hides and shows the controls.
   *
   * @private
   * @method handleTechTap_
   */

  Player.prototype.handleTechTap_ = function handleTechTap_() {
    this.userActive(!this.userActive());
  };

  /**
   * Handle touch to start
   *
   * @private
   * @method handleTechTouchStart_
   */

  Player.prototype.handleTechTouchStart_ = function handleTechTouchStart_() {
    this.userWasActive = this.userActive();
  };

  /**
   * Handle touch to move
   *
   * @private
   * @method handleTechTouchMove_
   */

  Player.prototype.handleTechTouchMove_ = function handleTechTouchMove_() {
    if (this.userWasActive) {
      this.reportUserActivity();
    }
  };

  /**
   * Handle touch to end
   *
   * @private
   * @method handleTechTouchEnd_
   */

  Player.prototype.handleTechTouchEnd_ = function handleTechTouchEnd_(event) {
    // Stop the mouse events from also happening
    event.preventDefault();
  };

  /**
   * Fired when the player switches in or out of fullscreen mode
   *
   * @private
   * @method handleFullscreenChange_
   */

  Player.prototype.handleFullscreenChange_ = function handleFullscreenChange_() {
    if (this.isFullscreen()) {
      this.addClass('vjs-fullscreen');
    } else {
      this.removeClass('vjs-fullscreen');
    }
  };

  /**
   * native click events on the SWF aren't triggered on IE11, Win8.1RT
   * use stageclick events triggered from inside the SWF instead
   *
   * @private
   * @method handleStageClick_
   */

  Player.prototype.handleStageClick_ = function handleStageClick_() {
    this.reportUserActivity();
  };

  /**
   * Handle Tech Fullscreen Change
   *
   * @private
   * @method handleTechFullscreenChange_
   */

  Player.prototype.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) {
    if (data) {
      this.isFullscreen(data.isFullscreen);
    }
    this.trigger('fullscreenchange');
  };

  /**
   * Fires when an error occurred during the loading of an audio/video
   *
   * @private
   * @method handleTechError_
   */

  Player.prototype.handleTechError_ = function handleTechError_() {
    var error = this.tech_.error();
    this.error(error && error.code);
  };

  /**
   * Fires when the browser is intentionally not getting media data
   *
   * @private
   * @method handleTechSuspend_
   */

  Player.prototype.handleTechSuspend_ = function handleTechSuspend_() {
    this.trigger('suspend');
  };

  /**
   * Fires when the loading of an audio/video is aborted
   *
   * @private
   * @method handleTechAbort_
   */

  Player.prototype.handleTechAbort_ = function handleTechAbort_() {
    this.trigger('abort');
  };

  /**
   * Fires when the current playlist is empty
   *
   * @private
   * @method handleTechEmptied_
   */

  Player.prototype.handleTechEmptied_ = function handleTechEmptied_() {
    this.trigger('emptied');
  };

  /**
   * Fires when the browser is trying to get media data, but data is not available
   *
   * @private
   * @method handleTechStalled_
   */

  Player.prototype.handleTechStalled_ = function handleTechStalled_() {
    this.trigger('stalled');
  };

  /**
   * Fires when the browser has loaded meta data for the audio/video
   *
   * @private
   * @method handleTechLoadedMetaData_
   */

  Player.prototype.handleTechLoadedMetaData_ = function handleTechLoadedMetaData_() {
    this.trigger('loadedmetadata');
  };

  /**
   * Fires when the browser has loaded the current frame of the audio/video
   *
   * @private
   * @method handleTechLoadedData_
   */

  Player.prototype.handleTechLoadedData_ = function handleTechLoadedData_() {
    this.trigger('loadeddata');
  };

  /**
   * Fires when the current playback position has changed
   *
   * @private
   * @method handleTechTimeUpdate_
   */

  Player.prototype.handleTechTimeUpdate_ = function handleTechTimeUpdate_() {
    this.trigger('timeupdate');
  };

  /**
   * Fires when the playing speed of the audio/video is changed
   *
   * @private
   * @method handleTechRateChange_
   */

  Player.prototype.handleTechRateChange_ = function handleTechRateChange_() {
    this.trigger('ratechange');
  };

  /**
   * Fires when the volume has been changed
   *
   * @private
   * @method handleTechVolumeChange_
   */

  Player.prototype.handleTechVolumeChange_ = function handleTechVolumeChange_() {
    this.trigger('volumechange');
  };

  /**
   * Fires when the text track has been changed
   *
   * @private
   * @method handleTechTextTrackChange_
   */

  Player.prototype.handleTechTextTrackChange_ = function handleTechTextTrackChange_() {
    this.trigger('texttrackchange');
  };

  /**
   * Get object for cached values.
   *
   * @return {Object}
   * @method getCache
   */

  Player.prototype.getCache = function getCache() {
    return this.cache_;
  };

  /**
   * Pass values to the playback tech
   *
   * @param {String=} method Method
   * @param {Object=} arg Argument
   * @private
   * @method techCall_
   */

  Player.prototype.techCall_ = function techCall_(method, arg) {
    // If it's not ready yet, call method when it is
    if (this.tech_ && !this.tech_.isReady_) {
      this.tech_.ready(function () {
        this[method](arg);
      }, true);

      // Otherwise call method now
    } else {
        try {
          this.tech_[method](arg);
        } catch (e) {
          _utilsLogJs2['default'](e);
          throw e;
        }
      }
  };

  /**
   * Get calls can't wait for the tech, and sometimes don't need to.
   *
   * @param {String} method Tech method
   * @return {Method}
   * @private
   * @method techGet_
   */

  Player.prototype.techGet_ = function techGet_(method) {
    if (this.tech_ && this.tech_.isReady_) {

      // Flash likes to die and reload when you hide or reposition it.
      // In these cases the object methods go away and we get errors.
      // When that happens we'll catch the errors and inform tech that it's not ready any more.
      try {
        return this.tech_[method]();
      } catch (e) {
        // When building additional tech libs, an expected method may not be defined yet
        if (this.tech_[method] === undefined) {
          _utilsLogJs2['default']('Video.js: ' + method + ' method not defined for ' + this.techName_ + ' playback technology.', e);
        } else {
          // When a method isn't available on the object it throws a TypeError
          if (e.name === 'TypeError') {
            _utilsLogJs2['default']('Video.js: ' + method + ' unavailable on ' + this.techName_ + ' playback technology element.', e);
            this.tech_.isReady_ = false;
          } else {
            _utilsLogJs2['default'](e);
          }
        }
        throw e;
      }
    }

    return;
  };

  /**
   * start media playback
   * ```js
   *     myPlayer.play();
   * ```
   *
   * @return {Player} self
   * @method play
   */

  Player.prototype.play = function play() {
    this.techCall_('play');
    return this;
  };

  /**
   * Pause the video playback
   * ```js
   *     myPlayer.pause();
   * ```
   *
   * @return {Player} self
   * @method pause
   */

  Player.prototype.pause = function pause() {
    this.techCall_('pause');
    return this;
  };

  /**
   * Check if the player is paused
   * ```js
   *     var isPaused = myPlayer.paused();
   *     var isPlaying = !myPlayer.paused();
   * ```
   *
   * @return {Boolean} false if the media is currently playing, or true otherwise
   * @method paused
   */

  Player.prototype.paused = function paused() {
    // The initial state of paused should be true (in Safari it's actually false)
    return this.techGet_('paused') === false ? false : true;
  };

  /**
   * Returns whether or not the user is "scrubbing". Scrubbing is when the user
   * has clicked the progress bar handle and is dragging it along the progress bar.
   *
   * @param  {Boolean} isScrubbing   True/false the user is scrubbing
   * @return {Boolean}               The scrubbing status when getting
   * @return {Object}                The player when setting
   * @method scrubbing
   */

  Player.prototype.scrubbing = function scrubbing(isScrubbing) {
    if (isScrubbing !== undefined) {
      this.scrubbing_ = !!isScrubbing;

      if (isScrubbing) {
        this.addClass('vjs-scrubbing');
      } else {
        this.removeClass('vjs-scrubbing');
      }

      return this;
    }

    return this.scrubbing_;
  };

  /**
   * Get or set the current time (in seconds)
   * ```js
   *     // get
   *     var whereYouAt = myPlayer.currentTime();
   *     // set
   *     myPlayer.currentTime(120); // 2 minutes into the video
   * ```
   *
   * @param  {Number|String=} seconds The time to seek to
   * @return {Number}        The time in seconds, when not setting
   * @return {Player}    self, when the current time is set
   * @method currentTime
   */

  Player.prototype.currentTime = function currentTime(seconds) {
    if (seconds !== undefined) {

      this.techCall_('setCurrentTime', seconds);

      return this;
    }

    // cache last currentTime and return. default to 0 seconds
    //
    // Caching the currentTime is meant to prevent a massive amount of reads on the tech's
    // currentTime when scrubbing, but may not provide much performance benefit afterall.
    // Should be tested. Also something has to read the actual current time or the cache will
    // never get updated.
    return this.cache_.currentTime = this.techGet_('currentTime') || 0;
  };

  /**
   * Get the length in time of the video in seconds
   * ```js
   *     var lengthOfVideo = myPlayer.duration();
   * ```
   * **NOTE**: The video must have started loading before the duration can be
   * known, and in the case of Flash, may not be known until the video starts
   * playing.
   *
   * @param {Number} seconds Duration when setting
   * @return {Number} The duration of the video in seconds when getting
   * @method duration
   */

  Player.prototype.duration = function duration(seconds) {
    if (seconds === undefined) {
      return this.cache_.duration || 0;
    }

    seconds = parseFloat(seconds) || 0;

    // Standardize on Inifity for signaling video is live
    if (seconds < 0) {
      seconds = Infinity;
    }

    if (seconds !== this.cache_.duration) {
      // Cache the last set value for optimized scrubbing (esp. Flash)
      this.cache_.duration = seconds;

      if (seconds === Infinity) {
        this.addClass('vjs-live');
      } else {
        this.removeClass('vjs-live');
      }

      this.trigger('durationchange');
    }

    return this;
  };

  /**
   * Calculates how much time is left.
   * ```js
   *     var timeLeft = myPlayer.remainingTime();
   * ```
   * Not a native video element function, but useful
   *
   * @return {Number} The time remaining in seconds
   * @method remainingTime
   */

  Player.prototype.remainingTime = function remainingTime() {
    return this.duration() - this.currentTime();
  };

  // http://dev.w3.org/html5/spec/video.html#dom-media-buffered
  // Buffered returns a timerange object.
  // Kind of like an array of portions of the video that have been downloaded.

  /**
   * Get a TimeRange object with the times of the video that have been downloaded
   * If you just want the percent of the video that's been downloaded,
   * use bufferedPercent.
   * ```js
   *     // Number of different ranges of time have been buffered. Usually 1.
   *     numberOfRanges = bufferedTimeRange.length,
   *     // Time in seconds when the first range starts. Usually 0.
   *     firstRangeStart = bufferedTimeRange.start(0),
   *     // Time in seconds when the first range ends
   *     firstRangeEnd = bufferedTimeRange.end(0),
   *     // Length in seconds of the first time range
   *     firstRangeLength = firstRangeEnd - firstRangeStart;
   * ```
   *
   * @return {Object} A mock TimeRange object (following HTML spec)
   * @method buffered
   */

  Player.prototype.buffered = function buffered() {
    var buffered = this.techGet_('buffered');

    if (!buffered || !buffered.length) {
      buffered = _utilsTimeRangesJs.createTimeRange(0, 0);
    }

    return buffered;
  };

  /**
   * Get the percent (as a decimal) of the video that's been downloaded
   * ```js
   *     var howMuchIsDownloaded = myPlayer.bufferedPercent();
   * ```
   * 0 means none, 1 means all.
   * (This method isn't in the HTML5 spec, but it's very convenient)
   *
   * @return {Number} A decimal between 0 and 1 representing the percent
   * @method bufferedPercent
   */

  Player.prototype.bufferedPercent = function bufferedPercent() {
    return _utilsBufferJs.bufferedPercent(this.buffered(), this.duration());
  };

  /**
   * Get the ending time of the last buffered time range
   * This is used in the progress bar to encapsulate all time ranges.
   *
   * @return {Number} The end of the last buffered time range
   * @method bufferedEnd
   */

  Player.prototype.bufferedEnd = function bufferedEnd() {
    var buffered = this.buffered(),
        duration = this.duration(),
        end = buffered.end(buffered.length - 1);

    if (end > duration) {
      end = duration;
    }

    return end;
  };

  /**
   * Get or set the current volume of the media
   * ```js
   *     // get
   *     var howLoudIsIt = myPlayer.volume();
   *     // set
   *     myPlayer.volume(0.5); // Set volume to half
   * ```
   * 0 is off (muted), 1.0 is all the way up, 0.5 is half way.
   *
   * @param  {Number} percentAsDecimal The new volume as a decimal percent
   * @return {Number}              The current volume when getting
   * @return {Player}              self when setting
   * @method volume
   */

  Player.prototype.volume = function volume(percentAsDecimal) {
    var vol = undefined;

    if (percentAsDecimal !== undefined) {
      vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); // Force value to between 0 and 1
      this.cache_.volume = vol;
      this.techCall_('setVolume', vol);

      return this;
    }

    // Default to 1 when returning current volume.
    vol = parseFloat(this.techGet_('volume'));
    return isNaN(vol) ? 1 : vol;
  };

  /**
   * Get the current muted state, or turn mute on or off
   * ```js
   *     // get
   *     var isVolumeMuted = myPlayer.muted();
   *     // set
   *     myPlayer.muted(true); // mute the volume
   * ```
   *
   * @param  {Boolean=} muted True to mute, false to unmute
   * @return {Boolean} True if mute is on, false if not when getting
   * @return {Player} self when setting mute
   * @method muted
   */

  Player.prototype.muted = function muted(_muted) {
    if (_muted !== undefined) {
      this.techCall_('setMuted', _muted);
      return this;
    }
    return this.techGet_('muted') || false; // Default to false
  };

  // Check if current tech can support native fullscreen
  // (e.g. with built in controls like iOS, so not our flash swf)
  /**
   * Check to see if fullscreen is supported
   *
   * @return {Boolean}
   * @method supportsFullScreen
   */

  Player.prototype.supportsFullScreen = function supportsFullScreen() {
    return this.techGet_('supportsFullScreen') || false;
  };

  /**
   * Check if the player is in fullscreen mode
   * ```js
   *     // get
   *     var fullscreenOrNot = myPlayer.isFullscreen();
   *     // set
   *     myPlayer.isFullscreen(true); // tell the player it's in fullscreen
   * ```
   * NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official
   * property and instead document.fullscreenElement is used. But isFullscreen is
   * still a valuable property for internal player workings.
   *
   * @param  {Boolean=} isFS Update the player's fullscreen state
   * @return {Boolean} true if fullscreen false if not when getting
   * @return {Player} self when setting
   * @method isFullscreen
   */

  Player.prototype.isFullscreen = function isFullscreen(isFS) {
    if (isFS !== undefined) {
      this.isFullscreen_ = !!isFS;
      return this;
    }
    return !!this.isFullscreen_;
  };

  /**
   * Increase the size of the video to full screen
   * ```js
   *     myPlayer.requestFullscreen();
   * ```
   * In some browsers, full screen is not supported natively, so it enters
   * "full window mode", where the video fills the browser window.
   * In browsers and devices that support native full screen, sometimes the
   * browser's default controls will be shown, and not the Video.js custom skin.
   * This includes most mobile devices (iOS, Android) and older versions of
   * Safari.
   *
   * @return {Player} self
   * @method requestFullscreen
   */

  Player.prototype.requestFullscreen = function requestFullscreen() {
    var fsApi = _fullscreenApiJs2['default'];

    this.isFullscreen(true);

    if (fsApi.requestFullscreen) {
      // the browser supports going fullscreen at the element level so we can
      // take the controls fullscreen as well as the video

      // Trigger fullscreenchange event after change
      // We have to specifically add this each time, and remove
      // when canceling fullscreen. Otherwise if there's multiple
      // players on a page, they would all be reacting to the same fullscreen
      // events
      Events.on(_globalDocument2['default'], fsApi.fullscreenchange, Fn.bind(this, function documentFullscreenChange(e) {
        this.isFullscreen(_globalDocument2['default'][fsApi.fullscreenElement]);

        // If cancelling fullscreen, remove event listener.
        if (this.isFullscreen() === false) {
          Events.off(_globalDocument2['default'], fsApi.fullscreenchange, documentFullscreenChange);
        }

        this.trigger('fullscreenchange');
      }));

      this.el_[fsApi.requestFullscreen]();
    } else if (this.tech_.supportsFullScreen()) {
      // we can't take the video.js controls fullscreen but we can go fullscreen
      // with native controls
      this.techCall_('enterFullScreen');
    } else {
      // fullscreen isn't supported so we'll just stretch the video element to
      // fill the viewport
      this.enterFullWindow();
      this.trigger('fullscreenchange');
    }

    return this;
  };

  /**
   * Return the video to its normal size after having been in full screen mode
   * ```js
   *     myPlayer.exitFullscreen();
   * ```
   *
   * @return {Player} self
   * @method exitFullscreen
   */

  Player.prototype.exitFullscreen = function exitFullscreen() {
    var fsApi = _fullscreenApiJs2['default'];
    this.isFullscreen(false);

    // Check for browser element fullscreen support
    if (fsApi.requestFullscreen) {
      _globalDocument2['default'][fsApi.exitFullscreen]();
    } else if (this.tech_.supportsFullScreen()) {
      this.techCall_('exitFullScreen');
    } else {
      this.exitFullWindow();
      this.trigger('fullscreenchange');
    }

    return this;
  };

  /**
   * When fullscreen isn't supported we can stretch the video container to as wide as the browser will let us.
   *
   * @method enterFullWindow
   */

  Player.prototype.enterFullWindow = function enterFullWindow() {
    this.isFullWindow = true;

    // Storing original doc overflow value to return to when fullscreen is off
    this.docOrigOverflow = _globalDocument2['default'].documentElement.style.overflow;

    // Add listener for esc key to exit fullscreen
    Events.on(_globalDocument2['default'], 'keydown', Fn.bind(this, this.fullWindowOnEscKey));

    // Hide any scroll bars
    _globalDocument2['default'].documentElement.style.overflow = 'hidden';

    // Apply fullscreen styles
    Dom.addElClass(_globalDocument2['default'].body, 'vjs-full-window');

    this.trigger('enterFullWindow');
  };

  /**
   * Check for call to either exit full window or full screen on ESC key
   *
   * @param {String} event Event to check for key press
   * @method fullWindowOnEscKey
   */

  Player.prototype.fullWindowOnEscKey = function fullWindowOnEscKey(event) {
    if (event.keyCode === 27) {
      if (this.isFullscreen() === true) {
        this.exitFullscreen();
      } else {
        this.exitFullWindow();
      }
    }
  };

  /**
   * Exit full window
   *
   * @method exitFullWindow
   */

  Player.prototype.exitFullWindow = function exitFullWindow() {
    this.isFullWindow = false;
    Events.off(_globalDocument2['default'], 'keydown', this.fullWindowOnEscKey);

    // Unhide scroll bars.
    _globalDocument2['default'].documentElement.style.overflow = this.docOrigOverflow;

    // Remove fullscreen styles
    Dom.removeElClass(_globalDocument2['default'].body, 'vjs-full-window');

    // Resize the box, controller, and poster to original sizes
    // this.positionAll();
    this.trigger('exitFullWindow');
  };

  /**
   * Check whether the player can play a given mimetype
   *
   * @param {String} type The mimetype to check
   * @return {String} 'probably', 'maybe', or '' (empty string)
   * @method canPlayType
   */

  Player.prototype.canPlayType = function canPlayType(type) {
    var can = undefined;

    // Loop through each playback technology in the options order
    for (var i = 0, j = this.options_.techOrder; i < j.length; i++) {
      var techName = _utilsToTitleCaseJs2['default'](j[i]);
      var tech = _techTechJs2['default'].getTech(techName);

      // Support old behavior of techs being registered as components.
      // Remove once that deprecated behavior is removed.
      if (!tech) {
        tech = _componentJs2['default'].getComponent(techName);
      }

      // Check if the current tech is defined before continuing
      if (!tech) {
        _utilsLogJs2['default'].error('The "' + techName + '" tech is undefined. Skipped browser support check for that tech.');
        continue;
      }

      // Check if the browser supports this technology
      if (tech.isSupported()) {
        can = tech.canPlayType(type);

        if (can) {
          return can;
        }
      }
    }

    return '';
  };

  /**
   * Select source based on tech-order or source-order
   * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise,
   * defaults to tech-order selection
   *
   * @param {Array} sources The sources for a media asset
   * @return {Object|Boolean} Object of source and tech order, otherwise false
   * @method selectSource
   */

  Player.prototype.selectSource = function selectSource(sources) {
    // Get only the techs specified in `techOrder` that exist and are supported by the
    // current platform
    var techs = this.options_.techOrder.map(_utilsToTitleCaseJs2['default']).map(function (techName) {
      // `Component.getComponent(...)` is for support of old behavior of techs
      // being registered as components.
      // Remove once that deprecated behavior is removed.
      return [techName, _techTechJs2['default'].getTech(techName) || _componentJs2['default'].getComponent(techName)];
    }).filter(function (_ref) {
      var techName = _ref[0];
      var tech = _ref[1];

      // Check if the current tech is defined before continuing
      if (tech) {
        // Check if the browser supports this technology
        return tech.isSupported();
      }

      _utilsLogJs2['default'].error('The "' + techName + '" tech is undefined. Skipped browser support check for that tech.');
      return false;
    });

    // Iterate over each `innerArray` element once per `outerArray` element and execute
    // `tester` with both. If `tester` returns a non-falsy value, exit early and return
    // that value.
    var findFirstPassingTechSourcePair = function findFirstPassingTechSourcePair(outerArray, innerArray, tester) {
      var found = undefined;

      outerArray.some(function (outerChoice) {
        return innerArray.some(function (innerChoice) {
          found = tester(outerChoice, innerChoice);

          if (found) {
            return true;
          }
        });
      });

      return found;
    };

    var foundSourceAndTech = undefined;
    var flip = function flip(fn) {
      return function (a, b) {
        return fn(b, a);
      };
    };
    var finder = function finder(_ref2, source) {
      var techName = _ref2[0];
      var tech = _ref2[1];

      if (tech.canPlaySource(source)) {
        return { source: source, tech: techName };
      }
    };

    // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources
    // to select from them based on their priority.
    if (this.options_.sourceOrder) {
      // Source-first ordering
      foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder));
    } else {
      // Tech-first ordering
      foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder);
    }

    return foundSourceAndTech || false;
  };

  /**
   * The source function updates the video source
   * There are three types of variables you can pass as the argument.
   * **URL String**: A URL to the the video file. Use this method if you are sure
   * the current playback technology (HTML5/Flash) can support the source you
   * provide. Currently only MP4 files can be used in both HTML5 and Flash.
   * ```js
   *     myPlayer.src("http://www.example.com/path/to/video.mp4");
   * ```
   * **Source Object (or element):* * A javascript object containing information
   * about the source file. Use this method if you want the player to determine if
   * it can support the file using the type information.
   * ```js
   *     myPlayer.src({ type: "video/mp4", src: "http://www.example.com/path/to/video.mp4" });
   * ```
   * **Array of Source Objects:* * To provide multiple versions of the source so
   * that it can be played using HTML5 across browsers you can use an array of
   * source objects. Video.js will detect which version is supported and load that
   * file.
   * ```js
   *     myPlayer.src([
   *       { type: "video/mp4", src: "http://www.example.com/path/to/video.mp4" },
   *       { type: "video/webm", src: "http://www.example.com/path/to/video.webm" },
   *       { type: "video/ogg", src: "http://www.example.com/path/to/video.ogv" }
   *     ]);
   * ```
   *
   * @param  {String|Object|Array=} source The source URL, object, or array of sources
   * @return {String} The current video source when getting
   * @return {String} The player when setting
   * @method src
   */

  Player.prototype.src = function src(source) {
    if (source === undefined) {
      return this.techGet_('src');
    }

    var currentTech = _techTechJs2['default'].getTech(this.techName_);
    // Support old behavior of techs being registered as components.
    // Remove once that deprecated behavior is removed.
    if (!currentTech) {
      currentTech = _componentJs2['default'].getComponent(this.techName_);
    }

    // case: Array of source objects to choose from and pick the best to play
    if (Array.isArray(source)) {
      this.sourceList_(source);

      // case: URL String (http://myvideo...)
    } else if (typeof source === 'string') {
        // create a source object from the string
        this.src({ src: source });

        // case: Source object { src: '', type: '' ... }
      } else if (source instanceof Object) {
          // check if the source has a type and the loaded tech cannot play the source
          // if there's no type we'll just try the current tech
          if (source.type && !currentTech.canPlaySource(source)) {
            // create a source list with the current source and send through
            // the tech loop to check for a compatible technology
            this.sourceList_([source]);
          } else {
            this.cache_.src = source.src;
            this.currentType_ = source.type || '';

            // wait until the tech is ready to set the source
            this.ready(function () {

              // The setSource tech method was added with source handlers
              // so older techs won't support it
              // We need to check the direct prototype for the case where subclasses
              // of the tech do not support source handlers
              if (currentTech.prototype.hasOwnProperty('setSource')) {
                this.techCall_('setSource', source);
              } else {
                this.techCall_('src', source.src);
              }

              if (this.options_.preload === 'auto') {
                this.load();
              }

              if (this.options_.autoplay) {
                this.play();
              }

              // Set the source synchronously if possible (#2326)
            }, true);
          }
        }

    return this;
  };

  /**
   * Handle an array of source objects
   *
   * @param  {Array} sources Array of source objects
   * @private
   * @method sourceList_
   */

  Player.prototype.sourceList_ = function sourceList_(sources) {
    var sourceTech = this.selectSource(sources);

    if (sourceTech) {
      if (sourceTech.tech === this.techName_) {
        // if this technology is already loaded, set the source
        this.src(sourceTech.source);
      } else {
        // load this technology with the chosen source
        this.loadTech_(sourceTech.tech, sourceTech.source);
      }
    } else {
      // We need to wrap this in a timeout to give folks a chance to add error event handlers
      this.setTimeout(function () {
        this.error({ code: 4, message: this.localize(this.options_.notSupportedMessage) });
      }, 0);

      // we could not find an appropriate tech, but let's still notify the delegate that this is it
      // this needs a better comment about why this is needed
      this.triggerReady();
    }
  };

  /**
   * Begin loading the src data.
   *
   * @return {Player} Returns the player
   * @method load
   */

  Player.prototype.load = function load() {
    this.techCall_('load');
    return this;
  };

  /**
   * Reset the player. Loads the first tech in the techOrder,
   * and calls `reset` on the tech`.
   *
   * @return {Player} Returns the player
   * @method reset
   */

  Player.prototype.reset = function reset() {
    this.loadTech_(_utilsToTitleCaseJs2['default'](this.options_.techOrder[0]), null);
    this.techCall_('reset');
    return this;
  };

  /**
   * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4
   * Can be used in conjuction with `currentType` to assist in rebuilding the current source object.
   *
   * @return {String} The current source
   * @method currentSrc
   */

  Player.prototype.currentSrc = function currentSrc() {
    return this.techGet_('currentSrc') || this.cache_.src || '';
  };

  /**
   * Get the current source type e.g. video/mp4
   * This can allow you rebuild the current source object so that you could load the same
   * source and tech later
   *
   * @return {String} The source MIME type
   * @method currentType
   */

  Player.prototype.currentType = function currentType() {
    return this.currentType_ || '';
  };

  /**
   * Get or set the preload attribute
   *
   * @param {Boolean} value Boolean to determine if preload should be used
   * @return {String} The preload attribute value when getting
   * @return {Player} Returns the player when setting
   * @method preload
   */

  Player.prototype.preload = function preload(value) {
    if (value !== undefined) {
      this.techCall_('setPreload', value);
      this.options_.preload = value;
      return this;
    }
    return this.techGet_('preload');
  };

  /**
   * Get or set the autoplay attribute.
   *
   * @param {Boolean} value Boolean to determine if video should autoplay
   * @return {String} The autoplay attribute value when getting
   * @return {Player} Returns the player when setting
   * @method autoplay
   */

  Player.prototype.autoplay = function autoplay(value) {
    if (value !== undefined) {
      this.techCall_('setAutoplay', value);
      this.options_.autoplay = value;
      return this;
    }
    return this.techGet_('autoplay', value);
  };

  /**
   * Get or set the loop attribute on the video element.
   *
   * @param {Boolean} value Boolean to determine if video should loop
   * @return {String} The loop attribute value when getting
   * @return {Player} Returns the player when setting
   * @method loop
   */

  Player.prototype.loop = function loop(value) {
    if (value !== undefined) {
      this.techCall_('setLoop', value);
      this.options_['loop'] = value;
      return this;
    }
    return this.techGet_('loop');
  };

  /**
   * Get or set the poster image source url
   *
   * ##### EXAMPLE:
   * ```js
   *     // get
   *     var currentPoster = myPlayer.poster();
   *     // set
   *     myPlayer.poster('http://example.com/myImage.jpg');
   * ```
   *
   * @param  {String=} src Poster image source URL
   * @return {String} poster URL when getting
   * @return {Player} self when setting
   * @method poster
   */

  Player.prototype.poster = function poster(src) {
    if (src === undefined) {
      return this.poster_;
    }

    // The correct way to remove a poster is to set as an empty string
    // other falsey values will throw errors
    if (!src) {
      src = '';
    }

    // update the internal poster variable
    this.poster_ = src;

    // update the tech's poster
    this.techCall_('setPoster', src);

    // alert components that the poster has been set
    this.trigger('posterchange');

    return this;
  };

  /**
   * Some techs (e.g. YouTube) can provide a poster source in an
   * asynchronous way. We want the poster component to use this
   * poster source so that it covers up the tech's controls.
   * (YouTube's play button). However we only want to use this
   * soruce if the player user hasn't set a poster through
   * the normal APIs.
   *
   * @private
   * @method handleTechPosterChange_
   */

  Player.prototype.handleTechPosterChange_ = function handleTechPosterChange_() {
    if (!this.poster_ && this.tech_ && this.tech_.poster) {
      this.poster_ = this.tech_.poster() || '';

      // Let components know the poster has changed
      this.trigger('posterchange');
    }
  };

  /**
   * Get or set whether or not the controls are showing.
   *
   * @param  {Boolean} bool Set controls to showing or not
   * @return {Boolean}    Controls are showing
   * @method controls
   */

  Player.prototype.controls = function controls(bool) {
    if (bool !== undefined) {
      bool = !!bool; // force boolean
      // Don't trigger a change event unless it actually changed
      if (this.controls_ !== bool) {
        this.controls_ = bool;

        if (this.usingNativeControls()) {
          this.techCall_('setControls', bool);
        }

        if (bool) {
          this.removeClass('vjs-controls-disabled');
          this.addClass('vjs-controls-enabled');
          this.trigger('controlsenabled');

          if (!this.usingNativeControls()) {
            this.addTechControlsListeners_();
          }
        } else {
          this.removeClass('vjs-controls-enabled');
          this.addClass('vjs-controls-disabled');
          this.trigger('controlsdisabled');

          if (!this.usingNativeControls()) {
            this.removeTechControlsListeners_();
          }
        }
      }
      return this;
    }
    return !!this.controls_;
  };

  /**
   * Toggle native controls on/off. Native controls are the controls built into
   * devices (e.g. default iPhone controls), Flash, or other techs
   * (e.g. Vimeo Controls)
   * **This should only be set by the current tech, because only the tech knows
   * if it can support native controls**
   *
   * @param  {Boolean} bool    True signals that native controls are on
   * @return {Player}      Returns the player
   * @private
   * @method usingNativeControls
   */

  Player.prototype.usingNativeControls = function usingNativeControls(bool) {
    if (bool !== undefined) {
      bool = !!bool; // force boolean
      // Don't trigger a change event unless it actually changed
      if (this.usingNativeControls_ !== bool) {
        this.usingNativeControls_ = bool;
        if (bool) {
          this.addClass('vjs-using-native-controls');

          /**
            * player is using the native device controls
           *
            * @event usingnativecontrols
            * @memberof Player
            * @instance
            * @private
            */
          this.trigger('usingnativecontrols');
        } else {
          this.removeClass('vjs-using-native-controls');

          /**
            * player is using the custom HTML controls
           *
            * @event usingcustomcontrols
            * @memberof Player
            * @instance
            * @private
            */
          this.trigger('usingcustomcontrols');
        }
      }
      return this;
    }
    return !!this.usingNativeControls_;
  };

  /**
   * Set or get the current MediaError
   *
   * @param  {*} err A MediaError or a String/Number to be turned into a MediaError
   * @return {MediaError|null}     when getting
   * @return {Player}              when setting
   * @method error
   */

  Player.prototype.error = function error(err) {
    if (err === undefined) {
      return this.error_ || null;
    }

    // restoring to default
    if (err === null) {
      this.error_ = err;
      this.removeClass('vjs-error');
      this.errorDisplay.close();
      return this;
    }

    // error instance
    if (err instanceof _mediaErrorJs2['default']) {
      this.error_ = err;
    } else {
      this.error_ = new _mediaErrorJs2['default'](err);
    }

    // add the vjs-error classname to the player
    this.addClass('vjs-error');

    // log the name of the error type and any message
    // ie8 just logs "[object object]" if you just log the error object
    _utilsLogJs2['default'].error('(CODE:' + this.error_.code + ' ' + _mediaErrorJs2['default'].errorTypes[this.error_.code] + ')', this.error_.message, this.error_);

    // fire an error event on the player
    this.trigger('error');

    return this;
  };

  /**
   * Returns whether or not the player is in the "ended" state.
   *
   * @return {Boolean} True if the player is in the ended state, false if not.
   * @method ended
   */

  Player.prototype.ended = function ended() {
    return this.techGet_('ended');
  };

  /**
   * Returns whether or not the player is in the "seeking" state.
   *
   * @return {Boolean} True if the player is in the seeking state, false if not.
   * @method seeking
   */

  Player.prototype.seeking = function seeking() {
    return this.techGet_('seeking');
  };

  /**
   * Returns the TimeRanges of the media that are currently available
   * for seeking to.
   *
   * @return {TimeRanges} the seekable intervals of the media timeline
   * @method seekable
   */

  Player.prototype.seekable = function seekable() {
    return this.techGet_('seekable');
  };

  /**
   * Report user activity
   *
   * @param {Object} event Event object
   * @method reportUserActivity
   */

  Player.prototype.reportUserActivity = function reportUserActivity(event) {
    this.userActivity_ = true;
  };

  /**
   * Get/set if user is active
   *
   * @param {Boolean} bool Value when setting
   * @return {Boolean} Value if user is active user when getting
   * @method userActive
   */

  Player.prototype.userActive = function userActive(bool) {
    if (bool !== undefined) {
      bool = !!bool;
      if (bool !== this.userActive_) {
        this.userActive_ = bool;
        if (bool) {
          // If the user was inactive and is now active we want to reset the
          // inactivity timer
          this.userActivity_ = true;
          this.removeClass('vjs-user-inactive');
          this.addClass('vjs-user-active');
          this.trigger('useractive');
        } else {
          // We're switching the state to inactive manually, so erase any other
          // activity
          this.userActivity_ = false;

          // Chrome/Safari/IE have bugs where when you change the cursor it can
          // trigger a mousemove event. This causes an issue when you're hiding
          // the cursor when the user is inactive, and a mousemove signals user
          // activity. Making it impossible to go into inactive mode. Specifically
          // this happens in fullscreen when we really need to hide the cursor.
          //
          // When this gets resolved in ALL browsers it can be removed
          // https://code.google.com/p/chromium/issues/detail?id=103041
          if (this.tech_) {
            this.tech_.one('mousemove', function (e) {
              e.stopPropagation();
              e.preventDefault();
            });
          }

          this.removeClass('vjs-user-active');
          this.addClass('vjs-user-inactive');
          this.trigger('userinactive');
        }
      }
      return this;
    }
    return this.userActive_;
  };

  /**
   * Listen for user activity based on timeout value
   *
   * @private
   * @method listenForUserActivity_
   */

  Player.prototype.listenForUserActivity_ = function listenForUserActivity_() {
    var mouseInProgress = undefined,
        lastMoveX = undefined,
        lastMoveY = undefined;

    var handleActivity = Fn.bind(this, this.reportUserActivity);

    var handleMouseMove = function handleMouseMove(e) {
      // #1068 - Prevent mousemove spamming
      // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970
      if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) {
        lastMoveX = e.screenX;
        lastMoveY = e.screenY;
        handleActivity();
      }
    };

    var handleMouseDown = function handleMouseDown() {
      handleActivity();
      // For as long as the they are touching the device or have their mouse down,
      // we consider them active even if they're not moving their finger or mouse.
      // So we want to continue to update that they are active
      this.clearInterval(mouseInProgress);
      // Setting userActivity=true now and setting the interval to the same time
      // as the activityCheck interval (250) should ensure we never miss the
      // next activityCheck
      mouseInProgress = this.setInterval(handleActivity, 250);
    };

    var handleMouseUp = function handleMouseUp(event) {
      handleActivity();
      // Stop the interval that maintains activity if the mouse/touch is down
      this.clearInterval(mouseInProgress);
    };

    // Any mouse movement will be considered user activity
    this.on('mousedown', handleMouseDown);
    this.on('mousemove', handleMouseMove);
    this.on('mouseup', handleMouseUp);

    // Listen for keyboard navigation
    // Shouldn't need to use inProgress interval because of key repeat
    this.on('keydown', handleActivity);
    this.on('keyup', handleActivity);

    // Run an interval every 250 milliseconds instead of stuffing everything into
    // the mousemove/touchmove function itself, to prevent performance degradation.
    // `this.reportUserActivity` simply sets this.userActivity_ to true, which
    // then gets picked up by this loop
    // http://ejohn.org/blog/learning-from-twitter/
    var inactivityTimeout = undefined;
    var activityCheck = this.setInterval(function () {
      // Check to see if mouse/touch activity has happened
      if (this.userActivity_) {
        // Reset the activity tracker
        this.userActivity_ = false;

        // If the user state was inactive, set the state to active
        this.userActive(true);

        // Clear any existing inactivity timeout to start the timer over
        this.clearTimeout(inactivityTimeout);

        var timeout = this.options_['inactivityTimeout'];
        if (timeout > 0) {
          // In <timeout> milliseconds, if no more activity has occurred the
          // user will be considered inactive
          inactivityTimeout = this.setTimeout(function () {
            // Protect against the case where the inactivityTimeout can trigger just
            // before the next user activity is picked up by the activityCheck loop
            // causing a flicker
            if (!this.userActivity_) {
              this.userActive(false);
            }
          }, timeout);
        }
      }
    }, 250);
  };

  /**
   * Gets or sets the current playback rate.  A playback rate of
   * 1.0 represents normal speed and 0.5 would indicate half-speed
   * playback, for instance.
   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate
   *
   * @param  {Number} rate    New playback rate to set.
   * @return {Number}         Returns the new playback rate when setting
   * @return {Number}         Returns the current playback rate when getting
   * @method playbackRate
   */

  Player.prototype.playbackRate = function playbackRate(rate) {
    if (rate !== undefined) {
      this.techCall_('setPlaybackRate', rate);
      return this;
    }

    if (this.tech_ && this.tech_['featuresPlaybackRate']) {
      return this.techGet_('playbackRate');
    } else {
      return 1.0;
    }
  };

  /**
   * Gets or sets the audio flag
   *
   * @param  {Boolean} bool    True signals that this is an audio player.
   * @return {Boolean}         Returns true if player is audio, false if not when getting
   * @return {Player}      Returns the player if setting
   * @private
   * @method isAudio
   */

  Player.prototype.isAudio = function isAudio(bool) {
    if (bool !== undefined) {
      this.isAudio_ = !!bool;
      return this;
    }

    return !!this.isAudio_;
  };

  /**
   * Returns the current state of network activity for the element, from
   * the codes in the list below.
   * - NETWORK_EMPTY (numeric value 0)
   *   The element has not yet been initialised. All attributes are in
   *   their initial states.
   * - NETWORK_IDLE (numeric value 1)
   *   The element's resource selection algorithm is active and has
   *   selected a resource, but it is not actually using the network at
   *   this time.
   * - NETWORK_LOADING (numeric value 2)
   *   The user agent is actively trying to download data.
   * - NETWORK_NO_SOURCE (numeric value 3)
   *   The element's resource selection algorithm is active, but it has
   *   not yet found a resource to use.
   *
   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states
   * @return {Number} the current network activity state
   * @method networkState
   */

  Player.prototype.networkState = function networkState() {
    return this.techGet_('networkState');
  };

  /**
   * Returns a value that expresses the current state of the element
   * with respect to rendering the current playback position, from the
   * codes in the list below.
   * - HAVE_NOTHING (numeric value 0)
   *   No information regarding the media resource is available.
   * - HAVE_METADATA (numeric value 1)
   *   Enough of the resource has been obtained that the duration of the
   *   resource is available.
   * - HAVE_CURRENT_DATA (numeric value 2)
   *   Data for the immediate current playback position is available.
   * - HAVE_FUTURE_DATA (numeric value 3)
   *   Data for the immediate current playback position is available, as
   *   well as enough data for the user agent to advance the current
   *   playback position in the direction of playback.
   * - HAVE_ENOUGH_DATA (numeric value 4)
   *   The user agent estimates that enough data is available for
   *   playback to proceed uninterrupted.
   *
   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate
   * @return {Number} the current playback rendering state
   * @method readyState
   */

  Player.prototype.readyState = function readyState() {
    return this.techGet_('readyState');
  };

  /*
    * Text tracks are tracks of timed text events.
    * Captions - text displayed over the video for the hearing impaired
    * Subtitles - text displayed over the video for those who don't understand language in the video
    * Chapters - text displayed in a menu allowing the user to jump to particular points (chapters) in the video
    * Descriptions (not supported yet) - audio descriptions that are read back to the user by a screen reading device
    */

  /**
   * Get an array of associated text tracks. captions, subtitles, chapters, descriptions
   * http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
   *
   * @return {Array}           Array of track objects
   * @method textTracks
   */

  Player.prototype.textTracks = function textTracks() {
    // cannot use techGet_ directly because it checks to see whether the tech is ready.
    // Flash is unlikely to be ready in time but textTracks should still work.
    return this.tech_ && this.tech_['textTracks']();
  };

  /**
   * Get an array of remote text tracks
   *
   * @return {Array}
   * @method remoteTextTracks
   */

  Player.prototype.remoteTextTracks = function remoteTextTracks() {
    return this.tech_ && this.tech_['remoteTextTracks']();
  };

  /**
   * Get an array of remote html track elements
   *
   * @return {HTMLTrackElement[]}
   * @method remoteTextTrackEls
   */

  Player.prototype.remoteTextTrackEls = function remoteTextTrackEls() {
    return this.tech_ && this.tech_['remoteTextTrackEls']();
  };

  /**
   * Add a text track
   * In addition to the W3C settings we allow adding additional info through options.
   * http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
   *
   * @param {String}  kind        Captions, subtitles, chapters, descriptions, or metadata
   * @param {String=} label       Optional label
   * @param {String=} language    Optional language
   * @method addTextTrack
   */

  Player.prototype.addTextTrack = function addTextTrack(kind, label, language) {
    return this.tech_ && this.tech_['addTextTrack'](kind, label, language);
  };

  /**
   * Add a remote text track
   *
   * @param {Object} options    Options for remote text track
   * @method addRemoteTextTrack
   */

  Player.prototype.addRemoteTextTrack = function addRemoteTextTrack(options) {
    return this.tech_ && this.tech_['addRemoteTextTrack'](options);
  };

  /**
   * Remove a remote text track
   *
   * @param {Object} track    Remote text track to remove
   * @method removeRemoteTextTrack
   */

  Player.prototype.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
    this.tech_ && this.tech_['removeRemoteTextTrack'](track);
  };

  /**
   * Get video width
   *
   * @return {Number} Video width
   * @method videoWidth
   */

  Player.prototype.videoWidth = function videoWidth() {
    return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0;
  };

  /**
   * Get video height
   *
   * @return {Number} Video height
   * @method videoHeight
   */

  Player.prototype.videoHeight = function videoHeight() {
    return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0;
  };

  // Methods to add support for
  // initialTime: function(){ return this.techCall_('initialTime'); },
  // startOffsetTime: function(){ return this.techCall_('startOffsetTime'); },
  // played: function(){ return this.techCall_('played'); },
  // videoTracks: function(){ return this.techCall_('videoTracks'); },
  // audioTracks: function(){ return this.techCall_('audioTracks'); },
  // defaultPlaybackRate: function(){ return this.techCall_('defaultPlaybackRate'); },
  // defaultMuted: function(){ return this.techCall_('defaultMuted'); }

  /**
   * The player's language code
   * NOTE: The language should be set in the player options if you want the
   * the controls to be built with a specific language. Changing the lanugage
   * later will not update controls text.
   *
   * @param {String} code  The locale string
   * @return {String}      The locale string when getting
   * @return {Player}      self when setting
   * @method language
   */

  Player.prototype.language = function language(code) {
    if (code === undefined) {
      return this.language_;
    }

    this.language_ = ('' + code).toLowerCase();
    return this;
  };

  /**
   * Get the player's language dictionary
   * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time
   * Languages specified directly in the player options have precedence
   *
   * @return {Array} Array of languages
   * @method languages
   */

  Player.prototype.languages = function languages() {
    return _utilsMergeOptionsJs2['default'](Player.prototype.options_.languages, this.languages_);
  };

  /**
   * Converts track info to JSON
   *
   * @return {Object} JSON object of options
   * @method toJSON
   */

  Player.prototype.toJSON = function toJSON() {
    var options = _utilsMergeOptionsJs2['default'](this.options_);
    var tracks = options.tracks;

    options.tracks = [];

    for (var i = 0; i < tracks.length; i++) {
      var track = tracks[i];

      // deep merge tracks and null out player so no circular references
      track = _utilsMergeOptionsJs2['default'](track);
      track.player = undefined;
      options.tracks[i] = track;
    }

    return options;
  };

  /**
   * Creates a simple modal dialog (an instance of the `ModalDialog`
   * component) that immediately overlays the player with arbitrary
   * content and removes itself when closed.
   *
   * @param {String|Function|Element|Array|Null} content
   *        Same as `ModalDialog#content`'s param of the same name.
   *
   *        The most straight-forward usage is to provide a string or DOM
   *        element.
   *
   * @param {Object} [options]
   *        Extra options which will be passed on to the `ModalDialog`.
   *
   * @return {ModalDialog}
   */

  Player.prototype.createModal = function createModal(content, options) {
    var player = this;

    options = options || {};
    options.content = content || '';

    var modal = new _modalDialog2['default'](player, options);

    player.addChild(modal);
    modal.on('dispose', function () {
      player.removeChild(modal);
    });

    return modal.open();
  };

  /**
   * Gets tag settings
   *
   * @param {Element} tag The player tag
   * @return {Array} An array of sources and track objects
   * @static
   * @method getTagSettings
   */

  Player.getTagSettings = function getTagSettings(tag) {
    var baseOptions = {
      'sources': [],
      'tracks': []
    };

    var tagOptions = Dom.getElAttributes(tag);
    var dataSetup = tagOptions['data-setup'];

    // Check if data-setup attr exists.
    if (dataSetup !== null) {
      // Parse options JSON

      var _safeParseTuple = _safeJsonParseTuple2['default'](dataSetup || '{}');

      var err = _safeParseTuple[0];
      var data = _safeParseTuple[1];

      if (err) {
        _utilsLogJs2['default'].error(err);
      }
      _objectAssign2['default'](tagOptions, data);
    }

    _objectAssign2['default'](baseOptions, tagOptions);

    // Get tag children settings
    if (tag.hasChildNodes()) {
      var children = tag.childNodes;

      for (var i = 0, j = children.length; i < j; i++) {
        var child = children[i];
        // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
        var childName = child.nodeName.toLowerCase();
        if (childName === 'source') {
          baseOptions.sources.push(Dom.getElAttributes(child));
        } else if (childName === 'track') {
          baseOptions.tracks.push(Dom.getElAttributes(child));
        }
      }
    }

    return baseOptions;
  };

  return Player;
})(_componentJs2['default']);

Player.players = {};

var navigator = _globalWindow2['default'].navigator;
/*
 * Player instance options, surfaced using options
 * options = Player.prototype.options_
 * Make changes in options, not here.
 *
 * @type {Object}
 * @private
 */
Player.prototype.options_ = {
  // Default order of fallback technology
  techOrder: ['html5', 'flash'],
  // techOrder: ['flash','html5'],

  html5: {},
  flash: {},

  // defaultVolume: 0.85,
  defaultVolume: 0.00, // The freakin seaguls are driving me crazy!

  // default inactivity timeout
  inactivityTimeout: 2000,

  // default playback rates
  playbackRates: [],
  // Add playback rate selection by adding rates
  // 'playbackRates': [0.5, 1, 1.5, 2],

  // Included control sets
  children: ['mediaLoader', 'posterImage', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'controlBar', 'errorDisplay', 'textTrackSettings'],

  language: _globalDocument2['default'].getElementsByTagName('html')[0].getAttribute('lang') || navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language || 'en',

  // locales and their language translations
  languages: {},

  // Default message to show when a video cannot be played.
  notSupportedMessage: 'No compatible source was found for this video.'
};

/**
 * Fired when the player has initial duration and dimension information
 *
 * @event loadedmetadata
 */
Player.prototype.handleLoadedMetaData_;

/**
 * Fired when the player has downloaded data at the current playback position
 *
 * @event loadeddata
 */
Player.prototype.handleLoadedData_;

/**
 * Fired when the user is active, e.g. moves the mouse over the player
 *
 * @event useractive
 */
Player.prototype.handleUserActive_;

/**
 * Fired when the user is inactive, e.g. a short delay after the last mouse move or control interaction
 *
 * @event userinactive
 */
Player.prototype.handleUserInactive_;

/**
 * Fired when the current playback position has changed *
 * During playback this is fired every 15-250 milliseconds, depending on the
 * playback technology in use.
 *
 * @event timeupdate
 */
Player.prototype.handleTimeUpdate_;

/**
 * Fired when video playback ends
 *
 * @event ended
 */
Player.prototype.handleTechEnded_;

/**
 * Fired when the volume changes
 *
 * @event volumechange
 */
Player.prototype.handleVolumeChange_;

/**
 * Fired when an error occurs
 *
 * @event error
 */
Player.prototype.handleError_;

Player.prototype.flexNotSupported_ = function () {
  var elem = _globalDocument2['default'].createElement('i');

  // Note: We don't actually use flexBasis (or flexOrder), but it's one of the more
  // common flex features that we can rely on when checking for flex support.
  return !('flexBasis' in elem.style || 'webkitFlexBasis' in elem.style || 'mozFlexBasis' in elem.style || 'msFlexBasis' in elem.style || 'msFlexOrder' in elem.style) /* IE10-specific (2012 flex spec)  */;
};

_componentJs2['default'].registerComponent('Player', Player);
exports['default'] = Player;
module.exports = exports['default'];
// If empty string, make it a parsable json object.

},{"./big-play-button.js":62,"./component.js":65,"./control-bar/control-bar.js":66,"./error-display.js":96,"./fullscreen-api.js":99,"./loading-spinner.js":100,"./media-error.js":101,"./modal-dialog":105,"./poster-image.js":108,"./tech/html5.js":113,"./tech/loader.js":114,"./tech/tech.js":115,"./tracks/text-track-display.js":119,"./tracks/text-track-list-converter.js":121,"./tracks/text-track-settings.js":123,"./utils/browser.js":125,"./utils/buffer.js":126,"./utils/dom.js":128,"./utils/events.js":129,"./utils/fn.js":130,"./utils/guid.js":132,"./utils/log.js":133,"./utils/merge-options.js":134,"./utils/stylesheet.js":135,"./utils/time-ranges.js":136,"./utils/to-title-case.js":137,"global/document":1,"global/window":2,"object.assign":45,"safe-json-parse/tuple":53}],107:[function(_dereq_,module,exports){
/**
 * @file plugins.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

var _playerJs = _dereq_('./player.js');

var _playerJs2 = _interopRequireDefault(_playerJs);

/**
 * The method for registering a video.js plugin
 *
 * @param  {String} name The name of the plugin
 * @param  {Function} init The function that is run when the player inits
 * @method plugin
 */
var plugin = function plugin(name, init) {
  _playerJs2['default'].prototype[name] = init;
};

exports['default'] = plugin;
module.exports = exports['default'];

},{"./player.js":106}],108:[function(_dereq_,module,exports){
/**
 * @file poster-image.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _buttonJs = _dereq_('./button.js');

var _buttonJs2 = _interopRequireDefault(_buttonJs);

var _componentJs = _dereq_('./component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

var _utilsFnJs = _dereq_('./utils/fn.js');

var Fn = _interopRequireWildcard(_utilsFnJs);

var _utilsDomJs = _dereq_('./utils/dom.js');

var Dom = _interopRequireWildcard(_utilsDomJs);

var _utilsBrowserJs = _dereq_('./utils/browser.js');

var browser = _interopRequireWildcard(_utilsBrowserJs);

/**
 * The component that handles showing the poster image.
 *
 * @param {Player|Object} player
 * @param {Object=} options
 * @extends Button
 * @class PosterImage
 */

var PosterImage = (function (_Button) {
  _inherits(PosterImage, _Button);

  function PosterImage(player, options) {
    _classCallCheck(this, PosterImage);

    _Button.call(this, player, options);

    this.update();
    player.on('posterchange', Fn.bind(this, this.update));
  }

  /**
   * Clean up the poster image
   *
   * @method dispose
   */

  PosterImage.prototype.dispose = function dispose() {
    this.player().off('posterchange', this.update);
    _Button.prototype.dispose.call(this);
  };

  /**
   * Create the poster's image element
   *
   * @return {Element}
   * @method createEl
   */

  PosterImage.prototype.createEl = function createEl() {
    var el = Dom.createEl('div', {
      className: 'vjs-poster',

      // Don't want poster to be tabbable.
      tabIndex: -1
    });

    // To ensure the poster image resizes while maintaining its original aspect
    // ratio, use a div with `background-size` when available. For browsers that
    // do not support `background-size` (e.g. IE8), fall back on using a regular
    // img element.
    if (!browser.BACKGROUND_SIZE_SUPPORTED) {
      this.fallbackImg_ = Dom.createEl('img');
      el.appendChild(this.fallbackImg_);
    }

    return el;
  };

  /**
   * Event handler for updates to the player's poster source
   *
   * @method update
   */

  PosterImage.prototype.update = function update() {
    var url = this.player().poster();

    this.setSrc(url);

    // If there's no poster source we should display:none on this component
    // so it's not still clickable or right-clickable
    if (url) {
      this.show();
    } else {
      this.hide();
    }
  };

  /**
   * Set the poster source depending on the display method
   *
   * @param {String} url The URL to the poster source
   * @method setSrc
   */

  PosterImage.prototype.setSrc = function setSrc(url) {
    if (this.fallbackImg_) {
      this.fallbackImg_.src = url;
    } else {
      var backgroundImage = '';
      // Any falsey values should stay as an empty string, otherwise
      // this will throw an extra error
      if (url) {
        backgroundImage = 'url("' + url + '")';
      }

      this.el_.style.backgroundImage = backgroundImage;
    }
  };

  /**
   * Event handler for clicks on the poster image
   *
   * @method handleClick
   */

  PosterImage.prototype.handleClick = function handleClick() {
    // We don't want a click to trigger playback when controls are disabled
    // but CSS should be hiding the poster to prevent that from happening
    if (this.player_.paused()) {
      this.player_.play();
    } else {
      this.player_.pause();
    }
  };

  return PosterImage;
})(_buttonJs2['default']);

_componentJs2['default'].registerComponent('PosterImage', PosterImage);
exports['default'] = PosterImage;
module.exports = exports['default'];

},{"./button.js":63,"./component.js":65,"./utils/browser.js":125,"./utils/dom.js":128,"./utils/fn.js":130}],109:[function(_dereq_,module,exports){
/**
 * @file setup.js
 *
 * Functions for automatically setting up a player
 * based on the data-setup attribute of the video tag
 */
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

var _utilsEventsJs = _dereq_('./utils/events.js');

var Events = _interopRequireWildcard(_utilsEventsJs);

var _globalDocument = _dereq_('global/document');

var _globalDocument2 = _interopRequireDefault(_globalDocument);

var _globalWindow = _dereq_('global/window');

var _globalWindow2 = _interopRequireDefault(_globalWindow);

var _windowLoaded = false;
var videojs = undefined;

// Automatically set up any tags that have a data-setup attribute
var autoSetup = function autoSetup() {
  // One day, when we stop supporting IE8, go back to this, but in the meantime...*hack hack hack*
  // var vids = Array.prototype.slice.call(document.getElementsByTagName('video'));
  // var audios = Array.prototype.slice.call(document.getElementsByTagName('audio'));
  // var mediaEls = vids.concat(audios);

  // Because IE8 doesn't support calling slice on a node list, we need to loop through each list of elements
  // to build up a new, combined list of elements.
  var vids = _globalDocument2['default'].getElementsByTagName('video');
  var audios = _globalDocument2['default'].getElementsByTagName('audio');
  var mediaEls = [];
  if (vids && vids.length > 0) {
    for (var i = 0, e = vids.length; i < e; i++) {
      mediaEls.push(vids[i]);
    }
  }
  if (audios && audios.length > 0) {
    for (var i = 0, e = audios.length; i < e; i++) {
      mediaEls.push(audios[i]);
    }
  }

  // Check if any media elements exist
  if (mediaEls && mediaEls.length > 0) {

    for (var i = 0, e = mediaEls.length; i < e; i++) {
      var mediaEl = mediaEls[i];

      // Check if element exists, has getAttribute func.
      // IE seems to consider typeof el.getAttribute == 'object' instead of 'function' like expected, at least when loading the player immediately.
      if (mediaEl && mediaEl.getAttribute) {

        // Make sure this player hasn't already been set up.
        if (mediaEl['player'] === undefined) {
          var options = mediaEl.getAttribute('data-setup');

          // Check if data-setup attr exists.
          // We only auto-setup if they've added the data-setup attr.
          if (options !== null) {
            // Create new video.js instance.
            var player = videojs(mediaEl);
          }
        }

        // If getAttribute isn't defined, we need to wait for the DOM.
      } else {
          autoSetupTimeout(1);
          break;
        }
    }

    // No videos were found, so keep looping unless page is finished loading.
  } else if (!_windowLoaded) {
      autoSetupTimeout(1);
    }
};

// Pause to let the DOM keep processing
var autoSetupTimeout = function autoSetupTimeout(wait, vjs) {
  videojs = vjs;
  setTimeout(autoSetup, wait);
};

if (_globalDocument2['default'].readyState === 'complete') {
  _windowLoaded = true;
} else {
  Events.one(_globalWindow2['default'], 'load', function () {
    _windowLoaded = true;
  });
}

var hasLoaded = function hasLoaded() {
  return _windowLoaded;
};

exports.autoSetup = autoSetup;
exports.autoSetupTimeout = autoSetupTimeout;
exports.hasLoaded = hasLoaded;

},{"./utils/events.js":129,"global/document":1,"global/window":2}],110:[function(_dereq_,module,exports){
/**
 * @file slider.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _componentJs = _dereq_('../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

var _utilsDomJs = _dereq_('../utils/dom.js');

var Dom = _interopRequireWildcard(_utilsDomJs);

var _globalDocument = _dereq_('global/document');

var _globalDocument2 = _interopRequireDefault(_globalDocument);

var _objectAssign = _dereq_('object.assign');

var _objectAssign2 = _interopRequireDefault(_objectAssign);

/**
 * The base functionality for sliders like the volume bar and seek bar
 *
 * @param {Player|Object} player
 * @param {Object=} options
 * @extends Component
 * @class Slider
 */

var Slider = (function (_Component) {
  _inherits(Slider, _Component);

  function Slider(player, options) {
    _classCallCheck(this, Slider);

    _Component.call(this, player, options);

    // Set property names to bar to match with the child Slider class is looking for
    this.bar = this.getChild(this.options_.barName);

    // Set a horizontal or vertical class on the slider depending on the slider type
    this.vertical(!!this.options_.vertical);

    this.on('mousedown', this.handleMouseDown);
    this.on('touchstart', this.handleMouseDown);
    this.on('focus', this.handleFocus);
    this.on('blur', this.handleBlur);
    this.on('click', this.handleClick);

    this.on(player, 'controlsvisible', this.update);
    this.on(player, this.playerEvent, this.update);
  }

  /**
   * Create the component's DOM element
   *
   * @param {String} type Type of element to create
   * @param {Object=} props List of properties in Object form
   * @return {Element}
   * @method createEl
   */

  Slider.prototype.createEl = function createEl(type) {
    var props = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
    var attributes = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];

    // Add the slider element class to all sub classes
    props.className = props.className + ' vjs-slider';
    props = _objectAssign2['default']({
      tabIndex: 0
    }, props);

    attributes = _objectAssign2['default']({
      'role': 'slider',
      'aria-valuenow': 0,
      'aria-valuemin': 0,
      'aria-valuemax': 100,
      tabIndex: 0
    }, attributes);

    return _Component.prototype.createEl.call(this, type, props, attributes);
  };

  /**
   * Handle mouse down on slider
   *
   * @param {Object} event Mouse down event object
   * @method handleMouseDown
   */

  Slider.prototype.handleMouseDown = function handleMouseDown(event) {
    event.preventDefault();
    Dom.blockTextSelection();

    this.addClass('vjs-sliding');
    this.trigger('slideractive');

    this.on(_globalDocument2['default'], 'mousemove', this.handleMouseMove);
    this.on(_globalDocument2['default'], 'mouseup', this.handleMouseUp);
    this.on(_globalDocument2['default'], 'touchmove', this.handleMouseMove);
    this.on(_globalDocument2['default'], 'touchend', this.handleMouseUp);

    this.handleMouseMove(event);
  };

  /**
   * To be overridden by a subclass
   *
   * @method handleMouseMove
   */

  Slider.prototype.handleMouseMove = function handleMouseMove() {};

  /**
   * Handle mouse up on Slider
   *
   * @method handleMouseUp
   */

  Slider.prototype.handleMouseUp = function handleMouseUp() {
    Dom.unblockTextSelection();

    this.removeClass('vjs-sliding');
    this.trigger('sliderinactive');

    this.off(_globalDocument2['default'], 'mousemove', this.handleMouseMove);
    this.off(_globalDocument2['default'], 'mouseup', this.handleMouseUp);
    this.off(_globalDocument2['default'], 'touchmove', this.handleMouseMove);
    this.off(_globalDocument2['default'], 'touchend', this.handleMouseUp);

    this.update();
  };

  /**
   * Update slider
   *
   * @method update
   */

  Slider.prototype.update = function update() {
    // In VolumeBar init we have a setTimeout for update that pops and update to the end of the
    // execution stack. The player is destroyed before then update will cause an error
    if (!this.el_) return;

    // If scrubbing, we could use a cached value to make the handle keep up with the user's mouse.
    // On HTML5 browsers scrubbing is really smooth, but some flash players are slow, so we might want to utilize this later.
    // var progress =  (this.player_.scrubbing()) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration();
    var progress = this.getPercent();
    var bar = this.bar;

    // If there's no bar...
    if (!bar) return;

    // Protect against no duration and other division issues
    if (typeof progress !== 'number' || progress !== progress || progress < 0 || progress === Infinity) {
      progress = 0;
    }

    // Convert to a percentage for setting
    var percentage = (progress * 100).toFixed(2) + '%';

    // Set the new bar width or height
    if (this.vertical()) {
      bar.el().style.height = percentage;
    } else {
      bar.el().style.width = percentage;
    }
  };

  /**
   * Calculate distance for slider
   *
   * @param {Object} event Event object
   * @method calculateDistance
   */

  Slider.prototype.calculateDistance = function calculateDistance(event) {
    var position = Dom.getPointerPosition(this.el_, event);
    if (this.vertical()) {
      return position.y;
    }
    return position.x;
  };

  /**
   * Handle on focus for slider
   *
   * @method handleFocus
   */

  Slider.prototype.handleFocus = function handleFocus() {
    this.on(_globalDocument2['default'], 'keydown', this.handleKeyPress);
  };

  /**
   * Handle key press for slider
   *
   * @param {Object} event Event object
   * @method handleKeyPress
   */

  Slider.prototype.handleKeyPress = function handleKeyPress(event) {
    if (event.which === 37 || event.which === 40) {
      // Left and Down Arrows
      event.preventDefault();
      this.stepBack();
    } else if (event.which === 38 || event.which === 39) {
      // Up and Right Arrows
      event.preventDefault();
      this.stepForward();
    }
  };

  /**
   * Handle on blur for slider
   *
   * @method handleBlur
   */

  Slider.prototype.handleBlur = function handleBlur() {
    this.off(_globalDocument2['default'], 'keydown', this.handleKeyPress);
  };

  /**
   * Listener for click events on slider, used to prevent clicks
   *   from bubbling up to parent elements like button menus.
   *
   * @param {Object} event Event object
   * @method handleClick
   */

  Slider.prototype.handleClick = function handleClick(event) {
    event.stopImmediatePropagation();
    event.preventDefault();
  };

  /**
   * Get/set if slider is horizontal for vertical
   *
   * @param {Boolean} bool True if slider is vertical, false is horizontal
   * @return {Boolean} True if slider is vertical, false is horizontal
   * @method vertical
   */

  Slider.prototype.vertical = function vertical(bool) {
    if (bool === undefined) {
      return this.vertical_ || false;
    }

    this.vertical_ = !!bool;

    if (this.vertical_) {
      this.addClass('vjs-slider-vertical');
    } else {
      this.addClass('vjs-slider-horizontal');
    }

    return this;
  };

  return Slider;
})(_componentJs2['default']);

_componentJs2['default'].registerComponent('Slider', Slider);
exports['default'] = Slider;
module.exports = exports['default'];

},{"../component.js":65,"../utils/dom.js":128,"global/document":1,"object.assign":45}],111:[function(_dereq_,module,exports){
/**
 * @file flash-rtmp.js
 */
'use strict';

exports.__esModule = true;
function FlashRtmpDecorator(Flash) {
  Flash.streamingFormats = {
    'rtmp/mp4': 'MP4',
    'rtmp/flv': 'FLV'
  };

  Flash.streamFromParts = function (connection, stream) {
    return connection + '&' + stream;
  };

  Flash.streamToParts = function (src) {
    var parts = {
      connection: '',
      stream: ''
    };

    if (!src) return parts;

    // Look for the normal URL separator we expect, '&'.
    // If found, we split the URL into two pieces around the
    // first '&'.
    var connEnd = src.search(/&(?!\w+=)/);
    var streamBegin = undefined;
    if (connEnd !== -1) {
      streamBegin = connEnd + 1;
    } else {
      // If there's not a '&', we use the last '/' as the delimiter.
      connEnd = streamBegin = src.lastIndexOf('/') + 1;
      if (connEnd === 0) {
        // really, there's not a '/'?
        connEnd = streamBegin = src.length;
      }
    }
    parts.connection = src.substring(0, connEnd);
    parts.stream = src.substring(streamBegin, src.length);

    return parts;
  };

  Flash.isStreamingType = function (srcType) {
    return srcType in Flash.streamingFormats;
  };

  // RTMP has four variations, any string starting
  // with one of these protocols should be valid
  Flash.RTMP_RE = /^rtmp[set]?:\/\//i;

  Flash.isStreamingSrc = function (src) {
    return Flash.RTMP_RE.test(src);
  };

  /**
   * A source handler for RTMP urls
   * @type {Object}
   */
  Flash.rtmpSourceHandler = {};

  /**
   * Check if Flash can play the given videotype
   * @param  {String} type    The mimetype to check
   * @return {String}         'probably', 'maybe', or '' (empty string)
   */
  Flash.rtmpSourceHandler.canPlayType = function (type) {
    if (Flash.isStreamingType(type)) {
      return 'maybe';
    }

    return '';
  };

  /**
   * Check if Flash can handle the source natively
   * @param  {Object} source  The source object
   * @return {String}         'probably', 'maybe', or '' (empty string)
   */
  Flash.rtmpSourceHandler.canHandleSource = function (source) {
    var can = Flash.rtmpSourceHandler.canPlayType(source.type);

    if (can) {
      return can;
    }

    if (Flash.isStreamingSrc(source.src)) {
      return 'maybe';
    }

    return '';
  };

  /**
   * Pass the source to the flash object
   * Adaptive source handlers will have more complicated workflows before passing
   * video data to the video element
   * @param  {Object} source    The source object
   * @param  {Flash} tech   The instance of the Flash tech
   */
  Flash.rtmpSourceHandler.handleSource = function (source, tech) {
    var srcParts = Flash.streamToParts(source.src);

    tech['setRtmpConnection'](srcParts.connection);
    tech['setRtmpStream'](srcParts.stream);
  };

  // Register the native source handler
  Flash.registerSourceHandler(Flash.rtmpSourceHandler);

  return Flash;
}

exports['default'] = FlashRtmpDecorator;
module.exports = exports['default'];

},{}],112:[function(_dereq_,module,exports){
/**
 * @file flash.js
 * VideoJS-SWF - Custom Flash Player with HTML5-ish API
 * https://github.com/zencoder/video-js-swf
 * Not using setupTriggers. Using global onEvent func to distribute events
 */

'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _tech = _dereq_('./tech');

var _tech2 = _interopRequireDefault(_tech);

var _utilsDomJs = _dereq_('../utils/dom.js');

var Dom = _interopRequireWildcard(_utilsDomJs);

var _utilsUrlJs = _dereq_('../utils/url.js');

var Url = _interopRequireWildcard(_utilsUrlJs);

var _utilsTimeRangesJs = _dereq_('../utils/time-ranges.js');

var _flashRtmp = _dereq_('./flash-rtmp');

var _flashRtmp2 = _interopRequireDefault(_flashRtmp);

var _component = _dereq_('../component');

var _component2 = _interopRequireDefault(_component);

var _globalWindow = _dereq_('global/window');

var _globalWindow2 = _interopRequireDefault(_globalWindow);

var _objectAssign = _dereq_('object.assign');

var _objectAssign2 = _interopRequireDefault(_objectAssign);

var navigator = _globalWindow2['default'].navigator;
/**
 * Flash Media Controller - Wrapper for fallback SWF API
 *
 * @param {Object=} options Object of option names and values
 * @param {Function=} ready Ready callback function
 * @extends Tech
 * @class Flash
 */

var Flash = (function (_Tech) {
  _inherits(Flash, _Tech);

  function Flash(options, ready) {
    _classCallCheck(this, Flash);

    _Tech.call(this, options, ready);

    // Set the source when ready
    if (options.source) {
      this.ready(function () {
        this.setSource(options.source);
      }, true);
    }

    // Having issues with Flash reloading on certain page actions (hide/resize/fullscreen) in certain browsers
    // This allows resetting the playhead when we catch the reload
    if (options.startTime) {
      this.ready(function () {
        this.load();
        this.play();
        this.currentTime(options.startTime);
      }, true);
    }

    // Add global window functions that the swf expects
    // A 4.x workflow we weren't able to solve for in 5.0
    // because of the need to hard code these functions
    // into the swf for security reasons
    _globalWindow2['default'].videojs = _globalWindow2['default'].videojs || {};
    _globalWindow2['default'].videojs.Flash = _globalWindow2['default'].videojs.Flash || {};
    _globalWindow2['default'].videojs.Flash.onReady = Flash.onReady;
    _globalWindow2['default'].videojs.Flash.onEvent = Flash.onEvent;
    _globalWindow2['default'].videojs.Flash.onError = Flash.onError;

    this.on('seeked', function () {
      this.lastSeekTarget_ = undefined;
    });
  }

  // Create setters and getters for attributes

  /**
   * Create the component's DOM element
   *
   * @return {Element}
   * @method createEl
   */

  Flash.prototype.createEl = function createEl() {
    var options = this.options_;

    // If video.js is hosted locally you should also set the location
    // for the hosted swf, which should be relative to the page (not video.js)
    // Otherwise this adds a CDN url.
    // The CDN also auto-adds a swf URL for that specific version.
    if (!options.swf) {
      options.swf = '//vjs.zencdn.net/swf/5.0.1/video-js.swf';
    }

    // Generate ID for swf object
    var objId = options.techId;

    // Merge default flashvars with ones passed in to init
    var flashVars = _objectAssign2['default']({

      // SWF Callback Functions
      'readyFunction': 'videojs.Flash.onReady',
      'eventProxyFunction': 'videojs.Flash.onEvent',
      'errorEventProxyFunction': 'videojs.Flash.onError',

      // Player Settings
      'autoplay': options.autoplay,
      'preload': options.preload,
      'loop': options.loop,
      'muted': options.muted

    }, options.flashVars);

    // Merge default parames with ones passed in
    var params = _objectAssign2['default']({
      'wmode': 'opaque', // Opaque is needed to overlay controls, but can affect playback performance
      'bgcolor': '#000000' // Using bgcolor prevents a white flash when the object is loading
    }, options.params);

    // Merge default attributes with ones passed in
    var attributes = _objectAssign2['default']({
      'id': objId,
      'name': objId, // Both ID and Name needed or swf to identify itself
      'class': 'vjs-tech'
    }, options.attributes);

    this.el_ = Flash.embed(options.swf, flashVars, params, attributes);
    this.el_.tech = this;

    return this.el_;
  };

  /**
   * Play for flash tech
   *
   * @method play
   */

  Flash.prototype.play = function play() {
    if (this.ended()) {
      this.setCurrentTime(0);
    }
    this.el_.vjs_play();
  };

  /**
   * Pause for flash tech
   *
   * @method pause
   */

  Flash.prototype.pause = function pause() {
    this.el_.vjs_pause();
  };

  /**
   * Get/set video
   *
   * @param {Object=} src Source object
   * @return {Object}
   * @method src
   */

  Flash.prototype.src = function src(_src) {
    if (_src === undefined) {
      return this.currentSrc();
    }

    // Setting src through `src` not `setSrc` will be deprecated
    return this.setSrc(_src);
  };

  /**
   * Set video
   *
   * @param {Object=} src Source object
   * @deprecated
   * @method setSrc
   */

  Flash.prototype.setSrc = function setSrc(src) {
    // Make sure source URL is absolute.
    src = Url.getAbsoluteURL(src);
    this.el_.vjs_src(src);

    // Currently the SWF doesn't autoplay if you load a source later.
    // e.g. Load player w/ no source, wait 2s, set src.
    if (this.autoplay()) {
      var tech = this;
      this.setTimeout(function () {
        tech.play();
      }, 0);
    }
  };

  /**
   * Returns true if the tech is currently seeking.
   * @return {boolean} true if seeking
   */

  Flash.prototype.seeking = function seeking() {
    return this.lastSeekTarget_ !== undefined;
  };

  /**
   * Set current time
   *
   * @param {Number} time Current time of video
   * @method setCurrentTime
   */

  Flash.prototype.setCurrentTime = function setCurrentTime(time) {
    var seekable = this.seekable();
    if (seekable.length) {
      // clamp to the current seekable range
      time = time > seekable.start(0) ? time : seekable.start(0);
      time = time < seekable.end(seekable.length - 1) ? time : seekable.end(seekable.length - 1);

      this.lastSeekTarget_ = time;
      this.trigger('seeking');
      this.el_.vjs_setProperty('currentTime', time);
      _Tech.prototype.setCurrentTime.call(this);
    }
  };

  /**
   * Get current time
   *
   * @param {Number=} time Current time of video
   * @return {Number} Current time
   * @method currentTime
   */

  Flash.prototype.currentTime = function currentTime(time) {
    // when seeking make the reported time keep up with the requested time
    // by reading the time we're seeking to
    if (this.seeking()) {
      return this.lastSeekTarget_ || 0;
    }
    return this.el_.vjs_getProperty('currentTime');
  };

  /**
   * Get current source
   *
   * @method currentSrc
   */

  Flash.prototype.currentSrc = function currentSrc() {
    if (this.currentSource_) {
      return this.currentSource_.src;
    } else {
      return this.el_.vjs_getProperty('currentSrc');
    }
  };

  /**
   * Load media into player
   *
   * @method load
   */

  Flash.prototype.load = function load() {
    this.el_.vjs_load();
  };

  /**
   * Get poster
   *
   * @method poster
   */

  Flash.prototype.poster = function poster() {
    this.el_.vjs_getProperty('poster');
  };

  /**
   * Poster images are not handled by the Flash tech so make this a no-op
   *
   * @method setPoster
   */

  Flash.prototype.setPoster = function setPoster() {};

  /**
   * Determine if can seek in media
   *
   * @return {TimeRangeObject}
   * @method seekable
   */

  Flash.prototype.seekable = function seekable() {
    var duration = this.duration();
    if (duration === 0) {
      return _utilsTimeRangesJs.createTimeRange();
    }
    return _utilsTimeRangesJs.createTimeRange(0, duration);
  };

  /**
   * Get buffered time range
   *
   * @return {TimeRangeObject}
   * @method buffered
   */

  Flash.prototype.buffered = function buffered() {
    var ranges = this.el_.vjs_getProperty('buffered');
    if (ranges.length === 0) {
      return _utilsTimeRangesJs.createTimeRange();
    }
    return _utilsTimeRangesJs.createTimeRange(ranges[0][0], ranges[0][1]);
  };

  /**
   * Get fullscreen support -
   * Flash does not allow fullscreen through javascript
   * so always returns false
   *
   * @return {Boolean} false
   * @method supportsFullScreen
   */

  Flash.prototype.supportsFullScreen = function supportsFullScreen() {
    return false; // Flash does not allow fullscreen through javascript
  };

  /**
   * Request to enter fullscreen
   * Flash does not allow fullscreen through javascript
   * so always returns false
   *
   * @return {Boolean} false
   * @method enterFullScreen
   */

  Flash.prototype.enterFullScreen = function enterFullScreen() {
    return false;
  };

  return Flash;
})(_tech2['default']);

var _api = Flash.prototype;
var _readWrite = 'rtmpConnection,rtmpStream,preload,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted'.split(',');
var _readOnly = 'networkState,readyState,initialTime,duration,startOffsetTime,paused,ended,videoTracks,audioTracks,videoWidth,videoHeight'.split(',');

function _createSetter(attr) {
  var attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1);
  _api['set' + attrUpper] = function (val) {
    return this.el_.vjs_setProperty(attr, val);
  };
}
function _createGetter(attr) {
  _api[attr] = function () {
    return this.el_.vjs_getProperty(attr);
  };
}

// Create getter and setters for all read/write attributes
for (var i = 0; i < _readWrite.length; i++) {
  _createGetter(_readWrite[i]);
  _createSetter(_readWrite[i]);
}

// Create getters for read-only attributes
for (var i = 0; i < _readOnly.length; i++) {
  _createGetter(_readOnly[i]);
}

/* Flash Support Testing -------------------------------------------------------- */

Flash.isSupported = function () {
  return Flash.version()[0] >= 10;
  // return swfobject.hasFlashPlayerVersion('10');
};

// Add Source Handler pattern functions to this tech
_tech2['default'].withSourceHandlers(Flash);

/*
 * The default native source handler.
 * This simply passes the source to the video element. Nothing fancy.
 *
 * @param  {Object} source   The source object
 * @param  {Flash} tech  The instance of the Flash tech
 */
Flash.nativeSourceHandler = {};

/**
 * Check if Flash can play the given videotype
 * @param  {String} type    The mimetype to check
 * @return {String}         'probably', 'maybe', or '' (empty string)
 */
Flash.nativeSourceHandler.canPlayType = function (type) {
  if (type in Flash.formats) {
    return 'maybe';
  }

  return '';
};

/*
 * Check Flash can handle the source natively
 *
 * @param  {Object} source  The source object
 * @return {String}         'probably', 'maybe', or '' (empty string)
 */
Flash.nativeSourceHandler.canHandleSource = function (source) {
  var type;

  function guessMimeType(src) {
    var ext = Url.getFileExtension(src);
    if (ext) {
      return 'video/' + ext;
    }
    return '';
  }

  if (!source.type) {
    type = guessMimeType(source.src);
  } else {
    // Strip code information from the type because we don't get that specific
    type = source.type.replace(/;.*/, '').toLowerCase();
  }

  return Flash.nativeSourceHandler.canPlayType(type);
};

/*
 * Pass the source to the flash object
 * Adaptive source handlers will have more complicated workflows before passing
 * video data to the video element
 *
 * @param  {Object} source    The source object
 * @param  {Flash} tech   The instance of the Flash tech
 */
Flash.nativeSourceHandler.handleSource = function (source, tech) {
  tech.setSrc(source.src);
};

/*
 * Clean up the source handler when disposing the player or switching sources..
 * (no cleanup is needed when supporting the format natively)
 */
Flash.nativeSourceHandler.dispose = function () {};

// Register the native source handler
Flash.registerSourceHandler(Flash.nativeSourceHandler);

Flash.formats = {
  'video/flv': 'FLV',
  'video/x-flv': 'FLV',
  'video/mp4': 'MP4',
  'video/m4v': 'MP4'
};

Flash.onReady = function (currSwf) {
  var el = Dom.getEl(currSwf);
  var tech = el && el.tech;

  // if there is no el then the tech has been disposed
  // and the tech element was removed from the player div
  if (tech && tech.el()) {
    // check that the flash object is really ready
    Flash.checkReady(tech);
  }
};

// The SWF isn't always ready when it says it is. Sometimes the API functions still need to be added to the object.
// If it's not ready, we set a timeout to check again shortly.
Flash.checkReady = function (tech) {
  // stop worrying if the tech has been disposed
  if (!tech.el()) {
    return;
  }

  // check if API property exists
  if (tech.el().vjs_getProperty) {
    // tell tech it's ready
    tech.triggerReady();
  } else {
    // wait longer
    this.setTimeout(function () {
      Flash['checkReady'](tech);
    }, 50);
  }
};

// Trigger events from the swf on the player
Flash.onEvent = function (swfID, eventName) {
  var tech = Dom.getEl(swfID).tech;
  tech.trigger(eventName);
};

// Log errors from the swf
Flash.onError = function (swfID, err) {
  var tech = Dom.getEl(swfID).tech;

  // trigger MEDIA_ERR_SRC_NOT_SUPPORTED
  if (err === 'srcnotfound') {
    return tech.error(4);
  }

  // trigger a custom error
  tech.error('FLASH: ' + err);
};

// Flash Version Check
Flash.version = function () {
  var version = '0,0,0';

  // IE
  try {
    version = new _globalWindow2['default'].ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version').replace(/\D+/g, ',').match(/^,?(.+),?$/)[1];

    // other browsers
  } catch (e) {
    try {
      if (navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin) {
        version = (navigator.plugins['Shockwave Flash 2.0'] || navigator.plugins['Shockwave Flash']).description.replace(/\D+/g, ',').match(/^,?(.+),?$/)[1];
      }
    } catch (err) {}
  }
  return version.split(',');
};

// Flash embedding method. Only used in non-iframe mode
Flash.embed = function (swf, flashVars, params, attributes) {
  var code = Flash.getEmbedCode(swf, flashVars, params, attributes);

  // Get element by embedding code and retrieving created element
  var obj = Dom.createEl('div', { innerHTML: code }).childNodes[0];

  return obj;
};

Flash.getEmbedCode = function (swf, flashVars, params, attributes) {
  var objTag = '<object type="application/x-shockwave-flash" ';
  var flashVarsString = '';
  var paramsString = '';
  var attrsString = '';

  // Convert flash vars to string
  if (flashVars) {
    Object.getOwnPropertyNames(flashVars).forEach(function (key) {
      flashVarsString += key + '=' + flashVars[key] + '&amp;';
    });
  }

  // Add swf, flashVars, and other default params
  params = _objectAssign2['default']({
    'movie': swf,
    'flashvars': flashVarsString,
    'allowScriptAccess': 'always', // Required to talk to swf
    'allowNetworking': 'all' // All should be default, but having security issues.
  }, params);

  // Create param tags string
  Object.getOwnPropertyNames(params).forEach(function (key) {
    paramsString += '<param name="' + key + '" value="' + params[key] + '" />';
  });

  attributes = _objectAssign2['default']({
    // Add swf to attributes (need both for IE and Others to work)
    'data': swf,

    // Default to 100% width/height
    'width': '100%',
    'height': '100%'

  }, attributes);

  // Create Attributes string
  Object.getOwnPropertyNames(attributes).forEach(function (key) {
    attrsString += key + '="' + attributes[key] + '" ';
  });

  return '' + objTag + attrsString + '>' + paramsString + '</object>';
};

// Run Flash through the RTMP decorator
_flashRtmp2['default'](Flash);

_component2['default'].registerComponent('Flash', Flash);
_tech2['default'].registerTech('Flash', Flash);
exports['default'] = Flash;
module.exports = exports['default'];

},{"../component":65,"../utils/dom.js":128,"../utils/time-ranges.js":136,"../utils/url.js":138,"./flash-rtmp":111,"./tech":115,"global/window":2,"object.assign":45}],113:[function(_dereq_,module,exports){
/**
 * @file html5.js
 * HTML5 Media Controller - Wrapper for HTML5 Media API
 */

'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _techJs = _dereq_('./tech.js');

var _techJs2 = _interopRequireDefault(_techJs);

var _component = _dereq_('../component');

var _component2 = _interopRequireDefault(_component);

var _utilsDomJs = _dereq_('../utils/dom.js');

var Dom = _interopRequireWildcard(_utilsDomJs);

var _utilsUrlJs = _dereq_('../utils/url.js');

var Url = _interopRequireWildcard(_utilsUrlJs);

var _utilsFnJs = _dereq_('../utils/fn.js');

var Fn = _interopRequireWildcard(_utilsFnJs);

var _utilsLogJs = _dereq_('../utils/log.js');

var _utilsLogJs2 = _interopRequireDefault(_utilsLogJs);

var _utilsBrowserJs = _dereq_('../utils/browser.js');

var browser = _interopRequireWildcard(_utilsBrowserJs);

var _globalDocument = _dereq_('global/document');

var _globalDocument2 = _interopRequireDefault(_globalDocument);

var _globalWindow = _dereq_('global/window');

var _globalWindow2 = _interopRequireDefault(_globalWindow);

var _objectAssign = _dereq_('object.assign');

var _objectAssign2 = _interopRequireDefault(_objectAssign);

var _utilsMergeOptionsJs = _dereq_('../utils/merge-options.js');

var _utilsMergeOptionsJs2 = _interopRequireDefault(_utilsMergeOptionsJs);

/**
 * HTML5 Media Controller - Wrapper for HTML5 Media API
 *
 * @param {Object=} options Object of option names and values
 * @param {Function=} ready Ready callback function
 * @extends Tech
 * @class Html5
 */

var Html5 = (function (_Tech) {
  _inherits(Html5, _Tech);

  function Html5(options, ready) {
    _classCallCheck(this, Html5);

    _Tech.call(this, options, ready);

    var source = options.source;

    // Set the source if one is provided
    // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
    // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source
    // anyway so the error gets fired.
    if (source && (this.el_.currentSrc !== source.src || options.tag && options.tag.initNetworkState_ === 3)) {
      this.setSource(source);
    } else {
      this.handleLateInit_(this.el_);
    }

    if (this.el_.hasChildNodes()) {

      var nodes = this.el_.childNodes;
      var nodesLength = nodes.length;
      var removeNodes = [];

      while (nodesLength--) {
        var node = nodes[nodesLength];
        var nodeName = node.nodeName.toLowerCase();

        if (nodeName === 'track') {
          if (!this.featuresNativeTextTracks) {
            // Empty video tag tracks so the built-in player doesn't use them also.
            // This may not be fast enough to stop HTML5 browsers from reading the tags
            // so we'll need to turn off any default tracks if we're manually doing
            // captions and subtitles. videoElement.textTracks
            removeNodes.push(node);
          } else {
            // store HTMLTrackElement and TextTrack to remote list
            this.remoteTextTrackEls().addTrackElement_(node);
            this.remoteTextTracks().addTrack_(node.track);
          }
        }
      }

      for (var i = 0; i < removeNodes.length; i++) {
        this.el_.removeChild(removeNodes[i]);
      }
    }

    if (this.featuresNativeTextTracks) {
      this.handleTextTrackChange_ = Fn.bind(this, this.handleTextTrackChange);
      this.handleTextTrackAdd_ = Fn.bind(this, this.handleTextTrackAdd);
      this.handleTextTrackRemove_ = Fn.bind(this, this.handleTextTrackRemove);
      this.proxyNativeTextTracks_();
    }

    // Determine if native controls should be used
    // Our goal should be to get the custom controls on mobile solid everywhere
    // so we can remove this all together. Right now this will block custom
    // controls on touch enabled laptops like the Chrome Pixel
    if (browser.TOUCH_ENABLED && options.nativeControlsForTouch === true || browser.IS_IPHONE || browser.IS_NATIVE_ANDROID) {
      this.setControls(true);
    }

    this.triggerReady();
  }

  /* HTML5 Support Testing ---------------------------------------------------- */

  /*
  * Element for testing browser HTML5 video capabilities
  *
  * @type {Element}
  * @constant
  * @private
  */

  /**
   * Dispose of html5 media element
   *
   * @method dispose
   */

  Html5.prototype.dispose = function dispose() {
    var tt = this.el().textTracks;
    var emulatedTt = this.textTracks();

    // remove native event listeners
    if (tt && tt.removeEventListener) {
      tt.removeEventListener('change', this.handleTextTrackChange_);
      tt.removeEventListener('addtrack', this.handleTextTrackAdd_);
      tt.removeEventListener('removetrack', this.handleTextTrackRemove_);
    }

    // clearout the emulated text track list.
    var i = emulatedTt.length;

    while (i--) {
      emulatedTt.removeTrack_(emulatedTt[i]);
    }

    Html5.disposeMediaElement(this.el_);
    _Tech.prototype.dispose.call(this);
  };

  /**
   * Create the component's DOM element
   *
   * @return {Element}
   * @method createEl
   */

  Html5.prototype.createEl = function createEl() {
    var el = this.options_.tag;

    // Check if this browser supports moving the element into the box.
    // On the iPhone video will break if you move the element,
    // So we have to create a brand new element.
    if (!el || this['movingMediaElementInDOM'] === false) {

      // If the original tag is still there, clone and remove it.
      if (el) {
        var clone = el.cloneNode(true);
        el.parentNode.insertBefore(clone, el);
        Html5.disposeMediaElement(el);
        el = clone;
      } else {
        el = _globalDocument2['default'].createElement('video');

        // determine if native controls should be used
        var tagAttributes = this.options_.tag && Dom.getElAttributes(this.options_.tag);
        var attributes = _utilsMergeOptionsJs2['default']({}, tagAttributes);
        if (!browser.TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) {
          delete attributes.controls;
        }

        Dom.setElAttributes(el, _objectAssign2['default'](attributes, {
          id: this.options_.techId,
          'class': 'vjs-tech'
        }));
      }
    }

    // Update specific tag settings, in case they were overridden
    var settingsAttrs = ['autoplay', 'preload', 'loop', 'muted'];
    for (var i = settingsAttrs.length - 1; i >= 0; i--) {
      var attr = settingsAttrs[i];
      var overwriteAttrs = {};
      if (typeof this.options_[attr] !== 'undefined') {
        overwriteAttrs[attr] = this.options_[attr];
      }
      Dom.setElAttributes(el, overwriteAttrs);
    }

    return el;
    // jenniisawesome = true;
  };

  // If we're loading the playback object after it has started loading
  // or playing the video (often with autoplay on) then the loadstart event
  // has already fired and we need to fire it manually because many things
  // rely on it.

  Html5.prototype.handleLateInit_ = function handleLateInit_(el) {
    var _this = this;

    if (el.networkState === 0 || el.networkState === 3) {
      // The video element hasn't started loading the source yet
      // or didn't find a source
      return;
    }

    if (el.readyState === 0) {
      var _ret = (function () {
        // NetworkState is set synchronously BUT loadstart is fired at the
        // end of the current stack, usually before setInterval(fn, 0).
        // So at this point we know loadstart may have already fired or is
        // about to fire, and either way the player hasn't seen it yet.
        // We don't want to fire loadstart prematurely here and cause a
        // double loadstart so we'll wait and see if it happens between now
        // and the next loop, and fire it if not.
        // HOWEVER, we also want to make sure it fires before loadedmetadata
        // which could also happen between now and the next loop, so we'll
        // watch for that also.
        var loadstartFired = false;
        var setLoadstartFired = function setLoadstartFired() {
          loadstartFired = true;
        };
        _this.on('loadstart', setLoadstartFired);

        var triggerLoadstart = function triggerLoadstart() {
          // We did miss the original loadstart. Make sure the player
          // sees loadstart before loadedmetadata
          if (!loadstartFired) {
            this.trigger('loadstart');
          }
        };
        _this.on('loadedmetadata', triggerLoadstart);

        _this.ready(function () {
          this.off('loadstart', setLoadstartFired);
          this.off('loadedmetadata', triggerLoadstart);

          if (!loadstartFired) {
            // We did miss the original native loadstart. Fire it now.
            this.trigger('loadstart');
          }
        });

        return {
          v: undefined
        };
      })();

      if (typeof _ret === 'object') return _ret.v;
    }

    // From here on we know that loadstart already fired and we missed it.
    // The other readyState events aren't as much of a problem if we double
    // them, so not going to go to as much trouble as loadstart to prevent
    // that unless we find reason to.
    var eventsToTrigger = ['loadstart'];

    // loadedmetadata: newly equal to HAVE_METADATA (1) or greater
    eventsToTrigger.push('loadedmetadata');

    // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater
    if (el.readyState >= 2) {
      eventsToTrigger.push('loadeddata');
    }

    // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater
    if (el.readyState >= 3) {
      eventsToTrigger.push('canplay');
    }

    // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4)
    if (el.readyState >= 4) {
      eventsToTrigger.push('canplaythrough');
    }

    // We still need to give the player time to add event listeners
    this.ready(function () {
      eventsToTrigger.forEach(function (type) {
        this.trigger(type);
      }, this);
    });
  };

  Html5.prototype.proxyNativeTextTracks_ = function proxyNativeTextTracks_() {
    var tt = this.el().textTracks;

    if (tt && tt.addEventListener) {
      tt.addEventListener('change', this.handleTextTrackChange_);
      tt.addEventListener('addtrack', this.handleTextTrackAdd_);
      tt.addEventListener('removetrack', this.handleTextTrackRemove_);
    }
  };

  Html5.prototype.handleTextTrackChange = function handleTextTrackChange(e) {
    var tt = this.textTracks();
    this.textTracks().trigger({
      type: 'change',
      target: tt,
      currentTarget: tt,
      srcElement: tt
    });
  };

  Html5.prototype.handleTextTrackAdd = function handleTextTrackAdd(e) {
    this.textTracks().addTrack_(e.track);
  };

  Html5.prototype.handleTextTrackRemove = function handleTextTrackRemove(e) {
    this.textTracks().removeTrack_(e.track);
  };

  /**
   * Play for html5 tech
   *
   * @method play
   */

  Html5.prototype.play = function play() {
    this.el_.play();
  };

  /**
   * Pause for html5 tech
   *
   * @method pause
   */

  Html5.prototype.pause = function pause() {
    this.el_.pause();
  };

  /**
   * Paused for html5 tech
   *
   * @return {Boolean}
   * @method paused
   */

  Html5.prototype.paused = function paused() {
    return this.el_.paused;
  };

  /**
   * Get current time
   *
   * @return {Number}
   * @method currentTime
   */

  Html5.prototype.currentTime = function currentTime() {
    return this.el_.currentTime;
  };

  /**
   * Set current time
   *
   * @param {Number} seconds Current time of video
   * @method setCurrentTime
   */

  Html5.prototype.setCurrentTime = function setCurrentTime(seconds) {
    try {
      this.el_.currentTime = seconds;
    } catch (e) {
      _utilsLogJs2['default'](e, 'Video is not ready. (Video.js)');
      // this.warning(VideoJS.warnings.videoNotReady);
    }
  };

  /**
   * Get duration
   *
   * @return {Number}
   * @method duration
   */

  Html5.prototype.duration = function duration() {
    return this.el_.duration || 0;
  };

  /**
   * Get a TimeRange object that represents the intersection
   * of the time ranges for which the user agent has all
   * relevant media
   *
   * @return {TimeRangeObject}
   * @method buffered
   */

  Html5.prototype.buffered = function buffered() {
    return this.el_.buffered;
  };

  /**
   * Get volume level
   *
   * @return {Number}
   * @method volume
   */

  Html5.prototype.volume = function volume() {
    return this.el_.volume;
  };

  /**
   * Set volume level
   *
   * @param {Number} percentAsDecimal Volume percent as a decimal
   * @method setVolume
   */

  Html5.prototype.setVolume = function setVolume(percentAsDecimal) {
    this.el_.volume = percentAsDecimal;
  };

  /**
   * Get if muted
   *
   * @return {Boolean}
   * @method muted
   */

  Html5.prototype.muted = function muted() {
    return this.el_.muted;
  };

  /**
   * Set muted
   *
   * @param {Boolean} If player is to be muted or note
   * @method setMuted
   */

  Html5.prototype.setMuted = function setMuted(muted) {
    this.el_.muted = muted;
  };

  /**
   * Get player width
   *
   * @return {Number}
   * @method width
   */

  Html5.prototype.width = function width() {
    return this.el_.offsetWidth;
  };

  /**
   * Get player height
   *
   * @return {Number}
   * @method height
   */

  Html5.prototype.height = function height() {
    return this.el_.offsetHeight;
  };

  /**
   * Get if there is fullscreen support
   *
   * @return {Boolean}
   * @method supportsFullScreen
   */

  Html5.prototype.supportsFullScreen = function supportsFullScreen() {
    if (typeof this.el_.webkitEnterFullScreen === 'function') {
      var userAgent = _globalWindow2['default'].navigator.userAgent;
      // Seems to be broken in Chromium/Chrome && Safari in Leopard
      if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) {
        return true;
      }
    }
    return false;
  };

  /**
   * Request to enter fullscreen
   *
   * @method enterFullScreen
   */

  Html5.prototype.enterFullScreen = function enterFullScreen() {
    var video = this.el_;

    if ('webkitDisplayingFullscreen' in video) {
      this.one('webkitbeginfullscreen', function () {
        this.one('webkitendfullscreen', function () {
          this.trigger('fullscreenchange', { isFullscreen: false });
        });

        this.trigger('fullscreenchange', { isFullscreen: true });
      });
    }

    if (video.paused && video.networkState <= video.HAVE_METADATA) {
      // attempt to prime the video element for programmatic access
      // this isn't necessary on the desktop but shouldn't hurt
      this.el_.play();

      // playing and pausing synchronously during the transition to fullscreen
      // can get iOS ~6.1 devices into a play/pause loop
      this.setTimeout(function () {
        video.pause();
        video.webkitEnterFullScreen();
      }, 0);
    } else {
      video.webkitEnterFullScreen();
    }
  };

  /**
   * Request to exit fullscreen
   *
   * @method exitFullScreen
   */

  Html5.prototype.exitFullScreen = function exitFullScreen() {
    this.el_.webkitExitFullScreen();
  };

  /**
   * Get/set video
   *
   * @param {Object=} src Source object
   * @return {Object}
   * @method src
   */

  Html5.prototype.src = function src(_src) {
    if (_src === undefined) {
      return this.el_.src;
    } else {
      // Setting src through `src` instead of `setSrc` will be deprecated
      this.setSrc(_src);
    }
  };

  /**
   * Set video
   *
   * @param {Object} src Source object
   * @deprecated
   * @method setSrc
   */

  Html5.prototype.setSrc = function setSrc(src) {
    this.el_.src = src;
  };

  /**
   * Load media into player
   *
   * @method load
   */

  Html5.prototype.load = function load() {
    this.el_.load();
  };

  /**
   * Reset the tech. Removes all sources and calls `load`.
   *
   * @method reset
   */

  Html5.prototype.reset = function reset() {
    Html5.resetMediaElement(this.el_);
  };

  /**
   * Get current source
   *
   * @return {Object}
   * @method currentSrc
   */

  Html5.prototype.currentSrc = function currentSrc() {
    if (this.currentSource_) {
      return this.currentSource_.src;
    } else {
      return this.el_.currentSrc;
    }
  };

  /**
   * Get poster
   *
   * @return {String}
   * @method poster
   */

  Html5.prototype.poster = function poster() {
    return this.el_.poster;
  };

  /**
   * Set poster
   *
   * @param {String} val URL to poster image
   * @method
   */

  Html5.prototype.setPoster = function setPoster(val) {
    this.el_.poster = val;
  };

  /**
   * Get preload attribute
   *
   * @return {String}
   * @method preload
   */

  Html5.prototype.preload = function preload() {
    return this.el_.preload;
  };

  /**
   * Set preload attribute
   *
   * @param {String} val Value for preload attribute
   * @method setPreload
   */

  Html5.prototype.setPreload = function setPreload(val) {
    this.el_.preload = val;
  };

  /**
   * Get autoplay attribute
   *
   * @return {String}
   * @method autoplay
   */

  Html5.prototype.autoplay = function autoplay() {
    return this.el_.autoplay;
  };

  /**
   * Set autoplay attribute
   *
   * @param {String} val Value for preload attribute
   * @method setAutoplay
   */

  Html5.prototype.setAutoplay = function setAutoplay(val) {
    this.el_.autoplay = val;
  };

  /**
   * Get controls attribute
   *
   * @return {String}
   * @method controls
   */

  Html5.prototype.controls = function controls() {
    return this.el_.controls;
  };

  /**
   * Set controls attribute
   *
   * @param {String} val Value for controls attribute
   * @method setControls
   */

  Html5.prototype.setControls = function setControls(val) {
    this.el_.controls = !!val;
  };

  /**
   * Get loop attribute
   *
   * @return {String}
   * @method loop
   */

  Html5.prototype.loop = function loop() {
    return this.el_.loop;
  };

  /**
   * Set loop attribute
   *
   * @param {String} val Value for loop attribute
   * @method setLoop
   */

  Html5.prototype.setLoop = function setLoop(val) {
    this.el_.loop = val;
  };

  /**
   * Get error value
   *
   * @return {String}
   * @method error
   */

  Html5.prototype.error = function error() {
    return this.el_.error;
  };

  /**
   * Get whether or not the player is in the "seeking" state
   *
   * @return {Boolean}
   * @method seeking
   */

  Html5.prototype.seeking = function seeking() {
    return this.el_.seeking;
  };

  /**
   * Get a TimeRanges object that represents the
   * ranges of the media resource to which it is possible
   * for the user agent to seek.
   *
   * @return {TimeRangeObject}
   * @method seekable
   */

  Html5.prototype.seekable = function seekable() {
    return this.el_.seekable;
  };

  /**
   * Get if video ended
   *
   * @return {Boolean}
   * @method ended
   */

  Html5.prototype.ended = function ended() {
    return this.el_.ended;
  };

  /**
   * Get the value of the muted content attribute
   * This attribute has no dynamic effect, it only
   * controls the default state of the element
   *
   * @return {Boolean}
   * @method defaultMuted
   */

  Html5.prototype.defaultMuted = function defaultMuted() {
    return this.el_.defaultMuted;
  };

  /**
   * Get desired speed at which the media resource is to play
   *
   * @return {Number}
   * @method playbackRate
   */

  Html5.prototype.playbackRate = function playbackRate() {
    return this.el_.playbackRate;
  };

  /**
   * Returns a TimeRanges object that represents the ranges of the
   * media resource that the user agent has played.
   * @return {TimeRangeObject} the range of points on the media
   * timeline that has been reached through normal playback
   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-played
   */

  Html5.prototype.played = function played() {
    return this.el_.played;
  };

  /**
   * Set desired speed at which the media resource is to play
   *
   * @param {Number} val Speed at which the media resource is to play
   * @method setPlaybackRate
   */

  Html5.prototype.setPlaybackRate = function setPlaybackRate(val) {
    this.el_.playbackRate = val;
  };

  /**
   * Get the current state of network activity for the element, from
   * the list below
   * NETWORK_EMPTY (numeric value 0)
   * NETWORK_IDLE (numeric value 1)
   * NETWORK_LOADING (numeric value 2)
   * NETWORK_NO_SOURCE (numeric value 3)
   *
   * @return {Number}
   * @method networkState
   */

  Html5.prototype.networkState = function networkState() {
    return this.el_.networkState;
  };

  /**
   * Get a value that expresses the current state of the element
   * with respect to rendering the current playback position, from
   * the codes in the list below
   * HAVE_NOTHING (numeric value 0)
   * HAVE_METADATA (numeric value 1)
   * HAVE_CURRENT_DATA (numeric value 2)
   * HAVE_FUTURE_DATA (numeric value 3)
   * HAVE_ENOUGH_DATA (numeric value 4)
   *
   * @return {Number}
   * @method readyState
   */

  Html5.prototype.readyState = function readyState() {
    return this.el_.readyState;
  };

  /**
   * Get width of video
   *
   * @return {Number}
   * @method videoWidth
   */

  Html5.prototype.videoWidth = function videoWidth() {
    return this.el_.videoWidth;
  };

  /**
   * Get height of video
   *
   * @return {Number}
   * @method videoHeight
   */

  Html5.prototype.videoHeight = function videoHeight() {
    return this.el_.videoHeight;
  };

  /**
   * Get text tracks
   *
   * @return {TextTrackList}
   * @method textTracks
   */

  Html5.prototype.textTracks = function textTracks() {
    return _Tech.prototype.textTracks.call(this);
  };

  /**
   * Creates and returns a text track object
   *
   * @param {String} kind Text track kind (subtitles, captions, descriptions
   *                                       chapters and metadata)
   * @param {String=} label Label to identify the text track
   * @param {String=} language Two letter language abbreviation
   * @return {TextTrackObject}
   * @method addTextTrack
   */

  Html5.prototype.addTextTrack = function addTextTrack(kind, label, language) {
    if (!this['featuresNativeTextTracks']) {
      return _Tech.prototype.addTextTrack.call(this, kind, label, language);
    }

    return this.el_.addTextTrack(kind, label, language);
  };

  /**
   * Creates a remote text track object and returns a html track element
   *
   * @param {Object} options The object should contain values for
   * kind, language, label and src (location of the WebVTT file)
   * @return {HTMLTrackElement}
   * @method addRemoteTextTrack
   */

  Html5.prototype.addRemoteTextTrack = function addRemoteTextTrack() {
    var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];

    if (!this['featuresNativeTextTracks']) {
      return _Tech.prototype.addRemoteTextTrack.call(this, options);
    }

    var htmlTrackElement = _globalDocument2['default'].createElement('track');

    if (options.kind) {
      htmlTrackElement.kind = options.kind;
    }
    if (options.label) {
      htmlTrackElement.label = options.label;
    }
    if (options.language || options.srclang) {
      htmlTrackElement.srclang = options.language || options.srclang;
    }
    if (options['default']) {
      htmlTrackElement['default'] = options['default'];
    }
    if (options.id) {
      htmlTrackElement.id = options.id;
    }
    if (options.src) {
      htmlTrackElement.src = options.src;
    }

    this.el().appendChild(htmlTrackElement);

    // store HTMLTrackElement and TextTrack to remote list
    this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
    this.remoteTextTracks().addTrack_(htmlTrackElement.track);

    return htmlTrackElement;
  };

  /**
   * Remove remote text track from TextTrackList object
   *
   * @param {TextTrackObject} track Texttrack object to remove
   * @method removeRemoteTextTrack
   */

  Html5.prototype.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
    if (!this['featuresNativeTextTracks']) {
      return _Tech.prototype.removeRemoteTextTrack.call(this, track);
    }

    var tracks = undefined,
        i = undefined;

    var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track);

    // remove HTMLTrackElement and TextTrack from remote list
    this.remoteTextTrackEls().removeTrackElement_(trackElement);
    this.remoteTextTracks().removeTrack_(track);

    tracks = this.$$('track');

    i = tracks.length;
    while (i--) {
      if (track === tracks[i] || track === tracks[i].track) {
        this.el().removeChild(tracks[i]);
      }
    }
  };

  return Html5;
})(_techJs2['default']);

Html5.TEST_VID = _globalDocument2['default'].createElement('video');
var track = _globalDocument2['default'].createElement('track');
track.kind = 'captions';
track.srclang = 'en';
track.label = 'English';
Html5.TEST_VID.appendChild(track);

/*
 * Check if HTML5 video is supported by this browser/device
 *
 * @return {Boolean}
 */
Html5.isSupported = function () {
  // IE9 with no Media Player is a LIAR! (#984)
  try {
    Html5.TEST_VID['volume'] = 0.5;
  } catch (e) {
    return false;
  }

  return !!Html5.TEST_VID.canPlayType;
};

// Add Source Handler pattern functions to this tech
_techJs2['default'].withSourceHandlers(Html5);

/*
 * The default native source handler.
 * This simply passes the source to the video element. Nothing fancy.
 *
 * @param  {Object} source   The source object
 * @param  {Html5} tech  The instance of the HTML5 tech
 */
Html5.nativeSourceHandler = {};

/*
 * Check if the video element can play the given videotype
 *
 * @param  {String} type    The mimetype to check
 * @return {String}         'probably', 'maybe', or '' (empty string)
 */
Html5.nativeSourceHandler.canPlayType = function (type) {
  // IE9 on Windows 7 without MediaPlayer throws an error here
  // https://github.com/videojs/video.js/issues/519
  try {
    return Html5.TEST_VID.canPlayType(type);
  } catch (e) {
    return '';
  }
};

/*
 * Check if the video element can handle the source natively
 *
 * @param  {Object} source  The source object
 * @return {String}         'probably', 'maybe', or '' (empty string)
 */
Html5.nativeSourceHandler.canHandleSource = function (source) {
  var match, ext;

  // If a type was provided we should rely on that
  if (source.type) {
    return Html5.nativeSourceHandler.canPlayType(source.type);
  } else if (source.src) {
    // If no type, fall back to checking 'video/[EXTENSION]'
    ext = Url.getFileExtension(source.src);

    return Html5.nativeSourceHandler.canPlayType('video/' + ext);
  }

  return '';
};

/*
 * Pass the source to the video element
 * Adaptive source handlers will have more complicated workflows before passing
 * video data to the video element
 *
 * @param  {Object} source    The source object
 * @param  {Html5} tech   The instance of the Html5 tech
 */
Html5.nativeSourceHandler.handleSource = function (source, tech) {
  tech.setSrc(source.src);
};

/*
* Clean up the source handler when disposing the player or switching sources..
* (no cleanup is needed when supporting the format natively)
*/
Html5.nativeSourceHandler.dispose = function () {};

// Register the native source handler
Html5.registerSourceHandler(Html5.nativeSourceHandler);

/*
 * Check if the volume can be changed in this browser/device.
 * Volume cannot be changed in a lot of mobile devices.
 * Specifically, it can't be changed from 1 on iOS.
 *
 * @return {Boolean}
 */
Html5.canControlVolume = function () {
  var volume = Html5.TEST_VID.volume;
  Html5.TEST_VID.volume = volume / 2 + 0.1;
  return volume !== Html5.TEST_VID.volume;
};

/*
 * Check if playbackRate is supported in this browser/device.
 *
 * @return {Number} [description]
 */
Html5.canControlPlaybackRate = function () {
  var playbackRate = Html5.TEST_VID.playbackRate;
  Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1;
  return playbackRate !== Html5.TEST_VID.playbackRate;
};

/*
 * Check to see if native text tracks are supported by this browser/device
 *
 * @return {Boolean}
 */
Html5.supportsNativeTextTracks = function () {
  var supportsTextTracks;

  // Figure out native text track support
  // If mode is a number, we cannot change it because it'll disappear from view.
  // Browsers with numeric modes include IE10 and older (<=2013) samsung android models.
  // Firefox isn't playing nice either with modifying the mode
  // TODO: Investigate firefox: https://github.com/videojs/video.js/issues/1862
  supportsTextTracks = !!Html5.TEST_VID.textTracks;
  if (supportsTextTracks && Html5.TEST_VID.textTracks.length > 0) {
    supportsTextTracks = typeof Html5.TEST_VID.textTracks[0]['mode'] !== 'number';
  }
  if (supportsTextTracks && browser.IS_FIREFOX) {
    supportsTextTracks = false;
  }
  if (supportsTextTracks && !('onremovetrack' in Html5.TEST_VID.textTracks)) {
    supportsTextTracks = false;
  }

  return supportsTextTracks;
};

/**
 * An array of events available on the Html5 tech.
 *
 * @private
 * @type {Array}
 */
Html5.Events = ['loadstart', 'suspend', 'abort', 'error', 'emptied', 'stalled', 'loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough', 'playing', 'waiting', 'seeking', 'seeked', 'ended', 'durationchange', 'timeupdate', 'progress', 'play', 'pause', 'ratechange', 'volumechange'];

/*
 * Set the tech's volume control support status
 *
 * @type {Boolean}
 */
Html5.prototype['featuresVolumeControl'] = Html5.canControlVolume();

/*
 * Set the tech's playbackRate support status
 *
 * @type {Boolean}
 */
Html5.prototype['featuresPlaybackRate'] = Html5.canControlPlaybackRate();

/*
 * Set the tech's status on moving the video element.
 * In iOS, if you move a video element in the DOM, it breaks video playback.
 *
 * @type {Boolean}
 */
Html5.prototype['movingMediaElementInDOM'] = !browser.IS_IOS;

/*
 * Set the the tech's fullscreen resize support status.
 * HTML video is able to automatically resize when going to fullscreen.
 * (No longer appears to be used. Can probably be removed.)
 */
Html5.prototype['featuresFullscreenResize'] = true;

/*
 * Set the tech's progress event support status
 * (this disables the manual progress events of the Tech)
 */
Html5.prototype['featuresProgressEvents'] = true;

/*
 * Sets the tech's status on native text track support
 *
 * @type {Boolean}
 */
Html5.prototype['featuresNativeTextTracks'] = Html5.supportsNativeTextTracks();

// HTML5 Feature detection and Device Fixes --------------------------------- //
var canPlayType = undefined;
var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
var mp4RE = /^video\/mp4/i;

Html5.patchCanPlayType = function () {
  // Android 4.0 and above can play HLS to some extent but it reports being unable to do so
  if (browser.ANDROID_VERSION >= 4.0) {
    if (!canPlayType) {
      canPlayType = Html5.TEST_VID.constructor.prototype.canPlayType;
    }

    Html5.TEST_VID.constructor.prototype.canPlayType = function (type) {
      if (type && mpegurlRE.test(type)) {
        return 'maybe';
      }
      return canPlayType.call(this, type);
    };
  }

  // Override Android 2.2 and less canPlayType method which is broken
  if (browser.IS_OLD_ANDROID) {
    if (!canPlayType) {
      canPlayType = Html5.TEST_VID.constructor.prototype.canPlayType;
    }

    Html5.TEST_VID.constructor.prototype.canPlayType = function (type) {
      if (type && mp4RE.test(type)) {
        return 'maybe';
      }
      return canPlayType.call(this, type);
    };
  }
};

Html5.unpatchCanPlayType = function () {
  var r = Html5.TEST_VID.constructor.prototype.canPlayType;
  Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType;
  canPlayType = null;
  return r;
};

// by default, patch the video element
Html5.patchCanPlayType();

Html5.disposeMediaElement = function (el) {
  if (!el) {
    return;
  }

  if (el.parentNode) {
    el.parentNode.removeChild(el);
  }

  // remove any child track or source nodes to prevent their loading
  while (el.hasChildNodes()) {
    el.removeChild(el.firstChild);
  }

  // remove any src reference. not setting `src=''` because that causes a warning
  // in firefox
  el.removeAttribute('src');

  // force the media element to update its loading state by calling load()
  // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793)
  if (typeof el.load === 'function') {
    // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
    (function () {
      try {
        el.load();
      } catch (e) {
        // not supported
      }
    })();
  }
};

Html5.resetMediaElement = function (el) {
  if (!el) {
    return;
  }

  var sources = el.querySelectorAll('source');
  var i = sources.length;
  while (i--) {
    el.removeChild(sources[i]);
  }

  // remove any src reference.
  // not setting `src=''` because that throws an error
  el.removeAttribute('src');

  if (typeof el.load === 'function') {
    // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
    (function () {
      try {
        el.load();
      } catch (e) {}
    })();
  }
};

_component2['default'].registerComponent('Html5', Html5);
_techJs2['default'].registerTech('Html5', Html5);
exports['default'] = Html5;
module.exports = exports['default'];

},{"../component":65,"../utils/browser.js":125,"../utils/dom.js":128,"../utils/fn.js":130,"../utils/log.js":133,"../utils/merge-options.js":134,"../utils/url.js":138,"./tech.js":115,"global/document":1,"global/window":2,"object.assign":45}],114:[function(_dereq_,module,exports){
/**
 * @file loader.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _componentJs = _dereq_('../component.js');

var _componentJs2 = _interopRequireDefault(_componentJs);

var _techJs = _dereq_('./tech.js');

var _techJs2 = _interopRequireDefault(_techJs);

var _globalWindow = _dereq_('global/window');

var _globalWindow2 = _interopRequireDefault(_globalWindow);

var _utilsToTitleCaseJs = _dereq_('../utils/to-title-case.js');

var _utilsToTitleCaseJs2 = _interopRequireDefault(_utilsToTitleCaseJs);

/**
 * The Media Loader is the component that decides which playback technology to load
 * when the player is initialized.
 *
 * @param {Object} player  Main Player
 * @param {Object=} options Object of option names and values
 * @param {Function=} ready    Ready callback function
 * @extends Component
 * @class MediaLoader
 */

var MediaLoader = (function (_Component) {
  _inherits(MediaLoader, _Component);

  function MediaLoader(player, options, ready) {
    _classCallCheck(this, MediaLoader);

    _Component.call(this, player, options, ready);

    // If there are no sources when the player is initialized,
    // load the first supported playback technology.

    if (!options.playerOptions['sources'] || options.playerOptions['sources'].length === 0) {
      for (var i = 0, j = options.playerOptions['techOrder']; i < j.length; i++) {
        var techName = _utilsToTitleCaseJs2['default'](j[i]);
        var tech = _techJs2['default'].getTech(techName);
        // Support old behavior of techs being registered as components.
        // Remove once that deprecated behavior is removed.
        if (!techName) {
          tech = _componentJs2['default'].getComponent(techName);
        }

        // Check if the browser supports this technology
        if (tech && tech.isSupported()) {
          player.loadTech_(techName);
          break;
        }
      }
    } else {
      // // Loop through playback technologies (HTML5, Flash) and check for support.
      // // Then load the best source.
      // // A few assumptions here:
      // //   All playback technologies respect preload false.
      player.src(options.playerOptions['sources']);
    }
  }

  return MediaLoader;
})(_componentJs2['default']);

_componentJs2['default'].registerComponent('MediaLoader', MediaLoader);
exports['default'] = MediaLoader;
module.exports = exports['default'];

},{"../component.js":65,"../utils/to-title-case.js":137,"./tech.js":115,"global/window":2}],115:[function(_dereq_,module,exports){
/**
 * @file tech.js
 * Media Technology Controller - Base class for media playback
 * technology controllers like Flash and HTML5
 */

'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _component = _dereq_('../component');

var _component2 = _interopRequireDefault(_component);

var _tracksHtmlTrackElement = _dereq_('../tracks/html-track-element');

var _tracksHtmlTrackElement2 = _interopRequireDefault(_tracksHtmlTrackElement);

var _tracksHtmlTrackElementList = _dereq_('../tracks/html-track-element-list');

var _tracksHtmlTrackElementList2 = _interopRequireDefault(_tracksHtmlTrackElementList);

var _utilsMergeOptionsJs = _dereq_('../utils/merge-options.js');

var _utilsMergeOptionsJs2 = _interopRequireDefault(_utilsMergeOptionsJs);

var _tracksTextTrack = _dereq_('../tracks/text-track');

var _tracksTextTrack2 = _interopRequireDefault(_tracksTextTrack);

var _tracksTextTrackList = _dereq_('../tracks/text-track-list');

var _tracksTextTrackList2 = _interopRequireDefault(_tracksTextTrackList);

var _utilsFnJs = _dereq_('../utils/fn.js');

var Fn = _interopRequireWildcard(_utilsFnJs);

var _utilsLogJs = _dereq_('../utils/log.js');

var _utilsLogJs2 = _interopRequireDefault(_utilsLogJs);

var _utilsTimeRangesJs = _dereq_('../utils/time-ranges.js');

var _utilsBufferJs = _dereq_('../utils/buffer.js');

var _mediaErrorJs = _dereq_('../media-error.js');

var _mediaErrorJs2 = _interopRequireDefault(_mediaErrorJs);

var _globalWindow = _dereq_('global/window');

var _globalWindow2 = _interopRequireDefault(_globalWindow);

var _globalDocument = _dereq_('global/document');

var _globalDocument2 = _interopRequireDefault(_globalDocument);

/**
 * Base class for media (HTML5 Video, Flash) controllers
 *
 * @param {Object=} options Options object
 * @param {Function=} ready Ready callback function
 * @extends Component
 * @class Tech
 */

var Tech = (function (_Component) {
  _inherits(Tech, _Component);

  function Tech() {
    var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
    var ready = arguments.length <= 1 || arguments[1] === undefined ? function () {} : arguments[1];

    _classCallCheck(this, Tech);

    // we don't want the tech to report user activity automatically.
    // This is done manually in addControlsListeners
    options.reportTouchActivity = false;
    _Component.call(this, null, options, ready);

    // keep track of whether the current source has played at all to
    // implement a very limited played()
    this.hasStarted_ = false;
    this.on('playing', function () {
      this.hasStarted_ = true;
    });
    this.on('loadstart', function () {
      this.hasStarted_ = false;
    });

    this.textTracks_ = options.textTracks;

    // Manually track progress in cases where the browser/flash player doesn't report it.
    if (!this.featuresProgressEvents) {
      this.manualProgressOn();
    }

    // Manually track timeupdates in cases where the browser/flash player doesn't report it.
    if (!this.featuresTimeupdateEvents) {
      this.manualTimeUpdatesOn();
    }

    if (options.nativeCaptions === false || options.nativeTextTracks === false) {
      this.featuresNativeTextTracks = false;
    }

    if (!this.featuresNativeTextTracks) {
      this.on('ready', this.emulateTextTracks);
    }

    this.initTextTrackListeners();

    // Turn on component tap events
    this.emitTapEvents();
  }

  /*
   * List of associated text tracks
   *
   * @type {Array}
   * @private
   */

  /* Fallbacks for unsupported event types
  ================================================================================ */
  // Manually trigger progress events based on changes to the buffered amount
  // Many flash players and older HTML5 browsers don't send progress or progress-like events
  /**
   * Turn on progress events
   *
   * @method manualProgressOn
   */

  Tech.prototype.manualProgressOn = function manualProgressOn() {
    this.on('durationchange', this.onDurationChange);

    this.manualProgress = true;

    // Trigger progress watching when a source begins loading
    this.one('ready', this.trackProgress);
  };

  /**
   * Turn off progress events
   *
   * @method manualProgressOff
   */

  Tech.prototype.manualProgressOff = function manualProgressOff() {
    this.manualProgress = false;
    this.stopTrackingProgress();

    this.off('durationchange', this.onDurationChange);
  };

  /**
   * Track progress
   *
   * @method trackProgress
   */

  Tech.prototype.trackProgress = function trackProgress() {
    this.stopTrackingProgress();
    this.progressInterval = this.setInterval(Fn.bind(this, function () {
      // Don't trigger unless buffered amount is greater than last time

      var numBufferedPercent = this.bufferedPercent();

      if (this.bufferedPercent_ !== numBufferedPercent) {
        this.trigger('progress');
      }

      this.bufferedPercent_ = numBufferedPercent;

      if (numBufferedPercent === 1) {
        this.stopTrackingProgress();
      }
    }), 500);
  };

  /**
   * Update duration
   *
   * @method onDurationChange
   */

  Tech.prototype.onDurationChange = function onDurationChange() {
    this.duration_ = this.duration();
  };

  /**
   * Create and get TimeRange object for buffering
   *
   * @return {TimeRangeObject}
   * @method buffered
   */

  Tech.prototype.buffered = function buffered() {
    return _utilsTimeRangesJs.createTimeRange(0, 0);
  };

  /**
   * Get buffered percent
   *
   * @return {Number}
   * @method bufferedPercent
   */

  Tech.prototype.bufferedPercent = function bufferedPercent() {
    return _utilsBufferJs.bufferedPercent(this.buffered(), this.duration_);
  };

  /**
   * Stops tracking progress by clearing progress interval
   *
   * @method stopTrackingProgress
   */

  Tech.prototype.stopTrackingProgress = function stopTrackingProgress() {
    this.clearInterval(this.progressInterval);
  };

  /*! Time Tracking -------------------------------------------------------------- */
  /**
   * Set event listeners for on play and pause and tracking current time
   *
   * @method manualTimeUpdatesOn
   */

  Tech.prototype.manualTimeUpdatesOn = function manualTimeUpdatesOn() {
    this.manualTimeUpdates = true;

    this.on('play', this.trackCurrentTime);
    this.on('pause', this.stopTrackingCurrentTime);
  };

  /**
   * Remove event listeners for on play and pause and tracking current time
   *
   * @method manualTimeUpdatesOff
   */

  Tech.prototype.manualTimeUpdatesOff = function manualTimeUpdatesOff() {
    this.manualTimeUpdates = false;
    this.stopTrackingCurrentTime();
    this.off('play', this.trackCurrentTime);
    this.off('pause', this.stopTrackingCurrentTime);
  };

  /**
   * Tracks current time
   *
   * @method trackCurrentTime
   */

  Tech.prototype.trackCurrentTime = function trackCurrentTime() {
    if (this.currentTimeInterval) {
      this.stopTrackingCurrentTime();
    }
    this.currentTimeInterval = this.setInterval(function () {
      this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
    }, 250); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
  };

  /**
   * Turn off play progress tracking (when paused or dragging)
   *
   * @method stopTrackingCurrentTime
   */

  Tech.prototype.stopTrackingCurrentTime = function stopTrackingCurrentTime() {
    this.clearInterval(this.currentTimeInterval);

    // #1002 - if the video ends right before the next timeupdate would happen,
    // the progress bar won't make it all the way to the end
    this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
  };

  /**
   * Turn off any manual progress or timeupdate tracking
   *
   * @method dispose
   */

  Tech.prototype.dispose = function dispose() {
    // clear out text tracks because we can't reuse them between techs
    var textTracks = this.textTracks();

    if (textTracks) {
      var i = textTracks.length;
      while (i--) {
        this.removeRemoteTextTrack(textTracks[i]);
      }
    }

    // Turn off any manual progress or timeupdate tracking
    if (this.manualProgress) {
      this.manualProgressOff();
    }

    if (this.manualTimeUpdates) {
      this.manualTimeUpdatesOff();
    }

    _Component.prototype.dispose.call(this);
  };

  /**
   * Reset the tech. Removes all sources and resets readyState.
   *
   * @method reset
   */

  Tech.prototype.reset = function reset() {};

  /**
   * When invoked without an argument, returns a MediaError object
   * representing the current error state of the player or null if
   * there is no error. When invoked with an argument, set the current
   * error state of the player.
   * @param {MediaError=} err    Optional an error object
   * @return {MediaError}        the current error object or null
   * @method error
   */

  Tech.prototype.error = function error(err) {
    if (err !== undefined) {
      if (err instanceof _mediaErrorJs2['default']) {
        this.error_ = err;
      } else {
        this.error_ = new _mediaErrorJs2['default'](err);
      }
      this.trigger('error');
    }
    return this.error_;
  };

  /**
   * Return the time ranges that have been played through for the
   * current source. This implementation is incomplete. It does not
   * track the played time ranges, only whether the source has played
   * at all or not.
   * @return {TimeRangeObject} a single time range if this video has
   * played or an empty set of ranges if not.
   * @method played
   */

  Tech.prototype.played = function played() {
    if (this.hasStarted_) {
      return _utilsTimeRangesJs.createTimeRange(0, 0);
    }
    return _utilsTimeRangesJs.createTimeRange();
  };

  /**
   * Set current time
   *
   * @method setCurrentTime
   */

  Tech.prototype.setCurrentTime = function setCurrentTime() {
    // improve the accuracy of manual timeupdates
    if (this.manualTimeUpdates) {
      this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
    }
  };

  /**
   * Initialize texttrack listeners
   *
   * @method initTextTrackListeners
   */

  Tech.prototype.initTextTrackListeners = function initTextTrackListeners() {
    var textTrackListChanges = Fn.bind(this, function () {
      this.trigger('texttrackchange');
    });

    var tracks = this.textTracks();

    if (!tracks) return;

    tracks.addEventListener('removetrack', textTrackListChanges);
    tracks.addEventListener('addtrack', textTrackListChanges);

    this.on('dispose', Fn.bind(this, function () {
      tracks.removeEventListener('removetrack', textTrackListChanges);
      tracks.removeEventListener('addtrack', textTrackListChanges);
    }));
  };

  /**
   * Emulate texttracks
   *
   * @method emulateTextTracks
   */

  Tech.prototype.emulateTextTracks = function emulateTextTracks() {
    var _this = this;

    var tracks = this.textTracks();
    if (!tracks) {
      return;
    }

    if (!_globalWindow2['default']['WebVTT'] && this.el().parentNode != null) {
      var script = _globalDocument2['default'].createElement('script');
      script.src = this.options_['vtt.js'] || 'https://cdn.rawgit.com/gkatsev/vtt.js/vjs-v0.12.1/dist/vtt.min.js';
      this.el().parentNode.appendChild(script);
      _globalWindow2['default']['WebVTT'] = true;
    }

    var updateDisplay = function updateDisplay() {
      return _this.trigger('texttrackchange');
    };
    var textTracksChanges = function textTracksChanges() {
      updateDisplay();

      for (var i = 0; i < tracks.length; i++) {
        var track = tracks[i];
        track.removeEventListener('cuechange', updateDisplay);
        if (track.mode === 'showing') {
          track.addEventListener('cuechange', updateDisplay);
        }
      }
    };

    textTracksChanges();
    tracks.addEventListener('change', textTracksChanges);

    this.on('dispose', function () {
      tracks.removeEventListener('change', textTracksChanges);
    });
  };

  /*
   * Provide default methods for text tracks.
   *
   * Html5 tech overrides these.
   */

  /**
   * Get texttracks
   *
   * @returns {TextTrackList}
   * @method textTracks
   */

  Tech.prototype.textTracks = function textTracks() {
    this.textTracks_ = this.textTracks_ || new _tracksTextTrackList2['default']();
    return this.textTracks_;
  };

  /**
   * Get remote texttracks
   *
   * @returns {TextTrackList}
   * @method remoteTextTracks
   */

  Tech.prototype.remoteTextTracks = function remoteTextTracks() {
    this.remoteTextTracks_ = this.remoteTextTracks_ || new _tracksTextTrackList2['default']();
    return this.remoteTextTracks_;
  };

  /**
   * Get remote htmltrackelements
   *
   * @returns {HTMLTrackElementList}
   * @method remoteTextTrackEls
   */

  Tech.prototype.remoteTextTrackEls = function remoteTextTrackEls() {
    this.remoteTextTrackEls_ = this.remoteTextTrackEls_ || new _tracksHtmlTrackElementList2['default']();
    return this.remoteTextTrackEls_;
  };

  /**
   * Creates and returns a remote text track object
   *
   * @param {String} kind Text track kind (subtitles, captions, descriptions
   *                                       chapters and metadata)
   * @param {String=} label Label to identify the text track
   * @param {String=} language Two letter language abbreviation
   * @return {TextTrackObject}
   * @method addTextTrack
   */

  Tech.prototype.addTextTrack = function addTextTrack(kind, label, language) {
    if (!kind) {
      throw new Error('TextTrack kind is required but was not provided');
    }

    return createTrackHelper(this, kind, label, language);
  };

  /**
   * Creates a remote text track object and returns a emulated html track element
   *
   * @param {Object} options The object should contain values for
   * kind, language, label and src (location of the WebVTT file)
   * @return {HTMLTrackElement}
   * @method addRemoteTextTrack
   */

  Tech.prototype.addRemoteTextTrack = function addRemoteTextTrack(options) {
    var track = _utilsMergeOptionsJs2['default'](options, {
      tech: this
    });

    var htmlTrackElement = new _tracksHtmlTrackElement2['default'](track);

    // store HTMLTrackElement and TextTrack to remote list
    this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
    this.remoteTextTracks().addTrack_(htmlTrackElement.track);

    // must come after remoteTextTracks()
    this.textTracks().addTrack_(htmlTrackElement.track);

    return htmlTrackElement;
  };

  /**
   * Remove remote texttrack
   *
   * @param {TextTrackObject} track Texttrack to remove
   * @method removeRemoteTextTrack
   */

  Tech.prototype.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
    this.textTracks().removeTrack_(track);

    var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track);

    // remove HTMLTrackElement and TextTrack from remote list
    this.remoteTextTrackEls().removeTrackElement_(trackElement);
    this.remoteTextTracks().removeTrack_(track);
  };

  /**
   * Provide a default setPoster method for techs
   * Poster support for techs should be optional, so we don't want techs to
   * break if they don't have a way to set a poster.
   *
   * @method setPoster
   */

  Tech.prototype.setPoster = function setPoster() {};

  /*
   * Check if the tech can support the given type
   *
   * The base tech does not support any type, but source handlers might
   * overwrite this.
   *
   * @param  {String} type    The mimetype to check
   * @return {String}         'probably', 'maybe', or '' (empty string)
   */

  Tech.prototype.canPlayType = function canPlayType() {
    return '';
  };

  /*
   * Return whether the argument is a Tech or not.
   * Can be passed either a Class like `Html5` or a instance like `player.tech_`
   *
   * @param {Object} component An item to check
   * @return {Boolean}         Whether it is a tech or not
   */

  Tech.isTech = function isTech(component) {
    return component.prototype instanceof Tech || component instanceof Tech || component === Tech;
  };

  /**
   * Registers a Tech
   *
   * @param {String} name Name of the Tech to register
   * @param {Object} tech The tech to register
   * @static
   * @method registerComponent
   */

  Tech.registerTech = function registerTech(name, tech) {
    if (!Tech.techs_) {
      Tech.techs_ = {};
    }

    if (!Tech.isTech(tech)) {
      throw new Error('Tech ' + name + ' must be a Tech');
    }

    Tech.techs_[name] = tech;
    return tech;
  };

  /**
   * Gets a component by name
   *
   * @param {String} name Name of the component to get
   * @return {Component}
   * @static
   * @method getComponent
   */

  Tech.getTech = function getTech(name) {
    if (Tech.techs_ && Tech.techs_[name]) {
      return Tech.techs_[name];
    }

    if (_globalWindow2['default'] && _globalWindow2['default'].videojs && _globalWindow2['default'].videojs[name]) {
      _utilsLogJs2['default'].warn('The ' + name + ' tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)');
      return _globalWindow2['default'].videojs[name];
    }
  };

  return Tech;
})(_component2['default']);

Tech.prototype.textTracks_;

var createTrackHelper = function createTrackHelper(self, kind, label, language) {
  var options = arguments.length <= 4 || arguments[4] === undefined ? {} : arguments[4];

  var tracks = self.textTracks();

  options.kind = kind;

  if (label) {
    options.label = label;
  }
  if (language) {
    options.language = language;
  }
  options.tech = self;

  var track = new _tracksTextTrack2['default'](options);
  tracks.addTrack_(track);

  return track;
};

Tech.prototype.featuresVolumeControl = true;

// Resizing plugins using request fullscreen reloads the plugin
Tech.prototype.featuresFullscreenResize = false;
Tech.prototype.featuresPlaybackRate = false;

// Optional events that we can manually mimic with timers
// currently not triggered by video-js-swf
Tech.prototype.featuresProgressEvents = false;
Tech.prototype.featuresTimeupdateEvents = false;

Tech.prototype.featuresNativeTextTracks = false;

/*
 * A functional mixin for techs that want to use the Source Handler pattern.
 *
 * ##### EXAMPLE:
 *
 *   Tech.withSourceHandlers.call(MyTech);
 *
 */
Tech.withSourceHandlers = function (_Tech) {
  /*
   * Register a source handler
   * Source handlers are scripts for handling specific formats.
   * The source handler pattern is used for adaptive formats (HLS, DASH) that
   * manually load video data and feed it into a Source Buffer (Media Source Extensions)
   * @param  {Function} handler  The source handler
   * @param  {Boolean}  first    Register it before any existing handlers
   */
  _Tech.registerSourceHandler = function (handler, index) {
    var handlers = _Tech.sourceHandlers;

    if (!handlers) {
      handlers = _Tech.sourceHandlers = [];
    }

    if (index === undefined) {
      // add to the end of the list
      index = handlers.length;
    }

    handlers.splice(index, 0, handler);
  };

  /*
   * Check if the tech can support the given type
   * @param  {String} type    The mimetype to check
   * @return {String}         'probably', 'maybe', or '' (empty string)
   */
  _Tech.canPlayType = function (type) {
    var handlers = _Tech.sourceHandlers || [];
    var can = undefined;

    for (var i = 0; i < handlers.length; i++) {
      can = handlers[i].canPlayType(type);

      if (can) {
        return can;
      }
    }

    return '';
  };

  /*
   * Return the first source handler that supports the source
   * TODO: Answer question: should 'probably' be prioritized over 'maybe'
   * @param  {Object} source The source object
   * @returns {Object}       The first source handler that supports the source
   * @returns {null}         Null if no source handler is found
   */
  _Tech.selectSourceHandler = function (source) {
    var handlers = _Tech.sourceHandlers || [];
    var can = undefined;

    for (var i = 0; i < handlers.length; i++) {
      can = handlers[i].canHandleSource(source);

      if (can) {
        return handlers[i];
      }
    }

    return null;
  };

  /*
   * Check if the tech can support the given source
   * @param  {Object} srcObj  The source object
   * @return {String}         'probably', 'maybe', or '' (empty string)
   */
  _Tech.canPlaySource = function (srcObj) {
    var sh = _Tech.selectSourceHandler(srcObj);

    if (sh) {
      return sh.canHandleSource(srcObj);
    }

    return '';
  };

  /*
   * When using a source handler, prefer its implementation of
   * any function normally provided by the tech.
   */
  var deferrable = ['seekable', 'duration'];

  deferrable.forEach(function (fnName) {
    var originalFn = this[fnName];

    if (typeof originalFn !== 'function') {
      return;
    }

    this[fnName] = function () {
      if (this.sourceHandler_ && this.sourceHandler_[fnName]) {
        return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments);
      }
      return originalFn.apply(this, arguments);
    };
  }, _Tech.prototype);

  /*
   * Create a function for setting the source using a source object
   * and source handlers.
   * Should never be called unless a source handler was found.
   * @param {Object} source  A source object with src and type keys
   * @return {Tech} self
   */
  _Tech.prototype.setSource = function (source) {
    var sh = _Tech.selectSourceHandler(source);

    if (!sh) {
      // Fall back to a native source hander when unsupported sources are
      // deliberately set
      if (_Tech.nativeSourceHandler) {
        sh = _Tech.nativeSourceHandler;
      } else {
        _utilsLogJs2['default'].error('No source hander found for the current source.');
      }
    }

    // Dispose any existing source handler
    this.disposeSourceHandler();
    this.off('dispose', this.disposeSourceHandler);

    this.currentSource_ = source;
    this.sourceHandler_ = sh.handleSource(source, this);
    this.on('dispose', this.disposeSourceHandler);

    return this;
  };

  /*
   * Clean up any existing source handler
   */
  _Tech.prototype.disposeSourceHandler = function () {
    if (this.sourceHandler_ && this.sourceHandler_.dispose) {
      this.sourceHandler_.dispose();
    }
  };
};

_component2['default'].registerComponent('Tech', Tech);
// Old name for Tech
_component2['default'].registerComponent('MediaTechController', Tech);
Tech.registerTech('Tech', Tech);
exports['default'] = Tech;
module.exports = exports['default'];

},{"../component":65,"../media-error.js":101,"../tracks/html-track-element":117,"../tracks/html-track-element-list":116,"../tracks/text-track":124,"../tracks/text-track-list":122,"../utils/buffer.js":126,"../utils/fn.js":130,"../utils/log.js":133,"../utils/merge-options.js":134,"../utils/time-ranges.js":136,"global/document":1,"global/window":2}],116:[function(_dereq_,module,exports){
/**
 * @file html-track-element-list.js
 */

'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

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

var _utilsBrowserJs = _dereq_('../utils/browser.js');

var browser = _interopRequireWildcard(_utilsBrowserJs);

var _globalDocument = _dereq_('global/document');

var _globalDocument2 = _interopRequireDefault(_globalDocument);

var HtmlTrackElementList = (function () {
  function HtmlTrackElementList() {
    var trackElements = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0];

    _classCallCheck(this, HtmlTrackElementList);

    var list = this;

    if (browser.IS_IE8) {
      list = _globalDocument2['default'].createElement('custom');

      for (var prop in HtmlTrackElementList.prototype) {
        if (prop !== 'constructor') {
          list[prop] = HtmlTrackElementList.prototype[prop];
        }
      }
    }

    list.trackElements_ = [];

    Object.defineProperty(list, 'length', {
      get: function get() {
        return this.trackElements_.length;
      }
    });

    for (var i = 0, _length = trackElements.length; i < _length; i++) {
      list.addTrackElement_(trackElements[i]);
    }

    if (browser.IS_IE8) {
      return list;
    }
  }

  HtmlTrackElementList.prototype.addTrackElement_ = function addTrackElement_(trackElement) {
    this.trackElements_.push(trackElement);
  };

  HtmlTrackElementList.prototype.getTrackElementByTrack_ = function getTrackElementByTrack_(track) {
    var trackElement_ = undefined;

    for (var i = 0, _length2 = this.trackElements_.length; i < _length2; i++) {
      if (track === this.trackElements_[i].track) {
        trackElement_ = this.trackElements_[i];

        break;
      }
    }

    return trackElement_;
  };

  HtmlTrackElementList.prototype.removeTrackElement_ = function removeTrackElement_(trackElement) {
    for (var i = 0, _length3 = this.trackElements_.length; i < _length3; i++) {
      if (trackElement === this.trackElements_[i]) {
        this.trackElements_.splice(i, 1);

        break;
      }
    }
  };

  return HtmlTrackElementList;
})();

exports['default'] = HtmlTrackElementList;
module.exports = exports['default'];

},{"../utils/browser.js":125,"global/document":1}],117:[function(_dereq_,module,exports){
/**
 * @file html-track-element.js
 */

'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _utilsBrowserJs = _dereq_('../utils/browser.js');

var browser = _interopRequireWildcard(_utilsBrowserJs);

var _globalDocument = _dereq_('global/document');

var _globalDocument2 = _interopRequireDefault(_globalDocument);

var _eventTarget = _dereq_('../event-target');

var _eventTarget2 = _interopRequireDefault(_eventTarget);

var _tracksTextTrack = _dereq_('../tracks/text-track');

var _tracksTextTrack2 = _interopRequireDefault(_tracksTextTrack);

var NONE = 0;
var LOADING = 1;
var LOADED = 2;
var ERROR = 3;

/**
 * https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement
 *
 * interface HTMLTrackElement : HTMLElement {
 *   attribute DOMString kind;
 *   attribute DOMString src;
 *   attribute DOMString srclang;
 *   attribute DOMString label;
 *   attribute boolean default;
 *
 *   const unsigned short NONE = 0;
 *   const unsigned short LOADING = 1;
 *   const unsigned short LOADED = 2;
 *   const unsigned short ERROR = 3;
 *   readonly attribute unsigned short readyState;
 *
 *   readonly attribute TextTrack track;
 * };
 *
 * @param {Object} options TextTrack configuration
 * @class HTMLTrackElement
 */

var HTMLTrackElement = (function (_EventTarget) {
  _inherits(HTMLTrackElement, _EventTarget);

  function HTMLTrackElement() {
    var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];

    _classCallCheck(this, HTMLTrackElement);

    _EventTarget.call(this);

    var readyState = undefined,
        trackElement = this;

    if (browser.IS_IE8) {
      trackElement = _globalDocument2['default'].createElement('custom');

      for (var prop in HTMLTrackElement.prototype) {
        if (prop !== 'constructor') {
          trackElement[prop] = HTMLTrackElement.prototype[prop];
        }
      }
    }

    var track = new _tracksTextTrack2['default'](options);

    trackElement.kind = track.kind;
    trackElement.src = track.src;
    trackElement.srclang = track.language;
    trackElement.label = track.label;
    trackElement['default'] = track['default'];

    Object.defineProperty(trackElement, 'readyState', {
      get: function get() {
        return readyState;
      }
    });

    Object.defineProperty(trackElement, 'track', {
      get: function get() {
        return track;
      }
    });

    readyState = NONE;

    track.addEventListener('loadeddata', function () {
      readyState = LOADED;

      trackElement.trigger({
        type: 'load',
        target: trackElement
      });
    });

    if (browser.IS_IE8) {
      return trackElement;
    }
  }

  return HTMLTrackElement;
})(_eventTarget2['default']);

HTMLTrackElement.prototype.allowedEvents_ = {
  load: 'load'
};

HTMLTrackElement.NONE = NONE;
HTMLTrackElement.LOADING = LOADING;
HTMLTrackElement.LOADED = LOADED;
HTMLTrackElement.ERROR = ERROR;

exports['default'] = HTMLTrackElement;
module.exports = exports['default'];

},{"../event-target":97,"../tracks/text-track":124,"../utils/browser.js":125,"global/document":1}],118:[function(_dereq_,module,exports){
/**
 * @file text-track-cue-list.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

var _utilsBrowserJs = _dereq_('../utils/browser.js');

var browser = _interopRequireWildcard(_utilsBrowserJs);

var _globalDocument = _dereq_('global/document');

var _globalDocument2 = _interopRequireDefault(_globalDocument);

/*
 * https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist
 *
 * interface TextTrackCueList {
 *   readonly attribute unsigned long length;
 *   getter TextTrackCue (unsigned long index);
 *   TextTrackCue? getCueById(DOMString id);
 * };
 */

var TextTrackCueList = function TextTrackCueList(cues) {
  var list = this;

  if (browser.IS_IE8) {
    list = _globalDocument2['default'].createElement('custom');

    for (var prop in TextTrackCueList.prototype) {
      if (prop !== 'constructor') {
        list[prop] = TextTrackCueList.prototype[prop];
      }
    }
  }

  TextTrackCueList.prototype.setCues_.call(list, cues);

  Object.defineProperty(list, 'length', {
    get: function get() {
      return this.length_;
    }
  });

  if (browser.IS_IE8) {
    return list;
  }
};

TextTrackCueList.prototype.setCues_ = function (cues) {
  var oldLength = this.length || 0;
  var i = 0;
  var l = cues.length;

  this.cues_ = cues;
  this.length_ = cues.length;

  var defineProp = function defineProp(i) {
    if (!('' + i in this)) {
      Object.defineProperty(this, '' + i, {
        get: function get() {
          return this.cues_[i];
        }
      });
    }
  };

  if (oldLength < l) {
    i = oldLength;

    for (; i < l; i++) {
      defineProp.call(this, i);
    }
  }
};

TextTrackCueList.prototype.getCueById = function (id) {
  var result = null;
  for (var i = 0, l = this.length; i < l; i++) {
    var cue = this[i];
    if (cue.id === id) {
      result = cue;
      break;
    }
  }

  return result;
};

exports['default'] = TextTrackCueList;
module.exports = exports['default'];

},{"../utils/browser.js":125,"global/document":1}],119:[function(_dereq_,module,exports){
/**
 * @file text-track-display.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _component = _dereq_('../component');

var _component2 = _interopRequireDefault(_component);

var _menuMenuJs = _dereq_('../menu/menu.js');

var _menuMenuJs2 = _interopRequireDefault(_menuMenuJs);

var _menuMenuItemJs = _dereq_('../menu/menu-item.js');

var _menuMenuItemJs2 = _interopRequireDefault(_menuMenuItemJs);

var _menuMenuButtonJs = _dereq_('../menu/menu-button.js');

var _menuMenuButtonJs2 = _interopRequireDefault(_menuMenuButtonJs);

var _utilsFnJs = _dereq_('../utils/fn.js');

var Fn = _interopRequireWildcard(_utilsFnJs);

var _globalDocument = _dereq_('global/document');

var _globalDocument2 = _interopRequireDefault(_globalDocument);

var _globalWindow = _dereq_('global/window');

var _globalWindow2 = _interopRequireDefault(_globalWindow);

var darkGray = '#222';
var lightGray = '#ccc';
var fontMap = {
  monospace: 'monospace',
  sansSerif: 'sans-serif',
  serif: 'serif',
  monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace',
  monospaceSerif: '"Courier New", monospace',
  proportionalSansSerif: 'sans-serif',
  proportionalSerif: 'serif',
  casual: '"Comic Sans MS", Impact, fantasy',
  script: '"Monotype Corsiva", cursive',
  smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif'
};

/**
 * The component for displaying text track cues
 *
 * @param {Object} player  Main Player
 * @param {Object=} options Object of option names and values
 * @param {Function=} ready    Ready callback function
 * @extends Component
 * @class TextTrackDisplay
 */

var TextTrackDisplay = (function (_Component) {
  _inherits(TextTrackDisplay, _Component);

  function TextTrackDisplay(player, options, ready) {
    _classCallCheck(this, TextTrackDisplay);

    _Component.call(this, player, options, ready);

    player.on('loadstart', Fn.bind(this, this.toggleDisplay));
    player.on('texttrackchange', Fn.bind(this, this.updateDisplay));

    // This used to be called during player init, but was causing an error
    // if a track should show by default and the display hadn't loaded yet.
    // Should probably be moved to an external track loader when we support
    // tracks that don't need a display.
    player.ready(Fn.bind(this, function () {
      if (player.tech_ && player.tech_['featuresNativeTextTracks']) {
        this.hide();
        return;
      }

      player.on('fullscreenchange', Fn.bind(this, this.updateDisplay));

      var tracks = this.options_.playerOptions['tracks'] || [];
      for (var i = 0; i < tracks.length; i++) {
        var track = tracks[i];
        this.player_.addRemoteTextTrack(track);
      }
    }));
  }

  /**
  * Add cue HTML to display
  *
  * @param {Number} color Hex number for color, like #f0e
  * @param {Number} opacity Value for opacity,0.0 - 1.0
  * @return {RGBAColor} In the form 'rgba(255, 0, 0, 0.3)'
  * @method constructColor
  */

  /**
   * Toggle display texttracks
   *
   * @method toggleDisplay
   */

  TextTrackDisplay.prototype.toggleDisplay = function toggleDisplay() {
    if (this.player_.tech_ && this.player_.tech_['featuresNativeTextTracks']) {
      this.hide();
    } else {
      this.show();
    }
  };

  /**
   * Create the component's DOM element
   *
   * @return {Element}
   * @method createEl
   */

  TextTrackDisplay.prototype.createEl = function createEl() {
    return _Component.prototype.createEl.call(this, 'div', {
      className: 'vjs-text-track-display'
    });
  };

  /**
   * Clear display texttracks
   *
   * @method clearDisplay
   */

  TextTrackDisplay.prototype.clearDisplay = function clearDisplay() {
    if (typeof _globalWindow2['default']['WebVTT'] === 'function') {
      _globalWindow2['default']['WebVTT']['processCues'](_globalWindow2['default'], [], this.el_);
    }
  };

  /**
   * Update display texttracks
   *
   * @method updateDisplay
   */

  TextTrackDisplay.prototype.updateDisplay = function updateDisplay() {
    var tracks = this.player_.textTracks();

    this.clearDisplay();

    if (!tracks) {
      return;
    }

    for (var i = 0; i < tracks.length; i++) {
      var track = tracks[i];
      if (track['mode'] === 'showing') {
        this.updateForTrack(track);
      }
    }
  };

  /**
   * Add texttrack to texttrack list
   *
   * @param {TextTrackObject} track Texttrack object to be added to list
   * @method updateForTrack
   */

  TextTrackDisplay.prototype.updateForTrack = function updateForTrack(track) {
    if (typeof _globalWindow2['default']['WebVTT'] !== 'function' || !track['activeCues']) {
      return;
    }

    var overrides = this.player_['textTrackSettings'].getValues();

    var cues = [];
    for (var _i = 0; _i < track['activeCues'].length; _i++) {
      cues.push(track['activeCues'][_i]);
    }

    _globalWindow2['default']['WebVTT']['processCues'](_globalWindow2['default'], track['activeCues'], this.el_);

    var i = cues.length;
    while (i--) {
      var cue = cues[i];
      if (!cue) {
        continue;
      }

      var cueDiv = cue.displayState;
      if (overrides.color) {
        cueDiv.firstChild.style.color = overrides.color;
      }
      if (overrides.textOpacity) {
        tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity));
      }
      if (overrides.backgroundColor) {
        cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor;
      }
      if (overrides.backgroundOpacity) {
        tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity));
      }
      if (overrides.windowColor) {
        if (overrides.windowOpacity) {
          tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity));
        } else {
          cueDiv.style.backgroundColor = overrides.windowColor;
        }
      }
      if (overrides.edgeStyle) {
        if (overrides.edgeStyle === 'dropshadow') {
          cueDiv.firstChild.style.textShadow = '2px 2px 3px ' + darkGray + ', 2px 2px 4px ' + darkGray + ', 2px 2px 5px ' + darkGray;
        } else if (overrides.edgeStyle === 'raised') {
          cueDiv.firstChild.style.textShadow = '1px 1px ' + darkGray + ', 2px 2px ' + darkGray + ', 3px 3px ' + darkGray;
        } else if (overrides.edgeStyle === 'depressed') {
          cueDiv.firstChild.style.textShadow = '1px 1px ' + lightGray + ', 0 1px ' + lightGray + ', -1px -1px ' + darkGray + ', 0 -1px ' + darkGray;
        } else if (overrides.edgeStyle === 'uniform') {
          cueDiv.firstChild.style.textShadow = '0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray;
        }
      }
      if (overrides.fontPercent && overrides.fontPercent !== 1) {
        var fontSize = _globalWindow2['default'].parseFloat(cueDiv.style.fontSize);
        cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px';
        cueDiv.style.height = 'auto';
        cueDiv.style.top = 'auto';
        cueDiv.style.bottom = '2px';
      }
      if (overrides.fontFamily && overrides.fontFamily !== 'default') {
        if (overrides.fontFamily === 'small-caps') {
          cueDiv.firstChild.style.fontVariant = 'small-caps';
        } else {
          cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily];
        }
      }
    }
  };

  return TextTrackDisplay;
})(_component2['default']);

function constructColor(color, opacity) {
  return 'rgba(' +
  // color looks like "#f0e"
  parseInt(color[1] + color[1], 16) + ',' + parseInt(color[2] + color[2], 16) + ',' + parseInt(color[3] + color[3], 16) + ',' + opacity + ')';
}

/**
 * Try to update style
 * Some style changes will throw an error, particularly in IE8. Those should be noops.
 *
 * @param {Element} el The element to be styles
 * @param {CSSProperty} style The CSS property to be styled
 * @param {CSSStyle} rule The actual style to be applied to the property
 * @method tryUpdateStyle
 */
function tryUpdateStyle(el, style, rule) {
  //
  try {
    el.style[style] = rule;
  } catch (e) {}
}

_component2['default'].registerComponent('TextTrackDisplay', TextTrackDisplay);
exports['default'] = TextTrackDisplay;
module.exports = exports['default'];

},{"../component":65,"../menu/menu-button.js":102,"../menu/menu-item.js":103,"../menu/menu.js":104,"../utils/fn.js":130,"global/document":1,"global/window":2}],120:[function(_dereq_,module,exports){
/**
 * @file text-track-enums.js
 *
 * https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
 *
 * enum TextTrackMode { "disabled",  "hidden",  "showing" };
 */
'use strict';

exports.__esModule = true;
var TextTrackMode = {
  'disabled': 'disabled',
  'hidden': 'hidden',
  'showing': 'showing'
};

/*
 * https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackkind
 *
 * enum TextTrackKind { "subtitles",  "captions",  "descriptions",  "chapters",  "metadata" };
 */
var TextTrackKind = {
  'subtitles': 'subtitles',
  'captions': 'captions',
  'descriptions': 'descriptions',
  'chapters': 'chapters',
  'metadata': 'metadata'
};

exports.TextTrackMode = TextTrackMode;
exports.TextTrackKind = TextTrackKind;

},{}],121:[function(_dereq_,module,exports){
/**
 * Utilities for capturing text track state and re-creating tracks
 * based on a capture.
 *
 * @file text-track-list-converter.js
 */

/**
 * Examine a single text track and return a JSON-compatible javascript
 * object that represents the text track's state.
 * @param track {TextTrackObject} the text track to query
 * @return {Object} a serializable javascript representation of the
 * @private
 */
'use strict';

exports.__esModule = true;
var trackToJson_ = function trackToJson_(track) {
  var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) {
    if (track[prop]) {
      acc[prop] = track[prop];
    }

    return acc;
  }, {
    cues: track.cues && Array.prototype.map.call(track.cues, function (cue) {
      return {
        startTime: cue.startTime,
        endTime: cue.endTime,
        text: cue.text,
        id: cue.id
      };
    })
  });

  return ret;
};

/**
 * Examine a tech and return a JSON-compatible javascript array that
 * represents the state of all text tracks currently configured. The
 * return array is compatible with `jsonToTextTracks`.
 * @param tech {tech} the tech object to query
 * @return {Array} a serializable javascript representation of the
 * @function textTracksToJson
 */
var textTracksToJson = function textTracksToJson(tech) {

  var trackEls = tech.$$('track');

  var trackObjs = Array.prototype.map.call(trackEls, function (t) {
    return t.track;
  });
  var tracks = Array.prototype.map.call(trackEls, function (trackEl) {
    var json = trackToJson_(trackEl.track);
    if (trackEl.src) {
      json.src = trackEl.src;
    }
    return json;
  });

  return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) {
    return trackObjs.indexOf(track) === -1;
  }).map(trackToJson_));
};

/**
 * Creates a set of remote text tracks on a tech based on an array of
 * javascript text track representations.
 * @param json {Array} an array of text track representation objects,
 * like those that would be produced by `textTracksToJson`
 * @param tech {tech} the tech to create text tracks on
 * @function jsonToTextTracks
 */
var jsonToTextTracks = function jsonToTextTracks(json, tech) {
  json.forEach(function (track) {
    var addedTrack = tech.addRemoteTextTrack(track).track;
    if (!track.src && track.cues) {
      track.cues.forEach(function (cue) {
        return addedTrack.addCue(cue);
      });
    }
  });

  return tech.textTracks();
};

exports['default'] = { textTracksToJson: textTracksToJson, jsonToTextTracks: jsonToTextTracks, trackToJson_: trackToJson_ };
module.exports = exports['default'];

},{}],122:[function(_dereq_,module,exports){
/**
 * @file text-track-list.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

var _eventTarget = _dereq_('../event-target');

var _eventTarget2 = _interopRequireDefault(_eventTarget);

var _utilsFnJs = _dereq_('../utils/fn.js');

var Fn = _interopRequireWildcard(_utilsFnJs);

var _utilsBrowserJs = _dereq_('../utils/browser.js');

var browser = _interopRequireWildcard(_utilsBrowserJs);

var _globalDocument = _dereq_('global/document');

var _globalDocument2 = _interopRequireDefault(_globalDocument);

/*
 * https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist
 *
 * interface TextTrackList : EventTarget {
 *   readonly attribute unsigned long length;
 *   getter TextTrack (unsigned long index);
 *   TextTrack? getTrackById(DOMString id);
 *
 *   attribute EventHandler onchange;
 *   attribute EventHandler onaddtrack;
 *   attribute EventHandler onremovetrack;
 * };
 */
var TextTrackList = function TextTrackList(tracks) {
  var list = this;

  if (browser.IS_IE8) {
    list = _globalDocument2['default'].createElement('custom');

    for (var prop in TextTrackList.prototype) {
      if (prop !== 'constructor') {
        list[prop] = TextTrackList.prototype[prop];
      }
    }
  }

  tracks = tracks || [];
  list.tracks_ = [];

  Object.defineProperty(list, 'length', {
    get: function get() {
      return this.tracks_.length;
    }
  });

  for (var i = 0; i < tracks.length; i++) {
    list.addTrack_(tracks[i]);
  }

  if (browser.IS_IE8) {
    return list;
  }
};

TextTrackList.prototype = Object.create(_eventTarget2['default'].prototype);
TextTrackList.prototype.constructor = TextTrackList;

/*
 * change - One or more tracks in the track list have been enabled or disabled.
 * addtrack - A track has been added to the track list.
 * removetrack - A track has been removed from the track list.
 */
TextTrackList.prototype.allowedEvents_ = {
  'change': 'change',
  'addtrack': 'addtrack',
  'removetrack': 'removetrack'
};

// emulate attribute EventHandler support to allow for feature detection
for (var _event in TextTrackList.prototype.allowedEvents_) {
  TextTrackList.prototype['on' + _event] = null;
}

/**
 * Add TextTrack from TextTrackList
 *
 * @param {TextTrack} track
 * @method addTrack_
 * @private
 */
TextTrackList.prototype.addTrack_ = function (track) {
  var index = this.tracks_.length;
  if (!('' + index in this)) {
    Object.defineProperty(this, index, {
      get: function get() {
        return this.tracks_[index];
      }
    });
  }

  track.addEventListener('modechange', Fn.bind(this, function () {
    this.trigger('change');
  }));
  this.tracks_.push(track);

  this.trigger({
    type: 'addtrack',
    track: track
  });
};

/**
 * Remove TextTrack from TextTrackList
 * NOTE: Be mindful of what is passed in as it may be a HTMLTrackElement
 *
 * @param {TextTrack} rtrack
 * @method removeTrack_
 * @private
 */
TextTrackList.prototype.removeTrack_ = function (rtrack) {
  var track = undefined;

  for (var i = 0, l = this.length; i < l; i++) {
    if (this[i] === rtrack) {
      track = this[i];

      this.tracks_.splice(i, 1);

      break;
    }
  }

  if (!track) {
    return;
  }

  this.trigger({
    type: 'removetrack',
    track: track
  });
};

TextTrackList.prototype.getTrackById = function (id) {
  var result = null;

  for (var i = 0, l = this.length; i < l; i++) {
    var track = this[i];
    if (track.id === id) {
      result = track;
      break;
    }
  }

  return result;
};

exports['default'] = TextTrackList;
module.exports = exports['default'];

},{"../event-target":97,"../utils/browser.js":125,"../utils/fn.js":130,"global/document":1}],123:[function(_dereq_,module,exports){
/**
 * @file text-track-settings.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

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

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _component = _dereq_('../component');

var _component2 = _interopRequireDefault(_component);

var _utilsEventsJs = _dereq_('../utils/events.js');

var Events = _interopRequireWildcard(_utilsEventsJs);

var _utilsFnJs = _dereq_('../utils/fn.js');

var Fn = _interopRequireWildcard(_utilsFnJs);

var _utilsLogJs = _dereq_('../utils/log.js');

var _utilsLogJs2 = _interopRequireDefault(_utilsLogJs);

var _safeJsonParseTuple = _dereq_('safe-json-parse/tuple');

var _safeJsonParseTuple2 = _interopRequireDefault(_safeJsonParseTuple);

var _globalWindow = _dereq_('global/window');

var _globalWindow2 = _interopRequireDefault(_globalWindow);

/**
 * Manipulate settings of texttracks
 *
 * @param {Object} player  Main Player
 * @param {Object=} options Object of option names and values
 * @extends Component
 * @class TextTrackSettings
 */

var TextTrackSettings = (function (_Component) {
  _inherits(TextTrackSettings, _Component);

  function TextTrackSettings(player, options) {
    _classCallCheck(this, TextTrackSettings);

    _Component.call(this, player, options);
    this.hide();

    // Grab `persistTextTrackSettings` from the player options if not passed in child options
    if (options.persistTextTrackSettings === undefined) {
      this.options_.persistTextTrackSettings = this.options_.playerOptions.persistTextTrackSettings;
    }

    Events.on(this.$('.vjs-done-button'), 'click', Fn.bind(this, function () {
      this.saveSettings();
      this.hide();
    }));

    Events.on(this.$('.vjs-default-button'), 'click', Fn.bind(this, function () {
      this.$('.vjs-fg-color > select').selectedIndex = 0;
      this.$('.vjs-bg-color > select').selectedIndex = 0;
      this.$('.window-color > select').selectedIndex = 0;
      this.$('.vjs-text-opacity > select').selectedIndex = 0;
      this.$('.vjs-bg-opacity > select').selectedIndex = 0;
      this.$('.vjs-window-opacity > select').selectedIndex = 0;
      this.$('.vjs-edge-style select').selectedIndex = 0;
      this.$('.vjs-font-family select').selectedIndex = 0;
      this.$('.vjs-font-percent select').selectedIndex = 2;
      this.updateDisplay();
    }));

    Events.on(this.$('.vjs-fg-color > select'), 'change', Fn.bind(this, this.updateDisplay));
    Events.on(this.$('.vjs-bg-color > select'), 'change', Fn.bind(this, this.updateDisplay));
    Events.on(this.$('.window-color > select'), 'change', Fn.bind(this, this.updateDisplay));
    Events.on(this.$('.vjs-text-opacity > select'), 'change', Fn.bind(this, this.updateDisplay));
    Events.on(this.$('.vjs-bg-opacity > select'), 'change', Fn.bind(this, this.updateDisplay));
    Events.on(this.$('.vjs-window-opacity > select'), 'change', Fn.bind(this, this.updateDisplay));
    Events.on(this.$('.vjs-font-percent select'), 'change', Fn.bind(this, this.updateDisplay));
    Events.on(this.$('.vjs-edge-style select'), 'change', Fn.bind(this, this.updateDisplay));
    Events.on(this.$('.vjs-font-family select'), 'change', Fn.bind(this, this.updateDisplay));

    if (this.options_.persistTextTrackSettings) {
      this.restoreSettings();
    }
  }

  /**
   * Create the component's DOM element
   *
   * @return {Element}
   * @method createEl
   */

  TextTrackSettings.prototype.createEl = function createEl() {
    return _Component.prototype.createEl.call(this, 'div', {
      className: 'vjs-caption-settings vjs-modal-overlay',
      innerHTML: captionOptionsMenuTemplate()
    });
  };

  /**
   * Get texttrack settings
   * Settings are
   * .vjs-edge-style
   * .vjs-font-family
   * .vjs-fg-color
   * .vjs-text-opacity
   * .vjs-bg-color
   * .vjs-bg-opacity
   * .window-color
   * .vjs-window-opacity
   *
   * @return {Object}
   * @method getValues
   */

  TextTrackSettings.prototype.getValues = function getValues() {
    var textEdge = getSelectedOptionValue(this.$('.vjs-edge-style select'));
    var fontFamily = getSelectedOptionValue(this.$('.vjs-font-family select'));
    var fgColor = getSelectedOptionValue(this.$('.vjs-fg-color > select'));
    var textOpacity = getSelectedOptionValue(this.$('.vjs-text-opacity > select'));
    var bgColor = getSelectedOptionValue(this.$('.vjs-bg-color > select'));
    var bgOpacity = getSelectedOptionValue(this.$('.vjs-bg-opacity > select'));
    var windowColor = getSelectedOptionValue(this.$('.window-color > select'));
    var windowOpacity = getSelectedOptionValue(this.$('.vjs-window-opacity > select'));
    var fontPercent = _globalWindow2['default']['parseFloat'](getSelectedOptionValue(this.$('.vjs-font-percent > select')));

    var result = {
      'backgroundOpacity': bgOpacity,
      'textOpacity': textOpacity,
      'windowOpacity': windowOpacity,
      'edgeStyle': textEdge,
      'fontFamily': fontFamily,
      'color': fgColor,
      'backgroundColor': bgColor,
      'windowColor': windowColor,
      'fontPercent': fontPercent
    };
    for (var _name in result) {
      if (result[_name] === '' || result[_name] === 'none' || _name === 'fontPercent' && result[_name] === 1.00) {
        delete result[_name];
      }
    }
    return result;
  };

  /**
   * Set texttrack settings
   * Settings are
   * .vjs-edge-style
   * .vjs-font-family
   * .vjs-fg-color
   * .vjs-text-opacity
   * .vjs-bg-color
   * .vjs-bg-opacity
   * .window-color
   * .vjs-window-opacity
   *
   * @param {Object} values Object with texttrack setting values
   * @method setValues
   */

  TextTrackSettings.prototype.setValues = function setValues(values) {
    setSelectedOption(this.$('.vjs-edge-style select'), values.edgeStyle);
    setSelectedOption(this.$('.vjs-font-family select'), values.fontFamily);
    setSelectedOption(this.$('.vjs-fg-color > select'), values.color);
    setSelectedOption(this.$('.vjs-text-opacity > select'), values.textOpacity);
    setSelectedOption(this.$('.vjs-bg-color > select'), values.backgroundColor);
    setSelectedOption(this.$('.vjs-bg-opacity > select'), values.backgroundOpacity);
    setSelectedOption(this.$('.window-color > select'), values.windowColor);
    setSelectedOption(this.$('.vjs-window-opacity > select'), values.windowOpacity);

    var fontPercent = values.fontPercent;

    if (fontPercent) {
      fontPercent = fontPercent.toFixed(2);
    }

    setSelectedOption(this.$('.vjs-font-percent > select'), fontPercent);
  };

  /**
   * Restore texttrack settings
   *
   * @method restoreSettings
   */

  TextTrackSettings.prototype.restoreSettings = function restoreSettings() {
    var _safeParseTuple = _safeJsonParseTuple2['default'](_globalWindow2['default'].localStorage.getItem('vjs-text-track-settings'));

    var err = _safeParseTuple[0];
    var values = _safeParseTuple[1];

    if (err) {
      _utilsLogJs2['default'].error(err);
    }

    if (values) {
      this.setValues(values);
    }
  };

  /**
   * Save texttrack settings to local storage
   *
   * @method saveSettings
   */

  TextTrackSettings.prototype.saveSettings = function saveSettings() {
    if (!this.options_.persistTextTrackSettings) {
      return;
    }

    var values = this.getValues();
    try {
      if (Object.getOwnPropertyNames(values).length > 0) {
        _globalWindow2['default'].localStorage.setItem('vjs-text-track-settings', JSON.stringify(values));
      } else {
        _globalWindow2['default'].localStorage.removeItem('vjs-text-track-settings');
      }
    } catch (e) {}
  };

  /**
   * Update display of texttrack settings
   *
   * @method updateDisplay
   */

  TextTrackSettings.prototype.updateDisplay = function updateDisplay() {
    var ttDisplay = this.player_.getChild('textTrackDisplay');
    if (ttDisplay) {
      ttDisplay.updateDisplay();
    }
  };

  return TextTrackSettings;
})(_component2['default']);

_component2['default'].registerComponent('TextTrackSettings', TextTrackSettings);

function getSelectedOptionValue(target) {
  var selectedOption = undefined;
  // not all browsers support selectedOptions, so, fallback to options
  if (target.selectedOptions) {
    selectedOption = target.selectedOptions[0];
  } else if (target.options) {
    selectedOption = target.options[target.options.selectedIndex];
  }

  return selectedOption.value;
}

function setSelectedOption(target, value) {
  if (!value) {
    return;
  }

  var i = undefined;
  for (i = 0; i < target.options.length; i++) {
    var option = target.options[i];
    if (option.value === value) {
      break;
    }
  }

  target.selectedIndex = i;
}

function captionOptionsMenuTemplate() {
  var template = '<div class="vjs-tracksettings">\n      <div class="vjs-tracksettings-colors">\n        <div class="vjs-fg-color vjs-tracksetting">\n            <label class="vjs-label">Foreground</label>\n            <select>\n              <option value="">---</option>\n              <option value="#FFF">White</option>\n              <option value="#000">Black</option>\n              <option value="#F00">Red</option>\n              <option value="#0F0">Green</option>\n              <option value="#00F">Blue</option>\n              <option value="#FF0">Yellow</option>\n              <option value="#F0F">Magenta</option>\n              <option value="#0FF">Cyan</option>\n            </select>\n            <span class="vjs-text-opacity vjs-opacity">\n              <select>\n                <option value="">---</option>\n                <option value="1">Opaque</option>\n                <option value="0.5">Semi-Opaque</option>\n              </select>\n            </span>\n        </div> <!-- vjs-fg-color -->\n        <div class="vjs-bg-color vjs-tracksetting">\n            <label class="vjs-label">Background</label>\n            <select>\n              <option value="">---</option>\n              <option value="#FFF">White</option>\n              <option value="#000">Black</option>\n              <option value="#F00">Red</option>\n              <option value="#0F0">Green</option>\n              <option value="#00F">Blue</option>\n              <option value="#FF0">Yellow</option>\n              <option value="#F0F">Magenta</option>\n              <option value="#0FF">Cyan</option>\n            </select>\n            <span class="vjs-bg-opacity vjs-opacity">\n                <select>\n                  <option value="">---</option>\n                  <option value="1">Opaque</option>\n                  <option value="0.5">Semi-Transparent</option>\n                  <option value="0">Transparent</option>\n                </select>\n            </span>\n        </div> <!-- vjs-bg-color -->\n        <div class="window-color vjs-tracksetting">\n            <label class="vjs-label">Window</label>\n            <select>\n              <option value="">---</option>\n              <option value="#FFF">White</option>\n              <option value="#000">Black</option>\n              <option value="#F00">Red</option>\n              <option value="#0F0">Green</option>\n              <option value="#00F">Blue</option>\n              <option value="#FF0">Yellow</option>\n              <option value="#F0F">Magenta</option>\n              <option value="#0FF">Cyan</option>\n            </select>\n            <span class="vjs-window-opacity vjs-opacity">\n                <select>\n                  <option value="">---</option>\n                  <option value="1">Opaque</option>\n                  <option value="0.5">Semi-Transparent</option>\n                  <option value="0">Transparent</option>\n                </select>\n            </span>\n        </div> <!-- vjs-window-color -->\n      </div> <!-- vjs-tracksettings -->\n      <div class="vjs-tracksettings-font">\n        <div class="vjs-font-percent vjs-tracksetting">\n          <label class="vjs-label">Font Size</label>\n          <select>\n            <option value="0.50">50%</option>\n            <option value="0.75">75%</option>\n            <option value="1.00" selected>100%</option>\n            <option value="1.25">125%</option>\n            <option value="1.50">150%</option>\n            <option value="1.75">175%</option>\n            <option value="2.00">200%</option>\n            <option value="3.00">300%</option>\n            <option value="4.00">400%</option>\n          </select>\n        </div> <!-- vjs-font-percent -->\n        <div class="vjs-edge-style vjs-tracksetting">\n          <label class="vjs-label">Text Edge Style</label>\n          <select>\n            <option value="none">None</option>\n            <option value="raised">Raised</option>\n            <option value="depressed">Depressed</option>\n            <option value="uniform">Uniform</option>\n            <option value="dropshadow">Dropshadow</option>\n          </select>\n        </div> <!-- vjs-edge-style -->\n        <div class="vjs-font-family vjs-tracksetting">\n          <label class="vjs-label">Font Family</label>\n          <select>\n            <option value="">Default</option>\n            <option value="monospaceSerif">Monospace Serif</option>\n            <option value="proportionalSerif">Proportional Serif</option>\n            <option value="monospaceSansSerif">Monospace Sans-Serif</option>\n            <option value="proportionalSansSerif">Proportional Sans-Serif</option>\n            <option value="casual">Casual</option>\n            <option value="script">Script</option>\n            <option value="small-caps">Small Caps</option>\n          </select>\n        </div> <!-- vjs-font-family -->\n      </div>\n    </div>\n    <div class="vjs-tracksettings-controls">\n      <button class="vjs-default-button">Defaults</button>\n      <button class="vjs-done-button">Done</button>\n    </div>';

  return template;
}

exports['default'] = TextTrackSettings;
module.exports = exports['default'];

},{"../component":65,"../utils/events.js":129,"../utils/fn.js":130,"../utils/log.js":133,"global/window":2,"safe-json-parse/tuple":53}],124:[function(_dereq_,module,exports){
/**
 * @file text-track.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

var _textTrackCueList = _dereq_('./text-track-cue-list');

var _textTrackCueList2 = _interopRequireDefault(_textTrackCueList);

var _utilsFnJs = _dereq_('../utils/fn.js');

var Fn = _interopRequireWildcard(_utilsFnJs);

var _utilsGuidJs = _dereq_('../utils/guid.js');

var Guid = _interopRequireWildcard(_utilsGuidJs);

var _utilsBrowserJs = _dereq_('../utils/browser.js');

var browser = _interopRequireWildcard(_utilsBrowserJs);

var _textTrackEnums = _dereq_('./text-track-enums');

var TextTrackEnum = _interopRequireWildcard(_textTrackEnums);

var _utilsLogJs = _dereq_('../utils/log.js');

var _utilsLogJs2 = _interopRequireDefault(_utilsLogJs);

var _eventTarget = _dereq_('../event-target');

var _eventTarget2 = _interopRequireDefault(_eventTarget);

var _globalDocument = _dereq_('global/document');

var _globalDocument2 = _interopRequireDefault(_globalDocument);

var _globalWindow = _dereq_('global/window');

var _globalWindow2 = _interopRequireDefault(_globalWindow);

var _utilsUrlJs = _dereq_('../utils/url.js');

var _xhr = _dereq_('xhr');

var _xhr2 = _interopRequireDefault(_xhr);

/*
 * https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack
 *
 * interface TextTrack : EventTarget {
 *   readonly attribute TextTrackKind kind;
 *   readonly attribute DOMString label;
 *   readonly attribute DOMString language;
 *
 *   readonly attribute DOMString id;
 *   readonly attribute DOMString inBandMetadataTrackDispatchType;
 *
 *   attribute TextTrackMode mode;
 *
 *   readonly attribute TextTrackCueList? cues;
 *   readonly attribute TextTrackCueList? activeCues;
 *
 *   void addCue(TextTrackCue cue);
 *   void removeCue(TextTrackCue cue);
 *
 *   attribute EventHandler oncuechange;
 * };
 */
var TextTrack = function TextTrack() {
  var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];

  if (!options.tech) {
    throw new Error('A tech was not provided.');
  }

  var tt = this;
  if (browser.IS_IE8) {
    tt = _globalDocument2['default'].createElement('custom');

    for (var prop in TextTrack.prototype) {
      if (prop !== 'constructor') {
        tt[prop] = TextTrack.prototype[prop];
      }
    }
  }

  tt.tech_ = options.tech;

  var mode = TextTrackEnum.TextTrackMode[options['mode']] || 'disabled';
  var kind = TextTrackEnum.TextTrackKind[options['kind']] || 'subtitles';
  var label = options['label'] || '';
  var language = options['language'] || options['srclang'] || '';
  var id = options['id'] || 'vjs_text_track_' + Guid.newGUID();

  if (kind === 'metadata' || kind === 'chapters') {
    mode = 'hidden';
  }

  tt.cues_ = [];
  tt.activeCues_ = [];

  var cues = new _textTrackCueList2['default'](tt.cues_);
  var activeCues = new _textTrackCueList2['default'](tt.activeCues_);

  var changed = false;
  var timeupdateHandler = Fn.bind(tt, function () {
    this['activeCues'];
    if (changed) {
      this['trigger']('cuechange');
      changed = false;
    }
  });
  if (mode !== 'disabled') {
    tt.tech_.on('timeupdate', timeupdateHandler);
  }

  Object.defineProperty(tt, 'kind', {
    get: function get() {
      return kind;
    },
    set: Function.prototype
  });

  Object.defineProperty(tt, 'label', {
    get: function get() {
      return label;
    },
    set: Function.prototype
  });

  Object.defineProperty(tt, 'language', {
    get: function get() {
      return language;
    },
    set: Function.prototype
  });

  Object.defineProperty(tt, 'id', {
    get: function get() {
      return id;
    },
    set: Function.prototype
  });

  Object.defineProperty(tt, 'mode', {
    get: function get() {
      return mode;
    },
    set: function set(newMode) {
      if (!TextTrackEnum.TextTrackMode[newMode]) {
        return;
      }
      mode = newMode;
      if (mode === 'showing') {
        this.tech_.on('timeupdate', timeupdateHandler);
      }
      this.trigger('modechange');
    }
  });

  Object.defineProperty(tt, 'cues', {
    get: function get() {
      if (!this.loaded_) {
        return null;
      }

      return cues;
    },
    set: Function.prototype
  });

  Object.defineProperty(tt, 'activeCues', {
    get: function get() {
      if (!this.loaded_) {
        return null;
      }

      if (this['cues'].length === 0) {
        return activeCues; // nothing to do
      }

      var ct = this.tech_.currentTime();
      var active = [];

      for (var i = 0, l = this['cues'].length; i < l; i++) {
        var cue = this['cues'][i];
        if (cue['startTime'] <= ct && cue['endTime'] >= ct) {
          active.push(cue);
        } else if (cue['startTime'] === cue['endTime'] && cue['startTime'] <= ct && cue['startTime'] + 0.5 >= ct) {
          active.push(cue);
        }
      }

      changed = false;

      if (active.length !== this.activeCues_.length) {
        changed = true;
      } else {
        for (var i = 0; i < active.length; i++) {
          if (indexOf.call(this.activeCues_, active[i]) === -1) {
            changed = true;
          }
        }
      }

      this.activeCues_ = active;
      activeCues.setCues_(this.activeCues_);

      return activeCues;
    },
    set: Function.prototype
  });

  if (options.src) {
    tt.src = options.src;
    loadTrack(options.src, tt);
  } else {
    tt.loaded_ = true;
  }

  if (browser.IS_IE8) {
    return tt;
  }
};

TextTrack.prototype = Object.create(_eventTarget2['default'].prototype);
TextTrack.prototype.constructor = TextTrack;

/*
 * cuechange - One or more cues in the track have become active or stopped being active.
 */
TextTrack.prototype.allowedEvents_ = {
  'cuechange': 'cuechange'
};

TextTrack.prototype.addCue = function (cue) {
  var tracks = this.tech_.textTracks();

  if (tracks) {
    for (var i = 0; i < tracks.length; i++) {
      if (tracks[i] !== this) {
        tracks[i].removeCue(cue);
      }
    }
  }

  this.cues_.push(cue);
  this['cues'].setCues_(this.cues_);
};

TextTrack.prototype.removeCue = function (removeCue) {
  var removed = false;

  for (var i = 0, l = this.cues_.length; i < l; i++) {
    var cue = this.cues_[i];
    if (cue === removeCue) {
      this.cues_.splice(i, 1);
      removed = true;
    }
  }

  if (removed) {
    this.cues.setCues_(this.cues_);
  }
};

/*
* Downloading stuff happens below this point
*/
var parseCues = function parseCues(srcContent, track) {
  var parser = new _globalWindow2['default'].WebVTT.Parser(_globalWindow2['default'], _globalWindow2['default'].vttjs, _globalWindow2['default'].WebVTT.StringDecoder());

  parser.oncue = function (cue) {
    track.addCue(cue);
  };

  parser.onparsingerror = function (error) {
    _utilsLogJs2['default'].error(error);
  };

  parser.onflush = function () {
    track.trigger({
      type: 'loadeddata',
      target: track
    });
  };

  parser.parse(srcContent);
  parser.flush();
};

var loadTrack = function loadTrack(src, track) {
  var opts = {
    uri: src
  };

  var crossOrigin = _utilsUrlJs.isCrossOrigin(src);
  if (crossOrigin) {
    opts.cors = crossOrigin;
  }

  _xhr2['default'](opts, Fn.bind(this, function (err, response, responseBody) {
    if (err) {
      return _utilsLogJs2['default'].error(err, response);
    }

    track.loaded_ = true;

    // NOTE: this is only used for the alt/video.novtt.js build
    if (typeof _globalWindow2['default'].WebVTT !== 'function') {
      _globalWindow2['default'].setTimeout(function () {
        parseCues(responseBody, track);
      }, 100);
    } else {
      parseCues(responseBody, track);
    }
  }));
};

var indexOf = function indexOf(searchElement, fromIndex) {
  if (this == null) {
    throw new TypeError('"this" is null or not defined');
  }

  var O = Object(this);

  var len = O.length >>> 0;

  if (len === 0) {
    return -1;
  }

  var n = +fromIndex || 0;

  if (Math.abs(n) === Infinity) {
    n = 0;
  }

  if (n >= len) {
    return -1;
  }

  var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);

  while (k < len) {
    if (k in O && O[k] === searchElement) {
      return k;
    }
    k++;
  }
  return -1;
};

exports['default'] = TextTrack;
module.exports = exports['default'];

},{"../event-target":97,"../utils/browser.js":125,"../utils/fn.js":130,"../utils/guid.js":132,"../utils/log.js":133,"../utils/url.js":138,"./text-track-cue-list":118,"./text-track-enums":120,"global/document":1,"global/window":2,"xhr":55}],125:[function(_dereq_,module,exports){
/**
 * @file browser.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

var _globalDocument = _dereq_('global/document');

var _globalDocument2 = _interopRequireDefault(_globalDocument);

var _globalWindow = _dereq_('global/window');

var _globalWindow2 = _interopRequireDefault(_globalWindow);

var USER_AGENT = _globalWindow2['default'].navigator.userAgent;
var webkitVersionMap = /AppleWebKit\/([\d.]+)/i.exec(USER_AGENT);
var appleWebkitVersion = webkitVersionMap ? parseFloat(webkitVersionMap.pop()) : null;

/*
 * Device is an iPhone
 *
 * @type {Boolean}
 * @constant
 * @private
 */
var IS_IPHONE = /iPhone/i.test(USER_AGENT);
exports.IS_IPHONE = IS_IPHONE;
var IS_IPAD = /iPad/i.test(USER_AGENT);
exports.IS_IPAD = IS_IPAD;
var IS_IPOD = /iPod/i.test(USER_AGENT);
exports.IS_IPOD = IS_IPOD;
var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD;

exports.IS_IOS = IS_IOS;
var IOS_VERSION = (function () {
  var match = USER_AGENT.match(/OS (\d+)_/i);
  if (match && match[1]) {
    return match[1];
  }
})();

exports.IOS_VERSION = IOS_VERSION;
var IS_ANDROID = /Android/i.test(USER_AGENT);
exports.IS_ANDROID = IS_ANDROID;
var ANDROID_VERSION = (function () {
  // This matches Android Major.Minor.Patch versions
  // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
  var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i),
      major,
      minor;

  if (!match) {
    return null;
  }

  major = match[1] && parseFloat(match[1]);
  minor = match[2] && parseFloat(match[2]);

  if (major && minor) {
    return parseFloat(match[1] + '.' + match[2]);
  } else if (major) {
    return major;
  } else {
    return null;
  }
})();
exports.ANDROID_VERSION = ANDROID_VERSION;
// Old Android is defined as Version older than 2.3, and requiring a webkit version of the android browser
var IS_OLD_ANDROID = IS_ANDROID && /webkit/i.test(USER_AGENT) && ANDROID_VERSION < 2.3;
exports.IS_OLD_ANDROID = IS_OLD_ANDROID;
var IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537;

exports.IS_NATIVE_ANDROID = IS_NATIVE_ANDROID;
var IS_FIREFOX = /Firefox/i.test(USER_AGENT);
exports.IS_FIREFOX = IS_FIREFOX;
var IS_CHROME = /Chrome/i.test(USER_AGENT);
exports.IS_CHROME = IS_CHROME;
var IS_IE8 = /MSIE\s8\.0/.test(USER_AGENT);

exports.IS_IE8 = IS_IE8;
var TOUCH_ENABLED = !!('ontouchstart' in _globalWindow2['default'] || _globalWindow2['default'].DocumentTouch && _globalDocument2['default'] instanceof _globalWindow2['default'].DocumentTouch);
exports.TOUCH_ENABLED = TOUCH_ENABLED;
var BACKGROUND_SIZE_SUPPORTED = ('backgroundSize' in _globalDocument2['default'].createElement('video').style);
exports.BACKGROUND_SIZE_SUPPORTED = BACKGROUND_SIZE_SUPPORTED;

},{"global/document":1,"global/window":2}],126:[function(_dereq_,module,exports){
/**
 * @file buffer.js
 */
'use strict';

exports.__esModule = true;
exports.bufferedPercent = bufferedPercent;

var _timeRangesJs = _dereq_('./time-ranges.js');

/**
 * Compute how much your video has been buffered
 *
 * @param  {Object} Buffered object
 * @param  {Number} Total duration
 * @return {Number} Percent buffered of the total duration
 * @private
 * @function bufferedPercent
 */

function bufferedPercent(buffered, duration) {
  var bufferedDuration = 0,
      start,
      end;

  if (!duration) {
    return 0;
  }

  if (!buffered || !buffered.length) {
    buffered = _timeRangesJs.createTimeRange(0, 0);
  }

  for (var i = 0; i < buffered.length; i++) {
    start = buffered.start(i);
    end = buffered.end(i);

    // buffered end can be bigger than duration by a very small fraction
    if (end > duration) {
      end = duration;
    }

    bufferedDuration += end - start;
  }

  return bufferedDuration / duration;
}

},{"./time-ranges.js":136}],127:[function(_dereq_,module,exports){
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

var _logJs = _dereq_('./log.js');

var _logJs2 = _interopRequireDefault(_logJs);

/**
 * Object containing the default behaviors for available handler methods.
 *
 * @private
 * @type {Object}
 */
var defaultBehaviors = {
  get: function get(obj, key) {
    return obj[key];
  },
  set: function set(obj, key, value) {
    obj[key] = value;
    return true;
  }
};

/**
 * Expose private objects publicly using a Proxy to log deprecation warnings.
 *
 * Browsers that do not support Proxy objects will simply return the `target`
 * object, so it can be directly exposed.
 *
 * @param {Object} target The target object.
 * @param {Object} messages Messages to display from a Proxy. Only operations
 *                          with an associated message will be proxied.
 * @param {String} [messages.get]
 * @param {String} [messages.set]
 * @return {Object} A Proxy if supported or the `target` argument.
 */

exports['default'] = function (target) {
  var messages = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];

  if (typeof Proxy === 'function') {
    var _ret = (function () {
      var handler = {};

      // Build a handler object based on those keys that have both messages
      // and default behaviors.
      Object.keys(messages).forEach(function (key) {
        if (defaultBehaviors.hasOwnProperty(key)) {
          handler[key] = function () {
            _logJs2['default'].warn(messages[key]);
            return defaultBehaviors[key].apply(this, arguments);
          };
        }
      });

      return {
        v: new Proxy(target, handler)
      };
    })();

    if (typeof _ret === 'object') return _ret.v;
  }
  return target;
};

module.exports = exports['default'];

},{"./log.js":133}],128:[function(_dereq_,module,exports){
/**
 * @file dom.js
 */
'use strict';

exports.__esModule = true;
exports.getEl = getEl;
exports.createEl = createEl;
exports.textContent = textContent;
exports.insertElFirst = insertElFirst;
exports.getElData = getElData;
exports.hasElData = hasElData;
exports.removeElData = removeElData;
exports.hasElClass = hasElClass;
exports.addElClass = addElClass;
exports.removeElClass = removeElClass;
exports.toggleElClass = toggleElClass;
exports.setElAttributes = setElAttributes;
exports.getElAttributes = getElAttributes;
exports.blockTextSelection = blockTextSelection;
exports.unblockTextSelection = unblockTextSelection;
exports.findElPosition = findElPosition;
exports.getPointerPosition = getPointerPosition;
exports.isEl = isEl;
exports.isTextNode = isTextNode;
exports.emptyEl = emptyEl;
exports.normalizeContent = normalizeContent;
exports.appendContent = appendContent;
exports.insertContent = insertContent;

var _templateObject = _taggedTemplateLiteralLoose(['Setting attributes in the second argument of createEl()\n                has been deprecated. Use the third argument instead.\n                createEl(type, properties, attributes). Attempting to set ', ' to ', '.'], ['Setting attributes in the second argument of createEl()\n                has been deprecated. Use the third argument instead.\n                createEl(type, properties, attributes). Attempting to set ', ' to ', '.']);

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

function _taggedTemplateLiteralLoose(strings, raw) { strings.raw = raw; return strings; }

var _globalDocument = _dereq_('global/document');

var _globalDocument2 = _interopRequireDefault(_globalDocument);

var _globalWindow = _dereq_('global/window');

var _globalWindow2 = _interopRequireDefault(_globalWindow);

var _guidJs = _dereq_('./guid.js');

var Guid = _interopRequireWildcard(_guidJs);

var _logJs = _dereq_('./log.js');

var _logJs2 = _interopRequireDefault(_logJs);

var _tsml = _dereq_('tsml');

var _tsml2 = _interopRequireDefault(_tsml);

/**
 * Detect if a value is a string with any non-whitespace characters.
 *
 * @param  {String} str
 * @return {Boolean}
 */
function isNonBlankString(str) {
  return typeof str === 'string' && /\S/.test(str);
}

/**
 * Throws an error if the passed string has whitespace. This is used by
 * class methods to be relatively consistent with the classList API.
 *
 * @param  {String} str
 * @return {Boolean}
 */
function throwIfWhitespace(str) {
  if (/\s/.test(str)) {
    throw new Error('class has illegal whitespace characters');
  }
}

/**
 * Produce a regular expression for matching a class name.
 *
 * @param  {String} className
 * @return {RegExp}
 */
function classRegExp(className) {
  return new RegExp('(^|\\s)' + className + '($|\\s)');
}

/**
 * Creates functions to query the DOM using a given method.
 *
 * @function createQuerier
 * @private
 * @param  {String} method
 * @return {Function}
 */
function createQuerier(method) {
  return function (selector, context) {
    if (!isNonBlankString(selector)) {
      return _globalDocument2['default'][method](null);
    }
    if (isNonBlankString(context)) {
      context = _globalDocument2['default'].querySelector(context);
    }
    return (isEl(context) ? context : _globalDocument2['default'])[method](selector);
  };
}

/**
 * Shorthand for document.getElementById()
 * Also allows for CSS (jQuery) ID syntax. But nothing other than IDs.
 *
 * @param  {String} id  Element ID
 * @return {Element}    Element with supplied ID
 * @function getEl
 */

function getEl(id) {
  if (id.indexOf('#') === 0) {
    id = id.slice(1);
  }

  return _globalDocument2['default'].getElementById(id);
}

/**
 * Creates an element and applies properties.
 *
 * @param  {String} [tagName='div'] Name of tag to be created.
 * @param  {Object} [properties={}] Element properties to be applied.
 * @param  {Object} [attributes={}] Element attributes to be applied.
 * @return {Element}
 * @function createEl
 */

function createEl() {
  var tagName = arguments.length <= 0 || arguments[0] === undefined ? 'div' : arguments[0];
  var properties = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
  var attributes = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];

  var el = _globalDocument2['default'].createElement(tagName);

  Object.getOwnPropertyNames(properties).forEach(function (propName) {
    var val = properties[propName];

    // See #2176
    // We originally were accepting both properties and attributes in the
    // same object, but that doesn't work so well.
    if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') {
      _logJs2['default'].warn(_tsml2['default'](_templateObject, propName, val));
      el.setAttribute(propName, val);
    } else {
      el[propName] = val;
    }
  });

  Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
    var val = attributes[attrName];
    el.setAttribute(attrName, attributes[attrName]);
  });

  return el;
}

/**
 * Injects text into an element, replacing any existing contents entirely.
 *
 * @param  {Element} el
 * @param  {String} text
 * @return {Element}
 * @function textContent
 */

function textContent(el, text) {
  if (typeof el.textContent === 'undefined') {
    el.innerText = text;
  } else {
    el.textContent = text;
  }
}

/**
 * Insert an element as the first child node of another
 *
 * @param  {Element} child   Element to insert
 * @param  {Element} parent Element to insert child into
 * @private
 * @function insertElFirst
 */

function insertElFirst(child, parent) {
  if (parent.firstChild) {
    parent.insertBefore(child, parent.firstChild);
  } else {
    parent.appendChild(child);
  }
}

/**
 * Element Data Store. Allows for binding data to an element without putting it directly on the element.
 * Ex. Event listeners are stored here.
 * (also from jsninja.com, slightly modified and updated for closure compiler)
 *
 * @type {Object}
 * @private
 */
var elData = {};

/*
 * Unique attribute name to store an element's guid in
 *
 * @type {String}
 * @constant
 * @private
 */
var elIdAttr = 'vdata' + new Date().getTime();

/**
 * Returns the cache object where data for an element is stored
 *
 * @param  {Element} el Element to store data for.
 * @return {Object}
 * @function getElData
 */

function getElData(el) {
  var id = el[elIdAttr];

  if (!id) {
    id = el[elIdAttr] = Guid.newGUID();
  }

  if (!elData[id]) {
    elData[id] = {};
  }

  return elData[id];
}

/**
 * Returns whether or not an element has cached data
 *
 * @param  {Element} el A dom element
 * @return {Boolean}
 * @private
 * @function hasElData
 */

function hasElData(el) {
  var id = el[elIdAttr];

  if (!id) {
    return false;
  }

  return !!Object.getOwnPropertyNames(elData[id]).length;
}

/**
 * Delete data for the element from the cache and the guid attr from getElementById
 *
 * @param  {Element} el Remove data for an element
 * @private
 * @function removeElData
 */

function removeElData(el) {
  var id = el[elIdAttr];

  if (!id) {
    return;
  }

  // Remove all stored data
  delete elData[id];

  // Remove the elIdAttr property from the DOM node
  try {
    delete el[elIdAttr];
  } catch (e) {
    if (el.removeAttribute) {
      el.removeAttribute(elIdAttr);
    } else {
      // IE doesn't appear to support removeAttribute on the document element
      el[elIdAttr] = null;
    }
  }
}

/**
 * Check if an element has a CSS class
 *
 * @function hasElClass
 * @param {Element} element Element to check
 * @param {String} classToCheck Classname to check
 */

function hasElClass(element, classToCheck) {
  if (element.classList) {
    return element.classList.contains(classToCheck);
  } else {
    throwIfWhitespace(classToCheck);
    return classRegExp(classToCheck).test(element.className);
  }
}

/**
 * Add a CSS class name to an element
 *
 * @function addElClass
 * @param {Element} element    Element to add class name to
 * @param {String} classToAdd Classname to add
 */

function addElClass(element, classToAdd) {
  if (element.classList) {
    element.classList.add(classToAdd);

    // Don't need to `throwIfWhitespace` here because `hasElClass` will do it
    // in the case of classList not being supported.
  } else if (!hasElClass(element, classToAdd)) {
      element.className = (element.className + ' ' + classToAdd).trim();
    }

  return element;
}

/**
 * Remove a CSS class name from an element
 *
 * @function removeElClass
 * @param {Element} element    Element to remove from class name
 * @param {String} classToRemove Classname to remove
 */

function removeElClass(element, classToRemove) {
  if (element.classList) {
    element.classList.remove(classToRemove);
  } else {
    throwIfWhitespace(classToRemove);
    element.className = element.className.split(/\s+/).filter(function (c) {
      return c !== classToRemove;
    }).join(' ');
  }

  return element;
}

/**
 * Adds or removes a CSS class name on an element depending on an optional
 * condition or the presence/absence of the class name.
 *
 * @function toggleElClass
 * @param    {Element} element
 * @param    {String} classToToggle
 * @param    {Boolean|Function} [predicate]
 *           Can be a function that returns a Boolean. If `true`, the class
 *           will be added; if `false`, the class will be removed. If not
 *           given, the class will be added if not present and vice versa.
 */

function toggleElClass(element, classToToggle, predicate) {

  // This CANNOT use `classList` internally because IE does not support the
  // second parameter to the `classList.toggle()` method! Which is fine because
  // `classList` will be used by the add/remove functions.
  var has = hasElClass(element, classToToggle);

  if (typeof predicate === 'function') {
    predicate = predicate(element, classToToggle);
  }

  if (typeof predicate !== 'boolean') {
    predicate = !has;
  }

  // If the necessary class operation matches the current state of the
  // element, no action is required.
  if (predicate === has) {
    return;
  }

  if (predicate) {
    addElClass(element, classToToggle);
  } else {
    removeElClass(element, classToToggle);
  }

  return element;
}

/**
 * Apply attributes to an HTML element.
 *
 * @param  {Element} el         Target element.
 * @param  {Object=} attributes Element attributes to be applied.
 * @private
 * @function setElAttributes
 */

function setElAttributes(el, attributes) {
  Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
    var attrValue = attributes[attrName];

    if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) {
      el.removeAttribute(attrName);
    } else {
      el.setAttribute(attrName, attrValue === true ? '' : attrValue);
    }
  });
}

/**
 * Get an element's attribute values, as defined on the HTML tag
 * Attributes are not the same as properties. They're defined on the tag
 * or with setAttribute (which shouldn't be used with HTML)
 * This will return true or false for boolean attributes.
 *
 * @param  {Element} tag Element from which to get tag attributes
 * @return {Object}
 * @private
 * @function getElAttributes
 */

function getElAttributes(tag) {
  var obj, knownBooleans, attrs, attrName, attrVal;

  obj = {};

  // known boolean attributes
  // we can check for matching boolean properties, but older browsers
  // won't know about HTML5 boolean attributes that we still read from
  knownBooleans = ',' + 'autoplay,controls,loop,muted,default' + ',';

  if (tag && tag.attributes && tag.attributes.length > 0) {
    attrs = tag.attributes;

    for (var i = attrs.length - 1; i >= 0; i--) {
      attrName = attrs[i].name;
      attrVal = attrs[i].value;

      // check for known booleans
      // the matching element property will return a value for typeof
      if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) {
        // the value of an included boolean attribute is typically an empty
        // string ('') which would equal false if we just check for a false value.
        // we also don't want support bad code like autoplay='false'
        attrVal = attrVal !== null ? true : false;
      }

      obj[attrName] = attrVal;
    }
  }

  return obj;
}

/**
 * Attempt to block the ability to select text while dragging controls
 *
 * @return {Boolean}
 * @function blockTextSelection
 */

function blockTextSelection() {
  _globalDocument2['default'].body.focus();
  _globalDocument2['default'].onselectstart = function () {
    return false;
  };
}

/**
 * Turn off text selection blocking
 *
 * @return {Boolean}
 * @function unblockTextSelection
 */

function unblockTextSelection() {
  _globalDocument2['default'].onselectstart = function () {
    return true;
  };
}

/**
 * Offset Left
 * getBoundingClientRect technique from
 * John Resig http://ejohn.org/blog/getboundingclientrect-is-awesome/
 *
 * @function findElPosition
 * @param {Element} el Element from which to get offset
 * @return {Object}
 */

function findElPosition(el) {
  var box = undefined;

  if (el.getBoundingClientRect && el.parentNode) {
    box = el.getBoundingClientRect();
  }

  if (!box) {
    return {
      left: 0,
      top: 0
    };
  }

  var docEl = _globalDocument2['default'].documentElement;
  var body = _globalDocument2['default'].body;

  var clientLeft = docEl.clientLeft || body.clientLeft || 0;
  var scrollLeft = _globalWindow2['default'].pageXOffset || body.scrollLeft;
  var left = box.left + scrollLeft - clientLeft;

  var clientTop = docEl.clientTop || body.clientTop || 0;
  var scrollTop = _globalWindow2['default'].pageYOffset || body.scrollTop;
  var top = box.top + scrollTop - clientTop;

  // Android sometimes returns slightly off decimal values, so need to round
  return {
    left: Math.round(left),
    top: Math.round(top)
  };
}

/**
 * Get pointer position in element
 * Returns an object with x and y coordinates.
 * The base on the coordinates are the bottom left of the element.
 *
 * @function getPointerPosition
 * @param {Element} el Element on which to get the pointer position on
 * @param {Event} event Event object
 * @return {Object} This object will have x and y coordinates corresponding to the mouse position
 */

function getPointerPosition(el, event) {
  var position = {};
  var box = findElPosition(el);
  var boxW = el.offsetWidth;
  var boxH = el.offsetHeight;

  var boxY = box.top;
  var boxX = box.left;
  var pageY = event.pageY;
  var pageX = event.pageX;

  if (event.changedTouches) {
    pageX = event.changedTouches[0].pageX;
    pageY = event.changedTouches[0].pageY;
  }

  position.y = Math.max(0, Math.min(1, (boxY - pageY + boxH) / boxH));
  position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW));

  return position;
}

/**
 * Determines, via duck typing, whether or not a value is a DOM element.
 *
 * @function isEl
 * @param    {Mixed} value
 * @return   {Boolean}
 */

function isEl(value) {
  return !!value && typeof value === 'object' && value.nodeType === 1;
}

/**
 * Determines, via duck typing, whether or not a value is a text node.
 *
 * @param  {Mixed} value
 * @return {Boolean}
 */

function isTextNode(value) {
  return !!value && typeof value === 'object' && value.nodeType === 3;
}

/**
 * Empties the contents of an element.
 *
 * @function emptyEl
 * @param    {Element} el
 * @return   {Element}
 */

function emptyEl(el) {
  while (el.firstChild) {
    el.removeChild(el.firstChild);
  }
  return el;
}

/**
 * Normalizes content for eventual insertion into the DOM.
 *
 * This allows a wide range of content definition methods, but protects
 * from falling into the trap of simply writing to `innerHTML`, which is
 * an XSS concern.
 *
 * The content for an element can be passed in multiple types and
 * combinations, whose behavior is as follows:
 *
 * - String
 *   Normalized into a text node.
 *
 * - Element, TextNode
 *   Passed through.
 *
 * - Array
 *   A one-dimensional array of strings, elements, nodes, or functions (which
 *   return single strings, elements, or nodes).
 *
 * - Function
 *   If the sole argument, is expected to produce a string, element,
 *   node, or array.
 *
 * @function normalizeContent
 * @param    {String|Element|TextNode|Array|Function} content
 * @return   {Array}
 */

function normalizeContent(content) {

  // First, invoke content if it is a function. If it produces an array,
  // that needs to happen before normalization.
  if (typeof content === 'function') {
    content = content();
  }

  // Next up, normalize to an array, so one or many items can be normalized,
  // filtered, and returned.
  return (Array.isArray(content) ? content : [content]).map(function (value) {

    // First, invoke value if it is a function to produce a new value,
    // which will be subsequently normalized to a Node of some kind.
    if (typeof value === 'function') {
      value = value();
    }

    if (isEl(value) || isTextNode(value)) {
      return value;
    }

    if (typeof value === 'string' && /\S/.test(value)) {
      return _globalDocument2['default'].createTextNode(value);
    }
  }).filter(function (value) {
    return value;
  });
}

/**
 * Normalizes and appends content to an element.
 *
 * @function appendContent
 * @param    {Element} el
 * @param    {String|Element|TextNode|Array|Function} content
 *           See: `normalizeContent`
 * @return   {Element}
 */

function appendContent(el, content) {
  normalizeContent(content).forEach(function (node) {
    return el.appendChild(node);
  });
  return el;
}

/**
 * Normalizes and inserts content into an element; this is identical to
 * `appendContent()`, except it empties the element first.
 *
 * @function insertContent
 * @param    {Element} el
 * @param    {String|Element|TextNode|Array|Function} content
 *           See: `normalizeContent`
 * @return   {Element}
 */

function insertContent(el, content) {
  return appendContent(emptyEl(el), content);
}

/**
 * Finds a single DOM element matching `selector` within the optional
 * `context` of another DOM element (defaulting to `document`).
 *
 * @function $
 * @param    {String} selector
 *           A valid CSS selector, which will be passed to `querySelector`.
 *
 * @param    {Element|String} [context=document]
 *           A DOM element within which to query. Can also be a selector
 *           string in which case the first matching element will be used
 *           as context. If missing (or no element matches selector), falls
 *           back to `document`.
 *
 * @return   {Element|null}
 */
var $ = createQuerier('querySelector');

exports.$ = $;
/**
 * Finds a all DOM elements matching `selector` within the optional
 * `context` of another DOM element (defaulting to `document`).
 *
 * @function $$
 * @param    {String} selector
 *           A valid CSS selector, which will be passed to `querySelectorAll`.
 *
 * @param    {Element|String} [context=document]
 *           A DOM element within which to query. Can also be a selector
 *           string in which case the first matching element will be used
 *           as context. If missing (or no element matches selector), falls
 *           back to `document`.
 *
 * @return   {NodeList}
 */
var $$ = createQuerier('querySelectorAll');
exports.$$ = $$;

},{"./guid.js":132,"./log.js":133,"global/document":1,"global/window":2,"tsml":54}],129:[function(_dereq_,module,exports){
/**
 * @file events.js
 *
 * Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
 * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
 * This should work very similarly to jQuery's events, however it's based off the book version which isn't as
 * robust as jquery's, so there's probably some differences.
 */

'use strict';

exports.__esModule = true;
exports.on = on;
exports.off = off;
exports.trigger = trigger;
exports.one = one;
exports.fixEvent = fixEvent;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

var _domJs = _dereq_('./dom.js');

var Dom = _interopRequireWildcard(_domJs);

var _guidJs = _dereq_('./guid.js');

var Guid = _interopRequireWildcard(_guidJs);

var _globalWindow = _dereq_('global/window');

var _globalWindow2 = _interopRequireDefault(_globalWindow);

var _globalDocument = _dereq_('global/document');

var _globalDocument2 = _interopRequireDefault(_globalDocument);

/**
 * Add an event listener to element
 * It stores the handler function in a separate cache object
 * and adds a generic handler to the element's event,
 * along with a unique id (guid) to the element.
 *
 * @param  {Element|Object}   elem Element or object to bind listeners to
 * @param  {String|Array}   type Type of event to bind to.
 * @param  {Function} fn   Event listener.
 * @method on
 */

function on(elem, type, fn) {
  if (Array.isArray(type)) {
    return _handleMultipleEvents(on, elem, type, fn);
  }

  var data = Dom.getElData(elem);

  // We need a place to store all our handler data
  if (!data.handlers) data.handlers = {};

  if (!data.handlers[type]) data.handlers[type] = [];

  if (!fn.guid) fn.guid = Guid.newGUID();

  data.handlers[type].push(fn);

  if (!data.dispatcher) {
    data.disabled = false;

    data.dispatcher = function (event, hash) {

      if (data.disabled) return;
      event = fixEvent(event);

      var handlers = data.handlers[event.type];

      if (handlers) {
        // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
        var handlersCopy = handlers.slice(0);

        for (var m = 0, n = handlersCopy.length; m < n; m++) {
          if (event.isImmediatePropagationStopped()) {
            break;
          } else {
            handlersCopy[m].call(elem, event, hash);
          }
        }
      }
    };
  }

  if (data.handlers[type].length === 1) {
    if (elem.addEventListener) {
      elem.addEventListener(type, data.dispatcher, false);
    } else if (elem.attachEvent) {
      elem.attachEvent('on' + type, data.dispatcher);
    }
  }
}

/**
 * Removes event listeners from an element
 *
 * @param  {Element|Object}   elem Object to remove listeners from
 * @param  {String|Array=}   type Type of listener to remove. Don't include to remove all events from element.
 * @param  {Function} fn   Specific listener to remove. Don't include to remove listeners for an event type.
 * @method off
 */

function off(elem, type, fn) {
  // Don't want to add a cache object through getElData if not needed
  if (!Dom.hasElData(elem)) return;

  var data = Dom.getElData(elem);

  // If no events exist, nothing to unbind
  if (!data.handlers) {
    return;
  }

  if (Array.isArray(type)) {
    return _handleMultipleEvents(off, elem, type, fn);
  }

  // Utility function
  var removeType = function removeType(t) {
    data.handlers[t] = [];
    _cleanUpEvents(elem, t);
  };

  // Are we removing all bound events?
  if (!type) {
    for (var t in data.handlers) {
      removeType(t);
    }return;
  }

  var handlers = data.handlers[type];

  // If no handlers exist, nothing to unbind
  if (!handlers) return;

  // If no listener was provided, remove all listeners for type
  if (!fn) {
    removeType(type);
    return;
  }

  // We're only removing a single handler
  if (fn.guid) {
    for (var n = 0; n < handlers.length; n++) {
      if (handlers[n].guid === fn.guid) {
        handlers.splice(n--, 1);
      }
    }
  }

  _cleanUpEvents(elem, type);
}

/**
 * Trigger an event for an element
 *
 * @param  {Element|Object}      elem  Element to trigger an event on
 * @param  {Event|Object|String} event A string (the type) or an event object with a type attribute
 * @param  {Object} [hash] data hash to pass along with the event
 * @return {Boolean=} Returned only if default was prevented
 * @method trigger
 */

function trigger(elem, event, hash) {
  // Fetches element data and a reference to the parent (for bubbling).
  // Don't want to add a data object to cache for every parent,
  // so checking hasElData first.
  var elemData = Dom.hasElData(elem) ? Dom.getElData(elem) : {};
  var parent = elem.parentNode || elem.ownerDocument;
  // type = event.type || event,
  // handler;

  // If an event name was passed as a string, creates an event out of it
  if (typeof event === 'string') {
    event = { type: event, target: elem };
  }
  // Normalizes the event properties.
  event = fixEvent(event);

  // If the passed element has a dispatcher, executes the established handlers.
  if (elemData.dispatcher) {
    elemData.dispatcher.call(elem, event, hash);
  }

  // Unless explicitly stopped or the event does not bubble (e.g. media events)
  // recursively calls this function to bubble the event up the DOM.
  if (parent && !event.isPropagationStopped() && event.bubbles === true) {
    trigger.call(null, parent, event, hash);

    // If at the top of the DOM, triggers the default action unless disabled.
  } else if (!parent && !event.defaultPrevented) {
      var targetData = Dom.getElData(event.target);

      // Checks if the target has a default action for this event.
      if (event.target[event.type]) {
        // Temporarily disables event dispatching on the target as we have already executed the handler.
        targetData.disabled = true;
        // Executes the default action.
        if (typeof event.target[event.type] === 'function') {
          event.target[event.type]();
        }
        // Re-enables event dispatching.
        targetData.disabled = false;
      }
    }

  // Inform the triggerer if the default was prevented by returning false
  return !event.defaultPrevented;
}

/**
 * Trigger a listener only once for an event
 *
 * @param  {Element|Object}   elem Element or object to
 * @param  {String|Array}   type Name/type of event
 * @param  {Function} fn Event handler function
 * @method one
 */

function one(elem, type, fn) {
  if (Array.isArray(type)) {
    return _handleMultipleEvents(one, elem, type, fn);
  }
  var func = function func() {
    off(elem, type, func);
    fn.apply(this, arguments);
  };
  // copy the guid to the new function so it can removed using the original function's ID
  func.guid = fn.guid = fn.guid || Guid.newGUID();
  on(elem, type, func);
}

/**
 * Fix a native event to have standard property values
 *
 * @param  {Object} event Event object to fix
 * @return {Object}
 * @private
 * @method fixEvent
 */

function fixEvent(event) {

  function returnTrue() {
    return true;
  }
  function returnFalse() {
    return false;
  }

  // Test if fixing up is needed
  // Used to check if !event.stopPropagation instead of isPropagationStopped
  // But native events return true for stopPropagation, but don't have
  // other expected methods like isPropagationStopped. Seems to be a problem
  // with the Javascript Ninja code. So we're just overriding all events now.
  if (!event || !event.isPropagationStopped) {
    var old = event || _globalWindow2['default'].event;

    event = {};
    // Clone the old object so that we can modify the values event = {};
    // IE8 Doesn't like when you mess with native event properties
    // Firefox returns false for event.hasOwnProperty('type') and other props
    //  which makes copying more difficult.
    // TODO: Probably best to create a whitelist of event props
    for (var key in old) {
      // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
      // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation
      // and webkitMovementX/Y
      if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY') {
        // Chrome 32+ warns if you try to copy deprecated returnValue, but
        // we still want to if preventDefault isn't supported (IE8).
        if (!(key === 'returnValue' && old.preventDefault)) {
          event[key] = old[key];
        }
      }
    }

    // The event occurred on this element
    if (!event.target) {
      event.target = event.srcElement || _globalDocument2['default'];
    }

    // Handle which other element the event is related to
    if (!event.relatedTarget) {
      event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
    }

    // Stop the default browser action
    event.preventDefault = function () {
      if (old.preventDefault) {
        old.preventDefault();
      }
      event.returnValue = false;
      old.returnValue = false;
      event.defaultPrevented = true;
    };

    event.defaultPrevented = false;

    // Stop the event from bubbling
    event.stopPropagation = function () {
      if (old.stopPropagation) {
        old.stopPropagation();
      }
      event.cancelBubble = true;
      old.cancelBubble = true;
      event.isPropagationStopped = returnTrue;
    };

    event.isPropagationStopped = returnFalse;

    // Stop the event from bubbling and executing other handlers
    event.stopImmediatePropagation = function () {
      if (old.stopImmediatePropagation) {
        old.stopImmediatePropagation();
      }
      event.isImmediatePropagationStopped = returnTrue;
      event.stopPropagation();
    };

    event.isImmediatePropagationStopped = returnFalse;

    // Handle mouse position
    if (event.clientX != null) {
      var doc = _globalDocument2['default'].documentElement,
          body = _globalDocument2['default'].body;

      event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
      event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
    }

    // Handle key presses
    event.which = event.charCode || event.keyCode;

    // Fix button for mouse clicks:
    // 0 == left; 1 == middle; 2 == right
    if (event.button != null) {
      event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0;
    }
  }

  // Returns fixed-up instance
  return event;
}

/**
 * Clean up the listener cache and dispatchers
*
 * @param  {Element|Object} elem Element to clean up
 * @param  {String} type Type of event to clean up
 * @private
 * @method _cleanUpEvents
 */
function _cleanUpEvents(elem, type) {
  var data = Dom.getElData(elem);

  // Remove the events of a particular type if there are none left
  if (data.handlers[type].length === 0) {
    delete data.handlers[type];
    // data.handlers[type] = null;
    // Setting to null was causing an error with data.handlers

    // Remove the meta-handler from the element
    if (elem.removeEventListener) {
      elem.removeEventListener(type, data.dispatcher, false);
    } else if (elem.detachEvent) {
      elem.detachEvent('on' + type, data.dispatcher);
    }
  }

  // Remove the events object if there are no types left
  if (Object.getOwnPropertyNames(data.handlers).length <= 0) {
    delete data.handlers;
    delete data.dispatcher;
    delete data.disabled;
  }

  // Finally remove the element data if there is no data left
  if (Object.getOwnPropertyNames(data).length === 0) {
    Dom.removeElData(elem);
  }
}

/**
 * Loops through an array of event types and calls the requested method for each type.
 *
 * @param  {Function} fn   The event method we want to use.
 * @param  {Element|Object} elem Element or object to bind listeners to
 * @param  {String}   type Type of event to bind to.
 * @param  {Function} callback   Event listener.
 * @private
 * @function _handleMultipleEvents
 */
function _handleMultipleEvents(fn, elem, types, callback) {
  types.forEach(function (type) {
    //Call the event method for each one of the types
    fn(elem, type, callback);
  });
}

},{"./dom.js":128,"./guid.js":132,"global/document":1,"global/window":2}],130:[function(_dereq_,module,exports){
/**
 * @file fn.js
 */
'use strict';

exports.__esModule = true;

var _guidJs = _dereq_('./guid.js');

/**
 * Bind (a.k.a proxy or Context). A simple method for changing the context of a function
 * It also stores a unique id on the function so it can be easily removed from events
 *
 * @param  {*}   context The object to bind as scope
 * @param  {Function} fn      The function to be bound to a scope
 * @param  {Number=}   uid     An optional unique ID for the function to be set
 * @return {Function}
 * @private
 * @method bind
 */
var bind = function bind(context, fn, uid) {
  // Make sure the function has a unique ID
  if (!fn.guid) {
    fn.guid = _guidJs.newGUID();
  }

  // Create the new function that changes the context
  var ret = function ret() {
    return fn.apply(context, arguments);
  };

  // Allow for the ability to individualize this function
  // Needed in the case where multiple objects might share the same prototype
  // IF both items add an event listener with the same function, then you try to remove just one
  // it will remove both because they both have the same guid.
  // when using this, you need to use the bind method when you remove the listener as well.
  // currently used in text tracks
  ret.guid = uid ? uid + '_' + fn.guid : fn.guid;

  return ret;
};
exports.bind = bind;

},{"./guid.js":132}],131:[function(_dereq_,module,exports){
/**
 * @file format-time.js
 *
 * Format seconds as a time string, H:MM:SS or M:SS
 * Supplying a guide (in seconds) will force a number of leading zeros
 * to cover the length of the guide
 *
 * @param  {Number} seconds Number of seconds to be turned into a string
 * @param  {Number} guide   Number (in seconds) to model the string after
 * @return {String}         Time formatted as H:MM:SS or M:SS
 * @private
 * @function formatTime
 */
'use strict';

exports.__esModule = true;
function formatTime(seconds) {
  var guide = arguments.length <= 1 || arguments[1] === undefined ? seconds : arguments[1];
  return (function () {
    seconds = seconds < 0 ? 0 : seconds;
    var s = Math.floor(seconds % 60);
    var m = Math.floor(seconds / 60 % 60);
    var h = Math.floor(seconds / 3600);
    var gm = Math.floor(guide / 60 % 60);
    var gh = Math.floor(guide / 3600);

    // handle invalid times
    if (isNaN(seconds) || seconds === Infinity) {
      // '-' is false for all relational operators (e.g. <, >=) so this setting
      // will add the minimum number of fields specified by the guide
      h = m = s = '-';
    }

    // Check if we need to show hours
    h = h > 0 || gh > 0 ? h + ':' : '';

    // If hours are showing, we may need to add a leading zero.
    // Always show at least one digit of minutes.
    m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':';

    // Check if leading zero is need for seconds
    s = s < 10 ? '0' + s : s;

    return h + m + s;
  })();
}

exports['default'] = formatTime;
module.exports = exports['default'];

},{}],132:[function(_dereq_,module,exports){
/**
 * @file guid.js
 *
 * Unique ID for an element or function
 * @type {Number}
 * @private
 */
"use strict";

exports.__esModule = true;
exports.newGUID = newGUID;
var _guid = 1;

/**
 * Get the next unique ID
 *
 * @return {String} 
 * @function newGUID
 */

function newGUID() {
  return _guid++;
}

},{}],133:[function(_dereq_,module,exports){
/**
 * @file log.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

var _globalWindow = _dereq_('global/window');

var _globalWindow2 = _interopRequireDefault(_globalWindow);

/**
 * Log plain debug messages
 */
var log = function log() {
  _logType(null, arguments);
};

/**
 * Keep a history of log messages
 * @type {Array}
 */
log.history = [];

/**
 * Log error messages
 */
log.error = function () {
  _logType('error', arguments);
};

/**
 * Log warning messages
 */
log.warn = function () {
  _logType('warn', arguments);
};

/**
 * Log messages to the console and history based on the type of message
 *
 * @param  {String} type The type of message, or `null` for `log`
 * @param  {Object} args The args to be passed to the log
 * @private
 * @method _logType
 */
function _logType(type, args) {
  // convert args to an array to get array functions
  var argsArray = Array.prototype.slice.call(args);
  // if there's no console then don't try to output messages
  // they will still be stored in log.history
  // Was setting these once outside of this function, but containing them
  // in the function makes it easier to test cases where console doesn't exist
  var noop = function noop() {};

  var console = _globalWindow2['default']['console'] || {
    'log': noop,
    'warn': noop,
    'error': noop
  };

  if (type) {
    // add the type to the front of the message
    argsArray.unshift(type.toUpperCase() + ':');
  } else {
    // default to log with no prefix
    type = 'log';
  }

  // add to history
  log.history.push(argsArray);

  // add console prefix after adding to history
  argsArray.unshift('VIDEOJS:');

  // call appropriate log function
  if (console[type].apply) {
    console[type].apply(console, argsArray);
  } else {
    // ie8 doesn't allow error.apply, but it will just join() the array anyway
    console[type](argsArray.join(' '));
  }
}

exports['default'] = log;
module.exports = exports['default'];

},{"global/window":2}],134:[function(_dereq_,module,exports){
/**
 * @file merge-options.js
 */
'use strict';

exports.__esModule = true;
exports['default'] = mergeOptions;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

var _lodashCompatObjectMerge = _dereq_('lodash-compat/object/merge');

var _lodashCompatObjectMerge2 = _interopRequireDefault(_lodashCompatObjectMerge);

function isPlain(obj) {
  return !!obj && typeof obj === 'object' && obj.toString() === '[object Object]' && obj.constructor === Object;
}

/**
 * Merge customizer. video.js simply overwrites non-simple objects
 * (like arrays) instead of attempting to overlay them.
 * @see https://lodash.com/docs#merge
 */
var customizer = function customizer(destination, source) {
  // If we're not working with a plain object, copy the value as is
  // If source is an array, for instance, it will replace destination
  if (!isPlain(source)) {
    return source;
  }

  // If the new value is a plain object but the first object value is not
  // we need to create a new object for the first object to merge with.
  // This makes it consistent with how merge() works by default
  // and also protects from later changes the to first object affecting
  // the second object's values.
  if (!isPlain(destination)) {
    return mergeOptions(source);
  }
};

/**
 * Merge one or more options objects, recursively merging **only**
 * plain object properties.  Previously `deepMerge`.
 *
 * @param  {...Object} source One or more objects to merge
 * @returns {Object}          a new object that is the union of all
 * provided objects
 * @function mergeOptions
 */

function mergeOptions() {
  // contruct the call dynamically to handle the variable number of
  // objects to merge
  var args = Array.prototype.slice.call(arguments);

  // unshift an empty object into the front of the call as the target
  // of the merge
  args.unshift({});

  // customize conflict resolution to match our historical merge behavior
  args.push(customizer);

  _lodashCompatObjectMerge2['default'].apply(null, args);

  // return the mutated result object
  return args[0];
}

module.exports = exports['default'];

},{"lodash-compat/object/merge":40}],135:[function(_dereq_,module,exports){
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

var _globalDocument = _dereq_('global/document');

var _globalDocument2 = _interopRequireDefault(_globalDocument);

var createStyleElement = function createStyleElement(className) {
  var style = _globalDocument2['default'].createElement('style');
  style.className = className;

  return style;
};

exports.createStyleElement = createStyleElement;
var setTextContent = function setTextContent(el, content) {
  if (el.styleSheet) {
    el.styleSheet.cssText = content;
  } else {
    el.textContent = content;
  }
};
exports.setTextContent = setTextContent;

},{"global/document":1}],136:[function(_dereq_,module,exports){
'use strict';

exports.__esModule = true;
exports.createTimeRanges = createTimeRanges;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

var _logJs = _dereq_('./log.js');

var _logJs2 = _interopRequireDefault(_logJs);

/**
 * @file time-ranges.js
 *
 * Should create a fake TimeRange object
 * Mimics an HTML5 time range instance, which has functions that
 * return the start and end times for a range
 * TimeRanges are returned by the buffered() method
 *
 * @param  {(Number|Array)} Start of a single range or an array of ranges
 * @param  {Number} End of a single range
 * @private
 * @method createTimeRanges
 */

function createTimeRanges(start, end) {
  if (Array.isArray(start)) {
    return createTimeRangesObj(start);
  } else if (start === undefined || end === undefined) {
    return createTimeRangesObj();
  }
  return createTimeRangesObj([[start, end]]);
}

exports.createTimeRange = createTimeRanges;

function createTimeRangesObj(ranges) {
  if (ranges === undefined || ranges.length === 0) {
    return {
      length: 0,
      start: function start() {
        throw new Error('This TimeRanges object is empty');
      },
      end: function end() {
        throw new Error('This TimeRanges object is empty');
      }
    };
  }
  return {
    length: ranges.length,
    start: getRange.bind(null, 'start', 0, ranges),
    end: getRange.bind(null, 'end', 1, ranges)
  };
}

function getRange(fnName, valueIndex, ranges, rangeIndex) {
  if (rangeIndex === undefined) {
    _logJs2['default'].warn('DEPRECATED: Function \'' + fnName + '\' on \'TimeRanges\' called without an index argument.');
    rangeIndex = 0;
  }
  rangeCheck(fnName, rangeIndex, ranges.length - 1);
  return ranges[rangeIndex][valueIndex];
}

function rangeCheck(fnName, index, maxIndex) {
  if (index < 0 || index > maxIndex) {
    throw new Error('Failed to execute \'' + fnName + '\' on \'TimeRanges\': The index provided (' + index + ') is greater than or equal to the maximum bound (' + maxIndex + ').');
  }
}

},{"./log.js":133}],137:[function(_dereq_,module,exports){
/**
 * @file to-title-case.js
 *
 * Uppercase the first letter of a string
 *
 * @param  {String} string String to be uppercased
 * @return {String}
 * @private
 * @method toTitleCase
 */
"use strict";

exports.__esModule = true;
function toTitleCase(string) {
  return string.charAt(0).toUpperCase() + string.slice(1);
}

exports["default"] = toTitleCase;
module.exports = exports["default"];

},{}],138:[function(_dereq_,module,exports){
/**
 * @file url.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

var _globalDocument = _dereq_('global/document');

var _globalDocument2 = _interopRequireDefault(_globalDocument);

var _globalWindow = _dereq_('global/window');

var _globalWindow2 = _interopRequireDefault(_globalWindow);

/**
 * Resolve and parse the elements of a URL
 *
 * @param  {String} url The url to parse
 * @return {Object}     An object of url details
 * @method parseUrl
 */
var parseUrl = function parseUrl(url) {
  var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host'];

  // add the url to an anchor and let the browser parse the URL
  var a = _globalDocument2['default'].createElement('a');
  a.href = url;

  // IE8 (and 9?) Fix
  // ie8 doesn't parse the URL correctly until the anchor is actually
  // added to the body, and an innerHTML is needed to trigger the parsing
  var addToBody = a.host === '' && a.protocol !== 'file:';
  var div = undefined;
  if (addToBody) {
    div = _globalDocument2['default'].createElement('div');
    div.innerHTML = '<a href="' + url + '"></a>';
    a = div.firstChild;
    // prevent the div from affecting layout
    div.setAttribute('style', 'display:none; position:absolute;');
    _globalDocument2['default'].body.appendChild(div);
  }

  // Copy the specific URL properties to a new object
  // This is also needed for IE8 because the anchor loses its
  // properties when it's removed from the dom
  var details = {};
  for (var i = 0; i < props.length; i++) {
    details[props[i]] = a[props[i]];
  }

  // IE9 adds the port to the host property unlike everyone else. If
  // a port identifier is added for standard ports, strip it.
  if (details.protocol === 'http:') {
    details.host = details.host.replace(/:80$/, '');
  }
  if (details.protocol === 'https:') {
    details.host = details.host.replace(/:443$/, '');
  }

  if (addToBody) {
    _globalDocument2['default'].body.removeChild(div);
  }

  return details;
};

exports.parseUrl = parseUrl;
/**
 * Get absolute version of relative URL. Used to tell flash correct URL.
 * http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
 *
 * @param  {String} url URL to make absolute
 * @return {String}     Absolute URL
 * @private
 * @method getAbsoluteURL
 */
var getAbsoluteURL = function getAbsoluteURL(url) {
  // Check if absolute URL
  if (!url.match(/^https?:\/\//)) {
    // Convert to absolute URL. Flash hosted off-site needs an absolute URL.
    var div = _globalDocument2['default'].createElement('div');
    div.innerHTML = '<a href="' + url + '">x</a>';
    url = div.firstChild.href;
  }

  return url;
};

exports.getAbsoluteURL = getAbsoluteURL;
/**
 * Returns the extension of the passed file name. It will return an empty string if you pass an invalid path
 *
 * @param {String}    path    The fileName path like '/path/to/file.mp4'
 * @returns {String}          The extension in lower case or an empty string if no extension could be found.
 * @method getFileExtension
 */
var getFileExtension = function getFileExtension(path) {
  if (typeof path === 'string') {
    var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/i;
    var pathParts = splitPathRe.exec(path);

    if (pathParts) {
      return pathParts.pop().toLowerCase();
    }
  }

  return '';
};

exports.getFileExtension = getFileExtension;
/**
 * Returns whether the url passed is a cross domain request or not.
 *
 * @param {String} url The url to check
 * @return {Boolean}   Whether it is a cross domain request or not
 * @method isCrossOrigin
 */
var isCrossOrigin = function isCrossOrigin(url) {
  var winLoc = _globalWindow2['default'].location;
  var urlInfo = parseUrl(url);

  // IE8 protocol relative urls will return ':' for protocol
  var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol;

  // Check if url is for another domain/origin
  // IE8 doesn't know location.origin, so we won't rely on it here
  var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host;

  return crossOrigin;
};
exports.isCrossOrigin = isCrossOrigin;

},{"global/document":1,"global/window":2}],139:[function(_dereq_,module,exports){
/**
 * @file video.js
 */
'use strict';

exports.__esModule = true;

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

var _globalDocument = _dereq_('global/document');

var _globalDocument2 = _interopRequireDefault(_globalDocument);

var _setup = _dereq_('./setup');

var setup = _interopRequireWildcard(_setup);

var _utilsStylesheetJs = _dereq_('./utils/stylesheet.js');

var stylesheet = _interopRequireWildcard(_utilsStylesheetJs);

var _component = _dereq_('./component');

var _component2 = _interopRequireDefault(_component);

var _eventTarget = _dereq_('./event-target');

var _eventTarget2 = _interopRequireDefault(_eventTarget);

var _utilsEventsJs = _dereq_('./utils/events.js');

var Events = _interopRequireWildcard(_utilsEventsJs);

var _player = _dereq_('./player');

var _player2 = _interopRequireDefault(_player);

var _pluginsJs = _dereq_('./plugins.js');

var _pluginsJs2 = _interopRequireDefault(_pluginsJs);

var _srcJsUtilsMergeOptionsJs = _dereq_('../../src/js/utils/merge-options.js');

var _srcJsUtilsMergeOptionsJs2 = _interopRequireDefault(_srcJsUtilsMergeOptionsJs);

var _utilsFnJs = _dereq_('./utils/fn.js');

var Fn = _interopRequireWildcard(_utilsFnJs);

var _tracksTextTrackJs = _dereq_('./tracks/text-track.js');

var _tracksTextTrackJs2 = _interopRequireDefault(_tracksTextTrackJs);

var _objectAssign = _dereq_('object.assign');

var _objectAssign2 = _interopRequireDefault(_objectAssign);

var _utilsTimeRangesJs = _dereq_('./utils/time-ranges.js');

var _utilsFormatTimeJs = _dereq_('./utils/format-time.js');

var _utilsFormatTimeJs2 = _interopRequireDefault(_utilsFormatTimeJs);

var _utilsLogJs = _dereq_('./utils/log.js');

var _utilsLogJs2 = _interopRequireDefault(_utilsLogJs);

var _utilsDomJs = _dereq_('./utils/dom.js');

var Dom = _interopRequireWildcard(_utilsDomJs);

var _utilsBrowserJs = _dereq_('./utils/browser.js');

var browser = _interopRequireWildcard(_utilsBrowserJs);

var _utilsUrlJs = _dereq_('./utils/url.js');

var Url = _interopRequireWildcard(_utilsUrlJs);

var _extendJs = _dereq_('./extend.js');

var _extendJs2 = _interopRequireDefault(_extendJs);

var _lodashCompatObjectMerge = _dereq_('lodash-compat/object/merge');

var _lodashCompatObjectMerge2 = _interopRequireDefault(_lodashCompatObjectMerge);

var _utilsCreateDeprecationProxyJs = _dereq_('./utils/create-deprecation-proxy.js');

var _utilsCreateDeprecationProxyJs2 = _interopRequireDefault(_utilsCreateDeprecationProxyJs);

var _xhr = _dereq_('xhr');

var _xhr2 = _interopRequireDefault(_xhr);

// Include the built-in techs

var _techTechJs = _dereq_('./tech/tech.js');

var _techTechJs2 = _interopRequireDefault(_techTechJs);

var _techHtml5Js = _dereq_('./tech/html5.js');

var _techHtml5Js2 = _interopRequireDefault(_techHtml5Js);

var _techFlashJs = _dereq_('./tech/flash.js');

var _techFlashJs2 = _interopRequireDefault(_techFlashJs);

// HTML5 Element Shim for IE8
if (typeof HTMLVideoElement === 'undefined') {
  _globalDocument2['default'].createElement('video');
  _globalDocument2['default'].createElement('audio');
  _globalDocument2['default'].createElement('track');
}

/**
 * Doubles as the main function for users to create a player instance and also
 * the main library object.
 * The `videojs` function can be used to initialize or retrieve a player.
 * ```js
 *     var myPlayer = videojs('my_video_id');
 * ```
 *
 * @param  {String|Element} id      Video element or video element ID
 * @param  {Object=} options        Optional options object for config/settings
 * @param  {Function=} ready        Optional ready callback
 * @return {Player}                 A player instance
 * @mixes videojs
 * @method videojs
 */
var videojs = function videojs(id, options, ready) {
  var tag = undefined; // Element of ID

  // Allow for element or ID to be passed in
  // String ID
  if (typeof id === 'string') {

    // Adjust for jQuery ID syntax
    if (id.indexOf('#') === 0) {
      id = id.slice(1);
    }

    // If a player instance has already been created for this ID return it.
    if (videojs.getPlayers()[id]) {

      // If options or ready funtion are passed, warn
      if (options) {
        _utilsLogJs2['default'].warn('Player "' + id + '" is already initialised. Options will not be applied.');
      }

      if (ready) {
        videojs.getPlayers()[id].ready(ready);
      }

      return videojs.getPlayers()[id];

      // Otherwise get element for ID
    } else {
        tag = Dom.getEl(id);
      }

    // ID is a media element
  } else {
      tag = id;
    }

  // Check for a useable element
  if (!tag || !tag.nodeName) {
    // re: nodeName, could be a box div also
    throw new TypeError('The element or ID supplied is not valid. (videojs)'); // Returns
  }

  // Element may have a player attr referring to an already created player instance.
  // If not, set up a new player and return the instance.
  return tag['player'] || _player2['default'].players[tag.playerId] || new _player2['default'](tag, options, ready);
};

// Add default styles
var style = Dom.$('.vjs-styles-defaults');
if (!style) {
  style = stylesheet.createStyleElement('vjs-styles-defaults');
  var head = Dom.$('head');
  head.insertBefore(style, head.firstChild);
  stylesheet.setTextContent(style, '\n    .video-js {\n      width: 300px;\n      height: 150px;\n    }\n\n    .vjs-fluid {\n      padding-top: 56.25%\n    }\n  ');
}

// Run Auto-load players
// You have to wait at least once in case this script is loaded after your video in the DOM (weird behavior only with minified version)
setup.autoSetupTimeout(1, videojs);

/*
 * Current software version (semver)
 *
 * @type {String}
 */
videojs.VERSION = '5.5.3';

/**
 * The global options object. These are the settings that take effect
 * if no overrides are specified when the player is created.
 *
 * ```js
 *     videojs.options.autoplay = true
 *     // -> all players will autoplay by default
 * ```
 *
 * @type {Object}
 */
videojs.options = _player2['default'].prototype.options_;

/**
 * Get an object with the currently created players, keyed by player ID
 *
 * @return {Object} The created players
 * @mixes videojs
 * @method getPlayers
 */
videojs.getPlayers = function () {
  return _player2['default'].players;
};

/**
 * For backward compatibility, expose players object.
 *
 * @deprecated
 * @memberOf videojs
 * @property {Object|Proxy} players
 */
videojs.players = _utilsCreateDeprecationProxyJs2['default'](_player2['default'].players, {
  get: 'Access to videojs.players is deprecated; use videojs.getPlayers instead',
  set: 'Modification of videojs.players is deprecated'
});

/**
 * Get a component class object by name
 * ```js
 *     var VjsButton = videojs.getComponent('Button');
 *     // Create a new instance of the component
 *     var myButton = new VjsButton(myPlayer);
 * ```
 *
 * @return {Component} Component identified by name
 * @mixes videojs
 * @method getComponent
 */
videojs.getComponent = _component2['default'].getComponent;

/**
 * Register a component so it can referred to by name
 * Used when adding to other
 * components, either through addChild
 * `component.addChild('myComponent')`
 * or through default children options
 * `{ children: ['myComponent'] }`.
 * ```js
 *     // Get a component to subclass
 *     var VjsButton = videojs.getComponent('Button');
 *     // Subclass the component (see 'extend' doc for more info)
 *     var MySpecialButton = videojs.extend(VjsButton, {});
 *     // Register the new component
 *     VjsButton.registerComponent('MySepcialButton', MySepcialButton);
 *     // (optionally) add the new component as a default player child
 *     myPlayer.addChild('MySepcialButton');
 * ```
 * NOTE: You could also just initialize the component before adding.
 * `component.addChild(new MyComponent());`
 *
 * @param {String} The class name of the component
 * @param {Component} The component class
 * @return {Component} The newly registered component
 * @mixes videojs
 * @method registerComponent
 */
videojs.registerComponent = function (name, comp) {
  if (_techTechJs2['default'].isTech(comp)) {
    _utilsLogJs2['default'].warn('The ' + name + ' tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)');
  }

  _component2['default'].registerComponent.call(_component2['default'], name, comp);
};

/**
 * Get a Tech class object by name
 * ```js
 *     var Html5 = videojs.getTech('Html5');
 *     // Create a new instance of the component
 *     var html5 = new Html5(options);
 * ```
 *
 * @return {Tech} Tech identified by name
 * @mixes videojs
 * @method getComponent
 */
videojs.getTech = _techTechJs2['default'].getTech;

/**
 * Register a Tech so it can referred to by name.
 * This is used in the tech order for the player.
 *
 * ```js
 *     // get the Html5 Tech
 *     var Html5 = videojs.getTech('Html5');
 *     var MyTech = videojs.extend(Html5, {});
 *     // Register the new Tech
 *     VjsButton.registerTech('Tech', MyTech);
 *     var player = videojs('myplayer', {
 *       techOrder: ['myTech', 'html5']
 *     });
 * ```
 *
 * @param {String} The class name of the tech
 * @param {Tech} The tech class
 * @return {Tech} The newly registered Tech
 * @mixes videojs
 * @method registerTech
 */
videojs.registerTech = _techTechJs2['default'].registerTech;

/**
 * A suite of browser and device tests
 *
 * @type {Object}
 * @private
 */
videojs.browser = browser;

/**
 * Whether or not the browser supports touch events. Included for backward
 * compatibility with 4.x, but deprecated. Use `videojs.browser.TOUCH_ENABLED`
 * instead going forward.
 *
 * @deprecated
 * @type {Boolean}
 */
videojs.TOUCH_ENABLED = browser.TOUCH_ENABLED;

/**
 * Subclass an existing class
 * Mimics ES6 subclassing with the `extend` keyword
 * ```js
 *     // Create a basic javascript 'class'
 *     function MyClass(name){
 *       // Set a property at initialization
 *       this.myName = name;
 *     }
 *     // Create an instance method
 *     MyClass.prototype.sayMyName = function(){
 *       alert(this.myName);
 *     };
 *     // Subclass the exisitng class and change the name
 *     // when initializing
 *     var MySubClass = videojs.extend(MyClass, {
 *       constructor: function(name) {
 *         // Call the super class constructor for the subclass
 *         MyClass.call(this, name)
 *       }
 *     });
 *     // Create an instance of the new sub class
 *     var myInstance = new MySubClass('John');
 *     myInstance.sayMyName(); // -> should alert "John"
 * ```
 *
 * @param {Function} The Class to subclass
 * @param {Object} An object including instace methods for the new class
 *                   Optionally including a `constructor` function
 * @return {Function} The newly created subclass
 * @mixes videojs
 * @method extend
 */
videojs.extend = _extendJs2['default'];

/**
 * Merge two options objects recursively
 * Performs a deep merge like lodash.merge but **only merges plain objects**
 * (not arrays, elements, anything else)
 * Other values will be copied directly from the second object.
 * ```js
 *     var defaultOptions = {
 *       foo: true,
 *       bar: {
 *         a: true,
 *         b: [1,2,3]
 *       }
 *     };
 *     var newOptions = {
 *       foo: false,
 *       bar: {
 *         b: [4,5,6]
 *       }
 *     };
 *     var result = videojs.mergeOptions(defaultOptions, newOptions);
 *     // result.foo = false;
 *     // result.bar.a = true;
 *     // result.bar.b = [4,5,6];
 * ```
 *
 * @param {Object} defaults  The options object whose values will be overriden
 * @param {Object} overrides The options object with values to override the first
 * @param {Object} etc       Any number of additional options objects
 *
 * @return {Object} a new object with the merged values
 * @mixes videojs
 * @method mergeOptions
 */
videojs.mergeOptions = _srcJsUtilsMergeOptionsJs2['default'];

/**
 * Change the context (this) of a function
 *
 *     videojs.bind(newContext, function(){
 *       this === newContext
 *     });
 *
 * NOTE: as of v5.0 we require an ES5 shim, so you should use the native
 * `function(){}.bind(newContext);` instead of this.
 *
 * @param  {*}        context The object to bind as scope
 * @param  {Function} fn      The function to be bound to a scope
 * @param  {Number=}  uid     An optional unique ID for the function to be set
 * @return {Function}
 */
videojs.bind = Fn.bind;

/**
 * Create a Video.js player plugin
 * Plugins are only initialized when options for the plugin are included
 * in the player options, or the plugin function on the player instance is
 * called.
 * **See the plugin guide in the docs for a more detailed example**
 * ```js
 *     // Make a plugin that alerts when the player plays
 *     videojs.plugin('myPlugin', function(myPluginOptions) {
 *       myPluginOptions = myPluginOptions || {};
 *
 *       var player = this;
 *       var alertText = myPluginOptions.text || 'Player is playing!'
 *
 *       player.on('play', function(){
 *         alert(alertText);
 *       });
 *     });
 *     // USAGE EXAMPLES
 *     // EXAMPLE 1: New player with plugin options, call plugin immediately
 *     var player1 = videojs('idOne', {
 *       myPlugin: {
 *         text: 'Custom text!'
 *       }
 *     });
 *     // Click play
 *     // --> Should alert 'Custom text!'
 *     // EXAMPLE 3: New player, initialize plugin later
 *     var player3 = videojs('idThree');
 *     // Click play
 *     // --> NO ALERT
 *     // Click pause
 *     // Initialize plugin using the plugin function on the player instance
 *     player3.myPlugin({
 *       text: 'Plugin added later!'
 *     });
 *     // Click play
 *     // --> Should alert 'Plugin added later!'
 * ```
 *
 * @param {String} name The plugin name
 * @param {Function} fn The plugin function that will be called with options
 * @mixes videojs
 * @method plugin
 */
videojs.plugin = _pluginsJs2['default'];

/**
 * Adding languages so that they're available to all players.
 * ```js
 *     videojs.addLanguage('es', { 'Hello': 'Hola' });
 * ```
 *
 * @param  {String} code The language code or dictionary property
 * @param  {Object} data The data values to be translated
 * @return {Object} The resulting language dictionary object
 * @mixes videojs
 * @method addLanguage
 */
videojs.addLanguage = function (code, data) {
  var _merge;

  code = ('' + code).toLowerCase();
  return _lodashCompatObjectMerge2['default'](videojs.options.languages, (_merge = {}, _merge[code] = data, _merge))[code];
};

/**
 * Log debug messages.
 *
 * @param {...Object} messages One or more messages to log
 */
videojs.log = _utilsLogJs2['default'];

/**
 * Creates an emulated TimeRange object.
 *
 * @param  {Number|Array} start Start time in seconds or an array of ranges
 * @param  {Number} end   End time in seconds
 * @return {Object}       Fake TimeRange object
 * @method createTimeRange
 */
videojs.createTimeRange = videojs.createTimeRanges = _utilsTimeRangesJs.createTimeRanges;

/**
 * Format seconds as a time string, H:MM:SS or M:SS
 * Supplying a guide (in seconds) will force a number of leading zeros
 * to cover the length of the guide
 *
 * @param  {Number} seconds Number of seconds to be turned into a string
 * @param  {Number} guide   Number (in seconds) to model the string after
 * @return {String}         Time formatted as H:MM:SS or M:SS
 * @method formatTime
 */
videojs.formatTime = _utilsFormatTimeJs2['default'];

/**
 * Resolve and parse the elements of a URL
 *
 * @param  {String} url The url to parse
 * @return {Object}     An object of url details
 * @method parseUrl
 */
videojs.parseUrl = Url.parseUrl;

/**
 * Returns whether the url passed is a cross domain request or not.
 *
 * @param {String} url The url to check
 * @return {Boolean}   Whether it is a cross domain request or not
 * @method isCrossOrigin
 */
videojs.isCrossOrigin = Url.isCrossOrigin;

/**
 * Event target class.
 *
 * @type {Function}
 */
videojs.EventTarget = _eventTarget2['default'];

/**
 * Add an event listener to element
 * It stores the handler function in a separate cache object
 * and adds a generic handler to the element's event,
 * along with a unique id (guid) to the element.
 *
 * @param  {Element|Object}   elem Element or object to bind listeners to
 * @param  {String|Array}   type Type of event to bind to.
 * @param  {Function} fn   Event listener.
 * @method on
 */
videojs.on = Events.on;

/**
 * Trigger a listener only once for an event
 *
 * @param  {Element|Object}   elem Element or object to
 * @param  {String|Array}   type Name/type of event
 * @param  {Function} fn Event handler function
 * @method one
 */
videojs.one = Events.one;

/**
 * Removes event listeners from an element
 *
 * @param  {Element|Object}   elem Object to remove listeners from
 * @param  {String|Array=}   type Type of listener to remove. Don't include to remove all events from element.
 * @param  {Function} fn   Specific listener to remove. Don't include to remove listeners for an event type.
 * @method off
 */
videojs.off = Events.off;

/**
 * Trigger an event for an element
 *
 * @param  {Element|Object}      elem  Element to trigger an event on
 * @param  {Event|Object|String} event A string (the type) or an event object with a type attribute
 * @param  {Object} [hash] data hash to pass along with the event
 * @return {Boolean=} Returned only if default was prevented
 * @method trigger
 */
videojs.trigger = Events.trigger;

/**
 * A cross-browser XMLHttpRequest wrapper. Here's a simple example:
 *
 *     videojs.xhr({
 *       body: someJSONString,
 *       uri: "/foo",
 *       headers: {
 *         "Content-Type": "application/json"
 *       }
 *     }, function (err, resp, body) {
 *       // check resp.statusCode
 *     });
 *
 * Check out the [full
 * documentation](https://github.com/Raynos/xhr/blob/v2.1.0/README.md)
 * for more options.
 *
 * @param {Object} options settings for the request.
 * @return {XMLHttpRequest|XDomainRequest} the request object.
 * @see https://github.com/Raynos/xhr
 */
videojs.xhr = _xhr2['default'];

/**
 * TextTrack class
 *
 * @type {Function}
 */
videojs.TextTrack = _tracksTextTrackJs2['default'];

/**
 * Determines, via duck typing, whether or not a value is a DOM element.
 *
 * @method isEl
 * @param  {Mixed} value
 * @return {Boolean}
 */
videojs.isEl = Dom.isEl;

/**
 * Determines, via duck typing, whether or not a value is a text node.
 *
 * @method isTextNode
 * @param  {Mixed} value
 * @return {Boolean}
 */
videojs.isTextNode = Dom.isTextNode;

/**
 * Creates an element and applies properties.
 *
 * @method createEl
 * @param  {String} [tagName='div'] Name of tag to be created.
 * @param  {Object} [properties={}] Element properties to be applied.
 * @param  {Object} [attributes={}] Element attributes to be applied.
 * @return {Element}
 */
videojs.createEl = Dom.createEl;

/**
 * Check if an element has a CSS class
 *
 * @method hasClass
 * @param {Element} element Element to check
 * @param {String} classToCheck Classname to check
 */
videojs.hasClass = Dom.hasElClass;

/**
 * Add a CSS class name to an element
 *
 * @method addClass
 * @param {Element} element    Element to add class name to
 * @param {String} classToAdd Classname to add
 */
videojs.addClass = Dom.addElClass;

/**
 * Remove a CSS class name from an element
 *
 * @method removeClass
 * @param {Element} element    Element to remove from class name
 * @param {String} classToRemove Classname to remove
 */
videojs.removeClass = Dom.removeElClass;

/**
 * Adds or removes a CSS class name on an element depending on an optional
 * condition or the presence/absence of the class name.
 *
 * @method toggleElClass
 * @param  {Element} element
 * @param  {String} classToToggle
 * @param  {Boolean|Function} [predicate]
 *         Can be a function that returns a Boolean. If `true`, the class
 *         will be added; if `false`, the class will be removed. If not
 *         given, the class will be added if not present and vice versa.
 */
videojs.toggleClass = Dom.toggleElClass;

/**
 * Apply attributes to an HTML element.
 *
 * @method setAttributes
 * @param  {Element} el         Target element.
 * @param  {Object=} attributes Element attributes to be applied.
 */
videojs.setAttributes = Dom.setElAttributes;

/**
 * Get an element's attribute values, as defined on the HTML tag
 * Attributes are not the same as properties. They're defined on the tag
 * or with setAttribute (which shouldn't be used with HTML)
 * This will return true or false for boolean attributes.
 *
 * @method getAttributes
 * @param  {Element} tag Element from which to get tag attributes
 * @return {Object}
 */
videojs.getAttributes = Dom.getElAttributes;

/**
 * Empties the contents of an element.
 *
 * @method emptyEl
 * @param  {Element} el
 * @return {Element}
 */
videojs.emptyEl = Dom.emptyEl;

/**
 * Normalizes and appends content to an element.
 *
 * The content for an element can be passed in multiple types and
 * combinations, whose behavior is as follows:
 *
 * - String
 *   Normalized into a text node.
 *
 * - Element, TextNode
 *   Passed through.
 *
 * - Array
 *   A one-dimensional array of strings, elements, nodes, or functions (which
 *   return single strings, elements, or nodes).
 *
 * - Function
 *   If the sole argument, is expected to produce a string, element,
 *   node, or array.
 *
 * @method appendContent
 * @param  {Element} el
 * @param  {String|Element|TextNode|Array|Function} content
 * @return {Element}
 */
videojs.appendContent = Dom.appendContent;

/**
 * Normalizes and inserts content into an element; this is identical to
 * `appendContent()`, except it empties the element first.
 *
 * The content for an element can be passed in multiple types and
 * combinations, whose behavior is as follows:
 *
 * - String
 *   Normalized into a text node.
 *
 * - Element, TextNode
 *   Passed through.
 *
 * - Array
 *   A one-dimensional array of strings, elements, nodes, or functions (which
 *   return single strings, elements, or nodes).
 *
 * - Function
 *   If the sole argument, is expected to produce a string, element,
 *   node, or array.
 *
 * @method insertContent
 * @param  {Element} el
 * @param  {String|Element|TextNode|Array|Function} content
 * @return {Element}
 */
videojs.insertContent = Dom.insertContent;

/*
 * Custom Universal Module Definition (UMD)
 *
 * Video.js will never be a non-browser lib so we can simplify UMD a bunch and
 * still support requirejs and browserify. This also needs to be closure
 * compiler compatible, so string keys are used.
 */
if (typeof define === 'function' && define['amd']) {
  define('videojs', [], function () {
    return videojs;
  });

  // checking that module is an object too because of umdjs/umd#35
} else if (typeof exports === 'object' && typeof module === 'object') {
    module['exports'] = videojs;
  }

exports['default'] = videojs;
module.exports = exports['default'];

},{"../../src/js/utils/merge-options.js":134,"./component":65,"./event-target":97,"./extend.js":98,"./player":106,"./plugins.js":107,"./setup":109,"./tech/flash.js":112,"./tech/html5.js":113,"./tech/tech.js":115,"./tracks/text-track.js":124,"./utils/browser.js":125,"./utils/create-deprecation-proxy.js":127,"./utils/dom.js":128,"./utils/events.js":129,"./utils/fn.js":130,"./utils/format-time.js":131,"./utils/log.js":133,"./utils/stylesheet.js":135,"./utils/time-ranges.js":136,"./utils/url.js":138,"global/document":1,"lodash-compat/object/merge":40,"object.assign":45,"xhr":55}]},{},[139])(139)
});


//# sourceMappingURL=video.js.map
/* vtt.js - v0.12.1 (https://github.com/mozilla/vtt.js) built on 08-07-2015 */

(function(root) {
  var vttjs = root.vttjs = {};
  var cueShim = vttjs.VTTCue;
  var regionShim = vttjs.VTTRegion;
  var oldVTTCue = root.VTTCue;
  var oldVTTRegion = root.VTTRegion;

  vttjs.shim = function() {
    vttjs.VTTCue = cueShim;
    vttjs.VTTRegion = regionShim;
  };

  vttjs.restore = function() {
    vttjs.VTTCue = oldVTTCue;
    vttjs.VTTRegion = oldVTTRegion;
  };
}(this));

/**
 * Copyright 2013 vtt.js Contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

(function(root, vttjs) {

  var autoKeyword = "auto";
  var directionSetting = {
    "": true,
    "lr": true,
    "rl": true
  };
  var alignSetting = {
    "start": true,
    "middle": true,
    "end": true,
    "left": true,
    "right": true
  };

  function findDirectionSetting(value) {
    if (typeof value !== "string") {
      return false;
    }
    var dir = directionSetting[value.toLowerCase()];
    return dir ? value.toLowerCase() : false;
  }

  function findAlignSetting(value) {
    if (typeof value !== "string") {
      return false;
    }
    var align = alignSetting[value.toLowerCase()];
    return align ? value.toLowerCase() : false;
  }

  function extend(obj) {
    var i = 1;
    for (; i < arguments.length; i++) {
      var cobj = arguments[i];
      for (var p in cobj) {
        obj[p] = cobj[p];
      }
    }

    return obj;
  }

  function VTTCue(startTime, endTime, text) {
    var cue = this;
    var isIE8 = (/MSIE\s8\.0/).test(navigator.userAgent);
    var baseObj = {};

    if (isIE8) {
      cue = document.createElement('custom');
    } else {
      baseObj.enumerable = true;
    }

    /**
     * Shim implementation specific properties. These properties are not in
     * the spec.
     */

    // Lets us know when the VTTCue's data has changed in such a way that we need
    // to recompute its display state. This lets us compute its display state
    // lazily.
    cue.hasBeenReset = false;

    /**
     * VTTCue and TextTrackCue properties
     * http://dev.w3.org/html5/webvtt/#vttcue-interface
     */

    var _id = "";
    var _pauseOnExit = false;
    var _startTime = startTime;
    var _endTime = endTime;
    var _text = text;
    var _region = null;
    var _vertical = "";
    var _snapToLines = true;
    var _line = "auto";
    var _lineAlign = "start";
    var _position = 50;
    var _positionAlign = "middle";
    var _size = 50;
    var _align = "middle";

    Object.defineProperty(cue,
      "id", extend({}, baseObj, {
        get: function() {
          return _id;
        },
        set: function(value) {
          _id = "" + value;
        }
      }));

    Object.defineProperty(cue,
      "pauseOnExit", extend({}, baseObj, {
        get: function() {
          return _pauseOnExit;
        },
        set: function(value) {
          _pauseOnExit = !!value;
        }
      }));

    Object.defineProperty(cue,
      "startTime", extend({}, baseObj, {
        get: function() {
          return _startTime;
        },
        set: function(value) {
          if (typeof value !== "number") {
            throw new TypeError("Start time must be set to a number.");
          }
          _startTime = value;
          this.hasBeenReset = true;
        }
      }));

    Object.defineProperty(cue,
      "endTime", extend({}, baseObj, {
        get: function() {
          return _endTime;
        },
        set: function(value) {
          if (typeof value !== "number") {
            throw new TypeError("End time must be set to a number.");
          }
          _endTime = value;
          this.hasBeenReset = true;
        }
      }));

    Object.defineProperty(cue,
      "text", extend({}, baseObj, {
        get: function() {
          return _text;
        },
        set: function(value) {
          _text = "" + value;
          this.hasBeenReset = true;
        }
      }));

    Object.defineProperty(cue,
      "region", extend({}, baseObj, {
        get: function() {
          return _region;
        },
        set: function(value) {
          _region = value;
          this.hasBeenReset = true;
        }
      }));

    Object.defineProperty(cue,
      "vertical", extend({}, baseObj, {
        get: function() {
          return _vertical;
        },
        set: function(value) {
          var setting = findDirectionSetting(value);
          // Have to check for false because the setting an be an empty string.
          if (setting === false) {
            throw new SyntaxError("An invalid or illegal string was specified.");
          }
          _vertical = setting;
          this.hasBeenReset = true;
        }
      }));

    Object.defineProperty(cue,
      "snapToLines", extend({}, baseObj, {
        get: function() {
          return _snapToLines;
        },
        set: function(value) {
          _snapToLines = !!value;
          this.hasBeenReset = true;
        }
      }));

    Object.defineProperty(cue,
      "line", extend({}, baseObj, {
        get: function() {
          return _line;
        },
        set: function(value) {
          if (typeof value !== "number" && value !== autoKeyword) {
            throw new SyntaxError("An invalid number or illegal string was specified.");
          }
          _line = value;
          this.hasBeenReset = true;
        }
      }));

    Object.defineProperty(cue,
      "lineAlign", extend({}, baseObj, {
        get: function() {
          return _lineAlign;
        },
        set: function(value) {
          var setting = findAlignSetting(value);
          if (!setting) {
            throw new SyntaxError("An invalid or illegal string was specified.");
          }
          _lineAlign = setting;
          this.hasBeenReset = true;
        }
      }));

    Object.defineProperty(cue,
      "position", extend({}, baseObj, {
        get: function() {
          return _position;
        },
        set: function(value) {
          if (value < 0 || value > 100) {
            throw new Error("Position must be between 0 and 100.");
          }
          _position = value;
          this.hasBeenReset = true;
        }
      }));

    Object.defineProperty(cue,
      "positionAlign", extend({}, baseObj, {
        get: function() {
          return _positionAlign;
        },
        set: function(value) {
          var setting = findAlignSetting(value);
          if (!setting) {
            throw new SyntaxError("An invalid or illegal string was specified.");
          }
          _positionAlign = setting;
          this.hasBeenReset = true;
        }
      }));

    Object.defineProperty(cue,
      "size", extend({}, baseObj, {
        get: function() {
          return _size;
        },
        set: function(value) {
          if (value < 0 || value > 100) {
            throw new Error("Size must be between 0 and 100.");
          }
          _size = value;
          this.hasBeenReset = true;
        }
      }));

    Object.defineProperty(cue,
      "align", extend({}, baseObj, {
        get: function() {
          return _align;
        },
        set: function(value) {
          var setting = findAlignSetting(value);
          if (!setting) {
            throw new SyntaxError("An invalid or illegal string was specified.");
          }
          _align = setting;
          this.hasBeenReset = true;
        }
      }));

    /**
     * Other <track> spec defined properties
     */

    // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state
    cue.displayState = undefined;

    if (isIE8) {
      return cue;
    }
  }

  /**
   * VTTCue methods
   */

  VTTCue.prototype.getCueAsHTML = function() {
    // Assume WebVTT.convertCueToDOMTree is on the global.
    return WebVTT.convertCueToDOMTree(window, this.text);
  };

  root.VTTCue = root.VTTCue || VTTCue;
  vttjs.VTTCue = VTTCue;
}(this, (this.vttjs || {})));

/**
 * Copyright 2013 vtt.js Contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

(function(root, vttjs) {

  var scrollSetting = {
    "": true,
    "up": true
  };

  function findScrollSetting(value) {
    if (typeof value !== "string") {
      return false;
    }
    var scroll = scrollSetting[value.toLowerCase()];
    return scroll ? value.toLowerCase() : false;
  }

  function isValidPercentValue(value) {
    return typeof value === "number" && (value >= 0 && value <= 100);
  }

  // VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface
  function VTTRegion() {
    var _width = 100;
    var _lines = 3;
    var _regionAnchorX = 0;
    var _regionAnchorY = 100;
    var _viewportAnchorX = 0;
    var _viewportAnchorY = 100;
    var _scroll = "";

    Object.defineProperties(this, {
      "width": {
        enumerable: true,
        get: function() {
          return _width;
        },
        set: function(value) {
          if (!isValidPercentValue(value)) {
            throw new Error("Width must be between 0 and 100.");
          }
          _width = value;
        }
      },
      "lines": {
        enumerable: true,
        get: function() {
          return _lines;
        },
        set: function(value) {
          if (typeof value !== "number") {
            throw new TypeError("Lines must be set to a number.");
          }
          _lines = value;
        }
      },
      "regionAnchorY": {
        enumerable: true,
        get: function() {
          return _regionAnchorY;
        },
        set: function(value) {
          if (!isValidPercentValue(value)) {
            throw new Error("RegionAnchorX must be between 0 and 100.");
          }
          _regionAnchorY = value;
        }
      },
      "regionAnchorX": {
        enumerable: true,
        get: function() {
          return _regionAnchorX;
        },
        set: function(value) {
          if(!isValidPercentValue(value)) {
            throw new Error("RegionAnchorY must be between 0 and 100.");
          }
          _regionAnchorX = value;
        }
      },
      "viewportAnchorY": {
        enumerable: true,
        get: function() {
          return _viewportAnchorY;
        },
        set: function(value) {
          if (!isValidPercentValue(value)) {
            throw new Error("ViewportAnchorY must be between 0 and 100.");
          }
          _viewportAnchorY = value;
        }
      },
      "viewportAnchorX": {
        enumerable: true,
        get: function() {
          return _viewportAnchorX;
        },
        set: function(value) {
          if (!isValidPercentValue(value)) {
            throw new Error("ViewportAnchorX must be between 0 and 100.");
          }
          _viewportAnchorX = value;
        }
      },
      "scroll": {
        enumerable: true,
        get: function() {
          return _scroll;
        },
        set: function(value) {
          var setting = findScrollSetting(value);
          // Have to check for false as an empty string is a legal value.
          if (setting === false) {
            throw new SyntaxError("An invalid or illegal string was specified.");
          }
          _scroll = setting;
        }
      }
    });
  }

  root.VTTRegion = root.VTTRegion || VTTRegion;
  vttjs.VTTRegion = VTTRegion;
}(this, (this.vttjs || {})));

/**
 * Copyright 2013 vtt.js Contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */

(function(global) {

  var _objCreate = Object.create || (function() {
    function F() {}
    return function(o) {
      if (arguments.length !== 1) {
        throw new Error('Object.create shim only accepts one parameter.');
      }
      F.prototype = o;
      return new F();
    };
  })();

  // Creates a new ParserError object from an errorData object. The errorData
  // object should have default code and message properties. The default message
  // property can be overriden by passing in a message parameter.
  // See ParsingError.Errors below for acceptable errors.
  function ParsingError(errorData, message) {
    this.name = "ParsingError";
    this.code = errorData.code;
    this.message = message || errorData.message;
  }
  ParsingError.prototype = _objCreate(Error.prototype);
  ParsingError.prototype.constructor = ParsingError;

  // ParsingError metadata for acceptable ParsingErrors.
  ParsingError.Errors = {
    BadSignature: {
      code: 0,
      message: "Malformed WebVTT signature."
    },
    BadTimeStamp: {
      code: 1,
      message: "Malformed time stamp."
    }
  };

  // Try to parse input as a time stamp.
  function parseTimeStamp(input) {

    function computeSeconds(h, m, s, f) {
      return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000;
    }

    var m = input.match(/^(\d+):(\d{2})(:\d{2})?\.(\d{3})/);
    if (!m) {
      return null;
    }

    if (m[3]) {
      // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds]
      return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]);
    } else if (m[1] > 59) {
      // Timestamp takes the form of [hours]:[minutes].[milliseconds]
      // First position is hours as it's over 59.
      return computeSeconds(m[1], m[2], 0,  m[4]);
    } else {
      // Timestamp takes the form of [minutes]:[seconds].[milliseconds]
      return computeSeconds(0, m[1], m[2], m[4]);
    }
  }

  // A settings object holds key/value pairs and will ignore anything but the first
  // assignment to a specific key.
  function Settings() {
    this.values = _objCreate(null);
  }

  Settings.prototype = {
    // Only accept the first assignment to any key.
    set: function(k, v) {
      if (!this.get(k) && v !== "") {
        this.values[k] = v;
      }
    },
    // Return the value for a key, or a default value.
    // If 'defaultKey' is passed then 'dflt' is assumed to be an object with
    // a number of possible default values as properties where 'defaultKey' is
    // the key of the property that will be chosen; otherwise it's assumed to be
    // a single value.
    get: function(k, dflt, defaultKey) {
      if (defaultKey) {
        return this.has(k) ? this.values[k] : dflt[defaultKey];
      }
      return this.has(k) ? this.values[k] : dflt;
    },
    // Check whether we have a value for a key.
    has: function(k) {
      return k in this.values;
    },
    // Accept a setting if its one of the given alternatives.
    alt: function(k, v, a) {
      for (var n = 0; n < a.length; ++n) {
        if (v === a[n]) {
          this.set(k, v);
          break;
        }
      }
    },
    // Accept a setting if its a valid (signed) integer.
    integer: function(k, v) {
      if (/^-?\d+$/.test(v)) { // integer
        this.set(k, parseInt(v, 10));
      }
    },
    // Accept a setting if its a valid percentage.
    percent: function(k, v) {
      var m;
      if ((m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/))) {
        v = parseFloat(v);
        if (v >= 0 && v <= 100) {
          this.set(k, v);
          return true;
        }
      }
      return false;
    }
  };

  // Helper function to parse input into groups separated by 'groupDelim', and
  // interprete each group as a key/value pair separated by 'keyValueDelim'.
  function parseOptions(input, callback, keyValueDelim, groupDelim) {
    var groups = groupDelim ? input.split(groupDelim) : [input];
    for (var i in groups) {
      if (typeof groups[i] !== "string") {
        continue;
      }
      var kv = groups[i].split(keyValueDelim);
      if (kv.length !== 2) {
        continue;
      }
      var k = kv[0];
      var v = kv[1];
      callback(k, v);
    }
  }

  function parseCue(input, cue, regionList) {
    // Remember the original input if we need to throw an error.
    var oInput = input;
    // 4.1 WebVTT timestamp
    function consumeTimeStamp() {
      var ts = parseTimeStamp(input);
      if (ts === null) {
        throw new ParsingError(ParsingError.Errors.BadTimeStamp,
                              "Malformed timestamp: " + oInput);
      }
      // Remove time stamp from input.
      input = input.replace(/^[^\sa-zA-Z-]+/, "");
      return ts;
    }

    // 4.4.2 WebVTT cue settings
    function consumeCueSettings(input, cue) {
      var settings = new Settings();

      parseOptions(input, function (k, v) {
        switch (k) {
        case "region":
          // Find the last region we parsed with the same region id.
          for (var i = regionList.length - 1; i >= 0; i--) {
            if (regionList[i].id === v) {
              settings.set(k, regionList[i].region);
              break;
            }
          }
          break;
        case "vertical":
          settings.alt(k, v, ["rl", "lr"]);
          break;
        case "line":
          var vals = v.split(","),
              vals0 = vals[0];
          settings.integer(k, vals0);
          settings.percent(k, vals0) ? settings.set("snapToLines", false) : null;
          settings.alt(k, vals0, ["auto"]);
          if (vals.length === 2) {
            settings.alt("lineAlign", vals[1], ["start", "middle", "end"]);
          }
          break;
        case "position":
          vals = v.split(",");
          settings.percent(k, vals[0]);
          if (vals.length === 2) {
            settings.alt("positionAlign", vals[1], ["start", "middle", "end"]);
          }
          break;
        case "size":
          settings.percent(k, v);
          break;
        case "align":
          settings.alt(k, v, ["start", "middle", "end", "left", "right"]);
          break;
        }
      }, /:/, /\s/);

      // Apply default values for any missing fields.
      cue.region = settings.get("region", null);
      cue.vertical = settings.get("vertical", "");
      cue.line = settings.get("line", "auto");
      cue.lineAlign = settings.get("lineAlign", "start");
      cue.snapToLines = settings.get("snapToLines", true);
      cue.size = settings.get("size", 100);
      cue.align = settings.get("align", "middle");
      cue.position = settings.get("position", {
        start: 0,
        left: 0,
        middle: 50,
        end: 100,
        right: 100
      }, cue.align);
      cue.positionAlign = settings.get("positionAlign", {
        start: "start",
        left: "start",
        middle: "middle",
        end: "end",
        right: "end"
      }, cue.align);
    }

    function skipWhitespace() {
      input = input.replace(/^\s+/, "");
    }

    // 4.1 WebVTT cue timings.
    skipWhitespace();
    cue.startTime = consumeTimeStamp();   // (1) collect cue start time
    skipWhitespace();
    if (input.substr(0, 3) !== "-->") {     // (3) next characters must match "-->"
      throw new ParsingError(ParsingError.Errors.BadTimeStamp,
                             "Malformed time stamp (time stamps must be separated by '-->'): " +
                             oInput);
    }
    input = input.substr(3);
    skipWhitespace();
    cue.endTime = consumeTimeStamp();     // (5) collect cue end time

    // 4.1 WebVTT cue settings list.
    skipWhitespace();
    consumeCueSettings(input, cue);
  }

  var ESCAPE = {
    "&amp;": "&",
    "&lt;": "<",
    "&gt;": ">",
    "&lrm;": "\u200e",
    "&rlm;": "\u200f",
    "&nbsp;": "\u00a0"
  };

  var TAG_NAME = {
    c: "span",
    i: "i",
    b: "b",
    u: "u",
    ruby: "ruby",
    rt: "rt",
    v: "span",
    lang: "span"
  };

  var TAG_ANNOTATION = {
    v: "title",
    lang: "lang"
  };

  var NEEDS_PARENT = {
    rt: "ruby"
  };

  // Parse content into a document fragment.
  function parseContent(window, input) {
    function nextToken() {
      // Check for end-of-string.
      if (!input) {
        return null;
      }

      // Consume 'n' characters from the input.
      function consume(result) {
        input = input.substr(result.length);
        return result;
      }

      var m = input.match(/^([^<]*)(<[^>]+>?)?/);
      // If there is some text before the next tag, return it, otherwise return
      // the tag.
      return consume(m[1] ? m[1] : m[2]);
    }

    // Unescape a string 's'.
    function unescape1(e) {
      return ESCAPE[e];
    }
    function unescape(s) {
      while ((m = s.match(/&(amp|lt|gt|lrm|rlm|nbsp);/))) {
        s = s.replace(m[0], unescape1);
      }
      return s;
    }

    function shouldAdd(current, element) {
      return !NEEDS_PARENT[element.localName] ||
             NEEDS_PARENT[element.localName] === current.localName;
    }

    // Create an element for this tag.
    function createElement(type, annotation) {
      var tagName = TAG_NAME[type];
      if (!tagName) {
        return null;
      }
      var element = window.document.createElement(tagName);
      element.localName = tagName;
      var name = TAG_ANNOTATION[type];
      if (name && annotation) {
        element[name] = annotation.trim();
      }
      return element;
    }

    var rootDiv = window.document.createElement("div"),
        current = rootDiv,
        t,
        tagStack = [];

    while ((t = nextToken()) !== null) {
      if (t[0] === '<') {
        if (t[1] === "/") {
          // If the closing tag matches, move back up to the parent node.
          if (tagStack.length &&
              tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) {
            tagStack.pop();
            current = current.parentNode;
          }
          // Otherwise just ignore the end tag.
          continue;
        }
        var ts = parseTimeStamp(t.substr(1, t.length - 2));
        var node;
        if (ts) {
          // Timestamps are lead nodes as well.
          node = window.document.createProcessingInstruction("timestamp", ts);
          current.appendChild(node);
          continue;
        }
        var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/);
        // If we can't parse the tag, skip to the next tag.
        if (!m) {
          continue;
        }
        // Try to construct an element, and ignore the tag if we couldn't.
        node = createElement(m[1], m[3]);
        if (!node) {
          continue;
        }
        // Determine if the tag should be added based on the context of where it
        // is placed in the cuetext.
        if (!shouldAdd(current, node)) {
          continue;
        }
        // Set the class list (as a list of classes, separated by space).
        if (m[2]) {
          node.className = m[2].substr(1).replace('.', ' ');
        }
        // Append the node to the current node, and enter the scope of the new
        // node.
        tagStack.push(m[1]);
        current.appendChild(node);
        current = node;
        continue;
      }

      // Text nodes are leaf nodes.
      current.appendChild(window.document.createTextNode(unescape(t)));
    }

    return rootDiv;
  }

  // This is a list of all the Unicode characters that have a strong
  // right-to-left category. What this means is that these characters are
  // written right-to-left for sure. It was generated by pulling all the strong
  // right-to-left characters out of the Unicode data table. That table can
  // found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt
  var strongRTLChars = [0x05BE, 0x05C0, 0x05C3, 0x05C6, 0x05D0, 0x05D1,
      0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, 0x05D8, 0x05D9, 0x05DA,
      0x05DB, 0x05DC, 0x05DD, 0x05DE, 0x05DF, 0x05E0, 0x05E1, 0x05E2, 0x05E3,
      0x05E4, 0x05E5, 0x05E6, 0x05E7, 0x05E8, 0x05E9, 0x05EA, 0x05F0, 0x05F1,
      0x05F2, 0x05F3, 0x05F4, 0x0608, 0x060B, 0x060D, 0x061B, 0x061E, 0x061F,
      0x0620, 0x0621, 0x0622, 0x0623, 0x0624, 0x0625, 0x0626, 0x0627, 0x0628,
      0x0629, 0x062A, 0x062B, 0x062C, 0x062D, 0x062E, 0x062F, 0x0630, 0x0631,
      0x0632, 0x0633, 0x0634, 0x0635, 0x0636, 0x0637, 0x0638, 0x0639, 0x063A,
      0x063B, 0x063C, 0x063D, 0x063E, 0x063F, 0x0640, 0x0641, 0x0642, 0x0643,
      0x0644, 0x0645, 0x0646, 0x0647, 0x0648, 0x0649, 0x064A, 0x066D, 0x066E,
      0x066F, 0x0671, 0x0672, 0x0673, 0x0674, 0x0675, 0x0676, 0x0677, 0x0678,
      0x0679, 0x067A, 0x067B, 0x067C, 0x067D, 0x067E, 0x067F, 0x0680, 0x0681,
      0x0682, 0x0683, 0x0684, 0x0685, 0x0686, 0x0687, 0x0688, 0x0689, 0x068A,
      0x068B, 0x068C, 0x068D, 0x068E, 0x068F, 0x0690, 0x0691, 0x0692, 0x0693,
      0x0694, 0x0695, 0x0696, 0x0697, 0x0698, 0x0699, 0x069A, 0x069B, 0x069C,
      0x069D, 0x069E, 0x069F, 0x06A0, 0x06A1, 0x06A2, 0x06A3, 0x06A4, 0x06A5,
      0x06A6, 0x06A7, 0x06A8, 0x06A9, 0x06AA, 0x06AB, 0x06AC, 0x06AD, 0x06AE,
      0x06AF, 0x06B0, 0x06B1, 0x06B2, 0x06B3, 0x06B4, 0x06B5, 0x06B6, 0x06B7,
      0x06B8, 0x06B9, 0x06BA, 0x06BB, 0x06BC, 0x06BD, 0x06BE, 0x06BF, 0x06C0,
      0x06C1, 0x06C2, 0x06C3, 0x06C4, 0x06C5, 0x06C6, 0x06C7, 0x06C8, 0x06C9,
      0x06CA, 0x06CB, 0x06CC, 0x06CD, 0x06CE, 0x06CF, 0x06D0, 0x06D1, 0x06D2,
      0x06D3, 0x06D4, 0x06D5, 0x06E5, 0x06E6, 0x06EE, 0x06EF, 0x06FA, 0x06FB,
      0x06FC, 0x06FD, 0x06FE, 0x06FF, 0x0700, 0x0701, 0x0702, 0x0703, 0x0704,
      0x0705, 0x0706, 0x0707, 0x0708, 0x0709, 0x070A, 0x070B, 0x070C, 0x070D,
      0x070F, 0x0710, 0x0712, 0x0713, 0x0714, 0x0715, 0x0716, 0x0717, 0x0718,
      0x0719, 0x071A, 0x071B, 0x071C, 0x071D, 0x071E, 0x071F, 0x0720, 0x0721,
      0x0722, 0x0723, 0x0724, 0x0725, 0x0726, 0x0727, 0x0728, 0x0729, 0x072A,
      0x072B, 0x072C, 0x072D, 0x072E, 0x072F, 0x074D, 0x074E, 0x074F, 0x0750,
      0x0751, 0x0752, 0x0753, 0x0754, 0x0755, 0x0756, 0x0757, 0x0758, 0x0759,
      0x075A, 0x075B, 0x075C, 0x075D, 0x075E, 0x075F, 0x0760, 0x0761, 0x0762,
      0x0763, 0x0764, 0x0765, 0x0766, 0x0767, 0x0768, 0x0769, 0x076A, 0x076B,
      0x076C, 0x076D, 0x076E, 0x076F, 0x0770, 0x0771, 0x0772, 0x0773, 0x0774,
      0x0775, 0x0776, 0x0777, 0x0778, 0x0779, 0x077A, 0x077B, 0x077C, 0x077D,
      0x077E, 0x077F, 0x0780, 0x0781, 0x0782, 0x0783, 0x0784, 0x0785, 0x0786,
      0x0787, 0x0788, 0x0789, 0x078A, 0x078B, 0x078C, 0x078D, 0x078E, 0x078F,
      0x0790, 0x0791, 0x0792, 0x0793, 0x0794, 0x0795, 0x0796, 0x0797, 0x0798,
      0x0799, 0x079A, 0x079B, 0x079C, 0x079D, 0x079E, 0x079F, 0x07A0, 0x07A1,
      0x07A2, 0x07A3, 0x07A4, 0x07A5, 0x07B1, 0x07C0, 0x07C1, 0x07C2, 0x07C3,
      0x07C4, 0x07C5, 0x07C6, 0x07C7, 0x07C8, 0x07C9, 0x07CA, 0x07CB, 0x07CC,
      0x07CD, 0x07CE, 0x07CF, 0x07D0, 0x07D1, 0x07D2, 0x07D3, 0x07D4, 0x07D5,
      0x07D6, 0x07D7, 0x07D8, 0x07D9, 0x07DA, 0x07DB, 0x07DC, 0x07DD, 0x07DE,
      0x07DF, 0x07E0, 0x07E1, 0x07E2, 0x07E3, 0x07E4, 0x07E5, 0x07E6, 0x07E7,
      0x07E8, 0x07E9, 0x07EA, 0x07F4, 0x07F5, 0x07FA, 0x0800, 0x0801, 0x0802,
      0x0803, 0x0804, 0x0805, 0x0806, 0x0807, 0x0808, 0x0809, 0x080A, 0x080B,
      0x080C, 0x080D, 0x080E, 0x080F, 0x0810, 0x0811, 0x0812, 0x0813, 0x0814,
      0x0815, 0x081A, 0x0824, 0x0828, 0x0830, 0x0831, 0x0832, 0x0833, 0x0834,
      0x0835, 0x0836, 0x0837, 0x0838, 0x0839, 0x083A, 0x083B, 0x083C, 0x083D,
      0x083E, 0x0840, 0x0841, 0x0842, 0x0843, 0x0844, 0x0845, 0x0846, 0x0847,
      0x0848, 0x0849, 0x084A, 0x084B, 0x084C, 0x084D, 0x084E, 0x084F, 0x0850,
      0x0851, 0x0852, 0x0853, 0x0854, 0x0855, 0x0856, 0x0857, 0x0858, 0x085E,
      0x08A0, 0x08A2, 0x08A3, 0x08A4, 0x08A5, 0x08A6, 0x08A7, 0x08A8, 0x08A9,
      0x08AA, 0x08AB, 0x08AC, 0x200F, 0xFB1D, 0xFB1F, 0xFB20, 0xFB21, 0xFB22,
      0xFB23, 0xFB24, 0xFB25, 0xFB26, 0xFB27, 0xFB28, 0xFB2A, 0xFB2B, 0xFB2C,
      0xFB2D, 0xFB2E, 0xFB2F, 0xFB30, 0xFB31, 0xFB32, 0xFB33, 0xFB34, 0xFB35,
      0xFB36, 0xFB38, 0xFB39, 0xFB3A, 0xFB3B, 0xFB3C, 0xFB3E, 0xFB40, 0xFB41,
      0xFB43, 0xFB44, 0xFB46, 0xFB47, 0xFB48, 0xFB49, 0xFB4A, 0xFB4B, 0xFB4C,
      0xFB4D, 0xFB4E, 0xFB4F, 0xFB50, 0xFB51, 0xFB52, 0xFB53, 0xFB54, 0xFB55,
      0xFB56, 0xFB57, 0xFB58, 0xFB59, 0xFB5A, 0xFB5B, 0xFB5C, 0xFB5D, 0xFB5E,
      0xFB5F, 0xFB60, 0xFB61, 0xFB62, 0xFB63, 0xFB64, 0xFB65, 0xFB66, 0xFB67,
      0xFB68, 0xFB69, 0xFB6A, 0xFB6B, 0xFB6C, 0xFB6D, 0xFB6E, 0xFB6F, 0xFB70,
      0xFB71, 0xFB72, 0xFB73, 0xFB74, 0xFB75, 0xFB76, 0xFB77, 0xFB78, 0xFB79,
      0xFB7A, 0xFB7B, 0xFB7C, 0xFB7D, 0xFB7E, 0xFB7F, 0xFB80, 0xFB81, 0xFB82,
      0xFB83, 0xFB84, 0xFB85, 0xFB86, 0xFB87, 0xFB88, 0xFB89, 0xFB8A, 0xFB8B,
      0xFB8C, 0xFB8D, 0xFB8E, 0xFB8F, 0xFB90, 0xFB91, 0xFB92, 0xFB93, 0xFB94,
      0xFB95, 0xFB96, 0xFB97, 0xFB98, 0xFB99, 0xFB9A, 0xFB9B, 0xFB9C, 0xFB9D,
      0xFB9E, 0xFB9F, 0xFBA0, 0xFBA1, 0xFBA2, 0xFBA3, 0xFBA4, 0xFBA5, 0xFBA6,
      0xFBA7, 0xFBA8, 0xFBA9, 0xFBAA, 0xFBAB, 0xFBAC, 0xFBAD, 0xFBAE, 0xFBAF,
      0xFBB0, 0xFBB1, 0xFBB2, 0xFBB3, 0xFBB4, 0xFBB5, 0xFBB6, 0xFBB7, 0xFBB8,
      0xFBB9, 0xFBBA, 0xFBBB, 0xFBBC, 0xFBBD, 0xFBBE, 0xFBBF, 0xFBC0, 0xFBC1,
      0xFBD3, 0xFBD4, 0xFBD5, 0xFBD6, 0xFBD7, 0xFBD8, 0xFBD9, 0xFBDA, 0xFBDB,
      0xFBDC, 0xFBDD, 0xFBDE, 0xFBDF, 0xFBE0, 0xFBE1, 0xFBE2, 0xFBE3, 0xFBE4,
      0xFBE5, 0xFBE6, 0xFBE7, 0xFBE8, 0xFBE9, 0xFBEA, 0xFBEB, 0xFBEC, 0xFBED,
      0xFBEE, 0xFBEF, 0xFBF0, 0xFBF1, 0xFBF2, 0xFBF3, 0xFBF4, 0xFBF5, 0xFBF6,
      0xFBF7, 0xFBF8, 0xFBF9, 0xFBFA, 0xFBFB, 0xFBFC, 0xFBFD, 0xFBFE, 0xFBFF,
      0xFC00, 0xFC01, 0xFC02, 0xFC03, 0xFC04, 0xFC05, 0xFC06, 0xFC07, 0xFC08,
      0xFC09, 0xFC0A, 0xFC0B, 0xFC0C, 0xFC0D, 0xFC0E, 0xFC0F, 0xFC10, 0xFC11,
      0xFC12, 0xFC13, 0xFC14, 0xFC15, 0xFC16, 0xFC17, 0xFC18, 0xFC19, 0xFC1A,
      0xFC1B, 0xFC1C, 0xFC1D, 0xFC1E, 0xFC1F, 0xFC20, 0xFC21, 0xFC22, 0xFC23,
      0xFC24, 0xFC25, 0xFC26, 0xFC27, 0xFC28, 0xFC29, 0xFC2A, 0xFC2B, 0xFC2C,
      0xFC2D, 0xFC2E, 0xFC2F, 0xFC30, 0xFC31, 0xFC32, 0xFC33, 0xFC34, 0xFC35,
      0xFC36, 0xFC37, 0xFC38, 0xFC39, 0xFC3A, 0xFC3B, 0xFC3C, 0xFC3D, 0xFC3E,
      0xFC3F, 0xFC40, 0xFC41, 0xFC42, 0xFC43, 0xFC44, 0xFC45, 0xFC46, 0xFC47,
      0xFC48, 0xFC49, 0xFC4A, 0xFC4B, 0xFC4C, 0xFC4D, 0xFC4E, 0xFC4F, 0xFC50,
      0xFC51, 0xFC52, 0xFC53, 0xFC54, 0xFC55, 0xFC56, 0xFC57, 0xFC58, 0xFC59,
      0xFC5A, 0xFC5B, 0xFC5C, 0xFC5D, 0xFC5E, 0xFC5F, 0xFC60, 0xFC61, 0xFC62,
      0xFC63, 0xFC64, 0xFC65, 0xFC66, 0xFC67, 0xFC68, 0xFC69, 0xFC6A, 0xFC6B,
      0xFC6C, 0xFC6D, 0xFC6E, 0xFC6F, 0xFC70, 0xFC71, 0xFC72, 0xFC73, 0xFC74,
      0xFC75, 0xFC76, 0xFC77, 0xFC78, 0xFC79, 0xFC7A, 0xFC7B, 0xFC7C, 0xFC7D,
      0xFC7E, 0xFC7F, 0xFC80, 0xFC81, 0xFC82, 0xFC83, 0xFC84, 0xFC85, 0xFC86,
      0xFC87, 0xFC88, 0xFC89, 0xFC8A, 0xFC8B, 0xFC8C, 0xFC8D, 0xFC8E, 0xFC8F,
      0xFC90, 0xFC91, 0xFC92, 0xFC93, 0xFC94, 0xFC95, 0xFC96, 0xFC97, 0xFC98,
      0xFC99, 0xFC9A, 0xFC9B, 0xFC9C, 0xFC9D, 0xFC9E, 0xFC9F, 0xFCA0, 0xFCA1,
      0xFCA2, 0xFCA3, 0xFCA4, 0xFCA5, 0xFCA6, 0xFCA7, 0xFCA8, 0xFCA9, 0xFCAA,
      0xFCAB, 0xFCAC, 0xFCAD, 0xFCAE, 0xFCAF, 0xFCB0, 0xFCB1, 0xFCB2, 0xFCB3,
      0xFCB4, 0xFCB5, 0xFCB6, 0xFCB7, 0xFCB8, 0xFCB9, 0xFCBA, 0xFCBB, 0xFCBC,
      0xFCBD, 0xFCBE, 0xFCBF, 0xFCC0, 0xFCC1, 0xFCC2, 0xFCC3, 0xFCC4, 0xFCC5,
      0xFCC6, 0xFCC7, 0xFCC8, 0xFCC9, 0xFCCA, 0xFCCB, 0xFCCC, 0xFCCD, 0xFCCE,
      0xFCCF, 0xFCD0, 0xFCD1, 0xFCD2, 0xFCD3, 0xFCD4, 0xFCD5, 0xFCD6, 0xFCD7,
      0xFCD8, 0xFCD9, 0xFCDA, 0xFCDB, 0xFCDC, 0xFCDD, 0xFCDE, 0xFCDF, 0xFCE0,
      0xFCE1, 0xFCE2, 0xFCE3, 0xFCE4, 0xFCE5, 0xFCE6, 0xFCE7, 0xFCE8, 0xFCE9,
      0xFCEA, 0xFCEB, 0xFCEC, 0xFCED, 0xFCEE, 0xFCEF, 0xFCF0, 0xFCF1, 0xFCF2,
      0xFCF3, 0xFCF4, 0xFCF5, 0xFCF6, 0xFCF7, 0xFCF8, 0xFCF9, 0xFCFA, 0xFCFB,
      0xFCFC, 0xFCFD, 0xFCFE, 0xFCFF, 0xFD00, 0xFD01, 0xFD02, 0xFD03, 0xFD04,
      0xFD05, 0xFD06, 0xFD07, 0xFD08, 0xFD09, 0xFD0A, 0xFD0B, 0xFD0C, 0xFD0D,
      0xFD0E, 0xFD0F, 0xFD10, 0xFD11, 0xFD12, 0xFD13, 0xFD14, 0xFD15, 0xFD16,
      0xFD17, 0xFD18, 0xFD19, 0xFD1A, 0xFD1B, 0xFD1C, 0xFD1D, 0xFD1E, 0xFD1F,
      0xFD20, 0xFD21, 0xFD22, 0xFD23, 0xFD24, 0xFD25, 0xFD26, 0xFD27, 0xFD28,
      0xFD29, 0xFD2A, 0xFD2B, 0xFD2C, 0xFD2D, 0xFD2E, 0xFD2F, 0xFD30, 0xFD31,
      0xFD32, 0xFD33, 0xFD34, 0xFD35, 0xFD36, 0xFD37, 0xFD38, 0xFD39, 0xFD3A,
      0xFD3B, 0xFD3C, 0xFD3D, 0xFD50, 0xFD51, 0xFD52, 0xFD53, 0xFD54, 0xFD55,
      0xFD56, 0xFD57, 0xFD58, 0xFD59, 0xFD5A, 0xFD5B, 0xFD5C, 0xFD5D, 0xFD5E,
      0xFD5F, 0xFD60, 0xFD61, 0xFD62, 0xFD63, 0xFD64, 0xFD65, 0xFD66, 0xFD67,
      0xFD68, 0xFD69, 0xFD6A, 0xFD6B, 0xFD6C, 0xFD6D, 0xFD6E, 0xFD6F, 0xFD70,
      0xFD71, 0xFD72, 0xFD73, 0xFD74, 0xFD75, 0xFD76, 0xFD77, 0xFD78, 0xFD79,
      0xFD7A, 0xFD7B, 0xFD7C, 0xFD7D, 0xFD7E, 0xFD7F, 0xFD80, 0xFD81, 0xFD82,
      0xFD83, 0xFD84, 0xFD85, 0xFD86, 0xFD87, 0xFD88, 0xFD89, 0xFD8A, 0xFD8B,
      0xFD8C, 0xFD8D, 0xFD8E, 0xFD8F, 0xFD92, 0xFD93, 0xFD94, 0xFD95, 0xFD96,
      0xFD97, 0xFD98, 0xFD99, 0xFD9A, 0xFD9B, 0xFD9C, 0xFD9D, 0xFD9E, 0xFD9F,
      0xFDA0, 0xFDA1, 0xFDA2, 0xFDA3, 0xFDA4, 0xFDA5, 0xFDA6, 0xFDA7, 0xFDA8,
      0xFDA9, 0xFDAA, 0xFDAB, 0xFDAC, 0xFDAD, 0xFDAE, 0xFDAF, 0xFDB0, 0xFDB1,
      0xFDB2, 0xFDB3, 0xFDB4, 0xFDB5, 0xFDB6, 0xFDB7, 0xFDB8, 0xFDB9, 0xFDBA,
      0xFDBB, 0xFDBC, 0xFDBD, 0xFDBE, 0xFDBF, 0xFDC0, 0xFDC1, 0xFDC2, 0xFDC3,
      0xFDC4, 0xFDC5, 0xFDC6, 0xFDC7, 0xFDF0, 0xFDF1, 0xFDF2, 0xFDF3, 0xFDF4,
      0xFDF5, 0xFDF6, 0xFDF7, 0xFDF8, 0xFDF9, 0xFDFA, 0xFDFB, 0xFDFC, 0xFE70,
      0xFE71, 0xFE72, 0xFE73, 0xFE74, 0xFE76, 0xFE77, 0xFE78, 0xFE79, 0xFE7A,
      0xFE7B, 0xFE7C, 0xFE7D, 0xFE7E, 0xFE7F, 0xFE80, 0xFE81, 0xFE82, 0xFE83,
      0xFE84, 0xFE85, 0xFE86, 0xFE87, 0xFE88, 0xFE89, 0xFE8A, 0xFE8B, 0xFE8C,
      0xFE8D, 0xFE8E, 0xFE8F, 0xFE90, 0xFE91, 0xFE92, 0xFE93, 0xFE94, 0xFE95,
      0xFE96, 0xFE97, 0xFE98, 0xFE99, 0xFE9A, 0xFE9B, 0xFE9C, 0xFE9D, 0xFE9E,
      0xFE9F, 0xFEA0, 0xFEA1, 0xFEA2, 0xFEA3, 0xFEA4, 0xFEA5, 0xFEA6, 0xFEA7,
      0xFEA8, 0xFEA9, 0xFEAA, 0xFEAB, 0xFEAC, 0xFEAD, 0xFEAE, 0xFEAF, 0xFEB0,
      0xFEB1, 0xFEB2, 0xFEB3, 0xFEB4, 0xFEB5, 0xFEB6, 0xFEB7, 0xFEB8, 0xFEB9,
      0xFEBA, 0xFEBB, 0xFEBC, 0xFEBD, 0xFEBE, 0xFEBF, 0xFEC0, 0xFEC1, 0xFEC2,
      0xFEC3, 0xFEC4, 0xFEC5, 0xFEC6, 0xFEC7, 0xFEC8, 0xFEC9, 0xFECA, 0xFECB,
      0xFECC, 0xFECD, 0xFECE, 0xFECF, 0xFED0, 0xFED1, 0xFED2, 0xFED3, 0xFED4,
      0xFED5, 0xFED6, 0xFED7, 0xFED8, 0xFED9, 0xFEDA, 0xFEDB, 0xFEDC, 0xFEDD,
      0xFEDE, 0xFEDF, 0xFEE0, 0xFEE1, 0xFEE2, 0xFEE3, 0xFEE4, 0xFEE5, 0xFEE6,
      0xFEE7, 0xFEE8, 0xFEE9, 0xFEEA, 0xFEEB, 0xFEEC, 0xFEED, 0xFEEE, 0xFEEF,
      0xFEF0, 0xFEF1, 0xFEF2, 0xFEF3, 0xFEF4, 0xFEF5, 0xFEF6, 0xFEF7, 0xFEF8,
      0xFEF9, 0xFEFA, 0xFEFB, 0xFEFC, 0x10800, 0x10801, 0x10802, 0x10803,
      0x10804, 0x10805, 0x10808, 0x1080A, 0x1080B, 0x1080C, 0x1080D, 0x1080E,
      0x1080F, 0x10810, 0x10811, 0x10812, 0x10813, 0x10814, 0x10815, 0x10816,
      0x10817, 0x10818, 0x10819, 0x1081A, 0x1081B, 0x1081C, 0x1081D, 0x1081E,
      0x1081F, 0x10820, 0x10821, 0x10822, 0x10823, 0x10824, 0x10825, 0x10826,
      0x10827, 0x10828, 0x10829, 0x1082A, 0x1082B, 0x1082C, 0x1082D, 0x1082E,
      0x1082F, 0x10830, 0x10831, 0x10832, 0x10833, 0x10834, 0x10835, 0x10837,
      0x10838, 0x1083C, 0x1083F, 0x10840, 0x10841, 0x10842, 0x10843, 0x10844,
      0x10845, 0x10846, 0x10847, 0x10848, 0x10849, 0x1084A, 0x1084B, 0x1084C,
      0x1084D, 0x1084E, 0x1084F, 0x10850, 0x10851, 0x10852, 0x10853, 0x10854,
      0x10855, 0x10857, 0x10858, 0x10859, 0x1085A, 0x1085B, 0x1085C, 0x1085D,
      0x1085E, 0x1085F, 0x10900, 0x10901, 0x10902, 0x10903, 0x10904, 0x10905,
      0x10906, 0x10907, 0x10908, 0x10909, 0x1090A, 0x1090B, 0x1090C, 0x1090D,
      0x1090E, 0x1090F, 0x10910, 0x10911, 0x10912, 0x10913, 0x10914, 0x10915,
      0x10916, 0x10917, 0x10918, 0x10919, 0x1091A, 0x1091B, 0x10920, 0x10921,
      0x10922, 0x10923, 0x10924, 0x10925, 0x10926, 0x10927, 0x10928, 0x10929,
      0x1092A, 0x1092B, 0x1092C, 0x1092D, 0x1092E, 0x1092F, 0x10930, 0x10931,
      0x10932, 0x10933, 0x10934, 0x10935, 0x10936, 0x10937, 0x10938, 0x10939,
      0x1093F, 0x10980, 0x10981, 0x10982, 0x10983, 0x10984, 0x10985, 0x10986,
      0x10987, 0x10988, 0x10989, 0x1098A, 0x1098B, 0x1098C, 0x1098D, 0x1098E,
      0x1098F, 0x10990, 0x10991, 0x10992, 0x10993, 0x10994, 0x10995, 0x10996,
      0x10997, 0x10998, 0x10999, 0x1099A, 0x1099B, 0x1099C, 0x1099D, 0x1099E,
      0x1099F, 0x109A0, 0x109A1, 0x109A2, 0x109A3, 0x109A4, 0x109A5, 0x109A6,
      0x109A7, 0x109A8, 0x109A9, 0x109AA, 0x109AB, 0x109AC, 0x109AD, 0x109AE,
      0x109AF, 0x109B0, 0x109B1, 0x109B2, 0x109B3, 0x109B4, 0x109B5, 0x109B6,
      0x109B7, 0x109BE, 0x109BF, 0x10A00, 0x10A10, 0x10A11, 0x10A12, 0x10A13,
      0x10A15, 0x10A16, 0x10A17, 0x10A19, 0x10A1A, 0x10A1B, 0x10A1C, 0x10A1D,
      0x10A1E, 0x10A1F, 0x10A20, 0x10A21, 0x10A22, 0x10A23, 0x10A24, 0x10A25,
      0x10A26, 0x10A27, 0x10A28, 0x10A29, 0x10A2A, 0x10A2B, 0x10A2C, 0x10A2D,
      0x10A2E, 0x10A2F, 0x10A30, 0x10A31, 0x10A32, 0x10A33, 0x10A40, 0x10A41,
      0x10A42, 0x10A43, 0x10A44, 0x10A45, 0x10A46, 0x10A47, 0x10A50, 0x10A51,
      0x10A52, 0x10A53, 0x10A54, 0x10A55, 0x10A56, 0x10A57, 0x10A58, 0x10A60,
      0x10A61, 0x10A62, 0x10A63, 0x10A64, 0x10A65, 0x10A66, 0x10A67, 0x10A68,
      0x10A69, 0x10A6A, 0x10A6B, 0x10A6C, 0x10A6D, 0x10A6E, 0x10A6F, 0x10A70,
      0x10A71, 0x10A72, 0x10A73, 0x10A74, 0x10A75, 0x10A76, 0x10A77, 0x10A78,
      0x10A79, 0x10A7A, 0x10A7B, 0x10A7C, 0x10A7D, 0x10A7E, 0x10A7F, 0x10B00,
      0x10B01, 0x10B02, 0x10B03, 0x10B04, 0x10B05, 0x10B06, 0x10B07, 0x10B08,
      0x10B09, 0x10B0A, 0x10B0B, 0x10B0C, 0x10B0D, 0x10B0E, 0x10B0F, 0x10B10,
      0x10B11, 0x10B12, 0x10B13, 0x10B14, 0x10B15, 0x10B16, 0x10B17, 0x10B18,
      0x10B19, 0x10B1A, 0x10B1B, 0x10B1C, 0x10B1D, 0x10B1E, 0x10B1F, 0x10B20,
      0x10B21, 0x10B22, 0x10B23, 0x10B24, 0x10B25, 0x10B26, 0x10B27, 0x10B28,
      0x10B29, 0x10B2A, 0x10B2B, 0x10B2C, 0x10B2D, 0x10B2E, 0x10B2F, 0x10B30,
      0x10B31, 0x10B32, 0x10B33, 0x10B34, 0x10B35, 0x10B40, 0x10B41, 0x10B42,
      0x10B43, 0x10B44, 0x10B45, 0x10B46, 0x10B47, 0x10B48, 0x10B49, 0x10B4A,
      0x10B4B, 0x10B4C, 0x10B4D, 0x10B4E, 0x10B4F, 0x10B50, 0x10B51, 0x10B52,
      0x10B53, 0x10B54, 0x10B55, 0x10B58, 0x10B59, 0x10B5A, 0x10B5B, 0x10B5C,
      0x10B5D, 0x10B5E, 0x10B5F, 0x10B60, 0x10B61, 0x10B62, 0x10B63, 0x10B64,
      0x10B65, 0x10B66, 0x10B67, 0x10B68, 0x10B69, 0x10B6A, 0x10B6B, 0x10B6C,
      0x10B6D, 0x10B6E, 0x10B6F, 0x10B70, 0x10B71, 0x10B72, 0x10B78, 0x10B79,
      0x10B7A, 0x10B7B, 0x10B7C, 0x10B7D, 0x10B7E, 0x10B7F, 0x10C00, 0x10C01,
      0x10C02, 0x10C03, 0x10C04, 0x10C05, 0x10C06, 0x10C07, 0x10C08, 0x10C09,
      0x10C0A, 0x10C0B, 0x10C0C, 0x10C0D, 0x10C0E, 0x10C0F, 0x10C10, 0x10C11,
      0x10C12, 0x10C13, 0x10C14, 0x10C15, 0x10C16, 0x10C17, 0x10C18, 0x10C19,
      0x10C1A, 0x10C1B, 0x10C1C, 0x10C1D, 0x10C1E, 0x10C1F, 0x10C20, 0x10C21,
      0x10C22, 0x10C23, 0x10C24, 0x10C25, 0x10C26, 0x10C27, 0x10C28, 0x10C29,
      0x10C2A, 0x10C2B, 0x10C2C, 0x10C2D, 0x10C2E, 0x10C2F, 0x10C30, 0x10C31,
      0x10C32, 0x10C33, 0x10C34, 0x10C35, 0x10C36, 0x10C37, 0x10C38, 0x10C39,
      0x10C3A, 0x10C3B, 0x10C3C, 0x10C3D, 0x10C3E, 0x10C3F, 0x10C40, 0x10C41,
      0x10C42, 0x10C43, 0x10C44, 0x10C45, 0x10C46, 0x10C47, 0x10C48, 0x1EE00,
      0x1EE01, 0x1EE02, 0x1EE03, 0x1EE05, 0x1EE06, 0x1EE07, 0x1EE08, 0x1EE09,
      0x1EE0A, 0x1EE0B, 0x1EE0C, 0x1EE0D, 0x1EE0E, 0x1EE0F, 0x1EE10, 0x1EE11,
      0x1EE12, 0x1EE13, 0x1EE14, 0x1EE15, 0x1EE16, 0x1EE17, 0x1EE18, 0x1EE19,
      0x1EE1A, 0x1EE1B, 0x1EE1C, 0x1EE1D, 0x1EE1E, 0x1EE1F, 0x1EE21, 0x1EE22,
      0x1EE24, 0x1EE27, 0x1EE29, 0x1EE2A, 0x1EE2B, 0x1EE2C, 0x1EE2D, 0x1EE2E,
      0x1EE2F, 0x1EE30, 0x1EE31, 0x1EE32, 0x1EE34, 0x1EE35, 0x1EE36, 0x1EE37,
      0x1EE39, 0x1EE3B, 0x1EE42, 0x1EE47, 0x1EE49, 0x1EE4B, 0x1EE4D, 0x1EE4E,
      0x1EE4F, 0x1EE51, 0x1EE52, 0x1EE54, 0x1EE57, 0x1EE59, 0x1EE5B, 0x1EE5D,
      0x1EE5F, 0x1EE61, 0x1EE62, 0x1EE64, 0x1EE67, 0x1EE68, 0x1EE69, 0x1EE6A,
      0x1EE6C, 0x1EE6D, 0x1EE6E, 0x1EE6F, 0x1EE70, 0x1EE71, 0x1EE72, 0x1EE74,
      0x1EE75, 0x1EE76, 0x1EE77, 0x1EE79, 0x1EE7A, 0x1EE7B, 0x1EE7C, 0x1EE7E,
      0x1EE80, 0x1EE81, 0x1EE82, 0x1EE83, 0x1EE84, 0x1EE85, 0x1EE86, 0x1EE87,
      0x1EE88, 0x1EE89, 0x1EE8B, 0x1EE8C, 0x1EE8D, 0x1EE8E, 0x1EE8F, 0x1EE90,
      0x1EE91, 0x1EE92, 0x1EE93, 0x1EE94, 0x1EE95, 0x1EE96, 0x1EE97, 0x1EE98,
      0x1EE99, 0x1EE9A, 0x1EE9B, 0x1EEA1, 0x1EEA2, 0x1EEA3, 0x1EEA5, 0x1EEA6,
      0x1EEA7, 0x1EEA8, 0x1EEA9, 0x1EEAB, 0x1EEAC, 0x1EEAD, 0x1EEAE, 0x1EEAF,
      0x1EEB0, 0x1EEB1, 0x1EEB2, 0x1EEB3, 0x1EEB4, 0x1EEB5, 0x1EEB6, 0x1EEB7,
      0x1EEB8, 0x1EEB9, 0x1EEBA, 0x1EEBB, 0x10FFFD];

  function determineBidi(cueDiv) {
    var nodeStack = [],
        text = "",
        charCode;

    if (!cueDiv || !cueDiv.childNodes) {
      return "ltr";
    }

    function pushNodes(nodeStack, node) {
      for (var i = node.childNodes.length - 1; i >= 0; i--) {
        nodeStack.push(node.childNodes[i]);
      }
    }

    function nextTextNode(nodeStack) {
      if (!nodeStack || !nodeStack.length) {
        return null;
      }

      var node = nodeStack.pop(),
          text = node.textContent || node.innerText;
      if (text) {
        // TODO: This should match all unicode type B characters (paragraph
        // separator characters). See issue #115.
        var m = text.match(/^.*(\n|\r)/);
        if (m) {
          nodeStack.length = 0;
          return m[0];
        }
        return text;
      }
      if (node.tagName === "ruby") {
        return nextTextNode(nodeStack);
      }
      if (node.childNodes) {
        pushNodes(nodeStack, node);
        return nextTextNode(nodeStack);
      }
    }

    pushNodes(nodeStack, cueDiv);
    while ((text = nextTextNode(nodeStack))) {
      for (var i = 0; i < text.length; i++) {
        charCode = text.charCodeAt(i);
        for (var j = 0; j < strongRTLChars.length; j++) {
          if (strongRTLChars[j] === charCode) {
            return "rtl";
          }
        }
      }
    }
    return "ltr";
  }

  function computeLinePos(cue) {
    if (typeof cue.line === "number" &&
        (cue.snapToLines || (cue.line >= 0 && cue.line <= 100))) {
      return cue.line;
    }
    if (!cue.track || !cue.track.textTrackList ||
        !cue.track.textTrackList.mediaElement) {
      return -1;
    }
    var track = cue.track,
        trackList = track.textTrackList,
        count = 0;
    for (var i = 0; i < trackList.length && trackList[i] !== track; i++) {
      if (trackList[i].mode === "showing") {
        count++;
      }
    }
    return ++count * -1;
  }

  function StyleBox() {
  }

  // Apply styles to a div. If there is no div passed then it defaults to the
  // div on 'this'.
  StyleBox.prototype.applyStyles = function(styles, div) {
    div = div || this.div;
    for (var prop in styles) {
      if (styles.hasOwnProperty(prop)) {
        div.style[prop] = styles[prop];
      }
    }
  };

  StyleBox.prototype.formatStyle = function(val, unit) {
    return val === 0 ? 0 : val + unit;
  };

  // Constructs the computed display state of the cue (a div). Places the div
  // into the overlay which should be a block level element (usually a div).
  function CueStyleBox(window, cue, styleOptions) {
    var isIE8 = (/MSIE\s8\.0/).test(navigator.userAgent);
    var color = "rgba(255, 255, 255, 1)";
    var backgroundColor = "rgba(0, 0, 0, 0.8)";

    if (isIE8) {
      color = "rgb(255, 255, 255)";
      backgroundColor = "rgb(0, 0, 0)";
    }

    StyleBox.call(this);
    this.cue = cue;

    // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will
    // have inline positioning and will function as the cue background box.
    this.cueDiv = parseContent(window, cue.text);
    var styles = {
      color: color,
      backgroundColor: backgroundColor,
      position: "relative",
      left: 0,
      right: 0,
      top: 0,
      bottom: 0,
      display: "inline"
    };

    if (!isIE8) {
      styles.writingMode = cue.vertical === "" ? "horizontal-tb"
                                               : cue.vertical === "lr" ? "vertical-lr"
                                                                       : "vertical-rl";
      styles.unicodeBidi = "plaintext";
    }
    this.applyStyles(styles, this.cueDiv);

    // Create an absolutely positioned div that will be used to position the cue
    // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS
    // mirrors of them except "middle" which is "center" in CSS.
    this.div = window.document.createElement("div");
    styles = {
      textAlign: cue.align === "middle" ? "center" : cue.align,
      font: styleOptions.font,
      whiteSpace: "pre-line",
      position: "absolute"
    };

    if (!isIE8) {
      styles.direction = determineBidi(this.cueDiv);
      styles.writingMode = cue.vertical === "" ? "horizontal-tb"
                                               : cue.vertical === "lr" ? "vertical-lr"
                                                                       : "vertical-rl".
      stylesunicodeBidi =  "plaintext";
    }

    this.applyStyles(styles);

    this.div.appendChild(this.cueDiv);

    // Calculate the distance from the reference edge of the viewport to the text
    // position of the cue box. The reference edge will be resolved later when
    // the box orientation styles are applied.
    var textPos = 0;
    switch (cue.positionAlign) {
    case "start":
      textPos = cue.position;
      break;
    case "middle":
      textPos = cue.position - (cue.size / 2);
      break;
    case "end":
      textPos = cue.position - cue.size;
      break;
    }

    // Horizontal box orientation; textPos is the distance from the left edge of the
    // area to the left edge of the box and cue.size is the distance extending to
    // the right from there.
    if (cue.vertical === "") {
      this.applyStyles({
        left:  this.formatStyle(textPos, "%"),
        width: this.formatStyle(cue.size, "%")
      });
    // Vertical box orientation; textPos is the distance from the top edge of the
    // area to the top edge of the box and cue.size is the height extending
    // downwards from there.
    } else {
      this.applyStyles({
        top: this.formatStyle(textPos, "%"),
        height: this.formatStyle(cue.size, "%")
      });
    }

    this.move = function(box) {
      this.applyStyles({
        top: this.formatStyle(box.top, "px"),
        bottom: this.formatStyle(box.bottom, "px"),
        left: this.formatStyle(box.left, "px"),
        right: this.formatStyle(box.right, "px"),
        height: this.formatStyle(box.height, "px"),
        width: this.formatStyle(box.width, "px")
      });
    };
  }
  CueStyleBox.prototype = _objCreate(StyleBox.prototype);
  CueStyleBox.prototype.constructor = CueStyleBox;

  // Represents the co-ordinates of an Element in a way that we can easily
  // compute things with such as if it overlaps or intersects with another Element.
  // Can initialize it with either a StyleBox or another BoxPosition.
  function BoxPosition(obj) {
    var isIE8 = (/MSIE\s8\.0/).test(navigator.userAgent);

    // Either a BoxPosition was passed in and we need to copy it, or a StyleBox
    // was passed in and we need to copy the results of 'getBoundingClientRect'
    // as the object returned is readonly. All co-ordinate values are in reference
    // to the viewport origin (top left).
    var lh, height, width, top;
    if (obj.div) {
      height = obj.div.offsetHeight;
      width = obj.div.offsetWidth;
      top = obj.div.offsetTop;

      var rects = (rects = obj.div.childNodes) && (rects = rects[0]) &&
                  rects.getClientRects && rects.getClientRects();
      obj = obj.div.getBoundingClientRect();
      // In certain cases the outter div will be slightly larger then the sum of
      // the inner div's lines. This could be due to bold text, etc, on some platforms.
      // In this case we should get the average line height and use that. This will
      // result in the desired behaviour.
      lh = rects ? Math.max((rects[0] && rects[0].height) || 0, obj.height / rects.length)
                 : 0;

    }
    this.left = obj.left;
    this.right = obj.right;
    this.top = obj.top || top;
    this.height = obj.height || height;
    this.bottom = obj.bottom || (top + (obj.height || height));
    this.width = obj.width || width;
    this.lineHeight = lh !== undefined ? lh : obj.lineHeight;

    if (isIE8 && !this.lineHeight) {
      this.lineHeight = 13;
    }
  }

  // Move the box along a particular axis. Optionally pass in an amount to move
  // the box. If no amount is passed then the default is the line height of the
  // box.
  BoxPosition.prototype.move = function(axis, toMove) {
    toMove = toMove !== undefined ? toMove : this.lineHeight;
    switch (axis) {
    case "+x":
      this.left += toMove;
      this.right += toMove;
      break;
    case "-x":
      this.left -= toMove;
      this.right -= toMove;
      break;
    case "+y":
      this.top += toMove;
      this.bottom += toMove;
      break;
    case "-y":
      this.top -= toMove;
      this.bottom -= toMove;
      break;
    }
  };

  // Check if this box overlaps another box, b2.
  BoxPosition.prototype.overlaps = function(b2) {
    return this.left < b2.right &&
           this.right > b2.left &&
           this.top < b2.bottom &&
           this.bottom > b2.top;
  };

  // Check if this box overlaps any other boxes in boxes.
  BoxPosition.prototype.overlapsAny = function(boxes) {
    for (var i = 0; i < boxes.length; i++) {
      if (this.overlaps(boxes[i])) {
        return true;
      }
    }
    return false;
  };

  // Check if this box is within another box.
  BoxPosition.prototype.within = function(container) {
    return this.top >= container.top &&
           this.bottom <= container.bottom &&
           this.left >= container.left &&
           this.right <= container.right;
  };

  // Check if this box is entirely within the container or it is overlapping
  // on the edge opposite of the axis direction passed. For example, if "+x" is
  // passed and the box is overlapping on the left edge of the container, then
  // return true.
  BoxPosition.prototype.overlapsOppositeAxis = function(container, axis) {
    switch (axis) {
    case "+x":
      return this.left < container.left;
    case "-x":
      return this.right > container.right;
    case "+y":
      return this.top < container.top;
    case "-y":
      return this.bottom > container.bottom;
    }
  };

  // Find the percentage of the area that this box is overlapping with another
  // box.
  BoxPosition.prototype.intersectPercentage = function(b2) {
    var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)),
        y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)),
        intersectArea = x * y;
    return intersectArea / (this.height * this.width);
  };

  // Convert the positions from this box to CSS compatible positions using
  // the reference container's positions. This has to be done because this
  // box's positions are in reference to the viewport origin, whereas, CSS
  // values are in referecne to their respective edges.
  BoxPosition.prototype.toCSSCompatValues = function(reference) {
    return {
      top: this.top - reference.top,
      bottom: reference.bottom - this.bottom,
      left: this.left - reference.left,
      right: reference.right - this.right,
      height: this.height,
      width: this.width
    };
  };

  // Get an object that represents the box's position without anything extra.
  // Can pass a StyleBox, HTMLElement, or another BoxPositon.
  BoxPosition.getSimpleBoxPosition = function(obj) {
    var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0;
    var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0;
    var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0;

    obj = obj.div ? obj.div.getBoundingClientRect() :
                  obj.tagName ? obj.getBoundingClientRect() : obj;
    var ret = {
      left: obj.left,
      right: obj.right,
      top: obj.top || top,
      height: obj.height || height,
      bottom: obj.bottom || (top + (obj.height || height)),
      width: obj.width || width
    };
    return ret;
  };

  // Move a StyleBox to its specified, or next best, position. The containerBox
  // is the box that contains the StyleBox, such as a div. boxPositions are
  // a list of other boxes that the styleBox can't overlap with.
  function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) {

    // Find the best position for a cue box, b, on the video. The axis parameter
    // is a list of axis, the order of which, it will move the box along. For example:
    // Passing ["+x", "-x"] will move the box first along the x axis in the positive
    // direction. If it doesn't find a good position for it there it will then move
    // it along the x axis in the negative direction.
    function findBestPosition(b, axis) {
      var bestPosition,
          specifiedPosition = new BoxPosition(b),
          percentage = 1; // Highest possible so the first thing we get is better.

      for (var i = 0; i < axis.length; i++) {
        while (b.overlapsOppositeAxis(containerBox, axis[i]) ||
               (b.within(containerBox) && b.overlapsAny(boxPositions))) {
          b.move(axis[i]);
        }
        // We found a spot where we aren't overlapping anything. This is our
        // best position.
        if (b.within(containerBox)) {
          return b;
        }
        var p = b.intersectPercentage(containerBox);
        // If we're outside the container box less then we were on our last try
        // then remember this position as the best position.
        if (percentage > p) {
          bestPosition = new BoxPosition(b);
          percentage = p;
        }
        // Reset the box position to the specified position.
        b = new BoxPosition(specifiedPosition);
      }
      return bestPosition || specifiedPosition;
    }

    var boxPosition = new BoxPosition(styleBox),
        cue = styleBox.cue,
        linePos = computeLinePos(cue),
        axis = [];

    // If we have a line number to align the cue to.
    if (cue.snapToLines) {
      var size;
      switch (cue.vertical) {
      case "":
        axis = [ "+y", "-y" ];
        size = "height";
        break;
      case "rl":
        axis = [ "+x", "-x" ];
        size = "width";
        break;
      case "lr":
        axis = [ "-x", "+x" ];
        size = "width";
        break;
      }

      var step = boxPosition.lineHeight,
          position = step * Math.round(linePos),
          maxPosition = containerBox[size] + step,
          initialAxis = axis[0];

      // If the specified intial position is greater then the max position then
      // clamp the box to the amount of steps it would take for the box to
      // reach the max position.
      if (Math.abs(position) > maxPosition) {
        position = position < 0 ? -1 : 1;
        position *= Math.ceil(maxPosition / step) * step;
      }

      // If computed line position returns negative then line numbers are
      // relative to the bottom of the video instead of the top. Therefore, we
      // need to increase our initial position by the length or width of the
      // video, depending on the writing direction, and reverse our axis directions.
      if (linePos < 0) {
        position += cue.vertical === "" ? containerBox.height : containerBox.width;
        axis = axis.reverse();
      }

      // Move the box to the specified position. This may not be its best
      // position.
      boxPosition.move(initialAxis, position);

    } else {
      // If we have a percentage line value for the cue.
      var calculatedPercentage = (boxPosition.lineHeight / containerBox.height) * 100;

      switch (cue.lineAlign) {
      case "middle":
        linePos -= (calculatedPercentage / 2);
        break;
      case "end":
        linePos -= calculatedPercentage;
        break;
      }

      // Apply initial line position to the cue box.
      switch (cue.vertical) {
      case "":
        styleBox.applyStyles({
          top: styleBox.formatStyle(linePos, "%")
        });
        break;
      case "rl":
        styleBox.applyStyles({
          left: styleBox.formatStyle(linePos, "%")
        });
        break;
      case "lr":
        styleBox.applyStyles({
          right: styleBox.formatStyle(linePos, "%")
        });
        break;
      }

      axis = [ "+y", "-x", "+x", "-y" ];

      // Get the box position again after we've applied the specified positioning
      // to it.
      boxPosition = new BoxPosition(styleBox);
    }

    var bestPosition = findBestPosition(boxPosition, axis);
    styleBox.move(bestPosition.toCSSCompatValues(containerBox));
  }

  function WebVTT() {
    // Nothing
  }

  // Helper to allow strings to be decoded instead of the default binary utf8 data.
  WebVTT.StringDecoder = function() {
    return {
      decode: function(data) {
        if (!data) {
          return "";
        }
        if (typeof data !== "string") {
          throw new Error("Error - expected string data.");
        }
        return decodeURIComponent(encodeURIComponent(data));
      }
    };
  };

  WebVTT.convertCueToDOMTree = function(window, cuetext) {
    if (!window || !cuetext) {
      return null;
    }
    return parseContent(window, cuetext);
  };

  var FONT_SIZE_PERCENT = 0.05;
  var FONT_STYLE = "sans-serif";
  var CUE_BACKGROUND_PADDING = "1.5%";

  // Runs the processing model over the cues and regions passed to it.
  // @param overlay A block level element (usually a div) that the computed cues
  //                and regions will be placed into.
  WebVTT.processCues = function(window, cues, overlay) {
    if (!window || !cues || !overlay) {
      return null;
    }

    // Remove all previous children.
    while (overlay.firstChild) {
      overlay.removeChild(overlay.firstChild);
    }

    var paddedOverlay = window.document.createElement("div");
    paddedOverlay.style.position = "absolute";
    paddedOverlay.style.left = "0";
    paddedOverlay.style.right = "0";
    paddedOverlay.style.top = "0";
    paddedOverlay.style.bottom = "0";
    paddedOverlay.style.margin = CUE_BACKGROUND_PADDING;
    overlay.appendChild(paddedOverlay);

    // Determine if we need to compute the display states of the cues. This could
    // be the case if a cue's state has been changed since the last computation or
    // if it has not been computed yet.
    function shouldCompute(cues) {
      for (var i = 0; i < cues.length; i++) {
        if (cues[i].hasBeenReset || !cues[i].displayState) {
          return true;
        }
      }
      return false;
    }

    // We don't need to recompute the cues' display states. Just reuse them.
    if (!shouldCompute(cues)) {
      for (var i = 0; i < cues.length; i++) {
        paddedOverlay.appendChild(cues[i].displayState);
      }
      return;
    }

    var boxPositions = [],
        containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay),
        fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100;
    var styleOptions = {
      font: fontSize + "px " + FONT_STYLE
    };

    (function() {
      var styleBox, cue;

      for (var i = 0; i < cues.length; i++) {
        cue = cues[i];

        // Compute the intial position and styles of the cue div.
        styleBox = new CueStyleBox(window, cue, styleOptions);
        paddedOverlay.appendChild(styleBox.div);

        // Move the cue div to it's correct line position.
        moveBoxToLinePosition(window, styleBox, containerBox, boxPositions);

        // Remember the computed div so that we don't have to recompute it later
        // if we don't have too.
        cue.displayState = styleBox.div;

        boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox));
      }
    })();
  };

  WebVTT.Parser = function(window, vttjs, decoder) {
    if (!decoder) {
      decoder = vttjs;
      vttjs = {};
    }
    if (!vttjs) {
      vttjs = {};
    }

    this.window = window;
    this.vttjs = vttjs;
    this.state = "INITIAL";
    this.buffer = "";
    this.decoder = decoder || new TextDecoder("utf8");
    this.regionList = [];
  };

  WebVTT.Parser.prototype = {
    // If the error is a ParsingError then report it to the consumer if
    // possible. If it's not a ParsingError then throw it like normal.
    reportOrThrowError: function(e) {
      if (e instanceof ParsingError) {
        this.onparsingerror && this.onparsingerror(e);
      } else {
        throw e;
      }
    },
    parse: function (data) {
      var self = this;

      // If there is no data then we won't decode it, but will just try to parse
      // whatever is in buffer already. This may occur in circumstances, for
      // example when flush() is called.
      if (data) {
        // Try to decode the data that we received.
        self.buffer += self.decoder.decode(data, {stream: true});
      }

      function collectNextLine() {
        var buffer = self.buffer;
        var pos = 0;
        while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') {
          ++pos;
        }
        var line = buffer.substr(0, pos);
        // Advance the buffer early in case we fail below.
        if (buffer[pos] === '\r') {
          ++pos;
        }
        if (buffer[pos] === '\n') {
          ++pos;
        }
        self.buffer = buffer.substr(pos);
        return line;
      }

      // 3.4 WebVTT region and WebVTT region settings syntax
      function parseRegion(input) {
        var settings = new Settings();

        parseOptions(input, function (k, v) {
          switch (k) {
          case "id":
            settings.set(k, v);
            break;
          case "width":
            settings.percent(k, v);
            break;
          case "lines":
            settings.integer(k, v);
            break;
          case "regionanchor":
          case "viewportanchor":
            var xy = v.split(',');
            if (xy.length !== 2) {
              break;
            }
            // We have to make sure both x and y parse, so use a temporary
            // settings object here.
            var anchor = new Settings();
            anchor.percent("x", xy[0]);
            anchor.percent("y", xy[1]);
            if (!anchor.has("x") || !anchor.has("y")) {
              break;
            }
            settings.set(k + "X", anchor.get("x"));
            settings.set(k + "Y", anchor.get("y"));
            break;
          case "scroll":
            settings.alt(k, v, ["up"]);
            break;
          }
        }, /=/, /\s/);

        // Create the region, using default values for any values that were not
        // specified.
        if (settings.has("id")) {
          var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)();
          region.width = settings.get("width", 100);
          region.lines = settings.get("lines", 3);
          region.regionAnchorX = settings.get("regionanchorX", 0);
          region.regionAnchorY = settings.get("regionanchorY", 100);
          region.viewportAnchorX = settings.get("viewportanchorX", 0);
          region.viewportAnchorY = settings.get("viewportanchorY", 100);
          region.scroll = settings.get("scroll", "");
          // Register the region.
          self.onregion && self.onregion(region);
          // Remember the VTTRegion for later in case we parse any VTTCues that
          // reference it.
          self.regionList.push({
            id: settings.get("id"),
            region: region
          });
        }
      }

      // 3.2 WebVTT metadata header syntax
      function parseHeader(input) {
        parseOptions(input, function (k, v) {
          switch (k) {
          case "Region":
            // 3.3 WebVTT region metadata header syntax
            parseRegion(v);
            break;
          }
        }, /:/);
      }

      // 5.1 WebVTT file parsing.
      try {
        var line;
        if (self.state === "INITIAL") {
          // We can't start parsing until we have the first line.
          if (!/\r\n|\n/.test(self.buffer)) {
            return this;
          }

          line = collectNextLine();

          var m = line.match(/^WEBVTT([ \t].*)?$/);
          if (!m || !m[0]) {
            throw new ParsingError(ParsingError.Errors.BadSignature);
          }

          self.state = "HEADER";
        }

        var alreadyCollectedLine = false;
        while (self.buffer) {
          // We can't parse a line until we have the full line.
          if (!/\r\n|\n/.test(self.buffer)) {
            return this;
          }

          if (!alreadyCollectedLine) {
            line = collectNextLine();
          } else {
            alreadyCollectedLine = false;
          }

          switch (self.state) {
          case "HEADER":
            // 13-18 - Allow a header (metadata) under the WEBVTT line.
            if (/:/.test(line)) {
              parseHeader(line);
            } else if (!line) {
              // An empty line terminates the header and starts the body (cues).
              self.state = "ID";
            }
            continue;
          case "NOTE":
            // Ignore NOTE blocks.
            if (!line) {
              self.state = "ID";
            }
            continue;
          case "ID":
            // Check for the start of NOTE blocks.
            if (/^NOTE($|[ \t])/.test(line)) {
              self.state = "NOTE";
              break;
            }
            // 19-29 - Allow any number of line terminators, then initialize new cue values.
            if (!line) {
              continue;
            }
            self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, "");
            self.state = "CUE";
            // 30-39 - Check if self line contains an optional identifier or timing data.
            if (line.indexOf("-->") === -1) {
              self.cue.id = line;
              continue;
            }
            // Process line as start of a cue.
            /*falls through*/
          case "CUE":
            // 40 - Collect cue timings and settings.
            try {
              parseCue(line, self.cue, self.regionList);
            } catch (e) {
              self.reportOrThrowError(e);
              // In case of an error ignore rest of the cue.
              self.cue = null;
              self.state = "BADCUE";
              continue;
            }
            self.state = "CUETEXT";
            continue;
          case "CUETEXT":
            var hasSubstring = line.indexOf("-->") !== -1;
            // 34 - If we have an empty line then report the cue.
            // 35 - If we have the special substring '-->' then report the cue,
            // but do not collect the line as we need to process the current
            // one as a new cue.
            if (!line || hasSubstring && (alreadyCollectedLine = true)) {
              // We are done parsing self cue.
              self.oncue && self.oncue(self.cue);
              self.cue = null;
              self.state = "ID";
              continue;
            }
            if (self.cue.text) {
              self.cue.text += "\n";
            }
            self.cue.text += line;
            continue;
          case "BADCUE": // BADCUE
            // 54-62 - Collect and discard the remaining cue.
            if (!line) {
              self.state = "ID";
            }
            continue;
          }
        }
      } catch (e) {
        self.reportOrThrowError(e);

        // If we are currently parsing a cue, report what we have.
        if (self.state === "CUETEXT" && self.cue && self.oncue) {
          self.oncue(self.cue);
        }
        self.cue = null;
        // Enter BADWEBVTT state if header was not parsed correctly otherwise
        // another exception occurred so enter BADCUE state.
        self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE";
      }
      return this;
    },
    flush: function () {
      var self = this;
      try {
        // Finish decoding the stream.
        self.buffer += self.decoder.decode();
        // Synthesize the end of the current cue or region.
        if (self.cue || self.state === "HEADER") {
          self.buffer += "\n\n";
          self.parse();
        }
        // If we've flushed, parsed, and we're still on the INITIAL state then
        // that means we don't have enough of the stream to parse the first
        // line.
        if (self.state === "INITIAL") {
          throw new ParsingError(ParsingError.Errors.BadSignature);
        }
      } catch(e) {
        self.reportOrThrowError(e);
      }
      self.onflush && self.onflush();
      return this;
    }
  };

  global.WebVTT = WebVTT;

}(this, (this.vttjs || {})));

/*! videojs-contrib-hls - v1.3.9 - 2016-02-22
* Copyright (c) 2016 Brightcove; Licensed  */
!function(a,b,c){var d=function(){this.init=function(){var a={};this.on=function(b,c){a[b]||(a[b]=[]),a[b].push(c)},this.off=function(b,c){var d;return a[b]?(d=a[b].indexOf(c),a[b].splice(d,1),d>-1):!1},this.trigger=function(b){var c,d,e,f;if(c=a[b])if(2===arguments.length)for(e=c.length,d=0;e>d;++d)c[d].call(this,arguments[1]);else{for(f=[],d=arguments.length,d=1;d<arguments.length;++d)f.push(arguments[d]);for(e=c.length,d=0;e>d;++d)c[d].apply(this,f)}},this.dispose=function(){a={}}}};d.prototype.pipe=function(a){return this.on("data",function(b){a.push(b)}),this.on("done",function(){a.flush()}),a},d.prototype.push=function(a){this.trigger("data",a)},d.prototype.flush=function(){this.trigger("done")},a.muxjs=a.muxjs||{},a.muxjs.utils=a.muxjs.utils||{},a.muxjs.utils.Stream=d}(this,this.muxjs),function(a,b){var c;c=function(a){var b=a.byteLength,c=0,d=0;this.length=function(){return 8*b},this.bitsAvailable=function(){return 8*b+d},this.loadWord=function(){var e=a.byteLength-b,f=new Uint8Array(4),g=Math.min(4,b);if(0===g)throw new Error("no bytes available");f.set(a.subarray(e,e+g)),c=new DataView(f.buffer).getUint32(0),d=8*g,b-=g},this.skipBits=function(a){var e;d>a?(c<<=a,d-=a):(a-=d,e=Math.floor(a/8),a-=8*e,b-=e,this.loadWord(),c<<=a,d-=a)},this.readBits=function(a){var e=Math.min(d,a),f=c>>>32-e;return console.assert(32>a,"Cannot read more than 32 bits at a time"),d-=e,d>0?c<<=e:b>0&&this.loadWord(),e=a-e,e>0?f<<e|this.readBits(e):f},this.skipLeadingZeros=function(){var a;for(a=0;d>a;++a)if(0!==(c&2147483648>>>a))return c<<=a,d-=a,a;return this.loadWord(),a+this.skipLeadingZeros()},this.skipUnsignedExpGolomb=function(){this.skipBits(1+this.skipLeadingZeros())},this.skipExpGolomb=function(){this.skipBits(1+this.skipLeadingZeros())},this.readUnsignedExpGolomb=function(){var a=this.skipLeadingZeros();return this.readBits(a+1)-1},this.readExpGolomb=function(){var a=this.readUnsignedExpGolomb();return 1&a?1+a>>>1:-1*(a>>>1)},this.readBoolean=function(){return 1===this.readBits(1)},this.readUnsignedByte=function(){return this.readBits(8)},this.loadWord()},a.muxjs=b||{},b.utils=b.utils||{},b.utils.ExpGolomb=c}(this,this.muxjs),function(a,b){"use strict";var c,d=[96e3,88200,64e3,48e3,44100,32e3,24e3,22050,16e3,12e3,11025,8e3,7350];c=function(){var a,b;c.prototype.init.call(this),a=this,this.push=function(a){var c,e,f,g,h,i,j=0,k=0;if("audio"===a.type)for(b?(g=b,b=new Uint8Array(g.byteLength+a.data.byteLength),b.set(g),b.set(a.data,g.byteLength)):b=a.data;j+5<b.length;)if(255===b[j]&&240===(246&b[j+1])){if(e=2*(1&~b[j+1]),c=(3&b[j+3])<<11|b[j+4]<<3|(224&b[j+5])>>5,h=1024*((3&b[j+6])+1),i=9e4*h/d[(60&b[j+2])>>>2],f=j+c,b.byteLength<f)return;if(this.trigger("data",{pts:a.pts+k*i,dts:a.dts+k*i,sampleCount:h,audioobjecttype:(b[j+2]>>>6&3)+1,channelcount:(1&b[j+2])<<3|(192&b[j+3])>>>6,samplerate:d[(60&b[j+2])>>>2],samplingfrequencyindex:(60&b[j+2])>>>2,samplesize:16,data:b.subarray(j+7+e,f)}),b.byteLength===f)return void(b=void 0);k++,b=b.subarray(f)}else j++}},c.prototype=new b.utils.Stream,b.codecs=b.codecs||{},b.codecs.AacStream=c}(this,this.muxjs),function(a,b){"use strict";var c,d;d=function(){var a,b,c=0;d.prototype.init.call(this),this.push=function(d){var e;for(b?(e=new Uint8Array(b.byteLength+d.data.byteLength),e.set(b),e.set(d.data,b.byteLength),b=e):b=d.data;c<b.byteLength-3;c++)if(1===b[c+2]){a=c+5;break}for(;a<b.byteLength;)switch(b[a]){case 0:if(0!==b[a-1]){a+=2;break}if(0!==b[a-2]){a++;break}this.trigger("data",b.subarray(c+3,a-2));do a++;while(1!==b[a]&&a<b.length);c=a-2,a+=3;break;case 1:if(0!==b[a-1]||0!==b[a-2]){a+=3;break}this.trigger("data",b.subarray(c+3,a-2)),c=a-2,a+=3;break;default:a+=3}b=b.subarray(c),a-=c,c=0},this.flush=function(){b&&b.byteLength>3&&this.trigger("data",b.subarray(c+3)),b=null,c=0,this.trigger("done")}},d.prototype=new b.utils.Stream,c=function(){var a,e,f,g,h,i,j,k=new d;c.prototype.init.call(this),a=this,this.push=function(a){"video"===a.type&&(e=a.trackId,f=a.pts,g=a.dts,k.push(a))},k.on("data",function(b){var c={trackId:e,pts:f,dts:g,data:b};switch(31&b[0]){case 5:c.nalUnitType="slice_layer_without_partitioning_rbsp_idr";break;case 6:c.nalUnitType="sei_rbsp",c.escapedRBSP=h(b.subarray(1));break;case 7:c.nalUnitType="seq_parameter_set_rbsp",c.escapedRBSP=h(b.subarray(1)),c.config=i(c.escapedRBSP);break;case 8:c.nalUnitType="pic_parameter_set_rbsp";break;case 9:c.nalUnitType="access_unit_delimiter_rbsp"}a.trigger("data",c)}),k.on("done",function(){a.trigger("done")}),this.flush=function(){k.flush()},j=function(a,b){var c,d,e=8,f=8;for(c=0;a>c;c++)0!==f&&(d=b.readExpGolomb(),f=(e+d+256)%256),e=0===f?e:f},h=function(a){for(var b,c,d=a.byteLength,e=[],f=1;d-2>f;)0===a[f]&&0===a[f+1]&&3===a[f+2]?(e.push(f+2),f+=2):f++;if(0===e.length)return a;b=d-e.length,c=new Uint8Array(b);var g=0;for(f=0;b>f;g++,f++)g===e[0]&&(g++,e.shift()),c[f]=a[g];return c},i=function(a){var c,d,e,f,g,h,i,k,l,m,n,o,p=0,q=0,r=0,s=0;if(c=new b.utils.ExpGolomb(a),d=c.readUnsignedByte(),f=c.readUnsignedByte(),e=c.readUnsignedByte(),c.skipUnsignedExpGolomb(),(100===d||110===d||122===d||244===d||44===d||83===d||86===d||118===d||128===d||138===d||139===d||134===d)&&(g=c.readUnsignedExpGolomb(),3===g&&c.skipBits(1),c.skipUnsignedExpGolomb(),c.skipUnsignedExpGolomb(),c.skipBits(1),c.readBoolean()))for(n=3!==g?8:12,o=0;n>o;o++)c.readBoolean()&&(6>o?j(16,c):j(64,c));if(c.skipUnsignedExpGolomb(),h=c.readUnsignedExpGolomb(),0===h)c.readUnsignedExpGolomb();else if(1===h)for(c.skipBits(1),c.skipExpGolomb(),c.skipExpGolomb(),i=c.readUnsignedExpGolomb(),o=0;i>o;o++)c.skipExpGolomb();return c.skipUnsignedExpGolomb(),c.skipBits(1),k=c.readUnsignedExpGolomb(),l=c.readUnsignedExpGolomb(),m=c.readBits(1),0===m&&c.skipBits(1),c.skipBits(1),c.readBoolean()&&(p=c.readUnsignedExpGolomb(),q=c.readUnsignedExpGolomb(),r=c.readUnsignedExpGolomb(),s=c.readUnsignedExpGolomb()),{profileIdc:d,levelIdc:e,profileCompatibility:f,width:16*(k+1)-2*p-2*q,height:(2-m)*(l+1)*16-2*r-2*s}}},c.prototype=new b.utils.Stream,b.codecs=b.codecs||{},b.codecs.H264Stream=c,b.codecs.NalByteStream=d}(this,this.muxjs),function(a,b,c){"use strict";var d,e,f,g,h,i,j,k;g=188,k=71,h=27,i=15,j=21,d=function(){var a=new Uint8Array(g),b=0;d.prototype.init.call(this),this.push=function(c){var d,e=0,f=g;for(b?(d=new Uint8Array(c.byteLength+b),d.set(a.subarray(0,b)),d.set(c,b),b=0):d=c;f<d.byteLength;)d[e]!==k||d[f]!==k?(e++,f++):(this.trigger("data",d.subarray(e,f)),e+=g,f+=g);e<d.byteLength&&(a.set(d.subarray(e),0),b=d.byteLength-e)},this.flush=function(){b===g&&a[0]===k&&(this.trigger("data",a),b=0),this.trigger("done")}},d.prototype=new b.utils.Stream,e=function(){var a,b,d,f;e.prototype.init.call(this),f=this,this.packetsWaitingForPmt=[],this.programMapTable=c,a=function(a,c){var e=0;c.payloadUnitStartIndicator&&(e+=a[e]+1),"pat"===c.type?b(a.subarray(e),c):d(a.subarray(e),c)},b=function(a,b){b.section_number=a[7],b.last_section_number=a[8],f.pmtPid=(31&a[10])<<8|a[11],b.pmtPid=f.pmtPid},d=function(a,b){var c,d,e,g;if(1&a[5]){for(f.programMapTable={},c=(15&a[1])<<8|a[2],d=3+c-4,e=(15&a[10])<<8|a[11],g=12+e;d>g;)f.programMapTable[(31&a[g+1])<<8|a[g+2]]=a[g],g+=((15&a[g+3])<<8|a[g+4])+5;for(b.programMapTable=f.programMapTable;f.packetsWaitingForPmt.length;)f.processPes_.apply(f,f.packetsWaitingForPmt.shift())}},this.push=function(b){var d={},e=4;d.payloadUnitStartIndicator=!!(64&b[1]),d.pid=31&b[1],d.pid<<=8,d.pid|=b[2],(48&b[3])>>>4>1&&(e+=b[e]+1),0===d.pid?(d.type="pat",a(b.subarray(e),d),this.trigger("data",d)):d.pid===this.pmtPid?(d.type="pmt",a(b.subarray(e),d),this.trigger("data",d)):this.programMapTable===c?this.packetsWaitingForPmt.push([b,e,d]):this.processPes_(b,e,d)},this.processPes_=function(a,b,c){c.streamType=this.programMapTable[c.pid],c.type="pes",c.data=a.subarray(b),this.trigger("data",c)}},e.prototype=new b.utils.Stream,e.STREAM_TYPES={h264:27,adts:15},f=function(){var a,b={data:[],size:0},c={data:[],size:0},d={data:[],size:0},e=function(a,b){var c;b.dataAlignmentIndicator=0!==(4&a[6]),c=a[7],192&c&&(b.pts=(14&a[9])<<27|(255&a[10])<<20|(254&a[11])<<12|(255&a[12])<<5|(254&a[13])>>>3,b.pts*=4,b.pts+=(6&a[13])>>>1,b.dts=b.pts,64&c&&(b.dts=(14&a[14])<<27|(255&a[15])<<20|(254&a[16])<<12|(255&a[17])<<5|(254&a[18])>>>3,b.dts*=4,b.dts+=(6&a[18])>>>1)),b.data=a.subarray(9+a[8])},g=function(b,c){var d,f=new Uint8Array(b.size),g={type:c},h=0;if(b.data.length){for(g.trackId=b.data[0].pid;b.data.length;)d=b.data.shift(),f.set(d.data,h),h+=d.data.byteLength;e(f,g),b.size=0,a.trigger("data",g)}};f.prototype.init.call(this),a=this,this.push=function(e){({pat:function(){},pes:function(){var a,f;switch(e.streamType){case h:a=b,f="video";break;case i:a=c,f="audio";break;case j:a=d,f="timed-metadata";break;default:return}e.payloadUnitStartIndicator&&g(a,f),a.data.push(e),a.size+=e.data.byteLength},pmt:function(){var b,c,d={type:"metadata",tracks:[]},f=e.programMapTable;for(b in f)f.hasOwnProperty(b)&&(c={timelineStartInfo:{baseMediaDecodeTime:0}},c.id=+b,f[b]===h?(c.codec="avc",c.type="video"):f[b]===i&&(c.codec="adts",c.type="audio"),d.tracks.push(c));a.trigger("data",d)}})[e.type]()},this.flush=function(){g(b,"video"),g(c,"audio"),g(d,"timed-metadata"),this.trigger("done")}},f.prototype=new b.utils.Stream,b.mp2t=b.mp2t||{},b.mp2t.PAT_PID=0,b.mp2t.MP2T_PACKET_LENGTH=g,b.mp2t.H264_STREAM_TYPE=h,b.mp2t.ADTS_STREAM_TYPE=i,b.mp2t.METADATA_STREAM_TYPE=j,b.mp2t.TransportPacketStream=d,b.mp2t.TransportParseStream=e,b.mp2t.ElementaryStream=f}(this,this.muxjs),function(a,b){"use strict";var c;c=function(a,b){var d,e=0,f=16384,g=function(a,b){var c,d=a.position+b;d<a.bytes.byteLength||(c=new Uint8Array(2*d),c.set(a.bytes.subarray(0,a.position),0),a.bytes=c,a.view=new DataView(a.bytes.buffer))},h=c.widthBytes||new Uint8Array("width".length),i=c.heightBytes||new Uint8Array("height".length),j=c.videocodecidBytes||new Uint8Array("videocodecid".length);if(!c.widthBytes){for(d=0;d<"width".length;d++)h[d]="width".charCodeAt(d);for(d=0;d<"height".length;d++)i[d]="height".charCodeAt(d);for(d=0;d<"videocodecid".length;d++)j[d]="videocodecid".charCodeAt(d);c.widthBytes=h,c.heightBytes=i,c.videocodecidBytes=j}switch(this.keyFrame=!1,a){case c.VIDEO_TAG:this.length=16,f*=6;break;case c.AUDIO_TAG:this.length=13,this.keyFrame=!0;break;case c.METADATA_TAG:this.length=29,this.keyFrame=!0;break;default:throw"Error Unknown TagType"}this.bytes=new Uint8Array(f),this.view=new DataView(this.bytes.buffer),this.bytes[0]=a,this.position=this.length,this.keyFrame=b,this.pts=0,this.dts=0,this.writeBytes=function(a,b,c){var d,e=b||0;c=c||a.byteLength,d=e+c,g(this,c),this.bytes.set(a.subarray(e,d),this.position),this.position+=c,this.length=Math.max(this.length,this.position)},this.writeByte=function(a){g(this,1),this.bytes[this.position]=a,this.position++,this.length=Math.max(this.length,this.position)},this.writeShort=function(a){g(this,2),this.view.setUint16(this.position,a),this.position+=2,this.length=Math.max(this.length,this.position)},this.negIndex=function(a){return this.bytes[this.length-a]},this.nalUnitSize=function(){return 0===e?0:this.length-(e+4)},this.startNalUnit=function(){if(e>0)throw new Error("Attempted to create new NAL wihout closing the old one");e=this.length,this.length+=4,this.position=this.length},this.endNalUnit=function(a){var b,c;this.length===e+4?this.length-=4:e>0&&(b=e+4,c=this.length-b,this.position=e,this.view.setUint32(this.position,c),this.position=this.length,a&&a.push(this.bytes.subarray(b,b+c))),e=0},this.writeMetaDataDouble=function(a,b){var c;if(g(this,2+a.length+9),this.view.setUint16(this.position,a.length),this.position+=2,"width"===a)this.bytes.set(h,this.position),this.position+=5;else if("height"===a)this.bytes.set(i,this.position),this.position+=6;else if("videocodecid"===a)this.bytes.set(j,this.position),this.position+=12;else for(c=0;c<a.length;c++)this.bytes[this.position]=a.charCodeAt(c),this.position++;this.position++,this.view.setFloat64(this.position,b),this.position+=8,this.length=Math.max(this.length,this.position),++e},this.writeMetaDataBoolean=function(a,b){var c;for(g(this,2),this.view.setUint16(this.position,a.length),this.position+=2,c=0;c<a.length;c++)console.assert(a.charCodeAt(c)<255),g(this,1),this.bytes[this.position]=a.charCodeAt(c),this.position++;g(this,2),this.view.setUint8(this.position,1),this.position++,this.view.setUint8(this.position,b?1:0),this.position++,this.length=Math.max(this.length,this.position),++e},this.finalize=function(){var a,d;switch(this.bytes[0]){case c.VIDEO_TAG:this.bytes[11]=7|(this.keyFrame||b?16:32),this.bytes[12]=b?0:1,a=this.pts-this.dts,this.bytes[13]=(16711680&a)>>>16,this.bytes[14]=(65280&a)>>>8,this.bytes[15]=(255&a)>>>0;break;case c.AUDIO_TAG:this.bytes[11]=175,this.bytes[12]=b?0:1;break;case c.METADATA_TAG:this.position=11,this.view.setUint8(this.position,2),this.position++,this.view.setUint16(this.position,10),this.position+=2,this.bytes.set([111,110,77,101,116,97,68,97,116,97],this.position),this.position+=10,this.bytes[this.position]=8,this.position++,this.view.setUint32(this.position,e),this.position=this.length,this.bytes.set([0,0,9],this.position),this.position+=3,this.length=this.position}return d=this.length-11,this.bytes[1]=(16711680&d)>>>16,this.bytes[2]=(65280&d)>>>8,this.bytes[3]=(255&d)>>>0,this.bytes[4]=(16711680&this.dts)>>>16,this.bytes[5]=(65280&this.dts)>>>8,this.bytes[6]=(255&this.dts)>>>0,this.bytes[7]=(4278190080&this.dts)>>>24,this.bytes[8]=0,this.bytes[9]=0,this.bytes[10]=0,g(this,4),this.view.setUint32(this.length,this.length),this.length+=4,this.position+=4,this.bytes=this.bytes.subarray(0,this.length),this.frameTime=c.frameTime(this.bytes),console.assert(this.bytes.byteLength===this.length),this}},c.AUDIO_TAG=8,c.VIDEO_TAG=9,c.METADATA_TAG=18,c.isAudioFrame=function(a){return c.AUDIO_TAG===a[0]},c.isVideoFrame=function(a){return c.VIDEO_TAG===a[0]},c.isMetaData=function(a){return c.METADATA_TAG===a[0]},c.isKeyFrame=function(a){return c.isVideoFrame(a)?23===a[11]:c.isAudioFrame(a)?!0:c.isMetaData(a)?!0:!1},c.frameTime=function(a){var b=a[4]<<16;return b|=a[5]<<8,b|=a[6]<<0,b|=a[7]<<24},b.flv=b.flv||{},b.flv.FlvTag=c}(this,this.muxjs),function(a,b,c){"use strict";var d,e=function(a,b,c){var d,e="";for(d=b;c>d;d++)e+="%"+("00"+a[d].toString(16)).slice(-2);return e},f=function(b,c,d){return a.decodeURIComponent(e(b,c,d))},g=function(b,c,d){return a.unescape(e(b,c,d))},h=function(a){return a[0]<<21|a[1]<<14|a[2]<<7|a[3]},i={TXXX:function(a){var b;if(3===a.data[0]){for(b=1;b<a.data.length;b++)if(0===a.data[b]){a.description=f(a.data,1,b),a.value=f(a.data,b+1,a.data.length-1);break}a.data=a.value}},WXXX:function(a){var b;if(3===a.data[0])for(b=1;b<a.data.length;b++)if(0===a.data[b]){a.description=f(a.data,1,b),a.url=f(a.data,b+1,a.data.length);break}},PRIV:function(a){var b;for(b=0;b<a.data.length;b++)if(0===a.data[b]){a.owner=g(a.data,0,b);break}a.privateData=a.data.subarray(b+1),a.data=a.privateData}};d=function(a){var c,e={debug:!(!a||!a.debug),descriptor:a&&a.descriptor},f=0,g=[],j=0;if(d.prototype.init.call(this),this.dispatchType=b.mp2t.METADATA_STREAM_TYPE.toString(16),e.descriptor)for(c=0;c<e.descriptor.length;c++)this.dispatchType+=("00"+e.descriptor[c].toString(16)).slice(-2);this.push=function(a){var b,c,d,k,l;if("timed-metadata"===a.type){if(a.dataAlignmentIndicator&&(j=0,g.length=0),0===g.length&&(a.data.length<10||a.data[0]!=="I".charCodeAt(0)||a.data[1]!=="D".charCodeAt(0)||a.data[2]!=="3".charCodeAt(0)))return void(e.debug&&console.log("Skipping unrecognized metadata packet"));if(g.push(a),j+=a.data.byteLength,1===g.length&&(f=h(a.data.subarray(6,10)),f+=10),!(f>j)){for(b={data:new Uint8Array(f),frames:[],pts:g[0].pts,dts:g[0].dts},l=0;f>l;)b.data.set(g[0].data.subarray(0,f-l),l),l+=g[0].data.byteLength,j-=g[0].data.byteLength,g.shift();c=10,64&b.data[5]&&(c+=4,c+=h(b.data.subarray(10,14)),f-=h(b.data.subarray(16,20)));do{if(d=h(b.data.subarray(c+4,c+8)),1>d)return console.log("Malformed ID3 frame encountered. Skipping metadata parsing.");k={id:String.fromCharCode(b.data[c],b.data[c+1],b.data[c+2],b.data[c+3]),data:b.data.subarray(c+10,c+d+10)},k.key=k.id,i[k.id]&&i[k.id](k),b.frames.push(k),c+=10,c+=d}while(f>c);this.trigger("data",b)}}}},d.prototype=new b.utils.Stream,b.mp2t=b.mp2t||{},b.mp2t.MetadataStream=d}(this,this.muxjs),function(a,b,c){"use strict";var d=4,e=128,f=function(a){for(var b=0,c={payloadType:-1,payloadSize:0},f=0,g=0;b<a.byteLength&&a[b]!==e;){for(;255===a[b];)f+=255,b++;for(f+=a[b++];255===a[b];)g+=255,b++;if(g+=a[b++],!c.payload&&f===d){c.payloadType=f,c.payloadSize=g,c.payload=a.subarray(b,b+g);break}b+=g,f=0,g=0}return c},g=function(a){return 181!==a.payload[0]?null:49!==(a.payload[1]<<8|a.payload[2])?null:"GA94"!==String.fromCharCode(a.payload[3],a.payload[4],a.payload[5],a.payload[6])?null:3!==a.payload[7]?null:a.payload.subarray(8,a.payload.length-1)},h=function(a,b){var c,d,e,f,g=[];if(!(64&b[0]))return g;for(d=31&b[0],c=0;d>c;c++)e=3*c,f={type:3&b[e+2],pts:a},4&b[e+2]&&(f.ccData=b[e+3]<<8|b[e+4],g.push(f));return g},i=function(){i.prototype.init.call(this),this.captionPackets_=[],this.field1_=new w,this.field1_.on("data",this.trigger.bind(this,"data")),this.field1_.on("done",this.trigger.bind(this,"done"))};i.prototype=new b.utils.Stream,i.prototype.push=function(a){var b,c;"sei_rbsp"===a.nalUnitType&&(b=f(a.escapedRBSP),b.payloadType===d&&(c=g(b),c&&(this.captionPackets_=this.captionPackets_.concat(h(a.pts,c)))))},i.prototype.flush=function(){return this.captionPackets_.length?(this.captionPackets_.sort(function(a,b){return a.pts-b.pts}),this.captionPackets_.forEach(this.field1_.push,this.field1_),this.captionPackets_.length=0,void this.field1_.flush()):void this.field1_.flush()};var j={42:225,92:233,94:237,95:243,96:250,123:231,124:247,125:209,126:241,127:9608},k=0,l=5152,m=5167,n=5157,o=5158,p=5159,q=5165,r=5153,s=5164,t=5166,u=14,v=function(){for(var a=[],b=u+1;b--;)a.push("");return a},w=function(){w.prototype.init.call(this),this.mode_="popOn",this.topRow_=0,this.startPts_=0,this.displayed_=v(),this.nonDisplayed_=v(),this.lastControlCode_=null,this.push=function(a){var b,c,d,e;if(b=32639&a.ccData,b===this.lastControlCode_)return void(this.lastControlCode_=null);switch(4096===(61440&b)?this.lastControlCode_=b:this.lastControlCode_=null,b){case k:break;case l:this.mode_="popOn";break;case m:this.flushDisplayed(a.pts),c=this.displayed_,this.displayed_=this.nonDisplayed_,this.nonDisplayed_=c,this.startPts_=a.pts;break;case n:this.topRow_=u-1,this.mode_="rollUp";break;case o:this.topRow_=u-2,this.mode_="rollUp";break;case p:this.topRow_=u-3,this.mode_="rollUp";break;case q:this.flushDisplayed(a.pts),this.shiftRowsUp_(),this.startPts_=a.pts;break;case r:"popOn"===this.mode_?this.nonDisplayed_[u]=this.nonDisplayed_[u].slice(0,-1):this.displayed_[u]=this.displayed_[u].slice(0,-1);break;case s:this.flushDisplayed(a.pts),this.displayed_=v();break;case t:this.nonDisplayed_=v();break;default:if(d=b>>>8,e=255&b,d>=16&&23>=d&&e>=64&&127>=e&&(16!==d||96>e)&&(d=e=32),16===(240&d))return;this[this.mode_](a.pts,d,e)}}};w.prototype=new b.utils.Stream,w.prototype.flushDisplayed=function(a){var b,c;for(c=0;c<this.displayed_.length;c++)b=this.displayed_[c],b.length&&this.trigger("data",{startPts:this.startPts_,endPts:a,text:b})},w.prototype.popOn=function(a,b,c){var d=this.nonDisplayed_[u];b=j[b]||b,d+=String.fromCharCode(b),c=j[c]||c,d+=String.fromCharCode(c),this.nonDisplayed_[u]=d},w.prototype.rollUp=function(a,b,c){var d=this.displayed_[u];""===d&&(this.flushDisplayed(a),this.startPts_=a),b=j[b]||b,d+=String.fromCharCode(b),c=j[c]||c,d+=String.fromCharCode(c),this.displayed_[u]=d},w.prototype.shiftRowsUp_=function(){var a;for(a=0;a<this.topRow_;a++)this.displayed_[a]="";for(a=this.topRow_;u>a;a++)this.displayed_[a]=this.displayed_[a+1];this.displayed_[u]=""},b.mp2t=b.mp2t||{},b.mp2t.CaptionStream=i,b.mp2t.Cea608Stream=w}(this,this.muxjs),function(a,b){"use strict";var c,d,e,f,g,h,i,j=b.flv.FlvTag;b.MetadataStream;g=function(a,b){"number"==typeof b.pts&&(void 0===a.timelineStartInfo.pts?a.timelineStartInfo.pts=b.pts:a.timelineStartInfo.pts=Math.min(a.timelineStartInfo.pts,b.pts)),"number"==typeof b.dts&&(void 0===a.timelineStartInfo.dts?a.timelineStartInfo.dts=b.dts:a.timelineStartInfo.dts=Math.min(a.timelineStartInfo.dts,b.dts))},h=function(a,b){var c=new j(j.METADATA_TAG);return c.dts=b,c.pts=b,c.writeMetaDataDouble("videocodecid",7),c.writeMetaDataDouble("width",a.width),c.writeMetaDataDouble("height",a.height),c},i=function(a,b){var c,d=new j(j.VIDEO_TAG,!0);for(d.dts=b,d.pts=b,d.writeByte(1),d.writeByte(a.profileIdc),d.writeByte(a.profileCompatibility),d.writeByte(a.levelIdc),d.writeByte(255),d.writeByte(225),d.writeShort(a.sps[0].length),d.writeBytes(a.sps[0]),d.writeByte(a.pps.length),c=0;c<a.pps.length;++c)d.writeShort(a.pps[c].length),d.writeBytes(a.pps[c]);return d},e=function(a){var b,c=[];e.prototype.init.call(this),this.push=function(b){g(a,b),a&&void 0===a.channelcount&&(a.audioobjecttype=b.audioobjecttype,a.channelcount=b.channelcount,a.samplerate=b.samplerate,a.samplingfrequencyindex=b.samplingfrequencyindex,a.samplesize=b.samplesize,a.extraData=a.audioobjecttype<<11|a.samplingfrequencyindex<<7|a.channelcount<<3),b.pts=Math.round(b.pts/90),b.dts=Math.round(b.dts/90),c.push(b)},this.flush=function(){var d,e,f,g=[];if(0===c.length)return void this.trigger("done");for(f=-(1/0);c.length;)d=c.shift(),(a.extraData!==b||d.pts-f>=1e3)&&(e=new j(j.METADATA_TAG),e.pts=d.pts,e.dts=d.dts,e.writeMetaDataDouble("audiocodecid",10),e.writeMetaDataBoolean("stereo",2===a.channelcount),e.writeMetaDataDouble("audiosamplerate",a.samplerate),e.writeMetaDataDouble("audiosamplesize",16),g.push(e),b=a.extraData,e=new j(j.AUDIO_TAG,!0),e.pts=d.pts,e.dts=d.dts,e.view.setUint16(e.position,a.extraData),e.position+=2,e.length=Math.max(e.length,e.position),g.push(e),f=d.pts),e=new j(j.AUDIO_TAG),e.pts=d.pts,e.dts=d.dts,e.writeBytes(d.data),g.push(e);b=null,this.trigger("data",{track:a,tags:g}),this.trigger("done")}},e.prototype=new b.utils.Stream,d=function(a){var b,c,e=[];d.prototype.init.call(this),this.finishFrame=function(c,d){d&&(a.newMetadata&&(d.keyFrame||0===c.length)&&(c.push(h(b,d.pts)),c.push(i(a,d.pts)),a.newMetadata=!1),d.endNalUnit(),c.push(d))},this.push=function(b){g(a,b),b.pts=Math.round(b.pts/90),b.dts=Math.round(b.dts/90),e.push(b)},this.flush=function(){for(var d,f=[];e.length&&"access_unit_delimiter_rbsp"!==e[0].nalUnitType;)e.shift();if(0===e.length)return void this.trigger("done");for(;e.length;)d=e.shift(),"seq_parameter_set_rbsp"===d.nalUnitType?(a.newMetadata=!0,b=d.config,a.width=b.width,a.height=b.height,a.sps=[d.data],a.profileIdc=b.profileIdc,a.levelIdc=b.levelIdc,a.profileCompatibility=b.profileCompatibility,c.endNalUnit()):"pic_parameter_set_rbsp"===d.nalUnitType?(a.newMetadata=!0,a.pps=[d.data],c.endNalUnit()):"access_unit_delimiter_rbsp"===d.nalUnitType?(c&&this.finishFrame(f,c),c=new j(j.VIDEO_TAG),c.pts=d.pts,c.dts=d.dts):("slice_layer_without_partitioning_rbsp_idr"===d.nalUnitType&&(c.keyFrame=!0),c.endNalUnit()),c.startNalUnit(),c.writeBytes(d.data);c&&this.finishFrame(f,c),this.trigger("data",{track:a,tags:f}),this.trigger("done")}},d.prototype=new b.utils.Stream,f=function(a){this.numberOfTracks=0,this.metadataStream=a.metadataStream,this.videoTags=[],this.audioTags=[],this.videoTrack=null,this.audioTrack=null,this.pendingCaptions=[],this.pendingMetadata=[],this.pendingTracks=0,f.prototype.init.call(this),this.push=function(a){return a.text?this.pendingCaptions.push(a):a.frames?this.pendingMetadata.push(a):("video"===a.track.type&&(this.videoTrack=a.track,this.videoTags=a.tags,this.pendingTracks++),void("audio"===a.track.type&&(this.audioTrack=a.track,this.audioTags=a.tags,this.pendingTracks++)))}},f.prototype=new b.utils.Stream,f.prototype.flush=function(){var a,b,c,d,e={tags:{},captions:[],metadata:[]};if(!(this.pendingTracks<this.numberOfTracks)){for(this.videoTrack?d=this.videoTrack.timelineStartInfo.pts:this.audioTrack&&(d=this.audioTrack.timelineStartInfo.pts),e.tags.videoTags=this.videoTags,e.tags.audioTags=this.audioTags,c=0;c<this.pendingCaptions.length;c++)b=this.pendingCaptions[c],b.startTime=b.startPts-d,b.startTime/=9e4,b.endTime=b.endPts-d,b.endTime/=9e4,e.captions.push(b);for(c=0;c<this.pendingMetadata.length;c++)a=this.pendingMetadata[c],a.cueTime=a.pts-d,a.cueTime/=9e4,e.metadata.push(a);e.metadata.dispatchType=this.metadataStream.dispatchType,this.videoTrack=null,this.audioTrack=null,this.videoTags=[],this.audioTags=[],this.pendingCaptions.length=0,this.pendingMetadata.length=0,this.pendingTracks=0,this.trigger("data",e),this.trigger("done")}},c=function(a){var g,h,i,k,l,m,n,o,p,q=this;c.prototype.init.call(this),a=a||{},this.metadataStream=new b.mp2t.MetadataStream,a.metadataStream=this.metadataStream,g=new b.mp2t.TransportPacketStream,h=new b.mp2t.TransportParseStream,i=new b.mp2t.ElementaryStream,k=new b.codecs.AacStream,l=new b.codecs.H264Stream,p=new f(a),g.pipe(h).pipe(i),i.pipe(l),i.pipe(k),i.pipe(this.metadataStream).pipe(p),b.mp2t.CaptionStream&&(o=new b.mp2t.CaptionStream,l.pipe(o).pipe(p)),i.on("data",function(a){var b,c,f;if("metadata"===a.type){for(b=a.tracks.length;b--;)"video"===a.tracks[b].type?c=a.tracks[b]:"audio"===a.tracks[b].type&&(f=a.tracks[b]);c&&!m&&(p.numberOfTracks++,m=new d(c),l.pipe(m).pipe(p)),f&&!n&&(p.numberOfTracks++,n=new e(f),k.pipe(n).pipe(p))}}),this.push=function(a){g.push(a)},this.flush=function(){g.flush()},p.on("data",function(a){q.trigger("data",a)}),p.on("done",function(){q.trigger("done")}),this.getFlvHeader=function(a,b,c){var d,e,f,g=new Uint8Array(9),h=new DataView(g.buffer);return a=a||0,b=void 0===b?!0:b,c=void 0===c?!0:c,h.setUint8(0,70),h.setUint8(1,76),h.setUint8(2,86),h.setUint8(3,1),h.setUint8(4,(b?4:0)|(c?1:0)),h.setUint32(5,g.byteLength),0>=a?(e=new Uint8Array(g.byteLength+4),e.set(g),e.set([0,0,0,0],g.byteLength),e):(d=new j(j.METADATA_TAG),d.pts=d.dts=0,d.writeMetaDataDouble("duration",a),f=d.finalize().length,e=new Uint8Array(g.byteLength+f),e.set(g),e.set(h.byteLength,f),e)}},c.prototype=new b.utils.Stream,b.flv=b.flv||{},b.flv.Transmuxer=c}(this,this.muxjs),function(a,b,c){"use strict";var d,e,f,g,h,i,j,k,l,m=0,n=videojs.EventTarget,o="blob:vjs-media-source/";i=function(a){Object.defineProperties(a.frame,{id:{get:function(){return videojs.log.warn("cue.frame.id is deprecated. Use cue.value.key instead."),a.value.key}},value:{get:function(){return videojs.log.warn("cue.frame.value is deprecated. Use cue.value.data instead."),a.value.data}},privateData:{get:function(){return videojs.log.warn("cue.frame.privateData is deprecated. Use cue.value.data instead."),a.value.data}}})},j=function(a,b,c){var d,e;if(c)for(d=c.cues.length;d--;)e=c.cues[d],e.startTime<=b&&e.endTime>=a&&c.removeCue(e)},k=function(a,b,c){c.captions&&c.captions.length&&!a.inbandTextTrack_&&(a.inbandTextTrack_=b.player_.addTextTrack("captions","cc1")),c.metadata&&c.metadata.length&&!a.metadataTrack_&&(a.metadataTrack_=b.player_.addTextTrack("metadata","Timed Metadata"),a.metadataTrack_.inBandMetadataTrackDispatchType=c.metadata.dispatchType)},l=function(b,c,d){h=a.WebKitDataCue||a.VTTCue,c&&c.forEach(function(a){this.inbandTextTrack_.addCue(new h(a.startTime+this.timestampOffset,a.endTime+this.timestampOffset,a.text))},b),d&&d.forEach(function(a){var b=a.cueTime+this.timestampOffset;a.frames.forEach(function(a){var c=new h(b,b,a.value||a.url||a.data||"");c.frame=a,c.value=a,i(c),this.metadataTrack_.addCue(c)},this)},b)},d={mode:"auto"},videojs.MediaSource=function(a){var b=videojs.mergeOptions(d,a);return"html5"===b.mode||"auto"===b.mode&&videojs.MediaSource.supportsNativeMediaSources()?new videojs.HtmlMediaSource:new videojs.FlashMediaSource},videojs.MediaSource.supportsNativeMediaSources=function(){return!!a.MediaSource&&a.MediaSource.isTypeSupported('video/mp4;codecs="avc1.4d400d,mp4a.40.2"')},videojs.HtmlMediaSource=videojs.extend(n,{constructor:function(){var b,c=this;this.mediaSource_=new a.MediaSource;for(b in this.mediaSource_)b in videojs.HtmlMediaSource.prototype||"function"!=typeof this.mediaSource_[b]||(this[b]=this.mediaSource_[b].bind(this.mediaSource_));this.duration_=NaN,Object.defineProperty(this,"duration",{get:function(){return c.duration_===1/0?c.duration_:c.mediaSource_.duration},set:function(a){return c.duration_=a,a!==1/0?void(c.mediaSource_.duration=a):void 0}}),Object.defineProperty(this,"seekable",{get:function(){return this.duration_===1/0?videojs.createTimeRanges([[0,c.mediaSource_.duration]]):c.mediaSource_.seekable}}),Object.defineProperty(this,"readyState",{get:function(){return c.mediaSource_.readyState}}),this.sourceBuffers=[],["sourceopen","sourceclose","sourceended"].forEach(function(a){this.mediaSource_.addEventListener(a,this.trigger.bind(this))},this),this.on("sourceopen",function(a){var b=document.querySelector('[src="'+c.url_+'"]');b&&(c.player_=videojs(b.parentNode))}),this.on("sourceclose",function(a){this.sourceBuffers.forEach(function(a){a.transmuxer_&&a.transmuxer_.terminate()}),this.sourceBuffers.length=0})},addSeekableRange_:function(a,b){var c;if(this.duration!==1/0)throw c=new Error("MediaSource.addSeekableRange() can only be invoked when the duration is Infinity"),c.name="InvalidStateError",c.code=11,c;(b>this.mediaSource_.duration||isNaN(this.mediaSource_.duration))&&(this.mediaSource_.duration=b)},addSourceBuffer:function(a){var b,c,d,f,g=/avc1\.[\da-f]+/i,h=/mp4a\.\d+.\d+/i;return/^video\/mp2t/i.test(a)?(c=a.split(";").slice(1).join(";"),c=p(c),d=(c.match(g)||[])[0],f=(c.match(h)||[])[0],d&&d.length||(d="avc1.4d400d"),f&&f.length||(f="mp4a.40.2"),b=new e(this,[d,f]),this.sourceBuffers.push(b),b):(b=this.mediaSource_.addSourceBuffer(a),this.sourceBuffers.push(b),b)}});var p=function(a){return a.replace(/avc1\.(\d+)\.(\d+)/i,function(a,b,c){var d=("00"+Number(b).toString(16)).slice(-2),e=("00"+Number(c).toString(16)).slice(-2);return"avc1."+d+"00"+e})};f=function(a,b,c){return function(){return a[b]&&a[b].updating?void 0:a.trigger(c)}},e=videojs.extend(n,{constructor:function(a,b){var c=this;this.timestampOffset_=0,this.pendingBuffers_=[],this.bufferUpdating_=!1,this.mediaSource_=a,this.codecs_=b,this.transmuxer_=new Worker(URL.createObjectURL(new Blob(['var muxjs={},transmuxer,initOptions={};!function(a,b,c){var d=function(){this.init=function(){var a={};this.on=function(b,c){a[b]||(a[b]=[]),a[b].push(c)},this.off=function(b,c){var d;return a[b]?(d=a[b].indexOf(c),a[b].splice(d,1),d>-1):!1},this.trigger=function(b){var c,d,e,f;if(c=a[b])if(2===arguments.length)for(e=c.length,d=0;e>d;++d)c[d].call(this,arguments[1]);else{for(f=[],d=arguments.length,d=1;d<arguments.length;++d)f.push(arguments[d]);for(e=c.length,d=0;e>d;++d)c[d].apply(this,f)}},this.dispose=function(){a={}}}};d.prototype.pipe=function(a){return this.on("data",function(b){a.push(b)}),this.on("done",function(){a.flush()}),a},d.prototype.push=function(a){this.trigger("data",a)},d.prototype.flush=function(){this.trigger("done")},a.muxjs=a.muxjs||{},a.muxjs.utils=a.muxjs.utils||{},a.muxjs.utils.Stream=d}(this,this.muxjs),function(a,b){var c;c=function(a){var b=a.byteLength,c=0,d=0;this.length=function(){return 8*b},this.bitsAvailable=function(){return 8*b+d},this.loadWord=function(){var e=a.byteLength-b,f=new Uint8Array(4),g=Math.min(4,b);if(0===g)throw new Error("no bytes available");f.set(a.subarray(e,e+g)),c=new DataView(f.buffer).getUint32(0),d=8*g,b-=g},this.skipBits=function(a){var e;d>a?(c<<=a,d-=a):(a-=d,e=Math.floor(a/8),a-=8*e,b-=e,this.loadWord(),c<<=a,d-=a)},this.readBits=function(a){var e=Math.min(d,a),f=c>>>32-e;return console.assert(32>a,"Cannot read more than 32 bits at a time"),d-=e,d>0?c<<=e:b>0&&this.loadWord(),e=a-e,e>0?f<<e|this.readBits(e):f},this.skipLeadingZeros=function(){var a;for(a=0;d>a;++a)if(0!==(c&2147483648>>>a))return c<<=a,d-=a,a;return this.loadWord(),a+this.skipLeadingZeros()},this.skipUnsignedExpGolomb=function(){this.skipBits(1+this.skipLeadingZeros())},this.skipExpGolomb=function(){this.skipBits(1+this.skipLeadingZeros())},this.readUnsignedExpGolomb=function(){var a=this.skipLeadingZeros();return this.readBits(a+1)-1},this.readExpGolomb=function(){var a=this.readUnsignedExpGolomb();return 1&a?1+a>>>1:-1*(a>>>1)},this.readBoolean=function(){return 1===this.readBits(1)},this.readUnsignedByte=function(){return this.readBits(8)},this.loadWord()},a.muxjs=b||{},b.utils=b.utils||{},b.utils.ExpGolomb=c}(this,this.muxjs),function(a,b,c){"use strict";var d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P;O=a.Uint8Array,P=a.DataView,function(){var a;A={avc1:[],avcC:[],btrt:[],dinf:[],dref:[],esds:[],ftyp:[],hdlr:[],mdat:[],mdhd:[],mdia:[],mfhd:[],minf:[],moof:[],moov:[],mp4a:[],mvex:[],mvhd:[],sdtp:[],smhd:[],stbl:[],stco:[],stsc:[],stsd:[],stsz:[],stts:[],styp:[],tfdt:[],tfhd:[],traf:[],trak:[],trun:[],trex:[],tkhd:[],vmhd:[]};for(a in A)A.hasOwnProperty(a)&&(A[a]=[a.charCodeAt(0),a.charCodeAt(1),a.charCodeAt(2),a.charCodeAt(3)]);B=new O(["i".charCodeAt(0),"s".charCodeAt(0),"o".charCodeAt(0),"m".charCodeAt(0)]),D=new O(["a".charCodeAt(0),"v".charCodeAt(0),"c".charCodeAt(0),"1".charCodeAt(0)]),C=new O([0,0,0,1]),E=new O([0,0,0,0,0,0,0,0,118,105,100,101,0,0,0,0,0,0,0,0,0,0,0,0,86,105,100,101,111,72,97,110,100,108,101,114,0]),F=new O([0,0,0,0,0,0,0,0,115,111,117,110,0,0,0,0,0,0,0,0,0,0,0,0,83,111,117,110,100,72,97,110,100,108,101,114,0]),G={video:E,audio:F},J=new O([0,0,0,0,0,0,0,1,0,0,0,12,117,114,108,32,0,0,0,1]),I=new O([0,0,0,0,0,0,0,0]),K=new O([0,0,0,0,0,0,0,0]),L=K,M=new O([0,0,0,0,0,0,0,0,0,0,0,0]),N=K,H=new O([0,0,0,1,0,0,0,0,0,0,0,0])}(),d=function(a){var b,c,d,e=[],f=0;for(b=1;b<arguments.length;b++)e.push(arguments[b]);for(b=e.length;b--;)f+=e[b].byteLength;for(c=new O(f+8),d=new P(c.buffer,c.byteOffset,c.byteLength),d.setUint32(0,c.byteLength),c.set(a,4),b=0,f=8;b<e.length;b++)c.set(e[b],f),f+=e[b].byteLength;return c},e=function(){return d(A.dinf,d(A.dref,J))},f=function(a){return d(A.esds,new O([0,0,0,0,3,25,0,0,0,4,17,64,21,0,6,0,0,0,218,192,0,0,218,192,5,2,a.audioobjecttype<<3|a.samplingfrequencyindex>>>1,a.samplingfrequencyindex<<7|a.channelcount<<3,6,1,2]))},g=function(){return d(A.ftyp,B,C,B,D)},s=function(a){return d(A.hdlr,G[a])},h=function(a){return d(A.mdat,a)},r=function(a){var b=new O([0,0,0,0,0,0,0,2,0,0,0,3,0,1,95,144,a.duration>>>24&255,a.duration>>>16&255,a.duration>>>8&255,255&a.duration,85,196,0,0]);return a.samplerate&&(b[12]=a.samplerate>>>24&255,b[13]=a.samplerate>>>16&255,b[14]=a.samplerate>>>8&255,b[15]=255&a.samplerate),d(A.mdhd,b)},q=function(a){return d(A.mdia,r(a),s(a.type),j(a))},i=function(a){return d(A.mfhd,new O([0,0,0,0,(4278190080&a)>>24,(16711680&a)>>16,(65280&a)>>8,255&a]))},j=function(a){return d(A.minf,"video"===a.type?d(A.vmhd,H):d(A.smhd,I),e(),u(a))},k=function(a,b){for(var c=[],e=b.length;e--;)c[e]=x(b[e]);return d.apply(null,[A.moof,i(a)].concat(c))},l=function(a){for(var b=a.length,c=[];b--;)c[b]=o(a[b]);return d.apply(null,[A.moov,n(4294967295)].concat(c).concat(m(a)))},m=function(a){for(var b=a.length,c=[];b--;)c[b]=y(a[b]);return d.apply(null,[A.mvex].concat(c))},n=function(a){var b=new O([0,0,0,0,0,0,0,1,0,0,0,2,0,1,95,144,(4278190080&a)>>24,(16711680&a)>>16,(65280&a)>>8,255&a,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255]);return d(A.mvhd,b)},t=function(a){var b,c,e=a.samples||[],f=new O(4+e.length);for(c=0;c<e.length;c++)b=e[c].flags,f[c+4]=b.dependsOn<<4|b.isDependedOn<<2|b.hasRedundancy;return d(A.sdtp,f)},u=function(a){return d(A.stbl,v(a),d(A.stts,N),d(A.stsc,L),d(A.stsz,M),d(A.stco,K))},function(){var a,b;v=function(c){return d(A.stsd,new O([0,0,0,0,0,0,0,1]),"video"===c.type?a(c):b(c))},a=function(a){var b,c=a.sps||[],e=a.pps||[],f=[],g=[];for(b=0;b<c.length;b++)f.push((65280&c[b].byteLength)>>>8),f.push(255&c[b].byteLength),f=f.concat(Array.prototype.slice.call(c[b]));for(b=0;b<e.length;b++)g.push((65280&e[b].byteLength)>>>8),g.push(255&e[b].byteLength),g=g.concat(Array.prototype.slice.call(e[b]));return d(A.avc1,new O([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,(65280&a.width)>>8,255&a.width,(65280&a.height)>>8,255&a.height,0,72,0,0,0,72,0,0,0,0,0,0,0,1,19,118,105,100,101,111,106,115,45,99,111,110,116,114,105,98,45,104,108,115,0,0,0,0,0,0,0,0,0,0,0,0,0,24,17,17]),d(A.avcC,new O([1,a.profileIdc,a.profileCompatibility,a.levelIdc,255].concat([c.length]).concat(f).concat([e.length]).concat(g))),d(A.btrt,new O([0,28,156,128,0,45,198,192,0,45,198,192])))},b=function(a){return d(A.mp4a,new O([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,(65280&a.channelcount)>>8,255&a.channelcount,(65280&a.samplesize)>>8,255&a.samplesize,0,0,0,0,(65280&a.samplerate)>>8,255&a.samplerate,0,0]),f(a))}}(),w=function(){return d(A.styp,B,C,B)},p=function(a){var b=new O([0,0,0,7,0,0,0,0,0,0,0,0,(4278190080&a.id)>>24,(16711680&a.id)>>16,(65280&a.id)>>8,255&a.id,0,0,0,0,(4278190080&a.duration)>>24,(16711680&a.duration)>>16,(65280&a.duration)>>8,255&a.duration,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,(65280&a.width)>>8,255&a.width,0,0,(65280&a.height)>>8,255&a.height,0,0]);return d(A.tkhd,b)},x=function(a){var b,c,e,f,g;return b=d(A.tfhd,new O([0,0,0,58,(4278190080&a.id)>>24,(16711680&a.id)>>16,(65280&a.id)>>8,255&a.id,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0])),c=d(A.tfdt,new O([0,0,0,0,a.baseMediaDecodeTime>>>24&255,a.baseMediaDecodeTime>>>16&255,a.baseMediaDecodeTime>>>8&255,255&a.baseMediaDecodeTime])),g=88,"audio"===a.type?(e=z(a,g),d(A.traf,b,c,e)):(f=t(a),e=z(a,f.length+g),d(A.traf,b,c,e,f))},o=function(a){return a.duration=a.duration||4294967295,d(A.trak,p(a),q(a))},y=function(a){var b=new O([0,0,0,0,(4278190080&a.id)>>24,(16711680&a.id)>>16,(65280&a.id)>>8,255&a.id,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,1]);return"video"!==a.type&&(b[b.length-1]=0),d(A.trex,b)},function(){var a,b,e;e=function(a,b){var d=0,e=0,f=0,g=0;return a.length&&(a[0].duration!==c&&(d=1),a[0].size!==c&&(e=2),a[0].flags!==c&&(f=4),a[0].compositionTimeOffset!==c&&(g=8)),[0,0,d|e|f|g,1,(4278190080&a.length)>>>24,(16711680&a.length)>>>16,(65280&a.length)>>>8,255&a.length,(4278190080&b)>>>24,(16711680&b)>>>16,(65280&b)>>>8,255&b]},b=function(a,b){var c,f,g,h;for(f=a.samples||[],b+=20+16*f.length,c=e(f,b),h=0;h<f.length;h++)g=f[h],c=c.concat([(4278190080&g.duration)>>>24,(16711680&g.duration)>>>16,(65280&g.duration)>>>8,255&g.duration,(4278190080&g.size)>>>24,(16711680&g.size)>>>16,(65280&g.size)>>>8,255&g.size,g.flags.isLeading<<2|g.flags.dependsOn,g.flags.isDependedOn<<6|g.flags.hasRedundancy<<4|g.flags.paddingValue<<1|g.flags.isNonSyncSample,61440&g.flags.degradationPriority,15&g.flags.degradationPriority,(4278190080&g.compositionTimeOffset)>>>24,(16711680&g.compositionTimeOffset)>>>16,(65280&g.compositionTimeOffset)>>>8,255&g.compositionTimeOffset]);return d(A.trun,new O(c))},a=function(a,b){var c,f,g,h;for(f=a.samples||[],b+=20+8*f.length,c=e(f,b),h=0;h<f.length;h++)g=f[h],c=c.concat([(4278190080&g.duration)>>>24,(16711680&g.duration)>>>16,(65280&g.duration)>>>8,255&g.duration,(4278190080&g.size)>>>24,(16711680&g.size)>>>16,(65280&g.size)>>>8,255&g.size]);return d(A.trun,new O(c))},z=function(c,d){return"audio"===c.type?a(c,d):b(c,d)}}(),b.mp4={ftyp:g,mdat:h,moof:k,moov:l,initSegment:function(a){var b,c=g(),d=l(a);return b=new O(c.byteLength+d.byteLength),b.set(c),b.set(d,c.byteLength),b}}}(this,this.muxjs),function(a,b){"use strict";var c,d=[96e3,88200,64e3,48e3,44100,32e3,24e3,22050,16e3,12e3,11025,8e3,7350];c=function(){var a,b;c.prototype.init.call(this),a=this,this.push=function(a){var c,e,f,g,h,i,j=0,k=0;if("audio"===a.type)for(b?(g=b,b=new Uint8Array(g.byteLength+a.data.byteLength),b.set(g),b.set(a.data,g.byteLength)):b=a.data;j+5<b.length;)if(255===b[j]&&240===(246&b[j+1])){if(e=2*(1&~b[j+1]),c=(3&b[j+3])<<11|b[j+4]<<3|(224&b[j+5])>>5,h=1024*((3&b[j+6])+1),i=9e4*h/d[(60&b[j+2])>>>2],f=j+c,b.byteLength<f)return;if(this.trigger("data",{pts:a.pts+k*i,dts:a.dts+k*i,sampleCount:h,audioobjecttype:(b[j+2]>>>6&3)+1,channelcount:(1&b[j+2])<<3|(192&b[j+3])>>>6,samplerate:d[(60&b[j+2])>>>2],samplingfrequencyindex:(60&b[j+2])>>>2,samplesize:16,data:b.subarray(j+7+e,f)}),b.byteLength===f)return void(b=void 0);k++,b=b.subarray(f)}else j++}},c.prototype=new b.utils.Stream,b.codecs=b.codecs||{},b.codecs.AacStream=c}(this,this.muxjs),function(a,b){"use strict";var c,d;d=function(){var a,b,c=0;d.prototype.init.call(this),this.push=function(d){var e;for(b?(e=new Uint8Array(b.byteLength+d.data.byteLength),e.set(b),e.set(d.data,b.byteLength),b=e):b=d.data;c<b.byteLength-3;c++)if(1===b[c+2]){a=c+5;break}for(;a<b.byteLength;)switch(b[a]){case 0:if(0!==b[a-1]){a+=2;break}if(0!==b[a-2]){a++;break}this.trigger("data",b.subarray(c+3,a-2));do a++;while(1!==b[a]&&a<b.length);c=a-2,a+=3;break;case 1:if(0!==b[a-1]||0!==b[a-2]){a+=3;break}this.trigger("data",b.subarray(c+3,a-2)),c=a-2,a+=3;break;default:a+=3}b=b.subarray(c),a-=c,c=0},this.flush=function(){b&&b.byteLength>3&&this.trigger("data",b.subarray(c+3)),b=null,c=0,this.trigger("done")}},d.prototype=new b.utils.Stream,c=function(){var a,e,f,g,h,i,j,k=new d;c.prototype.init.call(this),a=this,this.push=function(a){"video"===a.type&&(e=a.trackId,f=a.pts,g=a.dts,k.push(a))},k.on("data",function(b){var c={trackId:e,pts:f,dts:g,data:b};switch(31&b[0]){case 5:c.nalUnitType="slice_layer_without_partitioning_rbsp_idr";break;case 6:c.nalUnitType="sei_rbsp",c.escapedRBSP=h(b.subarray(1));break;case 7:c.nalUnitType="seq_parameter_set_rbsp",c.escapedRBSP=h(b.subarray(1)),c.config=i(c.escapedRBSP);break;case 8:c.nalUnitType="pic_parameter_set_rbsp";break;case 9:c.nalUnitType="access_unit_delimiter_rbsp"}a.trigger("data",c)}),k.on("done",function(){a.trigger("done")}),this.flush=function(){k.flush()},j=function(a,b){var c,d,e=8,f=8;for(c=0;a>c;c++)0!==f&&(d=b.readExpGolomb(),f=(e+d+256)%256),e=0===f?e:f},h=function(a){for(var b,c,d=a.byteLength,e=[],f=1;d-2>f;)0===a[f]&&0===a[f+1]&&3===a[f+2]?(e.push(f+2),f+=2):f++;if(0===e.length)return a;b=d-e.length,c=new Uint8Array(b);var g=0;for(f=0;b>f;g++,f++)g===e[0]&&(g++,e.shift()),c[f]=a[g];return c},i=function(a){var c,d,e,f,g,h,i,k,l,m,n,o,p=0,q=0,r=0,s=0;if(c=new b.utils.ExpGolomb(a),d=c.readUnsignedByte(),f=c.readUnsignedByte(),e=c.readUnsignedByte(),c.skipUnsignedExpGolomb(),(100===d||110===d||122===d||244===d||44===d||83===d||86===d||118===d||128===d||138===d||139===d||134===d)&&(g=c.readUnsignedExpGolomb(),3===g&&c.skipBits(1),c.skipUnsignedExpGolomb(),c.skipUnsignedExpGolomb(),c.skipBits(1),c.readBoolean()))for(n=3!==g?8:12,o=0;n>o;o++)c.readBoolean()&&(6>o?j(16,c):j(64,c));if(c.skipUnsignedExpGolomb(),h=c.readUnsignedExpGolomb(),0===h)c.readUnsignedExpGolomb();else if(1===h)for(c.skipBits(1),c.skipExpGolomb(),c.skipExpGolomb(),i=c.readUnsignedExpGolomb(),o=0;i>o;o++)c.skipExpGolomb();return c.skipUnsignedExpGolomb(),c.skipBits(1),k=c.readUnsignedExpGolomb(),l=c.readUnsignedExpGolomb(),m=c.readBits(1),0===m&&c.skipBits(1),c.skipBits(1),c.readBoolean()&&(p=c.readUnsignedExpGolomb(),q=c.readUnsignedExpGolomb(),r=c.readUnsignedExpGolomb(),s=c.readUnsignedExpGolomb()),{profileIdc:d,levelIdc:e,profileCompatibility:f,width:16*(k+1)-2*p-2*q,height:(2-m)*(l+1)*16-2*r-2*s}}},c.prototype=new b.utils.Stream,b.codecs=b.codecs||{},b.codecs.H264Stream=c,b.codecs.NalByteStream=d}(this,this.muxjs),function(a,b,c){"use strict";var d,e,f,g,h,i,j,k;g=188,k=71,h=27,i=15,j=21,d=function(){var a=new Uint8Array(g),b=0;d.prototype.init.call(this),this.push=function(c){var d,e=0,f=g;for(b?(d=new Uint8Array(c.byteLength+b),d.set(a.subarray(0,b)),d.set(c,b),b=0):d=c;f<d.byteLength;)d[e]!==k||d[f]!==k?(e++,f++):(this.trigger("data",d.subarray(e,f)),e+=g,f+=g);e<d.byteLength&&(a.set(d.subarray(e),0),b=d.byteLength-e)},this.flush=function(){b===g&&a[0]===k&&(this.trigger("data",a),b=0),this.trigger("done")}},d.prototype=new b.utils.Stream,e=function(){var a,b,d,f;e.prototype.init.call(this),f=this,this.packetsWaitingForPmt=[],this.programMapTable=c,a=function(a,c){var e=0;c.payloadUnitStartIndicator&&(e+=a[e]+1),"pat"===c.type?b(a.subarray(e),c):d(a.subarray(e),c)},b=function(a,b){b.section_number=a[7],b.last_section_number=a[8],f.pmtPid=(31&a[10])<<8|a[11],b.pmtPid=f.pmtPid},d=function(a,b){var c,d,e,g;if(1&a[5]){for(f.programMapTable={},c=(15&a[1])<<8|a[2],d=3+c-4,e=(15&a[10])<<8|a[11],g=12+e;d>g;)f.programMapTable[(31&a[g+1])<<8|a[g+2]]=a[g],g+=((15&a[g+3])<<8|a[g+4])+5;for(b.programMapTable=f.programMapTable;f.packetsWaitingForPmt.length;)f.processPes_.apply(f,f.packetsWaitingForPmt.shift())}},this.push=function(b){var d={},e=4;d.payloadUnitStartIndicator=!!(64&b[1]),d.pid=31&b[1],d.pid<<=8,d.pid|=b[2],(48&b[3])>>>4>1&&(e+=b[e]+1),0===d.pid?(d.type="pat",a(b.subarray(e),d),this.trigger("data",d)):d.pid===this.pmtPid?(d.type="pmt",a(b.subarray(e),d),this.trigger("data",d)):this.programMapTable===c?this.packetsWaitingForPmt.push([b,e,d]):this.processPes_(b,e,d)},this.processPes_=function(a,b,c){c.streamType=this.programMapTable[c.pid],c.type="pes",c.data=a.subarray(b),this.trigger("data",c)}},e.prototype=new b.utils.Stream,e.STREAM_TYPES={h264:27,adts:15},f=function(){var a,b={data:[],size:0},c={data:[],size:0},d={data:[],size:0},e=function(a,b){var c;b.dataAlignmentIndicator=0!==(4&a[6]),c=a[7],192&c&&(b.pts=(14&a[9])<<27|(255&a[10])<<20|(254&a[11])<<12|(255&a[12])<<5|(254&a[13])>>>3,b.pts*=4,b.pts+=(6&a[13])>>>1,b.dts=b.pts,64&c&&(b.dts=(14&a[14])<<27|(255&a[15])<<20|(254&a[16])<<12|(255&a[17])<<5|(254&a[18])>>>3,b.dts*=4,b.dts+=(6&a[18])>>>1)),b.data=a.subarray(9+a[8])},g=function(b,c){var d,f=new Uint8Array(b.size),g={type:c},h=0;if(b.data.length){for(g.trackId=b.data[0].pid;b.data.length;)d=b.data.shift(),f.set(d.data,h),h+=d.data.byteLength;e(f,g),b.size=0,a.trigger("data",g)}};f.prototype.init.call(this),a=this,this.push=function(e){({pat:function(){},pes:function(){var a,f;switch(e.streamType){case h:a=b,f="video";break;case i:a=c,f="audio";break;case j:a=d,f="timed-metadata";break;default:return}e.payloadUnitStartIndicator&&g(a,f),a.data.push(e),a.size+=e.data.byteLength},pmt:function(){var b,c,d={type:"metadata",tracks:[]},f=e.programMapTable;for(b in f)f.hasOwnProperty(b)&&(c={timelineStartInfo:{baseMediaDecodeTime:0}},c.id=+b,f[b]===h?(c.codec="avc",c.type="video"):f[b]===i&&(c.codec="adts",c.type="audio"),d.tracks.push(c));a.trigger("data",d)}})[e.type]()},this.flush=function(){g(b,"video"),g(c,"audio"),g(d,"timed-metadata"),this.trigger("done")}},f.prototype=new b.utils.Stream,b.mp2t=b.mp2t||{},b.mp2t.PAT_PID=0,b.mp2t.MP2T_PACKET_LENGTH=g,b.mp2t.H264_STREAM_TYPE=h,b.mp2t.ADTS_STREAM_TYPE=i,b.mp2t.METADATA_STREAM_TYPE=j,b.mp2t.TransportPacketStream=d,b.mp2t.TransportParseStream=e,b.mp2t.ElementaryStream=f}(this,this.muxjs),function(a,b,c){"use strict";var d=4,e=128,f=function(a){for(var b=0,c={payloadType:-1,payloadSize:0},f=0,g=0;b<a.byteLength&&a[b]!==e;){for(;255===a[b];)f+=255,b++;for(f+=a[b++];255===a[b];)g+=255,b++;if(g+=a[b++],!c.payload&&f===d){c.payloadType=f,c.payloadSize=g,c.payload=a.subarray(b,b+g);break}b+=g,f=0,g=0}return c},g=function(a){return 181!==a.payload[0]?null:49!==(a.payload[1]<<8|a.payload[2])?null:"GA94"!==String.fromCharCode(a.payload[3],a.payload[4],a.payload[5],a.payload[6])?null:3!==a.payload[7]?null:a.payload.subarray(8,a.payload.length-1)},h=function(a,b){var c,d,e,f,g=[];if(!(64&b[0]))return g;for(d=31&b[0],c=0;d>c;c++)e=3*c,f={type:3&b[e+2],pts:a},4&b[e+2]&&(f.ccData=b[e+3]<<8|b[e+4],g.push(f));return g},i=function(){i.prototype.init.call(this),this.captionPackets_=[],this.field1_=new w,this.field1_.on("data",this.trigger.bind(this,"data")),this.field1_.on("done",this.trigger.bind(this,"done"))};i.prototype=new b.utils.Stream,i.prototype.push=function(a){var b,c;"sei_rbsp"===a.nalUnitType&&(b=f(a.escapedRBSP),b.payloadType===d&&(c=g(b),c&&(this.captionPackets_=this.captionPackets_.concat(h(a.pts,c)))))},i.prototype.flush=function(){return this.captionPackets_.length?(this.captionPackets_.sort(function(a,b){return a.pts-b.pts}),this.captionPackets_.forEach(this.field1_.push,this.field1_),this.captionPackets_.length=0,void this.field1_.flush()):void this.field1_.flush()};var j={42:225,92:233,94:237,95:243,96:250,123:231,124:247,125:209,126:241,127:9608},k=0,l=5152,m=5167,n=5157,o=5158,p=5159,q=5165,r=5153,s=5164,t=5166,u=14,v=function(){for(var a=[],b=u+1;b--;)a.push("");return a},w=function(){w.prototype.init.call(this),this.mode_="popOn",this.topRow_=0,this.startPts_=0,this.displayed_=v(),this.nonDisplayed_=v(),this.lastControlCode_=null,this.push=function(a){var b,c,d,e;if(b=32639&a.ccData,b===this.lastControlCode_)return void(this.lastControlCode_=null);switch(4096===(61440&b)?this.lastControlCode_=b:this.lastControlCode_=null,b){case k:break;case l:this.mode_="popOn";break;case m:this.flushDisplayed(a.pts),c=this.displayed_,this.displayed_=this.nonDisplayed_,this.nonDisplayed_=c,this.startPts_=a.pts;break;case n:this.topRow_=u-1,this.mode_="rollUp";break;case o:this.topRow_=u-2,this.mode_="rollUp";break;case p:this.topRow_=u-3,this.mode_="rollUp";break;case q:this.flushDisplayed(a.pts),this.shiftRowsUp_(),this.startPts_=a.pts;break;case r:"popOn"===this.mode_?this.nonDisplayed_[u]=this.nonDisplayed_[u].slice(0,-1):this.displayed_[u]=this.displayed_[u].slice(0,-1);break;case s:this.flushDisplayed(a.pts),this.displayed_=v();break;case t:this.nonDisplayed_=v();break;default:if(d=b>>>8,e=255&b,d>=16&&23>=d&&e>=64&&127>=e&&(16!==d||96>e)&&(d=e=32),16===(240&d))return;this[this.mode_](a.pts,d,e)}}};w.prototype=new b.utils.Stream,w.prototype.flushDisplayed=function(a){var b,c;for(c=0;c<this.displayed_.length;c++)b=this.displayed_[c],b.length&&this.trigger("data",{startPts:this.startPts_,endPts:a,text:b})},w.prototype.popOn=function(a,b,c){var d=this.nonDisplayed_[u];b=j[b]||b,d+=String.fromCharCode(b),c=j[c]||c,d+=String.fromCharCode(c),this.nonDisplayed_[u]=d},w.prototype.rollUp=function(a,b,c){var d=this.displayed_[u];""===d&&(this.flushDisplayed(a),this.startPts_=a),b=j[b]||b,d+=String.fromCharCode(b),c=j[c]||c,d+=String.fromCharCode(c),this.displayed_[u]=d},w.prototype.shiftRowsUp_=function(){var a;for(a=0;a<this.topRow_;a++)this.displayed_[a]="";for(a=this.topRow_;u>a;a++)this.displayed_[a]=this.displayed_[a+1];this.displayed_[u]=""},b.mp2t=b.mp2t||{},b.mp2t.CaptionStream=i,b.mp2t.Cea608Stream=w}(this,this.muxjs),function(a,b,c){"use strict";var d,e=function(a,b,c){var d,e="";for(d=b;c>d;d++)e+="%"+("00"+a[d].toString(16)).slice(-2);return e},f=function(b,c,d){return a.decodeURIComponent(e(b,c,d))},g=function(b,c,d){return a.unescape(e(b,c,d))},h=function(a){return a[0]<<21|a[1]<<14|a[2]<<7|a[3]},i={TXXX:function(a){var b;if(3===a.data[0]){for(b=1;b<a.data.length;b++)if(0===a.data[b]){a.description=f(a.data,1,b),a.value=f(a.data,b+1,a.data.length-1);break}a.data=a.value}},WXXX:function(a){var b;if(3===a.data[0])for(b=1;b<a.data.length;b++)if(0===a.data[b]){a.description=f(a.data,1,b),a.url=f(a.data,b+1,a.data.length);break}},PRIV:function(a){var b;for(b=0;b<a.data.length;b++)if(0===a.data[b]){a.owner=g(a.data,0,b);break}a.privateData=a.data.subarray(b+1),a.data=a.privateData}};d=function(a){var c,e={debug:!(!a||!a.debug),descriptor:a&&a.descriptor},f=0,g=[],j=0;if(d.prototype.init.call(this),this.dispatchType=b.mp2t.METADATA_STREAM_TYPE.toString(16),e.descriptor)for(c=0;c<e.descriptor.length;c++)this.dispatchType+=("00"+e.descriptor[c].toString(16)).slice(-2);this.push=function(a){var b,c,d,k,l;if("timed-metadata"===a.type){if(a.dataAlignmentIndicator&&(j=0,g.length=0),0===g.length&&(a.data.length<10||a.data[0]!=="I".charCodeAt(0)||a.data[1]!=="D".charCodeAt(0)||a.data[2]!=="3".charCodeAt(0)))return void(e.debug&&console.log("Skipping unrecognized metadata packet"));if(g.push(a),j+=a.data.byteLength,1===g.length&&(f=h(a.data.subarray(6,10)),f+=10),!(f>j)){for(b={data:new Uint8Array(f),frames:[],pts:g[0].pts,dts:g[0].dts},l=0;f>l;)b.data.set(g[0].data.subarray(0,f-l),l),l+=g[0].data.byteLength,j-=g[0].data.byteLength,g.shift();c=10,64&b.data[5]&&(c+=4,c+=h(b.data.subarray(10,14)),f-=h(b.data.subarray(16,20)));do{if(d=h(b.data.subarray(c+4,c+8)),1>d)return console.log("Malformed ID3 frame encountered. Skipping metadata parsing.");k={id:String.fromCharCode(b.data[c],b.data[c+1],b.data[c+2],b.data[c+3]),data:b.data.subarray(c+10,c+d+10)},k.key=k.id,i[k.id]&&i[k.id](k),b.frames.push(k),c+=10,c+=d}while(f>c);this.trigger("data",b)}}}},d.prototype=new b.utils.Stream,b.mp2t=b.mp2t||{},b.mp2t.MetadataStream=d}(this,this.muxjs),function(a,b,c){"use strict";var d,e,f,g,h,i,j,k=b.mp4;e=function(a){var b=[],c=0,d=0,f=0;e.prototype.init.call(this),this.push=function(d){h(a,d),a&&(a.audioobjecttype=d.audioobjecttype,a.channelcount=d.channelcount,a.samplerate=d.samplerate,a.samplingfrequencyindex=d.samplingfrequencyindex,a.samplesize=d.samplesize),b.push(d),c+=d.data.byteLength},this.setEarliestDts=function(b){f=b-a.timelineStartInfo.baseMediaDecodeTime},this.flush=function(){var e,g,h,l,m,n,o;if(0===c)return void this.trigger("done");for(a.minSegmentDts<f&&(a.minSegmentDts=1/0,b=b.filter(function(b){return b.dts>=f?(a.minSegmentDts=Math.min(a.minSegmentDts,b.dts),a.minSegmentPts=a.minSegmentDts,!0):(c-=b.data.byteLength,!1)})),h=new Uint8Array(c),a.samples=[],m=0;b.length;)g=b[0],l={size:g.data.byteLength,duration:1024},a.samples.push(l),h.set(g.data,m),m+=g.data.byteLength,b.shift();c=0,n=k.mdat(h),j(a),o=k.moof(d,[a]),e=new Uint8Array(o.byteLength+n.byteLength),d++,e.set(o),e.set(n,o.byteLength),i(a),this.trigger("data",{track:a,boxes:e}),this.trigger("done")}},e.prototype=new b.utils.Stream,d=function(a){var b,e,f=0,g=[],l=0;d.prototype.init.call(this),delete a.minPTS,this.push=function(c){h(a,c),"seq_parameter_set_rbsp"!==c.nalUnitType||b||(b=c.config,a.width=b.width,a.height=b.height,a.sps=[c.data],a.profileIdc=b.profileIdc,a.levelIdc=b.levelIdc,a.profileCompatibility=b.profileCompatibility),"pic_parameter_set_rbsp"!==c.nalUnitType||e||(e=c.data,a.pps=[c.data]),g.push(c),l+=c.data.byteLength},this.flush=function(){for(var d,h,m,n,o,p,q,r,s,t;g.length&&"access_unit_delimiter_rbsp"!==g[0].nalUnitType;)g.shift();if(0===l)return void this.trigger("done");for(q=new Uint8Array(l+4*g.length),r=new DataView(q.buffer),a.samples=[],s={size:0,flags:{isLeading:0,dependsOn:1,isDependedOn:0,hasRedundancy:0,degradationPriority:0}},p=0;g.length;)h=g[0],"access_unit_delimiter_rbsp"===h.nalUnitType&&(d&&(s.duration=h.dts-d.dts,a.samples.push(s)),s={size:0,flags:{isLeading:0,dependsOn:1,isDependedOn:0,hasRedundancy:0,degradationPriority:0},dataOffset:p,compositionTimeOffset:h.pts-h.dts},d=h),"slice_layer_without_partitioning_rbsp_idr"===h.nalUnitType&&(s.flags.dependsOn=2),s.size+=4,s.size+=h.data.byteLength,r.setUint32(p,h.data.byteLength),p+=4,q.set(h.data,p),p+=h.data.byteLength,g.shift();for(a.samples.length&&(s.duration=a.samples[a.samples.length-1].duration),a.samples.push(s),t=0;a.samples.length;){if(s=a.samples[0],2===s.flags.dependsOn){q=q.subarray(s.dataOffset),s.duration+=t;break}t+=s.duration,a.samples.shift()}l=0,n=k.mdat(q),j(a),this.trigger("timelineStartInfo",a.timelineStartInfo),m=k.moof(f,[a]),o=new Uint8Array(m.byteLength+n.byteLength),f++,o.set(m),o.set(n,m.byteLength),i(a),this.trigger("data",{track:a,boxes:o}),b=c,e=c,this.trigger("done")}},d.prototype=new b.utils.Stream,h=function(a,b){"number"==typeof b.pts&&(a.timelineStartInfo.pts===c&&(a.timelineStartInfo.pts=b.pts),a.minSegmentPts===c?a.minSegmentPts=b.pts:a.minSegmentPts=Math.min(a.minSegmentPts,b.pts),a.maxSegmentPts===c?a.maxSegmentPts=b.pts:a.maxSegmentPts=Math.max(a.maxSegmentPts,b.pts)),"number"==typeof b.dts&&(a.timelineStartInfo.dts===c&&(a.timelineStartInfo.dts=b.dts),a.minSegmentDts===c?a.minSegmentDts=b.dts:a.minSegmentDts=Math.min(a.minSegmentDts,b.dts),a.maxSegmentDts===c?a.maxSegmentDts=b.dts:a.maxSegmentDts=Math.max(a.maxSegmentDts,b.dts))},i=function(a){delete a.minSegmentDts,delete a.maxSegmentDts,delete a.minSegmentPts,delete a.maxSegmentPts},j=function(a){var b,c=9e4,d=a.minSegmentDts-a.timelineStartInfo.dts,e=a.minSegmentPts-a.minSegmentDts;a.baseMediaDecodeTime=a.timelineStartInfo.baseMediaDecodeTime,a.baseMediaDecodeTime+=d,a.baseMediaDecodeTime-=e,a.baseMediaDecodeTime=Math.max(0,a.baseMediaDecodeTime),"audio"===a.type&&(b=a.samplerate/c,a.baseMediaDecodeTime*=b,a.baseMediaDecodeTime=Math.floor(a.baseMediaDecodeTime))},g=function(a){this.numberOfTracks=0,this.metadataStream=a.metadataStream,"undefined"!=typeof a.remux?this.remuxTracks=!!a.remux:this.remuxTracks=!0,this.pendingTracks=[],this.videoTrack=null,this.pendingBoxes=[],this.pendingCaptions=[],this.pendingMetadata=[],this.pendingBytes=0,this.emittedTracks=0,g.prototype.init.call(this),this.push=function(a){return a.text?this.pendingCaptions.push(a):a.frames?this.pendingMetadata.push(a):(this.pendingTracks.push(a.track),this.pendingBoxes.push(a.boxes),this.pendingBytes+=a.boxes.byteLength,"video"===a.track.type&&(this.videoTrack=a.track),void("audio"===a.track.type&&(this.audioTrack=a.track)))}},g.prototype=new b.utils.Stream,g.prototype.flush=function(){var a,c,d,e,f=0,g={captions:[],metadata:[]},h=0;if(!(0===this.pendingTracks.length||this.remuxTracks&&this.pendingTracks.length<this.numberOfTracks)){for(this.videoTrack?h=this.videoTrack.timelineStartInfo.pts:this.audioTrack&&(h=this.audioTrack.timelineStartInfo.pts),1===this.pendingTracks.length?g.type=this.pendingTracks[0].type:g.type="combined",this.emittedTracks+=this.pendingTracks.length,d=b.mp4.initSegment(this.pendingTracks),g.data=new Uint8Array(d.byteLength+this.pendingBytes),g.data.set(d),f+=d.byteLength,e=0;e<this.pendingBoxes.length;e++)g.data.set(this.pendingBoxes[e],f),f+=this.pendingBoxes[e].byteLength;for(e=0;e<this.pendingCaptions.length;e++)a=this.pendingCaptions[e],a.startTime=a.startPts-h,a.startTime/=9e4,a.endTime=a.endPts-h,a.endTime/=9e4,g.captions.push(a);for(e=0;e<this.pendingMetadata.length;e++)c=this.pendingMetadata[e],c.cueTime=c.pts-h,c.cueTime/=9e4,g.metadata.push(c);g.metadata.dispatchType=this.metadataStream.dispatchType,this.pendingTracks.length=0,this.videoTrack=null,this.pendingBoxes.length=0,this.pendingCaptions.length=0,this.pendingBytes=0,this.pendingMetadata.length=0,this.trigger("data",g),this.emittedTracks>=this.numberOfTracks&&(this.trigger("done"),this.emittedTracks=0)}},f=function(a){var h,j,k,l,m,n,o,p,q,r,s,t=this;f.prototype.init.call(this),a=a||{},this.baseMediaDecodeTime=a.baseMediaDecodeTime||0,this.metadataStream=new b.mp2t.MetadataStream,a.metadataStream=this.metadataStream,k=new b.mp2t.TransportPacketStream,l=new b.mp2t.TransportParseStream,m=new b.mp2t.ElementaryStream,n=new b.codecs.AacStream,o=new b.codecs.H264Stream,s=new g(a),k.pipe(l).pipe(m),m.pipe(o),m.pipe(n),m.pipe(this.metadataStream).pipe(s),b.mp2t.CaptionStream&&(r=new b.mp2t.CaptionStream,o.pipe(r).pipe(s)),m.on("data",function(a){var b;if("metadata"===a.type){for(b=a.tracks.length;b--;)h||"video"!==a.tracks[b].type?j||"audio"!==a.tracks[b].type||(j=a.tracks[b],j.timelineStartInfo.baseMediaDecodeTime=t.baseMediaDecodeTime):(h=a.tracks[b],h.timelineStartInfo.baseMediaDecodeTime=t.baseMediaDecodeTime);h&&!p&&(s.numberOfTracks++,p=new d(h),p.on("timelineStartInfo",function(a){j&&(j.timelineStartInfo=a,q.setEarliestDts(a.dts))}),o.pipe(p).pipe(s)),j&&!q&&(s.numberOfTracks++,q=new e(j),n.pipe(q).pipe(s))}}),this.setBaseMediaDecodeTime=function(a){this.baseMediaDecodeTime=a,j&&(j.timelineStartInfo.dts=c,j.timelineStartInfo.pts=c,i(j),j.timelineStartInfo.baseMediaDecodeTime=a),h&&(h.timelineStartInfo.dts=c,h.timelineStartInfo.pts=c,i(h),h.timelineStartInfo.baseMediaDecodeTime=a)},this.push=function(a){k.push(a)},this.flush=function(){k.flush()},s.on("data",function(a){t.trigger("data",a)}),s.on("done",function(){t.trigger("done")})},f.prototype=new b.utils.Stream,b.mp4=b.mp4||{},b.mp4.VideoSegmentStream=d,b.mp4.AudioSegmentStream=e,b.mp4.Transmuxer=f}(this,this.muxjs);var wireTransmuxerEvents=function(a){a.on("data",function(a){var b=a.data;a.data=b.buffer,postMessage({action:"data",segment:a,byteOffset:b.byteOffset,byteLength:b.byteLength},[a.data])}),a.captionStream&&a.captionStream.on("data",function(a){postMessage({action:"caption",data:a})}),a.on("done",function(a){postMessage({action:"done"})})},messageHandlers={init:function(a){initOptions=a&&a.options||{},this.defaultInit()},defaultInit:function(){transmuxer&&transmuxer.dispose(),transmuxer=new muxjs.mp4.Transmuxer(initOptions),wireTransmuxerEvents(transmuxer)},push:function(a){var b=new Uint8Array(a.data,a.byteOffset,a.byteLength);transmuxer.push(b)},reset:function(){this.defaultInit()},setTimestampOffset:function(a){var b=a.timestampOffset||0;transmuxer.setBaseMediaDecodeTime(Math.round(9e4*b))},flush:function(a){transmuxer.flush()}};onmessage=function(a){transmuxer||"init"===a.data.action||messageHandlers.defaultInit(),a.data&&a.data.action&&messageHandlers[a.data.action]&&messageHandlers[a.data.action](a.data)};'],{
type:"application/javascript"}))),this.transmuxer_.postMessage({action:"init",options:{remux:!1}}),this.transmuxer_.onmessage=function(a){return"data"===a.data.action?c.data_(a):"done"===a.data.action?c.done_(a):void 0},Object.defineProperty(this,"timestampOffset",{get:function(){return this.timestampOffset_},set:function(a){"number"==typeof a&&a>=0&&(this.timestampOffset_=a,this.transmuxer_.postMessage({action:"setTimestampOffset",timestampOffset:a}))}}),Object.defineProperty(this,"appendWindowStart",{get:function(){return(this.videoBuffer_||this.audioBuffer_).appendWindowStart},set:function(a){this.videoBuffer_&&(this.videoBuffer_.appendWindowStart=a),this.audioBuffer_&&(this.audioBuffer_.appendWindowStart=a)}}),Object.defineProperty(this,"updating",{get:function(){return this.bufferUpdating_||this.audioBuffer_&&this.audioBuffer_.updating||this.videoBuffer_&&this.videoBuffer_.updating}}),Object.defineProperty(this,"buffered",{get:function(){var a=null,b=null,c=0,d=[],e=[];if(!(this.videoBuffer_&&0!==this.videoBuffer_.buffered.length||this.audioBuffer_&&0!==this.audioBuffer_.buffered.length))return videojs.createTimeRange();if(!this.videoBuffer_)return this.audioBuffer_.buffered;if(!this.audioBuffer_)return this.videoBuffer_.buffered;for(var f=this.videoBuffer_.buffered,g=this.audioBuffer_.buffered,h=f.length;h--;)d.push({time:f.start(h),type:"start"}),d.push({time:f.end(h),type:"end"});for(h=g.length;h--;)d.push({time:g.start(h),type:"start"}),d.push({time:g.end(h),type:"end"});for(d.sort(function(a,b){return a.time-b.time}),h=0;h<d.length;h++)"start"===d[h].type?(c++,2===c&&(a=d[h].time)):"end"===d[h].type&&(c--,1===c&&(b=d[h].time)),null!==a&&null!==b&&(e.push([a,b]),a=null,b=null);return videojs.createTimeRanges(e)}})},data_:function(a){var b=a.data.segment,c=this.mediaSource_.mediaSource_;b.data=new Uint8Array(b.data,a.data.byteOffset,a.data.byteLength),"video"===b.type?this.videoBuffer_||(this.videoBuffer_=c.addSourceBuffer('video/mp4;codecs="'+this.codecs_[0]+'"'),this.videoBuffer_.addEventListener("updatestart",f(this,"audioBuffer_","updatestart")),this.videoBuffer_.addEventListener("update",f(this,"audioBuffer_","update")),this.videoBuffer_.addEventListener("updateend",f(this,"audioBuffer_","updateend"))):"audio"===b.type?this.audioBuffer_||(this.audioBuffer_=c.addSourceBuffer('audio/mp4;codecs="'+this.codecs_[1]+'"'),this.audioBuffer_.addEventListener("updatestart",f(this,"videoBuffer_","updatestart")),this.audioBuffer_.addEventListener("update",f(this,"videoBuffer_","update")),this.audioBuffer_.addEventListener("updateend",f(this,"videoBuffer_","updateend"))):"combined"===b.type&&(this.videoBuffer_||(this.videoBuffer_=c.addSourceBuffer('video/mp4;codecs="'+this.codecs_.join(",")+'"'),this.videoBuffer_.addEventListener("updatestart",f(this,"videoBuffer_","updatestart")),this.videoBuffer_.addEventListener("update",f(this,"videoBuffer_","update")),this.videoBuffer_.addEventListener("updateend",f(this,"videoBuffer_","updateend")))),k(this,this.mediaSource_,b),this.pendingBuffers_.push(b)},done_:function(){this.processPendingSegments_()},appendBuffer:function(a){this.bufferUpdating_=!0,this.transmuxer_.postMessage({action:"push",data:a.buffer,byteOffset:a.byteOffset,byteLength:a.byteLength},[a.buffer]),this.transmuxer_.postMessage({action:"flush"})},remove:function(a,b){this.videoBuffer_&&this.videoBuffer_.remove(a,b),this.audioBuffer_&&this.audioBuffer_.remove(a,b),j(a,b,this.metadataTrack_),j(a,b,this.inbandTextTrack_)},processPendingSegments_:function(){var a={video:{segments:[],bytes:0},audio:{segments:[],bytes:0},captions:[],metadata:[]};a=this.pendingBuffers_.reduce(function(a,b){var c=b.type,d=b.data;return"combined"===c&&(c="video"),a[c].segments.push(d),a[c].bytes+=d.byteLength,b.captions&&(a.captions=a.captions.concat(b.captions)),b.metadata&&(a.metadata=a.metadata.concat(b.metadata)),a},a),l(this,a.captions,a.metadata),this.concatAndAppendSegments_(a.video,this.videoBuffer_),this.concatAndAppendSegments_(a.audio,this.audioBuffer_),this.pendingBuffers_.length=0,this.bufferUpdating_=!1},concatAndAppendSegments_:function(a,b){var c,d=0;a.bytes&&(c=new Uint8Array(a.bytes),a.segments.forEach(function(a){c.set(a,d),d+=a.byteLength}),b.appendBuffer(c))},abort:function(){this.videoBuffer_&&this.videoBuffer_.abort(),this.audioBuffer_&&this.audioBuffer_.abort(),this.transmuxer_&&this.transmuxer_.postMessage({action:"reset"}),this.pendingBuffers_.length=0,this.bufferUpdating_=!1}}),videojs.FlashMediaSource=videojs.extend(n,{constructor:function(){var a=this;this.sourceBuffers=[],this.readyState="closed",this.on(["sourceopen","webkitsourceopen"],function(b){this.swfObj=document.getElementById(b.swfId),this.player_=videojs(this.swfObj.parentNode),this.tech_=this.swfObj.tech,this.readyState="open",this.tech_.on("seeking",function(){for(var b=a.sourceBuffers.length;b--;)a.sourceBuffers[b].abort()}),this.swfObj&&this.swfObj.vjs_load()})},addSeekableRange_:function(){}}),videojs.FlashMediaSource.TIME_BETWEEN_TICKS=Math.floor(1e3/480),videojs.FlashMediaSource.TIME_PER_TICK=Math.floor(1e3/240),videojs.FlashMediaSource.BYTES_PER_CHUNK=1024,videojs.FlashMediaSource.MIN_CHUNK=1024,videojs.FlashMediaSource.MAX_CHUNK=1048576,videojs.FlashMediaSource.prototype.addSourceBuffer=function(a){var b;if(0!==a.indexOf("video/mp2t"))throw new Error("NotSupportedError (Video.js)");return b=new videojs.FlashSourceBuffer(this),this.sourceBuffers.push(b),b};try{Object.defineProperty(videojs.FlashMediaSource.prototype,"duration",{get:function(){return this.swfObj?this.swfObj.vjs_getProperty("duration"):NaN},set:function(a){var b,c=this.swfObj.vjs_getProperty("duration");if(this.swfObj.vjs_setProperty("duration",a),c>a)for(b=0;b<this.sourceBuffers.length;b++)this.sourceBuffers[b].remove(a,c);return a}})}catch(q){videojs.FlashMediaSource.prototype.duration=NaN}videojs.FlashMediaSource.prototype.endOfStream=function(a){"network"===a?this.tech_.error(2):"decode"===a&&this.tech_.error(3),"ended"!==this.readyState&&(this.readyState="ended",this.swfObj.vjs_endOfStream())},videojs.mediaSources={},videojs.MediaSource.open=function(a,b){var c=videojs.mediaSources[a];if(!c)throw new Error("Media Source not found (Video.js)");c.trigger({type:"sourceopen",swfId:b})},g=function(b){a.setTimeout(b,videojs.FlashMediaSource.TIME_BETWEEN_TICKS)},videojs.FlashSourceBuffer=videojs.extend(n,{constructor:function(c){var d,e=this;this.chunkSize_=videojs.FlashMediaSource.BYTES_PER_CHUNK,this.buffer_=[],this.bufferSize_=0,this.basePtsOffset_=NaN,this.mediaSource=c,this.updating=!1,this.timestampOffset_=0,this.segmentParser_=new b.flv.Transmuxer,this.segmentParser_.on("data",this.receiveBuffer_.bind(this)),d=a.btoa(String.fromCharCode.apply(null,Array.prototype.slice.call(this.segmentParser_.getFlvHeader()))),this.mediaSource.swfObj.vjs_appendBuffer(d),Object.defineProperty(this,"timestampOffset",{get:function(){return this.timestampOffset_},set:function(a){"number"==typeof a&&a>=0&&(this.timestampOffset_=a,this.segmentParser_=new b.flv.Transmuxer,this.segmentParser_.on("data",this.receiveBuffer_.bind(this)),this.mediaSource.swfObj.vjs_discontinuity(),this.basePtsOffset_=NaN)}}),Object.defineProperty(this,"buffered",{get:function(){return videojs.createTimeRanges(this.mediaSource.swfObj.vjs_getProperty("buffered"))}}),this.mediaSource.player_.on("seeked",function(){j(0,1/0,e.metadataTrack_),j(0,1/0,e.inbandTextTrack_)})},appendBuffer:function(a){var b,c=this;if(this.updating)throw b=new Error("SourceBuffer.append() cannot be called while an update is in progress"),b.name="InvalidStateError",b.code=11,b;this.updating=!0,this.mediaSource.readyState="open",this.trigger({type:"update"});var d=524288,e=0;!function f(){c.segmentParser_.push(a.subarray(e,e+d)),e+=d,g(e<a.byteLength?f:c.segmentParser_.flush.bind(c.segmentParser_))}()},abort:function(){this.buffer_=[],this.bufferSize_=0,this.mediaSource.swfObj.vjs_abort(),this.updating&&(this.updating=!1,this.trigger({type:"updateend"}))},remove:function(a,b){j(a,b,this.metadataTrack_),j(a,b,this.inbandTextTrack_),this.trigger({type:"update"}),this.trigger({type:"updateend"})},receiveBuffer_:function(a){var b=this;k(this,this.mediaSource,a),l(this,a.captions,a.metadata),g(function(){0===b.buffer_.length&&g(b.processBuffer_.bind(b));var c=b.convertTagsToData_(a);c&&(b.buffer_.push(c),b.bufferSize_+=c.byteLength)})},processBuffer_:function(){var b,c,d,e,f,h,i=0,j=0,k=+new Date;if(!this.buffer_.length)return void(this.updating!==!1&&(this.updating=!1,this.trigger({type:"updateend"})));do{for(j++,b=this.buffer_[0].subarray(i,i+this.chunkSize_),b.byteLength<this.chunkSize_||this.buffer_[0].byteLength===i+this.chunkSize_?(i=0,this.buffer_.shift()):i+=this.chunkSize_,this.bufferSize_-=b.byteLength,e="",d=b.byteLength,c=0;d>c;c++)e+=String.fromCharCode(b[c]);f=a.btoa(e),this.mediaSource.swfObj.CallFunction('<invoke name="vjs_appendBuffer"returntype="javascript"><arguments><string>'+f+"</string></arguments></invoke>"),h=new Date-k}while(this.buffer_.length&&h<videojs.FlashMediaSource.TIME_PER_TICK);this.buffer_.length&&i&&(this.buffer_[0]=this.buffer_[0].subarray(i)),h>=videojs.FlashMediaSource.TIME_PER_TICK&&(this.chunkSize_=Math.floor(this.chunkSize_*(j/4))),this.chunkSize_=Math.max(videojs.FlashMediaSource.MIN_CHUNK,Math.min(this.chunkSize_,videojs.FlashMediaSource.MAX_CHUNK)),0!==this.bufferSize_?g(this.processBuffer_.bind(this)):(this.updating=!1,this.trigger({type:"updateend"}))},convertTagsToData_:function(a){var b,c,d,e=0,f=this.mediaSource.tech_,g=0,h=[],i=this.getOrderedTags_(a);for(isNaN(this.basePtsOffset_)&&i.length&&(this.basePtsOffset_=i[0].pts),f.buffered().length&&(g=f.buffered().end(0)-this.timestampOffset),g=Math.max(g,f.currentTime()-this.timestampOffset),g*=1e3,g+=this.basePtsOffset_,b=0;b<i.length;b++)i[b].pts>=g&&h.push(i[b]);if(0!==h.length){for(b=0;b<h.length;b++)e+=h[b].bytes.byteLength;for(d=new Uint8Array(e),b=0,c=0;b<h.length;b++)d.set(h[b].bytes,c),c+=h[b].bytes.byteLength;return d}},getOrderedTags_:function(a){for(var b,c=a.tags.videoTags,d=a.tags.audioTags,e=[];c.length||d.length;)b=c.length?d.length&&d[0].dts<c[0].dts?d.shift():c.shift():d.shift(),e.push(b.finalize());return e}}),videojs.URL={createObjectURL:function(b){var c;return b instanceof videojs.HtmlMediaSource?(c=a.URL.createObjectURL(b.mediaSource_),b.url_=c,c):b instanceof videojs.FlashMediaSource?(c=o+m,m++,videojs.mediaSources[c]=b,c):(c=a.URL.createObjectURL(b),b.url_=c,c)}}}(this,this.muxjs),function(a,b,c,d){"use strict";var e,f,g,h=1.2,i=3e5,j=1/30,k=b.getComponent("Component"),l=500;f=function(a){return a.retries&&a.retries>=2},b.Hls={},b.HlsHandler=b.extend(k,{constructor:function(a,c){var d,e=this;k.call(this,a),a.options_&&a.options_.playerId&&(d=b(a.options_.playerId),d.hls||Object.defineProperty(d,"hls",{get:function(){return b.log.warn("player.hls is deprecated. Use player.tech.hls instead."),e}})),this.tech_=a,this.source_=c.source,this.mode_=c.mode,this.pendingSegment_=null,this.bandwidth=c.bandwidth||4194304,this.bytesReceived=0,this.loadingState_="none","none"!==this.tech_.preload()&&(this.loadingState_="meta"),this.startCheckingBuffer_(),this.on(this.tech_,"seeking",function(){this.setCurrentTime(this.tech_.currentTime())}),this.on(this.tech_,"error",function(){this.stopCheckingBuffer_()}),this.on(this.tech_,"play",this.play)}}),b.Hls.canPlaySource=function(){return b.log.warn("HLS is no longer a tech. Please remove it from your player's techOrder.")},b.HlsSourceHandler=function(a){return{canHandleSource:function(a){return b.HlsSourceHandler.canPlayType(a.type)},handleSource:function(c,d){return"flash"===a&&d.setTimeout(function(){d.trigger("loadstart")},1),d.hls=new b.HlsHandler(d,{source:c,mode:a}),d.hls.src(c.src),d.hls},canPlayType:function(a){return b.HlsSourceHandler.canPlayType(a)}}},b.HlsSourceHandler.canPlayType=function(a){var c=/^application\/(?:x-|vnd\.apple\.)mpegurl/i;return b.Hls.supportsNativeHls?!1:c.test(a)},b.MediaSource.supportsNativeMediaSources()&&b.getComponent("Html5").registerSourceHandler(b.HlsSourceHandler("html5")),a.Uint8Array&&b.getComponent("Flash").registerSourceHandler(b.HlsSourceHandler("flash")),b.Hls.GOAL_BUFFER_LENGTH=30,b.HlsHandler.prototype.src=function(a){var c;a&&(this.mediaSource=new b.MediaSource({mode:this.mode_}),this.mediaSource.addEventListener("sourceopen",this.handleSourceOpen.bind(this)),this.options_={},this.source_.withCredentials!==d?this.options_.withCredentials=this.source_.withCredentials:b.options.hls&&(this.options_.withCredentials=b.options.hls.withCredentials),this.playlists=new b.Hls.PlaylistLoader(this.source_.src,this.options_.withCredentials),this.tech_.one("canplay",this.setupFirstPlay.bind(this)),this.playlists.on("loadedmetadata",function(){c=this.playlists.media(),c.endList&&"metadata"!==this.tech_.preload()&&"none"!==this.tech_.preload()&&(this.loadingState_="segments"),this.setupSourceBuffer_(),this.setupFirstPlay(),this.fillBuffer(),this.tech_.trigger("loadedmetadata")}.bind(this)),this.playlists.on("error",function(){this.blacklistCurrentPlaylist_(this.playlists.error)}.bind(this)),this.playlists.on("loadedplaylist",function(){var a,b=this.playlists.media();return b?(this.updateDuration(this.playlists.media()),a=this.seekable(),this.duration()===1/0&&0!==a.length&&this.mediaSource.addSeekableRange_(a.start(0),a.end(0)),void(c=b)):void this.playlists.media(this.selectPlaylist())}.bind(this)),this.playlists.on("mediachange",function(){this.tech_.trigger({type:"mediachange",bubbles:!0})}.bind(this)),this.tech_.el()&&this.tech_.src(b.URL.createObjectURL(this.mediaSource)))},b.HlsHandler.prototype.handleSourceOpen=function(){this.sourceBuffer||this.setupSourceBuffer_(),this.tech_.autoplay()&&this.play()},b.Hls.findSoleUncommonTimeRangesEnd_=function(a,b){var c,d,e,f=[],g=[],h=function(a){return a[0]<=e&&a[1]>=e};if(a)for(c=0;c<a.length;c++)d=a.start(c),e=a.end(c),g.push([d,e]);if(b)for(c=0;c<b.length;c++)d=b.start(c),e=b.end(c),g.some(h)||f.push(e);return 1!==f.length?null:f[0]},b.HlsHandler.prototype.updateSegmentMetadata_=function(a,b,c){var d,e;a&&(d=a.segments[b],e=a.segments[b-1],c&&d&&(d.end=c,e?e.end&&(d.duration=d.end-e.end):d.duration=d.end))},b.HlsHandler.prototype.isEndOfStream_=function(a,b,c){var d,e,f=a.segments;return a?(d=b===f.length-1,e=c.length&&f[f.length-1].end<=c.end(0),a.endList&&"open"===this.mediaSource.readyState&&(d||e)):!1};var m=function(a){var b={codecCount:0,videoCodec:null,audioProfile:null};return b.codecCount=a.split(",").length,b.codecCount=b.codecCount||2,b.videoCodec=/(^|\s|,)+(avc1)[^ ,]*/i.exec(a),b.videoCodec=b.videoCodec&&b.videoCodec[2],b.audioProfile=/(^|\s|,)+mp4a.\d+\.(\d+)/i.exec(a),b.audioProfile=b.audioProfile&&b.audioProfile[2],b};b.HlsHandler.prototype.excludeIncompatibleVariants_=function(a){var b,c=this.playlists.master,d=2,e=null,f=null;a.attributes&&a.attributes.CODECS&&(b=m(a.attributes.CODECS),e=b.videoCodec,f=b.audioProfile,d=b.codecCount),c.playlists.forEach(function(a){var b={codecCount:2,videoCodec:null,audioProfile:null};a.attributes&&a.attributes.CODECS&&(b=m(a.attributes.CODECS)),b.codecCount!==d&&(a.excludeUntil=1/0),b.videoCodec!==e&&(a.excludeUntil=1/0),("5"===b.audioProfile&&"5"!==f||"5"===f&&"5"!==b.audioProfile)&&(a.excludeUntil=1/0)})},b.HlsHandler.prototype.setupSourceBuffer_=function(){var a,b=this.playlists.media();b&&"open"===this.mediaSource.readyState&&(a="video/mp2t",b.attributes&&b.attributes.CODECS&&(a+='; codecs="'+b.attributes.CODECS+'"'),this.sourceBuffer=this.mediaSource.addSourceBuffer(a),this.excludeIncompatibleVariants_(b),this.sourceBuffer.addEventListener("updateend",this.updateEndHandler_.bind(this)))},b.HlsHandler.prototype.setupFirstPlay=function(){var a,b;b=this.playlists.media(),this.duration()===1/0&&0===this.tech_.played().length&&!this.tech_.paused()&&this.sourceBuffer&&b&&this.tech_.readyState()>=1&&(this.playlists.trigger("firstplay"),a=this.seekable(),a.length&&this.tech_.setCurrentTime(a.end(0)))},b.HlsHandler.prototype.play=function(){return this.loadingState_="segments",this.tech_.ended()&&this.tech_.setCurrentTime(0),0===this.tech_.played().length?this.setupFirstPlay():void(this.duration()===1/0&&this.tech_.currentTime()<this.seekable().start(0)&&this.tech_.setCurrentTime(this.seekable().start(0)))},b.HlsHandler.prototype.setCurrentTime=function(a){var b=this.findBufferedRange_();return this.playlists&&this.playlists.media()&&this.playlists.media().segments?b&&b.length?a:this.pendingSegment_&&this.pendingSegment_.buffered?a:(this.lastSegmentLoaded_=null,this.cancelSegmentXhr(),this.keyXhr_&&(this.keyXhr_.aborted=!0,this.cancelKeyXhr()),void this.fillBuffer(this.playlists.getMediaIndexForTime_(a))):0},b.HlsHandler.prototype.duration=function(){var a=this.playlists;return a?this.mediaSource?this.mediaSource.duration:b.Hls.Playlist.duration(a.media()):0},b.HlsHandler.prototype.seekable=function(){var a,c;return this.playlists&&(a=this.playlists.media())?(c=b.Hls.Playlist.seekable(a),0===c.length?c:0===c.start(0)?b.createTimeRanges([[this.playlists.expired_,this.playlists.expired_+c.end(0)]]):c):b.createTimeRanges()},b.HlsHandler.prototype.updateDuration=function(a){var c=this.mediaSource.duration,d=b.Hls.Playlist.duration(a),e=this.tech_.buffered(),f=function(){this.mediaSource.duration=d,this.tech_.trigger("durationchange"),this.mediaSource.removeEventListener("sourceopen",f)}.bind(this);e.length>0&&(d=Math.max(d,e.end(e.length-1))),c!==d&&("open"!==this.mediaSource.readyState?this.mediaSource.addEventListener("sourceopen",f):this.sourceBuffer&&this.sourceBuffer.updating||(this.mediaSource.duration=d,this.tech_.trigger("durationchange")))},b.HlsHandler.prototype.resetSrc_=function(){this.cancelSegmentXhr(),this.cancelKeyXhr(),this.sourceBuffer&&"open"===this.mediaSource.readyState&&this.sourceBuffer.abort()},b.HlsHandler.prototype.cancelKeyXhr=function(){this.keyXhr_&&(this.keyXhr_.onreadystatechange=null,this.keyXhr_.abort(),this.keyXhr_=null)},b.HlsHandler.prototype.cancelSegmentXhr=function(){this.segmentXhr_&&(this.segmentXhr_.onreadystatechange=null,this.segmentXhr_.abort(),this.segmentXhr_=null),this.pendingSegment_=null},e=function(a,b){var c;return a?(c=getComputedStyle(a),c?c[b]:""):""},b.HlsHandler.prototype.dispose=function(){this.stopCheckingBuffer_(),this.playlists&&this.playlists.dispose(),this.resetSrc_(),k.prototype.dispose.call(this)},b.HlsHandler.prototype.selectPlaylist=function(){var a,c,f,g,i,j,k,l,m=this.playlists.master.playlists.slice(),n=[],o=+new Date;for(m.sort(b.Hls.comparePlaylistBandwidth),m=m.filter(function(a){return a.excludeUntil!==d?o>=a.excludeUntil:!0}),c=m.length;c--;)f=m[c],f.attributes&&f.attributes.BANDWIDTH&&(a=f.attributes.BANDWIDTH*h,a<this.bandwidth&&(n.push(f),g||(g=f)));for(c=n.length,n.sort(b.Hls.comparePlaylistResolution),f=null,k=parseInt(e(this.tech_.el(),"width"),10),l=parseInt(e(this.tech_.el(),"height"),10);c--;)if(f=n[c],f.attributes&&f.attributes.RESOLUTION&&f.attributes.RESOLUTION.width&&f.attributes.RESOLUTION.height){if(f.attributes.RESOLUTION.width===k&&f.attributes.RESOLUTION.height===l){i=null,j=f;break}if(f.attributes.RESOLUTION.width<k&&f.attributes.RESOLUTION.height<l)break;(!i||f.attributes.RESOLUTION.width<i.attributes.RESOLUTION.width&&f.attributes.RESOLUTION.height<i.attributes.RESOLUTION.height)&&(i=f)}return i||j||g||m[0]},b.HlsHandler.prototype.checkBuffer_=function(){this.checkBufferTimeout_&&(a.clearTimeout(this.checkBufferTimeout_),this.checkBufferTimeout_=null),this.fillBuffer(),this.drainBuffer(),this.checkBufferTimeout_=a.setTimeout(this.checkBuffer_.bind(this),l)},b.HlsHandler.prototype.startCheckingBuffer_=function(){this.checkBuffer_()},b.HlsHandler.prototype.stopCheckingBuffer_=function(){this.checkBufferTimeout_&&(a.clearTimeout(this.checkBufferTimeout_),this.checkBufferTimeout_=null)};var n=function(a){return function(c){var e,f=[],g=this.tech_,h=g.buffered();if(c===d&&(c=g.currentTime()),h&&h.length)for(e=0;e<h.length;e++)a(h.start(e),h.end(e),c)&&f.push([h.start(e),h.end(e)]);return b.createTimeRanges(f)}};b.HlsHandler.prototype.findBufferedRange_=n(function(a,b,c){return c>=a-j&&b+j>=c}),b.HlsHandler.prototype.findNextBufferedRange_=n(function(a,b,c){return a-j>=c}),b.HlsHandler.prototype.fillBuffer=function(a){var c,e,f,g=this.tech_,h=g.currentTime(),i=0!==this.tech_.buffered().length,j=this.findBufferedRange_(),k=!(j&&j.length),l=0,m=0;if("segments"===this.loadingState_&&g.currentSrc()&&this.playlists&&!this.segmentXhr_&&!this.pendingSegment_&&"HAVE_NOTHING"!==this.playlists.state&&this.playlists.media()&&this.playlists.media().segments&&"SWITCHING_MEDIA"!==this.playlists.state){if(a===d)if(j&&j.length){if(l=j.end(0),a=this.playlists.getMediaIndexForTime_(l),m=Math.max(0,l-h),m>=b.Hls.GOAL_BUFFER_LENGTH)return}else a=this.playlists.getMediaIndexForTime_(this.tech_.currentTime());if(c=this.playlists.media().segments[a]){if(this.lastSegmentLoaded_&&this.playlistUriToUrl(this.lastSegmentLoaded_.uri)===this.playlistUriToUrl(c.uri)&&this.lastSegmentLoaded_.byterange===c.byterange)return this.fillBuffer(a+1);e={uri:this.playlistUriToUrl(c.uri),mediaIndex:a,mediaSequence:this.playlists.media().mediaSequence,playlist:this.playlists.media(),currentBufferedEnd:l,bytes:null,encryptedBytes:null,decrypter:null,buffered:null,timestampOffset:null},a>0&&(f=b.Hls.Playlist.duration(e.playlist,e.playlist.mediaSequence+a)+this.playlists.expired_),this.tech_.seeking()&&k?e.playlist.discontinuityStarts.length&&(e.timestampOffset=f):c.discontinuity&&j.length?e.timestampOffset=j.end(0):!i&&this.tech_.currentTime()>.05&&(e.timestampOffset=f),this.loadSegment(e)}}},b.HlsHandler.prototype.playlistUriToUrl=function(a){var b;return b=this.playlists.media().uri===this.source_.src?g(this.source_.src,a):g(g(this.source_.src,this.playlists.media().uri||""),a)},b.HlsHandler.prototype.byterangeStr_=function(a){var b,c;return c=a.offset+a.length-1,b=a.offset,"bytes="+b+"-"+c},b.HlsHandler.prototype.segmentXhrHeaders_=function(a){var b={};return"byterange"in a&&(b.Range=this.byterangeStr_(a.byterange)),b},b.HlsHandler.prototype.setBandwidth=function(a){this.segmentXhrTime=a.roundTripTime,this.bandwidth=a.bandwidth,this.bytesReceived+=a.bytesReceived||0,this.tech_.trigger("bandwidthupdate")},b.HlsHandler.prototype.blacklistCurrentPlaylist_=function(a){var c,d;return(c=a.playlist||this.playlists.media())?(c.excludeUntil=Date.now()+i,d=this.selectPlaylist(),d?(b.log.warn("Problem encountered with the current HLS playlist. Switching to another playlist."),this.playlists.media(d)):(b.log.warn("Problem encountered with the current HLS playlist. No suitable alternatives found."),this.error=a,this.mediaSource.endOfStream("network"))):(this.error=a,this.mediaSource.endOfStream("network"))},b.HlsHandler.prototype.loadSegment=function(a){var c=this,d=a.playlist.segments[a.mediaIndex],e=0,f=this.seekable(),g=this.tech_.currentTime();this.sourceBuffer&&!this.sourceBuffer.updating&&(e=f.length&&f.start(0)>0&&f.start(0)<g?f.start(0):g-60,e>0&&this.sourceBuffer.remove(0,e)),d.key&&this.fetchKey_(d),this.segmentXhr_=b.Hls.xhr({uri:a.uri,responseType:"arraybuffer",withCredentials:this.source_.withCredentials,timeout:1.5*d.duration*1e3,headers:this.segmentXhrHeaders_(d)},function(b,e){return c.segmentXhr_&&e===c.segmentXhr_?(c.segmentXhr_=null,e.timedout?(c.bandwidth=1,c.playlists.media(c.selectPlaylist())):!e.aborted&&b?c.blacklistCurrentPlaylist_({status:e.status,message:"HLS segment request error at URL: "+a.uri,code:e.status>=500?4:2}):void(e.response&&(c.lastSegmentLoaded_=d,c.setBandwidth(e),d.key?a.encryptedBytes=new Uint8Array(e.response):a.bytes=new Uint8Array(e.response),c.pendingSegment_=a,c.tech_.trigger("progress"),c.drainBuffer(),c.playlists.media(c.selectPlaylist())))):void 0})},b.HlsHandler.prototype.drainBuffer=function(){var a,c,d,e,g,h,i,j;if(this.pendingSegment_&&this.sourceBuffer&&!this.pendingSegment_.buffered&&!this.sourceBuffer.updating){if(a=this.pendingSegment_,c=a.mediaIndex,d=a.playlist,e=a.offset,g=a.bytes,h=d.segments[c],h.key&&!g)return f(h.key)?this.blacklistCurrentPlaylist_({message:"HLS segment key request error.",code:4}):h.key.bytes?a.decrypter?void 0:(j=h.key.iv||new Uint32Array([0,0,0,c+d.mediaSequence]),i=new b.Hls.Decrypter(a.encryptedBytes,h.key.bytes,j,function(b,c){a.bytes=c}),void(a.decrypter=i)):void 0;this.pendingSegment_.buffered=this.tech_.buffered(),null!==a.timestampOffset&&(this.sourceBuffer.timestampOffset=a.timestampOffset),this.sourceBuffer.appendBuffer(g)}},b.HlsHandler.prototype.updateEndHandler_=function(){var a,c,d,e,f,g,h,i,k=this.pendingSegment_;if(k&&k.buffered){if(this.pendingSegment_=null,d=k.playlist,c=d.segments,e=k.mediaIndex+(k.mediaSequence-d.mediaSequence),f=this.findBufferedRange_(),i=this.isEndOfStream_(d,e,f),k.playlist.uri!==this.playlists.media().uri)return i?this.mediaSource.endOfStream():this.fillBuffer();if(a=d.segments[e],g=this.seekable(),this.tech_.seeking()&&0===f.length&&g.length&&this.tech_.currentTime()<g.start(0)){var l=this.findNextBufferedRange_();l.length&&(b.log("tried seeking to",this.tech_.currentTime(),"but that was too early, retrying at",l.start(0)),this.tech_.setCurrentTime(l.start(0)+j))}return h=b.Hls.findSoleUncommonTimeRangesEnd_(k.buffered,this.tech_.buffered()),this.updateSegmentMetadata_(d,e,h),i?this.mediaSource.endOfStream():null!==h||k.buffered.length!==this.tech_.buffered().length?(this.updateDuration(d),void this.fillBuffer()):void this.fillBuffer(e+1)}},b.HlsHandler.prototype.fetchKey_=function(a){var c,d,e,g;if(!this.keyXhr_&&(d=this,e=this.options_,g=function(b){return function(c,e){var f;return d.keyXhr_=null,c||!e.response||16!==e.response.byteLength?(b.retries=b.retries||0,b.retries++,void(e.aborted||d.fetchKey_(a))):(f=new DataView(e.response),b.bytes=new Uint32Array([f.getUint32(0),f.getUint32(4),f.getUint32(8),f.getUint32(12)]),void d.checkBuffer_())}},c=a.key))return c.bytes||f(c)?void 0:void(this.keyXhr_=b.Hls.xhr({uri:this.playlistUriToUrl(c.uri),responseType:"arraybuffer",withCredentials:e.withCredentials},g(c)))},b.Hls.supportsNativeHls=function(){var a,d,e=c.createElement("video");return b.getComponent("Html5").isSupported()?(a=e.canPlayType("application/x-mpegURL"),d=e.canPlayType("application/vnd.apple.mpegURL"),/probably|maybe/.test(a)||/probably|maybe/.test(d)):!1}(),b.Hls.isSupported=function(){return b.log.warn("HLS is no longer a tech. Please remove it from your player's techOrder.")},b.Hls.comparePlaylistBandwidth=function(b,c){var d,e;return b.attributes&&b.attributes.BANDWIDTH&&(d=b.attributes.BANDWIDTH),d=d||a.Number.MAX_VALUE,c.attributes&&c.attributes.BANDWIDTH&&(e=c.attributes.BANDWIDTH),e=e||a.Number.MAX_VALUE,d-e},b.Hls.comparePlaylistResolution=function(b,c){var d,e;return b.attributes&&b.attributes.RESOLUTION&&b.attributes.RESOLUTION.width&&(d=b.attributes.RESOLUTION.width),d=d||a.Number.MAX_VALUE,c.attributes&&c.attributes.RESOLUTION&&c.attributes.RESOLUTION.width&&(e=c.attributes.RESOLUTION.width),e=e||a.Number.MAX_VALUE,d===e&&b.attributes.BANDWIDTH&&c.attributes.BANDWIDTH?b.attributes.BANDWIDTH-c.attributes.BANDWIDTH:d-e},g=b.Hls.resolveUrl=function(a,b){var d,e,f=c.querySelector("base"),g=c.querySelector("head"),h=c.createElement("a"),i=f;return f?d=f.href:i=g.appendChild(c.createElement("base")),i.href=a,h.href=b,e=h.href,f?f.href=d:g.removeChild(i),e}}(window,window.videojs,document),function(a){"use strict";a.Hls.xhr=function(b,c){b=a.mergeOptions({timeout:45e3},b);var d=a.xhr(b,function(a,b){!a&&d.response&&(d.responseTime=(new Date).getTime(),d.roundTripTime=d.responseTime-d.requestTime,d.bytesReceived=d.response.byteLength||d.response.length,d.bandwidth||(d.bandwidth=Math.floor(d.bytesReceived/d.roundTripTime*8*1e3))),a||d.timedout?d.timedout=d.timedout||"ETIMEDOUT"===a.code:d.timedout=!1,a||200===b.statusCode||206===b.statusCode||0===b.statusCode||(a=new Error("XHR Failed with a response of: "+(d&&(d.response||d.responseText)))),c(a,d)});return d.requestTime=(new Date).getTime(),d}}(window.videojs),function(a,b){var c=function(){this.init=function(){var a={};this.on=function(b,c){a[b]||(a[b]=[]),a[b].push(c)},this.off=function(b,c){var d;return a[b]?(d=a[b].indexOf(c),a[b].splice(d,1),d>-1):!1},this.trigger=function(b){var c,d,e,f;if(c=a[b])if(2===arguments.length)for(e=c.length,d=0;e>d;++d)c[d].call(this,arguments[1]);else for(f=Array.prototype.slice.call(arguments,1),e=c.length,d=0;e>d;++d)c[d].apply(this,f)},this.dispose=function(){a={}}}};c.prototype.pipe=function(a){this.on("data",function(b){a.push(b)})},a.Hls.Stream=c}(window.videojs),function(a,b,c,d,e){var f,g,h,i=function(){},j=function(){var a="[^=]*",b='"[^"]*"|[^,]*',c="(?:"+a+")=(?:"+b+")";return new RegExp("(?:^|,)("+c+")")}(),k=function(a){for(var b,c=a.split(j),d=c.length,e={};d--;)""!==c[d]&&(b=/([^=]*)=(.*)/.exec(c[d]).slice(1),b[0]=b[0].replace(/^\s+|\s+$/g,""),b[1]=b[1].replace(/^\s+|\s+$/g,""),b[1]=b[1].replace(/^['"](.*)['"]$/g,"$1"),e[b[0]]=b[1]);return e},l=a.Hls.Stream;f=function(){var a="";f.prototype.init.call(this),this.push=function(b){var c;for(a+=b,c=a.indexOf("\n");c>-1;c=a.indexOf("\n"))this.trigger("data",a.substring(0,c)),a=a.substring(c+1)}},f.prototype=new l,g=function(){g.prototype.init.call(this)},g.prototype=new l,g.prototype.push=function(a){var c,d;return a=a.replace(/^[\u0000\s]+|[\u0000\s]+$/g,""),0!==a.length?"#"!==a[0]?void this.trigger("data",{type:"uri",uri:a}):0!==a.indexOf("#EXT")?void this.trigger("data",{type:"comment",text:a.slice(1)}):(a=a.replace("\r",""),(c=/^#EXTM3U/.exec(a))?void this.trigger("data",{type:"tag",tagType:"m3u"}):(c=/^#EXTINF:?([0-9\.]*)?,?(.*)?$/.exec(a))?(d={type:"tag",tagType:"inf"},c[1]&&(d.duration=parseFloat(c[1])),c[2]&&(d.title=c[2]),void this.trigger("data",d)):(c=/^#EXT-X-TARGETDURATION:?([0-9.]*)?/.exec(a))?(d={type:"tag",tagType:"targetduration"},c[1]&&(d.duration=b(c[1],10)),void this.trigger("data",d)):(c=/^#ZEN-TOTAL-DURATION:?([0-9.]*)?/.exec(a))?(d={type:"tag",tagType:"totalduration"},c[1]&&(d.duration=b(c[1],10)),void this.trigger("data",d)):(c=/^#EXT-X-VERSION:?([0-9.]*)?/.exec(a))?(d={type:"tag",tagType:"version"},c[1]&&(d.version=b(c[1],10)),void this.trigger("data",d)):(c=/^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/.exec(a))?(d={type:"tag",tagType:"media-sequence"},c[1]&&(d.number=b(c[1],10)),void this.trigger("data",d)):(c=/^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/.exec(a))?(d={type:"tag",tagType:"discontinuity-sequence"},c[1]&&(d.number=b(c[1],10)),void this.trigger("data",d)):(c=/^#EXT-X-PLAYLIST-TYPE:?(.*)?$/.exec(a))?(d={type:"tag",tagType:"playlist-type"},c[1]&&(d.playlistType=c[1]),void this.trigger("data",d)):(c=/^#EXT-X-BYTERANGE:?([0-9.]*)?@?([0-9.]*)?/.exec(a))?(d={type:"tag",tagType:"byterange"},c[1]&&(d.length=b(c[1],10)),c[2]&&(d.offset=b(c[2],10)),void this.trigger("data",d)):(c=/^#EXT-X-ALLOW-CACHE:?(YES|NO)?/.exec(a))?(d={type:"tag",tagType:"allow-cache"},c[1]&&(d.allowed=!/NO/.test(c[1])),void this.trigger("data",d)):(c=/^#EXT-X-STREAM-INF:?(.*)$/.exec(a))?(d={type:"tag",tagType:"stream-inf"},c[1]&&(d.attributes=k(c[1]),d.attributes.RESOLUTION&&!function(){var a=d.attributes.RESOLUTION.split("x"),c={};a[0]&&(c.width=b(a[0],10)),a[1]&&(c.height=b(a[1],10)),d.attributes.RESOLUTION=c}(),d.attributes.BANDWIDTH&&(d.attributes.BANDWIDTH=b(d.attributes.BANDWIDTH,10)),d.attributes["PROGRAM-ID"]&&(d.attributes["PROGRAM-ID"]=b(d.attributes["PROGRAM-ID"],10))),void this.trigger("data",d)):(c=/^#EXT-X-ENDLIST/.exec(a))?void this.trigger("data",{type:"tag",tagType:"endlist"}):(c=/^#EXT-X-DISCONTINUITY/.exec(a))?void this.trigger("data",{type:"tag",tagType:"discontinuity"}):(c=/^#EXT-X-KEY:?(.*)$/.exec(a))?(d={type:"tag",tagType:"key"},c[1]&&(d.attributes=k(c[1]),d.attributes.IV&&("0x"===d.attributes.IV.substring(0,2)&&(d.attributes.IV=d.attributes.IV.substring(2)),d.attributes.IV=d.attributes.IV.match(/.{8}/g),d.attributes.IV[0]=b(d.attributes.IV[0],16),d.attributes.IV[1]=b(d.attributes.IV[1],16),d.attributes.IV[2]=b(d.attributes.IV[2],16),d.attributes.IV[3]=b(d.attributes.IV[3],16),d.attributes.IV=new Uint32Array(d.attributes.IV))),
void this.trigger("data",d)):void this.trigger("data",{type:"tag",data:a.slice(4,a.length)})):void 0},h=function(){var a,b=this,j=[],k={};h.prototype.init.call(this),this.lineStream=new f,this.parseStream=new g,this.lineStream.pipe(this.parseStream),this.manifest={allowCache:!0,discontinuityStarts:[]},this.parseStream.on("data",function(f){({tag:function(){(({"allow-cache":function(){this.manifest.allowCache=f.allowed,"allowed"in f||(this.trigger("info",{message:"defaulting allowCache to YES"}),this.manifest.allowCache=!0)},byterange:function(){var a={};"length"in f&&(k.byterange=a,a.length=f.length,"offset"in f||(this.trigger("info",{message:"defaulting offset to zero"}),f.offset=0)),"offset"in f&&(k.byterange=a,a.offset=f.offset)},endlist:function(){this.manifest.endList=!0},inf:function(){"mediaSequence"in this.manifest||(this.manifest.mediaSequence=0,this.trigger("info",{message:"defaulting media sequence to zero"})),"discontinuitySequence"in this.manifest||(this.manifest.discontinuitySequence=0,this.trigger("info",{message:"defaulting discontinuity sequence to zero"})),f.duration>=0&&(k.duration=f.duration),this.manifest.segments=j},key:function(){return f.attributes?"NONE"===f.attributes.METHOD?void(a=null):f.attributes.URI?(f.attributes.METHOD||this.trigger("warn",{message:"defaulting key method to AES-128"}),a={method:f.attributes.METHOD||"AES-128",uri:f.attributes.URI},void(f.attributes.IV!==e&&(a.iv=f.attributes.IV))):void this.trigger("warn",{message:"ignoring key declaration without URI"}):void this.trigger("warn",{message:"ignoring key declaration without attribute list"})},"media-sequence":function(){return c(f.number)?void(this.manifest.mediaSequence=f.number):void this.trigger("warn",{message:"ignoring invalid media sequence: "+f.number})},"discontinuity-sequence":function(){return c(f.number)?void(this.manifest.discontinuitySequence=f.number):void this.trigger("warn",{message:"ignoring invalid discontinuity sequence: "+f.number})},"playlist-type":function(){return/VOD|EVENT/.test(f.playlistType)?void(this.manifest.playlistType=f.playlistType):void this.trigger("warn",{message:"ignoring unknown playlist type: "+f.playlist})},"stream-inf":function(){return this.manifest.playlists=j,f.attributes?(k.attributes||(k.attributes={}),void(k.attributes=d(k.attributes,f.attributes))):void this.trigger("warn",{message:"ignoring empty stream-inf attributes"})},discontinuity:function(){k.discontinuity=!0,this.manifest.discontinuityStarts.push(j.length)},targetduration:function(){return!c(f.duration)||f.duration<0?void this.trigger("warn",{message:"ignoring invalid target duration: "+f.duration}):void(this.manifest.targetDuration=f.duration)},totalduration:function(){return!c(f.duration)||f.duration<0?void this.trigger("warn",{message:"ignoring invalid total duration: "+f.duration}):void(this.manifest.totalDuration=f.duration)}})[f.tagType]||i).call(b)},uri:function(){k.uri=f.uri,j.push(k),!this.manifest.targetDuration||"duration"in k||(this.trigger("warn",{message:"defaulting segment duration to the target duration"}),k.duration=this.manifest.targetDuration),a&&(k.key=a),k={}},comment:function(){}})[f.type].call(b)})},h.prototype=new l,h.prototype.push=function(a){this.lineStream.push(a)},h.prototype.end=function(){this.lineStream.push("\n")},window.videojs.m3u8={LineStream:f,ParseStream:g,Parser:h}}(window.videojs,window.parseInt,window.isFinite,window.videojs.mergeOptions),function(a,b){"use strict";var c,d,e,f,g,h={UNSAFE_LIVE_SEGMENTS:3};e=function(a,b){var c,d,e=0;if(d=b-a.mediaSequence,c=a.segments[d]){if(void 0!==c.start)return{result:c.start,precise:!0};if(void 0!==c.end)return{result:c.end-c.duration,precise:!0}}for(;d--;){if(c=a.segments[d],void 0!==c.end)return{result:e+c.end,precise:!0};if(e+=c.duration,void 0!==c.start)return{result:e+c.start,precise:!0}}return{result:e,precise:!1}},f=function(a,b){var c,d,e=0;for(d=b-a.mediaSequence;d<a.segments.length;d++){if(c=a.segments[d],void 0!==c.start)return{result:c.start-e,precise:!0};if(e+=c.duration,void 0!==c.end)return{result:c.end-e,precise:!0}}return{result:-1,precise:!1}},d=function(a,b){var c,d;return void 0===b&&(b=a.mediaSequence+a.segments.length),b<a.mediaSequence?0:(c=e(a,b),c.precise?c.result:(d=f(a,b),d.precise?d.result:c.result))},c=function(b,c,e){if(!b)return 0;if(void 0===e&&(e=!0),void 0===c){if(b.totalDuration)return b.totalDuration;if(!b.endList)return a.Infinity}return d(b,c,e)},g=function(a){var e,f;return a.segments?a.endList?b.createTimeRange(0,c(a)):(e=d(a,a.mediaSequence),f=d(a,a.mediaSequence+Math.max(0,a.segments.length-h.UNSAFE_LIVE_SEGMENTS)),b.createTimeRange(e,f)):b.createTimeRange()},h.duration=c,h.seekable=g,b.Hls.Playlist=h}(window,window.videojs),function(a,b){"use strict";var c=b.Hls.resolveUrl,d=b.Hls.xhr,e=b.mergeOptions,f=function(a,b){var c,d,f=!1,h=e(a,{});for(c=a.playlists.length;c--;)if(d=h.playlists[c],d.uri===b.uri){if(d.segments&&b.segments&&d.segments.length===b.segments.length&&d.mediaSequence===b.mediaSequence)continue;h.playlists[c]=e(d,b),h.playlists[b.uri]=h.playlists[c],d.segments&&(h.playlists[c].segments=g(d.segments,b.segments,b.mediaSequence-d.mediaSequence)),f=!0}return f?h:null},g=function(a,b,c){var d,f,g=b.slice();for(c=c||0,d=Math.min(a.length,b.length+c),f=c;d>f;f++)g[f-c]=e(a[f],g[f-c]);return g},h=function(e,g){var i,j,k,l,m,n=this;if(h.prototype.init.call(this),this.trackExpiredTime_=!1,!e)throw new Error("A non-empty playlist URL is required");l=function(a,b,c){n.setBandwidth(k||a),k=null,c&&(n.state=c),n.error={playlist:n.master.playlists[b],status:a.status,message:"HLS playlist request error at URL: "+b,responseText:a.responseText,code:a.status>=500?4:2},n.trigger("error")},m=function(c,d){var e,g,h;n.setBandwidth(k||c),k=null,n.state="HAVE_METADATA",e=new b.m3u8.Parser,e.push(c.responseText),e.end(),e.manifest.uri=d,h=f(n.master,e.manifest),g=1e3*(e.manifest.targetDuration||10),h?(n.master=h,n.updateMediaPlaylist_(e.manifest)):g/=2,n.media().endList||(a.clearTimeout(j),j=a.setTimeout(function(){n.trigger("mediaupdatetimeout")},g)),n.trigger("loadedplaylist")},n.state="HAVE_NOTHING",this.expired_=0,i=this.dispose,n.dispose=function(){k&&(k.onreadystatechange=null,k.abort(),k=null),a.clearTimeout(j),i.call(this)},n.media=function(a){var b,e=n.state;if(!a)return n.media_;if("HAVE_NOTHING"===n.state)throw new Error("Cannot switch media playlist from "+n.state);if("string"==typeof a){if(!n.master.playlists[a])throw new Error("Unknown playlist URI: "+a);a=n.master.playlists[a]}if(b=!n.media_||a.uri!==n.media_.uri,n.master.playlists[a.uri].endList)return k&&(k.onreadystatechange=null,k.abort(),k=null),n.state="HAVE_METADATA",n.media_=a,void(b&&n.trigger("mediachange"));if(b){if(n.state="SWITCHING_MEDIA",k){if(c(n.master.uri,a.uri)===k.url)return;k.onreadystatechange=null,k.abort(),k=null}k=d({uri:c(n.master.uri,a.uri),withCredentials:g},function(b,c){return b?l(c,a.uri,e):(m(c,a.uri),void("HAVE_MASTER"===e?n.trigger("loadedmetadata"):n.trigger("mediachange")))})}},n.setBandwidth=function(a){n.bandwidth=a.bandwidth},n.on("firstplay",function(){this.trackExpiredTime_=!0}),n.on("mediaupdatetimeout",function(){"HAVE_METADATA"===n.state&&(n.state="HAVE_CURRENT_METADATA",k=d({uri:c(n.master.uri,n.media().uri),withCredentials:g},function(a,b){return a?l(b,n.media().uri):void m(b,n.media().uri)}))}),k=d({uri:e,withCredentials:g},function(c,d){var f,g;if(k=null,c)return n.error={status:d.status,message:"HLS playlist request error at URL: "+e,responseText:d.responseText,code:2},n.trigger("error");if(f=new b.m3u8.Parser,f.push(d.responseText),f.end(),n.state="HAVE_MASTER",f.manifest.uri=e,f.manifest.playlists){for(n.master=f.manifest,g=n.master.playlists.length;g--;)n.master.playlists[n.master.playlists[g].uri]=n.master.playlists[g];return n.trigger("loadedplaylist"),void(k||n.media(f.manifest.playlists[0]))}return n.master={uri:a.location.href,playlists:[{uri:e}]},n.master.playlists[e]=n.master.playlists[0],m(d,e),n.trigger("loadedmetadata")})};h.prototype=new b.Hls.Stream,h.prototype.updateMediaPlaylist_=function(a){var b,c,d;if(b=this.media_,this.media_=this.master.playlists[a.uri],b&&this.trackExpiredTime_&&a.uri===b.uri){if(a.segments.length){if(void 0!==a.segments[0].start)return void(this.expired_=a.segments[0].start);if(void 0!==a.segments[0].end)return void(this.expired_=a.segments[0].end-a.segments[0].duration)}for(c=a.mediaSequence-b.mediaSequence-1;c>=0;c--)if(d=b.segments[c]){if(void 0!==d.end)return void(this.expired_=d.end);if(void 0!==d.start)return void(this.expired_=d.start+d.duration);this.expired_+=d.duration}else this.expired_+=b.targetDuration||10}},h.prototype.getMediaIndexForTime_=function(a){var b,c,d,e,f,g,h=a,i=this.media_.segments.length,j=i-1;if(!this.media_)return 0;if(0>a)return 0;for(b=0;i>b;b++)if(c=this.media_.segments[b],c.end){if(c.end>a){g=c.end,e=b;break}f=c.end,d=b+1}if(void 0!==d){for(a-=f,b=d;(e||i)>b;b++)if(c=this.media_.segments[b],a-=c.duration,0>a)return b;return b>=e?d+Math.floor((h-f)/(g-f)*(e-d)):j}if(void 0!==e){for(a=g-a,b=e;b>=0;b--)if(c=this.media_.segments[b],a-=c.duration,0>a)return b;return 0===a?0:-1}if(a-=this.expired_,0>a)return-1;for(b=0;i>b;b++)if(c=this.media_.segments[b],a-=c.duration,0>a)return b;return j},b.Hls.PlaylistLoader=h}(window,window.videojs),function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);throw new Error("Cannot find module '"+g+"'")}var j=c[g]={exports:{}};b[g][0].call(j.exports,function(a){var c=b[g][1][a];return e(c?c:a)},j,j.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b,c){(function(b){b.window.pkcs7={unpad:a("./unpad")}}).call(this,"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./unpad":2}],2:[function(a,b,c){"use strict";b.exports=function(a){return a.subarray(0,a.byteLength-a[a.byteLength-1])}},{}]},{},[1]),function(a,b,c){"use strict";var d,e,f,g,h;h=function(a){return a<<24|(65280&a)<<8|(16711680&a)>>8|a>>>24},d=function(a){this._precompute();var b,c,d,e,f,g=this._tables[0][4],h=this._tables[1],i=a.length,j=1;if(4!==i&&6!==i&&8!==i)throw new Error("Invalid aes key size");for(e=a.slice(0),f=[],this._key=[e,f],b=i;4*i+28>b;b++)d=e[b-1],(b%i===0||8===i&&b%i===4)&&(d=g[d>>>24]<<24^g[d>>16&255]<<16^g[d>>8&255]<<8^g[255&d],b%i===0&&(d=d<<8^d>>>24^j<<24,j=j<<1^283*(j>>7))),e[b]=e[b-i]^d;for(c=0;b;c++,b--)d=e[3&c?b:b-4],4>=b||4>c?f[c]=d:f[c]=h[0][g[d>>>24]]^h[1][g[d>>16&255]]^h[2][g[d>>8&255]]^h[3][g[255&d]]},d.prototype={_tables:[[[],[],[],[],[]],[[],[],[],[],[]]],_precompute:function(){var a,b,c,d,e,f,g,h,i,j=this._tables[0],k=this._tables[1],l=j[4],m=k[4],n=[],o=[];for(a=0;256>a;a++)o[(n[a]=a<<1^283*(a>>7))^a]=a;for(b=c=0;!l[b];b^=d||1,c=o[c]||1)for(g=c^c<<1^c<<2^c<<3^c<<4,g=g>>8^255&g^99,l[b]=g,m[g]=b,f=n[e=n[d=n[b]]],i=16843009*f^65537*e^257*d^16843008*b,h=257*n[g]^16843008*g,a=0;4>a;a++)j[a][b]=h=h<<24^h>>>8,k[a][g]=i=i<<24^i>>>8;for(a=0;5>a;a++)j[a]=j[a].slice(0),k[a]=k[a].slice(0)},decrypt:function(a,b,c,d,e,f){var g,h,i,j,k=this._key[1],l=a^k[0],m=d^k[1],n=c^k[2],o=b^k[3],p=k.length/4-2,q=4,r=this._tables[1],s=r[0],t=r[1],u=r[2],v=r[3],w=r[4];for(j=0;p>j;j++)g=s[l>>>24]^t[m>>16&255]^u[n>>8&255]^v[255&o]^k[q],h=s[m>>>24]^t[n>>16&255]^u[o>>8&255]^v[255&l]^k[q+1],i=s[n>>>24]^t[o>>16&255]^u[l>>8&255]^v[255&m]^k[q+2],o=s[o>>>24]^t[l>>16&255]^u[m>>8&255]^v[255&n]^k[q+3],q+=4,l=g,m=h,n=i;for(j=0;4>j;j++)e[(3&-j)+f]=w[l>>>24]<<24^w[m>>16&255]<<16^w[n>>8&255]<<8^w[255&o]^k[q++],g=l,l=m,m=n,n=o,o=g}},g=function(a,b,c){var e,f,g,i,j,k,l,m,n,o=new Int32Array(a.buffer,a.byteOffset,a.byteLength>>2),p=new d(Array.prototype.slice.call(b)),q=new Uint8Array(a.byteLength),r=new Int32Array(q.buffer);for(e=c[0],f=c[1],g=c[2],i=c[3],n=0;n<o.length;n+=4)j=h(o[n]),k=h(o[n+1]),l=h(o[n+2]),m=h(o[n+3]),p.decrypt(j,k,l,m,r,n),r[n]=h(r[n]^e),r[n+1]=h(r[n+1]^f),r[n+2]=h(r[n+2]^g),r[n+3]=h(r[n+3]^i),e=j,f=k,g=l,i=m;return q},e=function(){this.jobs=[],this.delay=1,this.timeout_=null},e.prototype=new b.Hls.Stream,e.prototype.processJob_=function(){this.jobs.shift()(),this.jobs.length?this.timeout_=setTimeout(this.processJob_.bind(this),this.delay):this.timeout_=null},e.prototype.push=function(a){this.jobs.push(a),this.timeout_||(this.timeout_=setTimeout(this.processJob_.bind(this),this.delay))},f=function(a,b,d,g){var i=f.STEP,j=new Int32Array(a.buffer),k=new Uint8Array(a.byteLength),l=0;for(this.asyncStream_=new e,this.asyncStream_.push(this.decryptChunk_(j.subarray(l,l+i),b,d,k)),l=i;l<j.length;l+=i)d=new Uint32Array([h(j[l-4]),h(j[l-3]),h(j[l-2]),h(j[l-1])]),this.asyncStream_.push(this.decryptChunk_(j.subarray(l,l+i),b,d,k));this.asyncStream_.push(function(){g(null,c(k))})},f.prototype=new b.Hls.Stream,f.prototype.decryptChunk_=function(a,b,c,d){return function(){var e=g(a,b,c);d.set(e,a.byteOffset)}},f.STEP=32e3,b.Hls.decrypt=g,b.Hls.Decrypter=f,b.Hls.AsyncStream=e}(window,window.videojs,window.pkcs7.unpad);
var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.owns=function(a,c){return Object.prototype.hasOwnProperty.call(a,c)};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(a,c,e){a!=Array.prototype&&a!=Object.prototype&&(a[c]=e.value)};
$jscomp.getGlobal=function(a){return"undefined"!=typeof window&&window===a?a:"undefined"!=typeof global&&null!=global?global:a};$jscomp.global=$jscomp.getGlobal(this);$jscomp.polyfill=function(a,c,e,f){if(c){e=$jscomp.global;a=a.split(".");for(f=0;f<a.length-1;f++){var b=a[f];b in e||(e[b]={});e=e[b]}a=a[a.length-1];f=e[a];c=c(f);c!=f&&null!=c&&$jscomp.defineProperty(e,a,{configurable:!0,writable:!0,value:c})}};
$jscomp.polyfill("Object.assign",function(a){return a?a:function(a,e){for(var c=1;c<arguments.length;c++){var b=arguments[c];if(b)for(var g in b)$jscomp.owns(b,g)&&(a[g]=b[g])}return a}},"es6","es3");$jscomp.SYMBOL_PREFIX="jscomp_symbol_";$jscomp.initSymbol=function(){$jscomp.initSymbol=function(){};$jscomp.global.Symbol||($jscomp.global.Symbol=$jscomp.Symbol)};$jscomp.Symbol=function(){var a=0;return function(c){return $jscomp.SYMBOL_PREFIX+(c||"")+a++}}();
$jscomp.initSymbolIterator=function(){$jscomp.initSymbol();var a=$jscomp.global.Symbol.iterator;a||(a=$jscomp.global.Symbol.iterator=$jscomp.global.Symbol("iterator"));"function"!=typeof Array.prototype[a]&&$jscomp.defineProperty(Array.prototype,a,{configurable:!0,writable:!0,value:function(){return $jscomp.arrayIterator(this)}});$jscomp.initSymbolIterator=function(){}};$jscomp.arrayIterator=function(a){var c=0;return $jscomp.iteratorPrototype(function(){return c<a.length?{done:!1,value:a[c++]}:{done:!0}})};
$jscomp.iteratorPrototype=function(a){$jscomp.initSymbolIterator();a={next:a};a[$jscomp.global.Symbol.iterator]=function(){return this};return a};$jscomp.makeIterator=function(a){$jscomp.initSymbolIterator();var c=a[Symbol.iterator];return c?c.call(a):$jscomp.arrayIterator(a)};$jscomp.FORCE_POLYFILL_PROMISE=!1;
$jscomp.polyfill("Promise",function(a){function c(){this.batch_=null}function e(d){return d instanceof b?d:new b(function(a,b){a(d)})}if(a&&!$jscomp.FORCE_POLYFILL_PROMISE)return a;c.prototype.asyncExecute=function(d){null==this.batch_&&(this.batch_=[],this.asyncExecuteBatch_());this.batch_.push(d);return this};c.prototype.asyncExecuteBatch_=function(){var d=this;this.asyncExecuteFunction(function(){d.executeBatch_()})};var f=$jscomp.global.setTimeout;c.prototype.asyncExecuteFunction=function(d){f(d,
0)};c.prototype.executeBatch_=function(){for(;this.batch_&&this.batch_.length;){var d=this.batch_;this.batch_=[];for(var a=0;a<d.length;++a){var b=d[a];delete d[a];try{b()}catch(h){this.asyncThrow_(h)}}}this.batch_=null};c.prototype.asyncThrow_=function(d){this.asyncExecuteFunction(function(){throw d;})};var b=function(d){this.state_=0;this.result_=void 0;this.onSettledCallbacks_=[];var a=this.createResolveAndReject_();try{d(a.resolve,a.reject)}catch(l){a.reject(l)}};b.prototype.createResolveAndReject_=
function(){function a(a){return function(d){c||(c=!0,a.call(b,d))}}var b=this,c=!1;return{resolve:a(this.resolveTo_),reject:a(this.reject_)}};b.prototype.resolveTo_=function(a){if(a===this)this.reject_(new TypeError("A Promise cannot resolve to itself"));else if(a instanceof b)this.settleSameAsPromise_(a);else{a:switch(typeof a){case "object":var d=null!=a;break a;case "function":d=!0;break a;default:d=!1}d?this.resolveToNonPromiseObj_(a):this.fulfill_(a)}};b.prototype.resolveToNonPromiseObj_=function(a){var b=
void 0;try{b=a.then}catch(l){this.reject_(l);return}"function"==typeof b?this.settleSameAsThenable_(b,a):this.fulfill_(a)};b.prototype.reject_=function(a){this.settle_(2,a)};b.prototype.fulfill_=function(a){this.settle_(1,a)};b.prototype.settle_=function(a,b){if(0!=this.state_)throw Error("Cannot settle("+a+", "+b|"): Promise already settled in state"+this.state_);this.state_=a;this.result_=b;this.executeOnSettledCallbacks_()};b.prototype.executeOnSettledCallbacks_=function(){if(null!=this.onSettledCallbacks_){for(var a=
this.onSettledCallbacks_,b=0;b<a.length;++b)a[b].call(),a[b]=null;this.onSettledCallbacks_=null}};var g=new c;b.prototype.settleSameAsPromise_=function(a){var b=this.createResolveAndReject_();a.callWhenSettled_(b.resolve,b.reject)};b.prototype.settleSameAsThenable_=function(a,b){var c=this.createResolveAndReject_();try{a.call(b,c.resolve,c.reject)}catch(h){c.reject(h)}};b.prototype.then=function(a,c){function d(a,b){return"function"==typeof a?function(b){try{h(a(b))}catch(m){e(m)}}:b}var h,e,g=new b(function(a,
b){h=a;e=b});this.callWhenSettled_(d(a,h),d(c,e));return g};b.prototype.catch=function(a){return this.then(void 0,a)};b.prototype.callWhenSettled_=function(a,b){function c(){switch(d.state_){case 1:a(d.result_);break;case 2:b(d.result_);break;default:throw Error("Unexpected state: "+d.state_);}}var d=this;null==this.onSettledCallbacks_?g.asyncExecute(c):this.onSettledCallbacks_.push(function(){g.asyncExecute(c)})};b.resolve=e;b.reject=function(a){return new b(function(b,c){c(a)})};b.race=function(a){return new b(function(b,
c){for(var d=$jscomp.makeIterator(a),g=d.next();!g.done;g=d.next())e(g.value).callWhenSettled_(b,c)})};b.all=function(a){var c=$jscomp.makeIterator(a),d=c.next();return d.done?e([]):new b(function(a,b){function g(b){return function(c){f[b]=c;h--;0==h&&a(f)}}var f=[],h=0;do f.push(void 0),h++,e(d.value).callWhenSettled_(g(f.length-1),b),d=c.next();while(!d.done)})};return b},"es6","es3");
(function(a,c){"object"===typeof exports&&"undefined"!==typeof module?module.exports=c():"function"===typeof define&&define.amd?define(c):a.canAutoplay=c()})(this,function(){function a(a){return Object.assign({muted:!1,timeout:250,inline:!1},a)}function c(a,c){var b=a.muted,e=a.timeout;a=a.inline;c=c();var f=c.element;c=c.source;var h=void 0,g=void 0,k=void 0;f.muted=b;!0===b&&f.setAttribute("muted","muted");!0===a&&f.setAttribute("playsinline","playsinline");f.src=c;return new Promise(function(a){h=
f.play();g=setTimeout(function(){k(!1,Error("Timeout "+e+" ms has been reached"))},e);k=function(b){var c=1<arguments.length&&void 0!==arguments[1]?arguments[1]:null;clearTimeout(g);a({result:b,error:c})};void 0!==h?h.then(function(){return k(!0)}).catch(function(a){return k(!1,a)}):k(!0)})}var e=new Blob([new Uint8Array([255,227,24,196,0,0,0,3,72,1,64,0,0,4,132,16,31,227,192,225,76,255,67,12,255,221,27,255,228,97,73,63,255,195,131,69,192,232,223,255,255,207,102,239,255,255,255,101,158,206,70,20,
59,255,254,95,70,149,66,4,16,128,0,2,2,32,240,138,255,36,106,183,255,227,24,196,59,11,34,62,80,49,135,40,0,253,29,191,209,200,141,71,7,255,252,152,74,15,130,33,185,6,63,255,252,195,70,203,86,53,15,255,255,247,103,76,121,64,32,47,255,34,227,194,209,138,76,65,77,69,51,46,57,55,170,170,170,170,170,170,170,170,170,170,255,227,24,196,73,13,153,210,100,81,135,56,0,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,
170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170])],{type:"audio/mpeg"}),f=new Blob([new Uint8Array([0,0,0,28,102,116,121,112,105,115,111,109,0,0,2,0,105,115,111,109,105,115,111,50,109,112,52,49,0,0,0,8,102,114,101,101,0,0,2,239,109,100,97,116,33,16,5,32,164,27,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,55,167,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,112,33,16,5,32,164,27,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,55,167,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,112,0,0,2,194,109,111,111,118,0,0,0,108,109,118,104,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,232,0,0,0,47,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,1,236,116,114,97,107,0,0,0,92,116,107,104,100,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,47,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,101,100,116,115,0,0,0,28,101,108,115,116,0,0,0,0,0,0,0,1,0,0,0,47,0,0,0,0,0,1,0,0,0,0,1,100,109,100,105,97,0,0,0,32,109,100,104,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,172,68,0,0,8,0,85,196,0,0,0,0,0,45,104,100,108,114,0,
0,0,0,0,0,0,0,115,111,117,110,0,0,0,0,0,0,0,0,0,0,0,0,83,111,117,110,100,72,97,110,100,108,101,114,0,0,0,1,15,109,105,110,102,0,0,0,16,115,109,104,100,0,0,0,0,0,0,0,0,0,0,0,36,100,105,110,102,0,0,0,28,100,114,101,102,0,0,0,0,0,0,0,1,0,0,0,12,117,114,108,32,0,0,0,1,0,0,0,211,115,116,98,108,0,0,0,103,115,116,115,100,0,0,0,0,0,0,0,1,0,0,0,87,109,112,52,97,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,2,0,16,0,0,0,0,172,68,0,0,0,0,0,51,101,115,100,115,0,0,0,0,3,128,128,128,34,0,2,0,4,128,128,128,20,64,21,0,0,0,0,
1,244,0,0,1,243,249,5,128,128,128,2,18,16,6,128,128,128,1,2,0,0,0,24,115,116,116,115,0,0,0,0,0,0,0,1,0,0,0,2,0,0,4,0,0,0,0,28,115,116,115,99,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,2,0,0,0,1,0,0,0,28,115,116,115,122,0,0,0,0,0,0,0,0,0,0,0,2,0,0,1,115,0,0,1,116,0,0,0,20,115,116,99,111,0,0,0,0,0,0,0,1,0,0,0,44,0,0,0,98,117,100,116,97,0,0,0,90,109,101,116,97,0,0,0,0,0,0,0,33,104,100,108,114,0,0,0,0,0,0,0,0,109,100,105,114,97,112,112,108,0,0,0,0,0,0,0,0,0,0,0,0,45,105,108,115,116,0,0,0,37,169,116,111,111,0,0,0,
29,100,97,116,97,0,0,0,1,0,0,0,0,76,97,118,102,53,54,46,52,48,46,49,48,49])],{type:"video/mp4"});return{audio:function(b){b=a(b);return c(b,function(){return{element:document.createElement("audio"),source:URL.createObjectURL(e)}})},video:function(b){b=a(b);return c(b,function(){return{element:document.createElement("video"),source:URL.createObjectURL(f)}})}}});

!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.io=e():t.io=e()}(this,function(){return function(t){function e(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return t[r].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){"use strict";function r(t,e){"object"===("undefined"==typeof t?"undefined":o(t))&&(e=t,t=void 0),e=e||{};var n,r=i(t),s=r.source,u=r.id,h=r.path,f=p[u]&&h in p[u].nsps,l=e.forceNew||e["force new connection"]||!1===e.multiplex||f;return l?(c("ignoring socket cache for %s",s),n=a(s,e)):(p[u]||(c("new io instance for %s",s),p[u]=a(s,e)),n=p[u]),r.query&&!e.query&&(e.query=r.query),n.socket(r.path,e)}var o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=n(1),s=n(7),a=n(13),c=n(3)("socket.io-client");t.exports=e=r;var p=e.managers={};e.protocol=s.protocol,e.connect=r,e.Manager=n(13),e.Socket=n(37)},function(t,e,n){(function(e){"use strict";function r(t,n){var r=t;n=n||e.location,null==t&&(t=n.protocol+"//"+n.host),"string"==typeof t&&("/"===t.charAt(0)&&(t="/"===t.charAt(1)?n.protocol+t:n.host+t),/^(https?|wss?):\/\//.test(t)||(i("protocol-less url %s",t),t="undefined"!=typeof n?n.protocol+"//"+t:"https://"+t),i("parse %s",t),r=o(t)),r.port||(/^(http|ws)$/.test(r.protocol)?r.port="80":/^(http|ws)s$/.test(r.protocol)&&(r.port="443")),r.path=r.path||"/";var s=r.host.indexOf(":")!==-1,a=s?"["+r.host+"]":r.host;return r.id=r.protocol+"://"+a+":"+r.port,r.href=r.protocol+"://"+a+(n&&n.port===r.port?"":":"+r.port),r}var o=n(2),i=n(3)("socket.io-client:url");t.exports=r}).call(e,function(){return this}())},function(t,e){var n=/^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,r=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];t.exports=function(t){var e=t,o=t.indexOf("["),i=t.indexOf("]");o!=-1&&i!=-1&&(t=t.substring(0,o)+t.substring(o,i).replace(/:/g,";")+t.substring(i,t.length));for(var s=n.exec(t||""),a={},c=14;c--;)a[r[c]]=s[c]||"";return o!=-1&&i!=-1&&(a.source=e,a.host=a.host.substring(1,a.host.length-1).replace(/;/g,":"),a.authority=a.authority.replace("[","").replace("]","").replace(/;/g,":"),a.ipv6uri=!0),a}},function(t,e,n){(function(r){function o(){return!("undefined"==typeof window||!window.process||"renderer"!==window.process.type)||("undefined"!=typeof document&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||"undefined"!=typeof window&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/))}function i(t){var n=this.useColors;if(t[0]=(n?"%c":"")+this.namespace+(n?" %c":" ")+t[0]+(n?"%c ":" ")+"+"+e.humanize(this.diff),n){var r="color: "+this.color;t.splice(1,0,r,"color: inherit");var o=0,i=0;t[0].replace(/%[a-zA-Z%]/g,function(t){"%%"!==t&&(o++,"%c"===t&&(i=o))}),t.splice(i,0,r)}}function s(){return"object"==typeof console&&console.log&&Function.prototype.apply.call(console.log,console,arguments)}function a(t){try{null==t?e.storage.removeItem("debug"):e.storage.debug=t}catch(n){}}function c(){var t;try{t=e.storage.debug}catch(n){}return!t&&"undefined"!=typeof r&&"env"in r&&(t=r.env.DEBUG),t}function p(){try{return window.localStorage}catch(t){}}e=t.exports=n(5),e.log=s,e.formatArgs=i,e.save=a,e.load=c,e.useColors=o,e.storage="undefined"!=typeof chrome&&"undefined"!=typeof chrome.storage?chrome.storage.local:p(),e.colors=["lightseagreen","forestgreen","goldenrod","dodgerblue","darkorchid","crimson"],e.formatters.j=function(t){try{return JSON.stringify(t)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}},e.enable(c())}).call(e,n(4))},function(t,e){function n(){throw new Error("setTimeout has not been defined")}function r(){throw new Error("clearTimeout has not been defined")}function o(t){if(u===setTimeout)return setTimeout(t,0);if((u===n||!u)&&setTimeout)return u=setTimeout,setTimeout(t,0);try{return u(t,0)}catch(e){try{return u.call(null,t,0)}catch(e){return u.call(this,t,0)}}}function i(t){if(h===clearTimeout)return clearTimeout(t);if((h===r||!h)&&clearTimeout)return h=clearTimeout,clearTimeout(t);try{return h(t)}catch(e){try{return h.call(null,t)}catch(e){return h.call(this,t)}}}function s(){y&&l&&(y=!1,l.length?d=l.concat(d):m=-1,d.length&&a())}function a(){if(!y){var t=o(s);y=!0;for(var e=d.length;e;){for(l=d,d=[];++m<e;)l&&l[m].run();m=-1,e=d.length}l=null,y=!1,i(t)}}function c(t,e){this.fun=t,this.array=e}function p(){}var u,h,f=t.exports={};!function(){try{u="function"==typeof setTimeout?setTimeout:n}catch(t){u=n}try{h="function"==typeof clearTimeout?clearTimeout:r}catch(t){h=r}}();var l,d=[],y=!1,m=-1;f.nextTick=function(t){var e=new Array(arguments.length-1);if(arguments.length>1)for(var n=1;n<arguments.length;n++)e[n-1]=arguments[n];d.push(new c(t,e)),1!==d.length||y||o(a)},c.prototype.run=function(){this.fun.apply(null,this.array)},f.title="browser",f.browser=!0,f.env={},f.argv=[],f.version="",f.versions={},f.on=p,f.addListener=p,f.once=p,f.off=p,f.removeListener=p,f.removeAllListeners=p,f.emit=p,f.prependListener=p,f.prependOnceListener=p,f.listeners=function(t){return[]},f.binding=function(t){throw new Error("process.binding is not supported")},f.cwd=function(){return"/"},f.chdir=function(t){throw new Error("process.chdir is not supported")},f.umask=function(){return 0}},function(t,e,n){function r(t){var n,r=0;for(n in t)r=(r<<5)-r+t.charCodeAt(n),r|=0;return e.colors[Math.abs(r)%e.colors.length]}function o(t){function n(){if(n.enabled){var t=n,r=+new Date,o=r-(p||r);t.diff=o,t.prev=p,t.curr=r,p=r;for(var i=new Array(arguments.length),s=0;s<i.length;s++)i[s]=arguments[s];i[0]=e.coerce(i[0]),"string"!=typeof i[0]&&i.unshift("%O");var a=0;i[0]=i[0].replace(/%([a-zA-Z%])/g,function(n,r){if("%%"===n)return n;a++;var o=e.formatters[r];if("function"==typeof o){var s=i[a];n=o.call(t,s),i.splice(a,1),a--}return n}),e.formatArgs.call(t,i);var c=n.log||e.log||console.log.bind(console);c.apply(t,i)}}return n.namespace=t,n.enabled=e.enabled(t),n.useColors=e.useColors(),n.color=r(t),"function"==typeof e.init&&e.init(n),n}function i(t){e.save(t),e.names=[],e.skips=[];for(var n=("string"==typeof t?t:"").split(/[\s,]+/),r=n.length,o=0;o<r;o++)n[o]&&(t=n[o].replace(/\*/g,".*?"),"-"===t[0]?e.skips.push(new RegExp("^"+t.substr(1)+"$")):e.names.push(new RegExp("^"+t+"$")))}function s(){e.enable("")}function a(t){var n,r;for(n=0,r=e.skips.length;n<r;n++)if(e.skips[n].test(t))return!1;for(n=0,r=e.names.length;n<r;n++)if(e.names[n].test(t))return!0;return!1}function c(t){return t instanceof Error?t.stack||t.message:t}e=t.exports=o.debug=o["default"]=o,e.coerce=c,e.disable=s,e.enable=i,e.enabled=a,e.humanize=n(6),e.names=[],e.skips=[],e.formatters={};var p},function(t,e){function n(t){if(t=String(t),!(t.length>100)){var e=/^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(t);if(e){var n=parseFloat(e[1]),r=(e[2]||"ms").toLowerCase();switch(r){case"years":case"year":case"yrs":case"yr":case"y":return n*u;case"days":case"day":case"d":return n*p;case"hours":case"hour":case"hrs":case"hr":case"h":return n*c;case"minutes":case"minute":case"mins":case"min":case"m":return n*a;case"seconds":case"second":case"secs":case"sec":case"s":return n*s;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return n;default:return}}}}function r(t){return t>=p?Math.round(t/p)+"d":t>=c?Math.round(t/c)+"h":t>=a?Math.round(t/a)+"m":t>=s?Math.round(t/s)+"s":t+"ms"}function o(t){return i(t,p,"day")||i(t,c,"hour")||i(t,a,"minute")||i(t,s,"second")||t+" ms"}function i(t,e,n){if(!(t<e))return t<1.5*e?Math.floor(t/e)+" "+n:Math.ceil(t/e)+" "+n+"s"}var s=1e3,a=60*s,c=60*a,p=24*c,u=365.25*p;t.exports=function(t,e){e=e||{};var i=typeof t;if("string"===i&&t.length>0)return n(t);if("number"===i&&isNaN(t)===!1)return e["long"]?o(t):r(t);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(t))}},function(t,e,n){function r(){}function o(t){var n=""+t.type;return e.BINARY_EVENT!==t.type&&e.BINARY_ACK!==t.type||(n+=t.attachments+"-"),t.nsp&&"/"!==t.nsp&&(n+=t.nsp+","),null!=t.id&&(n+=t.id),null!=t.data&&(n+=JSON.stringify(t.data)),h("encoded %j as %s",t,n),n}function i(t,e){function n(t){var n=d.deconstructPacket(t),r=o(n.packet),i=n.buffers;i.unshift(r),e(i)}d.removeBlobs(t,n)}function s(){this.reconstructor=null}function a(t){var n=0,r={type:Number(t.charAt(0))};if(null==e.types[r.type])return u();if(e.BINARY_EVENT===r.type||e.BINARY_ACK===r.type){for(var o="";"-"!==t.charAt(++n)&&(o+=t.charAt(n),n!=t.length););if(o!=Number(o)||"-"!==t.charAt(n))throw new Error("Illegal attachments");r.attachments=Number(o)}if("/"===t.charAt(n+1))for(r.nsp="";++n;){var i=t.charAt(n);if(","===i)break;if(r.nsp+=i,n===t.length)break}else r.nsp="/";var s=t.charAt(n+1);if(""!==s&&Number(s)==s){for(r.id="";++n;){var i=t.charAt(n);if(null==i||Number(i)!=i){--n;break}if(r.id+=t.charAt(n),n===t.length)break}r.id=Number(r.id)}return t.charAt(++n)&&(r=c(r,t.substr(n))),h("decoded %s as %j",t,r),r}function c(t,e){try{t.data=JSON.parse(e)}catch(n){return u()}return t}function p(t){this.reconPack=t,this.buffers=[]}function u(){return{type:e.ERROR,data:"parser error"}}var h=n(3)("socket.io-parser"),f=n(8),l=n(9),d=n(11),y=n(12);e.protocol=4,e.types=["CONNECT","DISCONNECT","EVENT","ACK","ERROR","BINARY_EVENT","BINARY_ACK"],e.CONNECT=0,e.DISCONNECT=1,e.EVENT=2,e.ACK=3,e.ERROR=4,e.BINARY_EVENT=5,e.BINARY_ACK=6,e.Encoder=r,e.Decoder=s,r.prototype.encode=function(t,n){if(t.type!==e.EVENT&&t.type!==e.ACK||!l(t.data)||(t.type=t.type===e.EVENT?e.BINARY_EVENT:e.BINARY_ACK),h("encoding packet %j",t),e.BINARY_EVENT===t.type||e.BINARY_ACK===t.type)i(t,n);else{var r=o(t);n([r])}},f(s.prototype),s.prototype.add=function(t){var n;if("string"==typeof t)n=a(t),e.BINARY_EVENT===n.type||e.BINARY_ACK===n.type?(this.reconstructor=new p(n),0===this.reconstructor.reconPack.attachments&&this.emit("decoded",n)):this.emit("decoded",n);else{if(!y(t)&&!t.base64)throw new Error("Unknown type: "+t);if(!this.reconstructor)throw new Error("got binary data when not reconstructing a packet");n=this.reconstructor.takeBinaryData(t),n&&(this.reconstructor=null,this.emit("decoded",n))}},s.prototype.destroy=function(){this.reconstructor&&this.reconstructor.finishedReconstruction()},p.prototype.takeBinaryData=function(t){if(this.buffers.push(t),this.buffers.length===this.reconPack.attachments){var e=d.reconstructPacket(this.reconPack,this.buffers);return this.finishedReconstruction(),e}return null},p.prototype.finishedReconstruction=function(){this.reconPack=null,this.buffers=[]}},function(t,e,n){function r(t){if(t)return o(t)}function o(t){for(var e in r.prototype)t[e]=r.prototype[e];return t}t.exports=r,r.prototype.on=r.prototype.addEventListener=function(t,e){return this._callbacks=this._callbacks||{},(this._callbacks["$"+t]=this._callbacks["$"+t]||[]).push(e),this},r.prototype.once=function(t,e){function n(){this.off(t,n),e.apply(this,arguments)}return n.fn=e,this.on(t,n),this},r.prototype.off=r.prototype.removeListener=r.prototype.removeAllListeners=r.prototype.removeEventListener=function(t,e){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var n=this._callbacks["$"+t];if(!n)return this;if(1==arguments.length)return delete this._callbacks["$"+t],this;for(var r,o=0;o<n.length;o++)if(r=n[o],r===e||r.fn===e){n.splice(o,1);break}return this},r.prototype.emit=function(t){this._callbacks=this._callbacks||{};var e=[].slice.call(arguments,1),n=this._callbacks["$"+t];if(n){n=n.slice(0);for(var r=0,o=n.length;r<o;++r)n[r].apply(this,e)}return this},r.prototype.listeners=function(t){return this._callbacks=this._callbacks||{},this._callbacks["$"+t]||[]},r.prototype.hasListeners=function(t){return!!this.listeners(t).length}},function(t,e,n){(function(e){function r(t){if(!t||"object"!=typeof t)return!1;if(o(t)){for(var n=0,i=t.length;n<i;n++)if(r(t[n]))return!0;return!1}if("function"==typeof e.Buffer&&e.Buffer.isBuffer&&e.Buffer.isBuffer(t)||"function"==typeof e.ArrayBuffer&&t instanceof ArrayBuffer||s&&t instanceof Blob||a&&t instanceof File)return!0;if(t.toJSON&&"function"==typeof t.toJSON&&1===arguments.length)return r(t.toJSON(),!0);for(var c in t)if(Object.prototype.hasOwnProperty.call(t,c)&&r(t[c]))return!0;return!1}var o=n(10),i=Object.prototype.toString,s="function"==typeof e.Blob||"[object BlobConstructor]"===i.call(e.Blob),a="function"==typeof e.File||"[object FileConstructor]"===i.call(e.File);t.exports=r}).call(e,function(){return this}())},function(t,e){var n={}.toString;t.exports=Array.isArray||function(t){return"[object Array]"==n.call(t)}},function(t,e,n){(function(t){function r(t,e){if(!t)return t;if(s(t)){var n={_placeholder:!0,num:e.length};return e.push(t),n}if(i(t)){for(var o=new Array(t.length),a=0;a<t.length;a++)o[a]=r(t[a],e);return o}if("object"==typeof t&&!(t instanceof Date)){var o={};for(var c in t)o[c]=r(t[c],e);return o}return t}function o(t,e){if(!t)return t;if(t&&t._placeholder)return e[t.num];if(i(t))for(var n=0;n<t.length;n++)t[n]=o(t[n],e);else if("object"==typeof t)for(var r in t)t[r]=o(t[r],e);return t}var i=n(10),s=n(12),a=Object.prototype.toString,c="function"==typeof t.Blob||"[object BlobConstructor]"===a.call(t.Blob),p="function"==typeof t.File||"[object FileConstructor]"===a.call(t.File);e.deconstructPacket=function(t){var e=[],n=t.data,o=t;return o.data=r(n,e),o.attachments=e.length,{packet:o,buffers:e}},e.reconstructPacket=function(t,e){return t.data=o(t.data,e),t.attachments=void 0,t},e.removeBlobs=function(t,e){function n(t,a,u){if(!t)return t;if(c&&t instanceof Blob||p&&t instanceof File){r++;var h=new FileReader;h.onload=function(){u?u[a]=this.result:o=this.result,--r||e(o)},h.readAsArrayBuffer(t)}else if(i(t))for(var f=0;f<t.length;f++)n(t[f],f,t);else if("object"==typeof t&&!s(t))for(var l in t)n(t[l],l,t)}var r=0,o=t;n(o),r||e(o)}}).call(e,function(){return this}())},function(t,e){(function(e){function n(t){return e.Buffer&&e.Buffer.isBuffer(t)||e.ArrayBuffer&&t instanceof ArrayBuffer}t.exports=n}).call(e,function(){return this}())},function(t,e,n){"use strict";function r(t,e){if(!(this instanceof r))return new r(t,e);t&&"object"===("undefined"==typeof t?"undefined":o(t))&&(e=t,t=void 0),e=e||{},e.path=e.path||"/socket.io",this.nsps={},this.subs=[],this.opts=e,this.reconnection(e.reconnection!==!1),this.reconnectionAttempts(e.reconnectionAttempts||1/0),this.reconnectionDelay(e.reconnectionDelay||1e3),this.reconnectionDelayMax(e.reconnectionDelayMax||5e3),this.randomizationFactor(e.randomizationFactor||.5),this.backoff=new l({min:this.reconnectionDelay(),max:this.reconnectionDelayMax(),jitter:this.randomizationFactor()}),this.timeout(null==e.timeout?2e4:e.timeout),this.readyState="closed",this.uri=t,this.connecting=[],this.lastPing=null,this.encoding=!1,this.packetBuffer=[];var n=e.parser||c;this.encoder=new n.Encoder,this.decoder=new n.Decoder,this.autoConnect=e.autoConnect!==!1,this.autoConnect&&this.open()}var o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=n(14),s=n(37),a=n(8),c=n(7),p=n(39),u=n(40),h=n(3)("socket.io-client:manager"),f=n(36),l=n(41),d=Object.prototype.hasOwnProperty;t.exports=r,r.prototype.emitAll=function(){this.emit.apply(this,arguments);for(var t in this.nsps)d.call(this.nsps,t)&&this.nsps[t].emit.apply(this.nsps[t],arguments)},r.prototype.updateSocketIds=function(){for(var t in this.nsps)d.call(this.nsps,t)&&(this.nsps[t].id=this.generateId(t))},r.prototype.generateId=function(t){return("/"===t?"":t+"#")+this.engine.id},a(r.prototype),r.prototype.reconnection=function(t){return arguments.length?(this._reconnection=!!t,this):this._reconnection},r.prototype.reconnectionAttempts=function(t){return arguments.length?(this._reconnectionAttempts=t,this):this._reconnectionAttempts},r.prototype.reconnectionDelay=function(t){return arguments.length?(this._reconnectionDelay=t,this.backoff&&this.backoff.setMin(t),this):this._reconnectionDelay},r.prototype.randomizationFactor=function(t){return arguments.length?(this._randomizationFactor=t,this.backoff&&this.backoff.setJitter(t),this):this._randomizationFactor},r.prototype.reconnectionDelayMax=function(t){return arguments.length?(this._reconnectionDelayMax=t,this.backoff&&this.backoff.setMax(t),this):this._reconnectionDelayMax},r.prototype.timeout=function(t){return arguments.length?(this._timeout=t,this):this._timeout},r.prototype.maybeReconnectOnOpen=function(){!this.reconnecting&&this._reconnection&&0===this.backoff.attempts&&this.reconnect()},r.prototype.open=r.prototype.connect=function(t,e){if(h("readyState %s",this.readyState),~this.readyState.indexOf("open"))return this;h("opening %s",this.uri),this.engine=i(this.uri,this.opts);var n=this.engine,r=this;this.readyState="opening",this.skipReconnect=!1;var o=p(n,"open",function(){r.onopen(),t&&t()}),s=p(n,"error",function(e){if(h("connect_error"),r.cleanup(),r.readyState="closed",r.emitAll("connect_error",e),t){var n=new Error("Connection error");n.data=e,t(n)}else r.maybeReconnectOnOpen()});if(!1!==this._timeout){var a=this._timeout;h("connect attempt will timeout after %d",a);var c=setTimeout(function(){h("connect attempt timed out after %d",a),o.destroy(),n.close(),n.emit("error","timeout"),r.emitAll("connect_timeout",a)},a);this.subs.push({destroy:function(){clearTimeout(c)}})}return this.subs.push(o),this.subs.push(s),this},r.prototype.onopen=function(){h("open"),this.cleanup(),this.readyState="open",this.emit("open");var t=this.engine;this.subs.push(p(t,"data",u(this,"ondata"))),this.subs.push(p(t,"ping",u(this,"onping"))),this.subs.push(p(t,"pong",u(this,"onpong"))),this.subs.push(p(t,"error",u(this,"onerror"))),this.subs.push(p(t,"close",u(this,"onclose"))),this.subs.push(p(this.decoder,"decoded",u(this,"ondecoded")))},r.prototype.onping=function(){this.lastPing=new Date,this.emitAll("ping")},r.prototype.onpong=function(){this.emitAll("pong",new Date-this.lastPing)},r.prototype.ondata=function(t){this.decoder.add(t)},r.prototype.ondecoded=function(t){this.emit("packet",t)},r.prototype.onerror=function(t){h("error",t),this.emitAll("error",t)},r.prototype.socket=function(t,e){function n(){~f(o.connecting,r)||o.connecting.push(r)}var r=this.nsps[t];if(!r){r=new s(this,t,e),this.nsps[t]=r;var o=this;r.on("connecting",n),r.on("connect",function(){r.id=o.generateId(t)}),this.autoConnect&&n()}return r},r.prototype.destroy=function(t){var e=f(this.connecting,t);~e&&this.connecting.splice(e,1),this.connecting.length||this.close()},r.prototype.packet=function(t){h("writing packet %j",t);var e=this;t.query&&0===t.type&&(t.nsp+="?"+t.query),e.encoding?e.packetBuffer.push(t):(e.encoding=!0,this.encoder.encode(t,function(n){for(var r=0;r<n.length;r++)e.engine.write(n[r],t.options);e.encoding=!1,e.processPacketQueue()}))},r.prototype.processPacketQueue=function(){if(this.packetBuffer.length>0&&!this.encoding){var t=this.packetBuffer.shift();this.packet(t)}},r.prototype.cleanup=function(){h("cleanup");for(var t=this.subs.length,e=0;e<t;e++){var n=this.subs.shift();n.destroy()}this.packetBuffer=[],this.encoding=!1,this.lastPing=null,this.decoder.destroy()},r.prototype.close=r.prototype.disconnect=function(){h("disconnect"),this.skipReconnect=!0,this.reconnecting=!1,"opening"===this.readyState&&this.cleanup(),this.backoff.reset(),this.readyState="closed",this.engine&&this.engine.close()},r.prototype.onclose=function(t){h("onclose"),this.cleanup(),this.backoff.reset(),this.readyState="closed",this.emit("close",t),this._reconnection&&!this.skipReconnect&&this.reconnect()},r.prototype.reconnect=function(){if(this.reconnecting||this.skipReconnect)return this;var t=this;if(this.backoff.attempts>=this._reconnectionAttempts)h("reconnect failed"),this.backoff.reset(),this.emitAll("reconnect_failed"),this.reconnecting=!1;else{var e=this.backoff.duration();h("will wait %dms before reconnect attempt",e),this.reconnecting=!0;var n=setTimeout(function(){t.skipReconnect||(h("attempting reconnect"),t.emitAll("reconnect_attempt",t.backoff.attempts),t.emitAll("reconnecting",t.backoff.attempts),t.skipReconnect||t.open(function(e){e?(h("reconnect attempt error"),t.reconnecting=!1,t.reconnect(),t.emitAll("reconnect_error",e.data)):(h("reconnect success"),t.onreconnect())}))},e);this.subs.push({destroy:function(){clearTimeout(n)}})}},r.prototype.onreconnect=function(){var t=this.backoff.attempts;this.reconnecting=!1,this.backoff.reset(),this.updateSocketIds(),this.emitAll("reconnect",t)}},function(t,e,n){t.exports=n(15),t.exports.parser=n(22)},function(t,e,n){(function(e){function r(t,n){if(!(this instanceof r))return new r(t,n);n=n||{},t&&"object"==typeof t&&(n=t,t=null),t?(t=u(t),n.hostname=t.host,n.secure="https"===t.protocol||"wss"===t.protocol,n.port=t.port,t.query&&(n.query=t.query)):n.host&&(n.hostname=u(n.host).host),this.secure=null!=n.secure?n.secure:e.location&&"https:"===location.protocol,n.hostname&&!n.port&&(n.port=this.secure?"443":"80"),this.agent=n.agent||!1,this.hostname=n.hostname||(e.location?location.hostname:"localhost"),this.port=n.port||(e.location&&location.port?location.port:this.secure?443:80),this.query=n.query||{},"string"==typeof this.query&&(this.query=h.decode(this.query)),this.upgrade=!1!==n.upgrade,this.path=(n.path||"/engine.io").replace(/\/$/,"")+"/",this.forceJSONP=!!n.forceJSONP,this.jsonp=!1!==n.jsonp,this.forceBase64=!!n.forceBase64,this.enablesXDR=!!n.enablesXDR,this.timestampParam=n.timestampParam||"t",this.timestampRequests=n.timestampRequests,this.transports=n.transports||["polling","websocket"],this.transportOptions=n.transportOptions||{},this.readyState="",this.writeBuffer=[],this.prevBufferLen=0,this.policyPort=n.policyPort||843,this.rememberUpgrade=n.rememberUpgrade||!1,this.binaryType=null,this.onlyBinaryUpgrades=n.onlyBinaryUpgrades,this.perMessageDeflate=!1!==n.perMessageDeflate&&(n.perMessageDeflate||{}),!0===this.perMessageDeflate&&(this.perMessageDeflate={}),this.perMessageDeflate&&null==this.perMessageDeflate.threshold&&(this.perMessageDeflate.threshold=1024),this.pfx=n.pfx||null,this.key=n.key||null,this.passphrase=n.passphrase||null,this.cert=n.cert||null,this.ca=n.ca||null,this.ciphers=n.ciphers||null,this.rejectUnauthorized=void 0===n.rejectUnauthorized||n.rejectUnauthorized,this.forceNode=!!n.forceNode;var o="object"==typeof e&&e;o.global===o&&(n.extraHeaders&&Object.keys(n.extraHeaders).length>0&&(this.extraHeaders=n.extraHeaders),n.localAddress&&(this.localAddress=n.localAddress)),this.id=null,this.upgrades=null,this.pingInterval=null,this.pingTimeout=null,this.pingIntervalTimer=null,this.pingTimeoutTimer=null,this.open()}function o(t){var e={};for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e}var i=n(16),s=n(8),a=n(3)("engine.io-client:socket"),c=n(36),p=n(22),u=n(2),h=n(30);t.exports=r,r.priorWebsocketSuccess=!1,s(r.prototype),r.protocol=p.protocol,r.Socket=r,r.Transport=n(21),r.transports=n(16),r.parser=n(22),r.prototype.createTransport=function(t){a('creating transport "%s"',t);var e=o(this.query);e.EIO=p.protocol,e.transport=t;var n=this.transportOptions[t]||{};this.id&&(e.sid=this.id);var r=new i[t]({query:e,socket:this,agent:n.agent||this.agent,hostname:n.hostname||this.hostname,port:n.port||this.port,secure:n.secure||this.secure,path:n.path||this.path,forceJSONP:n.forceJSONP||this.forceJSONP,jsonp:n.jsonp||this.jsonp,forceBase64:n.forceBase64||this.forceBase64,enablesXDR:n.enablesXDR||this.enablesXDR,timestampRequests:n.timestampRequests||this.timestampRequests,timestampParam:n.timestampParam||this.timestampParam,policyPort:n.policyPort||this.policyPort,pfx:n.pfx||this.pfx,key:n.key||this.key,passphrase:n.passphrase||this.passphrase,cert:n.cert||this.cert,ca:n.ca||this.ca,ciphers:n.ciphers||this.ciphers,rejectUnauthorized:n.rejectUnauthorized||this.rejectUnauthorized,perMessageDeflate:n.perMessageDeflate||this.perMessageDeflate,extraHeaders:n.extraHeaders||this.extraHeaders,forceNode:n.forceNode||this.forceNode,localAddress:n.localAddress||this.localAddress,requestTimeout:n.requestTimeout||this.requestTimeout,protocols:n.protocols||void 0});return r},r.prototype.open=function(){var t;if(this.rememberUpgrade&&r.priorWebsocketSuccess&&this.transports.indexOf("websocket")!==-1)t="websocket";else{if(0===this.transports.length){var e=this;return void setTimeout(function(){e.emit("error","No transports available")},0)}t=this.transports[0]}this.readyState="opening";try{t=this.createTransport(t)}catch(n){return this.transports.shift(),void this.open()}t.open(),this.setTransport(t)},r.prototype.setTransport=function(t){a("setting transport %s",t.name);var e=this;this.transport&&(a("clearing existing transport %s",this.transport.name),this.transport.removeAllListeners()),this.transport=t,t.on("drain",function(){e.onDrain()}).on("packet",function(t){e.onPacket(t)}).on("error",function(t){e.onError(t)}).on("close",function(){e.onClose("transport close")})},r.prototype.probe=function(t){function e(){if(f.onlyBinaryUpgrades){var e=!this.supportsBinary&&f.transport.supportsBinary;h=h||e}h||(a('probe transport "%s" opened',t),u.send([{type:"ping",data:"probe"}]),u.once("packet",function(e){if(!h)if("pong"===e.type&&"probe"===e.data){if(a('probe transport "%s" pong',t),f.upgrading=!0,f.emit("upgrading",u),!u)return;r.priorWebsocketSuccess="websocket"===u.name,a('pausing current transport "%s"',f.transport.name),f.transport.pause(function(){h||"closed"!==f.readyState&&(a("changing transport and sending upgrade packet"),p(),f.setTransport(u),u.send([{type:"upgrade"}]),f.emit("upgrade",u),u=null,f.upgrading=!1,f.flush())})}else{a('probe transport "%s" failed',t);var n=new Error("probe error");n.transport=u.name,f.emit("upgradeError",n)}}))}function n(){h||(h=!0,p(),u.close(),u=null)}function o(e){var r=new Error("probe error: "+e);r.transport=u.name,n(),a('probe transport "%s" failed because of error: %s',t,e),f.emit("upgradeError",r)}function i(){o("transport closed")}function s(){o("socket closed")}function c(t){u&&t.name!==u.name&&(a('"%s" works - aborting "%s"',t.name,u.name),n())}function p(){u.removeListener("open",e),u.removeListener("error",o),u.removeListener("close",i),f.removeListener("close",s),f.removeListener("upgrading",c)}a('probing transport "%s"',t);var u=this.createTransport(t,{probe:1}),h=!1,f=this;r.priorWebsocketSuccess=!1,u.once("open",e),u.once("error",o),u.once("close",i),this.once("close",s),this.once("upgrading",c),u.open()},r.prototype.onOpen=function(){if(a("socket open"),this.readyState="open",r.priorWebsocketSuccess="websocket"===this.transport.name,this.emit("open"),this.flush(),"open"===this.readyState&&this.upgrade&&this.transport.pause){a("starting upgrade probes");for(var t=0,e=this.upgrades.length;t<e;t++)this.probe(this.upgrades[t])}},r.prototype.onPacket=function(t){if("opening"===this.readyState||"open"===this.readyState||"closing"===this.readyState)switch(a('socket receive: type "%s", data "%s"',t.type,t.data),this.emit("packet",t),this.emit("heartbeat"),t.type){case"open":this.onHandshake(JSON.parse(t.data));break;case"pong":this.setPing(),this.emit("pong");break;case"error":var e=new Error("server error");e.code=t.data,this.onError(e);break;case"message":this.emit("data",t.data),this.emit("message",t.data)}else a('packet received with socket readyState "%s"',this.readyState)},r.prototype.onHandshake=function(t){this.emit("handshake",t),this.id=t.sid,this.transport.query.sid=t.sid,this.upgrades=this.filterUpgrades(t.upgrades),this.pingInterval=t.pingInterval,this.pingTimeout=t.pingTimeout,this.onOpen(),"closed"!==this.readyState&&(this.setPing(),this.removeListener("heartbeat",this.onHeartbeat),this.on("heartbeat",this.onHeartbeat))},r.prototype.onHeartbeat=function(t){clearTimeout(this.pingTimeoutTimer);var e=this;e.pingTimeoutTimer=setTimeout(function(){"closed"!==e.readyState&&e.onClose("ping timeout")},t||e.pingInterval+e.pingTimeout)},r.prototype.setPing=function(){var t=this;clearTimeout(t.pingIntervalTimer),t.pingIntervalTimer=setTimeout(function(){a("writing ping packet - expecting pong within %sms",t.pingTimeout),t.ping(),t.onHeartbeat(t.pingTimeout)},t.pingInterval)},r.prototype.ping=function(){var t=this;this.sendPacket("ping",function(){t.emit("ping")})},r.prototype.onDrain=function(){this.writeBuffer.splice(0,this.prevBufferLen),this.prevBufferLen=0,0===this.writeBuffer.length?this.emit("drain"):this.flush()},r.prototype.flush=function(){"closed"!==this.readyState&&this.transport.writable&&!this.upgrading&&this.writeBuffer.length&&(a("flushing %d packets in socket",this.writeBuffer.length),this.transport.send(this.writeBuffer),this.prevBufferLen=this.writeBuffer.length,this.emit("flush"))},r.prototype.write=r.prototype.send=function(t,e,n){return this.sendPacket("message",t,e,n),this},r.prototype.sendPacket=function(t,e,n,r){if("function"==typeof e&&(r=e,e=void 0),"function"==typeof n&&(r=n,n=null),"closing"!==this.readyState&&"closed"!==this.readyState){n=n||{},n.compress=!1!==n.compress;var o={type:t,data:e,options:n};this.emit("packetCreate",o),this.writeBuffer.push(o),r&&this.once("flush",r),this.flush()}},r.prototype.close=function(){function t(){r.onClose("forced close"),a("socket closing - telling transport to close"),r.transport.close()}function e(){r.removeListener("upgrade",e),r.removeListener("upgradeError",e),t()}function n(){r.once("upgrade",e),r.once("upgradeError",e)}if("opening"===this.readyState||"open"===this.readyState){this.readyState="closing";var r=this;this.writeBuffer.length?this.once("drain",function(){this.upgrading?n():t()}):this.upgrading?n():t()}return this},r.prototype.onError=function(t){a("socket error %j",t),r.priorWebsocketSuccess=!1,this.emit("error",t),this.onClose("transport error",t)},r.prototype.onClose=function(t,e){if("opening"===this.readyState||"open"===this.readyState||"closing"===this.readyState){a('socket close with reason: "%s"',t);var n=this;clearTimeout(this.pingIntervalTimer),clearTimeout(this.pingTimeoutTimer),this.transport.removeAllListeners("close"),this.transport.close(),this.transport.removeAllListeners(),this.readyState="closed",this.id=null,this.emit("close",t,e),n.writeBuffer=[],n.prevBufferLen=0}},r.prototype.filterUpgrades=function(t){for(var e=[],n=0,r=t.length;n<r;n++)~c(this.transports,t[n])&&e.push(t[n]);return e}}).call(e,function(){return this}())},function(t,e,n){(function(t){function r(e){var n,r=!1,a=!1,c=!1!==e.jsonp;if(t.location){var p="https:"===location.protocol,u=location.port;u||(u=p?443:80),r=e.hostname!==location.hostname||u!==e.port,a=e.secure!==p}if(e.xdomain=r,e.xscheme=a,n=new o(e),"open"in n&&!e.forceJSONP)return new i(e);if(!c)throw new Error("JSONP disabled");return new s(e)}var o=n(17),i=n(19),s=n(33),a=n(34);e.polling=r,e.websocket=a}).call(e,function(){return this}())},function(t,e,n){(function(e){var r=n(18);t.exports=function(t){var n=t.xdomain,o=t.xscheme,i=t.enablesXDR;try{if("undefined"!=typeof XMLHttpRequest&&(!n||r))return new XMLHttpRequest}catch(s){}try{if("undefined"!=typeof XDomainRequest&&!o&&i)return new XDomainRequest}catch(s){}if(!n)try{return new(e[["Active"].concat("Object").join("X")])("Microsoft.XMLHTTP");
}catch(s){}}}).call(e,function(){return this}())},function(t,e){try{t.exports="undefined"!=typeof XMLHttpRequest&&"withCredentials"in new XMLHttpRequest}catch(n){t.exports=!1}},function(t,e,n){(function(e){function r(){}function o(t){if(c.call(this,t),this.requestTimeout=t.requestTimeout,this.extraHeaders=t.extraHeaders,e.location){var n="https:"===location.protocol,r=location.port;r||(r=n?443:80),this.xd=t.hostname!==e.location.hostname||r!==t.port,this.xs=t.secure!==n}}function i(t){this.method=t.method||"GET",this.uri=t.uri,this.xd=!!t.xd,this.xs=!!t.xs,this.async=!1!==t.async,this.data=void 0!==t.data?t.data:null,this.agent=t.agent,this.isBinary=t.isBinary,this.supportsBinary=t.supportsBinary,this.enablesXDR=t.enablesXDR,this.requestTimeout=t.requestTimeout,this.pfx=t.pfx,this.key=t.key,this.passphrase=t.passphrase,this.cert=t.cert,this.ca=t.ca,this.ciphers=t.ciphers,this.rejectUnauthorized=t.rejectUnauthorized,this.extraHeaders=t.extraHeaders,this.create()}function s(){for(var t in i.requests)i.requests.hasOwnProperty(t)&&i.requests[t].abort()}var a=n(17),c=n(20),p=n(8),u=n(31),h=n(3)("engine.io-client:polling-xhr");t.exports=o,t.exports.Request=i,u(o,c),o.prototype.supportsBinary=!0,o.prototype.request=function(t){return t=t||{},t.uri=this.uri(),t.xd=this.xd,t.xs=this.xs,t.agent=this.agent||!1,t.supportsBinary=this.supportsBinary,t.enablesXDR=this.enablesXDR,t.pfx=this.pfx,t.key=this.key,t.passphrase=this.passphrase,t.cert=this.cert,t.ca=this.ca,t.ciphers=this.ciphers,t.rejectUnauthorized=this.rejectUnauthorized,t.requestTimeout=this.requestTimeout,t.extraHeaders=this.extraHeaders,new i(t)},o.prototype.doWrite=function(t,e){var n="string"!=typeof t&&void 0!==t,r=this.request({method:"POST",data:t,isBinary:n}),o=this;r.on("success",e),r.on("error",function(t){o.onError("xhr post error",t)}),this.sendXhr=r},o.prototype.doPoll=function(){h("xhr poll");var t=this.request(),e=this;t.on("data",function(t){e.onData(t)}),t.on("error",function(t){e.onError("xhr poll error",t)}),this.pollXhr=t},p(i.prototype),i.prototype.create=function(){var t={agent:this.agent,xdomain:this.xd,xscheme:this.xs,enablesXDR:this.enablesXDR};t.pfx=this.pfx,t.key=this.key,t.passphrase=this.passphrase,t.cert=this.cert,t.ca=this.ca,t.ciphers=this.ciphers,t.rejectUnauthorized=this.rejectUnauthorized;var n=this.xhr=new a(t),r=this;try{h("xhr open %s: %s",this.method,this.uri),n.open(this.method,this.uri,this.async);try{if(this.extraHeaders){n.setDisableHeaderCheck&&n.setDisableHeaderCheck(!0);for(var o in this.extraHeaders)this.extraHeaders.hasOwnProperty(o)&&n.setRequestHeader(o,this.extraHeaders[o])}}catch(s){}if("POST"===this.method)try{this.isBinary?n.setRequestHeader("Content-type","application/octet-stream"):n.setRequestHeader("Content-type","text/plain;charset=UTF-8")}catch(s){}try{n.setRequestHeader("Accept","*/*")}catch(s){}"withCredentials"in n&&(n.withCredentials=!0),this.requestTimeout&&(n.timeout=this.requestTimeout),this.hasXDR()?(n.onload=function(){r.onLoad()},n.onerror=function(){r.onError(n.responseText)}):n.onreadystatechange=function(){if(2===n.readyState){var t;try{t=n.getResponseHeader("Content-Type")}catch(e){}"application/octet-stream"===t&&(n.responseType="arraybuffer")}4===n.readyState&&(200===n.status||1223===n.status?r.onLoad():setTimeout(function(){r.onError(n.status)},0))},h("xhr data %s",this.data),n.send(this.data)}catch(s){return void setTimeout(function(){r.onError(s)},0)}e.document&&(this.index=i.requestsCount++,i.requests[this.index]=this)},i.prototype.onSuccess=function(){this.emit("success"),this.cleanup()},i.prototype.onData=function(t){this.emit("data",t),this.onSuccess()},i.prototype.onError=function(t){this.emit("error",t),this.cleanup(!0)},i.prototype.cleanup=function(t){if("undefined"!=typeof this.xhr&&null!==this.xhr){if(this.hasXDR()?this.xhr.onload=this.xhr.onerror=r:this.xhr.onreadystatechange=r,t)try{this.xhr.abort()}catch(n){}e.document&&delete i.requests[this.index],this.xhr=null}},i.prototype.onLoad=function(){var t;try{var e;try{e=this.xhr.getResponseHeader("Content-Type")}catch(n){}t="application/octet-stream"===e?this.xhr.response||this.xhr.responseText:this.xhr.responseText}catch(n){this.onError(n)}null!=t&&this.onData(t)},i.prototype.hasXDR=function(){return"undefined"!=typeof e.XDomainRequest&&!this.xs&&this.enablesXDR},i.prototype.abort=function(){this.cleanup()},i.requestsCount=0,i.requests={},e.document&&(e.attachEvent?e.attachEvent("onunload",s):e.addEventListener&&e.addEventListener("beforeunload",s,!1))}).call(e,function(){return this}())},function(t,e,n){function r(t){var e=t&&t.forceBase64;u&&!e||(this.supportsBinary=!1),o.call(this,t)}var o=n(21),i=n(30),s=n(22),a=n(31),c=n(32),p=n(3)("engine.io-client:polling");t.exports=r;var u=function(){var t=n(17),e=new t({xdomain:!1});return null!=e.responseType}();a(r,o),r.prototype.name="polling",r.prototype.doOpen=function(){this.poll()},r.prototype.pause=function(t){function e(){p("paused"),n.readyState="paused",t()}var n=this;if(this.readyState="pausing",this.polling||!this.writable){var r=0;this.polling&&(p("we are currently polling - waiting to pause"),r++,this.once("pollComplete",function(){p("pre-pause polling complete"),--r||e()})),this.writable||(p("we are currently writing - waiting to pause"),r++,this.once("drain",function(){p("pre-pause writing complete"),--r||e()}))}else e()},r.prototype.poll=function(){p("polling"),this.polling=!0,this.doPoll(),this.emit("poll")},r.prototype.onData=function(t){var e=this;p("polling got data %s",t);var n=function(t,n,r){return"opening"===e.readyState&&e.onOpen(),"close"===t.type?(e.onClose(),!1):void e.onPacket(t)};s.decodePayload(t,this.socket.binaryType,n),"closed"!==this.readyState&&(this.polling=!1,this.emit("pollComplete"),"open"===this.readyState?this.poll():p('ignoring poll - transport state "%s"',this.readyState))},r.prototype.doClose=function(){function t(){p("writing close packet"),e.write([{type:"close"}])}var e=this;"open"===this.readyState?(p("transport open - closing"),t()):(p("transport not open - deferring close"),this.once("open",t))},r.prototype.write=function(t){var e=this;this.writable=!1;var n=function(){e.writable=!0,e.emit("drain")};s.encodePayload(t,this.supportsBinary,function(t){e.doWrite(t,n)})},r.prototype.uri=function(){var t=this.query||{},e=this.secure?"https":"http",n="";!1!==this.timestampRequests&&(t[this.timestampParam]=c()),this.supportsBinary||t.sid||(t.b64=1),t=i.encode(t),this.port&&("https"===e&&443!==Number(this.port)||"http"===e&&80!==Number(this.port))&&(n=":"+this.port),t.length&&(t="?"+t);var r=this.hostname.indexOf(":")!==-1;return e+"://"+(r?"["+this.hostname+"]":this.hostname)+n+this.path+t}},function(t,e,n){function r(t){this.path=t.path,this.hostname=t.hostname,this.port=t.port,this.secure=t.secure,this.query=t.query,this.timestampParam=t.timestampParam,this.timestampRequests=t.timestampRequests,this.readyState="",this.agent=t.agent||!1,this.socket=t.socket,this.enablesXDR=t.enablesXDR,this.pfx=t.pfx,this.key=t.key,this.passphrase=t.passphrase,this.cert=t.cert,this.ca=t.ca,this.ciphers=t.ciphers,this.rejectUnauthorized=t.rejectUnauthorized,this.forceNode=t.forceNode,this.extraHeaders=t.extraHeaders,this.localAddress=t.localAddress}var o=n(22),i=n(8);t.exports=r,i(r.prototype),r.prototype.onError=function(t,e){var n=new Error(t);return n.type="TransportError",n.description=e,this.emit("error",n),this},r.prototype.open=function(){return"closed"!==this.readyState&&""!==this.readyState||(this.readyState="opening",this.doOpen()),this},r.prototype.close=function(){return"opening"!==this.readyState&&"open"!==this.readyState||(this.doClose(),this.onClose()),this},r.prototype.send=function(t){if("open"!==this.readyState)throw new Error("Transport not open");this.write(t)},r.prototype.onOpen=function(){this.readyState="open",this.writable=!0,this.emit("open")},r.prototype.onData=function(t){var e=o.decodePacket(t,this.socket.binaryType);this.onPacket(e)},r.prototype.onPacket=function(t){this.emit("packet",t)},r.prototype.onClose=function(){this.readyState="closed",this.emit("close")}},function(t,e,n){(function(t){function r(t,n){var r="b"+e.packets[t.type]+t.data.data;return n(r)}function o(t,n,r){if(!n)return e.encodeBase64Packet(t,r);var o=t.data,i=new Uint8Array(o),s=new Uint8Array(1+o.byteLength);s[0]=v[t.type];for(var a=0;a<i.length;a++)s[a+1]=i[a];return r(s.buffer)}function i(t,n,r){if(!n)return e.encodeBase64Packet(t,r);var o=new FileReader;return o.onload=function(){t.data=o.result,e.encodePacket(t,n,!0,r)},o.readAsArrayBuffer(t.data)}function s(t,n,r){if(!n)return e.encodeBase64Packet(t,r);if(g)return i(t,n,r);var o=new Uint8Array(1);o[0]=v[t.type];var s=new k([o.buffer,t.data]);return r(s)}function a(t){try{t=d.decode(t,{strict:!1})}catch(e){return!1}return t}function c(t,e,n){for(var r=new Array(t.length),o=l(t.length,n),i=function(t,n,o){e(n,function(e,n){r[t]=n,o(e,r)})},s=0;s<t.length;s++)i(s,t[s],o)}var p,u=n(23),h=n(9),f=n(24),l=n(25),d=n(26);t&&t.ArrayBuffer&&(p=n(28));var y="undefined"!=typeof navigator&&/Android/i.test(navigator.userAgent),m="undefined"!=typeof navigator&&/PhantomJS/i.test(navigator.userAgent),g=y||m;e.protocol=3;var v=e.packets={open:0,close:1,ping:2,pong:3,message:4,upgrade:5,noop:6},b=u(v),w={type:"error",data:"parser error"},k=n(29);e.encodePacket=function(e,n,i,a){"function"==typeof n&&(a=n,n=!1),"function"==typeof i&&(a=i,i=null);var c=void 0===e.data?void 0:e.data.buffer||e.data;if(t.ArrayBuffer&&c instanceof ArrayBuffer)return o(e,n,a);if(k&&c instanceof t.Blob)return s(e,n,a);if(c&&c.base64)return r(e,a);var p=v[e.type];return void 0!==e.data&&(p+=i?d.encode(String(e.data),{strict:!1}):String(e.data)),a(""+p)},e.encodeBase64Packet=function(n,r){var o="b"+e.packets[n.type];if(k&&n.data instanceof t.Blob){var i=new FileReader;return i.onload=function(){var t=i.result.split(",")[1];r(o+t)},i.readAsDataURL(n.data)}var s;try{s=String.fromCharCode.apply(null,new Uint8Array(n.data))}catch(a){for(var c=new Uint8Array(n.data),p=new Array(c.length),u=0;u<c.length;u++)p[u]=c[u];s=String.fromCharCode.apply(null,p)}return o+=t.btoa(s),r(o)},e.decodePacket=function(t,n,r){if(void 0===t)return w;if("string"==typeof t){if("b"===t.charAt(0))return e.decodeBase64Packet(t.substr(1),n);if(r&&(t=a(t),t===!1))return w;var o=t.charAt(0);return Number(o)==o&&b[o]?t.length>1?{type:b[o],data:t.substring(1)}:{type:b[o]}:w}var i=new Uint8Array(t),o=i[0],s=f(t,1);return k&&"blob"===n&&(s=new k([s])),{type:b[o],data:s}},e.decodeBase64Packet=function(t,e){var n=b[t.charAt(0)];if(!p)return{type:n,data:{base64:!0,data:t.substr(1)}};var r=p.decode(t.substr(1));return"blob"===e&&k&&(r=new k([r])),{type:n,data:r}},e.encodePayload=function(t,n,r){function o(t){return t.length+":"+t}function i(t,r){e.encodePacket(t,!!s&&n,!1,function(t){r(null,o(t))})}"function"==typeof n&&(r=n,n=null);var s=h(t);return n&&s?k&&!g?e.encodePayloadAsBlob(t,r):e.encodePayloadAsArrayBuffer(t,r):t.length?void c(t,i,function(t,e){return r(e.join(""))}):r("0:")},e.decodePayload=function(t,n,r){if("string"!=typeof t)return e.decodePayloadAsBinary(t,n,r);"function"==typeof n&&(r=n,n=null);var o;if(""===t)return r(w,0,1);for(var i,s,a="",c=0,p=t.length;c<p;c++){var u=t.charAt(c);if(":"===u){if(""===a||a!=(i=Number(a)))return r(w,0,1);if(s=t.substr(c+1,i),a!=s.length)return r(w,0,1);if(s.length){if(o=e.decodePacket(s,n,!1),w.type===o.type&&w.data===o.data)return r(w,0,1);var h=r(o,c+i,p);if(!1===h)return}c+=i,a=""}else a+=u}return""!==a?r(w,0,1):void 0},e.encodePayloadAsArrayBuffer=function(t,n){function r(t,n){e.encodePacket(t,!0,!0,function(t){return n(null,t)})}return t.length?void c(t,r,function(t,e){var r=e.reduce(function(t,e){var n;return n="string"==typeof e?e.length:e.byteLength,t+n.toString().length+n+2},0),o=new Uint8Array(r),i=0;return e.forEach(function(t){var e="string"==typeof t,n=t;if(e){for(var r=new Uint8Array(t.length),s=0;s<t.length;s++)r[s]=t.charCodeAt(s);n=r.buffer}e?o[i++]=0:o[i++]=1;for(var a=n.byteLength.toString(),s=0;s<a.length;s++)o[i++]=parseInt(a[s]);o[i++]=255;for(var r=new Uint8Array(n),s=0;s<r.length;s++)o[i++]=r[s]}),n(o.buffer)}):n(new ArrayBuffer(0))},e.encodePayloadAsBlob=function(t,n){function r(t,n){e.encodePacket(t,!0,!0,function(t){var e=new Uint8Array(1);if(e[0]=1,"string"==typeof t){for(var r=new Uint8Array(t.length),o=0;o<t.length;o++)r[o]=t.charCodeAt(o);t=r.buffer,e[0]=0}for(var i=t instanceof ArrayBuffer?t.byteLength:t.size,s=i.toString(),a=new Uint8Array(s.length+1),o=0;o<s.length;o++)a[o]=parseInt(s[o]);if(a[s.length]=255,k){var c=new k([e.buffer,a.buffer,t]);n(null,c)}})}c(t,r,function(t,e){return n(new k(e))})},e.decodePayloadAsBinary=function(t,n,r){"function"==typeof n&&(r=n,n=null);for(var o=t,i=[];o.byteLength>0;){for(var s=new Uint8Array(o),a=0===s[0],c="",p=1;255!==s[p];p++){if(c.length>310)return r(w,0,1);c+=s[p]}o=f(o,2+c.length),c=parseInt(c);var u=f(o,0,c);if(a)try{u=String.fromCharCode.apply(null,new Uint8Array(u))}catch(h){var l=new Uint8Array(u);u="";for(var p=0;p<l.length;p++)u+=String.fromCharCode(l[p])}i.push(u),o=f(o,c)}var d=i.length;i.forEach(function(t,o){r(e.decodePacket(t,n,!0),o,d)})}}).call(e,function(){return this}())},function(t,e){t.exports=Object.keys||function(t){var e=[],n=Object.prototype.hasOwnProperty;for(var r in t)n.call(t,r)&&e.push(r);return e}},function(t,e){t.exports=function(t,e,n){var r=t.byteLength;if(e=e||0,n=n||r,t.slice)return t.slice(e,n);if(e<0&&(e+=r),n<0&&(n+=r),n>r&&(n=r),e>=r||e>=n||0===r)return new ArrayBuffer(0);for(var o=new Uint8Array(t),i=new Uint8Array(n-e),s=e,a=0;s<n;s++,a++)i[a]=o[s];return i.buffer}},function(t,e){function n(t,e,n){function o(t,r){if(o.count<=0)throw new Error("after called too many times");--o.count,t?(i=!0,e(t),e=n):0!==o.count||i||e(null,r)}var i=!1;return n=n||r,o.count=t,0===t?e():o}function r(){}t.exports=n},function(t,e,n){var r;(function(t,o){!function(i){function s(t){for(var e,n,r=[],o=0,i=t.length;o<i;)e=t.charCodeAt(o++),e>=55296&&e<=56319&&o<i?(n=t.charCodeAt(o++),56320==(64512&n)?r.push(((1023&e)<<10)+(1023&n)+65536):(r.push(e),o--)):r.push(e);return r}function a(t){for(var e,n=t.length,r=-1,o="";++r<n;)e=t[r],e>65535&&(e-=65536,o+=w(e>>>10&1023|55296),e=56320|1023&e),o+=w(e);return o}function c(t,e){if(t>=55296&&t<=57343){if(e)throw Error("Lone surrogate U+"+t.toString(16).toUpperCase()+" is not a scalar value");return!1}return!0}function p(t,e){return w(t>>e&63|128)}function u(t,e){if(0==(4294967168&t))return w(t);var n="";return 0==(4294965248&t)?n=w(t>>6&31|192):0==(4294901760&t)?(c(t,e)||(t=65533),n=w(t>>12&15|224),n+=p(t,6)):0==(4292870144&t)&&(n=w(t>>18&7|240),n+=p(t,12),n+=p(t,6)),n+=w(63&t|128)}function h(t,e){e=e||{};for(var n,r=!1!==e.strict,o=s(t),i=o.length,a=-1,c="";++a<i;)n=o[a],c+=u(n,r);return c}function f(){if(b>=v)throw Error("Invalid byte index");var t=255&g[b];if(b++,128==(192&t))return 63&t;throw Error("Invalid continuation byte")}function l(t){var e,n,r,o,i;if(b>v)throw Error("Invalid byte index");if(b==v)return!1;if(e=255&g[b],b++,0==(128&e))return e;if(192==(224&e)){if(n=f(),i=(31&e)<<6|n,i>=128)return i;throw Error("Invalid continuation byte")}if(224==(240&e)){if(n=f(),r=f(),i=(15&e)<<12|n<<6|r,i>=2048)return c(i,t)?i:65533;throw Error("Invalid continuation byte")}if(240==(248&e)&&(n=f(),r=f(),o=f(),i=(7&e)<<18|n<<12|r<<6|o,i>=65536&&i<=1114111))return i;throw Error("Invalid UTF-8 detected")}function d(t,e){e=e||{};var n=!1!==e.strict;g=s(t),v=g.length,b=0;for(var r,o=[];(r=l(n))!==!1;)o.push(r);return a(o)}var y="object"==typeof e&&e,m=("object"==typeof t&&t&&t.exports==y&&t,"object"==typeof o&&o);m.global!==m&&m.window!==m||(i=m);var g,v,b,w=String.fromCharCode,k={version:"2.1.2",encode:h,decode:d};r=function(){return k}.call(e,n,e,t),!(void 0!==r&&(t.exports=r))}(this)}).call(e,n(27)(t),function(){return this}())},function(t,e){t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children=[],t.webpackPolyfill=1),t}},function(t,e){!function(){"use strict";for(var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",n=new Uint8Array(256),r=0;r<t.length;r++)n[t.charCodeAt(r)]=r;e.encode=function(e){var n,r=new Uint8Array(e),o=r.length,i="";for(n=0;n<o;n+=3)i+=t[r[n]>>2],i+=t[(3&r[n])<<4|r[n+1]>>4],i+=t[(15&r[n+1])<<2|r[n+2]>>6],i+=t[63&r[n+2]];return o%3===2?i=i.substring(0,i.length-1)+"=":o%3===1&&(i=i.substring(0,i.length-2)+"=="),i},e.decode=function(t){var e,r,o,i,s,a=.75*t.length,c=t.length,p=0;"="===t[t.length-1]&&(a--,"="===t[t.length-2]&&a--);var u=new ArrayBuffer(a),h=new Uint8Array(u);for(e=0;e<c;e+=4)r=n[t.charCodeAt(e)],o=n[t.charCodeAt(e+1)],i=n[t.charCodeAt(e+2)],s=n[t.charCodeAt(e+3)],h[p++]=r<<2|o>>4,h[p++]=(15&o)<<4|i>>2,h[p++]=(3&i)<<6|63&s;return u}}()},function(t,e){(function(e){function n(t){for(var e=0;e<t.length;e++){var n=t[e];if(n.buffer instanceof ArrayBuffer){var r=n.buffer;if(n.byteLength!==r.byteLength){var o=new Uint8Array(n.byteLength);o.set(new Uint8Array(r,n.byteOffset,n.byteLength)),r=o.buffer}t[e]=r}}}function r(t,e){e=e||{};var r=new i;n(t);for(var o=0;o<t.length;o++)r.append(t[o]);return e.type?r.getBlob(e.type):r.getBlob()}function o(t,e){return n(t),new Blob(t,e||{})}var i=e.BlobBuilder||e.WebKitBlobBuilder||e.MSBlobBuilder||e.MozBlobBuilder,s=function(){try{var t=new Blob(["hi"]);return 2===t.size}catch(e){return!1}}(),a=s&&function(){try{var t=new Blob([new Uint8Array([1,2])]);return 2===t.size}catch(e){return!1}}(),c=i&&i.prototype.append&&i.prototype.getBlob;t.exports=function(){return s?a?e.Blob:o:c?r:void 0}()}).call(e,function(){return this}())},function(t,e){e.encode=function(t){var e="";for(var n in t)t.hasOwnProperty(n)&&(e.length&&(e+="&"),e+=encodeURIComponent(n)+"="+encodeURIComponent(t[n]));return e},e.decode=function(t){for(var e={},n=t.split("&"),r=0,o=n.length;r<o;r++){var i=n[r].split("=");e[decodeURIComponent(i[0])]=decodeURIComponent(i[1])}return e}},function(t,e){t.exports=function(t,e){var n=function(){};n.prototype=e.prototype,t.prototype=new n,t.prototype.constructor=t}},function(t,e){"use strict";function n(t){var e="";do e=s[t%a]+e,t=Math.floor(t/a);while(t>0);return e}function r(t){var e=0;for(u=0;u<t.length;u++)e=e*a+c[t.charAt(u)];return e}function o(){var t=n(+new Date);return t!==i?(p=0,i=t):t+"."+n(p++)}for(var i,s="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_".split(""),a=64,c={},p=0,u=0;u<a;u++)c[s[u]]=u;o.encode=n,o.decode=r,t.exports=o},function(t,e,n){(function(e){function r(){}function o(t){i.call(this,t),this.query=this.query||{},a||(e.___eio||(e.___eio=[]),a=e.___eio),this.index=a.length;var n=this;a.push(function(t){n.onData(t)}),this.query.j=this.index,e.document&&e.addEventListener&&e.addEventListener("beforeunload",function(){n.script&&(n.script.onerror=r)},!1)}var i=n(20),s=n(31);t.exports=o;var a,c=/\n/g,p=/\\n/g;s(o,i),o.prototype.supportsBinary=!1,o.prototype.doClose=function(){this.script&&(this.script.parentNode.removeChild(this.script),this.script=null),this.form&&(this.form.parentNode.removeChild(this.form),this.form=null,this.iframe=null),i.prototype.doClose.call(this)},o.prototype.doPoll=function(){var t=this,e=document.createElement("script");this.script&&(this.script.parentNode.removeChild(this.script),this.script=null),e.async=!0,e.src=this.uri(),e.onerror=function(e){t.onError("jsonp poll error",e)};var n=document.getElementsByTagName("script")[0];n?n.parentNode.insertBefore(e,n):(document.head||document.body).appendChild(e),this.script=e;var r="undefined"!=typeof navigator&&/gecko/i.test(navigator.userAgent);r&&setTimeout(function(){var t=document.createElement("iframe");document.body.appendChild(t),document.body.removeChild(t)},100)},o.prototype.doWrite=function(t,e){function n(){r(),e()}function r(){if(o.iframe)try{o.form.removeChild(o.iframe)}catch(t){o.onError("jsonp polling iframe removal error",t)}try{var e='<iframe src="javascript:0" name="'+o.iframeId+'">';i=document.createElement(e)}catch(t){i=document.createElement("iframe"),i.name=o.iframeId,i.src="javascript:0"}i.id=o.iframeId,o.form.appendChild(i),o.iframe=i}var o=this;if(!this.form){var i,s=document.createElement("form"),a=document.createElement("textarea"),u=this.iframeId="eio_iframe_"+this.index;s.className="socketio",s.style.position="absolute",s.style.top="-1000px",s.style.left="-1000px",s.target=u,s.method="POST",s.setAttribute("accept-charset","utf-8"),a.name="d",s.appendChild(a),document.body.appendChild(s),this.form=s,this.area=a}this.form.action=this.uri(),r(),t=t.replace(p,"\\\n"),this.area.value=t.replace(c,"\\n");try{this.form.submit()}catch(h){}this.iframe.attachEvent?this.iframe.onreadystatechange=function(){"complete"===o.iframe.readyState&&n()}:this.iframe.onload=n}}).call(e,function(){return this}())},function(t,e,n){(function(e){function r(t){var e=t&&t.forceBase64;e&&(this.supportsBinary=!1),this.perMessageDeflate=t.perMessageDeflate,this.usingBrowserWebSocket=h&&!t.forceNode,this.protocols=t.protocols,this.usingBrowserWebSocket||(l=o),i.call(this,t)}var o,i=n(21),s=n(22),a=n(30),c=n(31),p=n(32),u=n(3)("engine.io-client:websocket"),h=e.WebSocket||e.MozWebSocket;if("undefined"==typeof window)try{o=n(35)}catch(f){}var l=h;l||"undefined"!=typeof window||(l=o),t.exports=r,c(r,i),r.prototype.name="websocket",r.prototype.supportsBinary=!0,r.prototype.doOpen=function(){if(this.check()){var t=this.uri(),e=this.protocols,n={agent:this.agent,perMessageDeflate:this.perMessageDeflate};n.pfx=this.pfx,n.key=this.key,n.passphrase=this.passphrase,n.cert=this.cert,n.ca=this.ca,n.ciphers=this.ciphers,n.rejectUnauthorized=this.rejectUnauthorized,this.extraHeaders&&(n.headers=this.extraHeaders),this.localAddress&&(n.localAddress=this.localAddress);try{this.ws=this.usingBrowserWebSocket?e?new l(t,e):new l(t):new l(t,e,n)}catch(r){return this.emit("error",r)}void 0===this.ws.binaryType&&(this.supportsBinary=!1),this.ws.supports&&this.ws.supports.binary?(this.supportsBinary=!0,this.ws.binaryType="nodebuffer"):this.ws.binaryType="arraybuffer",this.addEventListeners()}},r.prototype.addEventListeners=function(){var t=this;this.ws.onopen=function(){t.onOpen()},this.ws.onclose=function(){t.onClose()},this.ws.onmessage=function(e){t.onData(e.data)},this.ws.onerror=function(e){t.onError("websocket error",e)}},r.prototype.write=function(t){function n(){r.emit("flush"),setTimeout(function(){r.writable=!0,r.emit("drain")},0)}var r=this;this.writable=!1;for(var o=t.length,i=0,a=o;i<a;i++)!function(t){s.encodePacket(t,r.supportsBinary,function(i){if(!r.usingBrowserWebSocket){var s={};if(t.options&&(s.compress=t.options.compress),r.perMessageDeflate){var a="string"==typeof i?e.Buffer.byteLength(i):i.length;a<r.perMessageDeflate.threshold&&(s.compress=!1)}}try{r.usingBrowserWebSocket?r.ws.send(i):r.ws.send(i,s)}catch(c){u("websocket closed before onclose event")}--o||n()})}(t[i])},r.prototype.onClose=function(){i.prototype.onClose.call(this)},r.prototype.doClose=function(){"undefined"!=typeof this.ws&&this.ws.close()},r.prototype.uri=function(){var t=this.query||{},e=this.secure?"wss":"ws",n="";this.port&&("wss"===e&&443!==Number(this.port)||"ws"===e&&80!==Number(this.port))&&(n=":"+this.port),this.timestampRequests&&(t[this.timestampParam]=p()),this.supportsBinary||(t.b64=1),t=a.encode(t),t.length&&(t="?"+t);var r=this.hostname.indexOf(":")!==-1;return e+"://"+(r?"["+this.hostname+"]":this.hostname)+n+this.path+t},r.prototype.check=function(){return!(!l||"__initialize"in l&&this.name===r.prototype.name)}}).call(e,function(){return this}())},function(t,e){},function(t,e){var n=[].indexOf;t.exports=function(t,e){if(n)return t.indexOf(e);for(var r=0;r<t.length;++r)if(t[r]===e)return r;return-1}},function(t,e,n){"use strict";function r(t,e,n){this.io=t,this.nsp=e,this.json=this,this.ids=0,this.acks={},this.receiveBuffer=[],this.sendBuffer=[],this.connected=!1,this.disconnected=!0,n&&n.query&&(this.query=n.query),this.io.autoConnect&&this.open()}var o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=n(7),s=n(8),a=n(38),c=n(39),p=n(40),u=n(3)("socket.io-client:socket"),h=n(30);t.exports=e=r;var f={connect:1,connect_error:1,connect_timeout:1,connecting:1,disconnect:1,error:1,reconnect:1,reconnect_attempt:1,reconnect_failed:1,reconnect_error:1,reconnecting:1,ping:1,pong:1},l=s.prototype.emit;s(r.prototype),r.prototype.subEvents=function(){if(!this.subs){var t=this.io;this.subs=[c(t,"open",p(this,"onopen")),c(t,"packet",p(this,"onpacket")),c(t,"close",p(this,"onclose"))]}},r.prototype.open=r.prototype.connect=function(){return this.connected?this:(this.subEvents(),this.io.open(),"open"===this.io.readyState&&this.onopen(),this.emit("connecting"),this)},r.prototype.send=function(){var t=a(arguments);return t.unshift("message"),this.emit.apply(this,t),this},r.prototype.emit=function(t){if(f.hasOwnProperty(t))return l.apply(this,arguments),this;var e=a(arguments),n={type:i.EVENT,data:e};return n.options={},n.options.compress=!this.flags||!1!==this.flags.compress,"function"==typeof e[e.length-1]&&(u("emitting packet with ack id %d",this.ids),this.acks[this.ids]=e.pop(),n.id=this.ids++),this.connected?this.packet(n):this.sendBuffer.push(n),delete this.flags,this},r.prototype.packet=function(t){t.nsp=this.nsp,this.io.packet(t)},r.prototype.onopen=function(){if(u("transport is open - connecting"),"/"!==this.nsp)if(this.query){var t="object"===o(this.query)?h.encode(this.query):this.query;u("sending connect packet with query %s",t),this.packet({type:i.CONNECT,query:t})}else this.packet({type:i.CONNECT})},r.prototype.onclose=function(t){u("close (%s)",t),this.connected=!1,this.disconnected=!0,delete this.id,this.emit("disconnect",t)},r.prototype.onpacket=function(t){if(t.nsp===this.nsp)switch(t.type){case i.CONNECT:this.onconnect();break;case i.EVENT:this.onevent(t);break;case i.BINARY_EVENT:this.onevent(t);break;case i.ACK:this.onack(t);break;case i.BINARY_ACK:this.onack(t);break;case i.DISCONNECT:this.ondisconnect();break;case i.ERROR:this.emit("error",t.data)}},r.prototype.onevent=function(t){var e=t.data||[];u("emitting event %j",e),null!=t.id&&(u("attaching ack callback to event"),e.push(this.ack(t.id))),this.connected?l.apply(this,e):this.receiveBuffer.push(e)},r.prototype.ack=function(t){var e=this,n=!1;return function(){if(!n){n=!0;var r=a(arguments);u("sending ack %j",r),e.packet({type:i.ACK,id:t,data:r})}}},r.prototype.onack=function(t){var e=this.acks[t.id];"function"==typeof e?(u("calling ack %s with %j",t.id,t.data),e.apply(this,t.data),delete this.acks[t.id]):u("bad ack %s",t.id)},r.prototype.onconnect=function(){this.connected=!0,this.disconnected=!1,this.emit("connect"),this.emitBuffered()},r.prototype.emitBuffered=function(){var t;for(t=0;t<this.receiveBuffer.length;t++)l.apply(this,this.receiveBuffer[t]);for(this.receiveBuffer=[],t=0;t<this.sendBuffer.length;t++)this.packet(this.sendBuffer[t]);this.sendBuffer=[]},r.prototype.ondisconnect=function(){u("server disconnect (%s)",this.nsp),this.destroy(),this.onclose("io server disconnect")},r.prototype.destroy=function(){if(this.subs){for(var t=0;t<this.subs.length;t++)this.subs[t].destroy();this.subs=null}this.io.destroy(this)},r.prototype.close=r.prototype.disconnect=function(){return this.connected&&(u("performing disconnect (%s)",this.nsp),this.packet({type:i.DISCONNECT})),this.destroy(),this.connected&&this.onclose("io client disconnect"),this},r.prototype.compress=function(t){return this.flags=this.flags||{},this.flags.compress=t,this}},function(t,e){function n(t,e){var n=[];e=e||0;for(var r=e||0;r<t.length;r++)n[r-e]=t[r];return n}t.exports=n},function(t,e){"use strict";function n(t,e,n){return t.on(e,n),{destroy:function(){t.removeListener(e,n)}}}t.exports=n},function(t,e){var n=[].slice;t.exports=function(t,e){if("string"==typeof e&&(e=t[e]),"function"!=typeof e)throw new Error("bind() requires a function");var r=n.call(arguments,2);return function(){return e.apply(t,r.concat(n.call(arguments)))}}},function(t,e){function n(t){t=t||{},this.ms=t.min||100,this.max=t.max||1e4,this.factor=t.factor||2,this.jitter=t.jitter>0&&t.jitter<=1?t.jitter:0,this.attempts=0}t.exports=n,n.prototype.duration=function(){var t=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var e=Math.random(),n=Math.floor(e*this.jitter*t);t=0==(1&Math.floor(10*e))?t-n:t+n}return 0|Math.min(t,this.max)},n.prototype.reset=function(){this.attempts=0},n.prototype.setMin=function(t){this.ms=t},n.prototype.setMax=function(t){this.max=t},n.prototype.setJitter=function(t){this.jitter=t}}])});
//# sourceMappingURL=socket.io.js.map
!function(){"use strict";function o(){var o=window,t=document;if(!("scrollBehavior"in t.documentElement.style&&!0!==o.__forceSmoothScrollPolyfill__)){var l,e=o.HTMLElement||o.Element,r=468,i={scroll:o.scroll||o.scrollTo,scrollBy:o.scrollBy,elementScroll:e.prototype.scroll||n,scrollIntoView:e.prototype.scrollIntoView},s=o.performance&&o.performance.now?o.performance.now.bind(o.performance):Date.now,c=(l=o.navigator.userAgent,new RegExp(["MSIE ","Trident/","Edge/"].join("|")).test(l)?1:0);o.scroll=o.scrollTo=function(){void 0!==arguments[0]&&(!0!==f(arguments[0])?h.call(o,t.body,void 0!==arguments[0].left?~~arguments[0].left:o.scrollX||o.pageXOffset,void 0!==arguments[0].top?~~arguments[0].top:o.scrollY||o.pageYOffset):i.scroll.call(o,void 0!==arguments[0].left?arguments[0].left:"object"!=typeof arguments[0]?arguments[0]:o.scrollX||o.pageXOffset,void 0!==arguments[0].top?arguments[0].top:void 0!==arguments[1]?arguments[1]:o.scrollY||o.pageYOffset))},o.scrollBy=function(){void 0!==arguments[0]&&(f(arguments[0])?i.scrollBy.call(o,void 0!==arguments[0].left?arguments[0].left:"object"!=typeof arguments[0]?arguments[0]:0,void 0!==arguments[0].top?arguments[0].top:void 0!==arguments[1]?arguments[1]:0):h.call(o,t.body,~~arguments[0].left+(o.scrollX||o.pageXOffset),~~arguments[0].top+(o.scrollY||o.pageYOffset)))},e.prototype.scroll=e.prototype.scrollTo=function(){if(void 0!==arguments[0])if(!0!==f(arguments[0])){var o=arguments[0].left,t=arguments[0].top;h.call(this,this,void 0===o?this.scrollLeft:~~o,void 0===t?this.scrollTop:~~t)}else{if("number"==typeof arguments[0]&&void 0===arguments[1])throw new SyntaxError("Value could not be converted");i.elementScroll.call(this,void 0!==arguments[0].left?~~arguments[0].left:"object"!=typeof arguments[0]?~~arguments[0]:this.scrollLeft,void 0!==arguments[0].top?~~arguments[0].top:void 0!==arguments[1]?~~arguments[1]:this.scrollTop)}},e.prototype.scrollBy=function(){void 0!==arguments[0]&&(!0!==f(arguments[0])?this.scroll({left:~~arguments[0].left+this.scrollLeft,top:~~arguments[0].top+this.scrollTop,behavior:arguments[0].behavior}):i.elementScroll.call(this,void 0!==arguments[0].left?~~arguments[0].left+this.scrollLeft:~~arguments[0]+this.scrollLeft,void 0!==arguments[0].top?~~arguments[0].top+this.scrollTop:~~arguments[1]+this.scrollTop))},e.prototype.scrollIntoView=function(){if(!0!==f(arguments[0])){var l=function(o){for(;o!==t.body&&!1===(e=p(l=o,"Y")&&a(l,"Y"),r=p(l,"X")&&a(l,"X"),e||r);)o=o.parentNode||o.host;var l,e,r;return o}(this),e=l.getBoundingClientRect(),r=this.getBoundingClientRect();l!==t.body?(h.call(this,l,l.scrollLeft+r.left-e.left,l.scrollTop+r.top-e.top),"fixed"!==o.getComputedStyle(l).position&&o.scrollBy({left:e.left,top:e.top,behavior:"smooth"})):o.scrollBy({left:r.left,top:r.top,behavior:"smooth"})}else i.scrollIntoView.call(this,void 0===arguments[0]||arguments[0])}}function n(o,t){this.scrollLeft=o,this.scrollTop=t}function f(o){if(null===o||"object"!=typeof o||void 0===o.behavior||"auto"===o.behavior||"instant"===o.behavior)return!0;if("object"==typeof o&&"smooth"===o.behavior)return!1;throw new TypeError("behavior member of ScrollOptions "+o.behavior+" is not a valid value for enumeration ScrollBehavior.")}function p(o,t){return"Y"===t?o.clientHeight+c<o.scrollHeight:"X"===t?o.clientWidth+c<o.scrollWidth:void 0}function a(t,l){var e=o.getComputedStyle(t,null)["overflow"+l];return"auto"===e||"scroll"===e}function d(t){var l,e,i,c,n=(s()-t.startTime)/r;c=n=n>1?1:n,l=.5*(1-Math.cos(Math.PI*c)),e=t.startX+(t.x-t.startX)*l,i=t.startY+(t.y-t.startY)*l,t.method.call(t.scrollable,e,i),e===t.x&&i===t.y||o.requestAnimationFrame(d.bind(o,t))}function h(l,e,r){var c,f,p,a,h=s();l===t.body?(c=o,f=o.scrollX||o.pageXOffset,p=o.scrollY||o.pageYOffset,a=i.scroll):(c=l,f=l.scrollLeft,p=l.scrollTop,a=n),d({scrollable:c,method:a,startTime:h,startX:f,startY:p,x:e,y:r})}}"object"==typeof exports&&"undefined"!=typeof module?module.exports={polyfill:o}:o()}();
//---------------------------------------------------------------------
//
// QR Code Generator for JavaScript
//
// Copyright (c) 2009 Kazuhiko Arase
//
// URL: http://www.d-project.com/
//
// Licensed under the MIT license:
//  http://www.opensource.org/licenses/mit-license.php
//
// The word 'QR Code' is registered trademark of
// DENSO WAVE INCORPORATED
//  http://www.denso-wave.com/qrcode/faqpatent-e.html
//
//---------------------------------------------------------------------

var qrcode = function() {

  //---------------------------------------------------------------------
  // qrcode
  //---------------------------------------------------------------------

  /**
   * qrcode
   * @param typeNumber 1 to 40
   * @param errorCorrectionLevel 'L','M','Q','H'
   */
  var qrcode = function(typeNumber, errorCorrectionLevel) {

    var PAD0 = 0xEC;
    var PAD1 = 0x11;

    var _typeNumber = typeNumber;
    var _errorCorrectionLevel = QRErrorCorrectionLevel[errorCorrectionLevel];
    var _modules = null;
    var _moduleCount = 0;
    var _dataCache = null;
    var _dataList = [];

    var _this = {};

    var makeImpl = function(test, maskPattern) {

      _moduleCount = _typeNumber * 4 + 17;
      _modules = function(moduleCount) {
        var modules = new Array(moduleCount);
        for (var row = 0; row < moduleCount; row += 1) {
          modules[row] = new Array(moduleCount);
          for (var col = 0; col < moduleCount; col += 1) {
            modules[row][col] = null;
          }
        }
        return modules;
      }(_moduleCount);

      setupPositionProbePattern(0, 0);
      setupPositionProbePattern(_moduleCount - 7, 0);
      setupPositionProbePattern(0, _moduleCount - 7);
      setupPositionAdjustPattern();
      setupTimingPattern();
      setupTypeInfo(test, maskPattern);

      if (_typeNumber >= 7) {
        setupTypeNumber(test);
      }

      if (_dataCache == null) {
        _dataCache = createData(_typeNumber, _errorCorrectionLevel, _dataList);
      }

      mapData(_dataCache, maskPattern);
    };

    var setupPositionProbePattern = function(row, col) {

      for (var r = -1; r <= 7; r += 1) {

        if (row + r <= -1 || _moduleCount <= row + r) continue;

        for (var c = -1; c <= 7; c += 1) {

          if (col + c <= -1 || _moduleCount <= col + c) continue;

          if ( (0 <= r && r <= 6 && (c == 0 || c == 6) )
              || (0 <= c && c <= 6 && (r == 0 || r == 6) )
              || (2 <= r && r <= 4 && 2 <= c && c <= 4) ) {
            _modules[row + r][col + c] = true;
          } else {
            _modules[row + r][col + c] = false;
          }
        }
      }
    };

    var getBestMaskPattern = function() {

      var minLostPoint = 0;
      var pattern = 0;

      for (var i = 0; i < 8; i += 1) {

        makeImpl(true, i);

        var lostPoint = QRUtil.getLostPoint(_this);

        if (i == 0 || minLostPoint > lostPoint) {
          minLostPoint = lostPoint;
          pattern = i;
        }
      }

      return pattern;
    };

    var setupTimingPattern = function() {

      for (var r = 8; r < _moduleCount - 8; r += 1) {
        if (_modules[r][6] != null) {
          continue;
        }
        _modules[r][6] = (r % 2 == 0);
      }

      for (var c = 8; c < _moduleCount - 8; c += 1) {
        if (_modules[6][c] != null) {
          continue;
        }
        _modules[6][c] = (c % 2 == 0);
      }
    };

    var setupPositionAdjustPattern = function() {

      var pos = QRUtil.getPatternPosition(_typeNumber);

      for (var i = 0; i < pos.length; i += 1) {

        for (var j = 0; j < pos.length; j += 1) {

          var row = pos[i];
          var col = pos[j];

          if (_modules[row][col] != null) {
            continue;
          }

          for (var r = -2; r <= 2; r += 1) {

            for (var c = -2; c <= 2; c += 1) {

              if (r == -2 || r == 2 || c == -2 || c == 2
                  || (r == 0 && c == 0) ) {
                _modules[row + r][col + c] = true;
              } else {
                _modules[row + r][col + c] = false;
              }
            }
          }
        }
      }
    };

    var setupTypeNumber = function(test) {

      var bits = QRUtil.getBCHTypeNumber(_typeNumber);

      for (var i = 0; i < 18; i += 1) {
        var mod = (!test && ( (bits >> i) & 1) == 1);
        _modules[Math.floor(i / 3)][i % 3 + _moduleCount - 8 - 3] = mod;
      }

      for (var i = 0; i < 18; i += 1) {
        var mod = (!test && ( (bits >> i) & 1) == 1);
        _modules[i % 3 + _moduleCount - 8 - 3][Math.floor(i / 3)] = mod;
      }
    };

    var setupTypeInfo = function(test, maskPattern) {

      var data = (_errorCorrectionLevel << 3) | maskPattern;
      var bits = QRUtil.getBCHTypeInfo(data);

      // vertical
      for (var i = 0; i < 15; i += 1) {

        var mod = (!test && ( (bits >> i) & 1) == 1);

        if (i < 6) {
          _modules[i][8] = mod;
        } else if (i < 8) {
          _modules[i + 1][8] = mod;
        } else {
          _modules[_moduleCount - 15 + i][8] = mod;
        }
      }

      // horizontal
      for (var i = 0; i < 15; i += 1) {

        var mod = (!test && ( (bits >> i) & 1) == 1);

        if (i < 8) {
          _modules[8][_moduleCount - i - 1] = mod;
        } else if (i < 9) {
          _modules[8][15 - i - 1 + 1] = mod;
        } else {
          _modules[8][15 - i - 1] = mod;
        }
      }

      // fixed module
      _modules[_moduleCount - 8][8] = (!test);
    };

    var mapData = function(data, maskPattern) {

      var inc = -1;
      var row = _moduleCount - 1;
      var bitIndex = 7;
      var byteIndex = 0;
      var maskFunc = QRUtil.getMaskFunction(maskPattern);

      for (var col = _moduleCount - 1; col > 0; col -= 2) {

        if (col == 6) col -= 1;

        while (true) {

          for (var c = 0; c < 2; c += 1) {

            if (_modules[row][col - c] == null) {

              var dark = false;

              if (byteIndex < data.length) {
                dark = ( ( (data[byteIndex] >>> bitIndex) & 1) == 1);
              }

              var mask = maskFunc(row, col - c);

              if (mask) {
                dark = !dark;
              }

              _modules[row][col - c] = dark;
              bitIndex -= 1;

              if (bitIndex == -1) {
                byteIndex += 1;
                bitIndex = 7;
              }
            }
          }

          row += inc;

          if (row < 0 || _moduleCount <= row) {
            row -= inc;
            inc = -inc;
            break;
          }
        }
      }
    };

    var createBytes = function(buffer, rsBlocks) {

      var offset = 0;

      var maxDcCount = 0;
      var maxEcCount = 0;

      var dcdata = new Array(rsBlocks.length);
      var ecdata = new Array(rsBlocks.length);

      for (var r = 0; r < rsBlocks.length; r += 1) {

        var dcCount = rsBlocks[r].dataCount;
        var ecCount = rsBlocks[r].totalCount - dcCount;

        maxDcCount = Math.max(maxDcCount, dcCount);
        maxEcCount = Math.max(maxEcCount, ecCount);

        dcdata[r] = new Array(dcCount);

        for (var i = 0; i < dcdata[r].length; i += 1) {
          dcdata[r][i] = 0xff & buffer.getBuffer()[i + offset];
        }
        offset += dcCount;

        var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount);
        var rawPoly = qrPolynomial(dcdata[r], rsPoly.getLength() - 1);

        var modPoly = rawPoly.mod(rsPoly);
        ecdata[r] = new Array(rsPoly.getLength() - 1);
        for (var i = 0; i < ecdata[r].length; i += 1) {
          var modIndex = i + modPoly.getLength() - ecdata[r].length;
          ecdata[r][i] = (modIndex >= 0)? modPoly.getAt(modIndex) : 0;
        }
      }

      var totalCodeCount = 0;
      for (var i = 0; i < rsBlocks.length; i += 1) {
        totalCodeCount += rsBlocks[i].totalCount;
      }

      var data = new Array(totalCodeCount);
      var index = 0;

      for (var i = 0; i < maxDcCount; i += 1) {
        for (var r = 0; r < rsBlocks.length; r += 1) {
          if (i < dcdata[r].length) {
            data[index] = dcdata[r][i];
            index += 1;
          }
        }
      }

      for (var i = 0; i < maxEcCount; i += 1) {
        for (var r = 0; r < rsBlocks.length; r += 1) {
          if (i < ecdata[r].length) {
            data[index] = ecdata[r][i];
            index += 1;
          }
        }
      }

      return data;
    };

    var createData = function(typeNumber, errorCorrectionLevel, dataList) {

      var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectionLevel);

      var buffer = qrBitBuffer();

      for (var i = 0; i < dataList.length; i += 1) {
        var data = dataList[i];
        buffer.put(data.getMode(), 4);
        buffer.put(data.getLength(), QRUtil.getLengthInBits(data.getMode(), typeNumber) );
        data.write(buffer);
      }

      // calc num max data.
      var totalDataCount = 0;
      for (var i = 0; i < rsBlocks.length; i += 1) {
        totalDataCount += rsBlocks[i].dataCount;
      }

      if (buffer.getLengthInBits() > totalDataCount * 8) {
        throw 'code length overflow. ('
          + buffer.getLengthInBits()
          + '>'
          + totalDataCount * 8
          + ')';
      }

      // end code
      if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) {
        buffer.put(0, 4);
      }

      // padding
      while (buffer.getLengthInBits() % 8 != 0) {
        buffer.putBit(false);
      }

      // padding
      while (true) {

        if (buffer.getLengthInBits() >= totalDataCount * 8) {
          break;
        }
        buffer.put(PAD0, 8);

        if (buffer.getLengthInBits() >= totalDataCount * 8) {
          break;
        }
        buffer.put(PAD1, 8);
      }

      return createBytes(buffer, rsBlocks);
    };

    _this.addData = function(data, mode) {

      mode = mode || 'Byte';

      var newData = null;

      switch(mode) {
      case 'Numeric' :
        newData = qrNumber(data);
        break;
      case 'Alphanumeric' :
        newData = qrAlphaNum(data);
        break;
      case 'Byte' :
        newData = qr8BitByte(data);
        break;
      case 'Kanji' :
        newData = qrKanji(data);
        break;
      default :
        throw 'mode:' + mode;
      }

      _dataList.push(newData);
      _dataCache = null;
    };

    _this.isDark = function(row, col) {
      if (row < 0 || _moduleCount <= row || col < 0 || _moduleCount <= col) {
        throw row + ',' + col;
      }
      return _modules[row][col];
    };

    _this.getModuleCount = function() {
      return _moduleCount;
    };

    _this.make = function() {
      if (_typeNumber < 1) {
        var typeNumber = 1;

        for (; typeNumber < 40; typeNumber++) {
          var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, _errorCorrectionLevel);
          var buffer = qrBitBuffer();

          for (var i = 0; i < _dataList.length; i++) {
            var data = _dataList[i];
            buffer.put(data.getMode(), 4);
            buffer.put(data.getLength(), QRUtil.getLengthInBits(data.getMode(), typeNumber) );
            data.write(buffer);
          }

          var totalDataCount = 0;
          for (var i = 0; i < rsBlocks.length; i++) {
            totalDataCount += rsBlocks[i].dataCount;
          }

          if (buffer.getLengthInBits() <= totalDataCount * 8) {
            break;
          }
        }

        _typeNumber = typeNumber;
      }

      makeImpl(false, getBestMaskPattern() );
    };

    _this.createTableTag = function(cellSize, margin) {

      cellSize = cellSize || 2;
      margin = (typeof margin == 'undefined')? cellSize * 4 : margin;

      var qrHtml = '';

      qrHtml += '<table style="';
      qrHtml += ' border-width: 0px; border-style: none;';
      qrHtml += ' border-collapse: collapse;';
      qrHtml += ' padding: 0px; margin: ' + margin + 'px;';
      qrHtml += '">';
      qrHtml += '<tbody>';

      for (var r = 0; r < _this.getModuleCount(); r += 1) {

        qrHtml += '<tr>';

        for (var c = 0; c < _this.getModuleCount(); c += 1) {
          qrHtml += '<td style="';
          qrHtml += ' border-width: 0px; border-style: none;';
          qrHtml += ' border-collapse: collapse;';
          qrHtml += ' padding: 0px; margin: 0px;';
          qrHtml += ' width: ' + cellSize + 'px;';
          qrHtml += ' height: ' + cellSize + 'px;';
          qrHtml += ' background-color: ';
          qrHtml += _this.isDark(r, c)? '#000000' : '#ffffff';
          qrHtml += ';';
          qrHtml += '"/>';
        }

        qrHtml += '</tr>';
      }

      qrHtml += '</tbody>';
      qrHtml += '</table>';

      return qrHtml;
    };

    _this.createSvgTag = function(cellSize, margin, alt, title) {

      var opts = {};
      if (typeof arguments[0] == 'object') {
        // Called by options.
        opts = arguments[0];
        // overwrite cellSize and margin.
        cellSize = opts.cellSize;
        margin = opts.margin;
        alt = opts.alt;
        title = opts.title;
      }

      cellSize = cellSize || 2;
      margin = (typeof margin == 'undefined')? cellSize * 4 : margin;

      // Compose alt property surrogate
      alt = (typeof alt === 'string') ? {text: alt} : alt || {};
      alt.text = alt.text || null;
      alt.id = (alt.text) ? alt.id || 'qrcode-description' : null;

      // Compose title property surrogate
      title = (typeof title === 'string') ? {text: title} : title || {};
      title.text = title.text || null;
      title.id = (title.text) ? title.id || 'qrcode-title' : null;

      var size = _this.getModuleCount() * cellSize + margin * 2;
      var c, mc, r, mr, qrSvg='', rect;

      rect = 'l' + cellSize + ',0 0,' + cellSize +
        ' -' + cellSize + ',0 0,-' + cellSize + 'z ';

      qrSvg += '<svg version="1.1" xmlns="http://www.w3.org/2000/svg"';
      qrSvg += !opts.scalable ? ' width="' + size + 'px" height="' + size + 'px"' : '';
      qrSvg += ' viewBox="0 0 ' + size + ' ' + size + '" ';
      qrSvg += ' preserveAspectRatio="xMinYMin meet"';
      qrSvg += (title.text || alt.text) ? ' role="img" aria-labelledby="' +
          escapeXml([title.id, alt.id].join(' ').trim() ) + '"' : '';
      qrSvg += '>';
      qrSvg += (title.text) ? '<title id="' + escapeXml(title.id) + '">' +
          escapeXml(title.text) + '</title>' : '';
      qrSvg += (alt.text) ? '<description id="' + escapeXml(alt.id) + '">' +
          escapeXml(alt.text) + '</description>' : '';
      qrSvg += '<rect width="100%" height="100%" fill="white" cx="0" cy="0"/>';
      qrSvg += '<path d="';

      for (r = 0; r < _this.getModuleCount(); r += 1) {
        mr = r * cellSize + margin;
        for (c = 0; c < _this.getModuleCount(); c += 1) {
          if (_this.isDark(r, c) ) {
            mc = c*cellSize+margin;
            qrSvg += 'M' + mc + ',' + mr + rect;
          }
        }
      }

      qrSvg += '" stroke="transparent" fill="black"/>';
      qrSvg += '</svg>';

      return qrSvg;
    };

    _this.createDataURL = function(cellSize, margin) {

      cellSize = cellSize || 2;
      margin = (typeof margin == 'undefined')? cellSize * 4 : margin;

      var size = _this.getModuleCount() * cellSize + margin * 2;
      var min = margin;
      var max = size - margin;

      return createDataURL(size, size, function(x, y) {
        if (min <= x && x < max && min <= y && y < max) {
          var c = Math.floor( (x - min) / cellSize);
          var r = Math.floor( (y - min) / cellSize);
          return _this.isDark(r, c)? 0 : 1;
        } else {
          return 1;
        }
      } );
    };

    _this.createImgTag = function(cellSize, margin, alt) {

      cellSize = cellSize || 2;
      margin = (typeof margin == 'undefined')? cellSize * 4 : margin;

      var size = _this.getModuleCount() * cellSize + margin * 2;

      var img = '';
      img += '<img';
      img += '\u0020src="';
      img += _this.createDataURL(cellSize, margin);
      img += '"';
      img += '\u0020width="';
      img += size;
      img += '"';
      img += '\u0020height="';
      img += size;
      img += '"';
      if (alt) {
        img += '\u0020alt="';
        img += escapeXml(alt);
        img += '"';
      }
      img += '/>';

      return img;
    };

    var escapeXml = function(s) {
      var escaped = '';
      for (var i = 0; i < s.length; i += 1) {
        var c = s.charAt(i);
        switch(c) {
        case '<': escaped += '&lt;'; break;
        case '>': escaped += '&gt;'; break;
        case '&': escaped += '&amp;'; break;
        case '"': escaped += '&quot;'; break;
        default : escaped += c; break;
        }
      }
      return escaped;
    };

    var _createHalfASCII = function(margin) {
      var cellSize = 1;
      margin = (typeof margin == 'undefined')? cellSize * 2 : margin;

      var size = _this.getModuleCount() * cellSize + margin * 2;
      var min = margin;
      var max = size - margin;

      var y, x, r1, r2, p;

      var blocks = {
        '██': '█',
        '█ ': '▀',
        ' █': '▄',
        '  ': ' '
      };

      var blocksLastLineNoMargin = {
        '██': '▀',
        '█ ': '▀',
        ' █': ' ',
        '  ': ' '
      };

      var ascii = '';
      for (y = 0; y < size; y += 2) {
        r1 = Math.floor((y - min) / cellSize);
        r2 = Math.floor((y + 1 - min) / cellSize);
        for (x = 0; x < size; x += 1) {
          p = '█';

          if (min <= x && x < max && min <= y && y < max && _this.isDark(r1, Math.floor((x - min) / cellSize))) {
            p = ' ';
          }

          if (min <= x && x < max && min <= y+1 && y+1 < max && _this.isDark(r2, Math.floor((x - min) / cellSize))) {
            p += ' ';
          }
          else {
            p += '█';
          }

          // Output 2 characters per pixel, to create full square. 1 character per pixels gives only half width of square.
          ascii += (margin < 1 && y+1 >= max) ? blocksLastLineNoMargin[p] : blocks[p];
        }

        ascii += '\n';
      }

      if (size % 2 && margin > 0) {
        return ascii.substring(0, ascii.length - size - 1) + Array(size+1).join('▀');
      }

      return ascii.substring(0, ascii.length-1);
    };

    _this.createASCII = function(cellSize, margin) {
      cellSize = cellSize || 1;

      if (cellSize < 2) {
        return _createHalfASCII(margin);
      }

      cellSize -= 1;
      margin = (typeof margin == 'undefined')? cellSize * 2 : margin;

      var size = _this.getModuleCount() * cellSize + margin * 2;
      var min = margin;
      var max = size - margin;

      var y, x, r, p;

      var white = Array(cellSize+1).join('██');
      var black = Array(cellSize+1).join('  ');

      var ascii = '';
      var line = '';
      for (y = 0; y < size; y += 1) {
        r = Math.floor( (y - min) / cellSize);
        line = '';
        for (x = 0; x < size; x += 1) {
          p = 1;

          if (min <= x && x < max && min <= y && y < max && _this.isDark(r, Math.floor((x - min) / cellSize))) {
            p = 0;
          }

          // Output 2 characters per pixel, to create full square. 1 character per pixels gives only half width of square.
          line += p ? white : black;
        }

        for (r = 0; r < cellSize; r += 1) {
          ascii += line + '\n';
        }
      }

      return ascii.substring(0, ascii.length-1);
    };

    _this.renderTo2dContext = function(context, cellSize) {
      cellSize = cellSize || 2;
      var length = _this.getModuleCount();
      for (var row = 0; row < length; row++) {
        for (var col = 0; col < length; col++) {
          context.fillStyle = _this.isDark(row, col) ? 'black' : 'white';
          context.fillRect(row * cellSize, col * cellSize, cellSize, cellSize);
        }
      }
    }

    return _this;
  };

  //---------------------------------------------------------------------
  // qrcode.stringToBytes
  //---------------------------------------------------------------------

  qrcode.stringToBytesFuncs = {
    'default' : function(s) {
      var bytes = [];
      for (var i = 0; i < s.length; i += 1) {
        var c = s.charCodeAt(i);
        bytes.push(c & 0xff);
      }
      return bytes;
    }
  };

  qrcode.stringToBytes = qrcode.stringToBytesFuncs['default'];

  //---------------------------------------------------------------------
  // qrcode.createStringToBytes
  //---------------------------------------------------------------------

  /**
   * @param unicodeData base64 string of byte array.
   * [16bit Unicode],[16bit Bytes], ...
   * @param numChars
   */
  qrcode.createStringToBytes = function(unicodeData, numChars) {

    // create conversion map.

    var unicodeMap = function() {

      var bin = base64DecodeInputStream(unicodeData);
      var read = function() {
        var b = bin.read();
        if (b == -1) throw 'eof';
        return b;
      };

      var count = 0;
      var unicodeMap = {};
      while (true) {
        var b0 = bin.read();
        if (b0 == -1) break;
        var b1 = read();
        var b2 = read();
        var b3 = read();
        var k = String.fromCharCode( (b0 << 8) | b1);
        var v = (b2 << 8) | b3;
        unicodeMap[k] = v;
        count += 1;
      }
      if (count != numChars) {
        throw count + ' != ' + numChars;
      }

      return unicodeMap;
    }();

    var unknownChar = '?'.charCodeAt(0);

    return function(s) {
      var bytes = [];
      for (var i = 0; i < s.length; i += 1) {
        var c = s.charCodeAt(i);
        if (c < 128) {
          bytes.push(c);
        } else {
          var b = unicodeMap[s.charAt(i)];
          if (typeof b == 'number') {
            if ( (b & 0xff) == b) {
              // 1byte
              bytes.push(b);
            } else {
              // 2bytes
              bytes.push(b >>> 8);
              bytes.push(b & 0xff);
            }
          } else {
            bytes.push(unknownChar);
          }
        }
      }
      return bytes;
    };
  };

  //---------------------------------------------------------------------
  // QRMode
  //---------------------------------------------------------------------

  var QRMode = {
    MODE_NUMBER :    1 << 0,
    MODE_ALPHA_NUM : 1 << 1,
    MODE_8BIT_BYTE : 1 << 2,
    MODE_KANJI :     1 << 3
  };

  //---------------------------------------------------------------------
  // QRErrorCorrectionLevel
  //---------------------------------------------------------------------

  var QRErrorCorrectionLevel = {
    L : 1,
    M : 0,
    Q : 3,
    H : 2
  };

  //---------------------------------------------------------------------
  // QRMaskPattern
  //---------------------------------------------------------------------

  var QRMaskPattern = {
    PATTERN000 : 0,
    PATTERN001 : 1,
    PATTERN010 : 2,
    PATTERN011 : 3,
    PATTERN100 : 4,
    PATTERN101 : 5,
    PATTERN110 : 6,
    PATTERN111 : 7
  };

  //---------------------------------------------------------------------
  // QRUtil
  //---------------------------------------------------------------------

  var QRUtil = function() {

    var PATTERN_POSITION_TABLE = [
      [],
      [6, 18],
      [6, 22],
      [6, 26],
      [6, 30],
      [6, 34],
      [6, 22, 38],
      [6, 24, 42],
      [6, 26, 46],
      [6, 28, 50],
      [6, 30, 54],
      [6, 32, 58],
      [6, 34, 62],
      [6, 26, 46, 66],
      [6, 26, 48, 70],
      [6, 26, 50, 74],
      [6, 30, 54, 78],
      [6, 30, 56, 82],
      [6, 30, 58, 86],
      [6, 34, 62, 90],
      [6, 28, 50, 72, 94],
      [6, 26, 50, 74, 98],
      [6, 30, 54, 78, 102],
      [6, 28, 54, 80, 106],
      [6, 32, 58, 84, 110],
      [6, 30, 58, 86, 114],
      [6, 34, 62, 90, 118],
      [6, 26, 50, 74, 98, 122],
      [6, 30, 54, 78, 102, 126],
      [6, 26, 52, 78, 104, 130],
      [6, 30, 56, 82, 108, 134],
      [6, 34, 60, 86, 112, 138],
      [6, 30, 58, 86, 114, 142],
      [6, 34, 62, 90, 118, 146],
      [6, 30, 54, 78, 102, 126, 150],
      [6, 24, 50, 76, 102, 128, 154],
      [6, 28, 54, 80, 106, 132, 158],
      [6, 32, 58, 84, 110, 136, 162],
      [6, 26, 54, 82, 110, 138, 166],
      [6, 30, 58, 86, 114, 142, 170]
    ];
    var G15 = (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0);
    var G18 = (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0);
    var G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1);

    var _this = {};

    var getBCHDigit = function(data) {
      var digit = 0;
      while (data != 0) {
        digit += 1;
        data >>>= 1;
      }
      return digit;
    };

    _this.getBCHTypeInfo = function(data) {
      var d = data << 10;
      while (getBCHDigit(d) - getBCHDigit(G15) >= 0) {
        d ^= (G15 << (getBCHDigit(d) - getBCHDigit(G15) ) );
      }
      return ( (data << 10) | d) ^ G15_MASK;
    };

    _this.getBCHTypeNumber = function(data) {
      var d = data << 12;
      while (getBCHDigit(d) - getBCHDigit(G18) >= 0) {
        d ^= (G18 << (getBCHDigit(d) - getBCHDigit(G18) ) );
      }
      return (data << 12) | d;
    };

    _this.getPatternPosition = function(typeNumber) {
      return PATTERN_POSITION_TABLE[typeNumber - 1];
    };

    _this.getMaskFunction = function(maskPattern) {

      switch (maskPattern) {

      case QRMaskPattern.PATTERN000 :
        return function(i, j) { return (i + j) % 2 == 0; };
      case QRMaskPattern.PATTERN001 :
        return function(i, j) { return i % 2 == 0; };
      case QRMaskPattern.PATTERN010 :
        return function(i, j) { return j % 3 == 0; };
      case QRMaskPattern.PATTERN011 :
        return function(i, j) { return (i + j) % 3 == 0; };
      case QRMaskPattern.PATTERN100 :
        return function(i, j) { return (Math.floor(i / 2) + Math.floor(j / 3) ) % 2 == 0; };
      case QRMaskPattern.PATTERN101 :
        return function(i, j) { return (i * j) % 2 + (i * j) % 3 == 0; };
      case QRMaskPattern.PATTERN110 :
        return function(i, j) { return ( (i * j) % 2 + (i * j) % 3) % 2 == 0; };
      case QRMaskPattern.PATTERN111 :
        return function(i, j) { return ( (i * j) % 3 + (i + j) % 2) % 2 == 0; };

      default :
        throw 'bad maskPattern:' + maskPattern;
      }
    };

    _this.getErrorCorrectPolynomial = function(errorCorrectLength) {
      var a = qrPolynomial([1], 0);
      for (var i = 0; i < errorCorrectLength; i += 1) {
        a = a.multiply(qrPolynomial([1, QRMath.gexp(i)], 0) );
      }
      return a;
    };

    _this.getLengthInBits = function(mode, type) {

      if (1 <= type && type < 10) {

        // 1 - 9

        switch(mode) {
        case QRMode.MODE_NUMBER    : return 10;
        case QRMode.MODE_ALPHA_NUM : return 9;
        case QRMode.MODE_8BIT_BYTE : return 8;
        case QRMode.MODE_KANJI     : return 8;
        default :
          throw 'mode:' + mode;
        }

      } else if (type < 27) {

        // 10 - 26

        switch(mode) {
        case QRMode.MODE_NUMBER    : return 12;
        case QRMode.MODE_ALPHA_NUM : return 11;
        case QRMode.MODE_8BIT_BYTE : return 16;
        case QRMode.MODE_KANJI     : return 10;
        default :
          throw 'mode:' + mode;
        }

      } else if (type < 41) {

        // 27 - 40

        switch(mode) {
        case QRMode.MODE_NUMBER    : return 14;
        case QRMode.MODE_ALPHA_NUM : return 13;
        case QRMode.MODE_8BIT_BYTE : return 16;
        case QRMode.MODE_KANJI     : return 12;
        default :
          throw 'mode:' + mode;
        }

      } else {
        throw 'type:' + type;
      }
    };

    _this.getLostPoint = function(qrcode) {

      var moduleCount = qrcode.getModuleCount();

      var lostPoint = 0;

      // LEVEL1

      for (var row = 0; row < moduleCount; row += 1) {
        for (var col = 0; col < moduleCount; col += 1) {

          var sameCount = 0;
          var dark = qrcode.isDark(row, col);

          for (var r = -1; r <= 1; r += 1) {

            if (row + r < 0 || moduleCount <= row + r) {
              continue;
            }

            for (var c = -1; c <= 1; c += 1) {

              if (col + c < 0 || moduleCount <= col + c) {
                continue;
              }

              if (r == 0 && c == 0) {
                continue;
              }

              if (dark == qrcode.isDark(row + r, col + c) ) {
                sameCount += 1;
              }
            }
          }

          if (sameCount > 5) {
            lostPoint += (3 + sameCount - 5);
          }
        }
      };

      // LEVEL2

      for (var row = 0; row < moduleCount - 1; row += 1) {
        for (var col = 0; col < moduleCount - 1; col += 1) {
          var count = 0;
          if (qrcode.isDark(row, col) ) count += 1;
          if (qrcode.isDark(row + 1, col) ) count += 1;
          if (qrcode.isDark(row, col + 1) ) count += 1;
          if (qrcode.isDark(row + 1, col + 1) ) count += 1;
          if (count == 0 || count == 4) {
            lostPoint += 3;
          }
        }
      }

      // LEVEL3

      for (var row = 0; row < moduleCount; row += 1) {
        for (var col = 0; col < moduleCount - 6; col += 1) {
          if (qrcode.isDark(row, col)
              && !qrcode.isDark(row, col + 1)
              &&  qrcode.isDark(row, col + 2)
              &&  qrcode.isDark(row, col + 3)
              &&  qrcode.isDark(row, col + 4)
              && !qrcode.isDark(row, col + 5)
              &&  qrcode.isDark(row, col + 6) ) {
            lostPoint += 40;
          }
        }
      }

      for (var col = 0; col < moduleCount; col += 1) {
        for (var row = 0; row < moduleCount - 6; row += 1) {
          if (qrcode.isDark(row, col)
              && !qrcode.isDark(row + 1, col)
              &&  qrcode.isDark(row + 2, col)
              &&  qrcode.isDark(row + 3, col)
              &&  qrcode.isDark(row + 4, col)
              && !qrcode.isDark(row + 5, col)
              &&  qrcode.isDark(row + 6, col) ) {
            lostPoint += 40;
          }
        }
      }

      // LEVEL4

      var darkCount = 0;

      for (var col = 0; col < moduleCount; col += 1) {
        for (var row = 0; row < moduleCount; row += 1) {
          if (qrcode.isDark(row, col) ) {
            darkCount += 1;
          }
        }
      }

      var ratio = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5;
      lostPoint += ratio * 10;

      return lostPoint;
    };

    return _this;
  }();

  //---------------------------------------------------------------------
  // QRMath
  //---------------------------------------------------------------------

  var QRMath = function() {

    var EXP_TABLE = new Array(256);
    var LOG_TABLE = new Array(256);

    // initialize tables
    for (var i = 0; i < 8; i += 1) {
      EXP_TABLE[i] = 1 << i;
    }
    for (var i = 8; i < 256; i += 1) {
      EXP_TABLE[i] = EXP_TABLE[i - 4]
        ^ EXP_TABLE[i - 5]
        ^ EXP_TABLE[i - 6]
        ^ EXP_TABLE[i - 8];
    }
    for (var i = 0; i < 255; i += 1) {
      LOG_TABLE[EXP_TABLE[i] ] = i;
    }

    var _this = {};

    _this.glog = function(n) {

      if (n < 1) {
        throw 'glog(' + n + ')';
      }

      return LOG_TABLE[n];
    };

    _this.gexp = function(n) {

      while (n < 0) {
        n += 255;
      }

      while (n >= 256) {
        n -= 255;
      }

      return EXP_TABLE[n];
    };

    return _this;
  }();

  //---------------------------------------------------------------------
  // qrPolynomial
  //---------------------------------------------------------------------

  function qrPolynomial(num, shift) {

    if (typeof num.length == 'undefined') {
      throw num.length + '/' + shift;
    }

    var _num = function() {
      var offset = 0;
      while (offset < num.length && num[offset] == 0) {
        offset += 1;
      }
      var _num = new Array(num.length - offset + shift);
      for (var i = 0; i < num.length - offset; i += 1) {
        _num[i] = num[i + offset];
      }
      return _num;
    }();

    var _this = {};

    _this.getAt = function(index) {
      return _num[index];
    };

    _this.getLength = function() {
      return _num.length;
    };

    _this.multiply = function(e) {

      var num = new Array(_this.getLength() + e.getLength() - 1);

      for (var i = 0; i < _this.getLength(); i += 1) {
        for (var j = 0; j < e.getLength(); j += 1) {
          num[i + j] ^= QRMath.gexp(QRMath.glog(_this.getAt(i) ) + QRMath.glog(e.getAt(j) ) );
        }
      }

      return qrPolynomial(num, 0);
    };

    _this.mod = function(e) {

      if (_this.getLength() - e.getLength() < 0) {
        return _this;
      }

      var ratio = QRMath.glog(_this.getAt(0) ) - QRMath.glog(e.getAt(0) );

      var num = new Array(_this.getLength() );
      for (var i = 0; i < _this.getLength(); i += 1) {
        num[i] = _this.getAt(i);
      }

      for (var i = 0; i < e.getLength(); i += 1) {
        num[i] ^= QRMath.gexp(QRMath.glog(e.getAt(i) ) + ratio);
      }

      // recursive call
      return qrPolynomial(num, 0).mod(e);
    };

    return _this;
  };

  //---------------------------------------------------------------------
  // QRRSBlock
  //---------------------------------------------------------------------

  var QRRSBlock = function() {

    var RS_BLOCK_TABLE = [

      // L
      // M
      // Q
      // H

      // 1
      [1, 26, 19],
      [1, 26, 16],
      [1, 26, 13],
      [1, 26, 9],

      // 2
      [1, 44, 34],
      [1, 44, 28],
      [1, 44, 22],
      [1, 44, 16],

      // 3
      [1, 70, 55],
      [1, 70, 44],
      [2, 35, 17],
      [2, 35, 13],

      // 4
      [1, 100, 80],
      [2, 50, 32],
      [2, 50, 24],
      [4, 25, 9],

      // 5
      [1, 134, 108],
      [2, 67, 43],
      [2, 33, 15, 2, 34, 16],
      [2, 33, 11, 2, 34, 12],

      // 6
      [2, 86, 68],
      [4, 43, 27],
      [4, 43, 19],
      [4, 43, 15],

      // 7
      [2, 98, 78],
      [4, 49, 31],
      [2, 32, 14, 4, 33, 15],
      [4, 39, 13, 1, 40, 14],

      // 8
      [2, 121, 97],
      [2, 60, 38, 2, 61, 39],
      [4, 40, 18, 2, 41, 19],
      [4, 40, 14, 2, 41, 15],

      // 9
      [2, 146, 116],
      [3, 58, 36, 2, 59, 37],
      [4, 36, 16, 4, 37, 17],
      [4, 36, 12, 4, 37, 13],

      // 10
      [2, 86, 68, 2, 87, 69],
      [4, 69, 43, 1, 70, 44],
      [6, 43, 19, 2, 44, 20],
      [6, 43, 15, 2, 44, 16],

      // 11
      [4, 101, 81],
      [1, 80, 50, 4, 81, 51],
      [4, 50, 22, 4, 51, 23],
      [3, 36, 12, 8, 37, 13],

      // 12
      [2, 116, 92, 2, 117, 93],
      [6, 58, 36, 2, 59, 37],
      [4, 46, 20, 6, 47, 21],
      [7, 42, 14, 4, 43, 15],

      // 13
      [4, 133, 107],
      [8, 59, 37, 1, 60, 38],
      [8, 44, 20, 4, 45, 21],
      [12, 33, 11, 4, 34, 12],

      // 14
      [3, 145, 115, 1, 146, 116],
      [4, 64, 40, 5, 65, 41],
      [11, 36, 16, 5, 37, 17],
      [11, 36, 12, 5, 37, 13],

      // 15
      [5, 109, 87, 1, 110, 88],
      [5, 65, 41, 5, 66, 42],
      [5, 54, 24, 7, 55, 25],
      [11, 36, 12, 7, 37, 13],

      // 16
      [5, 122, 98, 1, 123, 99],
      [7, 73, 45, 3, 74, 46],
      [15, 43, 19, 2, 44, 20],
      [3, 45, 15, 13, 46, 16],

      // 17
      [1, 135, 107, 5, 136, 108],
      [10, 74, 46, 1, 75, 47],
      [1, 50, 22, 15, 51, 23],
      [2, 42, 14, 17, 43, 15],

      // 18
      [5, 150, 120, 1, 151, 121],
      [9, 69, 43, 4, 70, 44],
      [17, 50, 22, 1, 51, 23],
      [2, 42, 14, 19, 43, 15],

      // 19
      [3, 141, 113, 4, 142, 114],
      [3, 70, 44, 11, 71, 45],
      [17, 47, 21, 4, 48, 22],
      [9, 39, 13, 16, 40, 14],

      // 20
      [3, 135, 107, 5, 136, 108],
      [3, 67, 41, 13, 68, 42],
      [15, 54, 24, 5, 55, 25],
      [15, 43, 15, 10, 44, 16],

      // 21
      [4, 144, 116, 4, 145, 117],
      [17, 68, 42],
      [17, 50, 22, 6, 51, 23],
      [19, 46, 16, 6, 47, 17],

      // 22
      [2, 139, 111, 7, 140, 112],
      [17, 74, 46],
      [7, 54, 24, 16, 55, 25],
      [34, 37, 13],

      // 23
      [4, 151, 121, 5, 152, 122],
      [4, 75, 47, 14, 76, 48],
      [11, 54, 24, 14, 55, 25],
      [16, 45, 15, 14, 46, 16],

      // 24
      [6, 147, 117, 4, 148, 118],
      [6, 73, 45, 14, 74, 46],
      [11, 54, 24, 16, 55, 25],
      [30, 46, 16, 2, 47, 17],

      // 25
      [8, 132, 106, 4, 133, 107],
      [8, 75, 47, 13, 76, 48],
      [7, 54, 24, 22, 55, 25],
      [22, 45, 15, 13, 46, 16],

      // 26
      [10, 142, 114, 2, 143, 115],
      [19, 74, 46, 4, 75, 47],
      [28, 50, 22, 6, 51, 23],
      [33, 46, 16, 4, 47, 17],

      // 27
      [8, 152, 122, 4, 153, 123],
      [22, 73, 45, 3, 74, 46],
      [8, 53, 23, 26, 54, 24],
      [12, 45, 15, 28, 46, 16],

      // 28
      [3, 147, 117, 10, 148, 118],
      [3, 73, 45, 23, 74, 46],
      [4, 54, 24, 31, 55, 25],
      [11, 45, 15, 31, 46, 16],

      // 29
      [7, 146, 116, 7, 147, 117],
      [21, 73, 45, 7, 74, 46],
      [1, 53, 23, 37, 54, 24],
      [19, 45, 15, 26, 46, 16],

      // 30
      [5, 145, 115, 10, 146, 116],
      [19, 75, 47, 10, 76, 48],
      [15, 54, 24, 25, 55, 25],
      [23, 45, 15, 25, 46, 16],

      // 31
      [13, 145, 115, 3, 146, 116],
      [2, 74, 46, 29, 75, 47],
      [42, 54, 24, 1, 55, 25],
      [23, 45, 15, 28, 46, 16],

      // 32
      [17, 145, 115],
      [10, 74, 46, 23, 75, 47],
      [10, 54, 24, 35, 55, 25],
      [19, 45, 15, 35, 46, 16],

      // 33
      [17, 145, 115, 1, 146, 116],
      [14, 74, 46, 21, 75, 47],
      [29, 54, 24, 19, 55, 25],
      [11, 45, 15, 46, 46, 16],

      // 34
      [13, 145, 115, 6, 146, 116],
      [14, 74, 46, 23, 75, 47],
      [44, 54, 24, 7, 55, 25],
      [59, 46, 16, 1, 47, 17],

      // 35
      [12, 151, 121, 7, 152, 122],
      [12, 75, 47, 26, 76, 48],
      [39, 54, 24, 14, 55, 25],
      [22, 45, 15, 41, 46, 16],

      // 36
      [6, 151, 121, 14, 152, 122],
      [6, 75, 47, 34, 76, 48],
      [46, 54, 24, 10, 55, 25],
      [2, 45, 15, 64, 46, 16],

      // 37
      [17, 152, 122, 4, 153, 123],
      [29, 74, 46, 14, 75, 47],
      [49, 54, 24, 10, 55, 25],
      [24, 45, 15, 46, 46, 16],

      // 38
      [4, 152, 122, 18, 153, 123],
      [13, 74, 46, 32, 75, 47],
      [48, 54, 24, 14, 55, 25],
      [42, 45, 15, 32, 46, 16],

      // 39
      [20, 147, 117, 4, 148, 118],
      [40, 75, 47, 7, 76, 48],
      [43, 54, 24, 22, 55, 25],
      [10, 45, 15, 67, 46, 16],

      // 40
      [19, 148, 118, 6, 149, 119],
      [18, 75, 47, 31, 76, 48],
      [34, 54, 24, 34, 55, 25],
      [20, 45, 15, 61, 46, 16]
    ];

    var qrRSBlock = function(totalCount, dataCount) {
      var _this = {};
      _this.totalCount = totalCount;
      _this.dataCount = dataCount;
      return _this;
    };

    var _this = {};

    var getRsBlockTable = function(typeNumber, errorCorrectionLevel) {

      switch(errorCorrectionLevel) {
      case QRErrorCorrectionLevel.L :
        return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0];
      case QRErrorCorrectionLevel.M :
        return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1];
      case QRErrorCorrectionLevel.Q :
        return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2];
      case QRErrorCorrectionLevel.H :
        return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3];
      default :
        return undefined;
      }
    };

    _this.getRSBlocks = function(typeNumber, errorCorrectionLevel) {

      var rsBlock = getRsBlockTable(typeNumber, errorCorrectionLevel);

      if (typeof rsBlock == 'undefined') {
        throw 'bad rs block @ typeNumber:' + typeNumber +
            '/errorCorrectionLevel:' + errorCorrectionLevel;
      }

      var length = rsBlock.length / 3;

      var list = [];

      for (var i = 0; i < length; i += 1) {

        var count = rsBlock[i * 3 + 0];
        var totalCount = rsBlock[i * 3 + 1];
        var dataCount = rsBlock[i * 3 + 2];

        for (var j = 0; j < count; j += 1) {
          list.push(qrRSBlock(totalCount, dataCount) );
        }
      }

      return list;
    };

    return _this;
  }();

  //---------------------------------------------------------------------
  // qrBitBuffer
  //---------------------------------------------------------------------

  var qrBitBuffer = function() {

    var _buffer = [];
    var _length = 0;

    var _this = {};

    _this.getBuffer = function() {
      return _buffer;
    };

    _this.getAt = function(index) {
      var bufIndex = Math.floor(index / 8);
      return ( (_buffer[bufIndex] >>> (7 - index % 8) ) & 1) == 1;
    };

    _this.put = function(num, length) {
      for (var i = 0; i < length; i += 1) {
        _this.putBit( ( (num >>> (length - i - 1) ) & 1) == 1);
      }
    };

    _this.getLengthInBits = function() {
      return _length;
    };

    _this.putBit = function(bit) {

      var bufIndex = Math.floor(_length / 8);
      if (_buffer.length <= bufIndex) {
        _buffer.push(0);
      }

      if (bit) {
        _buffer[bufIndex] |= (0x80 >>> (_length % 8) );
      }

      _length += 1;
    };

    return _this;
  };

  //---------------------------------------------------------------------
  // qrNumber
  //---------------------------------------------------------------------

  var qrNumber = function(data) {

    var _mode = QRMode.MODE_NUMBER;
    var _data = data;

    var _this = {};

    _this.getMode = function() {
      return _mode;
    };

    _this.getLength = function(buffer) {
      return _data.length;
    };

    _this.write = function(buffer) {

      var data = _data;

      var i = 0;

      while (i + 2 < data.length) {
        buffer.put(strToNum(data.substring(i, i + 3) ), 10);
        i += 3;
      }

      if (i < data.length) {
        if (data.length - i == 1) {
          buffer.put(strToNum(data.substring(i, i + 1) ), 4);
        } else if (data.length - i == 2) {
          buffer.put(strToNum(data.substring(i, i + 2) ), 7);
        }
      }
    };

    var strToNum = function(s) {
      var num = 0;
      for (var i = 0; i < s.length; i += 1) {
        num = num * 10 + chatToNum(s.charAt(i) );
      }
      return num;
    };

    var chatToNum = function(c) {
      if ('0' <= c && c <= '9') {
        return c.charCodeAt(0) - '0'.charCodeAt(0);
      }
      throw 'illegal char :' + c;
    };

    return _this;
  };

  //---------------------------------------------------------------------
  // qrAlphaNum
  //---------------------------------------------------------------------

  var qrAlphaNum = function(data) {

    var _mode = QRMode.MODE_ALPHA_NUM;
    var _data = data;

    var _this = {};

    _this.getMode = function() {
      return _mode;
    };

    _this.getLength = function(buffer) {
      return _data.length;
    };

    _this.write = function(buffer) {

      var s = _data;

      var i = 0;

      while (i + 1 < s.length) {
        buffer.put(
          getCode(s.charAt(i) ) * 45 +
          getCode(s.charAt(i + 1) ), 11);
        i += 2;
      }

      if (i < s.length) {
        buffer.put(getCode(s.charAt(i) ), 6);
      }
    };

    var getCode = function(c) {

      if ('0' <= c && c <= '9') {
        return c.charCodeAt(0) - '0'.charCodeAt(0);
      } else if ('A' <= c && c <= 'Z') {
        return c.charCodeAt(0) - 'A'.charCodeAt(0) + 10;
      } else {
        switch (c) {
        case ' ' : return 36;
        case '$' : return 37;
        case '%' : return 38;
        case '*' : return 39;
        case '+' : return 40;
        case '-' : return 41;
        case '.' : return 42;
        case '/' : return 43;
        case ':' : return 44;
        default :
          throw 'illegal char :' + c;
        }
      }
    };

    return _this;
  };

  //---------------------------------------------------------------------
  // qr8BitByte
  //---------------------------------------------------------------------

  var qr8BitByte = function(data) {

    var _mode = QRMode.MODE_8BIT_BYTE;
    var _data = data;
    var _bytes = qrcode.stringToBytes(data);

    var _this = {};

    _this.getMode = function() {
      return _mode;
    };

    _this.getLength = function(buffer) {
      return _bytes.length;
    };

    _this.write = function(buffer) {
      for (var i = 0; i < _bytes.length; i += 1) {
        buffer.put(_bytes[i], 8);
      }
    };

    return _this;
  };

  //---------------------------------------------------------------------
  // qrKanji
  //---------------------------------------------------------------------

  var qrKanji = function(data) {

    var _mode = QRMode.MODE_KANJI;
    var _data = data;

    var stringToBytes = qrcode.stringToBytesFuncs['SJIS'];
    if (!stringToBytes) {
      throw 'sjis not supported.';
    }
    !function(c, code) {
      // self test for sjis support.
      var test = stringToBytes(c);
      if (test.length != 2 || ( (test[0] << 8) | test[1]) != code) {
        throw 'sjis not supported.';
      }
    }('\u53cb', 0x9746);

    var _bytes = stringToBytes(data);

    var _this = {};

    _this.getMode = function() {
      return _mode;
    };

    _this.getLength = function(buffer) {
      return ~~(_bytes.length / 2);
    };

    _this.write = function(buffer) {

      var data = _bytes;

      var i = 0;

      while (i + 1 < data.length) {

        var c = ( (0xff & data[i]) << 8) | (0xff & data[i + 1]);

        if (0x8140 <= c && c <= 0x9FFC) {
          c -= 0x8140;
        } else if (0xE040 <= c && c <= 0xEBBF) {
          c -= 0xC140;
        } else {
          throw 'illegal char at ' + (i + 1) + '/' + c;
        }

        c = ( (c >>> 8) & 0xff) * 0xC0 + (c & 0xff);

        buffer.put(c, 13);

        i += 2;
      }

      if (i < data.length) {
        throw 'illegal char at ' + (i + 1);
      }
    };

    return _this;
  };

  //=====================================================================
  // GIF Support etc.
  //

  //---------------------------------------------------------------------
  // byteArrayOutputStream
  //---------------------------------------------------------------------

  var byteArrayOutputStream = function() {

    var _bytes = [];

    var _this = {};

    _this.writeByte = function(b) {
      _bytes.push(b & 0xff);
    };

    _this.writeShort = function(i) {
      _this.writeByte(i);
      _this.writeByte(i >>> 8);
    };

    _this.writeBytes = function(b, off, len) {
      off = off || 0;
      len = len || b.length;
      for (var i = 0; i < len; i += 1) {
        _this.writeByte(b[i + off]);
      }
    };

    _this.writeString = function(s) {
      for (var i = 0; i < s.length; i += 1) {
        _this.writeByte(s.charCodeAt(i) );
      }
    };

    _this.toByteArray = function() {
      return _bytes;
    };

    _this.toString = function() {
      var s = '';
      s += '[';
      for (var i = 0; i < _bytes.length; i += 1) {
        if (i > 0) {
          s += ',';
        }
        s += _bytes[i];
      }
      s += ']';
      return s;
    };

    return _this;
  };

  //---------------------------------------------------------------------
  // base64EncodeOutputStream
  //---------------------------------------------------------------------

  var base64EncodeOutputStream = function() {

    var _buffer = 0;
    var _buflen = 0;
    var _length = 0;
    var _base64 = '';

    var _this = {};

    var writeEncoded = function(b) {
      _base64 += String.fromCharCode(encode(b & 0x3f) );
    };

    var encode = function(n) {
      if (n < 0) {
        // error.
      } else if (n < 26) {
        return 0x41 + n;
      } else if (n < 52) {
        return 0x61 + (n - 26);
      } else if (n < 62) {
        return 0x30 + (n - 52);
      } else if (n == 62) {
        return 0x2b;
      } else if (n == 63) {
        return 0x2f;
      }
      throw 'n:' + n;
    };

    _this.writeByte = function(n) {

      _buffer = (_buffer << 8) | (n & 0xff);
      _buflen += 8;
      _length += 1;

      while (_buflen >= 6) {
        writeEncoded(_buffer >>> (_buflen - 6) );
        _buflen -= 6;
      }
    };

    _this.flush = function() {

      if (_buflen > 0) {
        writeEncoded(_buffer << (6 - _buflen) );
        _buffer = 0;
        _buflen = 0;
      }

      if (_length % 3 != 0) {
        // padding
        var padlen = 3 - _length % 3;
        for (var i = 0; i < padlen; i += 1) {
          _base64 += '=';
        }
      }
    };

    _this.toString = function() {
      return _base64;
    };

    return _this;
  };

  //---------------------------------------------------------------------
  // base64DecodeInputStream
  //---------------------------------------------------------------------

  var base64DecodeInputStream = function(str) {

    var _str = str;
    var _pos = 0;
    var _buffer = 0;
    var _buflen = 0;

    var _this = {};

    _this.read = function() {

      while (_buflen < 8) {

        if (_pos >= _str.length) {
          if (_buflen == 0) {
            return -1;
          }
          throw 'unexpected end of file./' + _buflen;
        }

        var c = _str.charAt(_pos);
        _pos += 1;

        if (c == '=') {
          _buflen = 0;
          return -1;
        } else if (c.match(/^\s$/) ) {
          // ignore if whitespace.
          continue;
        }

        _buffer = (_buffer << 6) | decode(c.charCodeAt(0) );
        _buflen += 6;
      }

      var n = (_buffer >>> (_buflen - 8) ) & 0xff;
      _buflen -= 8;
      return n;
    };

    var decode = function(c) {
      if (0x41 <= c && c <= 0x5a) {
        return c - 0x41;
      } else if (0x61 <= c && c <= 0x7a) {
        return c - 0x61 + 26;
      } else if (0x30 <= c && c <= 0x39) {
        return c - 0x30 + 52;
      } else if (c == 0x2b) {
        return 62;
      } else if (c == 0x2f) {
        return 63;
      } else {
        throw 'c:' + c;
      }
    };

    return _this;
  };

  //---------------------------------------------------------------------
  // gifImage (B/W)
  //---------------------------------------------------------------------

  var gifImage = function(width, height) {

    var _width = width;
    var _height = height;
    var _data = new Array(width * height);

    var _this = {};

    _this.setPixel = function(x, y, pixel) {
      _data[y * _width + x] = pixel;
    };

    _this.write = function(out) {

      //---------------------------------
      // GIF Signature

      out.writeString('GIF87a');

      //---------------------------------
      // Screen Descriptor

      out.writeShort(_width);
      out.writeShort(_height);

      out.writeByte(0x80); // 2bit
      out.writeByte(0);
      out.writeByte(0);

      //---------------------------------
      // Global Color Map

      // black
      out.writeByte(0x00);
      out.writeByte(0x00);
      out.writeByte(0x00);

      // white
      out.writeByte(0xff);
      out.writeByte(0xff);
      out.writeByte(0xff);

      //---------------------------------
      // Image Descriptor

      out.writeString(',');
      out.writeShort(0);
      out.writeShort(0);
      out.writeShort(_width);
      out.writeShort(_height);
      out.writeByte(0);

      //---------------------------------
      // Local Color Map

      //---------------------------------
      // Raster Data

      var lzwMinCodeSize = 2;
      var raster = getLZWRaster(lzwMinCodeSize);

      out.writeByte(lzwMinCodeSize);

      var offset = 0;

      while (raster.length - offset > 255) {
        out.writeByte(255);
        out.writeBytes(raster, offset, 255);
        offset += 255;
      }

      out.writeByte(raster.length - offset);
      out.writeBytes(raster, offset, raster.length - offset);
      out.writeByte(0x00);

      //---------------------------------
      // GIF Terminator
      out.writeString(';');
    };

    var bitOutputStream = function(out) {

      var _out = out;
      var _bitLength = 0;
      var _bitBuffer = 0;

      var _this = {};

      _this.write = function(data, length) {

        if ( (data >>> length) != 0) {
          throw 'length over';
        }

        while (_bitLength + length >= 8) {
          _out.writeByte(0xff & ( (data << _bitLength) | _bitBuffer) );
          length -= (8 - _bitLength);
          data >>>= (8 - _bitLength);
          _bitBuffer = 0;
          _bitLength = 0;
        }

        _bitBuffer = (data << _bitLength) | _bitBuffer;
        _bitLength = _bitLength + length;
      };

      _this.flush = function() {
        if (_bitLength > 0) {
          _out.writeByte(_bitBuffer);
        }
      };

      return _this;
    };

    var getLZWRaster = function(lzwMinCodeSize) {

      var clearCode = 1 << lzwMinCodeSize;
      var endCode = (1 << lzwMinCodeSize) + 1;
      var bitLength = lzwMinCodeSize + 1;

      // Setup LZWTable
      var table = lzwTable();

      for (var i = 0; i < clearCode; i += 1) {
        table.add(String.fromCharCode(i) );
      }
      table.add(String.fromCharCode(clearCode) );
      table.add(String.fromCharCode(endCode) );

      var byteOut = byteArrayOutputStream();
      var bitOut = bitOutputStream(byteOut);

      // clear code
      bitOut.write(clearCode, bitLength);

      var dataIndex = 0;

      var s = String.fromCharCode(_data[dataIndex]);
      dataIndex += 1;

      while (dataIndex < _data.length) {

        var c = String.fromCharCode(_data[dataIndex]);
        dataIndex += 1;

        if (table.contains(s + c) ) {

          s = s + c;

        } else {

          bitOut.write(table.indexOf(s), bitLength);

          if (table.size() < 0xfff) {

            if (table.size() == (1 << bitLength) ) {
              bitLength += 1;
            }

            table.add(s + c);
          }

          s = c;
        }
      }

      bitOut.write(table.indexOf(s), bitLength);

      // end code
      bitOut.write(endCode, bitLength);

      bitOut.flush();

      return byteOut.toByteArray();
    };

    var lzwTable = function() {

      var _map = {};
      var _size = 0;

      var _this = {};

      _this.add = function(key) {
        if (_this.contains(key) ) {
          throw 'dup key:' + key;
        }
        _map[key] = _size;
        _size += 1;
      };

      _this.size = function() {
        return _size;
      };

      _this.indexOf = function(key) {
        return _map[key];
      };

      _this.contains = function(key) {
        return typeof _map[key] != 'undefined';
      };

      return _this;
    };

    return _this;
  };

  var createDataURL = function(width, height, getPixel) {
    var gif = gifImage(width, height);
    for (var y = 0; y < height; y += 1) {
      for (var x = 0; x < width; x += 1) {
        gif.setPixel(x, y, getPixel(x, y) );
      }
    }

    var b = byteArrayOutputStream();
    gif.write(b);

    var base64 = base64EncodeOutputStream();
    var bytes = b.toByteArray();
    for (var i = 0; i < bytes.length; i += 1) {
      base64.writeByte(bytes[i]);
    }
    base64.flush();

    return 'data:image/gif;base64,' + base64;
  };

  //---------------------------------------------------------------------
  // returns qrcode function.

  return qrcode;
}();

// multibyte support
!function() {

  qrcode.stringToBytesFuncs['UTF-8'] = function(s) {
    // http://stackoverflow.com/questions/18729405/how-to-convert-utf8-string-to-byte-array
    function toUTF8Array(str) {
      var utf8 = [];
      for (var i=0; i < str.length; i++) {
        var charcode = str.charCodeAt(i);
        if (charcode < 0x80) utf8.push(charcode);
        else if (charcode < 0x800) {
          utf8.push(0xc0 | (charcode >> 6),
              0x80 | (charcode & 0x3f));
        }
        else if (charcode < 0xd800 || charcode >= 0xe000) {
          utf8.push(0xe0 | (charcode >> 12),
              0x80 | ((charcode>>6) & 0x3f),
              0x80 | (charcode & 0x3f));
        }
        // surrogate pair
        else {
          i++;
          // UTF-16 encodes 0x10000-0x10FFFF by
          // subtracting 0x10000 and splitting the
          // 20 bits of 0x0-0xFFFFF into two halves
          charcode = 0x10000 + (((charcode & 0x3ff)<<10)
            | (str.charCodeAt(i) & 0x3ff));
          utf8.push(0xf0 | (charcode >>18),
              0x80 | ((charcode>>12) & 0x3f),
              0x80 | ((charcode>>6) & 0x3f),
              0x80 | (charcode & 0x3f));
        }
      }
      return utf8;
    }
    return toUTF8Array(s);
  };

}();

(function (factory) {
  if (typeof define === 'function' && define.amd) {
      define([], factory);
  } else if (typeof exports === 'object') {
      module.exports = factory();
  }
}(function () {
    return qrcode;
}));

//---------------------------------------------------------------------
//
// QR Code Generator for JavaScript UTF8 Support (optional)
//
// Copyright (c) 2011 Kazuhiko Arase
//
// URL: http://www.d-project.com/
//
// Licensed under the MIT license:
//  http://www.opensource.org/licenses/mit-license.php
//
// The word 'QR Code' is registered trademark of
// DENSO WAVE INCORPORATED
//  http://www.denso-wave.com/qrcode/faqpatent-e.html
//
//---------------------------------------------------------------------

!function(qrcode) {

  //---------------------------------------------------------------------
  // overwrite qrcode.stringToBytes
  //---------------------------------------------------------------------

  qrcode.stringToBytes = qrcode.stringToBytesFuncs['UTF-8'];

}(qrcode);

/*
 * angular-qrcode
 * (c) 2017 Monospaced http://monospaced.com
 * License: MIT
 */

if (typeof module !== 'undefined' &&
    typeof exports !== 'undefined' &&
    module.exports === exports){
  module.exports = 'monospaced.qrcode';
}

angular.module('monospaced.qrcode', [])
  .directive('qrcode', ['$window', function($window) {

    var canvas2D = !!$window.CanvasRenderingContext2D,
        levels = {
          'L': 'Low',
          'M': 'Medium',
          'Q': 'Quartile',
          'H': 'High'
        },
        draw = function(context, qr, modules, tile, color) {
          for (var row = 0; row < modules; row++) {
            for (var col = 0; col < modules; col++) {
              var w = (Math.ceil((col + 1) * tile) - Math.floor(col * tile)),
                  h = (Math.ceil((row + 1) * tile) - Math.floor(row * tile));

              context.fillStyle = qr.isDark(row, col) ? color.foreground : color.background;
              context.fillRect(Math.round(col * tile),
                               Math.round(row * tile), w, h);
            }
          }
        };

    return {
      restrict: 'E',
      template: '<canvas class="qrcode"></canvas>',
      link: function(scope, element, attrs) {
        var domElement = element[0],
            $canvas = element.find('canvas'),
            canvas = $canvas[0],
            context = canvas2D ? canvas.getContext('2d') : null,
            download = 'download' in attrs,
            href = attrs.href,
            link = download || href ? document.createElement('a') : '',
            trim = /^\s+|\s+$/g,
            error,
            version,
            errorCorrectionLevel,
            data,
            size,
            modules,
            tile,
            qr,
            $img,
            color = {
              foreground: '#000',
              background: '#fff'
            },
            setColor = function(value) {
              color.foreground = value || color.foreground;
            },
            setBackground = function(value) {
              color.background = value || color.background;
            },
            setVersion = function(value) {
              version = Math.max(1, Math.min(parseInt(value, 10), 40)) || 5;
            },
            setErrorCorrectionLevel = function(value) {
              errorCorrectionLevel = value in levels ? value : 'M';
            },
            setData = function(value) {
              if (!value) {
                return;
              }

              data = value.replace(trim, '');
              qr = qrcode(version, errorCorrectionLevel);
              qr.addData(data);

              try {
                qr.make();
              } catch (e) {
                var newVersion;
                if (version >= 40) {
                  throw new Error('Data is too long', e);
                }
                newVersion = version + 1;
                setVersion(newVersion);
                console.warn('qrcode version is too low and has been incremented to', newVersion)
                setData(value);
                return;
              }

              error = false;
              modules = qr.getModuleCount();
            },
            setSize = function(value) {
              size = parseInt(value, 10) || modules * 2;
              tile = size / modules;
              canvas.width = canvas.height = size;
            },
            render = function() {
              if (!qr) {
                return;
              }

              if (error) {
                if (link) {
                  link.removeAttribute('download');
                  link.title = '';
                  link.href = '#_';
                }
                if (!canvas2D) {
                  domElement.innerHTML = '<img src width="' + size + '"' +
                                         'height="' + size + '"' +
                                         'class="qrcode">';
                }
                scope.$emit('qrcode:error', error);
                return;
              }

              if (download) {
                domElement.download = 'qrcode.png';
                domElement.title = 'Download QR code';
              }

              if (canvas2D) {
                draw(context, qr, modules, tile, color);

                if (download) {
                  domElement.href = canvas.toDataURL('image/png');
                  return;
                }
              } else {
                domElement.innerHTML = qr.createImgTag(tile, 0);
                $img = element.find('img');
                $img.addClass('qrcode');

                if (download) {
                  domElement.href = $img[0].src;
                  return;
                }
              }

              if (href) {
                domElement.href = href;
              }
            };

        if (link) {
          link.className = 'qrcode-link';
          $canvas.wrap(link);
          domElement = domElement.firstChild;
        }

        setColor(attrs.color);
        setBackground(attrs.background);
        setVersion(attrs.version);
        setErrorCorrectionLevel(attrs.errorCorrectionLevel);
        setSize(attrs.size);

        attrs.$observe('version', function(value) {
          if (!value) {
            return;
          }

          setVersion(value);
          setData(data);
          setSize(size);
          render();
        });

        attrs.$observe('errorCorrectionLevel', function(value) {
          if (!value) {
            return;
          }

          setErrorCorrectionLevel(value);
          setData(data);
          setSize(size);
          render();
        });

        attrs.$observe('data', function(value) {
          if (!value) {
            return;
          }

          setData(value);
          setSize(size);
          render();
        });

        attrs.$observe('size', function(value) {
          if (!value) {
            return;
          }

          setSize(value);
          render();
        });

        attrs.$observe('color', function(value) {
          if (!value) {
            return;
          }

          setColor(value);
          render();
        });

        attrs.$observe('background', function(value) {
          if (!value) {
            return;
          }

          setBackground(value);
          render();
        });

        attrs.$observe('href', function(value) {
          if (!value) {
            return;
          }

          href = value;
          render();
        });
      }
    };
  }]);

/* global define */
(function (root, factory) {
  /* istanbul ignore next */
  if (typeof define === 'function' && define.amd) {
    define([], factory);
  } else if (typeof exports === 'object') {
    module.exports = factory();
  } else {
    root.compareVersions = factory();
  }
}(this, function () {

  var semver = /^v?(?:\d+)(\.(?:[x*]|\d+)(\.(?:[x*]|\d+)(\.(?:[x*]|\d+))?(?:-[\da-z\-]+(?:\.[\da-z\-]+)*)?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i;

  function indexOrEnd(str, q) {
    return str.indexOf(q) === -1 ? str.length : str.indexOf(q);
  }

  function split(v) {
    var c = v.replace(/^v/, '').replace(/\+.*$/, '');
    var patchIndex = indexOrEnd(c, '-');
    var arr = c.substring(0, patchIndex).split('.');
    arr.push(c.substring(patchIndex + 1));
    return arr;
  }

  function tryParse(v) {
    return isNaN(Number(v)) ? v : Number(v);
  }

  function validate(version) {
    if (typeof version !== 'string') {
      throw new TypeError('Invalid argument expected string');
    }
    if (!semver.test(version)) {
      throw new Error('Invalid argument not valid semver (\''+version+'\' received)');
    }
  }

  function compareVersions(v1, v2) {
    [v1, v2].forEach(validate);

    var s1 = split(v1);
    var s2 = split(v2);

    for (var i = 0; i < Math.max(s1.length - 1, s2.length - 1); i++) {
      var n1 = parseInt(s1[i] || 0, 10);
      var n2 = parseInt(s2[i] || 0, 10);

      if (n1 > n2) return 1;
      if (n2 > n1) return -1;
    }

    var sp1 = s1[s1.length - 1];
    var sp2 = s2[s2.length - 1];

    if (sp1 && sp2) {
      var p1 = sp1.split('.').map(tryParse);
      var p2 = sp2.split('.').map(tryParse);

      for (i = 0; i < Math.max(p1.length, p2.length); i++) {
        if (p1[i] === undefined || typeof p2[i] === 'string' && typeof p1[i] === 'number') return -1;
        if (p2[i] === undefined || typeof p1[i] === 'string' && typeof p2[i] === 'number') return 1;

        if (p1[i] > p2[i]) return 1;
        if (p2[i] > p1[i]) return -1;
      }
    } else if (sp1 || sp2) {
      return sp1 ? -1 : 1;
    }

    return 0;
  };

  var allowedOperators = [
    '>',
    '>=',
    '=',
    '<',
    '<='
  ];

  var operatorResMap = {
    '>': [1],
    '>=': [0, 1],
    '=': [0],
    '<=': [-1, 0],
    '<': [-1]
  };

  function validateOperator(op) {
    if (typeof op !== 'string') {
      throw new TypeError('Invalid operator type, expected string but got ' + typeof op);
    }
    if (allowedOperators.indexOf(op) === -1) {
      throw new TypeError('Invalid operator, expected one of ' + allowedOperators.join('|'));
    }
  }

  compareVersions.compare = function (v1, v2, operator) {
    // Validate operator
    validateOperator(operator);

    // since result of compareVersions can only be -1 or 0 or 1
    // a simple map can be used to replace switch
    var res = compareVersions(v1, v2);
    return operatorResMap[operator].indexOf(res) > -1;
  }

  return compareVersions;
}));

!function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b,c){function d(a){this.message=a}function e(a){var b=String(a).replace(/=+$/,"");if(b.length%4==1)throw new d("'atob' failed: The string to be decoded is not correctly encoded.");for(var c,e,g=0,h=0,i="";e=b.charAt(h++);~e&&(c=g%4?64*c+e:e,g++%4)?i+=String.fromCharCode(255&c>>(-2*g&6)):0)e=f.indexOf(e);return i}var f="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";d.prototype=new Error,d.prototype.name="InvalidCharacterError",b.exports="undefined"!=typeof window&&window.atob&&window.atob.bind(window)||e},{}],2:[function(a,b,c){function d(a){return decodeURIComponent(e(a).replace(/(.)/g,function(a,b){var c=b.charCodeAt(0).toString(16).toUpperCase();return c.length<2&&(c="0"+c),"%"+c}))}var e=a("./atob");b.exports=function(a){var b=a.replace(/-/g,"+").replace(/_/g,"/");switch(b.length%4){case 0:break;case 2:b+="==";break;case 3:b+="=";break;default:throw"Illegal base64url string!"}try{return d(b)}catch(c){return e(b)}}},{"./atob":1}],3:[function(a,b,c){"use strict";function d(a){this.message=a}var e=a("./base64_url_decode");d.prototype=new Error,d.prototype.name="InvalidTokenError",b.exports=function(a,b){if("string"!=typeof a)throw new d("Invalid token specified");b=b||{};var c=b.header===!0?0:1;try{return JSON.parse(e(a.split(".")[c]))}catch(f){throw new d("Invalid token specified: "+f.message)}},b.exports.InvalidTokenError=d},{"./base64_url_decode":2}],4:[function(a,b,c){(function(b){var c=a("./lib/index");"function"==typeof b.window.define&&b.window.define.amd?b.window.define("jwt_decode",function(){return c}):b.window&&(b.window.jwt_decode=c)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./lib/index":3}]},{},[4]);
!function(n,r){"object"==typeof exports&&"undefined"!=typeof module?module.exports=r():"function"==typeof define&&define.amd?define(r):(n=n||self).InlineStyleParser=r()}(this,function(){"use strict";var v=/\/\*[^*]*\*+([^/*][^*]*\*+)*\//g,d=/\n/g,n=/^\s*/,g=/^(\*?[-#/*\\\w]+(\[[0-9a-z_-]+\])?)\s*/,y=/^:\s*/,w=/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^)]*?\)|[^};])+)/,A=/^[;\s]*/,r=/^\s+|\s+$/g,x="";function E(n){return n?n.replace(r,x):x}return function(t,e){if("string"!=typeof t)throw new TypeError("First argument must be a string");if(!t)return[];e=e||{};var o=1,i=1;function u(n){var r=n.match(d);r&&(o+=r.length);var e=n.lastIndexOf("\n");i=~e?n.length-e:i+n.length}function c(){var r={line:o,column:i};return function(n){return n.position=new f(r),l(),n}}function f(n){this.start=n,this.end={line:o,column:i},this.source=e.source}function s(n){var r=new Error(e.source+":"+o+":"+i+": "+n);if(r.reason=n,r.filename=e.source,r.line=o,r.column=i,r.source=t,!e.silent)throw r}function a(n){var r=n.exec(t);if(r){var e=r[0];return u(e),t=t.slice(e.length),r}}function l(){a(n)}function p(n){var r;for(n=n||[];r=h();)!1!==r&&n.push(r);return n}function h(){var n=c();if("/"==t.charAt(0)&&"*"==t.charAt(1)){for(var r=2;x!=t.charAt(r)&&("*"!=t.charAt(r)||"/"!=t.charAt(r+1));)++r;if(r+=2,x===t.charAt(r-1))return s("End of comment missing");var e=t.slice(2,r-2);return i+=2,u(e),t=t.slice(r),i+=2,n({type:"comment",comment:e})}}function m(){var n=c(),r=a(g);if(r){if(h(),!a(y))return s("property missing ':'");var e=a(w),t=n({type:"declaration",property:E(r[0].replace(v,x)),value:e?E(e[0].replace(v,x)):x});return a(A),t}}return f.prototype.content=t,l(),function(){var n,r=[];for(p(r);n=m();)!1!==n&&(r.push(n),p(r));return r}()}});
//# sourceMappingURL=inline-style-parser.min.js.map

/*! appspace-authentication-library v1.4.5-546ade5 | (c) Appspace | https://docs.appspace.com/faq/legal/ */
(e=>{("object"!=typeof exports||"undefined"==typeof module)&&"function"==typeof define&&define.amd?define(e):e()})(function(){function e(t){var n=this.constructor;return this.then(function(e){return n.resolve(t()).then(function(){return e})},function(e){return n.resolve(t()).then(function(){return n.reject(e)})})}function a(e){return e&&void 0!==e.length}function n(){}function i(e){if(!(this instanceof i))throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=void 0,this._deferreds=[],l(e,this)}function r(n,o){for(;3===n._state;)n=n._value;0!==n._state?(n._handled=!0,i._immediateFn(function(){var e,t=1===n._state?o.onFulfilled:o.onRejected;if(null!==t){try{e=t(n._value)}catch(e){return void c(o.promise,e)}s(o.promise,e)}else(1===n._state?s:c)(o.promise,n._value)})):n._deferreds.push(o)}function s(t,e){try{if(e===t)throw new TypeError("A promise cannot be resolved with itself.");if(e&&("object"==typeof e||"function"==typeof e)){var n=e.then;if(e instanceof i)return t._state=3,t._value=e,p(t);if("function"==typeof n)return l((o=n,r=e,function(){o.apply(r,arguments)}),t)}t._state=1,t._value=e,p(t)}catch(e){c(t,e)}var o,r}function c(e,t){e._state=2,e._value=t,p(e)}function p(e){2===e._state&&0===e._deferreds.length&&i._immediateFn(function(){e._handled||i._unhandledRejectionFn(e._value)});for(var t=0,n=e._deferreds.length;t<n;t++)r(e,e._deferreds[t]);e._deferreds=null}function l(e,t){var n=!1;try{e(function(e){n||(n=!0,s(t,e))},function(e){n||(n=!0,c(t,e))})}catch(e){n||(n=!0,c(t,e))}}var t=setTimeout,o=(i.prototype.catch=function(e){return this.then(null,e)},i.prototype.then=function(o,e){var t=new this.constructor(n);return r(this,new function(e,t,n){this.onFulfilled="function"==typeof o?o:null,this.onRejected="function"==typeof t?t:null,this.promise=n}(0,e,t)),t},i.prototype.finally=e,i.all=function(t){return new i(function(r,i){if(!a(t))return i(new TypeError("Promise.all accepts an array"));var s=Array.prototype.slice.call(t);if(0===s.length)return r([]);for(var c=s.length,e=0;s.length>e;e++)!function t(n,e){try{if(e&&("object"==typeof e||"function"==typeof e)){var o=e.then;if("function"==typeof o)return o.call(e,function(e){t(n,e)},i)}s[n]=e,0==--c&&r(s)}catch(e){i(e)}}(e,s[e])})},i.resolve=function(t){return t&&"object"==typeof t&&t.constructor===i?t:new i(function(e){e(t)})},i.reject=function(n){return new i(function(e,t){t(n)})},i.race=function(r){return new i(function(e,t){if(!a(r))return t(new TypeError("Promise.race accepts an array"));for(var n=0,o=r.length;n<o;n++)i.resolve(r[n]).then(e,t)})},i._immediateFn="function"==typeof setImmediate?function(e){setImmediate(e)}:function(e){t(e,0)},i._unhandledRejectionFn=function(e){void 0!==console&&console&&console.warn("Possible Unhandled Promise Rejection:",e)},(()=>{if("undefined"!=typeof self)return self;if("undefined"!=typeof window)return window;if("undefined"!=typeof global)return global;throw Error("unable to locate global object")})());"Promise"in o?o.Promise.prototype.finally||(o.Promise.prototype.finally=e):o.Promise=i}),function o(r,i,s){function c(n,e){if(!i[n]){if(!r[n]){var t="function"==typeof require&&require;if(!e&&t)return t(n,!0);if(a)return a(n,!0);e=new Error("Cannot find module '"+n+"'");throw e.code="MODULE_NOT_FOUND",e}t=i[n]={exports:{}};r[n][0].call(t.exports,function(e){var t=r[n][1][e];return c(t||e)},t,t.exports,o,r,i,s)}return i[n].exports}for(var a="function"==typeof require&&require,e=0;e<s.length;e++)c(s[e]);return c}({1:[function(e,t,n){function c(e){this.message=e}(c.prototype=new Error).name="InvalidCharacterError",t.exports="undefined"!=typeof window&&window.atob&&window.atob.bind(window)||function(e){var t=String(e).replace(/=+$/,"");if(t.length%4==1)throw new c("'atob' failed: The string to be decoded is not correctly encoded.");for(var n,o,r=0,i=0,s="";o=t.charAt(i++);~o&&(n=r%4?64*n+o:o,r++%4)&&(s+=String.fromCharCode(255&n>>(-2*r&6))))o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(o);return s}},{}],2:[function(e,t,n){var o=e("./atob");t.exports=function(e){var t=e.replace(/-/g,"+").replace(/_/g,"/");switch(t.length%4){case 0:break;case 2:t+="==";break;case 3:t+="=";break;default:throw"Illegal base64url string!"}try{return decodeURIComponent(o(t).replace(/(.)/g,function(e,t){t=t.charCodeAt(0).toString(16).toUpperCase();return"%"+(t=t.length<2?"0"+t:t)}))}catch(e){return o(t)}}},{"./atob":1}],3:[function(e,t,n){function o(e){this.message=e}var r=e("./base64_url_decode");(o.prototype=new Error).name="InvalidTokenError",t.exports=function(e,t){if("string"!=typeof e)throw new o("Invalid token specified");t=!0===(t=t||{}).header?0:1;try{return JSON.parse(r(e.split(".")[t]))}catch(e){throw new o("Invalid token specified: "+e.message)}},t.exports.InvalidTokenError=o},{"./base64_url_decode":2}],4:[function(n,e,t){!function(e){var t=n("./lib/index");"function"==typeof e.window.define&&e.window.define.amd?e.window.define("jwt_decode",function(){return t}):e.window&&(e.window.jwt_decode=t)}.call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./lib/index":3}]},{},[4]),window.appspace=window.appspace||{},window.appspace.CacsTokenInfo=function(e){this.token=(e=e||{}).token,this.serviceUrls=e.serviceUrls,this.expireTimestamp=e.expireTimestamp},window.appspace.CloudTokenInfo=function(e){this.clientToken=(e=e||{}).clientToken,this.expireTimestamp=e.expireTimestamp,this.serviceUrls=e.serviceUrls,this.acsUrl=e.acsUrl,this.scope=e.scope},window.appspace.ConstellationTokens=function(e,t,n){this.accessToken=e,this.refreshToken=t,this.idToken=n},window.appspace.AuthenticationHelper={parseConstellationTokensFromQueryString:function(){var e=this.Appspace.WindowHelper.getQueryString();if(e){for(var t={},n=e.substr(1).split("&"),o=0;o<n.length;o++){var r=n[o].split("=");t[r[0].toLowerCase()]=decodeURI(r[1])}return this.parseConstellationTokens(t)}},parseConstellationTokens:function(e){if(e){for(var t=new this.Appspace.ConstellationTokens,n=Object.keys(e),o=0;o<n.length;o++){var r=n[o],i=e[r];switch(r.toLowerCase()){case"accesstoken":t.accessToken=i;break;case"refreshtoken":t.refreshToken=i;break;case"idtoken":t.idToken=i}}return t}},parseJwtToken:function(e){return e?this.Appspace.JwtHelper.decode(e):void 0}},window.appspace.AuthenticationHelper.Appspace=window.appspace,window.appspace.DateHelper={TO_MILLISECONDS:1e3,parseTimestamp:function(e){if(!e||e<0)throw new Error("Ticks cannot be undefined or less than or equals to zero.");return new Date(e*this.TO_MILLISECONDS).getTime()},isExpired:function(e){return!e||e<Date.now()}},window.appspace.HttpHelper={trimEndUrl:function(e){return(e||"").replace(/\/$/,"")},trimStartUrl:function(e){return(e||"").replace(/^\//,"")},combineUrl:function(e,t){return this.trimEndUrl(e)+"/"+this.trimStartUrl(t)},parseConfiguration:function(e){e=e||{};return e.headers=e.headers||{},e}},window.appspace.JwtHelper={decode:function(e){try{return jwt_decode(e)}catch(e){}}},window.appspace.WindowHelper={getQueryString:function(){return window.location.search}},(()=>{function e(e){this.options=e||{},this.AUTHORIZATION_HEADER_KEY="Authorization",this.CLOUD_SERVICE_HEADER_KEY="x-appspace-cacs-token",this.CLOUD_CLIENT_HEADER_KEY="x-appspace-client-token",this.CREATE_CLOUD_TOKEN_ENDPOINT="/api/v3/authorization/cloud/token",this.BEARER_KEY="Bearer",this.GRANT_TYPE_REFRESH="refreshToken",this.CREATE_TOKEN_ENDPOINT="/api/v3/authorization/token",this.NON_EXISTENT_REFRESH_TOKEN=401,this.EXPIRED_REFRESH_TOKEN=403,this.EXPIRED_REFRESH_TOKEN_STATUS_CODES=[this.NON_EXISTENT_REFRESH_TOKEN,this.EXPIRED_REFRESH_TOKEN],this.constellationTokens,this.cloudTokenInfo,this.constellationBaseUrl,this.cacsTokenInfo,this.accountId,this.requestCacsTokenPromise,this.getConstellationTokensPromise,this.getCloudClientTokenPromise,this.subjectType,this.subjectId}e.prototype.setOptions=function(e){this.options=e||{}},e.prototype.setConstellationTokens=function(e){if(!(e&&e.accessToken&&e.refreshToken&&e.idToken))throw new Error("Missing accessToken or refreshToken or idToken.");this.constellationTokens=e;e=this.Appspace.AuthenticationHelper.parseJwtToken(e.idToken);if(!(e&&e.instanceUrl&&e.subjectType&&e.subjectId))throw new Error("Unable to decode instance URL and subject information from idToken.");this.constellationBaseUrl=this.Appspace.HttpHelper.trimEndUrl(e.instanceUrl),this.accountId=e.accountId,this.subjectId=e.subjectId,this.subjectType=e.subjectType},e.prototype.requestConstellationService=function(t,n){var e=this.getConfigurationError()||this.getConstellationConfigurationError(t);return e?Promise.reject(e):this.getTokens().then(function(e){return(n=this.Appspace.HttpHelper.parseConfiguration(n)).headers[this.AUTHORIZATION_HEADER_KEY]=this.BEARER_KEY+" "+e.accessToken,this.requestConstellationServiceDirect(t,n)}.bind(this),function(e){return Promise.reject(this.constellationTokens?e:new Error("Missing constellation tokens."))}.bind(this))},e.prototype.getServiceUrl=function(t,e){return this.getCloudClientToken(e).then(function(e){return e.serviceUrls=e.serviceUrls||{},Promise.resolve(e.serviceUrls[t])},function(e){return Promise.reject(e)})},e.prototype.getConstellationBaseUrl=function(){return this.getTokens().then(function(){return Promise.resolve(this.constellationBaseUrl)}.bind(this),function(e){return Promise.reject(e)})},e.prototype.getAccountId=function(){return this.getTokens().then(function(){return Promise.resolve(this.accountId)}.bind(this),function(e){return Promise.reject(e)})},e.prototype.requestCloudService=function(t,n,o,e){var r=this.getConfigurationError();return r?Promise.reject(r):t?this.getCacsToken(e).then(function(e){(o=this.Appspace.HttpHelper.parseConfiguration(o)).headers[this.CLOUD_SERVICE_HEADER_KEY]=e.token;e=e.serviceUrls[t];return e&&(o.url=this.Appspace.HttpHelper.combineUrl(e,n)),o.url?Promise.resolve(o):Promise.reject(new Error("Url is not configured."))}.bind(this),function(e){return Promise.reject(this.cacsTokenInfo?e:new Error("Missing CACS token info."))}.bind(this)).then(this.options.requestClient.bind(this)):Promise.reject(new Error("Service name cannot be undefined."))},e.prototype.getConfigurationError=function(){if(!this.options.requestClient)return new Error("Missing request client.")},e.prototype.getConstellationConfigurationError=function(e){if(!e)return new Error("Missing relative service URL.")},e.prototype.isAccessTokenValid=function(e){if(!e)throw new Error("Token cannot be undefined.");e=this.Appspace.AuthenticationHelper.parseJwtToken(e.accessToken);if(e&&e.exp)return!this.Appspace.DateHelper.isExpired(this.Appspace.DateHelper.parseTimestamp(e.exp));throw new Error("Unable to decode expiry date from accessToken.")},e.prototype.createTokenByRefreshToken=function(e,t,n,o){n=this.Appspace.HttpHelper.parseConfiguration({method:"POST",data:{subjectType:n,subjectId:t,grantType:this.GRANT_TYPE_REFRESH,refreshToken:e}});return n.baseUrl=o,this.requestConstellationServiceDirect(this.CREATE_TOKEN_ENDPOINT,n)},e.prototype.refreshToken=function(t){return this.setConstellationTokens(t),this.createTokenByRefreshToken(t.refreshToken,this.subjectId,this.subjectType).then(function(e){try{return Promise.resolve(this.onTokenResponse(e))}catch(e){return Promise.reject(e)}}.bind(this),function(e){return this.options.onConstellationTokensRefreshFailed&&this.options.onConstellationTokensRefreshFailed({error:e,payload:{refreshToken:t.refreshToken,subjectId:this.subjectId,subjectType:this.subjectType,grantType:this.GRANT_TYPE_REFRESH}}),-1!==this.EXPIRED_REFRESH_TOKEN_STATUS_CODES.indexOf((e||{}).status)&&this.options.requestConstellationTokens?this.requestTokens():Promise.reject(e)}.bind(this))},e.prototype.requestTokens=function(){return this.options.requestConstellationTokens?this.options.requestConstellationTokens().then(function(e){try{var t=this.onTokenResponse(e);return this.setConstellationTokens(t),Promise.resolve(t)}catch(e){return Promise.reject(e)}}.bind(this)):Promise.reject(new Error("Unable to request tokens."))},e.prototype.onTokenResponse=function(e){if(!e||0==Object.keys(e).length)throw new Error("Received empty response upon tokens refresh.");e=this.Appspace.AuthenticationHelper.parseConstellationTokens(e);if(!e)throw new Error("Unable to parse tokens after refresh.");if(this.isAccessTokenValid(e))return this.constellationTokens=e,this.options.onConstellationTokensRefreshed&&this.options.onConstellationTokensRefreshed(this.constellationTokens),e;throw new Error("Retrieved token is invalid.")},e.prototype.getTokens=function(){return this.getConstellationTokensPromise||(this.getConstellationTokensPromise=this.processGetConstellationTokens().finally(function(){this.getConstellationTokensPromise=void 0}.bind(this))),this.getConstellationTokensPromise},e.prototype.processGetConstellationTokens=function(){if(!this.constellationTokens)return this.requestTokens();try{return this.isAccessTokenValid(this.constellationTokens)?Promise.resolve(this.constellationTokens):this.refreshToken(this.constellationTokens)}catch(e){return Promise.reject(e)}},e.prototype.getCacsToken=function(e){if(!this.requestCacsTokenPromise){if(this.cacsTokenInfo&&!this.Appspace.DateHelper.isExpired(this.cacsTokenInfo.expireTimestamp))return Promise.resolve(this.cacsTokenInfo);var n;this.requestCacsTokenPromise=this.getCloudClientToken(e).then(function(e){var t=this.Appspace.HttpHelper.parseConfiguration({method:"POST",url:e.acsUrl,data:{scope:e.scope}});return n=e,t.headers[this.CLOUD_CLIENT_HEADER_KEY]=e.clientToken,Promise.resolve(t)}.bind(this),function(e){return Promise.reject(this.cloudTokenInfo?e:new Error("Missing client token."))}.bind(this)).then(this.options.requestClient.bind(this)).then(function(e){var t=new window.appspace.CacsTokenInfo(e);return e&&0!=Object.keys(e).length?t.token?(e=this.Appspace.AuthenticationHelper.parseJwtToken(t.token))?(t.expireTimestamp=this.Appspace.DateHelper.parseTimestamp(e.exp),this.Appspace.DateHelper.isExpired(t.expireTimestamp)?Promise.reject(new Error("CACS token has already expired.")):(t.serviceUrls=n.serviceUrls,this.cacsTokenInfo=t,Promise.resolve(this.cacsTokenInfo))):Promise.reject(new Error("Unable to parse CACS tokens.")):Promise.reject(new Error("CACS token cannot be undefined.")):Promise.reject(new Error("Received empty response for CACS tokens."))}.bind(this)).finally(function(){this.requestCacsTokenPromise=void 0}.bind(this))}return this.requestCacsTokenPromise},e.prototype.getCloudClientToken=function(e){if(!this.getCloudClientTokenPromise){if(this.cloudTokenInfo&&!this.Appspace.DateHelper.isExpired(this.cloudTokenInfo.expireTimestamp))return Promise.resolve(this.cloudTokenInfo);if(!e)return Promise.reject(new Error("Context is required to get cloud client token."));this.getCloudClientTokenPromise=this.requestConstellationService(this.CREATE_CLOUD_TOKEN_ENDPOINT,{method:"POST",data:{context:e}}).then(function(e){var t=new window.appspace.CloudTokenInfo(e);return e&&0!=Object.keys(e).length?t.clientToken?t.acsUrl?(e=this.Appspace.AuthenticationHelper.parseJwtToken(t.clientToken))?e.cloud_acs_scope?(t.scope=e.cloud_acs_scope,t.expireTimestamp=this.Appspace.DateHelper.parseTimestamp(e.exp),this.Appspace.DateHelper.isExpired(t.expireTimestamp)?Promise.reject(new Error("Cloud token has already expired.")):(t.serviceUrls=t.serviceUrls||{},this.cloudTokenInfo=t,Promise.resolve(this.cloudTokenInfo))):Promise.reject(new Error("The scope for the token cannot be undefined.")):Promise.reject(new Error("Unable to parse cloud tokens.")):Promise.reject(new Error("ACS url cannot be undefined.")):Promise.reject(new Error("Client token cannot be undefined.")):Promise.reject(new Error("Received empty response for cloud tokens."))}.bind(this)).finally(function(){this.getCloudClientTokenPromise=void 0}.bind(this))}return this.getCloudClientTokenPromise},e.prototype.requestConstellationServiceDirect=function(e,t){var n=t.baseUrl||this.constellationBaseUrl;return t.url=this.Appspace.HttpHelper.combineUrl(n,e),this.options.requestClient(t)},window.appspace.AuthenticationClient=e,window.appspace.AuthenticationClient.prototype.Appspace=window.appspace})();
(function webpackUniversalModuleDefinition(root, factory) {
	if(typeof exports === 'object' && typeof module === 'object')
		module.exports = factory();
	else if(typeof define === 'function' && define.amd)
		define("PNSClient", [], factory);
	else if(typeof exports === 'object')
		exports["PNSClient"] = factory();
	else
		root["PNSClient"] = factory();
})(self, function() {
return /******/ (function() { // webpackBootstrap
/******/ 	var __webpack_modules__ = ({

/***/ "./node_modules/assert/build/assert.js":
/*!*********************************************!*\
  !*** ./node_modules/assert/build/assert.js ***!
  \*********************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";
/* provided dependency */ var process = __webpack_require__(/*! process/browser */ "./node_modules/process/browser.js");
// Currently in sync with Node.js lib/assert.js
// https://github.com/nodejs/node/commit/2a51ae424a513ec9a6aa3466baa0cc1d55dd4f3b
// Originally from narwhal.js (http://narwhaljs.org)
// Copyright (c) 2009 Thomas Robinson <280north.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the 'Software'), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


function _typeof(obj) {
  if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
    _typeof = function _typeof(obj) {
      return typeof obj;
    };
  } else {
    _typeof = function _typeof(obj) {
      return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
    };
  }

  return _typeof(obj);
}

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

var _require = __webpack_require__(/*! ./internal/errors */ "./node_modules/assert/build/internal/errors.js"),
    _require$codes = _require.codes,
    ERR_AMBIGUOUS_ARGUMENT = _require$codes.ERR_AMBIGUOUS_ARGUMENT,
    ERR_INVALID_ARG_TYPE = _require$codes.ERR_INVALID_ARG_TYPE,
    ERR_INVALID_ARG_VALUE = _require$codes.ERR_INVALID_ARG_VALUE,
    ERR_INVALID_RETURN_VALUE = _require$codes.ERR_INVALID_RETURN_VALUE,
    ERR_MISSING_ARGS = _require$codes.ERR_MISSING_ARGS;

var AssertionError = __webpack_require__(/*! ./internal/assert/assertion_error */ "./node_modules/assert/build/internal/assert/assertion_error.js");

var _require2 = __webpack_require__(/*! util/ */ "./node_modules/util/util.js"),
    inspect = _require2.inspect;

var _require$types = (__webpack_require__(/*! util/ */ "./node_modules/util/util.js").types),
    isPromise = _require$types.isPromise,
    isRegExp = _require$types.isRegExp;

var objectAssign = Object.assign ? Object.assign : (__webpack_require__(/*! es6-object-assign */ "./node_modules/es6-object-assign/index.js").assign);
var objectIs = Object.is ? Object.is : __webpack_require__(/*! object-is */ "./node_modules/object-is/index.js");
var errorCache = new Map();
var isDeepEqual;
var isDeepStrictEqual;
var parseExpressionAt;
var findNodeAround;
var decoder;

function lazyLoadComparison() {
  var comparison = __webpack_require__(/*! ./internal/util/comparisons */ "./node_modules/assert/build/internal/util/comparisons.js");

  isDeepEqual = comparison.isDeepEqual;
  isDeepStrictEqual = comparison.isDeepStrictEqual;
} // Escape control characters but not \n and \t to keep the line breaks and
// indentation intact.
// eslint-disable-next-line no-control-regex


var escapeSequencesRegExp = /[\x00-\x08\x0b\x0c\x0e-\x1f]/g;
var meta = ["\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", "\\u0007", '\\b', '', '', "\\u000b", '\\f', '', "\\u000e", "\\u000f", "\\u0010", "\\u0011", "\\u0012", "\\u0013", "\\u0014", "\\u0015", "\\u0016", "\\u0017", "\\u0018", "\\u0019", "\\u001a", "\\u001b", "\\u001c", "\\u001d", "\\u001e", "\\u001f"];

var escapeFn = function escapeFn(str) {
  return meta[str.charCodeAt(0)];
};

var warned = false; // The assert module provides functions that throw
// AssertionError's when particular conditions are not met. The
// assert module must conform to the following interface.

var assert = module.exports = ok;
var NO_EXCEPTION_SENTINEL = {}; // All of the following functions must throw an AssertionError
// when a corresponding condition is not met, with a message that
// may be undefined if not provided. All assertion methods provide
// both the actual and expected values to the assertion error for
// display purposes.

function innerFail(obj) {
  if (obj.message instanceof Error) throw obj.message;
  throw new AssertionError(obj);
}

function fail(actual, expected, message, operator, stackStartFn) {
  var argsLen = arguments.length;
  var internalMessage;

  if (argsLen === 0) {
    internalMessage = 'Failed';
  } else if (argsLen === 1) {
    message = actual;
    actual = undefined;
  } else {
    if (warned === false) {
      warned = true;
      var warn = process.emitWarning ? process.emitWarning : console.warn.bind(console);
      warn('assert.fail() with more than one argument is deprecated. ' + 'Please use assert.strictEqual() instead or only pass a message.', 'DeprecationWarning', 'DEP0094');
    }

    if (argsLen === 2) operator = '!=';
  }

  if (message instanceof Error) throw message;
  var errArgs = {
    actual: actual,
    expected: expected,
    operator: operator === undefined ? 'fail' : operator,
    stackStartFn: stackStartFn || fail
  };

  if (message !== undefined) {
    errArgs.message = message;
  }

  var err = new AssertionError(errArgs);

  if (internalMessage) {
    err.message = internalMessage;
    err.generatedMessage = true;
  }

  throw err;
}

assert.fail = fail; // The AssertionError is defined in internal/error.

assert.AssertionError = AssertionError;

function innerOk(fn, argLen, value, message) {
  if (!value) {
    var generatedMessage = false;

    if (argLen === 0) {
      generatedMessage = true;
      message = 'No value argument passed to `assert.ok()`';
    } else if (message instanceof Error) {
      throw message;
    }

    var err = new AssertionError({
      actual: value,
      expected: true,
      message: message,
      operator: '==',
      stackStartFn: fn
    });
    err.generatedMessage = generatedMessage;
    throw err;
  }
} // Pure assertion tests whether a value is truthy, as determined
// by !!value.


function ok() {
  for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
    args[_key] = arguments[_key];
  }

  innerOk.apply(void 0, [ok, args.length].concat(args));
}

assert.ok = ok; // The equality assertion tests shallow, coercive equality with ==.

/* eslint-disable no-restricted-properties */

assert.equal = function equal(actual, expected, message) {
  if (arguments.length < 2) {
    throw new ERR_MISSING_ARGS('actual', 'expected');
  } // eslint-disable-next-line eqeqeq


  if (actual != expected) {
    innerFail({
      actual: actual,
      expected: expected,
      message: message,
      operator: '==',
      stackStartFn: equal
    });
  }
}; // The non-equality assertion tests for whether two objects are not
// equal with !=.


assert.notEqual = function notEqual(actual, expected, message) {
  if (arguments.length < 2) {
    throw new ERR_MISSING_ARGS('actual', 'expected');
  } // eslint-disable-next-line eqeqeq


  if (actual == expected) {
    innerFail({
      actual: actual,
      expected: expected,
      message: message,
      operator: '!=',
      stackStartFn: notEqual
    });
  }
}; // The equivalence assertion tests a deep equality relation.


assert.deepEqual = function deepEqual(actual, expected, message) {
  if (arguments.length < 2) {
    throw new ERR_MISSING_ARGS('actual', 'expected');
  }

  if (isDeepEqual === undefined) lazyLoadComparison();

  if (!isDeepEqual(actual, expected)) {
    innerFail({
      actual: actual,
      expected: expected,
      message: message,
      operator: 'deepEqual',
      stackStartFn: deepEqual
    });
  }
}; // The non-equivalence assertion tests for any deep inequality.


assert.notDeepEqual = function notDeepEqual(actual, expected, message) {
  if (arguments.length < 2) {
    throw new ERR_MISSING_ARGS('actual', 'expected');
  }

  if (isDeepEqual === undefined) lazyLoadComparison();

  if (isDeepEqual(actual, expected)) {
    innerFail({
      actual: actual,
      expected: expected,
      message: message,
      operator: 'notDeepEqual',
      stackStartFn: notDeepEqual
    });
  }
};
/* eslint-enable */


assert.deepStrictEqual = function deepStrictEqual(actual, expected, message) {
  if (arguments.length < 2) {
    throw new ERR_MISSING_ARGS('actual', 'expected');
  }

  if (isDeepEqual === undefined) lazyLoadComparison();

  if (!isDeepStrictEqual(actual, expected)) {
    innerFail({
      actual: actual,
      expected: expected,
      message: message,
      operator: 'deepStrictEqual',
      stackStartFn: deepStrictEqual
    });
  }
};

assert.notDeepStrictEqual = notDeepStrictEqual;

function notDeepStrictEqual(actual, expected, message) {
  if (arguments.length < 2) {
    throw new ERR_MISSING_ARGS('actual', 'expected');
  }

  if (isDeepEqual === undefined) lazyLoadComparison();

  if (isDeepStrictEqual(actual, expected)) {
    innerFail({
      actual: actual,
      expected: expected,
      message: message,
      operator: 'notDeepStrictEqual',
      stackStartFn: notDeepStrictEqual
    });
  }
}

assert.strictEqual = function strictEqual(actual, expected, message) {
  if (arguments.length < 2) {
    throw new ERR_MISSING_ARGS('actual', 'expected');
  }

  if (!objectIs(actual, expected)) {
    innerFail({
      actual: actual,
      expected: expected,
      message: message,
      operator: 'strictEqual',
      stackStartFn: strictEqual
    });
  }
};

assert.notStrictEqual = function notStrictEqual(actual, expected, message) {
  if (arguments.length < 2) {
    throw new ERR_MISSING_ARGS('actual', 'expected');
  }

  if (objectIs(actual, expected)) {
    innerFail({
      actual: actual,
      expected: expected,
      message: message,
      operator: 'notStrictEqual',
      stackStartFn: notStrictEqual
    });
  }
};

var Comparison = function Comparison(obj, keys, actual) {
  var _this = this;

  _classCallCheck(this, Comparison);

  keys.forEach(function (key) {
    if (key in obj) {
      if (actual !== undefined && typeof actual[key] === 'string' && isRegExp(obj[key]) && obj[key].test(actual[key])) {
        _this[key] = actual[key];
      } else {
        _this[key] = obj[key];
      }
    }
  });
};

function compareExceptionKey(actual, expected, key, message, keys, fn) {
  if (!(key in actual) || !isDeepStrictEqual(actual[key], expected[key])) {
    if (!message) {
      // Create placeholder objects to create a nice output.
      var a = new Comparison(actual, keys);
      var b = new Comparison(expected, keys, actual);
      var err = new AssertionError({
        actual: a,
        expected: b,
        operator: 'deepStrictEqual',
        stackStartFn: fn
      });
      err.actual = actual;
      err.expected = expected;
      err.operator = fn.name;
      throw err;
    }

    innerFail({
      actual: actual,
      expected: expected,
      message: message,
      operator: fn.name,
      stackStartFn: fn
    });
  }
}

function expectedException(actual, expected, msg, fn) {
  if (typeof expected !== 'function') {
    if (isRegExp(expected)) return expected.test(actual); // assert.doesNotThrow does not accept objects.

    if (arguments.length === 2) {
      throw new ERR_INVALID_ARG_TYPE('expected', ['Function', 'RegExp'], expected);
    } // Handle primitives properly.


    if (_typeof(actual) !== 'object' || actual === null) {
      var err = new AssertionError({
        actual: actual,
        expected: expected,
        message: msg,
        operator: 'deepStrictEqual',
        stackStartFn: fn
      });
      err.operator = fn.name;
      throw err;
    }

    var keys = Object.keys(expected); // Special handle errors to make sure the name and the message are compared
    // as well.

    if (expected instanceof Error) {
      keys.push('name', 'message');
    } else if (keys.length === 0) {
      throw new ERR_INVALID_ARG_VALUE('error', expected, 'may not be an empty object');
    }

    if (isDeepEqual === undefined) lazyLoadComparison();
    keys.forEach(function (key) {
      if (typeof actual[key] === 'string' && isRegExp(expected[key]) && expected[key].test(actual[key])) {
        return;
      }

      compareExceptionKey(actual, expected, key, msg, keys, fn);
    });
    return true;
  } // Guard instanceof against arrow functions as they don't have a prototype.


  if (expected.prototype !== undefined && actual instanceof expected) {
    return true;
  }

  if (Error.isPrototypeOf(expected)) {
    return false;
  }

  return expected.call({}, actual) === true;
}

function getActual(fn) {
  if (typeof fn !== 'function') {
    throw new ERR_INVALID_ARG_TYPE('fn', 'Function', fn);
  }

  try {
    fn();
  } catch (e) {
    return e;
  }

  return NO_EXCEPTION_SENTINEL;
}

function checkIsPromise(obj) {
  // Accept native ES6 promises and promises that are implemented in a similar
  // way. Do not accept thenables that use a function as `obj` and that have no
  // `catch` handler.
  // TODO: thenables are checked up until they have the correct methods,
  // but according to documentation, the `then` method should receive
  // the `fulfill` and `reject` arguments as well or it may be never resolved.
  return isPromise(obj) || obj !== null && _typeof(obj) === 'object' && typeof obj.then === 'function' && typeof obj.catch === 'function';
}

function waitForActual(promiseFn) {
  return Promise.resolve().then(function () {
    var resultPromise;

    if (typeof promiseFn === 'function') {
      // Return a rejected promise if `promiseFn` throws synchronously.
      resultPromise = promiseFn(); // Fail in case no promise is returned.

      if (!checkIsPromise(resultPromise)) {
        throw new ERR_INVALID_RETURN_VALUE('instance of Promise', 'promiseFn', resultPromise);
      }
    } else if (checkIsPromise(promiseFn)) {
      resultPromise = promiseFn;
    } else {
      throw new ERR_INVALID_ARG_TYPE('promiseFn', ['Function', 'Promise'], promiseFn);
    }

    return Promise.resolve().then(function () {
      return resultPromise;
    }).then(function () {
      return NO_EXCEPTION_SENTINEL;
    }).catch(function (e) {
      return e;
    });
  });
}

function expectsError(stackStartFn, actual, error, message) {
  if (typeof error === 'string') {
    if (arguments.length === 4) {
      throw new ERR_INVALID_ARG_TYPE('error', ['Object', 'Error', 'Function', 'RegExp'], error);
    }

    if (_typeof(actual) === 'object' && actual !== null) {
      if (actual.message === error) {
        throw new ERR_AMBIGUOUS_ARGUMENT('error/message', "The error message \"".concat(actual.message, "\" is identical to the message."));
      }
    } else if (actual === error) {
      throw new ERR_AMBIGUOUS_ARGUMENT('error/message', "The error \"".concat(actual, "\" is identical to the message."));
    }

    message = error;
    error = undefined;
  } else if (error != null && _typeof(error) !== 'object' && typeof error !== 'function') {
    throw new ERR_INVALID_ARG_TYPE('error', ['Object', 'Error', 'Function', 'RegExp'], error);
  }

  if (actual === NO_EXCEPTION_SENTINEL) {
    var details = '';

    if (error && error.name) {
      details += " (".concat(error.name, ")");
    }

    details += message ? ": ".concat(message) : '.';
    var fnType = stackStartFn.name === 'rejects' ? 'rejection' : 'exception';
    innerFail({
      actual: undefined,
      expected: error,
      operator: stackStartFn.name,
      message: "Missing expected ".concat(fnType).concat(details),
      stackStartFn: stackStartFn
    });
  }

  if (error && !expectedException(actual, error, message, stackStartFn)) {
    throw actual;
  }
}

function expectsNoError(stackStartFn, actual, error, message) {
  if (actual === NO_EXCEPTION_SENTINEL) return;

  if (typeof error === 'string') {
    message = error;
    error = undefined;
  }

  if (!error || expectedException(actual, error)) {
    var details = message ? ": ".concat(message) : '.';
    var fnType = stackStartFn.name === 'doesNotReject' ? 'rejection' : 'exception';
    innerFail({
      actual: actual,
      expected: error,
      operator: stackStartFn.name,
      message: "Got unwanted ".concat(fnType).concat(details, "\n") + "Actual message: \"".concat(actual && actual.message, "\""),
      stackStartFn: stackStartFn
    });
  }

  throw actual;
}

assert.throws = function throws(promiseFn) {
  for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
    args[_key2 - 1] = arguments[_key2];
  }

  expectsError.apply(void 0, [throws, getActual(promiseFn)].concat(args));
};

assert.rejects = function rejects(promiseFn) {
  for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
    args[_key3 - 1] = arguments[_key3];
  }

  return waitForActual(promiseFn).then(function (result) {
    return expectsError.apply(void 0, [rejects, result].concat(args));
  });
};

assert.doesNotThrow = function doesNotThrow(fn) {
  for (var _len4 = arguments.length, args = new Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) {
    args[_key4 - 1] = arguments[_key4];
  }

  expectsNoError.apply(void 0, [doesNotThrow, getActual(fn)].concat(args));
};

assert.doesNotReject = function doesNotReject(fn) {
  for (var _len5 = arguments.length, args = new Array(_len5 > 1 ? _len5 - 1 : 0), _key5 = 1; _key5 < _len5; _key5++) {
    args[_key5 - 1] = arguments[_key5];
  }

  return waitForActual(fn).then(function (result) {
    return expectsNoError.apply(void 0, [doesNotReject, result].concat(args));
  });
};

assert.ifError = function ifError(err) {
  if (err !== null && err !== undefined) {
    var message = 'ifError got unwanted exception: ';

    if (_typeof(err) === 'object' && typeof err.message === 'string') {
      if (err.message.length === 0 && err.constructor) {
        message += err.constructor.name;
      } else {
        message += err.message;
      }
    } else {
      message += inspect(err);
    }

    var newErr = new AssertionError({
      actual: err,
      expected: null,
      operator: 'ifError',
      message: message,
      stackStartFn: ifError
    }); // Make sure we actually have a stack trace!

    var origStack = err.stack;

    if (typeof origStack === 'string') {
      // This will remove any duplicated frames from the error frames taken
      // from within `ifError` and add the original error frames to the newly
      // created ones.
      var tmp2 = origStack.split('\n');
      tmp2.shift(); // Filter all frames existing in err.stack.

      var tmp1 = newErr.stack.split('\n');

      for (var i = 0; i < tmp2.length; i++) {
        // Find the first occurrence of the frame.
        var pos = tmp1.indexOf(tmp2[i]);

        if (pos !== -1) {
          // Only keep new frames.
          tmp1 = tmp1.slice(0, pos);
          break;
        }
      }

      newErr.stack = "".concat(tmp1.join('\n'), "\n").concat(tmp2.join('\n'));
    }

    throw newErr;
  }
}; // Expose a strict only variant of assert


function strict() {
  for (var _len6 = arguments.length, args = new Array(_len6), _key6 = 0; _key6 < _len6; _key6++) {
    args[_key6] = arguments[_key6];
  }

  innerOk.apply(void 0, [strict, args.length].concat(args));
}

assert.strict = objectAssign(strict, assert, {
  equal: assert.strictEqual,
  deepEqual: assert.deepStrictEqual,
  notEqual: assert.notStrictEqual,
  notDeepEqual: assert.notDeepStrictEqual
});
assert.strict.strict = assert.strict;

/***/ }),

/***/ "./node_modules/assert/build/internal/assert/assertion_error.js":
/*!**********************************************************************!*\
  !*** ./node_modules/assert/build/internal/assert/assertion_error.js ***!
  \**********************************************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";
/* provided dependency */ var process = __webpack_require__(/*! process/browser */ "./node_modules/process/browser.js");
// Currently in sync with Node.js lib/internal/assert/assertion_error.js
// https://github.com/nodejs/node/commit/0817840f775032169ddd70c85ac059f18ffcc81c


function _objectSpread(target) {
  for (var i = 1; i < arguments.length; i++) {
    var source = arguments[i] != null ? arguments[i] : {};
    var ownKeys = Object.keys(source);

    if (typeof Object.getOwnPropertySymbols === 'function') {
      ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) {
        return Object.getOwnPropertyDescriptor(source, sym).enumerable;
      }));
    }

    ownKeys.forEach(function (key) {
      _defineProperty(target, key, source[key]);
    });
  }

  return target;
}

function _defineProperty(obj, key, value) {
  if (key in obj) {
    Object.defineProperty(obj, key, {
      value: value,
      enumerable: true,
      configurable: true,
      writable: true
    });
  } else {
    obj[key] = value;
  }

  return obj;
}

function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a 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);
  }
}

function _createClass(Constructor, protoProps, staticProps) {
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  if (staticProps) _defineProperties(Constructor, staticProps);
  return Constructor;
}

function _possibleConstructorReturn(self, call) {
  if (call && (_typeof(call) === "object" || typeof call === "function")) {
    return call;
  }

  return _assertThisInitialized(self);
}

function _assertThisInitialized(self) {
  if (self === void 0) {
    throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  }

  return self;
}

function _inherits(subClass, superClass) {
  if (typeof superClass !== "function" && superClass !== null) {
    throw new TypeError("Super expression must either be null or a function");
  }

  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: {
      value: subClass,
      writable: true,
      configurable: true
    }
  });
  if (superClass) _setPrototypeOf(subClass, superClass);
}

function _wrapNativeSuper(Class) {
  var _cache = typeof Map === "function" ? new Map() : undefined;

  _wrapNativeSuper = function _wrapNativeSuper(Class) {
    if (Class === null || !_isNativeFunction(Class)) return Class;

    if (typeof Class !== "function") {
      throw new TypeError("Super expression must either be null or a function");
    }

    if (typeof _cache !== "undefined") {
      if (_cache.has(Class)) return _cache.get(Class);

      _cache.set(Class, Wrapper);
    }

    function Wrapper() {
      return _construct(Class, arguments, _getPrototypeOf(this).constructor);
    }

    Wrapper.prototype = Object.create(Class.prototype, {
      constructor: {
        value: Wrapper,
        enumerable: false,
        writable: true,
        configurable: true
      }
    });
    return _setPrototypeOf(Wrapper, Class);
  };

  return _wrapNativeSuper(Class);
}

function isNativeReflectConstruct() {
  if (typeof Reflect === "undefined" || !Reflect.construct) return false;
  if (Reflect.construct.sham) return false;
  if (typeof Proxy === "function") return true;

  try {
    Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
    return true;
  } catch (e) {
    return false;
  }
}

function _construct(Parent, args, Class) {
  if (isNativeReflectConstruct()) {
    _construct = Reflect.construct;
  } else {
    _construct = function _construct(Parent, args, Class) {
      var a = [null];
      a.push.apply(a, args);
      var Constructor = Function.bind.apply(Parent, a);
      var instance = new Constructor();
      if (Class) _setPrototypeOf(instance, Class.prototype);
      return instance;
    };
  }

  return _construct.apply(null, arguments);
}

function _isNativeFunction(fn) {
  return Function.toString.call(fn).indexOf("[native code]") !== -1;
}

function _setPrototypeOf(o, p) {
  _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
    o.__proto__ = p;
    return o;
  };

  return _setPrototypeOf(o, p);
}

function _getPrototypeOf(o) {
  _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
    return o.__proto__ || Object.getPrototypeOf(o);
  };
  return _getPrototypeOf(o);
}

function _typeof(obj) {
  if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
    _typeof = function _typeof(obj) {
      return typeof obj;
    };
  } else {
    _typeof = function _typeof(obj) {
      return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
    };
  }

  return _typeof(obj);
}

var _require = __webpack_require__(/*! util/ */ "./node_modules/util/util.js"),
    inspect = _require.inspect;

var _require2 = __webpack_require__(/*! ../errors */ "./node_modules/assert/build/internal/errors.js"),
    ERR_INVALID_ARG_TYPE = _require2.codes.ERR_INVALID_ARG_TYPE; // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith


function endsWith(str, search, this_len) {
  if (this_len === undefined || this_len > str.length) {
    this_len = str.length;
  }

  return str.substring(this_len - search.length, this_len) === search;
} // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat


function repeat(str, count) {
  count = Math.floor(count);
  if (str.length == 0 || count == 0) return '';
  var maxCount = str.length * count;
  count = Math.floor(Math.log(count) / Math.log(2));

  while (count) {
    str += str;
    count--;
  }

  str += str.substring(0, maxCount - str.length);
  return str;
}

var blue = '';
var green = '';
var red = '';
var white = '';
var kReadableOperator = {
  deepStrictEqual: 'Expected values to be strictly deep-equal:',
  strictEqual: 'Expected values to be strictly equal:',
  strictEqualObject: 'Expected "actual" to be reference-equal to "expected":',
  deepEqual: 'Expected values to be loosely deep-equal:',
  equal: 'Expected values to be loosely equal:',
  notDeepStrictEqual: 'Expected "actual" not to be strictly deep-equal to:',
  notStrictEqual: 'Expected "actual" to be strictly unequal to:',
  notStrictEqualObject: 'Expected "actual" not to be reference-equal to "expected":',
  notDeepEqual: 'Expected "actual" not to be loosely deep-equal to:',
  notEqual: 'Expected "actual" to be loosely unequal to:',
  notIdentical: 'Values identical but not reference-equal:'
}; // Comparing short primitives should just show === / !== instead of using the
// diff.

var kMaxShortLength = 10;

function copyError(source) {
  var keys = Object.keys(source);
  var target = Object.create(Object.getPrototypeOf(source));
  keys.forEach(function (key) {
    target[key] = source[key];
  });
  Object.defineProperty(target, 'message', {
    value: source.message
  });
  return target;
}

function inspectValue(val) {
  // The util.inspect default values could be changed. This makes sure the
  // error messages contain the necessary information nevertheless.
  return inspect(val, {
    compact: false,
    customInspect: false,
    depth: 1000,
    maxArrayLength: Infinity,
    // Assert compares only enumerable properties (with a few exceptions).
    showHidden: false,
    // Having a long line as error is better than wrapping the line for
    // comparison for now.
    // TODO(BridgeAR): `breakLength` should be limited as soon as soon as we
    // have meta information about the inspected properties (i.e., know where
    // in what line the property starts and ends).
    breakLength: Infinity,
    // Assert does not detect proxies currently.
    showProxy: false,
    sorted: true,
    // Inspect getters as we also check them when comparing entries.
    getters: true
  });
}

function createErrDiff(actual, expected, operator) {
  var other = '';
  var res = '';
  var lastPos = 0;
  var end = '';
  var skipped = false;
  var actualInspected = inspectValue(actual);
  var actualLines = actualInspected.split('\n');
  var expectedLines = inspectValue(expected).split('\n');
  var i = 0;
  var indicator = ''; // In case both values are objects explicitly mark them as not reference equal
  // for the `strictEqual` operator.

  if (operator === 'strictEqual' && _typeof(actual) === 'object' && _typeof(expected) === 'object' && actual !== null && expected !== null) {
    operator = 'strictEqualObject';
  } // If "actual" and "expected" fit on a single line and they are not strictly
  // equal, check further special handling.


  if (actualLines.length === 1 && expectedLines.length === 1 && actualLines[0] !== expectedLines[0]) {
    var inputLength = actualLines[0].length + expectedLines[0].length; // If the character length of "actual" and "expected" together is less than
    // kMaxShortLength and if neither is an object and at least one of them is
    // not `zero`, use the strict equal comparison to visualize the output.

    if (inputLength <= kMaxShortLength) {
      if ((_typeof(actual) !== 'object' || actual === null) && (_typeof(expected) !== 'object' || expected === null) && (actual !== 0 || expected !== 0)) {
        // -0 === +0
        return "".concat(kReadableOperator[operator], "\n\n") + "".concat(actualLines[0], " !== ").concat(expectedLines[0], "\n");
      }
    } else if (operator !== 'strictEqualObject') {
      // If the stderr is a tty and the input length is lower than the current
      // columns per line, add a mismatch indicator below the output. If it is
      // not a tty, use a default value of 80 characters.
      var maxLength = process.stderr && process.stderr.isTTY ? process.stderr.columns : 80;

      if (inputLength < maxLength) {
        while (actualLines[0][i] === expectedLines[0][i]) {
          i++;
        } // Ignore the first characters.


        if (i > 2) {
          // Add position indicator for the first mismatch in case it is a
          // single line and the input length is less than the column length.
          indicator = "\n  ".concat(repeat(' ', i), "^");
          i = 0;
        }
      }
    }
  } // Remove all ending lines that match (this optimizes the output for
  // readability by reducing the number of total changed lines).


  var a = actualLines[actualLines.length - 1];
  var b = expectedLines[expectedLines.length - 1];

  while (a === b) {
    if (i++ < 2) {
      end = "\n  ".concat(a).concat(end);
    } else {
      other = a;
    }

    actualLines.pop();
    expectedLines.pop();
    if (actualLines.length === 0 || expectedLines.length === 0) break;
    a = actualLines[actualLines.length - 1];
    b = expectedLines[expectedLines.length - 1];
  }

  var maxLines = Math.max(actualLines.length, expectedLines.length); // Strict equal with identical objects that are not identical by reference.
  // E.g., assert.deepStrictEqual({ a: Symbol() }, { a: Symbol() })

  if (maxLines === 0) {
    // We have to get the result again. The lines were all removed before.
    var _actualLines = actualInspected.split('\n'); // Only remove lines in case it makes sense to collapse those.
    // TODO: Accept env to always show the full error.


    if (_actualLines.length > 30) {
      _actualLines[26] = "".concat(blue, "...").concat(white);

      while (_actualLines.length > 27) {
        _actualLines.pop();
      }
    }

    return "".concat(kReadableOperator.notIdentical, "\n\n").concat(_actualLines.join('\n'), "\n");
  }

  if (i > 3) {
    end = "\n".concat(blue, "...").concat(white).concat(end);
    skipped = true;
  }

  if (other !== '') {
    end = "\n  ".concat(other).concat(end);
    other = '';
  }

  var printedLines = 0;
  var msg = kReadableOperator[operator] + "\n".concat(green, "+ actual").concat(white, " ").concat(red, "- expected").concat(white);
  var skippedMsg = " ".concat(blue, "...").concat(white, " Lines skipped");

  for (i = 0; i < maxLines; i++) {
    // Only extra expected lines exist
    var cur = i - lastPos;

    if (actualLines.length < i + 1) {
      // If the last diverging line is more than one line above and the
      // current line is at least line three, add some of the former lines and
      // also add dots to indicate skipped entries.
      if (cur > 1 && i > 2) {
        if (cur > 4) {
          res += "\n".concat(blue, "...").concat(white);
          skipped = true;
        } else if (cur > 3) {
          res += "\n  ".concat(expectedLines[i - 2]);
          printedLines++;
        }

        res += "\n  ".concat(expectedLines[i - 1]);
        printedLines++;
      } // Mark the current line as the last diverging one.


      lastPos = i; // Add the expected line to the cache.

      other += "\n".concat(red, "-").concat(white, " ").concat(expectedLines[i]);
      printedLines++; // Only extra actual lines exist
    } else if (expectedLines.length < i + 1) {
      // If the last diverging line is more than one line above and the
      // current line is at least line three, add some of the former lines and
      // also add dots to indicate skipped entries.
      if (cur > 1 && i > 2) {
        if (cur > 4) {
          res += "\n".concat(blue, "...").concat(white);
          skipped = true;
        } else if (cur > 3) {
          res += "\n  ".concat(actualLines[i - 2]);
          printedLines++;
        }

        res += "\n  ".concat(actualLines[i - 1]);
        printedLines++;
      } // Mark the current line as the last diverging one.


      lastPos = i; // Add the actual line to the result.

      res += "\n".concat(green, "+").concat(white, " ").concat(actualLines[i]);
      printedLines++; // Lines diverge
    } else {
      var expectedLine = expectedLines[i];
      var actualLine = actualLines[i]; // If the lines diverge, specifically check for lines that only diverge by
      // a trailing comma. In that case it is actually identical and we should
      // mark it as such.

      var divergingLines = actualLine !== expectedLine && (!endsWith(actualLine, ',') || actualLine.slice(0, -1) !== expectedLine); // If the expected line has a trailing comma but is otherwise identical,
      // add a comma at the end of the actual line. Otherwise the output could
      // look weird as in:
      //
      //   [
      //     1         // No comma at the end!
      // +   2
      //   ]
      //

      if (divergingLines && endsWith(expectedLine, ',') && expectedLine.slice(0, -1) === actualLine) {
        divergingLines = false;
        actualLine += ',';
      }

      if (divergingLines) {
        // If the last diverging line is more than one line above and the
        // current line is at least line three, add some of the former lines and
        // also add dots to indicate skipped entries.
        if (cur > 1 && i > 2) {
          if (cur > 4) {
            res += "\n".concat(blue, "...").concat(white);
            skipped = true;
          } else if (cur > 3) {
            res += "\n  ".concat(actualLines[i - 2]);
            printedLines++;
          }

          res += "\n  ".concat(actualLines[i - 1]);
          printedLines++;
        } // Mark the current line as the last diverging one.


        lastPos = i; // Add the actual line to the result and cache the expected diverging
        // line so consecutive diverging lines show up as +++--- and not +-+-+-.

        res += "\n".concat(green, "+").concat(white, " ").concat(actualLine);
        other += "\n".concat(red, "-").concat(white, " ").concat(expectedLine);
        printedLines += 2; // Lines are identical
      } else {
        // Add all cached information to the result before adding other things
        // and reset the cache.
        res += other;
        other = ''; // If the last diverging line is exactly one line above or if it is the
        // very first line, add the line to the result.

        if (cur === 1 || i === 0) {
          res += "\n  ".concat(actualLine);
          printedLines++;
        }
      }
    } // Inspected object to big (Show ~20 rows max)


    if (printedLines > 20 && i < maxLines - 2) {
      return "".concat(msg).concat(skippedMsg, "\n").concat(res, "\n").concat(blue, "...").concat(white).concat(other, "\n") + "".concat(blue, "...").concat(white);
    }
  }

  return "".concat(msg).concat(skipped ? skippedMsg : '', "\n").concat(res).concat(other).concat(end).concat(indicator);
}

var AssertionError = /*#__PURE__*/function (_Error) {
  _inherits(AssertionError, _Error);

  function AssertionError(options) {
    var _this;

    _classCallCheck(this, AssertionError);

    if (_typeof(options) !== 'object' || options === null) {
      throw new ERR_INVALID_ARG_TYPE('options', 'Object', options);
    }

    var message = options.message,
        operator = options.operator,
        stackStartFn = options.stackStartFn;
    var actual = options.actual,
        expected = options.expected;
    var limit = Error.stackTraceLimit;
    Error.stackTraceLimit = 0;

    if (message != null) {
      _this = _possibleConstructorReturn(this, _getPrototypeOf(AssertionError).call(this, String(message)));
    } else {
      if (process.stderr && process.stderr.isTTY) {
        // Reset on each call to make sure we handle dynamically set environment
        // variables correct.
        if (process.stderr && process.stderr.getColorDepth && process.stderr.getColorDepth() !== 1) {
          blue = "\x1B[34m";
          green = "\x1B[32m";
          white = "\x1B[39m";
          red = "\x1B[31m";
        } else {
          blue = '';
          green = '';
          white = '';
          red = '';
        }
      } // Prevent the error stack from being visible by duplicating the error
      // in a very close way to the original in case both sides are actually
      // instances of Error.


      if (_typeof(actual) === 'object' && actual !== null && _typeof(expected) === 'object' && expected !== null && 'stack' in actual && actual instanceof Error && 'stack' in expected && expected instanceof Error) {
        actual = copyError(actual);
        expected = copyError(expected);
      }

      if (operator === 'deepStrictEqual' || operator === 'strictEqual') {
        _this = _possibleConstructorReturn(this, _getPrototypeOf(AssertionError).call(this, createErrDiff(actual, expected, operator)));
      } else if (operator === 'notDeepStrictEqual' || operator === 'notStrictEqual') {
        // In case the objects are equal but the operator requires unequal, show
        // the first object and say A equals B
        var base = kReadableOperator[operator];
        var res = inspectValue(actual).split('\n'); // In case "actual" is an object, it should not be reference equal.

        if (operator === 'notStrictEqual' && _typeof(actual) === 'object' && actual !== null) {
          base = kReadableOperator.notStrictEqualObject;
        } // Only remove lines in case it makes sense to collapse those.
        // TODO: Accept env to always show the full error.


        if (res.length > 30) {
          res[26] = "".concat(blue, "...").concat(white);

          while (res.length > 27) {
            res.pop();
          }
        } // Only print a single input.


        if (res.length === 1) {
          _this = _possibleConstructorReturn(this, _getPrototypeOf(AssertionError).call(this, "".concat(base, " ").concat(res[0])));
        } else {
          _this = _possibleConstructorReturn(this, _getPrototypeOf(AssertionError).call(this, "".concat(base, "\n\n").concat(res.join('\n'), "\n")));
        }
      } else {
        var _res = inspectValue(actual);

        var other = '';
        var knownOperators = kReadableOperator[operator];

        if (operator === 'notDeepEqual' || operator === 'notEqual') {
          _res = "".concat(kReadableOperator[operator], "\n\n").concat(_res);

          if (_res.length > 1024) {
            _res = "".concat(_res.slice(0, 1021), "...");
          }
        } else {
          other = "".concat(inspectValue(expected));

          if (_res.length > 512) {
            _res = "".concat(_res.slice(0, 509), "...");
          }

          if (other.length > 512) {
            other = "".concat(other.slice(0, 509), "...");
          }

          if (operator === 'deepEqual' || operator === 'equal') {
            _res = "".concat(knownOperators, "\n\n").concat(_res, "\n\nshould equal\n\n");
          } else {
            other = " ".concat(operator, " ").concat(other);
          }
        }

        _this = _possibleConstructorReturn(this, _getPrototypeOf(AssertionError).call(this, "".concat(_res).concat(other)));
      }
    }

    Error.stackTraceLimit = limit;
    _this.generatedMessage = !message;
    Object.defineProperty(_assertThisInitialized(_this), 'name', {
      value: 'AssertionError [ERR_ASSERTION]',
      enumerable: false,
      writable: true,
      configurable: true
    });
    _this.code = 'ERR_ASSERTION';
    _this.actual = actual;
    _this.expected = expected;
    _this.operator = operator;

    if (Error.captureStackTrace) {
      // eslint-disable-next-line no-restricted-syntax
      Error.captureStackTrace(_assertThisInitialized(_this), stackStartFn);
    } // Create error message including the error code in the name.


    _this.stack; // Reset the name.

    _this.name = 'AssertionError';
    return _possibleConstructorReturn(_this);
  }

  _createClass(AssertionError, [{
    key: "toString",
    value: function toString() {
      return "".concat(this.name, " [").concat(this.code, "]: ").concat(this.message);
    }
  }, {
    key: inspect.custom,
    value: function value(recurseTimes, ctx) {
      // This limits the `actual` and `expected` property default inspection to
      // the minimum depth. Otherwise those values would be too verbose compared
      // to the actual error message which contains a combined view of these two
      // input values.
      return inspect(this, _objectSpread({}, ctx, {
        customInspect: false,
        depth: 0
      }));
    }
  }]);

  return AssertionError;
}(_wrapNativeSuper(Error));

module.exports = AssertionError;

/***/ }),

/***/ "./node_modules/assert/build/internal/errors.js":
/*!******************************************************!*\
  !*** ./node_modules/assert/build/internal/errors.js ***!
  \******************************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";
// Currently in sync with Node.js lib/internal/errors.js
// https://github.com/nodejs/node/commit/3b044962c48fe313905877a96b5d0894a5404f6f

/* eslint node-core/documented-errors: "error" */

/* eslint node-core/alphabetize-errors: "error" */

/* eslint node-core/prefer-util-format-errors: "error" */
 // The whole point behind this internal module is to allow Node.js to no
// longer be forced to treat every error message change as a semver-major
// change. The NodeError classes here all expose a `code` property whose
// value statically and permanently identifies the error. While the error
// message may change, the code should not.

function _typeof(obj) {
  if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
    _typeof = function _typeof(obj) {
      return typeof obj;
    };
  } else {
    _typeof = function _typeof(obj) {
      return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
    };
  }

  return _typeof(obj);
}

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

function _possibleConstructorReturn(self, call) {
  if (call && (_typeof(call) === "object" || typeof call === "function")) {
    return call;
  }

  return _assertThisInitialized(self);
}

function _assertThisInitialized(self) {
  if (self === void 0) {
    throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  }

  return self;
}

function _getPrototypeOf(o) {
  _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
    return o.__proto__ || Object.getPrototypeOf(o);
  };
  return _getPrototypeOf(o);
}

function _inherits(subClass, superClass) {
  if (typeof superClass !== "function" && superClass !== null) {
    throw new TypeError("Super expression must either be null or a function");
  }

  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: {
      value: subClass,
      writable: true,
      configurable: true
    }
  });
  if (superClass) _setPrototypeOf(subClass, superClass);
}

function _setPrototypeOf(o, p) {
  _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
    o.__proto__ = p;
    return o;
  };

  return _setPrototypeOf(o, p);
}

var codes = {}; // Lazy loaded

var assert;
var util;

function createErrorType(code, message, Base) {
  if (!Base) {
    Base = Error;
  }

  function getMessage(arg1, arg2, arg3) {
    if (typeof message === 'string') {
      return message;
    } else {
      return message(arg1, arg2, arg3);
    }
  }

  var NodeError = /*#__PURE__*/function (_Base) {
    _inherits(NodeError, _Base);

    function NodeError(arg1, arg2, arg3) {
      var _this;

      _classCallCheck(this, NodeError);

      _this = _possibleConstructorReturn(this, _getPrototypeOf(NodeError).call(this, getMessage(arg1, arg2, arg3)));
      _this.code = code;
      return _this;
    }

    return NodeError;
  }(Base);

  codes[code] = NodeError;
} // https://github.com/nodejs/node/blob/v10.8.0/lib/internal/errors.js


function oneOf(expected, thing) {
  if (Array.isArray(expected)) {
    var len = expected.length;
    expected = expected.map(function (i) {
      return String(i);
    });

    if (len > 2) {
      return "one of ".concat(thing, " ").concat(expected.slice(0, len - 1).join(', '), ", or ") + expected[len - 1];
    } else if (len === 2) {
      return "one of ".concat(thing, " ").concat(expected[0], " or ").concat(expected[1]);
    } else {
      return "of ".concat(thing, " ").concat(expected[0]);
    }
  } else {
    return "of ".concat(thing, " ").concat(String(expected));
  }
} // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith


function startsWith(str, search, pos) {
  return str.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search;
} // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith


function endsWith(str, search, this_len) {
  if (this_len === undefined || this_len > str.length) {
    this_len = str.length;
  }

  return str.substring(this_len - search.length, this_len) === search;
} // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes


function includes(str, search, start) {
  if (typeof start !== 'number') {
    start = 0;
  }

  if (start + search.length > str.length) {
    return false;
  } else {
    return str.indexOf(search, start) !== -1;
  }
}

createErrorType('ERR_AMBIGUOUS_ARGUMENT', 'The "%s" argument is ambiguous. %s', TypeError);
createErrorType('ERR_INVALID_ARG_TYPE', function (name, expected, actual) {
  if (assert === undefined) assert = __webpack_require__(/*! ../assert */ "./node_modules/assert/build/assert.js");
  assert(typeof name === 'string', "'name' must be a string"); // determiner: 'must be' or 'must not be'

  var determiner;

  if (typeof expected === 'string' && startsWith(expected, 'not ')) {
    determiner = 'must not be';
    expected = expected.replace(/^not /, '');
  } else {
    determiner = 'must be';
  }

  var msg;

  if (endsWith(name, ' argument')) {
    // For cases like 'first argument'
    msg = "The ".concat(name, " ").concat(determiner, " ").concat(oneOf(expected, 'type'));
  } else {
    var type = includes(name, '.') ? 'property' : 'argument';
    msg = "The \"".concat(name, "\" ").concat(type, " ").concat(determiner, " ").concat(oneOf(expected, 'type'));
  } // TODO(BridgeAR): Improve the output by showing `null` and similar.


  msg += ". Received type ".concat(_typeof(actual));
  return msg;
}, TypeError);
createErrorType('ERR_INVALID_ARG_VALUE', function (name, value) {
  var reason = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'is invalid';
  if (util === undefined) util = __webpack_require__(/*! util/ */ "./node_modules/util/util.js");
  var inspected = util.inspect(value);

  if (inspected.length > 128) {
    inspected = "".concat(inspected.slice(0, 128), "...");
  }

  return "The argument '".concat(name, "' ").concat(reason, ". Received ").concat(inspected);
}, TypeError, RangeError);
createErrorType('ERR_INVALID_RETURN_VALUE', function (input, name, value) {
  var type;

  if (value && value.constructor && value.constructor.name) {
    type = "instance of ".concat(value.constructor.name);
  } else {
    type = "type ".concat(_typeof(value));
  }

  return "Expected ".concat(input, " to be returned from the \"").concat(name, "\"") + " function but got ".concat(type, ".");
}, TypeError);
createErrorType('ERR_MISSING_ARGS', function () {
  for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
    args[_key] = arguments[_key];
  }

  if (assert === undefined) assert = __webpack_require__(/*! ../assert */ "./node_modules/assert/build/assert.js");
  assert(args.length > 0, 'At least one arg needs to be specified');
  var msg = 'The ';
  var len = args.length;
  args = args.map(function (a) {
    return "\"".concat(a, "\"");
  });

  switch (len) {
    case 1:
      msg += "".concat(args[0], " argument");
      break;

    case 2:
      msg += "".concat(args[0], " and ").concat(args[1], " arguments");
      break;

    default:
      msg += args.slice(0, len - 1).join(', ');
      msg += ", and ".concat(args[len - 1], " arguments");
      break;
  }

  return "".concat(msg, " must be specified");
}, TypeError);
module.exports.codes = codes;

/***/ }),

/***/ "./node_modules/assert/build/internal/util/comparisons.js":
/*!****************************************************************!*\
  !*** ./node_modules/assert/build/internal/util/comparisons.js ***!
  \****************************************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";
// Currently in sync with Node.js lib/internal/util/comparisons.js
// https://github.com/nodejs/node/commit/112cc7c27551254aa2b17098fb774867f05ed0d9


function _slicedToArray(arr, i) {
  return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest();
}

function _nonIterableRest() {
  throw new TypeError("Invalid attempt to destructure non-iterable instance");
}

function _iterableToArrayLimit(arr, i) {
  var _arr = [];
  var _n = true;
  var _d = false;
  var _e = undefined;

  try {
    for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
      _arr.push(_s.value);

      if (i && _arr.length === i) break;
    }
  } catch (err) {
    _d = true;
    _e = err;
  } finally {
    try {
      if (!_n && _i["return"] != null) _i["return"]();
    } finally {
      if (_d) throw _e;
    }
  }

  return _arr;
}

function _arrayWithHoles(arr) {
  if (Array.isArray(arr)) return arr;
}

function _typeof(obj) {
  if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
    _typeof = function _typeof(obj) {
      return typeof obj;
    };
  } else {
    _typeof = function _typeof(obj) {
      return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
    };
  }

  return _typeof(obj);
}

var regexFlagsSupported = /a/g.flags !== undefined;

var arrayFromSet = function arrayFromSet(set) {
  var array = [];
  set.forEach(function (value) {
    return array.push(value);
  });
  return array;
};

var arrayFromMap = function arrayFromMap(map) {
  var array = [];
  map.forEach(function (value, key) {
    return array.push([key, value]);
  });
  return array;
};

var objectIs = Object.is ? Object.is : __webpack_require__(/*! object-is */ "./node_modules/object-is/index.js");
var objectGetOwnPropertySymbols = Object.getOwnPropertySymbols ? Object.getOwnPropertySymbols : function () {
  return [];
};
var numberIsNaN = Number.isNaN ? Number.isNaN : __webpack_require__(/*! is-nan */ "./node_modules/is-nan/index.js");

function uncurryThis(f) {
  return f.call.bind(f);
}

var hasOwnProperty = uncurryThis(Object.prototype.hasOwnProperty);
var propertyIsEnumerable = uncurryThis(Object.prototype.propertyIsEnumerable);
var objectToString = uncurryThis(Object.prototype.toString);

var _require$types = (__webpack_require__(/*! util/ */ "./node_modules/util/util.js").types),
    isAnyArrayBuffer = _require$types.isAnyArrayBuffer,
    isArrayBufferView = _require$types.isArrayBufferView,
    isDate = _require$types.isDate,
    isMap = _require$types.isMap,
    isRegExp = _require$types.isRegExp,
    isSet = _require$types.isSet,
    isNativeError = _require$types.isNativeError,
    isBoxedPrimitive = _require$types.isBoxedPrimitive,
    isNumberObject = _require$types.isNumberObject,
    isStringObject = _require$types.isStringObject,
    isBooleanObject = _require$types.isBooleanObject,
    isBigIntObject = _require$types.isBigIntObject,
    isSymbolObject = _require$types.isSymbolObject,
    isFloat32Array = _require$types.isFloat32Array,
    isFloat64Array = _require$types.isFloat64Array;

function isNonIndex(key) {
  if (key.length === 0 || key.length > 10) return true;

  for (var i = 0; i < key.length; i++) {
    var code = key.charCodeAt(i);
    if (code < 48 || code > 57) return true;
  } // The maximum size for an array is 2 ** 32 -1.


  return key.length === 10 && key >= Math.pow(2, 32);
}

function getOwnNonIndexProperties(value) {
  return Object.keys(value).filter(isNonIndex).concat(objectGetOwnPropertySymbols(value).filter(Object.prototype.propertyIsEnumerable.bind(value)));
} // Taken from https://github.com/feross/buffer/blob/680e9e5e488f22aac27599a57dc844a6315928dd/index.js
// original notice:

/*!
 * The buffer module from node.js, for the browser.
 *
 * @author   Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
 * @license  MIT
 */


function compare(a, b) {
  if (a === b) {
    return 0;
  }

  var x = a.length;
  var y = b.length;

  for (var i = 0, len = Math.min(x, y); i < len; ++i) {
    if (a[i] !== b[i]) {
      x = a[i];
      y = b[i];
      break;
    }
  }

  if (x < y) {
    return -1;
  }

  if (y < x) {
    return 1;
  }

  return 0;
}

var ONLY_ENUMERABLE = undefined;
var kStrict = true;
var kLoose = false;
var kNoIterator = 0;
var kIsArray = 1;
var kIsSet = 2;
var kIsMap = 3; // Check if they have the same source and flags

function areSimilarRegExps(a, b) {
  return regexFlagsSupported ? a.source === b.source && a.flags === b.flags : RegExp.prototype.toString.call(a) === RegExp.prototype.toString.call(b);
}

function areSimilarFloatArrays(a, b) {
  if (a.byteLength !== b.byteLength) {
    return false;
  }

  for (var offset = 0; offset < a.byteLength; offset++) {
    if (a[offset] !== b[offset]) {
      return false;
    }
  }

  return true;
}

function areSimilarTypedArrays(a, b) {
  if (a.byteLength !== b.byteLength) {
    return false;
  }

  return compare(new Uint8Array(a.buffer, a.byteOffset, a.byteLength), new Uint8Array(b.buffer, b.byteOffset, b.byteLength)) === 0;
}

function areEqualArrayBuffers(buf1, buf2) {
  return buf1.byteLength === buf2.byteLength && compare(new Uint8Array(buf1), new Uint8Array(buf2)) === 0;
}

function isEqualBoxedPrimitive(val1, val2) {
  if (isNumberObject(val1)) {
    return isNumberObject(val2) && objectIs(Number.prototype.valueOf.call(val1), Number.prototype.valueOf.call(val2));
  }

  if (isStringObject(val1)) {
    return isStringObject(val2) && String.prototype.valueOf.call(val1) === String.prototype.valueOf.call(val2);
  }

  if (isBooleanObject(val1)) {
    return isBooleanObject(val2) && Boolean.prototype.valueOf.call(val1) === Boolean.prototype.valueOf.call(val2);
  }

  if (isBigIntObject(val1)) {
    return isBigIntObject(val2) && BigInt.prototype.valueOf.call(val1) === BigInt.prototype.valueOf.call(val2);
  }

  return isSymbolObject(val2) && Symbol.prototype.valueOf.call(val1) === Symbol.prototype.valueOf.call(val2);
} // Notes: Type tags are historical [[Class]] properties that can be set by
// FunctionTemplate::SetClassName() in C++ or Symbol.toStringTag in JS
// and retrieved using Object.prototype.toString.call(obj) in JS
// See https://tc39.github.io/ecma262/#sec-object.prototype.tostring
// for a list of tags pre-defined in the spec.
// There are some unspecified tags in the wild too (e.g. typed array tags).
// Since tags can be altered, they only serve fast failures
//
// Typed arrays and buffers are checked by comparing the content in their
// underlying ArrayBuffer. This optimization requires that it's
// reasonable to interpret their underlying memory in the same way,
// which is checked by comparing their type tags.
// (e.g. a Uint8Array and a Uint16Array with the same memory content
// could still be different because they will be interpreted differently).
//
// For strict comparison, objects should have
// a) The same built-in type tags
// b) The same prototypes.


function innerDeepEqual(val1, val2, strict, memos) {
  // All identical values are equivalent, as determined by ===.
  if (val1 === val2) {
    if (val1 !== 0) return true;
    return strict ? objectIs(val1, val2) : true;
  } // Check more closely if val1 and val2 are equal.


  if (strict) {
    if (_typeof(val1) !== 'object') {
      return typeof val1 === 'number' && numberIsNaN(val1) && numberIsNaN(val2);
    }

    if (_typeof(val2) !== 'object' || val1 === null || val2 === null) {
      return false;
    }

    if (Object.getPrototypeOf(val1) !== Object.getPrototypeOf(val2)) {
      return false;
    }
  } else {
    if (val1 === null || _typeof(val1) !== 'object') {
      if (val2 === null || _typeof(val2) !== 'object') {
        // eslint-disable-next-line eqeqeq
        return val1 == val2;
      }

      return false;
    }

    if (val2 === null || _typeof(val2) !== 'object') {
      return false;
    }
  }

  var val1Tag = objectToString(val1);
  var val2Tag = objectToString(val2);

  if (val1Tag !== val2Tag) {
    return false;
  }

  if (Array.isArray(val1)) {
    // Check for sparse arrays and general fast path
    if (val1.length !== val2.length) {
      return false;
    }

    var keys1 = getOwnNonIndexProperties(val1, ONLY_ENUMERABLE);
    var keys2 = getOwnNonIndexProperties(val2, ONLY_ENUMERABLE);

    if (keys1.length !== keys2.length) {
      return false;
    }

    return keyCheck(val1, val2, strict, memos, kIsArray, keys1);
  } // [browserify] This triggers on certain types in IE (Map/Set) so we don't
  // wan't to early return out of the rest of the checks. However we can check
  // if the second value is one of these values and the first isn't.


  if (val1Tag === '[object Object]') {
    // return keyCheck(val1, val2, strict, memos, kNoIterator);
    if (!isMap(val1) && isMap(val2) || !isSet(val1) && isSet(val2)) {
      return false;
    }
  }

  if (isDate(val1)) {
    if (!isDate(val2) || Date.prototype.getTime.call(val1) !== Date.prototype.getTime.call(val2)) {
      return false;
    }
  } else if (isRegExp(val1)) {
    if (!isRegExp(val2) || !areSimilarRegExps(val1, val2)) {
      return false;
    }
  } else if (isNativeError(val1) || val1 instanceof Error) {
    // Do not compare the stack as it might differ even though the error itself
    // is otherwise identical.
    if (val1.message !== val2.message || val1.name !== val2.name) {
      return false;
    }
  } else if (isArrayBufferView(val1)) {
    if (!strict && (isFloat32Array(val1) || isFloat64Array(val1))) {
      if (!areSimilarFloatArrays(val1, val2)) {
        return false;
      }
    } else if (!areSimilarTypedArrays(val1, val2)) {
      return false;
    } // Buffer.compare returns true, so val1.length === val2.length. If they both
    // only contain numeric keys, we don't need to exam further than checking
    // the symbols.


    var _keys = getOwnNonIndexProperties(val1, ONLY_ENUMERABLE);

    var _keys2 = getOwnNonIndexProperties(val2, ONLY_ENUMERABLE);

    if (_keys.length !== _keys2.length) {
      return false;
    }

    return keyCheck(val1, val2, strict, memos, kNoIterator, _keys);
  } else if (isSet(val1)) {
    if (!isSet(val2) || val1.size !== val2.size) {
      return false;
    }

    return keyCheck(val1, val2, strict, memos, kIsSet);
  } else if (isMap(val1)) {
    if (!isMap(val2) || val1.size !== val2.size) {
      return false;
    }

    return keyCheck(val1, val2, strict, memos, kIsMap);
  } else if (isAnyArrayBuffer(val1)) {
    if (!areEqualArrayBuffers(val1, val2)) {
      return false;
    }
  } else if (isBoxedPrimitive(val1) && !isEqualBoxedPrimitive(val1, val2)) {
    return false;
  }

  return keyCheck(val1, val2, strict, memos, kNoIterator);
}

function getEnumerables(val, keys) {
  return keys.filter(function (k) {
    return propertyIsEnumerable(val, k);
  });
}

function keyCheck(val1, val2, strict, memos, iterationType, aKeys) {
  // For all remaining Object pairs, including Array, objects and Maps,
  // equivalence is determined by having:
  // a) The same number of owned enumerable properties
  // b) The same set of keys/indexes (although not necessarily the same order)
  // c) Equivalent values for every corresponding key/index
  // d) For Sets and Maps, equal contents
  // Note: this accounts for both named and indexed properties on Arrays.
  if (arguments.length === 5) {
    aKeys = Object.keys(val1);
    var bKeys = Object.keys(val2); // The pair must have the same number of owned properties.

    if (aKeys.length !== bKeys.length) {
      return false;
    }
  } // Cheap key test


  var i = 0;

  for (; i < aKeys.length; i++) {
    if (!hasOwnProperty(val2, aKeys[i])) {
      return false;
    }
  }

  if (strict && arguments.length === 5) {
    var symbolKeysA = objectGetOwnPropertySymbols(val1);

    if (symbolKeysA.length !== 0) {
      var count = 0;

      for (i = 0; i < symbolKeysA.length; i++) {
        var key = symbolKeysA[i];

        if (propertyIsEnumerable(val1, key)) {
          if (!propertyIsEnumerable(val2, key)) {
            return false;
          }

          aKeys.push(key);
          count++;
        } else if (propertyIsEnumerable(val2, key)) {
          return false;
        }
      }

      var symbolKeysB = objectGetOwnPropertySymbols(val2);

      if (symbolKeysA.length !== symbolKeysB.length && getEnumerables(val2, symbolKeysB).length !== count) {
        return false;
      }
    } else {
      var _symbolKeysB = objectGetOwnPropertySymbols(val2);

      if (_symbolKeysB.length !== 0 && getEnumerables(val2, _symbolKeysB).length !== 0) {
        return false;
      }
    }
  }

  if (aKeys.length === 0 && (iterationType === kNoIterator || iterationType === kIsArray && val1.length === 0 || val1.size === 0)) {
    return true;
  } // Use memos to handle cycles.


  if (memos === undefined) {
    memos = {
      val1: new Map(),
      val2: new Map(),
      position: 0
    };
  } else {
    // We prevent up to two map.has(x) calls by directly retrieving the value
    // and checking for undefined. The map can only contain numbers, so it is
    // safe to check for undefined only.
    var val2MemoA = memos.val1.get(val1);

    if (val2MemoA !== undefined) {
      var val2MemoB = memos.val2.get(val2);

      if (val2MemoB !== undefined) {
        return val2MemoA === val2MemoB;
      }
    }

    memos.position++;
  }

  memos.val1.set(val1, memos.position);
  memos.val2.set(val2, memos.position);
  var areEq = objEquiv(val1, val2, strict, aKeys, memos, iterationType);
  memos.val1.delete(val1);
  memos.val2.delete(val2);
  return areEq;
}

function setHasEqualElement(set, val1, strict, memo) {
  // Go looking.
  var setValues = arrayFromSet(set);

  for (var i = 0; i < setValues.length; i++) {
    var val2 = setValues[i];

    if (innerDeepEqual(val1, val2, strict, memo)) {
      // Remove the matching element to make sure we do not check that again.
      set.delete(val2);
      return true;
    }
  }

  return false;
} // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness#Loose_equality_using
// Sadly it is not possible to detect corresponding values properly in case the
// type is a string, number, bigint or boolean. The reason is that those values
// can match lots of different string values (e.g., 1n == '+00001').


function findLooseMatchingPrimitives(prim) {
  switch (_typeof(prim)) {
    case 'undefined':
      return null;

    case 'object':
      // Only pass in null as object!
      return undefined;

    case 'symbol':
      return false;

    case 'string':
      prim = +prim;
    // Loose equal entries exist only if the string is possible to convert to
    // a regular number and not NaN.
    // Fall through

    case 'number':
      if (numberIsNaN(prim)) {
        return false;
      }

  }

  return true;
}

function setMightHaveLoosePrim(a, b, prim) {
  var altValue = findLooseMatchingPrimitives(prim);
  if (altValue != null) return altValue;
  return b.has(altValue) && !a.has(altValue);
}

function mapMightHaveLoosePrim(a, b, prim, item, memo) {
  var altValue = findLooseMatchingPrimitives(prim);

  if (altValue != null) {
    return altValue;
  }

  var curB = b.get(altValue);

  if (curB === undefined && !b.has(altValue) || !innerDeepEqual(item, curB, false, memo)) {
    return false;
  }

  return !a.has(altValue) && innerDeepEqual(item, curB, false, memo);
}

function setEquiv(a, b, strict, memo) {
  // This is a lazily initiated Set of entries which have to be compared
  // pairwise.
  var set = null;
  var aValues = arrayFromSet(a);

  for (var i = 0; i < aValues.length; i++) {
    var val = aValues[i]; // Note: Checking for the objects first improves the performance for object
    // heavy sets but it is a minor slow down for primitives. As they are fast
    // to check this improves the worst case scenario instead.

    if (_typeof(val) === 'object' && val !== null) {
      if (set === null) {
        set = new Set();
      } // If the specified value doesn't exist in the second set its an not null
      // object (or non strict only: a not matching primitive) we'll need to go
      // hunting for something thats deep-(strict-)equal to it. To make this
      // O(n log n) complexity we have to copy these values in a new set first.


      set.add(val);
    } else if (!b.has(val)) {
      if (strict) return false; // Fast path to detect missing string, symbol, undefined and null values.

      if (!setMightHaveLoosePrim(a, b, val)) {
        return false;
      }

      if (set === null) {
        set = new Set();
      }

      set.add(val);
    }
  }

  if (set !== null) {
    var bValues = arrayFromSet(b);

    for (var _i = 0; _i < bValues.length; _i++) {
      var _val = bValues[_i]; // We have to check if a primitive value is already
      // matching and only if it's not, go hunting for it.

      if (_typeof(_val) === 'object' && _val !== null) {
        if (!setHasEqualElement(set, _val, strict, memo)) return false;
      } else if (!strict && !a.has(_val) && !setHasEqualElement(set, _val, strict, memo)) {
        return false;
      }
    }

    return set.size === 0;
  }

  return true;
}

function mapHasEqualEntry(set, map, key1, item1, strict, memo) {
  // To be able to handle cases like:
  //   Map([[{}, 'a'], [{}, 'b']]) vs Map([[{}, 'b'], [{}, 'a']])
  // ... we need to consider *all* matching keys, not just the first we find.
  var setValues = arrayFromSet(set);

  for (var i = 0; i < setValues.length; i++) {
    var key2 = setValues[i];

    if (innerDeepEqual(key1, key2, strict, memo) && innerDeepEqual(item1, map.get(key2), strict, memo)) {
      set.delete(key2);
      return true;
    }
  }

  return false;
}

function mapEquiv(a, b, strict, memo) {
  var set = null;
  var aEntries = arrayFromMap(a);

  for (var i = 0; i < aEntries.length; i++) {
    var _aEntries$i = _slicedToArray(aEntries[i], 2),
        key = _aEntries$i[0],
        item1 = _aEntries$i[1];

    if (_typeof(key) === 'object' && key !== null) {
      if (set === null) {
        set = new Set();
      }

      set.add(key);
    } else {
      // By directly retrieving the value we prevent another b.has(key) check in
      // almost all possible cases.
      var item2 = b.get(key);

      if (item2 === undefined && !b.has(key) || !innerDeepEqual(item1, item2, strict, memo)) {
        if (strict) return false; // Fast path to detect missing string, symbol, undefined and null
        // keys.

        if (!mapMightHaveLoosePrim(a, b, key, item1, memo)) return false;

        if (set === null) {
          set = new Set();
        }

        set.add(key);
      }
    }
  }

  if (set !== null) {
    var bEntries = arrayFromMap(b);

    for (var _i2 = 0; _i2 < bEntries.length; _i2++) {
      var _bEntries$_i = _slicedToArray(bEntries[_i2], 2),
          key = _bEntries$_i[0],
          item = _bEntries$_i[1];

      if (_typeof(key) === 'object' && key !== null) {
        if (!mapHasEqualEntry(set, a, key, item, strict, memo)) return false;
      } else if (!strict && (!a.has(key) || !innerDeepEqual(a.get(key), item, false, memo)) && !mapHasEqualEntry(set, a, key, item, false, memo)) {
        return false;
      }
    }

    return set.size === 0;
  }

  return true;
}

function objEquiv(a, b, strict, keys, memos, iterationType) {
  // Sets and maps don't have their entries accessible via normal object
  // properties.
  var i = 0;

  if (iterationType === kIsSet) {
    if (!setEquiv(a, b, strict, memos)) {
      return false;
    }
  } else if (iterationType === kIsMap) {
    if (!mapEquiv(a, b, strict, memos)) {
      return false;
    }
  } else if (iterationType === kIsArray) {
    for (; i < a.length; i++) {
      if (hasOwnProperty(a, i)) {
        if (!hasOwnProperty(b, i) || !innerDeepEqual(a[i], b[i], strict, memos)) {
          return false;
        }
      } else if (hasOwnProperty(b, i)) {
        return false;
      } else {
        // Array is sparse.
        var keysA = Object.keys(a);

        for (; i < keysA.length; i++) {
          var key = keysA[i];

          if (!hasOwnProperty(b, key) || !innerDeepEqual(a[key], b[key], strict, memos)) {
            return false;
          }
        }

        if (keysA.length !== Object.keys(b).length) {
          return false;
        }

        return true;
      }
    }
  } // The pair must have equivalent values for every corresponding key.
  // Possibly expensive deep test:


  for (i = 0; i < keys.length; i++) {
    var _key = keys[i];

    if (!innerDeepEqual(a[_key], b[_key], strict, memos)) {
      return false;
    }
  }

  return true;
}

function isDeepEqual(val1, val2) {
  return innerDeepEqual(val1, val2, kLoose);
}

function isDeepStrictEqual(val1, val2) {
  return innerDeepEqual(val1, val2, kStrict);
}

module.exports = {
  isDeepEqual: isDeepEqual,
  isDeepStrictEqual: isDeepStrictEqual
};

/***/ }),

/***/ "./node_modules/call-bind/callBound.js":
/*!*********************************************!*\
  !*** ./node_modules/call-bind/callBound.js ***!
  \*********************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";


var GetIntrinsic = __webpack_require__(/*! get-intrinsic */ "./node_modules/get-intrinsic/index.js");

var callBind = __webpack_require__(/*! ./ */ "./node_modules/call-bind/index.js");

var $indexOf = callBind(GetIntrinsic('String.prototype.indexOf'));

module.exports = function callBoundIntrinsic(name, allowMissing) {
  var intrinsic = GetIntrinsic(name, !!allowMissing);

  if (typeof intrinsic === 'function' && $indexOf(name, '.prototype.') > -1) {
    return callBind(intrinsic);
  }

  return intrinsic;
};

/***/ }),

/***/ "./node_modules/call-bind/index.js":
/*!*****************************************!*\
  !*** ./node_modules/call-bind/index.js ***!
  \*****************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";


var bind = __webpack_require__(/*! function-bind */ "./node_modules/function-bind/index.js");

var GetIntrinsic = __webpack_require__(/*! get-intrinsic */ "./node_modules/get-intrinsic/index.js");

var $apply = GetIntrinsic('%Function.prototype.apply%');
var $call = GetIntrinsic('%Function.prototype.call%');
var $reflectApply = GetIntrinsic('%Reflect.apply%', true) || bind.call($call, $apply);
var $gOPD = GetIntrinsic('%Object.getOwnPropertyDescriptor%', true);
var $defineProperty = GetIntrinsic('%Object.defineProperty%', true);
var $max = GetIntrinsic('%Math.max%');

if ($defineProperty) {
  try {
    $defineProperty({}, 'a', {
      value: 1
    });
  } catch (e) {
    // IE 8 has a broken defineProperty
    $defineProperty = null;
  }
}

module.exports = function callBind(originalFunction) {
  var func = $reflectApply(bind, $call, arguments);

  if ($gOPD && $defineProperty) {
    var desc = $gOPD(func, 'length');

    if (desc.configurable) {
      // original length, plus the receiver, minus any additional arguments (after the receiver)
      $defineProperty(func, 'length', {
        value: 1 + $max(0, originalFunction.length - (arguments.length - 1))
      });
    }
  }

  return func;
};

var applyBind = function applyBind() {
  return $reflectApply(bind, $apply, arguments);
};

if ($defineProperty) {
  $defineProperty(module.exports, 'apply', {
    value: applyBind
  });
} else {
  module.exports.apply = applyBind;
}

/***/ }),

/***/ "./node_modules/define-properties/index.js":
/*!*************************************************!*\
  !*** ./node_modules/define-properties/index.js ***!
  \*************************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";


var keys = __webpack_require__(/*! object-keys */ "./node_modules/object-keys/index.js");

var hasSymbols = typeof Symbol === 'function' && typeof Symbol('foo') === 'symbol';
var toStr = Object.prototype.toString;
var concat = Array.prototype.concat;
var origDefineProperty = Object.defineProperty;

var isFunction = function (fn) {
  return typeof fn === 'function' && toStr.call(fn) === '[object Function]';
};

var hasPropertyDescriptors = __webpack_require__(/*! has-property-descriptors */ "./node_modules/has-property-descriptors/index.js")();

var supportsDescriptors = origDefineProperty && hasPropertyDescriptors;

var defineProperty = function (object, name, value, predicate) {
  if (name in object && (!isFunction(predicate) || !predicate())) {
    return;
  }

  if (supportsDescriptors) {
    origDefineProperty(object, name, {
      configurable: true,
      enumerable: false,
      value: value,
      writable: true
    });
  } else {
    object[name] = value; // eslint-disable-line no-param-reassign
  }
};

var defineProperties = function (object, map) {
  var predicates = arguments.length > 2 ? arguments[2] : {};
  var props = keys(map);

  if (hasSymbols) {
    props = concat.call(props, Object.getOwnPropertySymbols(map));
  }

  for (var i = 0; i < props.length; i += 1) {
    defineProperty(object, props[i], map[props[i]], predicates[props[i]]);
  }
};

defineProperties.supportsDescriptors = !!supportsDescriptors;
module.exports = defineProperties;

/***/ }),

/***/ "./node_modules/es6-object-assign/index.js":
/*!*************************************************!*\
  !*** ./node_modules/es6-object-assign/index.js ***!
  \*************************************************/
/***/ (function(module) {

"use strict";
/**
 * Code refactored from Mozilla Developer Network:
 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
 */


function assign(target, firstSource) {
  if (target === undefined || target === null) {
    throw new TypeError('Cannot convert first argument to object');
  }

  var to = Object(target);

  for (var i = 1; i < arguments.length; i++) {
    var nextSource = arguments[i];

    if (nextSource === undefined || nextSource === null) {
      continue;
    }

    var keysArray = Object.keys(Object(nextSource));

    for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
      var nextKey = keysArray[nextIndex];
      var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);

      if (desc !== undefined && desc.enumerable) {
        to[nextKey] = nextSource[nextKey];
      }
    }
  }

  return to;
}

function polyfill() {
  if (!Object.assign) {
    Object.defineProperty(Object, 'assign', {
      enumerable: false,
      configurable: true,
      writable: true,
      value: assign
    });
  }
}

module.exports = {
  assign: assign,
  polyfill: polyfill
};

/***/ }),

/***/ "./node_modules/for-each/index.js":
/*!****************************************!*\
  !*** ./node_modules/for-each/index.js ***!
  \****************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";


var isCallable = __webpack_require__(/*! is-callable */ "./node_modules/is-callable/index.js");

var toStr = Object.prototype.toString;
var hasOwnProperty = Object.prototype.hasOwnProperty;

var forEachArray = function forEachArray(array, iterator, receiver) {
  for (var i = 0, len = array.length; i < len; i++) {
    if (hasOwnProperty.call(array, i)) {
      if (receiver == null) {
        iterator(array[i], i, array);
      } else {
        iterator.call(receiver, array[i], i, array);
      }
    }
  }
};

var forEachString = function forEachString(string, iterator, receiver) {
  for (var i = 0, len = string.length; i < len; i++) {
    // no such thing as a sparse string.
    if (receiver == null) {
      iterator(string.charAt(i), i, string);
    } else {
      iterator.call(receiver, string.charAt(i), i, string);
    }
  }
};

var forEachObject = function forEachObject(object, iterator, receiver) {
  for (var k in object) {
    if (hasOwnProperty.call(object, k)) {
      if (receiver == null) {
        iterator(object[k], k, object);
      } else {
        iterator.call(receiver, object[k], k, object);
      }
    }
  }
};

var forEach = function forEach(list, iterator, thisArg) {
  if (!isCallable(iterator)) {
    throw new TypeError('iterator must be a function');
  }

  var receiver;

  if (arguments.length >= 3) {
    receiver = thisArg;
  }

  if (toStr.call(list) === '[object Array]') {
    forEachArray(list, iterator, receiver);
  } else if (typeof list === 'string') {
    forEachString(list, iterator, receiver);
  } else {
    forEachObject(list, iterator, receiver);
  }
};

module.exports = forEach;

/***/ }),

/***/ "./node_modules/function-bind/implementation.js":
/*!******************************************************!*\
  !*** ./node_modules/function-bind/implementation.js ***!
  \******************************************************/
/***/ (function(module) {

"use strict";

/* eslint no-invalid-this: 1 */

var ERROR_MESSAGE = 'Function.prototype.bind called on incompatible ';
var slice = Array.prototype.slice;
var toStr = Object.prototype.toString;
var funcType = '[object Function]';

module.exports = function bind(that) {
  var target = this;

  if (typeof target !== 'function' || toStr.call(target) !== funcType) {
    throw new TypeError(ERROR_MESSAGE + target);
  }

  var args = slice.call(arguments, 1);
  var bound;

  var binder = function () {
    if (this instanceof bound) {
      var result = target.apply(this, args.concat(slice.call(arguments)));

      if (Object(result) === result) {
        return result;
      }

      return this;
    } else {
      return target.apply(that, args.concat(slice.call(arguments)));
    }
  };

  var boundLength = Math.max(0, target.length - args.length);
  var boundArgs = [];

  for (var i = 0; i < boundLength; i++) {
    boundArgs.push('$' + i);
  }

  bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this,arguments); }')(binder);

  if (target.prototype) {
    var Empty = function Empty() {};

    Empty.prototype = target.prototype;
    bound.prototype = new Empty();
    Empty.prototype = null;
  }

  return bound;
};

/***/ }),

/***/ "./node_modules/function-bind/index.js":
/*!*********************************************!*\
  !*** ./node_modules/function-bind/index.js ***!
  \*********************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";


var implementation = __webpack_require__(/*! ./implementation */ "./node_modules/function-bind/implementation.js");

module.exports = Function.prototype.bind || implementation;

/***/ }),

/***/ "./node_modules/get-intrinsic/index.js":
/*!*********************************************!*\
  !*** ./node_modules/get-intrinsic/index.js ***!
  \*********************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";


var undefined;
var $SyntaxError = SyntaxError;
var $Function = Function;
var $TypeError = TypeError; // eslint-disable-next-line consistent-return

var getEvalledConstructor = function (expressionSyntax) {
  try {
    return $Function('"use strict"; return (' + expressionSyntax + ').constructor;')();
  } catch (e) {}
};

var $gOPD = Object.getOwnPropertyDescriptor;

if ($gOPD) {
  try {
    $gOPD({}, '');
  } catch (e) {
    $gOPD = null; // this is IE 8, which has a broken gOPD
  }
}

var throwTypeError = function () {
  throw new $TypeError();
};

var ThrowTypeError = $gOPD ? function () {
  try {
    // eslint-disable-next-line no-unused-expressions, no-caller, no-restricted-properties
    arguments.callee; // IE 8 does not throw here

    return throwTypeError;
  } catch (calleeThrows) {
    try {
      // IE 8 throws on Object.getOwnPropertyDescriptor(arguments, '')
      return $gOPD(arguments, 'callee').get;
    } catch (gOPDthrows) {
      return throwTypeError;
    }
  }
}() : throwTypeError;

var hasSymbols = __webpack_require__(/*! has-symbols */ "./node_modules/has-symbols/index.js")();

var getProto = Object.getPrototypeOf || function (x) {
  return x.__proto__;
}; // eslint-disable-line no-proto


var needsEval = {};
var TypedArray = typeof Uint8Array === 'undefined' ? undefined : getProto(Uint8Array);
var INTRINSICS = {
  '%AggregateError%': typeof AggregateError === 'undefined' ? undefined : AggregateError,
  '%Array%': Array,
  '%ArrayBuffer%': typeof ArrayBuffer === 'undefined' ? undefined : ArrayBuffer,
  '%ArrayIteratorPrototype%': hasSymbols ? getProto([][Symbol.iterator]()) : undefined,
  '%AsyncFromSyncIteratorPrototype%': undefined,
  '%AsyncFunction%': needsEval,
  '%AsyncGenerator%': needsEval,
  '%AsyncGeneratorFunction%': needsEval,
  '%AsyncIteratorPrototype%': needsEval,
  '%Atomics%': typeof Atomics === 'undefined' ? undefined : Atomics,
  '%BigInt%': typeof BigInt === 'undefined' ? undefined : BigInt,
  '%Boolean%': Boolean,
  '%DataView%': typeof DataView === 'undefined' ? undefined : DataView,
  '%Date%': Date,
  '%decodeURI%': decodeURI,
  '%decodeURIComponent%': decodeURIComponent,
  '%encodeURI%': encodeURI,
  '%encodeURIComponent%': encodeURIComponent,
  '%Error%': Error,
  '%eval%': eval,
  // eslint-disable-line no-eval
  '%EvalError%': EvalError,
  '%Float32Array%': typeof Float32Array === 'undefined' ? undefined : Float32Array,
  '%Float64Array%': typeof Float64Array === 'undefined' ? undefined : Float64Array,
  '%FinalizationRegistry%': typeof FinalizationRegistry === 'undefined' ? undefined : FinalizationRegistry,
  '%Function%': $Function,
  '%GeneratorFunction%': needsEval,
  '%Int8Array%': typeof Int8Array === 'undefined' ? undefined : Int8Array,
  '%Int16Array%': typeof Int16Array === 'undefined' ? undefined : Int16Array,
  '%Int32Array%': typeof Int32Array === 'undefined' ? undefined : Int32Array,
  '%isFinite%': isFinite,
  '%isNaN%': isNaN,
  '%IteratorPrototype%': hasSymbols ? getProto(getProto([][Symbol.iterator]())) : undefined,
  '%JSON%': typeof JSON === 'object' ? JSON : undefined,
  '%Map%': typeof Map === 'undefined' ? undefined : Map,
  '%MapIteratorPrototype%': typeof Map === 'undefined' || !hasSymbols ? undefined : getProto(new Map()[Symbol.iterator]()),
  '%Math%': Math,
  '%Number%': Number,
  '%Object%': Object,
  '%parseFloat%': parseFloat,
  '%parseInt%': parseInt,
  '%Promise%': typeof Promise === 'undefined' ? undefined : Promise,
  '%Proxy%': typeof Proxy === 'undefined' ? undefined : Proxy,
  '%RangeError%': RangeError,
  '%ReferenceError%': ReferenceError,
  '%Reflect%': typeof Reflect === 'undefined' ? undefined : Reflect,
  '%RegExp%': RegExp,
  '%Set%': typeof Set === 'undefined' ? undefined : Set,
  '%SetIteratorPrototype%': typeof Set === 'undefined' || !hasSymbols ? undefined : getProto(new Set()[Symbol.iterator]()),
  '%SharedArrayBuffer%': typeof SharedArrayBuffer === 'undefined' ? undefined : SharedArrayBuffer,
  '%String%': String,
  '%StringIteratorPrototype%': hasSymbols ? getProto(''[Symbol.iterator]()) : undefined,
  '%Symbol%': hasSymbols ? Symbol : undefined,
  '%SyntaxError%': $SyntaxError,
  '%ThrowTypeError%': ThrowTypeError,
  '%TypedArray%': TypedArray,
  '%TypeError%': $TypeError,
  '%Uint8Array%': typeof Uint8Array === 'undefined' ? undefined : Uint8Array,
  '%Uint8ClampedArray%': typeof Uint8ClampedArray === 'undefined' ? undefined : Uint8ClampedArray,
  '%Uint16Array%': typeof Uint16Array === 'undefined' ? undefined : Uint16Array,
  '%Uint32Array%': typeof Uint32Array === 'undefined' ? undefined : Uint32Array,
  '%URIError%': URIError,
  '%WeakMap%': typeof WeakMap === 'undefined' ? undefined : WeakMap,
  '%WeakRef%': typeof WeakRef === 'undefined' ? undefined : WeakRef,
  '%WeakSet%': typeof WeakSet === 'undefined' ? undefined : WeakSet
};

var doEval = function doEval(name) {
  var value;

  if (name === '%AsyncFunction%') {
    value = getEvalledConstructor('async function () {}');
  } else if (name === '%GeneratorFunction%') {
    value = getEvalledConstructor('function* () {}');
  } else if (name === '%AsyncGeneratorFunction%') {
    value = getEvalledConstructor('async function* () {}');
  } else if (name === '%AsyncGenerator%') {
    var fn = doEval('%AsyncGeneratorFunction%');

    if (fn) {
      value = fn.prototype;
    }
  } else if (name === '%AsyncIteratorPrototype%') {
    var gen = doEval('%AsyncGenerator%');

    if (gen) {
      value = getProto(gen.prototype);
    }
  }

  INTRINSICS[name] = value;
  return value;
};

var LEGACY_ALIASES = {
  '%ArrayBufferPrototype%': ['ArrayBuffer', 'prototype'],
  '%ArrayPrototype%': ['Array', 'prototype'],
  '%ArrayProto_entries%': ['Array', 'prototype', 'entries'],
  '%ArrayProto_forEach%': ['Array', 'prototype', 'forEach'],
  '%ArrayProto_keys%': ['Array', 'prototype', 'keys'],
  '%ArrayProto_values%': ['Array', 'prototype', 'values'],
  '%AsyncFunctionPrototype%': ['AsyncFunction', 'prototype'],
  '%AsyncGenerator%': ['AsyncGeneratorFunction', 'prototype'],
  '%AsyncGeneratorPrototype%': ['AsyncGeneratorFunction', 'prototype', 'prototype'],
  '%BooleanPrototype%': ['Boolean', 'prototype'],
  '%DataViewPrototype%': ['DataView', 'prototype'],
  '%DatePrototype%': ['Date', 'prototype'],
  '%ErrorPrototype%': ['Error', 'prototype'],
  '%EvalErrorPrototype%': ['EvalError', 'prototype'],
  '%Float32ArrayPrototype%': ['Float32Array', 'prototype'],
  '%Float64ArrayPrototype%': ['Float64Array', 'prototype'],
  '%FunctionPrototype%': ['Function', 'prototype'],
  '%Generator%': ['GeneratorFunction', 'prototype'],
  '%GeneratorPrototype%': ['GeneratorFunction', 'prototype', 'prototype'],
  '%Int8ArrayPrototype%': ['Int8Array', 'prototype'],
  '%Int16ArrayPrototype%': ['Int16Array', 'prototype'],
  '%Int32ArrayPrototype%': ['Int32Array', 'prototype'],
  '%JSONParse%': ['JSON', 'parse'],
  '%JSONStringify%': ['JSON', 'stringify'],
  '%MapPrototype%': ['Map', 'prototype'],
  '%NumberPrototype%': ['Number', 'prototype'],
  '%ObjectPrototype%': ['Object', 'prototype'],
  '%ObjProto_toString%': ['Object', 'prototype', 'toString'],
  '%ObjProto_valueOf%': ['Object', 'prototype', 'valueOf'],
  '%PromisePrototype%': ['Promise', 'prototype'],
  '%PromiseProto_then%': ['Promise', 'prototype', 'then'],
  '%Promise_all%': ['Promise', 'all'],
  '%Promise_reject%': ['Promise', 'reject'],
  '%Promise_resolve%': ['Promise', 'resolve'],
  '%RangeErrorPrototype%': ['RangeError', 'prototype'],
  '%ReferenceErrorPrototype%': ['ReferenceError', 'prototype'],
  '%RegExpPrototype%': ['RegExp', 'prototype'],
  '%SetPrototype%': ['Set', 'prototype'],
  '%SharedArrayBufferPrototype%': ['SharedArrayBuffer', 'prototype'],
  '%StringPrototype%': ['String', 'prototype'],
  '%SymbolPrototype%': ['Symbol', 'prototype'],
  '%SyntaxErrorPrototype%': ['SyntaxError', 'prototype'],
  '%TypedArrayPrototype%': ['TypedArray', 'prototype'],
  '%TypeErrorPrototype%': ['TypeError', 'prototype'],
  '%Uint8ArrayPrototype%': ['Uint8Array', 'prototype'],
  '%Uint8ClampedArrayPrototype%': ['Uint8ClampedArray', 'prototype'],
  '%Uint16ArrayPrototype%': ['Uint16Array', 'prototype'],
  '%Uint32ArrayPrototype%': ['Uint32Array', 'prototype'],
  '%URIErrorPrototype%': ['URIError', 'prototype'],
  '%WeakMapPrototype%': ['WeakMap', 'prototype'],
  '%WeakSetPrototype%': ['WeakSet', 'prototype']
};

var bind = __webpack_require__(/*! function-bind */ "./node_modules/function-bind/index.js");

var hasOwn = __webpack_require__(/*! has */ "./node_modules/has/src/index.js");

var $concat = bind.call(Function.call, Array.prototype.concat);
var $spliceApply = bind.call(Function.apply, Array.prototype.splice);
var $replace = bind.call(Function.call, String.prototype.replace);
var $strSlice = bind.call(Function.call, String.prototype.slice);
var $exec = bind.call(Function.call, RegExp.prototype.exec);
/* adapted from https://github.com/lodash/lodash/blob/4.17.15/dist/lodash.js#L6735-L6744 */

var rePropName = /[^%.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|%$))/g;
var reEscapeChar = /\\(\\)?/g;
/** Used to match backslashes in property paths. */

var stringToPath = function stringToPath(string) {
  var first = $strSlice(string, 0, 1);
  var last = $strSlice(string, -1);

  if (first === '%' && last !== '%') {
    throw new $SyntaxError('invalid intrinsic syntax, expected closing `%`');
  } else if (last === '%' && first !== '%') {
    throw new $SyntaxError('invalid intrinsic syntax, expected opening `%`');
  }

  var result = [];
  $replace(string, rePropName, function (match, number, quote, subString) {
    result[result.length] = quote ? $replace(subString, reEscapeChar, '$1') : number || match;
  });
  return result;
};
/* end adaptation */


var getBaseIntrinsic = function getBaseIntrinsic(name, allowMissing) {
  var intrinsicName = name;
  var alias;

  if (hasOwn(LEGACY_ALIASES, intrinsicName)) {
    alias = LEGACY_ALIASES[intrinsicName];
    intrinsicName = '%' + alias[0] + '%';
  }

  if (hasOwn(INTRINSICS, intrinsicName)) {
    var value = INTRINSICS[intrinsicName];

    if (value === needsEval) {
      value = doEval(intrinsicName);
    }

    if (typeof value === 'undefined' && !allowMissing) {
      throw new $TypeError('intrinsic ' + name + ' exists, but is not available. Please file an issue!');
    }

    return {
      alias: alias,
      name: intrinsicName,
      value: value
    };
  }

  throw new $SyntaxError('intrinsic ' + name + ' does not exist!');
};

module.exports = function GetIntrinsic(name, allowMissing) {
  if (typeof name !== 'string' || name.length === 0) {
    throw new $TypeError('intrinsic name must be a non-empty string');
  }

  if (arguments.length > 1 && typeof allowMissing !== 'boolean') {
    throw new $TypeError('"allowMissing" argument must be a boolean');
  }

  if ($exec(/^%?[^%]*%?$/g, name) === null) {
    throw new $SyntaxError('`%` may not be present anywhere but at the beginning and end of the intrinsic name');
  }

  var parts = stringToPath(name);
  var intrinsicBaseName = parts.length > 0 ? parts[0] : '';
  var intrinsic = getBaseIntrinsic('%' + intrinsicBaseName + '%', allowMissing);
  var intrinsicRealName = intrinsic.name;
  var value = intrinsic.value;
  var skipFurtherCaching = false;
  var alias = intrinsic.alias;

  if (alias) {
    intrinsicBaseName = alias[0];
    $spliceApply(parts, $concat([0, 1], alias));
  }

  for (var i = 1, isOwn = true; i < parts.length; i += 1) {
    var part = parts[i];
    var first = $strSlice(part, 0, 1);
    var last = $strSlice(part, -1);

    if ((first === '"' || first === "'" || first === '`' || last === '"' || last === "'" || last === '`') && first !== last) {
      throw new $SyntaxError('property names with quotes must have matching quotes');
    }

    if (part === 'constructor' || !isOwn) {
      skipFurtherCaching = true;
    }

    intrinsicBaseName += '.' + part;
    intrinsicRealName = '%' + intrinsicBaseName + '%';

    if (hasOwn(INTRINSICS, intrinsicRealName)) {
      value = INTRINSICS[intrinsicRealName];
    } else if (value != null) {
      if (!(part in value)) {
        if (!allowMissing) {
          throw new $TypeError('base intrinsic for ' + name + ' exists, but the property is not available.');
        }

        return void undefined;
      }

      if ($gOPD && i + 1 >= parts.length) {
        var desc = $gOPD(value, part);
        isOwn = !!desc; // By convention, when a data property is converted to an accessor
        // property to emulate a data property that does not suffer from
        // the override mistake, that accessor's getter is marked with
        // an `originalValue` property. Here, when we detect this, we
        // uphold the illusion by pretending to see that original data
        // property, i.e., returning the value rather than the getter
        // itself.

        if (isOwn && 'get' in desc && !('originalValue' in desc.get)) {
          value = desc.get;
        } else {
          value = value[part];
        }
      } else {
        isOwn = hasOwn(value, part);
        value = value[part];
      }

      if (isOwn && !skipFurtherCaching) {
        INTRINSICS[intrinsicRealName] = value;
      }
    }
  }

  return value;
};

/***/ }),

/***/ "./node_modules/has-property-descriptors/index.js":
/*!********************************************************!*\
  !*** ./node_modules/has-property-descriptors/index.js ***!
  \********************************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";


var GetIntrinsic = __webpack_require__(/*! get-intrinsic */ "./node_modules/get-intrinsic/index.js");

var $defineProperty = GetIntrinsic('%Object.defineProperty%', true);

var hasPropertyDescriptors = function hasPropertyDescriptors() {
  if ($defineProperty) {
    try {
      $defineProperty({}, 'a', {
        value: 1
      });
      return true;
    } catch (e) {
      // IE 8 has a broken defineProperty
      return false;
    }
  }

  return false;
};

hasPropertyDescriptors.hasArrayLengthDefineBug = function hasArrayLengthDefineBug() {
  // node v0.6 has a bug where array lengths can be Set but not Defined
  if (!hasPropertyDescriptors()) {
    return null;
  }

  try {
    return $defineProperty([], 'length', {
      value: 1
    }).length !== 1;
  } catch (e) {
    // In Firefox 4-22, defining length on an array throws an exception.
    return true;
  }
};

module.exports = hasPropertyDescriptors;

/***/ }),

/***/ "./node_modules/has-symbols/index.js":
/*!*******************************************!*\
  !*** ./node_modules/has-symbols/index.js ***!
  \*******************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";


var origSymbol = typeof Symbol !== 'undefined' && Symbol;

var hasSymbolSham = __webpack_require__(/*! ./shams */ "./node_modules/has-symbols/shams.js");

module.exports = function hasNativeSymbols() {
  if (typeof origSymbol !== 'function') {
    return false;
  }

  if (typeof Symbol !== 'function') {
    return false;
  }

  if (typeof origSymbol('foo') !== 'symbol') {
    return false;
  }

  if (typeof Symbol('bar') !== 'symbol') {
    return false;
  }

  return hasSymbolSham();
};

/***/ }),

/***/ "./node_modules/has-symbols/shams.js":
/*!*******************************************!*\
  !*** ./node_modules/has-symbols/shams.js ***!
  \*******************************************/
/***/ (function(module) {

"use strict";

/* eslint complexity: [2, 18], max-statements: [2, 33] */

module.exports = function hasSymbols() {
  if (typeof Symbol !== 'function' || typeof Object.getOwnPropertySymbols !== 'function') {
    return false;
  }

  if (typeof Symbol.iterator === 'symbol') {
    return true;
  }

  var obj = {};
  var sym = Symbol('test');
  var symObj = Object(sym);

  if (typeof sym === 'string') {
    return false;
  }

  if (Object.prototype.toString.call(sym) !== '[object Symbol]') {
    return false;
  }

  if (Object.prototype.toString.call(symObj) !== '[object Symbol]') {
    return false;
  } // temp disabled per https://github.com/ljharb/object.assign/issues/17
  // if (sym instanceof Symbol) { return false; }
  // temp disabled per https://github.com/WebReflection/get-own-property-symbols/issues/4
  // if (!(symObj instanceof Symbol)) { return false; }
  // if (typeof Symbol.prototype.toString !== 'function') { return false; }
  // if (String(sym) !== Symbol.prototype.toString.call(sym)) { return false; }


  var symVal = 42;
  obj[sym] = symVal;

  for (sym in obj) {
    return false;
  } // eslint-disable-line no-restricted-syntax, no-unreachable-loop


  if (typeof Object.keys === 'function' && Object.keys(obj).length !== 0) {
    return false;
  }

  if (typeof Object.getOwnPropertyNames === 'function' && Object.getOwnPropertyNames(obj).length !== 0) {
    return false;
  }

  var syms = Object.getOwnPropertySymbols(obj);

  if (syms.length !== 1 || syms[0] !== sym) {
    return false;
  }

  if (!Object.prototype.propertyIsEnumerable.call(obj, sym)) {
    return false;
  }

  if (typeof Object.getOwnPropertyDescriptor === 'function') {
    var descriptor = Object.getOwnPropertyDescriptor(obj, sym);

    if (descriptor.value !== symVal || descriptor.enumerable !== true) {
      return false;
    }
  }

  return true;
};

/***/ }),

/***/ "./node_modules/has-tostringtag/shams.js":
/*!***********************************************!*\
  !*** ./node_modules/has-tostringtag/shams.js ***!
  \***********************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";


var hasSymbols = __webpack_require__(/*! has-symbols/shams */ "./node_modules/has-symbols/shams.js");

module.exports = function hasToStringTagShams() {
  return hasSymbols() && !!Symbol.toStringTag;
};

/***/ }),

/***/ "./node_modules/has/src/index.js":
/*!***************************************!*\
  !*** ./node_modules/has/src/index.js ***!
  \***************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";


var bind = __webpack_require__(/*! function-bind */ "./node_modules/function-bind/index.js");

module.exports = bind.call(Function.call, Object.prototype.hasOwnProperty);

/***/ }),

/***/ "./node_modules/inherits/inherits.js":
/*!*******************************************!*\
  !*** ./node_modules/inherits/inherits.js ***!
  \*******************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

try {
  var util = __webpack_require__(/*! util */ "./node_modules/util/util.js");
  /* istanbul ignore next */


  if (typeof util.inherits !== 'function') throw '';
  module.exports = util.inherits;
} catch (e) {
  /* istanbul ignore next */
  module.exports = __webpack_require__(/*! ./inherits_browser.js */ "./node_modules/inherits/inherits_browser.js");
}

/***/ }),

/***/ "./node_modules/inherits/inherits_browser.js":
/*!***************************************************!*\
  !*** ./node_modules/inherits/inherits_browser.js ***!
  \***************************************************/
/***/ (function(module) {

if (typeof Object.create === 'function') {
  // implementation from standard node.js 'util' module
  module.exports = function inherits(ctor, superCtor) {
    if (superCtor) {
      ctor.super_ = superCtor;
      ctor.prototype = Object.create(superCtor.prototype, {
        constructor: {
          value: ctor,
          enumerable: false,
          writable: true,
          configurable: true
        }
      });
    }
  };
} else {
  // old school shim for old browsers
  module.exports = function inherits(ctor, superCtor) {
    if (superCtor) {
      ctor.super_ = superCtor;

      var TempCtor = function () {};

      TempCtor.prototype = superCtor.prototype;
      ctor.prototype = new TempCtor();
      ctor.prototype.constructor = ctor;
    }
  };
}

/***/ }),

/***/ "./node_modules/is-arguments/index.js":
/*!********************************************!*\
  !*** ./node_modules/is-arguments/index.js ***!
  \********************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";


var hasToStringTag = __webpack_require__(/*! has-tostringtag/shams */ "./node_modules/has-tostringtag/shams.js")();

var callBound = __webpack_require__(/*! call-bind/callBound */ "./node_modules/call-bind/callBound.js");

var $toString = callBound('Object.prototype.toString');

var isStandardArguments = function isArguments(value) {
  if (hasToStringTag && value && typeof value === 'object' && Symbol.toStringTag in value) {
    return false;
  }

  return $toString(value) === '[object Arguments]';
};

var isLegacyArguments = function isArguments(value) {
  if (isStandardArguments(value)) {
    return true;
  }

  return value !== null && typeof value === 'object' && typeof value.length === 'number' && value.length >= 0 && $toString(value) !== '[object Array]' && $toString(value.callee) === '[object Function]';
};

var supportsStandardArguments = function () {
  return isStandardArguments(arguments);
}();

isStandardArguments.isLegacyArguments = isLegacyArguments; // for tests

module.exports = supportsStandardArguments ? isStandardArguments : isLegacyArguments;

/***/ }),

/***/ "./node_modules/is-callable/index.js":
/*!*******************************************!*\
  !*** ./node_modules/is-callable/index.js ***!
  \*******************************************/
/***/ (function(module) {

"use strict";


var fnToStr = Function.prototype.toString;
var reflectApply = typeof Reflect === 'object' && Reflect !== null && Reflect.apply;
var badArrayLike;
var isCallableMarker;

if (typeof reflectApply === 'function' && typeof Object.defineProperty === 'function') {
  try {
    badArrayLike = Object.defineProperty({}, 'length', {
      get: function () {
        throw isCallableMarker;
      }
    });
    isCallableMarker = {}; // eslint-disable-next-line no-throw-literal

    reflectApply(function () {
      throw 42;
    }, null, badArrayLike);
  } catch (_) {
    if (_ !== isCallableMarker) {
      reflectApply = null;
    }
  }
} else {
  reflectApply = null;
}

var constructorRegex = /^\s*class\b/;

var isES6ClassFn = function isES6ClassFunction(value) {
  try {
    var fnStr = fnToStr.call(value);
    return constructorRegex.test(fnStr);
  } catch (e) {
    return false; // not a function
  }
};

var tryFunctionObject = function tryFunctionToStr(value) {
  try {
    if (isES6ClassFn(value)) {
      return false;
    }

    fnToStr.call(value);
    return true;
  } catch (e) {
    return false;
  }
};

var toStr = Object.prototype.toString;
var fnClass = '[object Function]';
var genClass = '[object GeneratorFunction]';
var hasToStringTag = typeof Symbol === 'function' && !!Symbol.toStringTag; // better: use `has-tostringtag`

/* globals document: false */

var documentDotAll = typeof document === 'object' && typeof document.all === 'undefined' && document.all !== undefined ? document.all : {};
module.exports = reflectApply ? function isCallable(value) {
  if (value === documentDotAll) {
    return true;
  }

  if (!value) {
    return false;
  }

  if (typeof value !== 'function' && typeof value !== 'object') {
    return false;
  }

  if (typeof value === 'function' && !value.prototype) {
    return true;
  }

  try {
    reflectApply(value, null, badArrayLike);
  } catch (e) {
    if (e !== isCallableMarker) {
      return false;
    }
  }

  return !isES6ClassFn(value);
} : function isCallable(value) {
  if (value === documentDotAll) {
    return true;
  }

  if (!value) {
    return false;
  }

  if (typeof value !== 'function' && typeof value !== 'object') {
    return false;
  }

  if (typeof value === 'function' && !value.prototype) {
    return true;
  }

  if (hasToStringTag) {
    return tryFunctionObject(value);
  }

  if (isES6ClassFn(value)) {
    return false;
  }

  var strClass = toStr.call(value);
  return strClass === fnClass || strClass === genClass;
};

/***/ }),

/***/ "./node_modules/is-generator-function/index.js":
/*!*****************************************************!*\
  !*** ./node_modules/is-generator-function/index.js ***!
  \*****************************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";


var toStr = Object.prototype.toString;
var fnToStr = Function.prototype.toString;
var isFnRegex = /^\s*(?:function)?\*/;

var hasToStringTag = __webpack_require__(/*! has-tostringtag/shams */ "./node_modules/has-tostringtag/shams.js")();

var getProto = Object.getPrototypeOf;

var getGeneratorFunc = function () {
  // eslint-disable-line consistent-return
  if (!hasToStringTag) {
    return false;
  }

  try {
    return Function('return function*() {}')();
  } catch (e) {}
};

var GeneratorFunction;

module.exports = function isGeneratorFunction(fn) {
  if (typeof fn !== 'function') {
    return false;
  }

  if (isFnRegex.test(fnToStr.call(fn))) {
    return true;
  }

  if (!hasToStringTag) {
    var str = toStr.call(fn);
    return str === '[object GeneratorFunction]';
  }

  if (!getProto) {
    return false;
  }

  if (typeof GeneratorFunction === 'undefined') {
    var generatorFunc = getGeneratorFunc();
    GeneratorFunction = generatorFunc ? getProto(generatorFunc) : false;
  }

  return getProto(fn) === GeneratorFunction;
};

/***/ }),

/***/ "./node_modules/is-nan/implementation.js":
/*!***********************************************!*\
  !*** ./node_modules/is-nan/implementation.js ***!
  \***********************************************/
/***/ (function(module) {

"use strict";

/* http://www.ecma-international.org/ecma-262/6.0/#sec-number.isnan */

module.exports = function isNaN(value) {
  return value !== value;
};

/***/ }),

/***/ "./node_modules/is-nan/index.js":
/*!**************************************!*\
  !*** ./node_modules/is-nan/index.js ***!
  \**************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";


var callBind = __webpack_require__(/*! call-bind */ "./node_modules/call-bind/index.js");

var define = __webpack_require__(/*! define-properties */ "./node_modules/define-properties/index.js");

var implementation = __webpack_require__(/*! ./implementation */ "./node_modules/is-nan/implementation.js");

var getPolyfill = __webpack_require__(/*! ./polyfill */ "./node_modules/is-nan/polyfill.js");

var shim = __webpack_require__(/*! ./shim */ "./node_modules/is-nan/shim.js");

var polyfill = callBind(getPolyfill(), Number);
/* http://www.ecma-international.org/ecma-262/6.0/#sec-number.isnan */

define(polyfill, {
  getPolyfill: getPolyfill,
  implementation: implementation,
  shim: shim
});
module.exports = polyfill;

/***/ }),

/***/ "./node_modules/is-nan/polyfill.js":
/*!*****************************************!*\
  !*** ./node_modules/is-nan/polyfill.js ***!
  \*****************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";


var implementation = __webpack_require__(/*! ./implementation */ "./node_modules/is-nan/implementation.js");

module.exports = function getPolyfill() {
  if (Number.isNaN && Number.isNaN(NaN) && !Number.isNaN('a')) {
    return Number.isNaN;
  }

  return implementation;
};

/***/ }),

/***/ "./node_modules/is-nan/shim.js":
/*!*************************************!*\
  !*** ./node_modules/is-nan/shim.js ***!
  \*************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";


var define = __webpack_require__(/*! define-properties */ "./node_modules/define-properties/index.js");

var getPolyfill = __webpack_require__(/*! ./polyfill */ "./node_modules/is-nan/polyfill.js");
/* http://www.ecma-international.org/ecma-262/6.0/#sec-number.isnan */


module.exports = function shimNumberIsNaN() {
  var polyfill = getPolyfill();
  define(Number, {
    isNaN: polyfill
  }, {
    isNaN: function testIsNaN() {
      return Number.isNaN !== polyfill;
    }
  });
  return polyfill;
};

/***/ }),

/***/ "./node_modules/is-typed-array/index.js":
/*!**********************************************!*\
  !*** ./node_modules/is-typed-array/index.js ***!
  \**********************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";


var forEach = __webpack_require__(/*! for-each */ "./node_modules/for-each/index.js");

var availableTypedArrays = __webpack_require__(/*! available-typed-arrays */ "./node_modules/available-typed-arrays/index.js");

var callBound = __webpack_require__(/*! call-bind/callBound */ "./node_modules/call-bind/callBound.js");

var $toString = callBound('Object.prototype.toString');

var hasToStringTag = __webpack_require__(/*! has-tostringtag/shams */ "./node_modules/has-tostringtag/shams.js")();

var g = typeof globalThis === 'undefined' ? __webpack_require__.g : globalThis;
var typedArrays = availableTypedArrays();

var $indexOf = callBound('Array.prototype.indexOf', true) || function indexOf(array, value) {
  for (var i = 0; i < array.length; i += 1) {
    if (array[i] === value) {
      return i;
    }
  }

  return -1;
};

var $slice = callBound('String.prototype.slice');
var toStrTags = {};

var gOPD = __webpack_require__(/*! es-abstract/helpers/getOwnPropertyDescriptor */ "./node_modules/es-abstract/helpers/getOwnPropertyDescriptor.js");

var getPrototypeOf = Object.getPrototypeOf; // require('getprototypeof');

if (hasToStringTag && gOPD && getPrototypeOf) {
  forEach(typedArrays, function (typedArray) {
    var arr = new g[typedArray]();

    if (Symbol.toStringTag in arr) {
      var proto = getPrototypeOf(arr);
      var descriptor = gOPD(proto, Symbol.toStringTag);

      if (!descriptor) {
        var superProto = getPrototypeOf(proto);
        descriptor = gOPD(superProto, Symbol.toStringTag);
      }

      toStrTags[typedArray] = descriptor.get;
    }
  });
}

var tryTypedArrays = function tryAllTypedArrays(value) {
  var anyTrue = false;
  forEach(toStrTags, function (getter, typedArray) {
    if (!anyTrue) {
      try {
        anyTrue = getter.call(value) === typedArray;
      } catch (e) {
        /**/
      }
    }
  });
  return anyTrue;
};

module.exports = function isTypedArray(value) {
  if (!value || typeof value !== 'object') {
    return false;
  }

  if (!hasToStringTag || !(Symbol.toStringTag in value)) {
    var tag = $slice($toString(value), 8, -1);
    return $indexOf(typedArrays, tag) > -1;
  }

  if (!gOPD) {
    return false;
  }

  return tryTypedArrays(value);
};

/***/ }),

/***/ "./node_modules/object-is/implementation.js":
/*!**************************************************!*\
  !*** ./node_modules/object-is/implementation.js ***!
  \**************************************************/
/***/ (function(module) {

"use strict";


var numberIsNaN = function (value) {
  return value !== value;
};

module.exports = function is(a, b) {
  if (a === 0 && b === 0) {
    return 1 / a === 1 / b;
  }

  if (a === b) {
    return true;
  }

  if (numberIsNaN(a) && numberIsNaN(b)) {
    return true;
  }

  return false;
};

/***/ }),

/***/ "./node_modules/object-is/index.js":
/*!*****************************************!*\
  !*** ./node_modules/object-is/index.js ***!
  \*****************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";


var define = __webpack_require__(/*! define-properties */ "./node_modules/define-properties/index.js");

var callBind = __webpack_require__(/*! call-bind */ "./node_modules/call-bind/index.js");

var implementation = __webpack_require__(/*! ./implementation */ "./node_modules/object-is/implementation.js");

var getPolyfill = __webpack_require__(/*! ./polyfill */ "./node_modules/object-is/polyfill.js");

var shim = __webpack_require__(/*! ./shim */ "./node_modules/object-is/shim.js");

var polyfill = callBind(getPolyfill(), Object);
define(polyfill, {
  getPolyfill: getPolyfill,
  implementation: implementation,
  shim: shim
});
module.exports = polyfill;

/***/ }),

/***/ "./node_modules/object-is/polyfill.js":
/*!********************************************!*\
  !*** ./node_modules/object-is/polyfill.js ***!
  \********************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";


var implementation = __webpack_require__(/*! ./implementation */ "./node_modules/object-is/implementation.js");

module.exports = function getPolyfill() {
  return typeof Object.is === 'function' ? Object.is : implementation;
};

/***/ }),

/***/ "./node_modules/object-is/shim.js":
/*!****************************************!*\
  !*** ./node_modules/object-is/shim.js ***!
  \****************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";


var getPolyfill = __webpack_require__(/*! ./polyfill */ "./node_modules/object-is/polyfill.js");

var define = __webpack_require__(/*! define-properties */ "./node_modules/define-properties/index.js");

module.exports = function shimObjectIs() {
  var polyfill = getPolyfill();
  define(Object, {
    is: polyfill
  }, {
    is: function testObjectIs() {
      return Object.is !== polyfill;
    }
  });
  return polyfill;
};

/***/ }),

/***/ "./node_modules/object-keys/implementation.js":
/*!****************************************************!*\
  !*** ./node_modules/object-keys/implementation.js ***!
  \****************************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";


var keysShim;

if (!Object.keys) {
  // modified from https://github.com/es-shims/es5-shim
  var has = Object.prototype.hasOwnProperty;
  var toStr = Object.prototype.toString;

  var isArgs = __webpack_require__(/*! ./isArguments */ "./node_modules/object-keys/isArguments.js"); // eslint-disable-line global-require


  var isEnumerable = Object.prototype.propertyIsEnumerable;
  var hasDontEnumBug = !isEnumerable.call({
    toString: null
  }, 'toString');
  var hasProtoEnumBug = isEnumerable.call(function () {}, 'prototype');
  var dontEnums = ['toString', 'toLocaleString', 'valueOf', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'constructor'];

  var equalsConstructorPrototype = function (o) {
    var ctor = o.constructor;
    return ctor && ctor.prototype === o;
  };

  var excludedKeys = {
    $applicationCache: true,
    $console: true,
    $external: true,
    $frame: true,
    $frameElement: true,
    $frames: true,
    $innerHeight: true,
    $innerWidth: true,
    $onmozfullscreenchange: true,
    $onmozfullscreenerror: true,
    $outerHeight: true,
    $outerWidth: true,
    $pageXOffset: true,
    $pageYOffset: true,
    $parent: true,
    $scrollLeft: true,
    $scrollTop: true,
    $scrollX: true,
    $scrollY: true,
    $self: true,
    $webkitIndexedDB: true,
    $webkitStorageInfo: true,
    $window: true
  };

  var hasAutomationEqualityBug = function () {
    /* global window */
    if (typeof window === 'undefined') {
      return false;
    }

    for (var k in window) {
      try {
        if (!excludedKeys['$' + k] && has.call(window, k) && window[k] !== null && typeof window[k] === 'object') {
          try {
            equalsConstructorPrototype(window[k]);
          } catch (e) {
            return true;
          }
        }
      } catch (e) {
        return true;
      }
    }

    return false;
  }();

  var equalsConstructorPrototypeIfNotBuggy = function (o) {
    /* global window */
    if (typeof window === 'undefined' || !hasAutomationEqualityBug) {
      return equalsConstructorPrototype(o);
    }

    try {
      return equalsConstructorPrototype(o);
    } catch (e) {
      return false;
    }
  };

  keysShim = function keys(object) {
    var isObject = object !== null && typeof object === 'object';
    var isFunction = toStr.call(object) === '[object Function]';
    var isArguments = isArgs(object);
    var isString = isObject && toStr.call(object) === '[object String]';
    var theKeys = [];

    if (!isObject && !isFunction && !isArguments) {
      throw new TypeError('Object.keys called on a non-object');
    }

    var skipProto = hasProtoEnumBug && isFunction;

    if (isString && object.length > 0 && !has.call(object, 0)) {
      for (var i = 0; i < object.length; ++i) {
        theKeys.push(String(i));
      }
    }

    if (isArguments && object.length > 0) {
      for (var j = 0; j < object.length; ++j) {
        theKeys.push(String(j));
      }
    } else {
      for (var name in object) {
        if (!(skipProto && name === 'prototype') && has.call(object, name)) {
          theKeys.push(String(name));
        }
      }
    }

    if (hasDontEnumBug) {
      var skipConstructor = equalsConstructorPrototypeIfNotBuggy(object);

      for (var k = 0; k < dontEnums.length; ++k) {
        if (!(skipConstructor && dontEnums[k] === 'constructor') && has.call(object, dontEnums[k])) {
          theKeys.push(dontEnums[k]);
        }
      }
    }

    return theKeys;
  };
}

module.exports = keysShim;

/***/ }),

/***/ "./node_modules/object-keys/index.js":
/*!*******************************************!*\
  !*** ./node_modules/object-keys/index.js ***!
  \*******************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";


var slice = Array.prototype.slice;

var isArgs = __webpack_require__(/*! ./isArguments */ "./node_modules/object-keys/isArguments.js");

var origKeys = Object.keys;
var keysShim = origKeys ? function keys(o) {
  return origKeys(o);
} : __webpack_require__(/*! ./implementation */ "./node_modules/object-keys/implementation.js");
var originalKeys = Object.keys;

keysShim.shim = function shimObjectKeys() {
  if (Object.keys) {
    var keysWorksWithArguments = function () {
      // Safari 5.0 bug
      var args = Object.keys(arguments);
      return args && args.length === arguments.length;
    }(1, 2);

    if (!keysWorksWithArguments) {
      Object.keys = function keys(object) {
        // eslint-disable-line func-name-matching
        if (isArgs(object)) {
          return originalKeys(slice.call(object));
        }

        return originalKeys(object);
      };
    }
  } else {
    Object.keys = keysShim;
  }

  return Object.keys || keysShim;
};

module.exports = keysShim;

/***/ }),

/***/ "./node_modules/object-keys/isArguments.js":
/*!*************************************************!*\
  !*** ./node_modules/object-keys/isArguments.js ***!
  \*************************************************/
/***/ (function(module) {

"use strict";


var toStr = Object.prototype.toString;

module.exports = function isArguments(value) {
  var str = toStr.call(value);
  var isArgs = str === '[object Arguments]';

  if (!isArgs) {
    isArgs = str !== '[object Array]' && value !== null && typeof value === 'object' && typeof value.length === 'number' && value.length >= 0 && toStr.call(value.callee) === '[object Function]';
  }

  return isArgs;
};

/***/ }),

/***/ "./node_modules/process/browser.js":
/*!*****************************************!*\
  !*** ./node_modules/process/browser.js ***!
  \*****************************************/
/***/ (function(module) {

// shim for using process in browser
var process = module.exports = {}; // cached from whatever global is present so that test runners that stub it
// don't break things.  But we need to wrap it in a try catch in case it is
// wrapped in strict mode code which doesn't define any globals.  It's inside a
// function because try/catches deoptimize in certain engines.

var cachedSetTimeout;
var cachedClearTimeout;

function defaultSetTimout() {
  throw new Error('setTimeout has not been defined');
}

function defaultClearTimeout() {
  throw new Error('clearTimeout has not been defined');
}

(function () {
  try {
    if (typeof setTimeout === 'function') {
      cachedSetTimeout = setTimeout;
    } else {
      cachedSetTimeout = defaultSetTimout;
    }
  } catch (e) {
    cachedSetTimeout = defaultSetTimout;
  }

  try {
    if (typeof clearTimeout === 'function') {
      cachedClearTimeout = clearTimeout;
    } else {
      cachedClearTimeout = defaultClearTimeout;
    }
  } catch (e) {
    cachedClearTimeout = defaultClearTimeout;
  }
})();

function runTimeout(fun) {
  if (cachedSetTimeout === setTimeout) {
    //normal enviroments in sane situations
    return setTimeout(fun, 0);
  } // if setTimeout wasn't available but was latter defined


  if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
    cachedSetTimeout = setTimeout;
    return setTimeout(fun, 0);
  }

  try {
    // when when somebody has screwed with setTimeout but no I.E. maddness
    return cachedSetTimeout(fun, 0);
  } catch (e) {
    try {
      // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
      return cachedSetTimeout.call(null, fun, 0);
    } catch (e) {
      // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error
      return cachedSetTimeout.call(this, fun, 0);
    }
  }
}

function runClearTimeout(marker) {
  if (cachedClearTimeout === clearTimeout) {
    //normal enviroments in sane situations
    return clearTimeout(marker);
  } // if clearTimeout wasn't available but was latter defined


  if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
    cachedClearTimeout = clearTimeout;
    return clearTimeout(marker);
  }

  try {
    // when when somebody has screwed with setTimeout but no I.E. maddness
    return cachedClearTimeout(marker);
  } catch (e) {
    try {
      // When we are in I.E. but the script has been evaled so I.E. doesn't  trust the global object when called normally
      return cachedClearTimeout.call(null, marker);
    } catch (e) {
      // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.
      // Some versions of I.E. have different rules for clearTimeout vs setTimeout
      return cachedClearTimeout.call(this, marker);
    }
  }
}

var queue = [];
var draining = false;
var currentQueue;
var queueIndex = -1;

function cleanUpNextTick() {
  if (!draining || !currentQueue) {
    return;
  }

  draining = false;

  if (currentQueue.length) {
    queue = currentQueue.concat(queue);
  } else {
    queueIndex = -1;
  }

  if (queue.length) {
    drainQueue();
  }
}

function drainQueue() {
  if (draining) {
    return;
  }

  var timeout = runTimeout(cleanUpNextTick);
  draining = true;
  var len = queue.length;

  while (len) {
    currentQueue = queue;
    queue = [];

    while (++queueIndex < len) {
      if (currentQueue) {
        currentQueue[queueIndex].run();
      }
    }

    queueIndex = -1;
    len = queue.length;
  }

  currentQueue = null;
  draining = false;
  runClearTimeout(timeout);
}

process.nextTick = function (fun) {
  var args = new Array(arguments.length - 1);

  if (arguments.length > 1) {
    for (var i = 1; i < arguments.length; i++) {
      args[i - 1] = arguments[i];
    }
  }

  queue.push(new Item(fun, args));

  if (queue.length === 1 && !draining) {
    runTimeout(drainQueue);
  }
}; // v8 likes predictible objects


function Item(fun, array) {
  this.fun = fun;
  this.array = array;
}

Item.prototype.run = function () {
  this.fun.apply(null, this.array);
};

process.title = 'browser';
process.browser = true;
process.env = {};
process.argv = [];
process.version = ''; // empty string to avoid regexp issues

process.versions = {};

function noop() {}

process.on = noop;
process.addListener = noop;
process.once = noop;
process.off = noop;
process.removeListener = noop;
process.removeAllListeners = noop;
process.emit = noop;
process.prependListener = noop;
process.prependOnceListener = noop;

process.listeners = function (name) {
  return [];
};

process.binding = function (name) {
  throw new Error('process.binding is not supported');
};

process.cwd = function () {
  return '/';
};

process.chdir = function (dir) {
  throw new Error('process.chdir is not supported');
};

process.umask = function () {
  return 0;
};

/***/ }),

/***/ "./node_modules/util/support/isBuffer.js":
/*!***********************************************!*\
  !*** ./node_modules/util/support/isBuffer.js ***!
  \***********************************************/
/***/ (function(module) {

module.exports = function isBuffer(arg) {
  return arg instanceof Buffer;
};

/***/ }),

/***/ "./node_modules/util/support/types.js":
/*!********************************************!*\
  !*** ./node_modules/util/support/types.js ***!
  \********************************************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {

"use strict";
// Currently in sync with Node.js lib/internal/util/types.js
// https://github.com/nodejs/node/commit/112cc7c27551254aa2b17098fb774867f05ed0d9


var isArgumentsObject = __webpack_require__(/*! is-arguments */ "./node_modules/is-arguments/index.js");

var isGeneratorFunction = __webpack_require__(/*! is-generator-function */ "./node_modules/is-generator-function/index.js");

var whichTypedArray = __webpack_require__(/*! which-typed-array */ "./node_modules/which-typed-array/index.js");

var isTypedArray = __webpack_require__(/*! is-typed-array */ "./node_modules/is-typed-array/index.js");

function uncurryThis(f) {
  return f.call.bind(f);
}

var BigIntSupported = typeof BigInt !== 'undefined';
var SymbolSupported = typeof Symbol !== 'undefined';
var ObjectToString = uncurryThis(Object.prototype.toString);
var numberValue = uncurryThis(Number.prototype.valueOf);
var stringValue = uncurryThis(String.prototype.valueOf);
var booleanValue = uncurryThis(Boolean.prototype.valueOf);

if (BigIntSupported) {
  var bigIntValue = uncurryThis(BigInt.prototype.valueOf);
}

if (SymbolSupported) {
  var symbolValue = uncurryThis(Symbol.prototype.valueOf);
}

function checkBoxedPrimitive(value, prototypeValueOf) {
  if (typeof value !== 'object') {
    return false;
  }

  try {
    prototypeValueOf(value);
    return true;
  } catch (e) {
    return false;
  }
}

exports.isArgumentsObject = isArgumentsObject;
exports.isGeneratorFunction = isGeneratorFunction;
exports.isTypedArray = isTypedArray; // Taken from here and modified for better browser support
// https://github.com/sindresorhus/p-is-promise/blob/cda35a513bda03f977ad5cde3a079d237e82d7ef/index.js

function isPromise(input) {
  return typeof Promise !== 'undefined' && input instanceof Promise || input !== null && typeof input === 'object' && typeof input.then === 'function' && typeof input.catch === 'function';
}

exports.isPromise = isPromise;

function isArrayBufferView(value) {
  if (typeof ArrayBuffer !== 'undefined' && ArrayBuffer.isView) {
    return ArrayBuffer.isView(value);
  }

  return isTypedArray(value) || isDataView(value);
}

exports.isArrayBufferView = isArrayBufferView;

function isUint8Array(value) {
  return whichTypedArray(value) === 'Uint8Array';
}

exports.isUint8Array = isUint8Array;

function isUint8ClampedArray(value) {
  return whichTypedArray(value) === 'Uint8ClampedArray';
}

exports.isUint8ClampedArray = isUint8ClampedArray;

function isUint16Array(value) {
  return whichTypedArray(value) === 'Uint16Array';
}

exports.isUint16Array = isUint16Array;

function isUint32Array(value) {
  return whichTypedArray(value) === 'Uint32Array';
}

exports.isUint32Array = isUint32Array;

function isInt8Array(value) {
  return whichTypedArray(value) === 'Int8Array';
}

exports.isInt8Array = isInt8Array;

function isInt16Array(value) {
  return whichTypedArray(value) === 'Int16Array';
}

exports.isInt16Array = isInt16Array;

function isInt32Array(value) {
  return whichTypedArray(value) === 'Int32Array';
}

exports.isInt32Array = isInt32Array;

function isFloat32Array(value) {
  return whichTypedArray(value) === 'Float32Array';
}

exports.isFloat32Array = isFloat32Array;

function isFloat64Array(value) {
  return whichTypedArray(value) === 'Float64Array';
}

exports.isFloat64Array = isFloat64Array;

function isBigInt64Array(value) {
  return whichTypedArray(value) === 'BigInt64Array';
}

exports.isBigInt64Array = isBigInt64Array;

function isBigUint64Array(value) {
  return whichTypedArray(value) === 'BigUint64Array';
}

exports.isBigUint64Array = isBigUint64Array;

function isMapToString(value) {
  return ObjectToString(value) === '[object Map]';
}

isMapToString.working = typeof Map !== 'undefined' && isMapToString(new Map());

function isMap(value) {
  if (typeof Map === 'undefined') {
    return false;
  }

  return isMapToString.working ? isMapToString(value) : value instanceof Map;
}

exports.isMap = isMap;

function isSetToString(value) {
  return ObjectToString(value) === '[object Set]';
}

isSetToString.working = typeof Set !== 'undefined' && isSetToString(new Set());

function isSet(value) {
  if (typeof Set === 'undefined') {
    return false;
  }

  return isSetToString.working ? isSetToString(value) : value instanceof Set;
}

exports.isSet = isSet;

function isWeakMapToString(value) {
  return ObjectToString(value) === '[object WeakMap]';
}

isWeakMapToString.working = typeof WeakMap !== 'undefined' && isWeakMapToString(new WeakMap());

function isWeakMap(value) {
  if (typeof WeakMap === 'undefined') {
    return false;
  }

  return isWeakMapToString.working ? isWeakMapToString(value) : value instanceof WeakMap;
}

exports.isWeakMap = isWeakMap;

function isWeakSetToString(value) {
  return ObjectToString(value) === '[object WeakSet]';
}

isWeakSetToString.working = typeof WeakSet !== 'undefined' && isWeakSetToString(new WeakSet());

function isWeakSet(value) {
  return isWeakSetToString(value);
}

exports.isWeakSet = isWeakSet;

function isArrayBufferToString(value) {
  return ObjectToString(value) === '[object ArrayBuffer]';
}

isArrayBufferToString.working = typeof ArrayBuffer !== 'undefined' && isArrayBufferToString(new ArrayBuffer());

function isArrayBuffer(value) {
  if (typeof ArrayBuffer === 'undefined') {
    return false;
  }

  return isArrayBufferToString.working ? isArrayBufferToString(value) : value instanceof ArrayBuffer;
}

exports.isArrayBuffer = isArrayBuffer;

function isDataViewToString(value) {
  return ObjectToString(value) === '[object DataView]';
}

isDataViewToString.working = typeof ArrayBuffer !== 'undefined' && typeof DataView !== 'undefined' && isDataViewToString(new DataView(new ArrayBuffer(1), 0, 1));

function isDataView(value) {
  if (typeof DataView === 'undefined') {
    return false;
  }

  return isDataViewToString.working ? isDataViewToString(value) : value instanceof DataView;
}

exports.isDataView = isDataView; // Store a copy of SharedArrayBuffer in case it's deleted elsewhere

var SharedArrayBufferCopy = typeof SharedArrayBuffer !== 'undefined' ? SharedArrayBuffer : undefined;

function isSharedArrayBufferToString(value) {
  return ObjectToString(value) === '[object SharedArrayBuffer]';
}

function isSharedArrayBuffer(value) {
  if (typeof SharedArrayBufferCopy === 'undefined') {
    return false;
  }

  if (typeof isSharedArrayBufferToString.working === 'undefined') {
    isSharedArrayBufferToString.working = isSharedArrayBufferToString(new SharedArrayBufferCopy());
  }

  return isSharedArrayBufferToString.working ? isSharedArrayBufferToString(value) : value instanceof SharedArrayBufferCopy;
}

exports.isSharedArrayBuffer = isSharedArrayBuffer;

function isAsyncFunction(value) {
  return ObjectToString(value) === '[object AsyncFunction]';
}

exports.isAsyncFunction = isAsyncFunction;

function isMapIterator(value) {
  return ObjectToString(value) === '[object Map Iterator]';
}

exports.isMapIterator = isMapIterator;

function isSetIterator(value) {
  return ObjectToString(value) === '[object Set Iterator]';
}

exports.isSetIterator = isSetIterator;

function isGeneratorObject(value) {
  return ObjectToString(value) === '[object Generator]';
}

exports.isGeneratorObject = isGeneratorObject;

function isWebAssemblyCompiledModule(value) {
  return ObjectToString(value) === '[object WebAssembly.Module]';
}

exports.isWebAssemblyCompiledModule = isWebAssemblyCompiledModule;

function isNumberObject(value) {
  return checkBoxedPrimitive(value, numberValue);
}

exports.isNumberObject = isNumberObject;

function isStringObject(value) {
  return checkBoxedPrimitive(value, stringValue);
}

exports.isStringObject = isStringObject;

function isBooleanObject(value) {
  return checkBoxedPrimitive(value, booleanValue);
}

exports.isBooleanObject = isBooleanObject;

function isBigIntObject(value) {
  return BigIntSupported && checkBoxedPrimitive(value, bigIntValue);
}

exports.isBigIntObject = isBigIntObject;

function isSymbolObject(value) {
  return SymbolSupported && checkBoxedPrimitive(value, symbolValue);
}

exports.isSymbolObject = isSymbolObject;

function isBoxedPrimitive(value) {
  return isNumberObject(value) || isStringObject(value) || isBooleanObject(value) || isBigIntObject(value) || isSymbolObject(value);
}

exports.isBoxedPrimitive = isBoxedPrimitive;

function isAnyArrayBuffer(value) {
  return typeof Uint8Array !== 'undefined' && (isArrayBuffer(value) || isSharedArrayBuffer(value));
}

exports.isAnyArrayBuffer = isAnyArrayBuffer;
['isProxy', 'isExternal', 'isModuleNamespaceObject'].forEach(function (method) {
  Object.defineProperty(exports, method, {
    enumerable: false,
    value: function () {
      throw new Error(method + ' is not supported in userland');
    }
  });
});

/***/ }),

/***/ "./node_modules/util/util.js":
/*!***********************************!*\
  !*** ./node_modules/util/util.js ***!
  \***********************************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {

/* provided dependency */ var process = __webpack_require__(/*! process/browser */ "./node_modules/process/browser.js");
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var getOwnPropertyDescriptors = Object.getOwnPropertyDescriptors || function getOwnPropertyDescriptors(obj) {
  var keys = Object.keys(obj);
  var descriptors = {};

  for (var i = 0; i < keys.length; i++) {
    descriptors[keys[i]] = Object.getOwnPropertyDescriptor(obj, keys[i]);
  }

  return descriptors;
};

var formatRegExp = /%[sdj%]/g;

exports.format = function (f) {
  if (!isString(f)) {
    var objects = [];

    for (var i = 0; i < arguments.length; i++) {
      objects.push(inspect(arguments[i]));
    }

    return objects.join(' ');
  }

  var i = 1;
  var args = arguments;
  var len = args.length;
  var str = String(f).replace(formatRegExp, function (x) {
    if (x === '%%') return '%';
    if (i >= len) return x;

    switch (x) {
      case '%s':
        return String(args[i++]);

      case '%d':
        return Number(args[i++]);

      case '%j':
        try {
          return JSON.stringify(args[i++]);
        } catch (_) {
          return '[Circular]';
        }

      default:
        return x;
    }
  });

  for (var x = args[i]; i < len; x = args[++i]) {
    if (isNull(x) || !isObject(x)) {
      str += ' ' + x;
    } else {
      str += ' ' + inspect(x);
    }
  }

  return str;
}; // Mark that a method should not be used.
// Returns a modified function which warns once by default.
// If --no-deprecation is set, then it is a no-op.


exports.deprecate = function (fn, msg) {
  if (typeof process !== 'undefined' && process.noDeprecation === true) {
    return fn;
  } // Allow for deprecating things in the process of starting up.


  if (typeof process === 'undefined') {
    return function () {
      return exports.deprecate(fn, msg).apply(this, arguments);
    };
  }

  var warned = false;

  function deprecated() {
    if (!warned) {
      if (process.throwDeprecation) {
        throw new Error(msg);
      } else if (process.traceDeprecation) {
        console.trace(msg);
      } else {
        console.error(msg);
      }

      warned = true;
    }

    return fn.apply(this, arguments);
  }

  return deprecated;
};

var debugs = {};
var debugEnvRegex = /^$/;

if (process.env.NODE_DEBUG) {
  var debugEnv = process.env.NODE_DEBUG;
  debugEnv = debugEnv.replace(/[|\\{}()[\]^$+?.]/g, '\\$&').replace(/\*/g, '.*').replace(/,/g, '$|^').toUpperCase();
  debugEnvRegex = new RegExp('^' + debugEnv + '$', 'i');
}

exports.debuglog = function (set) {
  set = set.toUpperCase();

  if (!debugs[set]) {
    if (debugEnvRegex.test(set)) {
      var pid = process.pid;

      debugs[set] = function () {
        var msg = exports.format.apply(exports, arguments);
        console.error('%s %d: %s', set, pid, msg);
      };
    } else {
      debugs[set] = function () {};
    }
  }

  return debugs[set];
};
/**
 * Echos the value of a value. Trys to print the value out
 * in the best way possible given the different types.
 *
 * @param {Object} obj The object to print out.
 * @param {Object} opts Optional options object that alters the output.
 */

/* legacy: obj, showHidden, depth, colors*/


function inspect(obj, opts) {
  // default options
  var ctx = {
    seen: [],
    stylize: stylizeNoColor
  }; // legacy...

  if (arguments.length >= 3) ctx.depth = arguments[2];
  if (arguments.length >= 4) ctx.colors = arguments[3];

  if (isBoolean(opts)) {
    // legacy...
    ctx.showHidden = opts;
  } else if (opts) {
    // got an "options" object
    exports._extend(ctx, opts);
  } // set default options


  if (isUndefined(ctx.showHidden)) ctx.showHidden = false;
  if (isUndefined(ctx.depth)) ctx.depth = 2;
  if (isUndefined(ctx.colors)) ctx.colors = false;
  if (isUndefined(ctx.customInspect)) ctx.customInspect = true;
  if (ctx.colors) ctx.stylize = stylizeWithColor;
  return formatValue(ctx, obj, ctx.depth);
}

exports.inspect = inspect; // http://en.wikipedia.org/wiki/ANSI_escape_code#graphics

inspect.colors = {
  'bold': [1, 22],
  'italic': [3, 23],
  'underline': [4, 24],
  'inverse': [7, 27],
  'white': [37, 39],
  'grey': [90, 39],
  'black': [30, 39],
  'blue': [34, 39],
  'cyan': [36, 39],
  'green': [32, 39],
  'magenta': [35, 39],
  'red': [31, 39],
  'yellow': [33, 39]
}; // Don't use 'blue' not visible on cmd.exe

inspect.styles = {
  'special': 'cyan',
  'number': 'yellow',
  'boolean': 'yellow',
  'undefined': 'grey',
  'null': 'bold',
  'string': 'green',
  'date': 'magenta',
  // "name": intentionally not styling
  'regexp': 'red'
};

function stylizeWithColor(str, styleType) {
  var style = inspect.styles[styleType];

  if (style) {
    return '\u001b[' + inspect.colors[style][0] + 'm' + str + '\u001b[' + inspect.colors[style][1] + 'm';
  } else {
    return str;
  }
}

function stylizeNoColor(str, styleType) {
  return str;
}

function arrayToHash(array) {
  var hash = {};
  array.forEach(function (val, idx) {
    hash[val] = true;
  });
  return hash;
}

function formatValue(ctx, value, recurseTimes) {
  // Provide a hook for user-specified inspect functions.
  // Check that value is an object with an inspect function on it
  if (ctx.customInspect && value && isFunction(value.inspect) && // Filter out the util module, it's inspect function is special
  value.inspect !== exports.inspect && // Also filter out any prototype objects using the circular check.
  !(value.constructor && value.constructor.prototype === value)) {
    var ret = value.inspect(recurseTimes, ctx);

    if (!isString(ret)) {
      ret = formatValue(ctx, ret, recurseTimes);
    }

    return ret;
  } // Primitive types cannot have properties


  var primitive = formatPrimitive(ctx, value);

  if (primitive) {
    return primitive;
  } // Look up the keys of the object.


  var keys = Object.keys(value);
  var visibleKeys = arrayToHash(keys);

  if (ctx.showHidden) {
    keys = Object.getOwnPropertyNames(value);
  } // IE doesn't make error fields non-enumerable
  // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx


  if (isError(value) && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) {
    return formatError(value);
  } // Some type of object without properties can be shortcutted.


  if (keys.length === 0) {
    if (isFunction(value)) {
      var name = value.name ? ': ' + value.name : '';
      return ctx.stylize('[Function' + name + ']', 'special');
    }

    if (isRegExp(value)) {
      return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
    }

    if (isDate(value)) {
      return ctx.stylize(Date.prototype.toString.call(value), 'date');
    }

    if (isError(value)) {
      return formatError(value);
    }
  }

  var base = '',
      array = false,
      braces = ['{', '}']; // Make Array say that they are Array

  if (isArray(value)) {
    array = true;
    braces = ['[', ']'];
  } // Make functions say that they are functions


  if (isFunction(value)) {
    var n = value.name ? ': ' + value.name : '';
    base = ' [Function' + n + ']';
  } // Make RegExps say that they are RegExps


  if (isRegExp(value)) {
    base = ' ' + RegExp.prototype.toString.call(value);
  } // Make dates with properties first say the date


  if (isDate(value)) {
    base = ' ' + Date.prototype.toUTCString.call(value);
  } // Make error with message first say the error


  if (isError(value)) {
    base = ' ' + formatError(value);
  }

  if (keys.length === 0 && (!array || value.length == 0)) {
    return braces[0] + base + braces[1];
  }

  if (recurseTimes < 0) {
    if (isRegExp(value)) {
      return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
    } else {
      return ctx.stylize('[Object]', 'special');
    }
  }

  ctx.seen.push(value);
  var output;

  if (array) {
    output = formatArray(ctx, value, recurseTimes, visibleKeys, keys);
  } else {
    output = keys.map(function (key) {
      return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array);
    });
  }

  ctx.seen.pop();
  return reduceToSingleString(output, base, braces);
}

function formatPrimitive(ctx, value) {
  if (isUndefined(value)) return ctx.stylize('undefined', 'undefined');

  if (isString(value)) {
    var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '').replace(/'/g, "\\'").replace(/\\"/g, '"') + '\'';
    return ctx.stylize(simple, 'string');
  }

  if (isNumber(value)) return ctx.stylize('' + value, 'number');
  if (isBoolean(value)) return ctx.stylize('' + value, 'boolean'); // For some reason typeof null is "object", so special case here.

  if (isNull(value)) return ctx.stylize('null', 'null');
}

function formatError(value) {
  return '[' + Error.prototype.toString.call(value) + ']';
}

function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
  var output = [];

  for (var i = 0, l = value.length; i < l; ++i) {
    if (hasOwnProperty(value, String(i))) {
      output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, String(i), true));
    } else {
      output.push('');
    }
  }

  keys.forEach(function (key) {
    if (!key.match(/^\d+$/)) {
      output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, key, true));
    }
  });
  return output;
}

function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) {
  var name, str, desc;
  desc = Object.getOwnPropertyDescriptor(value, key) || {
    value: value[key]
  };

  if (desc.get) {
    if (desc.set) {
      str = ctx.stylize('[Getter/Setter]', 'special');
    } else {
      str = ctx.stylize('[Getter]', 'special');
    }
  } else {
    if (desc.set) {
      str = ctx.stylize('[Setter]', 'special');
    }
  }

  if (!hasOwnProperty(visibleKeys, key)) {
    name = '[' + key + ']';
  }

  if (!str) {
    if (ctx.seen.indexOf(desc.value) < 0) {
      if (isNull(recurseTimes)) {
        str = formatValue(ctx, desc.value, null);
      } else {
        str = formatValue(ctx, desc.value, recurseTimes - 1);
      }

      if (str.indexOf('\n') > -1) {
        if (array) {
          str = str.split('\n').map(function (line) {
            return '  ' + line;
          }).join('\n').substr(2);
        } else {
          str = '\n' + str.split('\n').map(function (line) {
            return '   ' + line;
          }).join('\n');
        }
      }
    } else {
      str = ctx.stylize('[Circular]', 'special');
    }
  }

  if (isUndefined(name)) {
    if (array && key.match(/^\d+$/)) {
      return str;
    }

    name = JSON.stringify('' + key);

    if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
      name = name.substr(1, name.length - 2);
      name = ctx.stylize(name, 'name');
    } else {
      name = name.replace(/'/g, "\\'").replace(/\\"/g, '"').replace(/(^"|"$)/g, "'");
      name = ctx.stylize(name, 'string');
    }
  }

  return name + ': ' + str;
}

function reduceToSingleString(output, base, braces) {
  var numLinesEst = 0;
  var length = output.reduce(function (prev, cur) {
    numLinesEst++;
    if (cur.indexOf('\n') >= 0) numLinesEst++;
    return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1;
  }, 0);

  if (length > 60) {
    return braces[0] + (base === '' ? '' : base + '\n ') + ' ' + output.join(',\n  ') + ' ' + braces[1];
  }

  return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
} // NOTE: These type checking functions intentionally don't use `instanceof`
// because it is fragile and can be easily faked with `Object.create()`.


exports.types = __webpack_require__(/*! ./support/types */ "./node_modules/util/support/types.js");

function isArray(ar) {
  return Array.isArray(ar);
}

exports.isArray = isArray;

function isBoolean(arg) {
  return typeof arg === 'boolean';
}

exports.isBoolean = isBoolean;

function isNull(arg) {
  return arg === null;
}

exports.isNull = isNull;

function isNullOrUndefined(arg) {
  return arg == null;
}

exports.isNullOrUndefined = isNullOrUndefined;

function isNumber(arg) {
  return typeof arg === 'number';
}

exports.isNumber = isNumber;

function isString(arg) {
  return typeof arg === 'string';
}

exports.isString = isString;

function isSymbol(arg) {
  return typeof arg === 'symbol';
}

exports.isSymbol = isSymbol;

function isUndefined(arg) {
  return arg === void 0;
}

exports.isUndefined = isUndefined;

function isRegExp(re) {
  return isObject(re) && objectToString(re) === '[object RegExp]';
}

exports.isRegExp = isRegExp;
exports.types.isRegExp = isRegExp;

function isObject(arg) {
  return typeof arg === 'object' && arg !== null;
}

exports.isObject = isObject;

function isDate(d) {
  return isObject(d) && objectToString(d) === '[object Date]';
}

exports.isDate = isDate;
exports.types.isDate = isDate;

function isError(e) {
  return isObject(e) && (objectToString(e) === '[object Error]' || e instanceof Error);
}

exports.isError = isError;
exports.types.isNativeError = isError;

function isFunction(arg) {
  return typeof arg === 'function';
}

exports.isFunction = isFunction;

function isPrimitive(arg) {
  return arg === null || typeof arg === 'boolean' || typeof arg === 'number' || typeof arg === 'string' || typeof arg === 'symbol' || // ES6 symbol
  typeof arg === 'undefined';
}

exports.isPrimitive = isPrimitive;
exports.isBuffer = __webpack_require__(/*! ./support/isBuffer */ "./node_modules/util/support/isBuffer.js");

function objectToString(o) {
  return Object.prototype.toString.call(o);
}

function pad(n) {
  return n < 10 ? '0' + n.toString(10) : n.toString(10);
}

var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; // 26 Feb 16:19:34

function timestamp() {
  var d = new Date();
  var time = [pad(d.getHours()), pad(d.getMinutes()), pad(d.getSeconds())].join(':');
  return [d.getDate(), months[d.getMonth()], time].join(' ');
} // log is just a thin wrapper to console.log that prepends a timestamp


exports.log = function () {
  console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments));
};
/**
 * Inherit the prototype methods from one constructor into another.
 *
 * The Function.prototype.inherits from lang.js rewritten as a standalone
 * function (not on Function.prototype). NOTE: If this file is to be loaded
 * during bootstrapping this function needs to be rewritten using some native
 * functions as prototype setup using normal JavaScript does not work as
 * expected during bootstrapping (see mirror.js in r114903).
 *
 * @param {function} ctor Constructor function which needs to inherit the
 *     prototype.
 * @param {function} superCtor Constructor function to inherit prototype from.
 */


exports.inherits = __webpack_require__(/*! inherits */ "./node_modules/inherits/inherits.js");

exports._extend = function (origin, add) {
  // Don't do anything if add isn't an object
  if (!add || !isObject(add)) return origin;
  var keys = Object.keys(add);
  var i = keys.length;

  while (i--) {
    origin[keys[i]] = add[keys[i]];
  }

  return origin;
};

function hasOwnProperty(obj, prop) {
  return Object.prototype.hasOwnProperty.call(obj, prop);
}

var kCustomPromisifiedSymbol = typeof Symbol !== 'undefined' ? Symbol('util.promisify.custom') : undefined;

exports.promisify = function promisify(original) {
  if (typeof original !== 'function') throw new TypeError('The "original" argument must be of type Function');

  if (kCustomPromisifiedSymbol && original[kCustomPromisifiedSymbol]) {
    var fn = original[kCustomPromisifiedSymbol];

    if (typeof fn !== 'function') {
      throw new TypeError('The "util.promisify.custom" argument must be of type Function');
    }

    Object.defineProperty(fn, kCustomPromisifiedSymbol, {
      value: fn,
      enumerable: false,
      writable: false,
      configurable: true
    });
    return fn;
  }

  function fn() {
    var promiseResolve, promiseReject;
    var promise = new Promise(function (resolve, reject) {
      promiseResolve = resolve;
      promiseReject = reject;
    });
    var args = [];

    for (var i = 0; i < arguments.length; i++) {
      args.push(arguments[i]);
    }

    args.push(function (err, value) {
      if (err) {
        promiseReject(err);
      } else {
        promiseResolve(value);
      }
    });

    try {
      original.apply(this, args);
    } catch (err) {
      promiseReject(err);
    }

    return promise;
  }

  Object.setPrototypeOf(fn, Object.getPrototypeOf(original));
  if (kCustomPromisifiedSymbol) Object.defineProperty(fn, kCustomPromisifiedSymbol, {
    value: fn,
    enumerable: false,
    writable: false,
    configurable: true
  });
  return Object.defineProperties(fn, getOwnPropertyDescriptors(original));
};

exports.promisify.custom = kCustomPromisifiedSymbol;

function callbackifyOnRejected(reason, cb) {
  // `!reason` guard inspired by bluebird (Ref: https://goo.gl/t5IS6M).
  // Because `null` is a special error value in callbacks which means "no error
  // occurred", we error-wrap so the callback consumer can distinguish between
  // "the promise rejected with null" or "the promise fulfilled with undefined".
  if (!reason) {
    var newReason = new Error('Promise was rejected with a falsy value');
    newReason.reason = reason;
    reason = newReason;
  }

  return cb(reason);
}

function callbackify(original) {
  if (typeof original !== 'function') {
    throw new TypeError('The "original" argument must be of type Function');
  } // We DO NOT return the promise as it gives the user a false sense that
  // the promise is actually somehow related to the callback's execution
  // and that the callback throwing will reject the promise.


  function callbackified() {
    var args = [];

    for (var i = 0; i < arguments.length; i++) {
      args.push(arguments[i]);
    }

    var maybeCb = args.pop();

    if (typeof maybeCb !== 'function') {
      throw new TypeError('The last argument must be of type Function');
    }

    var self = this;

    var cb = function () {
      return maybeCb.apply(self, arguments);
    }; // In true node style we process the callback on `nextTick` with all the
    // implications (stack, `uncaughtException`, `async_hooks`)


    original.apply(this, args).then(function (ret) {
      process.nextTick(cb.bind(null, null, ret));
    }, function (rej) {
      process.nextTick(callbackifyOnRejected.bind(null, rej, cb));
    });
  }

  Object.setPrototypeOf(callbackified, Object.getPrototypeOf(original));
  Object.defineProperties(callbackified, getOwnPropertyDescriptors(original));
  return callbackified;
}

exports.callbackify = callbackify;

/***/ }),

/***/ "./node_modules/uuid/dist/esm-browser/index.js":
/*!*****************************************************!*\
  !*** ./node_modules/uuid/dist/esm-browser/index.js ***!
  \*****************************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "NIL": function() { return /* reexport safe */ _nil_js__WEBPACK_IMPORTED_MODULE_4__["default"]; },
/* harmony export */   "parse": function() { return /* reexport safe */ _parse_js__WEBPACK_IMPORTED_MODULE_8__["default"]; },
/* harmony export */   "stringify": function() { return /* reexport safe */ _stringify_js__WEBPACK_IMPORTED_MODULE_7__["default"]; },
/* harmony export */   "v1": function() { return /* reexport safe */ _v1_js__WEBPACK_IMPORTED_MODULE_0__["default"]; },
/* harmony export */   "v3": function() { return /* reexport safe */ _v3_js__WEBPACK_IMPORTED_MODULE_1__["default"]; },
/* harmony export */   "v4": function() { return /* reexport safe */ _v4_js__WEBPACK_IMPORTED_MODULE_2__["default"]; },
/* harmony export */   "v5": function() { return /* reexport safe */ _v5_js__WEBPACK_IMPORTED_MODULE_3__["default"]; },
/* harmony export */   "validate": function() { return /* reexport safe */ _validate_js__WEBPACK_IMPORTED_MODULE_6__["default"]; },
/* harmony export */   "version": function() { return /* reexport safe */ _version_js__WEBPACK_IMPORTED_MODULE_5__["default"]; }
/* harmony export */ });
/* harmony import */ var _v1_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./v1.js */ "./node_modules/uuid/dist/esm-browser/v1.js");
/* harmony import */ var _v3_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./v3.js */ "./node_modules/uuid/dist/esm-browser/v3.js");
/* harmony import */ var _v4_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./v4.js */ "./node_modules/uuid/dist/esm-browser/v4.js");
/* harmony import */ var _v5_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./v5.js */ "./node_modules/uuid/dist/esm-browser/v5.js");
/* harmony import */ var _nil_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./nil.js */ "./node_modules/uuid/dist/esm-browser/nil.js");
/* harmony import */ var _version_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./version.js */ "./node_modules/uuid/dist/esm-browser/version.js");
/* harmony import */ var _validate_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./validate.js */ "./node_modules/uuid/dist/esm-browser/validate.js");
/* harmony import */ var _stringify_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./stringify.js */ "./node_modules/uuid/dist/esm-browser/stringify.js");
/* harmony import */ var _parse_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./parse.js */ "./node_modules/uuid/dist/esm-browser/parse.js");










/***/ }),

/***/ "./node_modules/uuid/dist/esm-browser/md5.js":
/*!***************************************************!*\
  !*** ./node_modules/uuid/dist/esm-browser/md5.js ***!
  \***************************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/*
 * Browser-compatible JavaScript MD5
 *
 * Modification of JavaScript MD5
 * https://github.com/blueimp/JavaScript-MD5
 *
 * Copyright 2011, Sebastian Tschan
 * https://blueimp.net
 *
 * Licensed under the MIT license:
 * https://opensource.org/licenses/MIT
 *
 * Based on
 * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
 * Digest Algorithm, as defined in RFC 1321.
 * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
 * Distributed under the BSD License
 * See http://pajhome.org.uk/crypt/md5 for more info.
 */
function md5(bytes) {
  if (typeof bytes === 'string') {
    var msg = unescape(encodeURIComponent(bytes)); // UTF8 escape

    bytes = new Uint8Array(msg.length);

    for (var i = 0; i < msg.length; ++i) {
      bytes[i] = msg.charCodeAt(i);
    }
  }

  return md5ToHexEncodedArray(wordsToMd5(bytesToWords(bytes), bytes.length * 8));
}
/*
 * Convert an array of little-endian words to an array of bytes
 */


function md5ToHexEncodedArray(input) {
  var output = [];
  var length32 = input.length * 32;
  var hexTab = '0123456789abcdef';

  for (var i = 0; i < length32; i += 8) {
    var x = input[i >> 5] >>> i % 32 & 0xff;
    var hex = parseInt(hexTab.charAt(x >>> 4 & 0x0f) + hexTab.charAt(x & 0x0f), 16);
    output.push(hex);
  }

  return output;
}
/**
 * Calculate output length with padding and bit length
 */


function getOutputLength(inputLength8) {
  return (inputLength8 + 64 >>> 9 << 4) + 14 + 1;
}
/*
 * Calculate the MD5 of an array of little-endian words, and a bit length.
 */


function wordsToMd5(x, len) {
  /* append padding */
  x[len >> 5] |= 0x80 << len % 32;
  x[getOutputLength(len) - 1] = len;
  var a = 1732584193;
  var b = -271733879;
  var c = -1732584194;
  var d = 271733878;

  for (var i = 0; i < x.length; i += 16) {
    var olda = a;
    var oldb = b;
    var oldc = c;
    var oldd = d;
    a = md5ff(a, b, c, d, x[i], 7, -680876936);
    d = md5ff(d, a, b, c, x[i + 1], 12, -389564586);
    c = md5ff(c, d, a, b, x[i + 2], 17, 606105819);
    b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330);
    a = md5ff(a, b, c, d, x[i + 4], 7, -176418897);
    d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426);
    c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341);
    b = md5ff(b, c, d, a, x[i + 7], 22, -45705983);
    a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416);
    d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417);
    c = md5ff(c, d, a, b, x[i + 10], 17, -42063);
    b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162);
    a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682);
    d = md5ff(d, a, b, c, x[i + 13], 12, -40341101);
    c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290);
    b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329);
    a = md5gg(a, b, c, d, x[i + 1], 5, -165796510);
    d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632);
    c = md5gg(c, d, a, b, x[i + 11], 14, 643717713);
    b = md5gg(b, c, d, a, x[i], 20, -373897302);
    a = md5gg(a, b, c, d, x[i + 5], 5, -701558691);
    d = md5gg(d, a, b, c, x[i + 10], 9, 38016083);
    c = md5gg(c, d, a, b, x[i + 15], 14, -660478335);
    b = md5gg(b, c, d, a, x[i + 4], 20, -405537848);
    a = md5gg(a, b, c, d, x[i + 9], 5, 568446438);
    d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690);
    c = md5gg(c, d, a, b, x[i + 3], 14, -187363961);
    b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501);
    a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467);
    d = md5gg(d, a, b, c, x[i + 2], 9, -51403784);
    c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473);
    b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734);
    a = md5hh(a, b, c, d, x[i + 5], 4, -378558);
    d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463);
    c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562);
    b = md5hh(b, c, d, a, x[i + 14], 23, -35309556);
    a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060);
    d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353);
    c = md5hh(c, d, a, b, x[i + 7], 16, -155497632);
    b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640);
    a = md5hh(a, b, c, d, x[i + 13], 4, 681279174);
    d = md5hh(d, a, b, c, x[i], 11, -358537222);
    c = md5hh(c, d, a, b, x[i + 3], 16, -722521979);
    b = md5hh(b, c, d, a, x[i + 6], 23, 76029189);
    a = md5hh(a, b, c, d, x[i + 9], 4, -640364487);
    d = md5hh(d, a, b, c, x[i + 12], 11, -421815835);
    c = md5hh(c, d, a, b, x[i + 15], 16, 530742520);
    b = md5hh(b, c, d, a, x[i + 2], 23, -995338651);
    a = md5ii(a, b, c, d, x[i], 6, -198630844);
    d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415);
    c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905);
    b = md5ii(b, c, d, a, x[i + 5], 21, -57434055);
    a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571);
    d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606);
    c = md5ii(c, d, a, b, x[i + 10], 15, -1051523);
    b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799);
    a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359);
    d = md5ii(d, a, b, c, x[i + 15], 10, -30611744);
    c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380);
    b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649);
    a = md5ii(a, b, c, d, x[i + 4], 6, -145523070);
    d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379);
    c = md5ii(c, d, a, b, x[i + 2], 15, 718787259);
    b = md5ii(b, c, d, a, x[i + 9], 21, -343485551);
    a = safeAdd(a, olda);
    b = safeAdd(b, oldb);
    c = safeAdd(c, oldc);
    d = safeAdd(d, oldd);
  }

  return [a, b, c, d];
}
/*
 * Convert an array bytes to an array of little-endian words
 * Characters >255 have their high-byte silently ignored.
 */


function bytesToWords(input) {
  if (input.length === 0) {
    return [];
  }

  var length8 = input.length * 8;
  var output = new Uint32Array(getOutputLength(length8));

  for (var i = 0; i < length8; i += 8) {
    output[i >> 5] |= (input[i / 8] & 0xff) << i % 32;
  }

  return output;
}
/*
 * Add integers, wrapping at 2^32. This uses 16-bit operations internally
 * to work around bugs in some JS interpreters.
 */


function safeAdd(x, y) {
  var lsw = (x & 0xffff) + (y & 0xffff);
  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
  return msw << 16 | lsw & 0xffff;
}
/*
 * Bitwise rotate a 32-bit number to the left.
 */


function bitRotateLeft(num, cnt) {
  return num << cnt | num >>> 32 - cnt;
}
/*
 * These functions implement the four basic operations the algorithm uses.
 */


function md5cmn(q, a, b, x, s, t) {
  return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b);
}

function md5ff(a, b, c, d, x, s, t) {
  return md5cmn(b & c | ~b & d, a, b, x, s, t);
}

function md5gg(a, b, c, d, x, s, t) {
  return md5cmn(b & d | c & ~d, a, b, x, s, t);
}

function md5hh(a, b, c, d, x, s, t) {
  return md5cmn(b ^ c ^ d, a, b, x, s, t);
}

function md5ii(a, b, c, d, x, s, t) {
  return md5cmn(c ^ (b | ~d), a, b, x, s, t);
}

/* harmony default export */ __webpack_exports__["default"] = (md5);

/***/ }),

/***/ "./node_modules/uuid/dist/esm-browser/nil.js":
/*!***************************************************!*\
  !*** ./node_modules/uuid/dist/esm-browser/nil.js ***!
  \***************************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ('00000000-0000-0000-0000-000000000000');

/***/ }),

/***/ "./node_modules/uuid/dist/esm-browser/parse.js":
/*!*****************************************************!*\
  !*** ./node_modules/uuid/dist/esm-browser/parse.js ***!
  \*****************************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _validate_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./validate.js */ "./node_modules/uuid/dist/esm-browser/validate.js");


function parse(uuid) {
  if (!(0,_validate_js__WEBPACK_IMPORTED_MODULE_0__["default"])(uuid)) {
    throw TypeError('Invalid UUID');
  }

  var v;
  var arr = new Uint8Array(16); // Parse ########-....-....-....-............

  arr[0] = (v = parseInt(uuid.slice(0, 8), 16)) >>> 24;
  arr[1] = v >>> 16 & 0xff;
  arr[2] = v >>> 8 & 0xff;
  arr[3] = v & 0xff; // Parse ........-####-....-....-............

  arr[4] = (v = parseInt(uuid.slice(9, 13), 16)) >>> 8;
  arr[5] = v & 0xff; // Parse ........-....-####-....-............

  arr[6] = (v = parseInt(uuid.slice(14, 18), 16)) >>> 8;
  arr[7] = v & 0xff; // Parse ........-....-....-####-............

  arr[8] = (v = parseInt(uuid.slice(19, 23), 16)) >>> 8;
  arr[9] = v & 0xff; // Parse ........-....-....-....-############
  // (Use "/" to avoid 32-bit truncation when bit-shifting high-order bytes)

  arr[10] = (v = parseInt(uuid.slice(24, 36), 16)) / 0x10000000000 & 0xff;
  arr[11] = v / 0x100000000 & 0xff;
  arr[12] = v >>> 24 & 0xff;
  arr[13] = v >>> 16 & 0xff;
  arr[14] = v >>> 8 & 0xff;
  arr[15] = v & 0xff;
  return arr;
}

/* harmony default export */ __webpack_exports__["default"] = (parse);

/***/ }),

/***/ "./node_modules/uuid/dist/esm-browser/regex.js":
/*!*****************************************************!*\
  !*** ./node_modules/uuid/dist/esm-browser/regex.js ***!
  \*****************************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = (/^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i);

/***/ }),

/***/ "./node_modules/uuid/dist/esm-browser/rng.js":
/*!***************************************************!*\
  !*** ./node_modules/uuid/dist/esm-browser/rng.js ***!
  \***************************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "default": function() { return /* binding */ rng; }
/* harmony export */ });
// Unique ID creation requires a high quality random # generator. In the browser we therefore
// require the crypto API and do not support built-in fallback to lower quality random number
// generators (like Math.random()).
var getRandomValues;
var rnds8 = new Uint8Array(16);
function rng() {
  // lazy load so that environments that need to polyfill have a chance to do so
  if (!getRandomValues) {
    // getRandomValues needs to be invoked in a context where "this" is a Crypto implementation. Also,
    // find the complete implementation of crypto (msCrypto) on IE11.
    getRandomValues = typeof crypto !== 'undefined' && crypto.getRandomValues && crypto.getRandomValues.bind(crypto) || typeof msCrypto !== 'undefined' && typeof msCrypto.getRandomValues === 'function' && msCrypto.getRandomValues.bind(msCrypto);

    if (!getRandomValues) {
      throw new Error('crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported');
    }
  }

  return getRandomValues(rnds8);
}

/***/ }),

/***/ "./node_modules/uuid/dist/esm-browser/sha1.js":
/*!****************************************************!*\
  !*** ./node_modules/uuid/dist/esm-browser/sha1.js ***!
  \****************************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
// Adapted from Chris Veness' SHA1 code at
// http://www.movable-type.co.uk/scripts/sha1.html
function f(s, x, y, z) {
  switch (s) {
    case 0:
      return x & y ^ ~x & z;

    case 1:
      return x ^ y ^ z;

    case 2:
      return x & y ^ x & z ^ y & z;

    case 3:
      return x ^ y ^ z;
  }
}

function ROTL(x, n) {
  return x << n | x >>> 32 - n;
}

function sha1(bytes) {
  var K = [0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6];
  var H = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0];

  if (typeof bytes === 'string') {
    var msg = unescape(encodeURIComponent(bytes)); // UTF8 escape

    bytes = [];

    for (var i = 0; i < msg.length; ++i) {
      bytes.push(msg.charCodeAt(i));
    }
  } else if (!Array.isArray(bytes)) {
    // Convert Array-like to Array
    bytes = Array.prototype.slice.call(bytes);
  }

  bytes.push(0x80);
  var l = bytes.length / 4 + 2;
  var N = Math.ceil(l / 16);
  var M = new Array(N);

  for (var _i = 0; _i < N; ++_i) {
    var arr = new Uint32Array(16);

    for (var j = 0; j < 16; ++j) {
      arr[j] = bytes[_i * 64 + j * 4] << 24 | bytes[_i * 64 + j * 4 + 1] << 16 | bytes[_i * 64 + j * 4 + 2] << 8 | bytes[_i * 64 + j * 4 + 3];
    }

    M[_i] = arr;
  }

  M[N - 1][14] = (bytes.length - 1) * 8 / Math.pow(2, 32);
  M[N - 1][14] = Math.floor(M[N - 1][14]);
  M[N - 1][15] = (bytes.length - 1) * 8 & 0xffffffff;

  for (var _i2 = 0; _i2 < N; ++_i2) {
    var W = new Uint32Array(80);

    for (var t = 0; t < 16; ++t) {
      W[t] = M[_i2][t];
    }

    for (var _t = 16; _t < 80; ++_t) {
      W[_t] = ROTL(W[_t - 3] ^ W[_t - 8] ^ W[_t - 14] ^ W[_t - 16], 1);
    }

    var a = H[0];
    var b = H[1];
    var c = H[2];
    var d = H[3];
    var e = H[4];

    for (var _t2 = 0; _t2 < 80; ++_t2) {
      var s = Math.floor(_t2 / 20);
      var T = ROTL(a, 5) + f(s, b, c, d) + e + K[s] + W[_t2] >>> 0;
      e = d;
      d = c;
      c = ROTL(b, 30) >>> 0;
      b = a;
      a = T;
    }

    H[0] = H[0] + a >>> 0;
    H[1] = H[1] + b >>> 0;
    H[2] = H[2] + c >>> 0;
    H[3] = H[3] + d >>> 0;
    H[4] = H[4] + e >>> 0;
  }

  return [H[0] >> 24 & 0xff, H[0] >> 16 & 0xff, H[0] >> 8 & 0xff, H[0] & 0xff, H[1] >> 24 & 0xff, H[1] >> 16 & 0xff, H[1] >> 8 & 0xff, H[1] & 0xff, H[2] >> 24 & 0xff, H[2] >> 16 & 0xff, H[2] >> 8 & 0xff, H[2] & 0xff, H[3] >> 24 & 0xff, H[3] >> 16 & 0xff, H[3] >> 8 & 0xff, H[3] & 0xff, H[4] >> 24 & 0xff, H[4] >> 16 & 0xff, H[4] >> 8 & 0xff, H[4] & 0xff];
}

/* harmony default export */ __webpack_exports__["default"] = (sha1);

/***/ }),

/***/ "./node_modules/uuid/dist/esm-browser/stringify.js":
/*!*********************************************************!*\
  !*** ./node_modules/uuid/dist/esm-browser/stringify.js ***!
  \*********************************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _validate_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./validate.js */ "./node_modules/uuid/dist/esm-browser/validate.js");

/**
 * Convert array of 16 byte values to UUID string format of the form:
 * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
 */

var byteToHex = [];

for (var i = 0; i < 256; ++i) {
  byteToHex.push((i + 0x100).toString(16).substr(1));
}

function stringify(arr) {
  var offset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; // Note: Be careful editing this code!  It's been tuned for performance
  // and works in ways you may not expect. See https://github.com/uuidjs/uuid/pull/434

  var uuid = (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + '-' + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + '-' + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + '-' + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + '-' + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase(); // Consistency check for valid UUID.  If this throws, it's likely due to one
  // of the following:
  // - One or more input array values don't map to a hex octet (leading to
  // "undefined" in the uuid)
  // - Invalid input values for the RFC `version` or `variant` fields

  if (!(0,_validate_js__WEBPACK_IMPORTED_MODULE_0__["default"])(uuid)) {
    throw TypeError('Stringified UUID is invalid');
  }

  return uuid;
}

/* harmony default export */ __webpack_exports__["default"] = (stringify);

/***/ }),

/***/ "./node_modules/uuid/dist/esm-browser/v1.js":
/*!**************************************************!*\
  !*** ./node_modules/uuid/dist/esm-browser/v1.js ***!
  \**************************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _rng_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./rng.js */ "./node_modules/uuid/dist/esm-browser/rng.js");
/* harmony import */ var _stringify_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./stringify.js */ "./node_modules/uuid/dist/esm-browser/stringify.js");

 // **`v1()` - Generate time-based UUID**
//
// Inspired by https://github.com/LiosK/UUID.js
// and http://docs.python.org/library/uuid.html

var _nodeId;

var _clockseq; // Previous uuid creation time


var _lastMSecs = 0;
var _lastNSecs = 0; // See https://github.com/uuidjs/uuid for API details

function v1(options, buf, offset) {
  var i = buf && offset || 0;
  var b = buf || new Array(16);
  options = options || {};
  var node = options.node || _nodeId;
  var clockseq = options.clockseq !== undefined ? options.clockseq : _clockseq; // node and clockseq need to be initialized to random values if they're not
  // specified.  We do this lazily to minimize issues related to insufficient
  // system entropy.  See #189

  if (node == null || clockseq == null) {
    var seedBytes = options.random || (options.rng || _rng_js__WEBPACK_IMPORTED_MODULE_0__["default"])();

    if (node == null) {
      // Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1)
      node = _nodeId = [seedBytes[0] | 0x01, seedBytes[1], seedBytes[2], seedBytes[3], seedBytes[4], seedBytes[5]];
    }

    if (clockseq == null) {
      // Per 4.2.2, randomize (14 bit) clockseq
      clockseq = _clockseq = (seedBytes[6] << 8 | seedBytes[7]) & 0x3fff;
    }
  } // UUID timestamps are 100 nano-second units since the Gregorian epoch,
  // (1582-10-15 00:00).  JSNumbers aren't precise enough for this, so
  // time is handled internally as 'msecs' (integer milliseconds) and 'nsecs'
  // (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01 00:00.


  var msecs = options.msecs !== undefined ? options.msecs : Date.now(); // Per 4.2.1.2, use count of uuid's generated during the current clock
  // cycle to simulate higher resolution clock

  var nsecs = options.nsecs !== undefined ? options.nsecs : _lastNSecs + 1; // Time since last uuid creation (in msecs)

  var dt = msecs - _lastMSecs + (nsecs - _lastNSecs) / 10000; // Per 4.2.1.2, Bump clockseq on clock regression

  if (dt < 0 && options.clockseq === undefined) {
    clockseq = clockseq + 1 & 0x3fff;
  } // Reset nsecs if clock regresses (new clockseq) or we've moved onto a new
  // time interval


  if ((dt < 0 || msecs > _lastMSecs) && options.nsecs === undefined) {
    nsecs = 0;
  } // Per 4.2.1.2 Throw error if too many uuids are requested


  if (nsecs >= 10000) {
    throw new Error("uuid.v1(): Can't create more than 10M uuids/sec");
  }

  _lastMSecs = msecs;
  _lastNSecs = nsecs;
  _clockseq = clockseq; // Per 4.1.4 - Convert from unix epoch to Gregorian epoch

  msecs += 12219292800000; // `time_low`

  var tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000;
  b[i++] = tl >>> 24 & 0xff;
  b[i++] = tl >>> 16 & 0xff;
  b[i++] = tl >>> 8 & 0xff;
  b[i++] = tl & 0xff; // `time_mid`

  var tmh = msecs / 0x100000000 * 10000 & 0xfffffff;
  b[i++] = tmh >>> 8 & 0xff;
  b[i++] = tmh & 0xff; // `time_high_and_version`

  b[i++] = tmh >>> 24 & 0xf | 0x10; // include version

  b[i++] = tmh >>> 16 & 0xff; // `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant)

  b[i++] = clockseq >>> 8 | 0x80; // `clock_seq_low`

  b[i++] = clockseq & 0xff; // `node`

  for (var n = 0; n < 6; ++n) {
    b[i + n] = node[n];
  }

  return buf || (0,_stringify_js__WEBPACK_IMPORTED_MODULE_1__["default"])(b);
}

/* harmony default export */ __webpack_exports__["default"] = (v1);

/***/ }),

/***/ "./node_modules/uuid/dist/esm-browser/v3.js":
/*!**************************************************!*\
  !*** ./node_modules/uuid/dist/esm-browser/v3.js ***!
  \**************************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _v35_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./v35.js */ "./node_modules/uuid/dist/esm-browser/v35.js");
/* harmony import */ var _md5_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./md5.js */ "./node_modules/uuid/dist/esm-browser/md5.js");


var v3 = (0,_v35_js__WEBPACK_IMPORTED_MODULE_0__["default"])('v3', 0x30, _md5_js__WEBPACK_IMPORTED_MODULE_1__["default"]);
/* harmony default export */ __webpack_exports__["default"] = (v3);

/***/ }),

/***/ "./node_modules/uuid/dist/esm-browser/v35.js":
/*!***************************************************!*\
  !*** ./node_modules/uuid/dist/esm-browser/v35.js ***!
  \***************************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "DNS": function() { return /* binding */ DNS; },
/* harmony export */   "URL": function() { return /* binding */ URL; },
/* harmony export */   "default": function() { return /* export default binding */ __WEBPACK_DEFAULT_EXPORT__; }
/* harmony export */ });
/* harmony import */ var _stringify_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./stringify.js */ "./node_modules/uuid/dist/esm-browser/stringify.js");
/* harmony import */ var _parse_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./parse.js */ "./node_modules/uuid/dist/esm-browser/parse.js");



function stringToBytes(str) {
  str = unescape(encodeURIComponent(str)); // UTF8 escape

  var bytes = [];

  for (var i = 0; i < str.length; ++i) {
    bytes.push(str.charCodeAt(i));
  }

  return bytes;
}

var DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
var URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8';
/* harmony default export */ function __WEBPACK_DEFAULT_EXPORT__(name, version, hashfunc) {
  function generateUUID(value, namespace, buf, offset) {
    if (typeof value === 'string') {
      value = stringToBytes(value);
    }

    if (typeof namespace === 'string') {
      namespace = (0,_parse_js__WEBPACK_IMPORTED_MODULE_0__["default"])(namespace);
    }

    if (namespace.length !== 16) {
      throw TypeError('Namespace must be array-like (16 iterable integer values, 0-255)');
    } // Compute hash of namespace and value, Per 4.3
    // Future: Use spread syntax when supported on all platforms, e.g. `bytes =
    // hashfunc([...namespace, ... value])`


    var bytes = new Uint8Array(16 + value.length);
    bytes.set(namespace);
    bytes.set(value, namespace.length);
    bytes = hashfunc(bytes);
    bytes[6] = bytes[6] & 0x0f | version;
    bytes[8] = bytes[8] & 0x3f | 0x80;

    if (buf) {
      offset = offset || 0;

      for (var i = 0; i < 16; ++i) {
        buf[offset + i] = bytes[i];
      }

      return buf;
    }

    return (0,_stringify_js__WEBPACK_IMPORTED_MODULE_1__["default"])(bytes);
  } // Function#name is not settable on some platforms (#270)


  try {
    generateUUID.name = name; // eslint-disable-next-line no-empty
  } catch (err) {} // For CommonJS default export support


  generateUUID.DNS = DNS;
  generateUUID.URL = URL;
  return generateUUID;
}

/***/ }),

/***/ "./node_modules/uuid/dist/esm-browser/v4.js":
/*!**************************************************!*\
  !*** ./node_modules/uuid/dist/esm-browser/v4.js ***!
  \**************************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _rng_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./rng.js */ "./node_modules/uuid/dist/esm-browser/rng.js");
/* harmony import */ var _stringify_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./stringify.js */ "./node_modules/uuid/dist/esm-browser/stringify.js");



function v4(options, buf, offset) {
  options = options || {};
  var rnds = options.random || (options.rng || _rng_js__WEBPACK_IMPORTED_MODULE_0__["default"])(); // Per 4.4, set bits for version and `clock_seq_hi_and_reserved`

  rnds[6] = rnds[6] & 0x0f | 0x40;
  rnds[8] = rnds[8] & 0x3f | 0x80; // Copy bytes to buffer, if provided

  if (buf) {
    offset = offset || 0;

    for (var i = 0; i < 16; ++i) {
      buf[offset + i] = rnds[i];
    }

    return buf;
  }

  return (0,_stringify_js__WEBPACK_IMPORTED_MODULE_1__["default"])(rnds);
}

/* harmony default export */ __webpack_exports__["default"] = (v4);

/***/ }),

/***/ "./node_modules/uuid/dist/esm-browser/v5.js":
/*!**************************************************!*\
  !*** ./node_modules/uuid/dist/esm-browser/v5.js ***!
  \**************************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _v35_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./v35.js */ "./node_modules/uuid/dist/esm-browser/v35.js");
/* harmony import */ var _sha1_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./sha1.js */ "./node_modules/uuid/dist/esm-browser/sha1.js");


var v5 = (0,_v35_js__WEBPACK_IMPORTED_MODULE_0__["default"])('v5', 0x50, _sha1_js__WEBPACK_IMPORTED_MODULE_1__["default"]);
/* harmony default export */ __webpack_exports__["default"] = (v5);

/***/ }),

/***/ "./node_modules/uuid/dist/esm-browser/validate.js":
/*!********************************************************!*\
  !*** ./node_modules/uuid/dist/esm-browser/validate.js ***!
  \********************************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _regex_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./regex.js */ "./node_modules/uuid/dist/esm-browser/regex.js");


function validate(uuid) {
  return typeof uuid === 'string' && _regex_js__WEBPACK_IMPORTED_MODULE_0__["default"].test(uuid);
}

/* harmony default export */ __webpack_exports__["default"] = (validate);

/***/ }),

/***/ "./node_modules/uuid/dist/esm-browser/version.js":
/*!*******************************************************!*\
  !*** ./node_modules/uuid/dist/esm-browser/version.js ***!
  \*******************************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _validate_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./validate.js */ "./node_modules/uuid/dist/esm-browser/validate.js");


function version(uuid) {
  if (!(0,_validate_js__WEBPACK_IMPORTED_MODULE_0__["default"])(uuid)) {
    throw TypeError('Invalid UUID');
  }

  return parseInt(uuid.substr(14, 1), 16);
}

/* harmony default export */ __webpack_exports__["default"] = (version);

/***/ }),

/***/ "./node_modules/which-typed-array/index.js":
/*!*************************************************!*\
  !*** ./node_modules/which-typed-array/index.js ***!
  \*************************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";


var forEach = __webpack_require__(/*! for-each */ "./node_modules/for-each/index.js");

var availableTypedArrays = __webpack_require__(/*! available-typed-arrays */ "./node_modules/available-typed-arrays/index.js");

var callBound = __webpack_require__(/*! call-bind/callBound */ "./node_modules/call-bind/callBound.js");

var $toString = callBound('Object.prototype.toString');

var hasToStringTag = __webpack_require__(/*! has-tostringtag/shams */ "./node_modules/has-tostringtag/shams.js")();

var g = typeof globalThis === 'undefined' ? __webpack_require__.g : globalThis;
var typedArrays = availableTypedArrays();
var $slice = callBound('String.prototype.slice');
var toStrTags = {};

var gOPD = __webpack_require__(/*! es-abstract/helpers/getOwnPropertyDescriptor */ "./node_modules/es-abstract/helpers/getOwnPropertyDescriptor.js");

var getPrototypeOf = Object.getPrototypeOf; // require('getprototypeof');

if (hasToStringTag && gOPD && getPrototypeOf) {
  forEach(typedArrays, function (typedArray) {
    if (typeof g[typedArray] === 'function') {
      var arr = new g[typedArray]();

      if (Symbol.toStringTag in arr) {
        var proto = getPrototypeOf(arr);
        var descriptor = gOPD(proto, Symbol.toStringTag);

        if (!descriptor) {
          var superProto = getPrototypeOf(proto);
          descriptor = gOPD(superProto, Symbol.toStringTag);
        }

        toStrTags[typedArray] = descriptor.get;
      }
    }
  });
}

var tryTypedArrays = function tryAllTypedArrays(value) {
  var foundName = false;
  forEach(toStrTags, function (getter, typedArray) {
    if (!foundName) {
      try {
        var name = getter.call(value);

        if (name === typedArray) {
          foundName = name;
        }
      } catch (e) {}
    }
  });
  return foundName;
};

var isTypedArray = __webpack_require__(/*! is-typed-array */ "./node_modules/is-typed-array/index.js");

module.exports = function whichTypedArray(value) {
  if (!isTypedArray(value)) {
    return false;
  }

  if (!hasToStringTag || !(Symbol.toStringTag in value)) {
    return $slice($toString(value), 8, -1);
  }

  return tryTypedArrays(value);
};

/***/ }),

/***/ "./src/constants.ts":
/*!**************************!*\
  !*** ./src/constants.ts ***!
  \**************************/
/***/ (function(__unused_webpack_module, exports) {

"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.INFINITE_RETRIES = exports.HTTP_RESPONSE_CODES = exports.fatalWebsocketCloseCodes = exports.WEBSOCKET_CONNECTION_CLOSE_CODES = exports.IGNORE_CID = void 0;
/**
 * @deprecated
 */
exports.IGNORE_CID = false;
exports.WEBSOCKET_CONNECTION_CLOSE_CODES = {
    ABNORMAL_CLOSURE: 1006,
    MANUAL_CLOSURE: 4001,
    INTERNAL_CLOSURE: 4002,
    AUTHENTICATION_FAILED: 4003,
    IDENTITY_FAILED: 4004,
    HEARTBEAT_TIMEOUT: 4005,
    HEARTBEAT_FAILED: 4006,
    WEBSOCKET_INITIALIZATION_FAILED: 4007, // Close by websocket initialization error. Reconnect strategy will be not invoke
};
// Codes that will not trigger reconnect strategy
exports.fatalWebsocketCloseCodes = [
    exports.WEBSOCKET_CONNECTION_CLOSE_CODES.AUTHENTICATION_FAILED,
    exports.WEBSOCKET_CONNECTION_CLOSE_CODES.IDENTITY_FAILED,
    exports.WEBSOCKET_CONNECTION_CLOSE_CODES.MANUAL_CLOSURE,
    exports.WEBSOCKET_CONNECTION_CLOSE_CODES.WEBSOCKET_INITIALIZATION_FAILED,
];
exports.HTTP_RESPONSE_CODES = {
    UNAUTHORIZED: 401,
    TIMEOUT: 504
};
exports.INFINITE_RETRIES = -1;


/***/ }),

/***/ "./src/defaults.ts":
/*!*************************!*\
  !*** ./src/defaults.ts ***!
  \*************************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {

"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.defaultOptions = void 0;
var reconnect_1 = __webpack_require__(/*! ./reconnect */ "./src/reconnect.ts");
var routes_1 = __webpack_require__(/*! ./routes */ "./src/routes.ts");
var keep_alive_1 = __webpack_require__(/*! ./keep-alive */ "./src/keep-alive.ts");
var constants_1 = __webpack_require__(/*! ./constants */ "./src/constants.ts");
exports.defaultOptions = {
    accessToken: "",
    autoStart: true,
    domain: "spiralscout.dev.appspace.com",
    idToken: "",
    reconnect: (0, reconnect_1.DefaultReconnectStrategy)({
        delay: 1000,
        retries: constants_1.INFINITE_RETRIES,
        exponentialMultiplier: 2,
        fatalWebsocketCode: constants_1.fatalWebsocketCloseCodes,
        maxDelay: 60000
    }),
    routes: routes_1.routes,
    keepAliveOptions: keep_alive_1.defaultKeepAliveOptions
};


/***/ }),

/***/ "./src/eventdispatcher/callback-registry.ts":
/*!**************************************************!*\
  !*** ./src/eventdispatcher/callback-registry.ts ***!
  \**************************************************/
/***/ (function(__unused_webpack_module, exports) {

"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.CallbackRegistry = void 0;
var CallbackRegistry = /** @class */ (function () {
    function CallbackRegistry() {
        this.callbacks = {};
    }
    CallbackRegistry.prototype.get = function (name) {
        var results = this.callbacks[name];
        return results || [];
    };
    CallbackRegistry.prototype.add = function (name, callback) {
        var _a;
        if (!this.callbacks[name]) {
            this.callbacks[name] = [];
        }
        (_a = this.callbacks[name]) === null || _a === void 0 ? void 0 : _a.push({
            fn: callback,
        });
    };
    CallbackRegistry.prototype.remove = function (name, callback) {
        // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
        if (!name && !callback) {
            this.callbacks = {};
            return;
        }
        var names = name ? [name] : Object.keys(this.callbacks);
        if (callback) {
            this.removeCallback(names, callback);
        }
        else {
            this.removeAllCallbacks(names);
        }
    };
    CallbackRegistry.prototype.removeCallback = function (names, callback) {
        var _this = this;
        names.forEach(function (name) {
            var callbacks = _this.callbacks[name] || [];
            _this.callbacks[name] = callbacks.filter(function (existedCallback) { return callback !== existedCallback.fn; });
        });
    };
    CallbackRegistry.prototype.removeAllCallbacks = function (names) {
        var _this = this;
        names.forEach(function (name) {
            delete _this.callbacks[name];
        });
    };
    return CallbackRegistry;
}());
exports.CallbackRegistry = CallbackRegistry;


/***/ }),

/***/ "./src/eventdispatcher/events-dispatcher.ts":
/*!**************************************************!*\
  !*** ./src/eventdispatcher/events-dispatcher.ts ***!
  \**************************************************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {

"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.EventsDispatcher = void 0;
var callback_registry_1 = __webpack_require__(/*! ./callback-registry */ "./src/eventdispatcher/callback-registry.ts");
var EventsDispatcher = /** @class */ (function () {
    function EventsDispatcher() {
        this.callbacks = new callback_registry_1.CallbackRegistry();
    }
    EventsDispatcher.prototype.on = function (eventName, callback) {
        this.callbacks.add(eventName, callback);
        return this;
    };
    EventsDispatcher.prototype.off = function (eventName, callback) {
        this.callbacks.remove(eventName, callback);
        return this;
    };
    EventsDispatcher.prototype.fire = function (eventName, event) {
        var callbacks = this.callbacks.get(eventName);
        var hasCallbacks = callbacks.length > 0;
        if (hasCallbacks) {
            callbacks.forEach(function (callback) {
                callback.fn(event);
            });
        }
        return this;
    };
    return EventsDispatcher;
}());
exports.EventsDispatcher = EventsDispatcher;


/***/ }),

/***/ "./src/eventdispatcher/types.ts":
/*!**************************************!*\
  !*** ./src/eventdispatcher/types.ts ***!
  \**************************************/
/***/ (function(__unused_webpack_module, exports) {

"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));


/***/ }),

/***/ "./src/index.ts":
/*!**********************!*\
  !*** ./src/index.ts ***!
  \**********************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {

"use strict";

var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
    for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.defaultOptions = void 0;
__exportStar(__webpack_require__(/*! ./constants */ "./src/constants.ts"), exports);
__exportStar(__webpack_require__(/*! ./pns-client */ "./src/pns-client.ts"), exports);
__exportStar(__webpack_require__(/*! ./types/websocket-command */ "./src/types/websocket-command.ts"), exports);
__exportStar(__webpack_require__(/*! ./eventdispatcher/events-dispatcher */ "./src/eventdispatcher/events-dispatcher.ts"), exports);
__exportStar(__webpack_require__(/*! ./eventdispatcher/callback-registry */ "./src/eventdispatcher/callback-registry.ts"), exports);
__exportStar(__webpack_require__(/*! ./eventdispatcher/types */ "./src/eventdispatcher/types.ts"), exports);
__exportStar(__webpack_require__(/*! ./reconnect */ "./src/reconnect.ts"), exports);
__exportStar(__webpack_require__(/*! ./types/pns-events */ "./src/types/pns-events.ts"), exports);
__exportStar(__webpack_require__(/*! ./types/message-wrapper */ "./src/types/message-wrapper.ts"), exports);
__exportStar(__webpack_require__(/*! ./types/jwt-payload */ "./src/types/jwt-payload.ts"), exports);
__exportStar(__webpack_require__(/*! ./utils/autobind */ "./src/utils/autobind.ts"), exports);
__exportStar(__webpack_require__(/*! ./utils/jwt */ "./src/utils/jwt.ts"), exports);
__exportStar(__webpack_require__(/*! ./utils/payload */ "./src/utils/payload.ts"), exports);
__exportStar(__webpack_require__(/*! ./types/pns-state */ "./src/types/pns-state.ts"), exports);
__exportStar(__webpack_require__(/*! ./types/base-broadcast-payload */ "./src/types/base-broadcast-payload.ts"), exports);
__exportStar(__webpack_require__(/*! ./types/pns-attribute-item */ "./src/types/pns-attribute-item.ts"), exports);
__exportStar(__webpack_require__(/*! ./types/connection-options */ "./src/types/connection-options.ts"), exports);
var defaults_1 = __webpack_require__(/*! ./defaults */ "./src/defaults.ts");
Object.defineProperty(exports, "defaultOptions", ({ enumerable: true, get: function () { return defaults_1.defaultOptions; } }));


/***/ }),

/***/ "./src/keep-alive.ts":
/*!***************************!*\
  !*** ./src/keep-alive.ts ***!
  \***************************/
/***/ (function(__unused_webpack_module, exports) {

"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.defaultKeepAliveOptions = void 0;
exports.defaultKeepAliveOptions = {
    isEnabled: false,
    interval: 900000,
    timeout: 30000,
    triggerOnFocus: true,
};


/***/ }),

/***/ "./src/pns-client.ts":
/*!***************************!*\
  !*** ./src/pns-client.ts ***!
  \***************************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {

"use strict";

var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        if (typeof b !== "function" && b !== null)
            throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (_) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.PnsClient = void 0;
var assert_1 = __importDefault(__webpack_require__(/*! assert */ "./node_modules/assert/build/assert.js"));
var uuid_1 = __webpack_require__(/*! uuid */ "./node_modules/uuid/dist/esm-browser/index.js");
var constants_1 = __webpack_require__(/*! ./constants */ "./src/constants.ts");
var events_dispatcher_1 = __webpack_require__(/*! ./eventdispatcher/events-dispatcher */ "./src/eventdispatcher/events-dispatcher.ts");
var pns_events_1 = __webpack_require__(/*! ./types/pns-events */ "./src/types/pns-events.ts");
var websocket_command_1 = __webpack_require__(/*! ./types/websocket-command */ "./src/types/websocket-command.ts");
var autobind_1 = __webpack_require__(/*! ./utils/autobind */ "./src/utils/autobind.ts");
var jwt_1 = __webpack_require__(/*! ./utils/jwt */ "./src/utils/jwt.ts");
var payload_1 = __webpack_require__(/*! ./utils/payload */ "./src/utils/payload.ts");
var defaults_1 = __webpack_require__(/*! ./defaults */ "./src/defaults.ts");
var default_message_processor_1 = __webpack_require__(/*! ./utils/default-message-processor */ "./src/utils/default-message-processor.ts");
var keep_alive_1 = __webpack_require__(/*! ./keep-alive */ "./src/keep-alive.ts");
var PnsClient = /** @class */ (function (_super) {
    __extends(PnsClient, _super);
    function PnsClient(options) {
        var _this = _super.call(this) || this;
        _this.wasConnected = false;
        _this.DEFAULT_SET_IDENTITY_COMMAND = websocket_command_1.WebsocketCommandValue.AddIdentity;
        _this.setIdentityCommand = _this.DEFAULT_SET_IDENTITY_COMMAND;
        _this.state = {
            isConnecting: false,
            isWebsocketOpened: false,
            isAuthenticating: false,
            isAuthenticated: false,
            isConnected: false,
            isStopped: false,
            commands: {},
            retryAttemptState: undefined,
        };
        _this.keepAliveOptions = keep_alive_1.defaultKeepAliveOptions;
        _this.pingRequestTimeout = null;
        _this.heartbeatCheckTimeout = null;
        _this.isHeartbeatRunning = false;
        _this.options = __assign(__assign({}, defaults_1.defaultOptions), options);
        _this.validate(_this.options);
        if (!_this.options.autoStart) {
            return _this;
        }
        setTimeout(function () {
            _this.start();
        }, 0);
        return _this;
    }
    /**
     * Starts WS connection if not already started
     * Returns false if already connected or connecting
     */
    PnsClient.prototype.start = function () {
        if (this.state.isConnecting || this.state.isConnected) {
            this.fire(pns_events_1.PNSEvents.debug, {
                level: "message",
                message: "Already connecting, skipping",
            });
            return false;
        }
        this.bindEvents();
        this.transitionToConnectionStarted();
        return this.startWebSocket();
    };
    /**
     * External stop
     * Destroys connection, closes WS and unbinds all events.
     */
    PnsClient.prototype.stop = function () {
        if (this.state.isStopped) {
            return;
        }
        // Gracefully close websocket with 4001 if opened
        if (this.state.isWebsocketOpened || this.socket.readyState == WebSocket.OPEN) {
            this.closeWebSocket(constants_1.WEBSOCKET_CONNECTION_CLOSE_CODES.MANUAL_CLOSURE);
            return;
        }
        this.transitionToConnectionClosed(constants_1.WEBSOCKET_CONNECTION_CLOSE_CODES.MANUAL_CLOSURE);
    };
    /**
     * Sets or updates identity used by PNS connection
     * If fails, promise rejects and internal options for connection are not updated
     * If OK, fires 'updated-identity' event and updates token data inside
     * @param accessToken
     * @param identityAttrs
     * @param idToken
     */
    PnsClient.prototype.cmdSetIdentity = function (_a) {
        var accessToken = _a.accessToken, identityAttrs = _a.identityAttrs, idToken = _a.idToken;
        return __awaiter(this, void 0, void 0, function () {
            var token, atoken, identityCommand;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        token = (0, jwt_1.decodeJwt)(idToken);
                        assert_1.default.ok((0, jwt_1.isAlikeIdToken)(token), "Id Token should have instanceId");
                        // This needs to be update so the next reconnect will use the new identity
                        this.options.idToken = idToken;
                        this.options.identityAttrs = identityAttrs;
                        if (accessToken) {
                            atoken = (0, jwt_1.decodeJwt)(accessToken);
                            assert_1.default.ok((0, jwt_1.isAlikeAccessToken)(atoken), "Access Token should have sourceId");
                            this.options.accessToken = accessToken;
                        }
                        if (!this.state.isWebsocketOpened) {
                            return [2 /*return*/];
                        }
                        identityCommand = this.setIdentityCommand;
                        // Need to update local value before try to send the command to PNS
                        // This is needed to prevent race condition on the next cmdSetIdentity is called before the current response is received
                        if (this.setIdentityCommand === websocket_command_1.WebsocketCommandValue.AddIdentity) {
                            this.setIdentityCommand = websocket_command_1.WebsocketCommandValue.UpdateIdentity;
                        }
                        return [4 /*yield*/, this.send({
                                command: identityCommand,
                                operationId: (0, uuid_1.v4)(),
                                attributes: identityAttrs,
                                idToken: idToken,
                            })];
                    case 1:
                        _b.sent();
                        this.fire(pns_events_1.PNSEvents.updatedIdentity, {
                            accessToken: accessToken,
                            idToken: idToken,
                            identityAttrs: identityAttrs,
                        });
                        this.setState(__assign(__assign({}, this.state), { currentIdentityAttrs: identityAttrs }));
                        return [2 /*return*/];
                }
            });
        });
    };
    Object.defineProperty(PnsClient.prototype, "isConnected", {
        get: function () {
            return this.state.isConnected;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(PnsClient.prototype, "isConnecting", {
        get: function () {
            return this.state.isConnecting;
        },
        enumerable: false,
        configurable: true
    });
    PnsClient.prototype.validate = function (options) {
        assert_1.default.ok(options.domain, "Domain should be specified");
        assert_1.default.ok(options.accessToken, "Access Token should be specified");
        assert_1.default.ok(options.idToken, "Id Token should be specified");
        var accessToken = (0, jwt_1.decodeJwt)(options.accessToken);
        assert_1.default.ok((0, jwt_1.isAlikeAccessToken)(accessToken), "Access Token should have sourceId");
        var idToken = (0, jwt_1.decodeJwt)(options.idToken);
        assert_1.default.ok((0, jwt_1.isAlikeIdToken)(idToken), "Id Token should have instanceId");
    };
    PnsClient.prototype.startWebSocket = function () {
        try {
            this.socket = new WebSocket("wss://".concat(this.options.domain).concat(this.options.routes.notificationServiceWebsockets));
            this.bindWebsocketEvents();
            return true;
        }
        catch (e) {
            this.transitionToConnectionError(e);
            this.transitionToConnectionClosed(constants_1.WEBSOCKET_CONNECTION_CLOSE_CODES.WEBSOCKET_INITIALIZATION_FAILED);
        }
        return false;
    };
    /**
     * Close and recycle the websocket.
     * @param websocketStatusCode
     * @param reason
     */
    PnsClient.prototype.closeWebSocket = function (websocketStatusCode) {
        this.socket.close(websocketStatusCode);
        this.cancelHeartbeatCheckTimeout();
        this.cancelPingRequestTimeout();
        this.isHeartbeatRunning = false;
    };
    PnsClient.prototype.bindEvents = function () {
        if (typeof window !== 'undefined' && typeof window.addEventListener === 'function') {
            // eslint-disable-next-line  @typescript-eslint/unbound-method, @typescript-eslint/no-misused-promises
            window.addEventListener('focus', this.onWindowFocus);
        }
    };
    PnsClient.prototype.unbindEvents = function () {
        if (typeof window !== 'undefined' && typeof window.removeEventListener === 'function') {
            // eslint-disable-next-line  @typescript-eslint/unbound-method, @typescript-eslint/no-misused-promises
            window.removeEventListener('focus', this.onWindowFocus);
        }
    };
    PnsClient.prototype.bindWebsocketEvents = function () {
        // eslint-disable-next-line  @typescript-eslint/unbound-method
        this.socket.addEventListener("open", this.onWebsocketOpen);
        // eslint-disable-next-line  @typescript-eslint/unbound-method
        this.socket.addEventListener("message", this.onWebsocketMessage);
        // eslint-disable-next-line  @typescript-eslint/unbound-method
        this.socket.addEventListener("close", this.onWebsocketClose);
        // eslint-disable-next-line  @typescript-eslint/unbound-method
        this.socket.addEventListener("error", this.onWebsocketError);
    };
    PnsClient.prototype.unbindWebsocketEvents = function () {
        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
        if (!this.socket) {
            return;
        }
        // eslint-disable-next-line  @typescript-eslint/unbound-method
        this.socket.removeEventListener("open", this.onWebsocketOpen);
        // eslint-disable-next-line  @typescript-eslint/unbound-method
        this.socket.removeEventListener("message", this.onWebsocketMessage);
        // eslint-disable-next-line  @typescript-eslint/unbound-method
        this.socket.removeEventListener("close", this.onWebsocketClose);
        // eslint-disable-next-line  @typescript-eslint/unbound-method
        this.socket.removeEventListener("error", this.onWebsocketError);
    };
    PnsClient.prototype.onWebsocketMessage = function (event) {
        var _this = this;
        var _a;
        this.fire(pns_events_1.PNSEvents.debug, {
            level: "message",
            // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/restrict-template-expressions
            message: "On websocket message.  eventDataLength: ".concat((_a = event.data) === null || _a === void 0 ? void 0 : _a.size),
        });
        if (!(event.data instanceof Blob)) {
            this.fire(pns_events_1.PNSEvents.message, {
                error: "Non-binary data received from websocket",
                valid: false,
            });
            return;
        }
        (this.options.customMessageProcessor || default_message_processor_1.defaultMessageProcessor)(event.data)
            .then(function (text) {
            try {
                _this.fire(pns_events_1.PNSEvents.debug, {
                    level: "message",
                    message: "Message processed. text: ".concat(text),
                });
                // Regular message
                // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
                var payload = JSON.parse(text);
                _this.fire(pns_events_1.PNSEvents.message, {
                    valid: true,
                    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
                    payload: payload,
                });
            }
            catch (_e1) {
                try {
                    // Base54 encoded one
                    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
                    var payload = JSON.parse((0, jwt_1.atobUTF8)(text));
                    _this.fire(pns_events_1.PNSEvents.message, {
                        valid: true,
                        // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
                        payload: payload,
                    });
                }
                catch (_e2) {
                    _this.fire(pns_events_1.PNSEvents.message, {
                        valid: false,
                        error: "Could not parse JSON response",
                    });
                }
            }
        })
            .catch(function () {
            _this.fire(pns_events_1.PNSEvents.message, {
                valid: false,
                error: "Could not parse binary data",
            });
        });
    };
    PnsClient.prototype.resetClientState = function () {
        this.setState({
            isWebsocketOpened: false,
            isConnecting: false,
            isAuthenticating: false,
            isAuthenticated: false,
            isConnected: false,
            commands: {},
            error: undefined,
        });
    };
    PnsClient.prototype.onWebsocketClose = function (event) {
        var _a;
        var reason;
        var code = event.code;
        // Handle websocket close during heartbeat is running.
        // There is scenario when heartbeat is running but the websocket is closed before the ping timeout elapsed.
        if (this.isHeartbeatRunning && event.code === constants_1.WEBSOCKET_CONNECTION_CLOSE_CODES.ABNORMAL_CLOSURE) {
            code = constants_1.WEBSOCKET_CONNECTION_CLOSE_CODES.HEARTBEAT_FAILED;
            this.isHeartbeatRunning = false;
        }
        this.cancelHeartbeatCheckTimeout();
        this.cancelPingRequestTimeout();
        try {
            // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
            reason = JSON.parse(event.reason);
            // eslint-disable-next-line no-empty
        }
        catch (e) { }
        var resolvedCode = this.resolveClosureCode(code);
        var resolvedCodeText = (_a = Object.entries(constants_1.WEBSOCKET_CONNECTION_CLOSE_CODES).find(function (_a) {
            var value = _a[1];
            return value === resolvedCode;
        })) === null || _a === void 0 ? void 0 : _a[0];
        this.fire(pns_events_1.PNSEvents.debug, {
            level: "message",
            data: reason || resolvedCodeText,
            message: "WebSocket closed: ".concat(resolvedCode, ", original code: ").concat(code),
        });
        // Only fire on first close after open to prevent duplicate events during failed reconnection
        if (this.state.isWebsocketOpened) {
            this.fire(pns_events_1.PNSEvents.disconnected, {
                code: resolvedCode,
                message: resolvedCodeText || "Unknown",
            });
        }
        // Reset state on close
        this.resetClientState();
        this.setIdentityCommand = websocket_command_1.WebsocketCommandValue.AddIdentity;
        // Websocket is event will be rebind when initialization
        // Hence we need to unbind first
        this.unbindWebsocketEvents();
        this.reconnect(resolvedCode);
    };
    PnsClient.prototype.onWebsocketError = function (event) {
        this.fire(pns_events_1.PNSEvents.debug, {
            level: "error",
            message: "WebSocket Error",
            originalError: event,
        });
        this.fire(pns_events_1.PNSEvents.error, {
            code: 900,
            message: "Websocket Error",
            originalError: event
        });
    };
    PnsClient.prototype.onWebsocketOpen = function (event) {
        this.fire(pns_events_1.PNSEvents.debug, {
            level: "message",
            data: event,
            message: "Websocket Opened",
        });
        this.setState({
            isWebsocketOpened: true,
        });
        // eslint-disable-next-line @typescript-eslint/no-floating-promises
        this.connectToPNS();
    };
    PnsClient.prototype.onWindowFocus = function () {
        return __awaiter(this, void 0, void 0, function () {
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        this.keepAliveOptions = __assign(__assign({}, keep_alive_1.defaultKeepAliveOptions), this.options.keepAliveOptions);
                        if (!this.keepAliveOptions.isEnabled) {
                            this.fire(pns_events_1.PNSEvents.heartbeat, {
                                status: "skipped",
                                reason: "Keep alive is disabled"
                            });
                            return [2 /*return*/];
                        }
                        if (!this.keepAliveOptions.triggerOnFocus) {
                            this.fire(pns_events_1.PNSEvents.debug, {
                                level: "message",
                                message: "Window focused but not configured to trigger heartbeat. Skipping heartbeat.",
                            });
                            return [2 /*return*/];
                        }
                        if (!this.state.isConnected) {
                            this.fire(pns_events_1.PNSEvents.debug, {
                                level: "message",
                                message: "Window focused but not connected. Skipping heartbeat.",
                            });
                            return [2 /*return*/];
                        }
                        return [4 /*yield*/, this.runHeartbeat()];
                    case 1:
                        _a.sent();
                        return [2 /*return*/];
                }
            });
        });
    };
    PnsClient.prototype.setState = function (state) {
        this.state = __assign(__assign({}, this.state), state);
        this.fire(pns_events_1.PNSEvents.state, this.state);
    };
    PnsClient.prototype.resolveClosureCode = function (originalCode) {
        // DO NOT CHANGE THE ORDER OF THIS ARRAY
        // The order of the array is important as it represents the bit position of the flags
        var connectionProgressState = [
            this.state.isConnecting,
            this.state.isWebsocketOpened,
            this.state.isAuthenticating,
            this.state.isAuthenticated,
            this.state.isConnected
        ];
        var stateToCodeMap = {
            '11100': constants_1.WEBSOCKET_CONNECTION_CLOSE_CODES.AUTHENTICATION_FAILED,
            '11010': constants_1.WEBSOCKET_CONNECTION_CLOSE_CODES.IDENTITY_FAILED,
        };
        return stateToCodeMap[this.convertToBits(connectionProgressState)] || originalCode;
    };
    PnsClient.prototype.convertToBits = function (array) {
        var result = '';
        for (var _i = 0, array_1 = array; _i < array_1.length; _i++) {
            var item = array_1[_i];
            result += item ? '1' : '0';
        }
        return result;
    };
    PnsClient.prototype.cancelPingRequestTimeout = function () {
        if (!this.pingRequestTimeout) {
            return;
        }
        clearTimeout(this.pingRequestTimeout);
        this.pingRequestTimeout = null;
    };
    PnsClient.prototype.checkHeartbeat = function () {
        return __awaiter(this, void 0, void 0, function () {
            var pingResponse, responsePayload, error_1;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        _a.trys.push([0, 2, , 3]);
                        return [4 /*yield*/, this.cmdPing()];
                    case 1:
                        pingResponse = _a.sent();
                        responsePayload = pingResponse.payload;
                        if (responsePayload.code !== 200 ||
                            responsePayload.message !== "pong") {
                            throw new Error("Unexpected response received: ".concat(JSON.stringify(responsePayload)));
                        }
                        return [2 /*return*/, true];
                    case 2:
                        error_1 = _a.sent();
                        throw new Error("Ping command failed with: ".concat(error_1.message));
                    case 3: return [2 /*return*/];
                }
            });
        });
    };
    PnsClient.prototype.keepAliveAsync = function () {
        return __awaiter(this, void 0, void 0, function () {
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        this.keepAliveOptions = __assign(__assign({}, keep_alive_1.defaultKeepAliveOptions), this.options.keepAliveOptions);
                        if (!this.keepAliveOptions.isEnabled) {
                            this.fire(pns_events_1.PNSEvents.heartbeat, {
                                status: "skipped",
                                reason: "Keep alive is disabled"
                            });
                            return [2 /*return*/];
                        }
                        _a.label = 1;
                    case 1:
                        if (!this.state.isConnected) return [3 /*break*/, 4];
                        return [4 /*yield*/, this.delayHeartbeatCheck()];
                    case 2:
                        _a.sent();
                        return [4 /*yield*/, this.runHeartbeat()];
                    case 3:
                        _a.sent();
                        return [3 /*break*/, 1];
                    case 4: return [2 /*return*/];
                }
            });
        });
    };
    PnsClient.prototype.runHeartbeat = function () {
        return __awaiter(this, void 0, void 0, function () {
            var error_2;
            var _this = this;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (this.isHeartbeatRunning || !this.state.isConnected) {
                            return [2 /*return*/];
                        }
                        this.isHeartbeatRunning = true;
                        this.fire(pns_events_1.PNSEvents.heartbeat, {
                            status: "in-progress"
                        });
                        _a.label = 1;
                    case 1:
                        _a.trys.push([1, 3, 4, 5]);
                        this.cancelPingRequestTimeout();
                        this.pingRequestTimeout = setTimeout(function () {
                            _this.fire(pns_events_1.PNSEvents.heartbeat, {
                                status: "failed",
                                reason: "Request timed out after ".concat(_this.keepAliveOptions.timeout, "ms")
                            });
                            // Consider websocket close
                            _this.fire(pns_events_1.PNSEvents.disconnected, {
                                code: constants_1.WEBSOCKET_CONNECTION_CLOSE_CODES.HEARTBEAT_TIMEOUT,
                                message: "Ping request timed out",
                            });
                            _this.resetClientState();
                            _this.unbindWebsocketEvents();
                            _this.closeWebSocket(constants_1.WEBSOCKET_CONNECTION_CLOSE_CODES.HEARTBEAT_TIMEOUT);
                            _this.isHeartbeatRunning = false;
                            _this.setIdentityCommand = websocket_command_1.WebsocketCommandValue.AddIdentity;
                            _this.reconnect(constants_1.WEBSOCKET_CONNECTION_CLOSE_CODES.HEARTBEAT_TIMEOUT);
                        }, this.keepAliveOptions.timeout);
                        return [4 /*yield*/, this.checkHeartbeat()];
                    case 2:
                        _a.sent();
                        this.fire(pns_events_1.PNSEvents.heartbeat, {
                            status: "success"
                        });
                        return [3 /*break*/, 5];
                    case 3:
                        error_2 = _a.sent();
                        this.fire(pns_events_1.PNSEvents.heartbeat, {
                            status: "failed",
                            reason: error_2.message
                        });
                        this.closeWebSocket(constants_1.WEBSOCKET_CONNECTION_CLOSE_CODES.HEARTBEAT_FAILED);
                        return [3 /*break*/, 5];
                    case 4:
                        this.cancelPingRequestTimeout();
                        this.isHeartbeatRunning = false;
                        return [7 /*endfinally*/];
                    case 5: return [2 /*return*/];
                }
            });
        });
    };
    PnsClient.prototype.cancelHeartbeatCheckTimeout = function () {
        if (!this.heartbeatCheckTimeout) {
            return;
        }
        clearTimeout(this.heartbeatCheckTimeout);
        this.heartbeatCheckTimeout = null;
    };
    PnsClient.prototype.delayHeartbeatCheck = function () {
        return __awaiter(this, void 0, void 0, function () {
            var randomDelay;
            var _this = this;
            return __generator(this, function (_a) {
                this.cancelHeartbeatCheckTimeout();
                this.cancelPingRequestTimeout();
                randomDelay = Math.floor(Math.random() * 5) * 1000;
                return [2 /*return*/, new Promise(function (resolve) {
                        _this.heartbeatCheckTimeout = setTimeout(resolve, _this.keepAliveOptions.interval + randomDelay);
                    })];
            });
        });
    };
    /**
     * Handles reconnection process, calls reconnect strategy
     */
    PnsClient.prototype.reconnect = function (websocketStatusCode) {
        var _this = this;
        this.fire(pns_events_1.PNSEvents.debug, {
            level: "message",
            message: "Reconnect method called with wsCode:".concat(websocketStatusCode),
        });
        if (websocketStatusCode === constants_1.WEBSOCKET_CONNECTION_CLOSE_CODES.MANUAL_CLOSURE) {
            this.transitionToConnectionClosed(websocketStatusCode);
            return false;
        }
        if (!this.options.reconnect) {
            this.fire(pns_events_1.PNSEvents.debug, {
                level: "message",
                message: "No reconnection strategy.",
            });
            this.transitionToConnectionClosed(websocketStatusCode);
            return false;
        }
        var isReconnected = false;
        this.options
            .reconnect({
            wsCode: websocketStatusCode,
            // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
            prevAttempt: this.state.retryAttemptState
        }, this)
            .then(function (result) {
            var _a, _b;
            if (!result.reconnect) {
                _this.fire(pns_events_1.PNSEvents.debug, {
                    level: "message",
                    message: "Not reconnecting due to websocket code: ".concat(websocketStatusCode),
                });
                _this.transitionToConnectionClosed(websocketStatusCode);
                return false;
            }
            // To prevent stop is called while awaiting the reconnect callback
            if (_this.state.isStopped) {
                return;
            }
            _this.fire(pns_events_1.PNSEvents.reconnecting, websocketStatusCode);
            _this.fire(pns_events_1.PNSEvents.debug, {
                level: "message",
                // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access
                message: "Reconnecting after ".concat((_b = (_a = result.state) === null || _a === void 0 ? void 0 : _a.lastTimer) !== null && _b !== void 0 ? _b : 0, "ms"),
            });
            _this.setState({
                // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
                retryAttemptState: result.state,
            });
            isReconnected = _this.start();
        })
            .catch(function (e) {
            _this.fire(pns_events_1.PNSEvents.error, {
                message: "Reconnection Error",
                originalError: e,
            });
            _this.transitionToConnectionClosed(websocketStatusCode);
        });
        return isReconnected;
    };
    PnsClient.prototype.transitionToConnectionError = function (e) {
        var error = {
            code: 900,
            originalError: e,
            message: "Connection Error",
        };
        this.setState({
            error: error,
        });
        this.fire(pns_events_1.PNSEvents.error, error);
    };
    /**
     * This is called when start() is called
     */
    PnsClient.prototype.transitionToConnectionStarted = function () {
        this.setState({
            isConnecting: true,
            isAuthenticating: false,
            isAuthenticated: false,
            isConnected: false,
            isStopped: false,
            error: undefined,
        });
        this.fire(pns_events_1.PNSEvents.debug, {
            level: "message",
            message: "Connecting with options: ".concat(JSON.stringify(this.options)),
        });
        this.fire(pns_events_1.PNSEvents.connecting, this.wasConnected);
    };
    PnsClient.prototype.transitionToConnectionSuccess = function () {
        this.setState({
            isConnected: true,
            isConnecting: false,
            retryAttemptState: undefined,
        });
        this.fire(pns_events_1.PNSEvents.connected, this.wasConnected);
        this.wasConnected = true;
        // eslint-disable-next-line @typescript-eslint/no-floating-promises
        this.keepAliveAsync();
    };
    /**
     * Only call this when completely closed and no reconnection.
     * @param websocketStatusCode
     */
    PnsClient.prototype.transitionToConnectionClosed = function (websocketStatusCode) {
        this.resetClientState();
        this.setState({
            isStopped: true,
            retryAttemptState: undefined,
        });
        this.wasConnected = false;
        this.setIdentityCommand = websocket_command_1.WebsocketCommandValue.AddIdentity;
        this.unbindEvents();
        this.fire(pns_events_1.PNSEvents.closed, websocketStatusCode);
    };
    PnsClient.prototype.cmdPing = function () {
        return __awaiter(this, void 0, void 0, function () {
            return __generator(this, function (_a) {
                return [2 /*return*/, this.send({
                        command: websocket_command_1.WebsocketCommandValue.Ping,
                        operationId: (0, uuid_1.v4)(),
                    })];
            });
        });
    };
    /**
     * Declares connection to PNS and sets identity
     */
    PnsClient.prototype.connectToPNS = function () {
        return __awaiter(this, void 0, void 0, function () {
            var e_1, e_2;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        this.fire(pns_events_1.PNSEvents.debug, {
                            level: "message",
                            message: "Authenticating",
                        });
                        _a.label = 1;
                    case 1:
                        _a.trys.push([1, 3, , 4]);
                        return [4 /*yield*/, this.cmdSendAuth()];
                    case 2:
                        _a.sent();
                        // Authentication successful
                        this.setState({
                            isAuthenticating: false,
                            isAuthenticated: true,
                        });
                        return [3 /*break*/, 4];
                    case 3:
                        e_1 = _a.sent();
                        this.transitionToConnectionError(e_1);
                        this.closeWebSocket(constants_1.WEBSOCKET_CONNECTION_CLOSE_CODES.AUTHENTICATION_FAILED);
                        return [2 /*return*/];
                    case 4:
                        _a.trys.push([4, 6, , 7]);
                        return [4 /*yield*/, this.cmdSetIdentity({
                                accessToken: this.options.accessToken,
                                idToken: this.options.idToken,
                                identityAttrs: this.options.identityAttrs,
                            })];
                    case 5:
                        _a.sent();
                        return [3 /*break*/, 7];
                    case 6:
                        e_2 = _a.sent();
                        this.transitionToConnectionError(e_2);
                        this.closeWebSocket(constants_1.WEBSOCKET_CONNECTION_CLOSE_CODES.IDENTITY_FAILED);
                        return [2 /*return*/];
                    case 7:
                        this.transitionToConnectionSuccess();
                        return [2 /*return*/];
                }
            });
        });
    };
    PnsClient.prototype.cmdSendAuth = function () {
        return __awaiter(this, void 0, void 0, function () {
            return __generator(this, function (_a) {
                this.setState({
                    isAuthenticating: true,
                    isAuthenticated: false,
                });
                return [2 /*return*/, this.send({
                        command: websocket_command_1.WebsocketCommandValue.Authenticate,
                        operationId: (0, uuid_1.v4)(),
                        accessToken: this.options.accessToken,
                    })];
            });
        });
    };
    PnsClient.prototype.send = function (command) {
        return __awaiter(this, void 0, void 0, function () {
            var _this = this;
            return __generator(this, function (_a) {
                return [2 /*return*/, new Promise(function (resolve, reject) {
                        var _a;
                        var operationId = command.operationId;
                        var onFinalize = function () {
                            var commands = __assign({}, _this.state.commands);
                            delete commands[operationId];
                            _this.setState({
                                commands: commands,
                            });
                            // eslint-disable-next-line @typescript-eslint/no-use-before-define
                            _this.off(pns_events_1.PNSEvents.message, callback);
                        };
                        var callback = function (msg) {
                            if (!msg.valid) {
                                return;
                            }
                            var payload = msg.payload;
                            if ((0, payload_1.isOperationMessage)(payload) &&
                                // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
                                payload.operationId === operationId) {
                                var code = payload.code;
                                if (code < 400) {
                                    resolve(msg);
                                }
                                else {
                                    reject(msg);
                                }
                                onFinalize();
                            }
                        };
                        if (_this.socket.readyState !== WebSocket.OPEN) {
                            return;
                        }
                        _this.setState({
                            commands: __assign(__assign({}, _this.state.commands), (_a = {}, _a[operationId] = { callback: callback, command: command }, _a)),
                        });
                        _this.on(pns_events_1.PNSEvents.message, callback);
                        _this.socket.send(JSON.stringify(command));
                    })];
            });
        });
    };
    __decorate([
        autobind_1.autobind
    ], PnsClient.prototype, "onWebsocketMessage", null);
    __decorate([
        autobind_1.autobind
    ], PnsClient.prototype, "onWebsocketClose", null);
    __decorate([
        autobind_1.autobind
    ], PnsClient.prototype, "onWebsocketError", null);
    __decorate([
        autobind_1.autobind
    ], PnsClient.prototype, "onWebsocketOpen", null);
    __decorate([
        autobind_1.autobind
    ], PnsClient.prototype, "onWindowFocus", null);
    return PnsClient;
}(events_dispatcher_1.EventsDispatcher));
exports.PnsClient = PnsClient;


/***/ }),

/***/ "./src/reconnect.ts":
/*!**************************!*\
  !*** ./src/reconnect.ts ***!
  \**************************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {

"use strict";

var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (_) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.NoReconnectStrategy = exports.DefaultReconnectStrategy = exports.pauseForMs = void 0;
var constants_1 = __webpack_require__(/*! ./constants */ "./src/constants.ts");
var pauseForMs = function (ms) { return __awaiter(void 0, void 0, void 0, function () {
    return __generator(this, function (_a) {
        return [2 /*return*/, new Promise(function (resolve) {
                setTimeout(resolve, ms);
            })];
    });
}); };
exports.pauseForMs = pauseForMs;
var calculateLastTimer = function (prevTimer, delay, exponentialMultiplier, maxDelay) {
    var lastTimer = exponentialMultiplier && prevTimer ? exponentialMultiplier * prevTimer : delay || 0;
    if (maxDelay !== undefined && lastTimer > maxDelay) {
        lastTimer = maxDelay;
    }
    return lastTimer;
};
var shouldReconnect = function (wsCode, fatalWebsocketCode) {
    if (!fatalWebsocketCode) {
        return true;
    }
    return !fatalWebsocketCode.includes(wsCode);
};
var DefaultReconnectStrategy = function (_a) {
    var retries = _a.retries, delay = _a.delay, exponentialMultiplier = _a.exponentialMultiplier, fatalWebsocketCode = _a.fatalWebsocketCode, maxDelay = _a.maxDelay;
    return function (conditions) { return __awaiter(void 0, void 0, void 0, function () {
        var prevTimer, lastTimer, lastTimer;
        var _a, _b, _c, _d;
        return __generator(this, function (_e) {
            switch (_e.label) {
                case 0:
                    fatalWebsocketCode = fatalWebsocketCode || constants_1.fatalWebsocketCloseCodes;
                    if (!shouldReconnect(conditions.wsCode, fatalWebsocketCode)) {
                        return [2 /*return*/, { reconnect: false }];
                    }
                    prevTimer = (_b = (_a = conditions.prevAttempt) === null || _a === void 0 ? void 0 : _a.lastTimer) !== null && _b !== void 0 ? _b : 0;
                    if (!(retries === constants_1.INFINITE_RETRIES)) return [3 /*break*/, 3];
                    lastTimer = calculateLastTimer(prevTimer, delay, exponentialMultiplier, maxDelay);
                    if (!lastTimer) return [3 /*break*/, 2];
                    return [4 /*yield*/, (0, exports.pauseForMs)(lastTimer)];
                case 1:
                    _e.sent();
                    _e.label = 2;
                case 2: return [2 /*return*/, {
                        reconnect: true,
                        state: {
                            attempts: constants_1.INFINITE_RETRIES,
                            lastTimer: lastTimer,
                        },
                    }];
                case 3:
                    if (!(!conditions.prevAttempt || conditions.prevAttempt.attempts < retries)) return [3 /*break*/, 6];
                    lastTimer = calculateLastTimer(prevTimer, delay, exponentialMultiplier, maxDelay);
                    if (!lastTimer) return [3 /*break*/, 5];
                    return [4 /*yield*/, (0, exports.pauseForMs)(lastTimer)];
                case 4:
                    _e.sent();
                    _e.label = 5;
                case 5: return [2 /*return*/, {
                        reconnect: true,
                        state: {
                            attempts: ((_d = (_c = conditions.prevAttempt) === null || _c === void 0 ? void 0 : _c.attempts) !== null && _d !== void 0 ? _d : 0) + 1,
                            lastTimer: lastTimer,
                        },
                    }];
                case 6: return [2 /*return*/, {
                        reconnect: false,
                    }];
            }
        });
    }); };
};
exports.DefaultReconnectStrategy = DefaultReconnectStrategy;
var NoReconnectStrategy = function () {
    // eslint-disable-next-line @typescript-eslint/require-await
    return function () { return __awaiter(void 0, void 0, void 0, function () {
        return __generator(this, function (_a) {
            return [2 /*return*/, Promise.resolve({
                    reconnect: false,
                })];
        });
    }); };
};
exports.NoReconnectStrategy = NoReconnectStrategy;


/***/ }),

/***/ "./src/routes.ts":
/*!***********************!*\
  !*** ./src/routes.ts ***!
  \***********************/
/***/ (function(__unused_webpack_module, exports) {

"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.routes = void 0;
exports.routes = {
    notificationServiceWebsockets: "/api/v3/pushnotifications/ws",
};


/***/ }),

/***/ "./src/types/base-broadcast-payload.ts":
/*!*********************************************!*\
  !*** ./src/types/base-broadcast-payload.ts ***!
  \*********************************************/
/***/ (function(__unused_webpack_module, exports) {

"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));


/***/ }),

/***/ "./src/types/connection-options.ts":
/*!*****************************************!*\
  !*** ./src/types/connection-options.ts ***!
  \*****************************************/
/***/ (function(__unused_webpack_module, exports) {

"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));


/***/ }),

/***/ "./src/types/jwt-payload.ts":
/*!**********************************!*\
  !*** ./src/types/jwt-payload.ts ***!
  \**********************************/
/***/ (function(__unused_webpack_module, exports) {

"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));


/***/ }),

/***/ "./src/types/message-wrapper.ts":
/*!**************************************!*\
  !*** ./src/types/message-wrapper.ts ***!
  \**************************************/
/***/ (function(__unused_webpack_module, exports) {

"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));


/***/ }),

/***/ "./src/types/pns-attribute-item.ts":
/*!*****************************************!*\
  !*** ./src/types/pns-attribute-item.ts ***!
  \*****************************************/
/***/ (function(__unused_webpack_module, exports) {

"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.Property = void 0;
// eslint-disable-next-line no-shadow
var Property;
(function (Property) {
    Property["SubjectID"] = "SubjectID";
    Property["Tag"] = "Tag";
    Property["NetworkID"] = "NetworkID";
    Property["SubjectGroupID"] = "SubjectGroupID";
    Property["DeviceType"] = "DeviceType";
    Property["AccountID"] = "AccountID";
    Property["SubjectType"] = "SubjectType";
})(Property = exports.Property || (exports.Property = {}));


/***/ }),

/***/ "./src/types/pns-events.ts":
/*!*********************************!*\
  !*** ./src/types/pns-events.ts ***!
  \*********************************/
/***/ (function(__unused_webpack_module, exports) {

"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.PNSEvents = void 0;
// eslint-disable-next-line no-shadow
var PNSEvents;
(function (PNSEvents) {
    PNSEvents["connecting"] = "connecting";
    PNSEvents["connected"] = "connected";
    PNSEvents["updatedIdentity"] = "updated-identity";
    PNSEvents["reconnecting"] = "reconnecting";
    PNSEvents["closed"] = "closed";
    PNSEvents["message"] = "message";
    PNSEvents["debug"] = "debug";
    PNSEvents["disconnected"] = "disconnected";
    PNSEvents["error"] = "error";
    PNSEvents["state"] = "state";
    PNSEvents["heartbeat"] = "heartbeat";
})(PNSEvents = exports.PNSEvents || (exports.PNSEvents = {}));


/***/ }),

/***/ "./src/types/pns-state.ts":
/*!********************************!*\
  !*** ./src/types/pns-state.ts ***!
  \********************************/
/***/ (function(__unused_webpack_module, exports) {

"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));


/***/ }),

/***/ "./src/types/websocket-command.ts":
/*!****************************************!*\
  !*** ./src/types/websocket-command.ts ***!
  \****************************************/
/***/ (function(__unused_webpack_module, exports) {

"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.WebsocketCommandValue = void 0;
// eslint-disable-next-line no-shadow
var WebsocketCommandValue;
(function (WebsocketCommandValue) {
    WebsocketCommandValue["Authenticate"] = "authenticate";
    WebsocketCommandValue["AddIdentity"] = "addIdentity";
    WebsocketCommandValue["UpdateIdentity"] = "updateIdentity";
    WebsocketCommandValue["Ping"] = "ping";
})(WebsocketCommandValue = exports.WebsocketCommandValue || (exports.WebsocketCommandValue = {}));


/***/ }),

/***/ "./src/utils/autobind.ts":
/*!*******************************!*\
  !*** ./src/utils/autobind.ts ***!
  \*******************************/
/***/ (function(__unused_webpack_module, exports) {

"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.autobind = void 0;
/**
 * Return a descriptor removing the value and returning a getter
 * The getter will return a .bind version of the function
 * and memoize the result against a symbol on the instance
 * This is 3rd party function, so lot of ignores
 */
/* istanbul ignore file */
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function autobind(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
target, key, descriptor) {
    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
    var fn = descriptor.value;
    if (typeof fn !== "function") {
        throw new TypeError("@autobind decorator can only be applied to methods not: ".concat(typeof fn));
    }
    // In IE11 calling Object.defineProperty has a side-effect of evaluating the
    // Getter for the property which is being replaced. This causes infinite
    // Recursion and an "Out of stack space" error.
    var definingProperty = false;
    return {
        configurable: true,
        get: function () {
            // eslint-disable-next-line no-prototype-builtins
            if (definingProperty ||
                // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
                this === target.prototype ||
                // eslint-disable-next-line
                this.hasOwnProperty(key) ||
                typeof fn !== "function") {
                // eslint-disable-next-line @typescript-eslint/no-unsafe-return
                return fn;
            }
            // eslint-disable-next-line
            var boundFn = fn.bind(this);
            definingProperty = true;
            Object.defineProperty(this, key, {
                configurable: true,
                get: function () {
                    // eslint-disable-next-line
                    return boundFn;
                },
                set: function (value) {
                    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
                    fn = value;
                    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
                    delete this[key];
                },
            });
            definingProperty = false;
            // eslint-disable-next-line
            return boundFn;
        },
        // eslint-disable-next-line @typescript-eslint/ban-types
        set: function (value) {
            fn = value;
        },
    };
}
exports.autobind = autobind;


/***/ }),

/***/ "./src/utils/default-message-processor.ts":
/*!************************************************!*\
  !*** ./src/utils/default-message-processor.ts ***!
  \************************************************/
/***/ (function(__unused_webpack_module, exports) {

"use strict";

var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (_) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.defaultMessageProcessor = void 0;
var defaultMessageProcessor = function (blob) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
    return [2 /*return*/, blob.text()];
}); }); };
exports.defaultMessageProcessor = defaultMessageProcessor;


/***/ }),

/***/ "./src/utils/jwt.ts":
/*!**************************!*\
  !*** ./src/utils/jwt.ts ***!
  \**************************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {

"use strict";

var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.isAlikeAccessToken = exports.isAlikeIdToken = exports.validateTokenExpiration = exports.decodeJwt = exports.atobUTF8 = void 0;
var assert_1 = __importDefault(__webpack_require__(/*! assert */ "./node_modules/assert/build/assert.js"));
var atobUTF8 = function (base64) {
    return decodeURIComponent(window
        .atob(base64)
        .split("")
        .map(function (c) {
        // eslint-disable-next-line @typescript-eslint/no-magic-numbers
        return "%".concat("00".concat(c.charCodeAt(0).toString(16)).slice(-2));
    })
        .join(""));
};
exports.atobUTF8 = atobUTF8;
var decodeJwt = function (token) {
    // eslint-disable-next-line @typescript-eslint/no-magic-numbers
    var base64Url = token.split(".")[1];
    var base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
    var jsonPayload = (0, exports.atobUTF8)(base64);
    return JSON.parse(jsonPayload);
};
exports.decodeJwt = decodeJwt;
function validateTokenExpiration(payload) {
    assert_1.default.ok(payload.exp, "Token should have expiration timestamp");
    assert_1.default.ok(typeof payload.exp === "number", "Token should have expiration timestamp");
    // eslint-disable-next-line @typescript-eslint/no-magic-numbers
    assert_1.default.ok(payload.exp * 1000 > Date.now(), "Token is expired");
}
exports.validateTokenExpiration = validateTokenExpiration;
function isAlikeIdToken(payload) {
    return !!payload.instanceId;
}
exports.isAlikeIdToken = isAlikeIdToken;
function isAlikeAccessToken(payload) {
    return !!payload.sourceId;
}
exports.isAlikeAccessToken = isAlikeAccessToken;


/***/ }),

/***/ "./src/utils/payload.ts":
/*!******************************!*\
  !*** ./src/utils/payload.ts ***!
  \******************************/
/***/ (function(__unused_webpack_module, exports) {

"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.isOperationMessage = void 0;
function isOperationMessage(payload) {
    return (!!payload.code &&
        !!payload.operationId);
}
exports.isOperationMessage = isOperationMessage;


/***/ }),

/***/ "./node_modules/available-typed-arrays/index.js":
/*!******************************************************!*\
  !*** ./node_modules/available-typed-arrays/index.js ***!
  \******************************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";


var possibleNames = ['BigInt64Array', 'BigUint64Array', 'Float32Array', 'Float64Array', 'Int16Array', 'Int32Array', 'Int8Array', 'Uint16Array', 'Uint32Array', 'Uint8Array', 'Uint8ClampedArray'];
var g = typeof globalThis === 'undefined' ? __webpack_require__.g : globalThis;

module.exports = function availableTypedArrays() {
  var out = [];

  for (var i = 0; i < possibleNames.length; i++) {
    if (typeof g[possibleNames[i]] === 'function') {
      out[out.length] = possibleNames[i];
    }
  }

  return out;
};

/***/ }),

/***/ "./node_modules/es-abstract/helpers/getOwnPropertyDescriptor.js":
/*!**********************************************************************!*\
  !*** ./node_modules/es-abstract/helpers/getOwnPropertyDescriptor.js ***!
  \**********************************************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {

"use strict";


var GetIntrinsic = __webpack_require__(/*! get-intrinsic */ "./node_modules/get-intrinsic/index.js");

var $gOPD = GetIntrinsic('%Object.getOwnPropertyDescriptor%', true);

if ($gOPD) {
  try {
    $gOPD([], 'length');
  } catch (e) {
    // IE 8 has a broken gOPD
    $gOPD = null;
  }
}

module.exports = $gOPD;

/***/ })

/******/ 	});
/************************************************************************/
/******/ 	// The module cache
/******/ 	var __webpack_module_cache__ = {};
/******/ 	
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/ 		// Check if module is in cache
/******/ 		var cachedModule = __webpack_module_cache__[moduleId];
/******/ 		if (cachedModule !== undefined) {
/******/ 			return cachedModule.exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = __webpack_module_cache__[moduleId] = {
/******/ 			// no module.id needed
/******/ 			// no module.loaded needed
/******/ 			exports: {}
/******/ 		};
/******/ 	
/******/ 		// Execute the module function
/******/ 		__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ 	
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/ 	
/************************************************************************/
/******/ 	/* webpack/runtime/define property getters */
/******/ 	!function() {
/******/ 		// define getter functions for harmony exports
/******/ 		__webpack_require__.d = function(exports, definition) {
/******/ 			for(var key in definition) {
/******/ 				if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ 					Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ 				}
/******/ 			}
/******/ 		};
/******/ 	}();
/******/ 	
/******/ 	/* webpack/runtime/global */
/******/ 	!function() {
/******/ 		__webpack_require__.g = (function() {
/******/ 			if (typeof globalThis === 'object') return globalThis;
/******/ 			try {
/******/ 				return this || new Function('return this')();
/******/ 			} catch (e) {
/******/ 				if (typeof window === 'object') return window;
/******/ 			}
/******/ 		})();
/******/ 	}();
/******/ 	
/******/ 	/* webpack/runtime/hasOwnProperty shorthand */
/******/ 	!function() {
/******/ 		__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }
/******/ 	}();
/******/ 	
/******/ 	/* webpack/runtime/make namespace object */
/******/ 	!function() {
/******/ 		// define __esModule on exports
/******/ 		__webpack_require__.r = function(exports) {
/******/ 			if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ 				Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ 			}
/******/ 			Object.defineProperty(exports, '__esModule', { value: true });
/******/ 		};
/******/ 	}();
/******/ 	
/************************************************************************/
/******/ 	
/******/ 	// startup
/******/ 	// Load entry module and return exports
/******/ 	// This entry module is referenced by other modules so it can't be inlined
/******/ 	var __webpack_exports__ = __webpack_require__("./src/index.ts");
/******/ 	
/******/ 	return __webpack_exports__;
/******/ })()
;
});
//# sourceMappingURL=pns-client.js.map
(function webpackUniversalModuleDefinition(root, factory) {
	if(typeof exports === 'object' && typeof module === 'object')
		module.exports = factory(require("appspace-push-notification-service-client"));
	else if(typeof define === 'function' && define.amd)
		define("BroadcastClient", ["appspace-push-notification-service-client"], factory);
	else if(typeof exports === 'object')
		exports["BroadcastClient"] = factory(require("appspace-push-notification-service-client"));
	else
		root["BroadcastClient"] = factory(root["PNSClient"]);
})(self, function(__WEBPACK_EXTERNAL_MODULE_appspace_push_notification_service_client__) {
return /******/ (function() { // webpackBootstrap
/******/ 	"use strict";
/******/ 	var __webpack_modules__ = ({

/***/ "./src/broadcast-client.ts":
/*!*********************************!*\
  !*** ./src/broadcast-client.ts ***!
  \*********************************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {


var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        if (typeof b !== "function" && b !== null)
            throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (_) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
    if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
        if (ar || !(i in from)) {
            if (!ar) ar = Array.prototype.slice.call(from, 0, i);
            ar[i] = from[i];
        }
    }
    return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.BroadcastClient = void 0;
var appspace_push_notification_service_client_1 = __webpack_require__(/*! appspace-push-notification-service-client */ "appspace-push-notification-service-client");
var constants_1 = __webpack_require__(/*! ./constants */ "./src/constants.ts");
var is_broadcast_message_1 = __webpack_require__(/*! ./utils/is-broadcast-message */ "./src/utils/is-broadcast-message.ts");
var request_1 = __webpack_require__(/*! ./request */ "./src/request.ts");
;
var BroadcastClient = /** @class */ (function (_super) {
    __extends(BroadcastClient, _super);
    function BroadcastClient(options) {
        var _this = _super.call(this) || this;
        _this.state = {
            isConnected: false,
            broadcasts: [],
        };
        _this.lastBroadcastsFetchTimestamp = 0;
        _this.options = __assign(__assign({}, constants_1.defaultOptions), options);
        if (_this.options.topicSubscription) {
            _this._topicSubscription = _this.options.topicSubscription;
        }
        if (_this.options.pnsConnection) {
            _this.pnsConnection = _this.options.pnsConnection;
        }
        if (!_this.options.autoStart) {
            return _this;
        }
        setTimeout(function () {
            // eslint-disable-next-line @typescript-eslint/no-floating-promises
            _this.start();
        }, 0);
        return _this;
    }
    BroadcastClient.prototype.start = function () {
        return __awaiter(this, void 0, void 0, function () {
            var e_1;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        this.fire('debug', {
                            level: 'message',
                            message: 'Starting Broadcast Client',
                        });
                        this.lastBroadcastsFetchTimestamp = Date.now();
                        _a.label = 1;
                    case 1:
                        _a.trys.push([1, 3, , 4]);
                        return [4 /*yield*/, this.getBroadcasts(true)];
                    case 2:
                        _a.sent();
                        return [3 /*break*/, 4];
                    case 3:
                        e_1 = _a.sent();
                        return [3 /*break*/, 4];
                    case 4: return [2 /*return*/];
                }
            });
        });
    };
    BroadcastClient.prototype.stop = function () {
        this.fire('debug', {
            level: 'message',
            message: 'Stopping Broadcast Client',
        });
        this.pnsConnection = undefined;
        this.setState({
            isConnected: false,
        });
    };
    /**
     * Maps PNS payload to one used in API
     * TODO: we should either not rely on this except id, or ensure payload structure remains
     * @param payload
     */
    BroadcastClient.prototype.mapToBroadcast = function (payload) {
        var appSpaceBroadcast = {
            id: payload.SubjectId,
            displayMode: payload.DisplayMode,
            priority: payload.Priority,
            endType: payload.EndType,
            duration: payload.Duration,
            priorityOrder: payload.PriorityOrder,
            thumbnailUrl: payload.ThumbnailUrl,
            updatedAt: payload.UpdatedAt,
            activatedAt: payload.ActivatedAt,
        };
        if (payload.Content) {
            var content_1 = {
                message: payload.Content.Message,
                title: payload.Content.Title,
                contentType: payload.Content.ContentType,
                channelId: payload.Content.ChannelId,
                channelType: payload.Content.ChannelType,
                iconColor: payload.Content.IconColor,
                mediaContentItems: [],
            };
            if (payload.Content.MediaContentItems) {
                payload.Content.MediaContentItems.forEach(function (mci) {
                    content_1.mediaContentItems.push({
                        contentType: mci.ContentType,
                        contentId: mci.ContentId,
                        thumbnailUrl: mci.ThumbnailUrl,
                    });
                });
            }
            appSpaceBroadcast.content = content_1;
        }
        return appSpaceBroadcast;
    };
    Object.defineProperty(BroadcastClient.prototype, "pnsConnection", {
        get: function () {
            return this._pnsConnection;
        },
        set: function (connection) {
            if (this._pnsConnection === connection) {
                return;
            }
            if (this._pnsConnection) {
                this.unBindEvents();
                this.fire('pns-client-removed', this._pnsConnection);
                if (this.state.isConnected) {
                    this.setState({
                        isConnected: false,
                    });
                    this.fire('pns-client-closed', 1000);
                }
            }
            this._pnsConnection = connection;
            if (!connection) {
                return;
            }
            this.bindEvents();
            this.fire('pns-client-added', connection);
            if (this.state.isConnected !== connection.isConnected) {
                this.setState({
                    isConnected: connection.isConnected,
                });
                // Note we can't have disconnected here as it disconnects in 100% in upper block
                this.fire('pns-client-connected', connection);
            }
        },
        enumerable: false,
        configurable: true
    });
    BroadcastClient.prototype.getBroadcasts = function (skipDebounce) {
        if (skipDebounce === void 0) { skipDebounce = false; }
        return __awaiter(this, void 0, void 0, function () {
            var debounceMilliseconds, timeSinceLastFetch, result, broadcasts, e_2, error;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (!skipDebounce) {
                            debounceMilliseconds = this.options.fetchDebounceMilliseconds;
                            timeSinceLastFetch = Date.now() - this.lastBroadcastsFetchTimestamp;
                            if (this.lastBroadcastsFetchTimestamp > 0 && timeSinceLastFetch < debounceMilliseconds) {
                                this.fire('debug', {
                                    level: 'message',
                                    message: "Skipping getBroadcasts - called ".concat(timeSinceLastFetch, "ms ago (debounce: ").concat(debounceMilliseconds, "ms)"),
                                });
                                return [2 /*return*/, this.state.broadcasts];
                            }
                        }
                        _a.label = 1;
                    case 1:
                        _a.trys.push([1, 3, , 4]);
                        return [4 /*yield*/, (0, request_1.request)("".concat(this.options.routes.activeDeviceBroadcasts, "?deviceid=").concat(this.options.deviceId), this.options.domain, this.options.accessToken, 'GET')];
                    case 2:
                        result = _a.sent();
                        broadcasts = result.items;
                        this.setState({
                            broadcasts: broadcasts,
                        });
                        this.fire('broadcasts', broadcasts);
                        return [2 /*return*/, broadcasts];
                    case 3:
                        e_2 = _a.sent();
                        error = { originalError: e_2, message: 'Failed to get active broadcasts list' };
                        this.setState({
                            error: error,
                        });
                        this.fire('error', error);
                        throw e_2;
                    case 4: return [2 /*return*/];
                }
            });
        });
    };
    BroadcastClient.prototype.setState = function (state) {
        this.state = __assign(__assign({}, this.state), state);
        this.fire('state', this.state);
    };
    BroadcastClient.prototype.bindEvents = function () {
        /* istanbul ignore if */
        if (!this._pnsConnection) {
            return;
        }
        // eslint-disable-next-line @typescript-eslint/unbound-method
        this._pnsConnection.on(appspace_push_notification_service_client_1.PNSEvents.connected, this.onPNSConnected);
        // eslint-disable-next-line @typescript-eslint/unbound-method
        if (this._topicSubscription && this._topicSubscription.on) {
            this._topicSubscription.on('broadcast', this.onBroadcastTopicMessage);
        }
        // eslint-disable-next-line @typescript-eslint/unbound-method
        this._pnsConnection.on(appspace_push_notification_service_client_1.PNSEvents.closed, this.onPNSClose);
        // eslint-disable-next-line @typescript-eslint/unbound-method
        this._pnsConnection.on(appspace_push_notification_service_client_1.PNSEvents.updatedIdentity, this.onPNSUpdatedIdentity);
    };
    BroadcastClient.prototype.unBindEvents = function () {
        /* istanbul ignore if */
        if (!this._pnsConnection) {
            return;
        }
        // eslint-disable-next-line @typescript-eslint/unbound-method
        this._pnsConnection.off(appspace_push_notification_service_client_1.PNSEvents.connected, this.onPNSConnected);
        // eslint-disable-next-line @typescript-eslint/unbound-method
        if (this._topicSubscription && this._topicSubscription.off) {
            this._topicSubscription.off('broadcast', this.onBroadcastTopicMessage);
        }
        // eslint-disable-next-line @typescript-eslint/unbound-method
        this._pnsConnection.off(appspace_push_notification_service_client_1.PNSEvents.closed, this.onPNSClose);
        // eslint-disable-next-line @typescript-eslint/unbound-method
        this._pnsConnection.off(appspace_push_notification_service_client_1.PNSEvents.updatedIdentity, this.onPNSUpdatedIdentity);
    };
    BroadcastClient.prototype.updateAccessToken = function (accessToken) {
        if (!accessToken) {
            return;
        }
        this.options.accessToken = accessToken;
        this.fire('debug', {
            level: 'message',
            message: 'Updated access token to ' + accessToken,
        });
    };
    BroadcastClient.prototype.onPNSUpdatedIdentity = function (data) {
        if (data && data.accessToken) {
            this.updateAccessToken(data.accessToken);
        }
        // eslint-disable-next-line @typescript-eslint/no-floating-promises
        this.getBroadcasts();
    };
    BroadcastClient.prototype.onPNSConnected = function (isReconnectRecovery) {
        this.setState({
            isConnected: true,
        });
        /* istanbul ignore else */
        if (this._pnsConnection) {
            this.fire('pns-client-connected', this._pnsConnection);
        }
        if (isReconnectRecovery) {
            // eslint-disable-next-line @typescript-eslint/no-floating-promises
            this.getBroadcasts();
        }
    };
    BroadcastClient.prototype.onBroadcastTopicMessage = function (message) {
        if (!message.valid || !(0, is_broadcast_message_1.isBroadcastMessage)(message.payload)) {
            return;
        }
        var payload = message.payload;
        var actionType = payload.Topic;
        try {
            // eslint-disable-next-line @typescript-eslint/no-unused-vars
            var _a = actionType.split('.'), deviceId = _a[0], deviceType = _a[1], hash = _a[2], messageType = _a[3], status_1 = _a[4];
            var activated = status_1 === 'activate';
            if (activated) {
                if (!this.state.broadcasts.find(function (d) { return d.id === payload.SubjectId; })) {
                    this.setState({
                        broadcasts: __spreadArray(__spreadArray([], this.state.broadcasts, true), [this.mapToBroadcast(payload)], false),
                    });
                }
            }
            else {
                this.setState({
                    broadcasts: this.state.broadcasts.filter(function (b) { return b.id !== payload.SubjectId; }),
                });
            }
            this.fire('broadcasts', this.state.broadcasts);
        }
        catch (e) {
            this.fire('debug', {
                level: 'error',
                message: 'Broadcast message has invalid Topic structure',
                originalError: e,
            });
        }
    };
    BroadcastClient.prototype.onPNSClose = function (code) {
        this.setState({
            isConnected: false,
        });
        this.fire('pns-client-closed', code);
    };
    __decorate([
        appspace_push_notification_service_client_1.autobind
    ], BroadcastClient.prototype, "onPNSUpdatedIdentity", null);
    __decorate([
        appspace_push_notification_service_client_1.autobind
    ], BroadcastClient.prototype, "onPNSConnected", null);
    __decorate([
        appspace_push_notification_service_client_1.autobind
    ], BroadcastClient.prototype, "onBroadcastTopicMessage", null);
    __decorate([
        appspace_push_notification_service_client_1.autobind
    ], BroadcastClient.prototype, "onPNSClose", null);
    return BroadcastClient;
}(appspace_push_notification_service_client_1.EventsDispatcher));
exports.BroadcastClient = BroadcastClient;


/***/ }),

/***/ "./src/constants.ts":
/*!**************************!*\
  !*** ./src/constants.ts ***!
  \**************************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {


Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.defaultOptions = void 0;
var routes_1 = __webpack_require__(/*! ./routes */ "./src/routes.ts");
exports.defaultOptions = {
    domain: 'spiralscout.dev.appspace.com',
    deviceId: '',
    routes: routes_1.routes,
    accessToken: '',
    autoStart: true,
    fetchDebounceMilliseconds: 5000,
};


/***/ }),

/***/ "./src/index.ts":
/*!**********************!*\
  !*** ./src/index.ts ***!
  \**********************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {


var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
    for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
__exportStar(__webpack_require__(/*! ./broadcast-client */ "./src/broadcast-client.ts"), exports);
__exportStar(__webpack_require__(/*! ./utils/is-broadcast-message */ "./src/utils/is-broadcast-message.ts"), exports);
__exportStar(__webpack_require__(/*! ./request */ "./src/request.ts"), exports);
__exportStar(__webpack_require__(/*! ./routes */ "./src/routes.ts"), exports);
__exportStar(__webpack_require__(/*! ./types/broadcast-events */ "./src/types/broadcast-events.ts"), exports);
__exportStar(__webpack_require__(/*! ./types/broadcast-state */ "./src/types/broadcast-state.ts"), exports);
__exportStar(__webpack_require__(/*! ./types/broadcast-options */ "./src/types/broadcast-options.ts"), exports);
__exportStar(__webpack_require__(/*! ./types/broadcast */ "./src/types/broadcast.ts"), exports);
__exportStar(__webpack_require__(/*! ./constants */ "./src/constants.ts"), exports);


/***/ }),

/***/ "./src/request.ts":
/*!************************!*\
  !*** ./src/request.ts ***!
  \************************/
/***/ (function(__unused_webpack_module, exports) {


var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        if (typeof b !== "function" && b !== null)
            throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (_) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.request = exports.RequestError = exports.isRequestError = void 0;
function isRequestError(e) {
    return !!(e.status && e.statusText);
}
exports.isRequestError = isRequestError;
var RequestError = /** @class */ (function (_super) {
    __extends(RequestError, _super);
    function RequestError(status, statusText, responseBody) {
        var _this = _super.call(this, "Error ".concat(status, ": ").concat(statusText)) || this;
        _this.status = status;
        _this.statusText = statusText;
        _this.responseBody = responseBody;
        return _this;
    }
    return RequestError;
}(Error));
exports.RequestError = RequestError;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
var request = function (route, base, token, method, body) {
    if (method === void 0) { method = 'POST'; }
    return __awaiter(void 0, void 0, void 0, function () {
        var url, promise, result, error, errorBody, e_1;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    url = "https://".concat(base).concat(route);
                    promise = fetch(url, {
                        headers: {
                            Authorization: "Bearer ".concat(token),
                            'Content-Type': 'application/json',
                        },
                        mode: 'cors',
                        method: method,
                        body: body,
                    });
                    return [4 /*yield*/, promise];
                case 1:
                    result = _a.sent();
                    if (!result.ok) return [3 /*break*/, 3];
                    return [4 /*yield*/, result.json()];
                case 2: return [2 /*return*/, (_a.sent())];
                case 3:
                    error = void 0;
                    _a.label = 4;
                case 4:
                    _a.trys.push([4, 6, , 7]);
                    return [4 /*yield*/, result.json()];
                case 5:
                    errorBody = _a.sent();
                    // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
                    error = new RequestError(result.status, result.statusText, errorBody);
                    return [3 /*break*/, 7];
                case 6:
                    e_1 = _a.sent();
                    error = new RequestError(result.status, result.statusText, null);
                    return [3 /*break*/, 7];
                case 7: throw error;
            }
        });
    });
};
exports.request = request;


/***/ }),

/***/ "./src/routes.ts":
/*!***********************!*\
  !*** ./src/routes.ts ***!
  \***********************/
/***/ (function(__unused_webpack_module, exports) {


Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.routes = void 0;
exports.routes = {
    activeDeviceBroadcasts: '/api/v3/activebroadcasts/devices/me/broadcasts',
};


/***/ }),

/***/ "./src/types/broadcast-events.ts":
/*!***************************************!*\
  !*** ./src/types/broadcast-events.ts ***!
  \***************************************/
/***/ (function(__unused_webpack_module, exports) {


Object.defineProperty(exports, "__esModule", ({ value: true }));


/***/ }),

/***/ "./src/types/broadcast-options.ts":
/*!****************************************!*\
  !*** ./src/types/broadcast-options.ts ***!
  \****************************************/
/***/ (function(__unused_webpack_module, exports) {


Object.defineProperty(exports, "__esModule", ({ value: true }));


/***/ }),

/***/ "./src/types/broadcast-state.ts":
/*!**************************************!*\
  !*** ./src/types/broadcast-state.ts ***!
  \**************************************/
/***/ (function(__unused_webpack_module, exports) {


Object.defineProperty(exports, "__esModule", ({ value: true }));


/***/ }),

/***/ "./src/types/broadcast.ts":
/*!********************************!*\
  !*** ./src/types/broadcast.ts ***!
  \********************************/
/***/ (function(__unused_webpack_module, exports) {


Object.defineProperty(exports, "__esModule", ({ value: true }));


/***/ }),

/***/ "./src/utils/is-broadcast-message.ts":
/*!*******************************************!*\
  !*** ./src/utils/is-broadcast-message.ts ***!
  \*******************************************/
/***/ (function(__unused_webpack_module, exports) {


Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.isBroadcastMessage = void 0;
function isBroadcastMessage(payload) {
    var data = payload;
    if (
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
    !data.Topic
        || !data.SubjectId
        || typeof data.Topic !== 'string' && typeof data.SubjectId !== 'string') {
        return false;
    }
    // Format is `${UUID}.device.#.broadcast.${'activate' | 'deactivate'}`
    var parts = data.Topic.split('.');
    if (parts.length !== 5) {
        return false;
    }
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    var uuid = parts[0], device = parts[1], hash = parts[2], broadcast = parts[3], status = parts[4];
    return !(device !== 'device' || broadcast !== 'broadcast');
}
exports.isBroadcastMessage = isBroadcastMessage;


/***/ }),

/***/ "appspace-push-notification-service-client":
/*!******************************************************************************************************************************************************************************************************!*\
  !*** external {"root":"PNSClient","commonjs":"appspace-push-notification-service-client","commonjs2":"appspace-push-notification-service-client","amd":"appspace-push-notification-service-client"} ***!
  \******************************************************************************************************************************************************************************************************/
/***/ (function(module) {

module.exports = __WEBPACK_EXTERNAL_MODULE_appspace_push_notification_service_client__;

/***/ })

/******/ 	});
/************************************************************************/
/******/ 	// The module cache
/******/ 	var __webpack_module_cache__ = {};
/******/ 	
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/ 		// Check if module is in cache
/******/ 		var cachedModule = __webpack_module_cache__[moduleId];
/******/ 		if (cachedModule !== undefined) {
/******/ 			return cachedModule.exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = __webpack_module_cache__[moduleId] = {
/******/ 			// no module.id needed
/******/ 			// no module.loaded needed
/******/ 			exports: {}
/******/ 		};
/******/ 	
/******/ 		// Execute the module function
/******/ 		__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ 	
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/ 	
/************************************************************************/
/******/ 	
/******/ 	// startup
/******/ 	// Load entry module and return exports
/******/ 	// This entry module is referenced by other modules so it can't be inlined
/******/ 	var __webpack_exports__ = __webpack_require__("./src/index.ts");
/******/ 	
/******/ 	return __webpack_exports__;
/******/ })()
;
});
//# sourceMappingURL=broadcast-client.js.map
(function webpackUniversalModuleDefinition(root, factory) {
	if(typeof exports === 'object' && typeof module === 'object')
		module.exports = factory();
	else if(typeof define === 'function' && define.amd)
		define([], factory);
	else if(typeof exports === 'object')
		exports["jsQR"] = factory();
	else
		root["jsQR"] = factory();
})(typeof self !== 'undefined' ? self : this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// Flag the module as loaded
/******/ 		module.l = true;
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/
/******/
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// define getter function for harmony exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, {
/******/ 				configurable: false,
/******/ 				enumerable: true,
/******/ 				get: getter
/******/ 			});
/******/ 		}
/******/ 	};
/******/
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = 3);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

Object.defineProperty(exports, "__esModule", { value: true });
var BitMatrix = /** @class */ (function () {
    function BitMatrix(data, width) {
        this.width = width;
        this.height = data.length / width;
        this.data = data;
    }
    BitMatrix.createEmpty = function (width, height) {
        return new BitMatrix(new Uint8ClampedArray(width * height), width);
    };
    BitMatrix.prototype.get = function (x, y) {
        if (x < 0 || x >= this.width || y < 0 || y >= this.height) {
            return false;
        }
        return !!this.data[y * this.width + x];
    };
    BitMatrix.prototype.set = function (x, y, v) {
        this.data[y * this.width + x] = v ? 1 : 0;
    };
    BitMatrix.prototype.setRegion = function (left, top, width, height, v) {
        for (var y = top; y < top + height; y++) {
            for (var x = left; x < left + width; x++) {
                this.set(x, y, !!v);
            }
        }
    };
    return BitMatrix;
}());
exports.BitMatrix = BitMatrix;


/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

Object.defineProperty(exports, "__esModule", { value: true });
var GenericGFPoly_1 = __webpack_require__(2);
function addOrSubtractGF(a, b) {
    return a ^ b; // tslint:disable-line:no-bitwise
}
exports.addOrSubtractGF = addOrSubtractGF;
var GenericGF = /** @class */ (function () {
    function GenericGF(primitive, size, genBase) {
        this.primitive = primitive;
        this.size = size;
        this.generatorBase = genBase;
        this.expTable = new Array(this.size);
        this.logTable = new Array(this.size);
        var x = 1;
        for (var i = 0; i < this.size; i++) {
            this.expTable[i] = x;
            x = x * 2;
            if (x >= this.size) {
                x = (x ^ this.primitive) & (this.size - 1); // tslint:disable-line:no-bitwise
            }
        }
        for (var i = 0; i < this.size - 1; i++) {
            this.logTable[this.expTable[i]] = i;
        }
        this.zero = new GenericGFPoly_1.default(this, Uint8ClampedArray.from([0]));
        this.one = new GenericGFPoly_1.default(this, Uint8ClampedArray.from([1]));
    }
    GenericGF.prototype.multiply = function (a, b) {
        if (a === 0 || b === 0) {
            return 0;
        }
        return this.expTable[(this.logTable[a] + this.logTable[b]) % (this.size - 1)];
    };
    GenericGF.prototype.inverse = function (a) {
        if (a === 0) {
            throw new Error("Can't invert 0");
        }
        return this.expTable[this.size - this.logTable[a] - 1];
    };
    GenericGF.prototype.buildMonomial = function (degree, coefficient) {
        if (degree < 0) {
            throw new Error("Invalid monomial degree less than 0");
        }
        if (coefficient === 0) {
            return this.zero;
        }
        var coefficients = new Uint8ClampedArray(degree + 1);
        coefficients[0] = coefficient;
        return new GenericGFPoly_1.default(this, coefficients);
    };
    GenericGF.prototype.log = function (a) {
        if (a === 0) {
            throw new Error("Can't take log(0)");
        }
        return this.logTable[a];
    };
    GenericGF.prototype.exp = function (a) {
        return this.expTable[a];
    };
    return GenericGF;
}());
exports.default = GenericGF;


/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

Object.defineProperty(exports, "__esModule", { value: true });
var GenericGF_1 = __webpack_require__(1);
var GenericGFPoly = /** @class */ (function () {
    function GenericGFPoly(field, coefficients) {
        if (coefficients.length === 0) {
            throw new Error("No coefficients.");
        }
        this.field = field;
        var coefficientsLength = coefficients.length;
        if (coefficientsLength > 1 && coefficients[0] === 0) {
            // Leading term must be non-zero for anything except the constant polynomial "0"
            var firstNonZero = 1;
            while (firstNonZero < coefficientsLength && coefficients[firstNonZero] === 0) {
                firstNonZero++;
            }
            if (firstNonZero === coefficientsLength) {
                this.coefficients = field.zero.coefficients;
            }
            else {
                this.coefficients = new Uint8ClampedArray(coefficientsLength - firstNonZero);
                for (var i = 0; i < this.coefficients.length; i++) {
                    this.coefficients[i] = coefficients[firstNonZero + i];
                }
            }
        }
        else {
            this.coefficients = coefficients;
        }
    }
    GenericGFPoly.prototype.degree = function () {
        return this.coefficients.length - 1;
    };
    GenericGFPoly.prototype.isZero = function () {
        return this.coefficients[0] === 0;
    };
    GenericGFPoly.prototype.getCoefficient = function (degree) {
        return this.coefficients[this.coefficients.length - 1 - degree];
    };
    GenericGFPoly.prototype.addOrSubtract = function (other) {
        var _a;
        if (this.isZero()) {
            return other;
        }
        if (other.isZero()) {
            return this;
        }
        var smallerCoefficients = this.coefficients;
        var largerCoefficients = other.coefficients;
        if (smallerCoefficients.length > largerCoefficients.length) {
            _a = [largerCoefficients, smallerCoefficients], smallerCoefficients = _a[0], largerCoefficients = _a[1];
        }
        var sumDiff = new Uint8ClampedArray(largerCoefficients.length);
        var lengthDiff = largerCoefficients.length - smallerCoefficients.length;
        for (var i = 0; i < lengthDiff; i++) {
            sumDiff[i] = largerCoefficients[i];
        }
        for (var i = lengthDiff; i < largerCoefficients.length; i++) {
            sumDiff[i] = GenericGF_1.addOrSubtractGF(smallerCoefficients[i - lengthDiff], largerCoefficients[i]);
        }
        return new GenericGFPoly(this.field, sumDiff);
    };
    GenericGFPoly.prototype.multiply = function (scalar) {
        if (scalar === 0) {
            return this.field.zero;
        }
        if (scalar === 1) {
            return this;
        }
        var size = this.coefficients.length;
        var product = new Uint8ClampedArray(size);
        for (var i = 0; i < size; i++) {
            product[i] = this.field.multiply(this.coefficients[i], scalar);
        }
        return new GenericGFPoly(this.field, product);
    };
    GenericGFPoly.prototype.multiplyPoly = function (other) {
        if (this.isZero() || other.isZero()) {
            return this.field.zero;
        }
        var aCoefficients = this.coefficients;
        var aLength = aCoefficients.length;
        var bCoefficients = other.coefficients;
        var bLength = bCoefficients.length;
        var product = new Uint8ClampedArray(aLength + bLength - 1);
        for (var i = 0; i < aLength; i++) {
            var aCoeff = aCoefficients[i];
            for (var j = 0; j < bLength; j++) {
                product[i + j] = GenericGF_1.addOrSubtractGF(product[i + j], this.field.multiply(aCoeff, bCoefficients[j]));
            }
        }
        return new GenericGFPoly(this.field, product);
    };
    GenericGFPoly.prototype.multiplyByMonomial = function (degree, coefficient) {
        if (degree < 0) {
            throw new Error("Invalid degree less than 0");
        }
        if (coefficient === 0) {
            return this.field.zero;
        }
        var size = this.coefficients.length;
        var product = new Uint8ClampedArray(size + degree);
        for (var i = 0; i < size; i++) {
            product[i] = this.field.multiply(this.coefficients[i], coefficient);
        }
        return new GenericGFPoly(this.field, product);
    };
    GenericGFPoly.prototype.evaluateAt = function (a) {
        var result = 0;
        if (a === 0) {
            // Just return the x^0 coefficient
            return this.getCoefficient(0);
        }
        var size = this.coefficients.length;
        if (a === 1) {
            // Just the sum of the coefficients
            this.coefficients.forEach(function (coefficient) {
                result = GenericGF_1.addOrSubtractGF(result, coefficient);
            });
            return result;
        }
        result = this.coefficients[0];
        for (var i = 1; i < size; i++) {
            result = GenericGF_1.addOrSubtractGF(this.field.multiply(a, result), this.coefficients[i]);
        }
        return result;
    };
    return GenericGFPoly;
}());
exports.default = GenericGFPoly;


/***/ }),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

Object.defineProperty(exports, "__esModule", { value: true });
var binarizer_1 = __webpack_require__(4);
var decoder_1 = __webpack_require__(5);
var extractor_1 = __webpack_require__(11);
var locator_1 = __webpack_require__(12);
function scan(matrix) {
    var locations = locator_1.locate(matrix);
    if (!locations) {
        return null;
    }
    for (var _i = 0, locations_1 = locations; _i < locations_1.length; _i++) {
        var location_1 = locations_1[_i];
        var extracted = extractor_1.extract(matrix, location_1);
        var decoded = decoder_1.decode(extracted.matrix);
        if (decoded) {
            return {
                binaryData: decoded.bytes,
                data: decoded.text,
                chunks: decoded.chunks,
                location: {
                    topRightCorner: extracted.mappingFunction(location_1.dimension, 0),
                    topLeftCorner: extracted.mappingFunction(0, 0),
                    bottomRightCorner: extracted.mappingFunction(location_1.dimension, location_1.dimension),
                    bottomLeftCorner: extracted.mappingFunction(0, location_1.dimension),
                    topRightFinderPattern: location_1.topRight,
                    topLeftFinderPattern: location_1.topLeft,
                    bottomLeftFinderPattern: location_1.bottomLeft,
                    bottomRightAlignmentPattern: location_1.alignmentPattern,
                },
            };
        }
    }
    return null;
}
var defaultOptions = {
    inversionAttempts: "attemptBoth",
};
function jsQR(data, width, height, providedOptions) {
    if (providedOptions === void 0) { providedOptions = {}; }
    var options = defaultOptions;
    Object.keys(options || {}).forEach(function (opt) {
        options[opt] = providedOptions[opt] || options[opt];
    });
    var shouldInvert = options.inversionAttempts === "attemptBoth" || options.inversionAttempts === "invertFirst";
    var tryInvertedFirst = options.inversionAttempts === "onlyInvert" || options.inversionAttempts === "invertFirst";
    var _a = binarizer_1.binarize(data, width, height, shouldInvert), binarized = _a.binarized, inverted = _a.inverted;
    var result = scan(tryInvertedFirst ? inverted : binarized);
    if (!result && (options.inversionAttempts === "attemptBoth" || options.inversionAttempts === "invertFirst")) {
        result = scan(tryInvertedFirst ? binarized : inverted);
    }
    return result;
}
jsQR.default = jsQR;
exports.default = jsQR;


/***/ }),
/* 4 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

Object.defineProperty(exports, "__esModule", { value: true });
var BitMatrix_1 = __webpack_require__(0);
var REGION_SIZE = 8;
var MIN_DYNAMIC_RANGE = 24;
function numBetween(value, min, max) {
    return value < min ? min : value > max ? max : value;
}
// Like BitMatrix but accepts arbitry Uint8 values
var Matrix = /** @class */ (function () {
    function Matrix(width, height) {
        this.width = width;
        this.data = new Uint8ClampedArray(width * height);
    }
    Matrix.prototype.get = function (x, y) {
        return this.data[y * this.width + x];
    };
    Matrix.prototype.set = function (x, y, value) {
        this.data[y * this.width + x] = value;
    };
    return Matrix;
}());
function binarize(data, width, height, returnInverted) {
    if (data.length !== width * height * 4) {
        throw new Error("Malformed data passed to binarizer.");
    }
    // Convert image to greyscale
    var greyscalePixels = new Matrix(width, height);
    for (var x = 0; x < width; x++) {
        for (var y = 0; y < height; y++) {
            var r = data[((y * width + x) * 4) + 0];
            var g = data[((y * width + x) * 4) + 1];
            var b = data[((y * width + x) * 4) + 2];
            greyscalePixels.set(x, y, 0.2126 * r + 0.7152 * g + 0.0722 * b);
        }
    }
    var horizontalRegionCount = Math.ceil(width / REGION_SIZE);
    var verticalRegionCount = Math.ceil(height / REGION_SIZE);
    var blackPoints = new Matrix(horizontalRegionCount, verticalRegionCount);
    for (var verticalRegion = 0; verticalRegion < verticalRegionCount; verticalRegion++) {
        for (var hortizontalRegion = 0; hortizontalRegion < horizontalRegionCount; hortizontalRegion++) {
            var sum = 0;
            var min = Infinity;
            var max = 0;
            for (var y = 0; y < REGION_SIZE; y++) {
                for (var x = 0; x < REGION_SIZE; x++) {
                    var pixelLumosity = greyscalePixels.get(hortizontalRegion * REGION_SIZE + x, verticalRegion * REGION_SIZE + y);
                    sum += pixelLumosity;
                    min = Math.min(min, pixelLumosity);
                    max = Math.max(max, pixelLumosity);
                }
            }
            var average = sum / (Math.pow(REGION_SIZE, 2));
            if (max - min <= MIN_DYNAMIC_RANGE) {
                // If variation within the block is low, assume this is a block with only light or only
                // dark pixels. In that case we do not want to use the average, as it would divide this
                // low contrast area into black and white pixels, essentially creating data out of noise.
                //
                // Default the blackpoint for these blocks to be half the min - effectively white them out
                average = min / 2;
                if (verticalRegion > 0 && hortizontalRegion > 0) {
                    // Correct the "white background" assumption for blocks that have neighbors by comparing
                    // the pixels in this block to the previously calculated black points. This is based on
                    // the fact that dark barcode symbology is always surrounded by some amount of light
                    // background for which reasonable black point estimates were made. The bp estimated at
                    // the boundaries is used for the interior.
                    // The (min < bp) is arbitrary but works better than other heuristics that were tried.
                    var averageNeighborBlackPoint = (blackPoints.get(hortizontalRegion, verticalRegion - 1) +
                        (2 * blackPoints.get(hortizontalRegion - 1, verticalRegion)) +
                        blackPoints.get(hortizontalRegion - 1, verticalRegion - 1)) / 4;
                    if (min < averageNeighborBlackPoint) {
                        average = averageNeighborBlackPoint;
                    }
                }
            }
            blackPoints.set(hortizontalRegion, verticalRegion, average);
        }
    }
    var binarized = BitMatrix_1.BitMatrix.createEmpty(width, height);
    var inverted = null;
    if (returnInverted) {
        inverted = BitMatrix_1.BitMatrix.createEmpty(width, height);
    }
    for (var verticalRegion = 0; verticalRegion < verticalRegionCount; verticalRegion++) {
        for (var hortizontalRegion = 0; hortizontalRegion < horizontalRegionCount; hortizontalRegion++) {
            var left = numBetween(hortizontalRegion, 2, horizontalRegionCount - 3);
            var top_1 = numBetween(verticalRegion, 2, verticalRegionCount - 3);
            var sum = 0;
            for (var xRegion = -2; xRegion <= 2; xRegion++) {
                for (var yRegion = -2; yRegion <= 2; yRegion++) {
                    sum += blackPoints.get(left + xRegion, top_1 + yRegion);
                }
            }
            var threshold = sum / 25;
            for (var xRegion = 0; xRegion < REGION_SIZE; xRegion++) {
                for (var yRegion = 0; yRegion < REGION_SIZE; yRegion++) {
                    var x = hortizontalRegion * REGION_SIZE + xRegion;
                    var y = verticalRegion * REGION_SIZE + yRegion;
                    var lum = greyscalePixels.get(x, y);
                    binarized.set(x, y, lum <= threshold);
                    if (returnInverted) {
                        inverted.set(x, y, !(lum <= threshold));
                    }
                }
            }
        }
    }
    if (returnInverted) {
        return { binarized: binarized, inverted: inverted };
    }
    return { binarized: binarized };
}
exports.binarize = binarize;


/***/ }),
/* 5 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

Object.defineProperty(exports, "__esModule", { value: true });
var BitMatrix_1 = __webpack_require__(0);
var decodeData_1 = __webpack_require__(6);
var reedsolomon_1 = __webpack_require__(9);
var version_1 = __webpack_require__(10);
// tslint:disable:no-bitwise
function numBitsDiffering(x, y) {
    var z = x ^ y;
    var bitCount = 0;
    while (z) {
        bitCount++;
        z &= z - 1;
    }
    return bitCount;
}
function pushBit(bit, byte) {
    return (byte << 1) | bit;
}
// tslint:enable:no-bitwise
var FORMAT_INFO_TABLE = [
    { bits: 0x5412, formatInfo: { errorCorrectionLevel: 1, dataMask: 0 } },
    { bits: 0x5125, formatInfo: { errorCorrectionLevel: 1, dataMask: 1 } },
    { bits: 0x5E7C, formatInfo: { errorCorrectionLevel: 1, dataMask: 2 } },
    { bits: 0x5B4B, formatInfo: { errorCorrectionLevel: 1, dataMask: 3 } },
    { bits: 0x45F9, formatInfo: { errorCorrectionLevel: 1, dataMask: 4 } },
    { bits: 0x40CE, formatInfo: { errorCorrectionLevel: 1, dataMask: 5 } },
    { bits: 0x4F97, formatInfo: { errorCorrectionLevel: 1, dataMask: 6 } },
    { bits: 0x4AA0, formatInfo: { errorCorrectionLevel: 1, dataMask: 7 } },
    { bits: 0x77C4, formatInfo: { errorCorrectionLevel: 0, dataMask: 0 } },
    { bits: 0x72F3, formatInfo: { errorCorrectionLevel: 0, dataMask: 1 } },
    { bits: 0x7DAA, formatInfo: { errorCorrectionLevel: 0, dataMask: 2 } },
    { bits: 0x789D, formatInfo: { errorCorrectionLevel: 0, dataMask: 3 } },
    { bits: 0x662F, formatInfo: { errorCorrectionLevel: 0, dataMask: 4 } },
    { bits: 0x6318, formatInfo: { errorCorrectionLevel: 0, dataMask: 5 } },
    { bits: 0x6C41, formatInfo: { errorCorrectionLevel: 0, dataMask: 6 } },
    { bits: 0x6976, formatInfo: { errorCorrectionLevel: 0, dataMask: 7 } },
    { bits: 0x1689, formatInfo: { errorCorrectionLevel: 3, dataMask: 0 } },
    { bits: 0x13BE, formatInfo: { errorCorrectionLevel: 3, dataMask: 1 } },
    { bits: 0x1CE7, formatInfo: { errorCorrectionLevel: 3, dataMask: 2 } },
    { bits: 0x19D0, formatInfo: { errorCorrectionLevel: 3, dataMask: 3 } },
    { bits: 0x0762, formatInfo: { errorCorrectionLevel: 3, dataMask: 4 } },
    { bits: 0x0255, formatInfo: { errorCorrectionLevel: 3, dataMask: 5 } },
    { bits: 0x0D0C, formatInfo: { errorCorrectionLevel: 3, dataMask: 6 } },
    { bits: 0x083B, formatInfo: { errorCorrectionLevel: 3, dataMask: 7 } },
    { bits: 0x355F, formatInfo: { errorCorrectionLevel: 2, dataMask: 0 } },
    { bits: 0x3068, formatInfo: { errorCorrectionLevel: 2, dataMask: 1 } },
    { bits: 0x3F31, formatInfo: { errorCorrectionLevel: 2, dataMask: 2 } },
    { bits: 0x3A06, formatInfo: { errorCorrectionLevel: 2, dataMask: 3 } },
    { bits: 0x24B4, formatInfo: { errorCorrectionLevel: 2, dataMask: 4 } },
    { bits: 0x2183, formatInfo: { errorCorrectionLevel: 2, dataMask: 5 } },
    { bits: 0x2EDA, formatInfo: { errorCorrectionLevel: 2, dataMask: 6 } },
    { bits: 0x2BED, formatInfo: { errorCorrectionLevel: 2, dataMask: 7 } },
];
var DATA_MASKS = [
    function (p) { return ((p.y + p.x) % 2) === 0; },
    function (p) { return (p.y % 2) === 0; },
    function (p) { return p.x % 3 === 0; },
    function (p) { return (p.y + p.x) % 3 === 0; },
    function (p) { return (Math.floor(p.y / 2) + Math.floor(p.x / 3)) % 2 === 0; },
    function (p) { return ((p.x * p.y) % 2) + ((p.x * p.y) % 3) === 0; },
    function (p) { return ((((p.y * p.x) % 2) + (p.y * p.x) % 3) % 2) === 0; },
    function (p) { return ((((p.y + p.x) % 2) + (p.y * p.x) % 3) % 2) === 0; },
];
function buildFunctionPatternMask(version) {
    var dimension = 17 + 4 * version.versionNumber;
    var matrix = BitMatrix_1.BitMatrix.createEmpty(dimension, dimension);
    matrix.setRegion(0, 0, 9, 9, true); // Top left finder pattern + separator + format
    matrix.setRegion(dimension - 8, 0, 8, 9, true); // Top right finder pattern + separator + format
    matrix.setRegion(0, dimension - 8, 9, 8, true); // Bottom left finder pattern + separator + format
    // Alignment patterns
    for (var _i = 0, _a = version.alignmentPatternCenters; _i < _a.length; _i++) {
        var x = _a[_i];
        for (var _b = 0, _c = version.alignmentPatternCenters; _b < _c.length; _b++) {
            var y = _c[_b];
            if (!(x === 6 && y === 6 || x === 6 && y === dimension - 7 || x === dimension - 7 && y === 6)) {
                matrix.setRegion(x - 2, y - 2, 5, 5, true);
            }
        }
    }
    matrix.setRegion(6, 9, 1, dimension - 17, true); // Vertical timing pattern
    matrix.setRegion(9, 6, dimension - 17, 1, true); // Horizontal timing pattern
    if (version.versionNumber > 6) {
        matrix.setRegion(dimension - 11, 0, 3, 6, true); // Version info, top right
        matrix.setRegion(0, dimension - 11, 6, 3, true); // Version info, bottom left
    }
    return matrix;
}
function readCodewords(matrix, version, formatInfo) {
    var dataMask = DATA_MASKS[formatInfo.dataMask];
    var dimension = matrix.height;
    var functionPatternMask = buildFunctionPatternMask(version);
    var codewords = [];
    var currentByte = 0;
    var bitsRead = 0;
    // Read columns in pairs, from right to left
    var readingUp = true;
    for (var columnIndex = dimension - 1; columnIndex > 0; columnIndex -= 2) {
        if (columnIndex === 6) { // Skip whole column with vertical alignment pattern;
            columnIndex--;
        }
        for (var i = 0; i < dimension; i++) {
            var y = readingUp ? dimension - 1 - i : i;
            for (var columnOffset = 0; columnOffset < 2; columnOffset++) {
                var x = columnIndex - columnOffset;
                if (!functionPatternMask.get(x, y)) {
                    bitsRead++;
                    var bit = matrix.get(x, y);
                    if (dataMask({ y: y, x: x })) {
                        bit = !bit;
                    }
                    currentByte = pushBit(bit, currentByte);
                    if (bitsRead === 8) { // Whole bytes
                        codewords.push(currentByte);
                        bitsRead = 0;
                        currentByte = 0;
                    }
                }
            }
        }
        readingUp = !readingUp;
    }
    return codewords;
}
function readVersion(matrix) {
    var dimension = matrix.height;
    var provisionalVersion = Math.floor((dimension - 17) / 4);
    if (provisionalVersion <= 6) { // 6 and under dont have version info in the QR code
        return version_1.VERSIONS[provisionalVersion - 1];
    }
    var topRightVersionBits = 0;
    for (var y = 5; y >= 0; y--) {
        for (var x = dimension - 9; x >= dimension - 11; x--) {
            topRightVersionBits = pushBit(matrix.get(x, y), topRightVersionBits);
        }
    }
    var bottomLeftVersionBits = 0;
    for (var x = 5; x >= 0; x--) {
        for (var y = dimension - 9; y >= dimension - 11; y--) {
            bottomLeftVersionBits = pushBit(matrix.get(x, y), bottomLeftVersionBits);
        }
    }
    var bestDifference = Infinity;
    var bestVersion;
    for (var _i = 0, VERSIONS_1 = version_1.VERSIONS; _i < VERSIONS_1.length; _i++) {
        var version = VERSIONS_1[_i];
        if (version.infoBits === topRightVersionBits || version.infoBits === bottomLeftVersionBits) {
            return version;
        }
        var difference = numBitsDiffering(topRightVersionBits, version.infoBits);
        if (difference < bestDifference) {
            bestVersion = version;
            bestDifference = difference;
        }
        difference = numBitsDiffering(bottomLeftVersionBits, version.infoBits);
        if (difference < bestDifference) {
            bestVersion = version;
            bestDifference = difference;
        }
    }
    // We can tolerate up to 3 bits of error since no two version info codewords will
    // differ in less than 8 bits.
    if (bestDifference <= 3) {
        return bestVersion;
    }
}
function readFormatInformation(matrix) {
    var topLeftFormatInfoBits = 0;
    for (var x = 0; x <= 8; x++) {
        if (x !== 6) { // Skip timing pattern bit
            topLeftFormatInfoBits = pushBit(matrix.get(x, 8), topLeftFormatInfoBits);
        }
    }
    for (var y = 7; y >= 0; y--) {
        if (y !== 6) { // Skip timing pattern bit
            topLeftFormatInfoBits = pushBit(matrix.get(8, y), topLeftFormatInfoBits);
        }
    }
    var dimension = matrix.height;
    var topRightBottomRightFormatInfoBits = 0;
    for (var y = dimension - 1; y >= dimension - 7; y--) { // bottom left
        topRightBottomRightFormatInfoBits = pushBit(matrix.get(8, y), topRightBottomRightFormatInfoBits);
    }
    for (var x = dimension - 8; x < dimension; x++) { // top right
        topRightBottomRightFormatInfoBits = pushBit(matrix.get(x, 8), topRightBottomRightFormatInfoBits);
    }
    var bestDifference = Infinity;
    var bestFormatInfo = null;
    for (var _i = 0, FORMAT_INFO_TABLE_1 = FORMAT_INFO_TABLE; _i < FORMAT_INFO_TABLE_1.length; _i++) {
        var _a = FORMAT_INFO_TABLE_1[_i], bits = _a.bits, formatInfo = _a.formatInfo;
        if (bits === topLeftFormatInfoBits || bits === topRightBottomRightFormatInfoBits) {
            return formatInfo;
        }
        var difference = numBitsDiffering(topLeftFormatInfoBits, bits);
        if (difference < bestDifference) {
            bestFormatInfo = formatInfo;
            bestDifference = difference;
        }
        if (topLeftFormatInfoBits !== topRightBottomRightFormatInfoBits) { // also try the other option
            difference = numBitsDiffering(topRightBottomRightFormatInfoBits, bits);
            if (difference < bestDifference) {
                bestFormatInfo = formatInfo;
                bestDifference = difference;
            }
        }
    }
    // Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits differing means we found a match
    if (bestDifference <= 3) {
        return bestFormatInfo;
    }
    return null;
}
function getDataBlocks(codewords, version, ecLevel) {
    var ecInfo = version.errorCorrectionLevels[ecLevel];
    var dataBlocks = [];
    var totalCodewords = 0;
    ecInfo.ecBlocks.forEach(function (block) {
        for (var i = 0; i < block.numBlocks; i++) {
            dataBlocks.push({ numDataCodewords: block.dataCodewordsPerBlock, codewords: [] });
            totalCodewords += block.dataCodewordsPerBlock + ecInfo.ecCodewordsPerBlock;
        }
    });
    // In some cases the QR code will be malformed enough that we pull off more or less than we should.
    // If we pull off less there's nothing we can do.
    // If we pull off more we can safely truncate
    if (codewords.length < totalCodewords) {
        return null;
    }
    codewords = codewords.slice(0, totalCodewords);
    var shortBlockSize = ecInfo.ecBlocks[0].dataCodewordsPerBlock;
    // Pull codewords to fill the blocks up to the minimum size
    for (var i = 0; i < shortBlockSize; i++) {
        for (var _i = 0, dataBlocks_1 = dataBlocks; _i < dataBlocks_1.length; _i++) {
            var dataBlock = dataBlocks_1[_i];
            dataBlock.codewords.push(codewords.shift());
        }
    }
    // If there are any large blocks, pull codewords to fill the last element of those
    if (ecInfo.ecBlocks.length > 1) {
        var smallBlockCount = ecInfo.ecBlocks[0].numBlocks;
        var largeBlockCount = ecInfo.ecBlocks[1].numBlocks;
        for (var i = 0; i < largeBlockCount; i++) {
            dataBlocks[smallBlockCount + i].codewords.push(codewords.shift());
        }
    }
    // Add the rest of the codewords to the blocks. These are the error correction codewords.
    while (codewords.length > 0) {
        for (var _a = 0, dataBlocks_2 = dataBlocks; _a < dataBlocks_2.length; _a++) {
            var dataBlock = dataBlocks_2[_a];
            dataBlock.codewords.push(codewords.shift());
        }
    }
    return dataBlocks;
}
function decodeMatrix(matrix) {
    var version = readVersion(matrix);
    if (!version) {
        return null;
    }
    var formatInfo = readFormatInformation(matrix);
    if (!formatInfo) {
        return null;
    }
    var codewords = readCodewords(matrix, version, formatInfo);
    var dataBlocks = getDataBlocks(codewords, version, formatInfo.errorCorrectionLevel);
    if (!dataBlocks) {
        return null;
    }
    // Count total number of data bytes
    var totalBytes = dataBlocks.reduce(function (a, b) { return a + b.numDataCodewords; }, 0);
    var resultBytes = new Uint8ClampedArray(totalBytes);
    var resultIndex = 0;
    for (var _i = 0, dataBlocks_3 = dataBlocks; _i < dataBlocks_3.length; _i++) {
        var dataBlock = dataBlocks_3[_i];
        var correctedBytes = reedsolomon_1.decode(dataBlock.codewords, dataBlock.codewords.length - dataBlock.numDataCodewords);
        if (!correctedBytes) {
            return null;
        }
        for (var i = 0; i < dataBlock.numDataCodewords; i++) {
            resultBytes[resultIndex++] = correctedBytes[i];
        }
    }
    try {
        return decodeData_1.decode(resultBytes, version.versionNumber);
    }
    catch (_a) {
        return null;
    }
}
function decode(matrix) {
    if (matrix == null) {
        return null;
    }
    var result = decodeMatrix(matrix);
    if (result) {
        return result;
    }
    // Decoding didn't work, try mirroring the QR across the topLeft -> bottomRight line.
    for (var x = 0; x < matrix.width; x++) {
        for (var y = x + 1; y < matrix.height; y++) {
            if (matrix.get(x, y) !== matrix.get(y, x)) {
                matrix.set(x, y, !matrix.get(x, y));
                matrix.set(y, x, !matrix.get(y, x));
            }
        }
    }
    return decodeMatrix(matrix);
}
exports.decode = decode;


/***/ }),
/* 6 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

Object.defineProperty(exports, "__esModule", { value: true });
// tslint:disable:no-bitwise
var BitStream_1 = __webpack_require__(7);
var shiftJISTable_1 = __webpack_require__(8);
var Mode;
(function (Mode) {
    Mode["Numeric"] = "numeric";
    Mode["Alphanumeric"] = "alphanumeric";
    Mode["Byte"] = "byte";
    Mode["Kanji"] = "kanji";
    Mode["ECI"] = "eci";
})(Mode = exports.Mode || (exports.Mode = {}));
var ModeByte;
(function (ModeByte) {
    ModeByte[ModeByte["Terminator"] = 0] = "Terminator";
    ModeByte[ModeByte["Numeric"] = 1] = "Numeric";
    ModeByte[ModeByte["Alphanumeric"] = 2] = "Alphanumeric";
    ModeByte[ModeByte["Byte"] = 4] = "Byte";
    ModeByte[ModeByte["Kanji"] = 8] = "Kanji";
    ModeByte[ModeByte["ECI"] = 7] = "ECI";
    // StructuredAppend = 0x3,
    // FNC1FirstPosition = 0x5,
    // FNC1SecondPosition = 0x9,
})(ModeByte || (ModeByte = {}));
function decodeNumeric(stream, size) {
    var bytes = [];
    var text = "";
    var characterCountSize = [10, 12, 14][size];
    var length = stream.readBits(characterCountSize);
    // Read digits in groups of 3
    while (length >= 3) {
        var num = stream.readBits(10);
        if (num >= 1000) {
            throw new Error("Invalid numeric value above 999");
        }
        var a = Math.floor(num / 100);
        var b = Math.floor(num / 10) % 10;
        var c = num % 10;
        bytes.push(48 + a, 48 + b, 48 + c);
        text += a.toString() + b.toString() + c.toString();
        length -= 3;
    }
    // If the number of digits aren't a multiple of 3, the remaining digits are special cased.
    if (length === 2) {
        var num = stream.readBits(7);
        if (num >= 100) {
            throw new Error("Invalid numeric value above 99");
        }
        var a = Math.floor(num / 10);
        var b = num % 10;
        bytes.push(48 + a, 48 + b);
        text += a.toString() + b.toString();
    }
    else if (length === 1) {
        var num = stream.readBits(4);
        if (num >= 10) {
            throw new Error("Invalid numeric value above 9");
        }
        bytes.push(48 + num);
        text += num.toString();
    }
    return { bytes: bytes, text: text };
}
var AlphanumericCharacterCodes = [
    "0", "1", "2", "3", "4", "5", "6", "7", "8",
    "9", "A", "B", "C", "D", "E", "F", "G", "H",
    "I", "J", "K", "L", "M", "N", "O", "P", "Q",
    "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
    " ", "$", "%", "*", "+", "-", ".", "/", ":",
];
function decodeAlphanumeric(stream, size) {
    var bytes = [];
    var text = "";
    var characterCountSize = [9, 11, 13][size];
    var length = stream.readBits(characterCountSize);
    while (length >= 2) {
        var v = stream.readBits(11);
        var a = Math.floor(v / 45);
        var b = v % 45;
        bytes.push(AlphanumericCharacterCodes[a].charCodeAt(0), AlphanumericCharacterCodes[b].charCodeAt(0));
        text += AlphanumericCharacterCodes[a] + AlphanumericCharacterCodes[b];
        length -= 2;
    }
    if (length === 1) {
        var a = stream.readBits(6);
        bytes.push(AlphanumericCharacterCodes[a].charCodeAt(0));
        text += AlphanumericCharacterCodes[a];
    }
    return { bytes: bytes, text: text };
}
function decodeByte(stream, size) {
    var bytes = [];
    var text = "";
    var characterCountSize = [8, 16, 16][size];
    var length = stream.readBits(characterCountSize);
    for (var i = 0; i < length; i++) {
        var b = stream.readBits(8);
        bytes.push(b);
    }
    try {
        text += decodeURIComponent(bytes.map(function (b) { return "%" + ("0" + b.toString(16)).substr(-2); }).join(""));
    }
    catch (_a) {
        // failed to decode
    }
    return { bytes: bytes, text: text };
}
function decodeKanji(stream, size) {
    var bytes = [];
    var text = "";
    var characterCountSize = [8, 10, 12][size];
    var length = stream.readBits(characterCountSize);
    for (var i = 0; i < length; i++) {
        var k = stream.readBits(13);
        var c = (Math.floor(k / 0xC0) << 8) | (k % 0xC0);
        if (c < 0x1F00) {
            c += 0x8140;
        }
        else {
            c += 0xC140;
        }
        bytes.push(c >> 8, c & 0xFF);
        text += String.fromCharCode(shiftJISTable_1.shiftJISTable[c]);
    }
    return { bytes: bytes, text: text };
}
function decode(data, version) {
    var _a, _b, _c, _d;
    var stream = new BitStream_1.BitStream(data);
    // There are 3 'sizes' based on the version. 1-9 is small (0), 10-26 is medium (1) and 27-40 is large (2).
    var size = version <= 9 ? 0 : version <= 26 ? 1 : 2;
    var result = {
        text: "",
        bytes: [],
        chunks: [],
    };
    while (stream.available() >= 4) {
        var mode = stream.readBits(4);
        if (mode === ModeByte.Terminator) {
            return result;
        }
        else if (mode === ModeByte.ECI) {
            if (stream.readBits(1) === 0) {
                result.chunks.push({
                    type: Mode.ECI,
                    assignmentNumber: stream.readBits(7),
                });
            }
            else if (stream.readBits(1) === 0) {
                result.chunks.push({
                    type: Mode.ECI,
                    assignmentNumber: stream.readBits(14),
                });
            }
            else if (stream.readBits(1) === 0) {
                result.chunks.push({
                    type: Mode.ECI,
                    assignmentNumber: stream.readBits(21),
                });
            }
            else {
                // ECI data seems corrupted
                result.chunks.push({
                    type: Mode.ECI,
                    assignmentNumber: -1,
                });
            }
        }
        else if (mode === ModeByte.Numeric) {
            var numericResult = decodeNumeric(stream, size);
            result.text += numericResult.text;
            (_a = result.bytes).push.apply(_a, numericResult.bytes);
            result.chunks.push({
                type: Mode.Numeric,
                text: numericResult.text,
            });
        }
        else if (mode === ModeByte.Alphanumeric) {
            var alphanumericResult = decodeAlphanumeric(stream, size);
            result.text += alphanumericResult.text;
            (_b = result.bytes).push.apply(_b, alphanumericResult.bytes);
            result.chunks.push({
                type: Mode.Alphanumeric,
                text: alphanumericResult.text,
            });
        }
        else if (mode === ModeByte.Byte) {
            var byteResult = decodeByte(stream, size);
            result.text += byteResult.text;
            (_c = result.bytes).push.apply(_c, byteResult.bytes);
            result.chunks.push({
                type: Mode.Byte,
                bytes: byteResult.bytes,
                text: byteResult.text,
            });
        }
        else if (mode === ModeByte.Kanji) {
            var kanjiResult = decodeKanji(stream, size);
            result.text += kanjiResult.text;
            (_d = result.bytes).push.apply(_d, kanjiResult.bytes);
            result.chunks.push({
                type: Mode.Kanji,
                bytes: kanjiResult.bytes,
                text: kanjiResult.text,
            });
        }
    }
    // If there is no data left, or the remaining bits are all 0, then that counts as a termination marker
    if (stream.available() === 0 || stream.readBits(stream.available()) === 0) {
        return result;
    }
}
exports.decode = decode;


/***/ }),
/* 7 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

// tslint:disable:no-bitwise
Object.defineProperty(exports, "__esModule", { value: true });
var BitStream = /** @class */ (function () {
    function BitStream(bytes) {
        this.byteOffset = 0;
        this.bitOffset = 0;
        this.bytes = bytes;
    }
    BitStream.prototype.readBits = function (numBits) {
        if (numBits < 1 || numBits > 32 || numBits > this.available()) {
            throw new Error("Cannot read " + numBits.toString() + " bits");
        }
        var result = 0;
        // First, read remainder from current byte
        if (this.bitOffset > 0) {
            var bitsLeft = 8 - this.bitOffset;
            var toRead = numBits < bitsLeft ? numBits : bitsLeft;
            var bitsToNotRead = bitsLeft - toRead;
            var mask = (0xFF >> (8 - toRead)) << bitsToNotRead;
            result = (this.bytes[this.byteOffset] & mask) >> bitsToNotRead;
            numBits -= toRead;
            this.bitOffset += toRead;
            if (this.bitOffset === 8) {
                this.bitOffset = 0;
                this.byteOffset++;
            }
        }
        // Next read whole bytes
        if (numBits > 0) {
            while (numBits >= 8) {
                result = (result << 8) | (this.bytes[this.byteOffset] & 0xFF);
                this.byteOffset++;
                numBits -= 8;
            }
            // Finally read a partial byte
            if (numBits > 0) {
                var bitsToNotRead = 8 - numBits;
                var mask = (0xFF >> bitsToNotRead) << bitsToNotRead;
                result = (result << numBits) | ((this.bytes[this.byteOffset] & mask) >> bitsToNotRead);
                this.bitOffset += numBits;
            }
        }
        return result;
    };
    BitStream.prototype.available = function () {
        return 8 * (this.bytes.length - this.byteOffset) - this.bitOffset;
    };
    return BitStream;
}());
exports.BitStream = BitStream;


/***/ }),
/* 8 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.shiftJISTable = {
    0x20: 0x0020,
    0x21: 0x0021,
    0x22: 0x0022,
    0x23: 0x0023,
    0x24: 0x0024,
    0x25: 0x0025,
    0x26: 0x0026,
    0x27: 0x0027,
    0x28: 0x0028,
    0x29: 0x0029,
    0x2A: 0x002A,
    0x2B: 0x002B,
    0x2C: 0x002C,
    0x2D: 0x002D,
    0x2E: 0x002E,
    0x2F: 0x002F,
    0x30: 0x0030,
    0x31: 0x0031,
    0x32: 0x0032,
    0x33: 0x0033,
    0x34: 0x0034,
    0x35: 0x0035,
    0x36: 0x0036,
    0x37: 0x0037,
    0x38: 0x0038,
    0x39: 0x0039,
    0x3A: 0x003A,
    0x3B: 0x003B,
    0x3C: 0x003C,
    0x3D: 0x003D,
    0x3E: 0x003E,
    0x3F: 0x003F,
    0x40: 0x0040,
    0x41: 0x0041,
    0x42: 0x0042,
    0x43: 0x0043,
    0x44: 0x0044,
    0x45: 0x0045,
    0x46: 0x0046,
    0x47: 0x0047,
    0x48: 0x0048,
    0x49: 0x0049,
    0x4A: 0x004A,
    0x4B: 0x004B,
    0x4C: 0x004C,
    0x4D: 0x004D,
    0x4E: 0x004E,
    0x4F: 0x004F,
    0x50: 0x0050,
    0x51: 0x0051,
    0x52: 0x0052,
    0x53: 0x0053,
    0x54: 0x0054,
    0x55: 0x0055,
    0x56: 0x0056,
    0x57: 0x0057,
    0x58: 0x0058,
    0x59: 0x0059,
    0x5A: 0x005A,
    0x5B: 0x005B,
    0x5C: 0x00A5,
    0x5D: 0x005D,
    0x5E: 0x005E,
    0x5F: 0x005F,
    0x60: 0x0060,
    0x61: 0x0061,
    0x62: 0x0062,
    0x63: 0x0063,
    0x64: 0x0064,
    0x65: 0x0065,
    0x66: 0x0066,
    0x67: 0x0067,
    0x68: 0x0068,
    0x69: 0x0069,
    0x6A: 0x006A,
    0x6B: 0x006B,
    0x6C: 0x006C,
    0x6D: 0x006D,
    0x6E: 0x006E,
    0x6F: 0x006F,
    0x70: 0x0070,
    0x71: 0x0071,
    0x72: 0x0072,
    0x73: 0x0073,
    0x74: 0x0074,
    0x75: 0x0075,
    0x76: 0x0076,
    0x77: 0x0077,
    0x78: 0x0078,
    0x79: 0x0079,
    0x7A: 0x007A,
    0x7B: 0x007B,
    0x7C: 0x007C,
    0x7D: 0x007D,
    0x7E: 0x203E,
    0x8140: 0x3000,
    0x8141: 0x3001,
    0x8142: 0x3002,
    0x8143: 0xFF0C,
    0x8144: 0xFF0E,
    0x8145: 0x30FB,
    0x8146: 0xFF1A,
    0x8147: 0xFF1B,
    0x8148: 0xFF1F,
    0x8149: 0xFF01,
    0x814A: 0x309B,
    0x814B: 0x309C,
    0x814C: 0x00B4,
    0x814D: 0xFF40,
    0x814E: 0x00A8,
    0x814F: 0xFF3E,
    0x8150: 0xFFE3,
    0x8151: 0xFF3F,
    0x8152: 0x30FD,
    0x8153: 0x30FE,
    0x8154: 0x309D,
    0x8155: 0x309E,
    0x8156: 0x3003,
    0x8157: 0x4EDD,
    0x8158: 0x3005,
    0x8159: 0x3006,
    0x815A: 0x3007,
    0x815B: 0x30FC,
    0x815C: 0x2015,
    0x815D: 0x2010,
    0x815E: 0xFF0F,
    0x815F: 0x005C,
    0x8160: 0x301C,
    0x8161: 0x2016,
    0x8162: 0xFF5C,
    0x8163: 0x2026,
    0x8164: 0x2025,
    0x8165: 0x2018,
    0x8166: 0x2019,
    0x8167: 0x201C,
    0x8168: 0x201D,
    0x8169: 0xFF08,
    0x816A: 0xFF09,
    0x816B: 0x3014,
    0x816C: 0x3015,
    0x816D: 0xFF3B,
    0x816E: 0xFF3D,
    0x816F: 0xFF5B,
    0x8170: 0xFF5D,
    0x8171: 0x3008,
    0x8172: 0x3009,
    0x8173: 0x300A,
    0x8174: 0x300B,
    0x8175: 0x300C,
    0x8176: 0x300D,
    0x8177: 0x300E,
    0x8178: 0x300F,
    0x8179: 0x3010,
    0x817A: 0x3011,
    0x817B: 0xFF0B,
    0x817C: 0x2212,
    0x817D: 0x00B1,
    0x817E: 0x00D7,
    0x8180: 0x00F7,
    0x8181: 0xFF1D,
    0x8182: 0x2260,
    0x8183: 0xFF1C,
    0x8184: 0xFF1E,
    0x8185: 0x2266,
    0x8186: 0x2267,
    0x8187: 0x221E,
    0x8188: 0x2234,
    0x8189: 0x2642,
    0x818A: 0x2640,
    0x818B: 0x00B0,
    0x818C: 0x2032,
    0x818D: 0x2033,
    0x818E: 0x2103,
    0x818F: 0xFFE5,
    0x8190: 0xFF04,
    0x8191: 0x00A2,
    0x8192: 0x00A3,
    0x8193: 0xFF05,
    0x8194: 0xFF03,
    0x8195: 0xFF06,
    0x8196: 0xFF0A,
    0x8197: 0xFF20,
    0x8198: 0x00A7,
    0x8199: 0x2606,
    0x819A: 0x2605,
    0x819B: 0x25CB,
    0x819C: 0x25CF,
    0x819D: 0x25CE,
    0x819E: 0x25C7,
    0x819F: 0x25C6,
    0x81A0: 0x25A1,
    0x81A1: 0x25A0,
    0x81A2: 0x25B3,
    0x81A3: 0x25B2,
    0x81A4: 0x25BD,
    0x81A5: 0x25BC,
    0x81A6: 0x203B,
    0x81A7: 0x3012,
    0x81A8: 0x2192,
    0x81A9: 0x2190,
    0x81AA: 0x2191,
    0x81AB: 0x2193,
    0x81AC: 0x3013,
    0x81B8: 0x2208,
    0x81B9: 0x220B,
    0x81BA: 0x2286,
    0x81BB: 0x2287,
    0x81BC: 0x2282,
    0x81BD: 0x2283,
    0x81BE: 0x222A,
    0x81BF: 0x2229,
    0x81C8: 0x2227,
    0x81C9: 0x2228,
    0x81CA: 0x00AC,
    0x81CB: 0x21D2,
    0x81CC: 0x21D4,
    0x81CD: 0x2200,
    0x81CE: 0x2203,
    0x81DA: 0x2220,
    0x81DB: 0x22A5,
    0x81DC: 0x2312,
    0x81DD: 0x2202,
    0x81DE: 0x2207,
    0x81DF: 0x2261,
    0x81E0: 0x2252,
    0x81E1: 0x226A,
    0x81E2: 0x226B,
    0x81E3: 0x221A,
    0x81E4: 0x223D,
    0x81E5: 0x221D,
    0x81E6: 0x2235,
    0x81E7: 0x222B,
    0x81E8: 0x222C,
    0x81F0: 0x212B,
    0x81F1: 0x2030,
    0x81F2: 0x266F,
    0x81F3: 0x266D,
    0x81F4: 0x266A,
    0x81F5: 0x2020,
    0x81F6: 0x2021,
    0x81F7: 0x00B6,
    0x81FC: 0x25EF,
    0x824F: 0xFF10,
    0x8250: 0xFF11,
    0x8251: 0xFF12,
    0x8252: 0xFF13,
    0x8253: 0xFF14,
    0x8254: 0xFF15,
    0x8255: 0xFF16,
    0x8256: 0xFF17,
    0x8257: 0xFF18,
    0x8258: 0xFF19,
    0x8260: 0xFF21,
    0x8261: 0xFF22,
    0x8262: 0xFF23,
    0x8263: 0xFF24,
    0x8264: 0xFF25,
    0x8265: 0xFF26,
    0x8266: 0xFF27,
    0x8267: 0xFF28,
    0x8268: 0xFF29,
    0x8269: 0xFF2A,
    0x826A: 0xFF2B,
    0x826B: 0xFF2C,
    0x826C: 0xFF2D,
    0x826D: 0xFF2E,
    0x826E: 0xFF2F,
    0x826F: 0xFF30,
    0x8270: 0xFF31,
    0x8271: 0xFF32,
    0x8272: 0xFF33,
    0x8273: 0xFF34,
    0x8274: 0xFF35,
    0x8275: 0xFF36,
    0x8276: 0xFF37,
    0x8277: 0xFF38,
    0x8278: 0xFF39,
    0x8279: 0xFF3A,
    0x8281: 0xFF41,
    0x8282: 0xFF42,
    0x8283: 0xFF43,
    0x8284: 0xFF44,
    0x8285: 0xFF45,
    0x8286: 0xFF46,
    0x8287: 0xFF47,
    0x8288: 0xFF48,
    0x8289: 0xFF49,
    0x828A: 0xFF4A,
    0x828B: 0xFF4B,
    0x828C: 0xFF4C,
    0x828D: 0xFF4D,
    0x828E: 0xFF4E,
    0x828F: 0xFF4F,
    0x8290: 0xFF50,
    0x8291: 0xFF51,
    0x8292: 0xFF52,
    0x8293: 0xFF53,
    0x8294: 0xFF54,
    0x8295: 0xFF55,
    0x8296: 0xFF56,
    0x8297: 0xFF57,
    0x8298: 0xFF58,
    0x8299: 0xFF59,
    0x829A: 0xFF5A,
    0x829F: 0x3041,
    0x82A0: 0x3042,
    0x82A1: 0x3043,
    0x82A2: 0x3044,
    0x82A3: 0x3045,
    0x82A4: 0x3046,
    0x82A5: 0x3047,
    0x82A6: 0x3048,
    0x82A7: 0x3049,
    0x82A8: 0x304A,
    0x82A9: 0x304B,
    0x82AA: 0x304C,
    0x82AB: 0x304D,
    0x82AC: 0x304E,
    0x82AD: 0x304F,
    0x82AE: 0x3050,
    0x82AF: 0x3051,
    0x82B0: 0x3052,
    0x82B1: 0x3053,
    0x82B2: 0x3054,
    0x82B3: 0x3055,
    0x82B4: 0x3056,
    0x82B5: 0x3057,
    0x82B6: 0x3058,
    0x82B7: 0x3059,
    0x82B8: 0x305A,
    0x82B9: 0x305B,
    0x82BA: 0x305C,
    0x82BB: 0x305D,
    0x82BC: 0x305E,
    0x82BD: 0x305F,
    0x82BE: 0x3060,
    0x82BF: 0x3061,
    0x82C0: 0x3062,
    0x82C1: 0x3063,
    0x82C2: 0x3064,
    0x82C3: 0x3065,
    0x82C4: 0x3066,
    0x82C5: 0x3067,
    0x82C6: 0x3068,
    0x82C7: 0x3069,
    0x82C8: 0x306A,
    0x82C9: 0x306B,
    0x82CA: 0x306C,
    0x82CB: 0x306D,
    0x82CC: 0x306E,
    0x82CD: 0x306F,
    0x82CE: 0x3070,
    0x82CF: 0x3071,
    0x82D0: 0x3072,
    0x82D1: 0x3073,
    0x82D2: 0x3074,
    0x82D3: 0x3075,
    0x82D4: 0x3076,
    0x82D5: 0x3077,
    0x82D6: 0x3078,
    0x82D7: 0x3079,
    0x82D8: 0x307A,
    0x82D9: 0x307B,
    0x82DA: 0x307C,
    0x82DB: 0x307D,
    0x82DC: 0x307E,
    0x82DD: 0x307F,
    0x82DE: 0x3080,
    0x82DF: 0x3081,
    0x82E0: 0x3082,
    0x82E1: 0x3083,
    0x82E2: 0x3084,
    0x82E3: 0x3085,
    0x82E4: 0x3086,
    0x82E5: 0x3087,
    0x82E6: 0x3088,
    0x82E7: 0x3089,
    0x82E8: 0x308A,
    0x82E9: 0x308B,
    0x82EA: 0x308C,
    0x82EB: 0x308D,
    0x82EC: 0x308E,
    0x82ED: 0x308F,
    0x82EE: 0x3090,
    0x82EF: 0x3091,
    0x82F0: 0x3092,
    0x82F1: 0x3093,
    0x8340: 0x30A1,
    0x8341: 0x30A2,
    0x8342: 0x30A3,
    0x8343: 0x30A4,
    0x8344: 0x30A5,
    0x8345: 0x30A6,
    0x8346: 0x30A7,
    0x8347: 0x30A8,
    0x8348: 0x30A9,
    0x8349: 0x30AA,
    0x834A: 0x30AB,
    0x834B: 0x30AC,
    0x834C: 0x30AD,
    0x834D: 0x30AE,
    0x834E: 0x30AF,
    0x834F: 0x30B0,
    0x8350: 0x30B1,
    0x8351: 0x30B2,
    0x8352: 0x30B3,
    0x8353: 0x30B4,
    0x8354: 0x30B5,
    0x8355: 0x30B6,
    0x8356: 0x30B7,
    0x8357: 0x30B8,
    0x8358: 0x30B9,
    0x8359: 0x30BA,
    0x835A: 0x30BB,
    0x835B: 0x30BC,
    0x835C: 0x30BD,
    0x835D: 0x30BE,
    0x835E: 0x30BF,
    0x835F: 0x30C0,
    0x8360: 0x30C1,
    0x8361: 0x30C2,
    0x8362: 0x30C3,
    0x8363: 0x30C4,
    0x8364: 0x30C5,
    0x8365: 0x30C6,
    0x8366: 0x30C7,
    0x8367: 0x30C8,
    0x8368: 0x30C9,
    0x8369: 0x30CA,
    0x836A: 0x30CB,
    0x836B: 0x30CC,
    0x836C: 0x30CD,
    0x836D: 0x30CE,
    0x836E: 0x30CF,
    0x836F: 0x30D0,
    0x8370: 0x30D1,
    0x8371: 0x30D2,
    0x8372: 0x30D3,
    0x8373: 0x30D4,
    0x8374: 0x30D5,
    0x8375: 0x30D6,
    0x8376: 0x30D7,
    0x8377: 0x30D8,
    0x8378: 0x30D9,
    0x8379: 0x30DA,
    0x837A: 0x30DB,
    0x837B: 0x30DC,
    0x837C: 0x30DD,
    0x837D: 0x30DE,
    0x837E: 0x30DF,
    0x8380: 0x30E0,
    0x8381: 0x30E1,
    0x8382: 0x30E2,
    0x8383: 0x30E3,
    0x8384: 0x30E4,
    0x8385: 0x30E5,
    0x8386: 0x30E6,
    0x8387: 0x30E7,
    0x8388: 0x30E8,
    0x8389: 0x30E9,
    0x838A: 0x30EA,
    0x838B: 0x30EB,
    0x838C: 0x30EC,
    0x838D: 0x30ED,
    0x838E: 0x30EE,
    0x838F: 0x30EF,
    0x8390: 0x30F0,
    0x8391: 0x30F1,
    0x8392: 0x30F2,
    0x8393: 0x30F3,
    0x8394: 0x30F4,
    0x8395: 0x30F5,
    0x8396: 0x30F6,
    0x839F: 0x0391,
    0x83A0: 0x0392,
    0x83A1: 0x0393,
    0x83A2: 0x0394,
    0x83A3: 0x0395,
    0x83A4: 0x0396,
    0x83A5: 0x0397,
    0x83A6: 0x0398,
    0x83A7: 0x0399,
    0x83A8: 0x039A,
    0x83A9: 0x039B,
    0x83AA: 0x039C,
    0x83AB: 0x039D,
    0x83AC: 0x039E,
    0x83AD: 0x039F,
    0x83AE: 0x03A0,
    0x83AF: 0x03A1,
    0x83B0: 0x03A3,
    0x83B1: 0x03A4,
    0x83B2: 0x03A5,
    0x83B3: 0x03A6,
    0x83B4: 0x03A7,
    0x83B5: 0x03A8,
    0x83B6: 0x03A9,
    0x83BF: 0x03B1,
    0x83C0: 0x03B2,
    0x83C1: 0x03B3,
    0x83C2: 0x03B4,
    0x83C3: 0x03B5,
    0x83C4: 0x03B6,
    0x83C5: 0x03B7,
    0x83C6: 0x03B8,
    0x83C7: 0x03B9,
    0x83C8: 0x03BA,
    0x83C9: 0x03BB,
    0x83CA: 0x03BC,
    0x83CB: 0x03BD,
    0x83CC: 0x03BE,
    0x83CD: 0x03BF,
    0x83CE: 0x03C0,
    0x83CF: 0x03C1,
    0x83D0: 0x03C3,
    0x83D1: 0x03C4,
    0x83D2: 0x03C5,
    0x83D3: 0x03C6,
    0x83D4: 0x03C7,
    0x83D5: 0x03C8,
    0x83D6: 0x03C9,
    0x8440: 0x0410,
    0x8441: 0x0411,
    0x8442: 0x0412,
    0x8443: 0x0413,
    0x8444: 0x0414,
    0x8445: 0x0415,
    0x8446: 0x0401,
    0x8447: 0x0416,
    0x8448: 0x0417,
    0x8449: 0x0418,
    0x844A: 0x0419,
    0x844B: 0x041A,
    0x844C: 0x041B,
    0x844D: 0x041C,
    0x844E: 0x041D,
    0x844F: 0x041E,
    0x8450: 0x041F,
    0x8451: 0x0420,
    0x8452: 0x0421,
    0x8453: 0x0422,
    0x8454: 0x0423,
    0x8455: 0x0424,
    0x8456: 0x0425,
    0x8457: 0x0426,
    0x8458: 0x0427,
    0x8459: 0x0428,
    0x845A: 0x0429,
    0x845B: 0x042A,
    0x845C: 0x042B,
    0x845D: 0x042C,
    0x845E: 0x042D,
    0x845F: 0x042E,
    0x8460: 0x042F,
    0x8470: 0x0430,
    0x8471: 0x0431,
    0x8472: 0x0432,
    0x8473: 0x0433,
    0x8474: 0x0434,
    0x8475: 0x0435,
    0x8476: 0x0451,
    0x8477: 0x0436,
    0x8478: 0x0437,
    0x8479: 0x0438,
    0x847A: 0x0439,
    0x847B: 0x043A,
    0x847C: 0x043B,
    0x847D: 0x043C,
    0x847E: 0x043D,
    0x8480: 0x043E,
    0x8481: 0x043F,
    0x8482: 0x0440,
    0x8483: 0x0441,
    0x8484: 0x0442,
    0x8485: 0x0443,
    0x8486: 0x0444,
    0x8487: 0x0445,
    0x8488: 0x0446,
    0x8489: 0x0447,
    0x848A: 0x0448,
    0x848B: 0x0449,
    0x848C: 0x044A,
    0x848D: 0x044B,
    0x848E: 0x044C,
    0x848F: 0x044D,
    0x8490: 0x044E,
    0x8491: 0x044F,
    0x849F: 0x2500,
    0x84A0: 0x2502,
    0x84A1: 0x250C,
    0x84A2: 0x2510,
    0x84A3: 0x2518,
    0x84A4: 0x2514,
    0x84A5: 0x251C,
    0x84A6: 0x252C,
    0x84A7: 0x2524,
    0x84A8: 0x2534,
    0x84A9: 0x253C,
    0x84AA: 0x2501,
    0x84AB: 0x2503,
    0x84AC: 0x250F,
    0x84AD: 0x2513,
    0x84AE: 0x251B,
    0x84AF: 0x2517,
    0x84B0: 0x2523,
    0x84B1: 0x2533,
    0x84B2: 0x252B,
    0x84B3: 0x253B,
    0x84B4: 0x254B,
    0x84B5: 0x2520,
    0x84B6: 0x252F,
    0x84B7: 0x2528,
    0x84B8: 0x2537,
    0x84B9: 0x253F,
    0x84BA: 0x251D,
    0x84BB: 0x2530,
    0x84BC: 0x2525,
    0x84BD: 0x2538,
    0x84BE: 0x2542,
    0x889F: 0x4E9C,
    0x88A0: 0x5516,
    0x88A1: 0x5A03,
    0x88A2: 0x963F,
    0x88A3: 0x54C0,
    0x88A4: 0x611B,
    0x88A5: 0x6328,
    0x88A6: 0x59F6,
    0x88A7: 0x9022,
    0x88A8: 0x8475,
    0x88A9: 0x831C,
    0x88AA: 0x7A50,
    0x88AB: 0x60AA,
    0x88AC: 0x63E1,
    0x88AD: 0x6E25,
    0x88AE: 0x65ED,
    0x88AF: 0x8466,
    0x88B0: 0x82A6,
    0x88B1: 0x9BF5,
    0x88B2: 0x6893,
    0x88B3: 0x5727,
    0x88B4: 0x65A1,
    0x88B5: 0x6271,
    0x88B6: 0x5B9B,
    0x88B7: 0x59D0,
    0x88B8: 0x867B,
    0x88B9: 0x98F4,
    0x88BA: 0x7D62,
    0x88BB: 0x7DBE,
    0x88BC: 0x9B8E,
    0x88BD: 0x6216,
    0x88BE: 0x7C9F,
    0x88BF: 0x88B7,
    0x88C0: 0x5B89,
    0x88C1: 0x5EB5,
    0x88C2: 0x6309,
    0x88C3: 0x6697,
    0x88C4: 0x6848,
    0x88C5: 0x95C7,
    0x88C6: 0x978D,
    0x88C7: 0x674F,
    0x88C8: 0x4EE5,
    0x88C9: 0x4F0A,
    0x88CA: 0x4F4D,
    0x88CB: 0x4F9D,
    0x88CC: 0x5049,
    0x88CD: 0x56F2,
    0x88CE: 0x5937,
    0x88CF: 0x59D4,
    0x88D0: 0x5A01,
    0x88D1: 0x5C09,
    0x88D2: 0x60DF,
    0x88D3: 0x610F,
    0x88D4: 0x6170,
    0x88D5: 0x6613,
    0x88D6: 0x6905,
    0x88D7: 0x70BA,
    0x88D8: 0x754F,
    0x88D9: 0x7570,
    0x88DA: 0x79FB,
    0x88DB: 0x7DAD,
    0x88DC: 0x7DEF,
    0x88DD: 0x80C3,
    0x88DE: 0x840E,
    0x88DF: 0x8863,
    0x88E0: 0x8B02,
    0x88E1: 0x9055,
    0x88E2: 0x907A,
    0x88E3: 0x533B,
    0x88E4: 0x4E95,
    0x88E5: 0x4EA5,
    0x88E6: 0x57DF,
    0x88E7: 0x80B2,
    0x88E8: 0x90C1,
    0x88E9: 0x78EF,
    0x88EA: 0x4E00,
    0x88EB: 0x58F1,
    0x88EC: 0x6EA2,
    0x88ED: 0x9038,
    0x88EE: 0x7A32,
    0x88EF: 0x8328,
    0x88F0: 0x828B,
    0x88F1: 0x9C2F,
    0x88F2: 0x5141,
    0x88F3: 0x5370,
    0x88F4: 0x54BD,
    0x88F5: 0x54E1,
    0x88F6: 0x56E0,
    0x88F7: 0x59FB,
    0x88F8: 0x5F15,
    0x88F9: 0x98F2,
    0x88FA: 0x6DEB,
    0x88FB: 0x80E4,
    0x88FC: 0x852D,
    0x8940: 0x9662,
    0x8941: 0x9670,
    0x8942: 0x96A0,
    0x8943: 0x97FB,
    0x8944: 0x540B,
    0x8945: 0x53F3,
    0x8946: 0x5B87,
    0x8947: 0x70CF,
    0x8948: 0x7FBD,
    0x8949: 0x8FC2,
    0x894A: 0x96E8,
    0x894B: 0x536F,
    0x894C: 0x9D5C,
    0x894D: 0x7ABA,
    0x894E: 0x4E11,
    0x894F: 0x7893,
    0x8950: 0x81FC,
    0x8951: 0x6E26,
    0x8952: 0x5618,
    0x8953: 0x5504,
    0x8954: 0x6B1D,
    0x8955: 0x851A,
    0x8956: 0x9C3B,
    0x8957: 0x59E5,
    0x8958: 0x53A9,
    0x8959: 0x6D66,
    0x895A: 0x74DC,
    0x895B: 0x958F,
    0x895C: 0x5642,
    0x895D: 0x4E91,
    0x895E: 0x904B,
    0x895F: 0x96F2,
    0x8960: 0x834F,
    0x8961: 0x990C,
    0x8962: 0x53E1,
    0x8963: 0x55B6,
    0x8964: 0x5B30,
    0x8965: 0x5F71,
    0x8966: 0x6620,
    0x8967: 0x66F3,
    0x8968: 0x6804,
    0x8969: 0x6C38,
    0x896A: 0x6CF3,
    0x896B: 0x6D29,
    0x896C: 0x745B,
    0x896D: 0x76C8,
    0x896E: 0x7A4E,
    0x896F: 0x9834,
    0x8970: 0x82F1,
    0x8971: 0x885B,
    0x8972: 0x8A60,
    0x8973: 0x92ED,
    0x8974: 0x6DB2,
    0x8975: 0x75AB,
    0x8976: 0x76CA,
    0x8977: 0x99C5,
    0x8978: 0x60A6,
    0x8979: 0x8B01,
    0x897A: 0x8D8A,
    0x897B: 0x95B2,
    0x897C: 0x698E,
    0x897D: 0x53AD,
    0x897E: 0x5186,
    0x8980: 0x5712,
    0x8981: 0x5830,
    0x8982: 0x5944,
    0x8983: 0x5BB4,
    0x8984: 0x5EF6,
    0x8985: 0x6028,
    0x8986: 0x63A9,
    0x8987: 0x63F4,
    0x8988: 0x6CBF,
    0x8989: 0x6F14,
    0x898A: 0x708E,
    0x898B: 0x7114,
    0x898C: 0x7159,
    0x898D: 0x71D5,
    0x898E: 0x733F,
    0x898F: 0x7E01,
    0x8990: 0x8276,
    0x8991: 0x82D1,
    0x8992: 0x8597,
    0x8993: 0x9060,
    0x8994: 0x925B,
    0x8995: 0x9D1B,
    0x8996: 0x5869,
    0x8997: 0x65BC,
    0x8998: 0x6C5A,
    0x8999: 0x7525,
    0x899A: 0x51F9,
    0x899B: 0x592E,
    0x899C: 0x5965,
    0x899D: 0x5F80,
    0x899E: 0x5FDC,
    0x899F: 0x62BC,
    0x89A0: 0x65FA,
    0x89A1: 0x6A2A,
    0x89A2: 0x6B27,
    0x89A3: 0x6BB4,
    0x89A4: 0x738B,
    0x89A5: 0x7FC1,
    0x89A6: 0x8956,
    0x89A7: 0x9D2C,
    0x89A8: 0x9D0E,
    0x89A9: 0x9EC4,
    0x89AA: 0x5CA1,
    0x89AB: 0x6C96,
    0x89AC: 0x837B,
    0x89AD: 0x5104,
    0x89AE: 0x5C4B,
    0x89AF: 0x61B6,
    0x89B0: 0x81C6,
    0x89B1: 0x6876,
    0x89B2: 0x7261,
    0x89B3: 0x4E59,
    0x89B4: 0x4FFA,
    0x89B5: 0x5378,
    0x89B6: 0x6069,
    0x89B7: 0x6E29,
    0x89B8: 0x7A4F,
    0x89B9: 0x97F3,
    0x89BA: 0x4E0B,
    0x89BB: 0x5316,
    0x89BC: 0x4EEE,
    0x89BD: 0x4F55,
    0x89BE: 0x4F3D,
    0x89BF: 0x4FA1,
    0x89C0: 0x4F73,
    0x89C1: 0x52A0,
    0x89C2: 0x53EF,
    0x89C3: 0x5609,
    0x89C4: 0x590F,
    0x89C5: 0x5AC1,
    0x89C6: 0x5BB6,
    0x89C7: 0x5BE1,
    0x89C8: 0x79D1,
    0x89C9: 0x6687,
    0x89CA: 0x679C,
    0x89CB: 0x67B6,
    0x89CC: 0x6B4C,
    0x89CD: 0x6CB3,
    0x89CE: 0x706B,
    0x89CF: 0x73C2,
    0x89D0: 0x798D,
    0x89D1: 0x79BE,
    0x89D2: 0x7A3C,
    0x89D3: 0x7B87,
    0x89D4: 0x82B1,
    0x89D5: 0x82DB,
    0x89D6: 0x8304,
    0x89D7: 0x8377,
    0x89D8: 0x83EF,
    0x89D9: 0x83D3,
    0x89DA: 0x8766,
    0x89DB: 0x8AB2,
    0x89DC: 0x5629,
    0x89DD: 0x8CA8,
    0x89DE: 0x8FE6,
    0x89DF: 0x904E,
    0x89E0: 0x971E,
    0x89E1: 0x868A,
    0x89E2: 0x4FC4,
    0x89E3: 0x5CE8,
    0x89E4: 0x6211,
    0x89E5: 0x7259,
    0x89E6: 0x753B,
    0x89E7: 0x81E5,
    0x89E8: 0x82BD,
    0x89E9: 0x86FE,
    0x89EA: 0x8CC0,
    0x89EB: 0x96C5,
    0x89EC: 0x9913,
    0x89ED: 0x99D5,
    0x89EE: 0x4ECB,
    0x89EF: 0x4F1A,
    0x89F0: 0x89E3,
    0x89F1: 0x56DE,
    0x89F2: 0x584A,
    0x89F3: 0x58CA,
    0x89F4: 0x5EFB,
    0x89F5: 0x5FEB,
    0x89F6: 0x602A,
    0x89F7: 0x6094,
    0x89F8: 0x6062,
    0x89F9: 0x61D0,
    0x89FA: 0x6212,
    0x89FB: 0x62D0,
    0x89FC: 0x6539,
    0x8A40: 0x9B41,
    0x8A41: 0x6666,
    0x8A42: 0x68B0,
    0x8A43: 0x6D77,
    0x8A44: 0x7070,
    0x8A45: 0x754C,
    0x8A46: 0x7686,
    0x8A47: 0x7D75,
    0x8A48: 0x82A5,
    0x8A49: 0x87F9,
    0x8A4A: 0x958B,
    0x8A4B: 0x968E,
    0x8A4C: 0x8C9D,
    0x8A4D: 0x51F1,
    0x8A4E: 0x52BE,
    0x8A4F: 0x5916,
    0x8A50: 0x54B3,
    0x8A51: 0x5BB3,
    0x8A52: 0x5D16,
    0x8A53: 0x6168,
    0x8A54: 0x6982,
    0x8A55: 0x6DAF,
    0x8A56: 0x788D,
    0x8A57: 0x84CB,
    0x8A58: 0x8857,
    0x8A59: 0x8A72,
    0x8A5A: 0x93A7,
    0x8A5B: 0x9AB8,
    0x8A5C: 0x6D6C,
    0x8A5D: 0x99A8,
    0x8A5E: 0x86D9,
    0x8A5F: 0x57A3,
    0x8A60: 0x67FF,
    0x8A61: 0x86CE,
    0x8A62: 0x920E,
    0x8A63: 0x5283,
    0x8A64: 0x5687,
    0x8A65: 0x5404,
    0x8A66: 0x5ED3,
    0x8A67: 0x62E1,
    0x8A68: 0x64B9,
    0x8A69: 0x683C,
    0x8A6A: 0x6838,
    0x8A6B: 0x6BBB,
    0x8A6C: 0x7372,
    0x8A6D: 0x78BA,
    0x8A6E: 0x7A6B,
    0x8A6F: 0x899A,
    0x8A70: 0x89D2,
    0x8A71: 0x8D6B,
    0x8A72: 0x8F03,
    0x8A73: 0x90ED,
    0x8A74: 0x95A3,
    0x8A75: 0x9694,
    0x8A76: 0x9769,
    0x8A77: 0x5B66,
    0x8A78: 0x5CB3,
    0x8A79: 0x697D,
    0x8A7A: 0x984D,
    0x8A7B: 0x984E,
    0x8A7C: 0x639B,
    0x8A7D: 0x7B20,
    0x8A7E: 0x6A2B,
    0x8A80: 0x6A7F,
    0x8A81: 0x68B6,
    0x8A82: 0x9C0D,
    0x8A83: 0x6F5F,
    0x8A84: 0x5272,
    0x8A85: 0x559D,
    0x8A86: 0x6070,
    0x8A87: 0x62EC,
    0x8A88: 0x6D3B,
    0x8A89: 0x6E07,
    0x8A8A: 0x6ED1,
    0x8A8B: 0x845B,
    0x8A8C: 0x8910,
    0x8A8D: 0x8F44,
    0x8A8E: 0x4E14,
    0x8A8F: 0x9C39,
    0x8A90: 0x53F6,
    0x8A91: 0x691B,
    0x8A92: 0x6A3A,
    0x8A93: 0x9784,
    0x8A94: 0x682A,
    0x8A95: 0x515C,
    0x8A96: 0x7AC3,
    0x8A97: 0x84B2,
    0x8A98: 0x91DC,
    0x8A99: 0x938C,
    0x8A9A: 0x565B,
    0x8A9B: 0x9D28,
    0x8A9C: 0x6822,
    0x8A9D: 0x8305,
    0x8A9E: 0x8431,
    0x8A9F: 0x7CA5,
    0x8AA0: 0x5208,
    0x8AA1: 0x82C5,
    0x8AA2: 0x74E6,
    0x8AA3: 0x4E7E,
    0x8AA4: 0x4F83,
    0x8AA5: 0x51A0,
    0x8AA6: 0x5BD2,
    0x8AA7: 0x520A,
    0x8AA8: 0x52D8,
    0x8AA9: 0x52E7,
    0x8AAA: 0x5DFB,
    0x8AAB: 0x559A,
    0x8AAC: 0x582A,
    0x8AAD: 0x59E6,
    0x8AAE: 0x5B8C,
    0x8AAF: 0x5B98,
    0x8AB0: 0x5BDB,
    0x8AB1: 0x5E72,
    0x8AB2: 0x5E79,
    0x8AB3: 0x60A3,
    0x8AB4: 0x611F,
    0x8AB5: 0x6163,
    0x8AB6: 0x61BE,
    0x8AB7: 0x63DB,
    0x8AB8: 0x6562,
    0x8AB9: 0x67D1,
    0x8ABA: 0x6853,
    0x8ABB: 0x68FA,
    0x8ABC: 0x6B3E,
    0x8ABD: 0x6B53,
    0x8ABE: 0x6C57,
    0x8ABF: 0x6F22,
    0x8AC0: 0x6F97,
    0x8AC1: 0x6F45,
    0x8AC2: 0x74B0,
    0x8AC3: 0x7518,
    0x8AC4: 0x76E3,
    0x8AC5: 0x770B,
    0x8AC6: 0x7AFF,
    0x8AC7: 0x7BA1,
    0x8AC8: 0x7C21,
    0x8AC9: 0x7DE9,
    0x8ACA: 0x7F36,
    0x8ACB: 0x7FF0,
    0x8ACC: 0x809D,
    0x8ACD: 0x8266,
    0x8ACE: 0x839E,
    0x8ACF: 0x89B3,
    0x8AD0: 0x8ACC,
    0x8AD1: 0x8CAB,
    0x8AD2: 0x9084,
    0x8AD3: 0x9451,
    0x8AD4: 0x9593,
    0x8AD5: 0x9591,
    0x8AD6: 0x95A2,
    0x8AD7: 0x9665,
    0x8AD8: 0x97D3,
    0x8AD9: 0x9928,
    0x8ADA: 0x8218,
    0x8ADB: 0x4E38,
    0x8ADC: 0x542B,
    0x8ADD: 0x5CB8,
    0x8ADE: 0x5DCC,
    0x8ADF: 0x73A9,
    0x8AE0: 0x764C,
    0x8AE1: 0x773C,
    0x8AE2: 0x5CA9,
    0x8AE3: 0x7FEB,
    0x8AE4: 0x8D0B,
    0x8AE5: 0x96C1,
    0x8AE6: 0x9811,
    0x8AE7: 0x9854,
    0x8AE8: 0x9858,
    0x8AE9: 0x4F01,
    0x8AEA: 0x4F0E,
    0x8AEB: 0x5371,
    0x8AEC: 0x559C,
    0x8AED: 0x5668,
    0x8AEE: 0x57FA,
    0x8AEF: 0x5947,
    0x8AF0: 0x5B09,
    0x8AF1: 0x5BC4,
    0x8AF2: 0x5C90,
    0x8AF3: 0x5E0C,
    0x8AF4: 0x5E7E,
    0x8AF5: 0x5FCC,
    0x8AF6: 0x63EE,
    0x8AF7: 0x673A,
    0x8AF8: 0x65D7,
    0x8AF9: 0x65E2,
    0x8AFA: 0x671F,
    0x8AFB: 0x68CB,
    0x8AFC: 0x68C4,
    0x8B40: 0x6A5F,
    0x8B41: 0x5E30,
    0x8B42: 0x6BC5,
    0x8B43: 0x6C17,
    0x8B44: 0x6C7D,
    0x8B45: 0x757F,
    0x8B46: 0x7948,
    0x8B47: 0x5B63,
    0x8B48: 0x7A00,
    0x8B49: 0x7D00,
    0x8B4A: 0x5FBD,
    0x8B4B: 0x898F,
    0x8B4C: 0x8A18,
    0x8B4D: 0x8CB4,
    0x8B4E: 0x8D77,
    0x8B4F: 0x8ECC,
    0x8B50: 0x8F1D,
    0x8B51: 0x98E2,
    0x8B52: 0x9A0E,
    0x8B53: 0x9B3C,
    0x8B54: 0x4E80,
    0x8B55: 0x507D,
    0x8B56: 0x5100,
    0x8B57: 0x5993,
    0x8B58: 0x5B9C,
    0x8B59: 0x622F,
    0x8B5A: 0x6280,
    0x8B5B: 0x64EC,
    0x8B5C: 0x6B3A,
    0x8B5D: 0x72A0,
    0x8B5E: 0x7591,
    0x8B5F: 0x7947,
    0x8B60: 0x7FA9,
    0x8B61: 0x87FB,
    0x8B62: 0x8ABC,
    0x8B63: 0x8B70,
    0x8B64: 0x63AC,
    0x8B65: 0x83CA,
    0x8B66: 0x97A0,
    0x8B67: 0x5409,
    0x8B68: 0x5403,
    0x8B69: 0x55AB,
    0x8B6A: 0x6854,
    0x8B6B: 0x6A58,
    0x8B6C: 0x8A70,
    0x8B6D: 0x7827,
    0x8B6E: 0x6775,
    0x8B6F: 0x9ECD,
    0x8B70: 0x5374,
    0x8B71: 0x5BA2,
    0x8B72: 0x811A,
    0x8B73: 0x8650,
    0x8B74: 0x9006,
    0x8B75: 0x4E18,
    0x8B76: 0x4E45,
    0x8B77: 0x4EC7,
    0x8B78: 0x4F11,
    0x8B79: 0x53CA,
    0x8B7A: 0x5438,
    0x8B7B: 0x5BAE,
    0x8B7C: 0x5F13,
    0x8B7D: 0x6025,
    0x8B7E: 0x6551,
    0x8B80: 0x673D,
    0x8B81: 0x6C42,
    0x8B82: 0x6C72,
    0x8B83: 0x6CE3,
    0x8B84: 0x7078,
    0x8B85: 0x7403,
    0x8B86: 0x7A76,
    0x8B87: 0x7AAE,
    0x8B88: 0x7B08,
    0x8B89: 0x7D1A,
    0x8B8A: 0x7CFE,
    0x8B8B: 0x7D66,
    0x8B8C: 0x65E7,
    0x8B8D: 0x725B,
    0x8B8E: 0x53BB,
    0x8B8F: 0x5C45,
    0x8B90: 0x5DE8,
    0x8B91: 0x62D2,
    0x8B92: 0x62E0,
    0x8B93: 0x6319,
    0x8B94: 0x6E20,
    0x8B95: 0x865A,
    0x8B96: 0x8A31,
    0x8B97: 0x8DDD,
    0x8B98: 0x92F8,
    0x8B99: 0x6F01,
    0x8B9A: 0x79A6,
    0x8B9B: 0x9B5A,
    0x8B9C: 0x4EA8,
    0x8B9D: 0x4EAB,
    0x8B9E: 0x4EAC,
    0x8B9F: 0x4F9B,
    0x8BA0: 0x4FA0,
    0x8BA1: 0x50D1,
    0x8BA2: 0x5147,
    0x8BA3: 0x7AF6,
    0x8BA4: 0x5171,
    0x8BA5: 0x51F6,
    0x8BA6: 0x5354,
    0x8BA7: 0x5321,
    0x8BA8: 0x537F,
    0x8BA9: 0x53EB,
    0x8BAA: 0x55AC,
    0x8BAB: 0x5883,
    0x8BAC: 0x5CE1,
    0x8BAD: 0x5F37,
    0x8BAE: 0x5F4A,
    0x8BAF: 0x602F,
    0x8BB0: 0x6050,
    0x8BB1: 0x606D,
    0x8BB2: 0x631F,
    0x8BB3: 0x6559,
    0x8BB4: 0x6A4B,
    0x8BB5: 0x6CC1,
    0x8BB6: 0x72C2,
    0x8BB7: 0x72ED,
    0x8BB8: 0x77EF,
    0x8BB9: 0x80F8,
    0x8BBA: 0x8105,
    0x8BBB: 0x8208,
    0x8BBC: 0x854E,
    0x8BBD: 0x90F7,
    0x8BBE: 0x93E1,
    0x8BBF: 0x97FF,
    0x8BC0: 0x9957,
    0x8BC1: 0x9A5A,
    0x8BC2: 0x4EF0,
    0x8BC3: 0x51DD,
    0x8BC4: 0x5C2D,
    0x8BC5: 0x6681,
    0x8BC6: 0x696D,
    0x8BC7: 0x5C40,
    0x8BC8: 0x66F2,
    0x8BC9: 0x6975,
    0x8BCA: 0x7389,
    0x8BCB: 0x6850,
    0x8BCC: 0x7C81,
    0x8BCD: 0x50C5,
    0x8BCE: 0x52E4,
    0x8BCF: 0x5747,
    0x8BD0: 0x5DFE,
    0x8BD1: 0x9326,
    0x8BD2: 0x65A4,
    0x8BD3: 0x6B23,
    0x8BD4: 0x6B3D,
    0x8BD5: 0x7434,
    0x8BD6: 0x7981,
    0x8BD7: 0x79BD,
    0x8BD8: 0x7B4B,
    0x8BD9: 0x7DCA,
    0x8BDA: 0x82B9,
    0x8BDB: 0x83CC,
    0x8BDC: 0x887F,
    0x8BDD: 0x895F,
    0x8BDE: 0x8B39,
    0x8BDF: 0x8FD1,
    0x8BE0: 0x91D1,
    0x8BE1: 0x541F,
    0x8BE2: 0x9280,
    0x8BE3: 0x4E5D,
    0x8BE4: 0x5036,
    0x8BE5: 0x53E5,
    0x8BE6: 0x533A,
    0x8BE7: 0x72D7,
    0x8BE8: 0x7396,
    0x8BE9: 0x77E9,
    0x8BEA: 0x82E6,
    0x8BEB: 0x8EAF,
    0x8BEC: 0x99C6,
    0x8BED: 0x99C8,
    0x8BEE: 0x99D2,
    0x8BEF: 0x5177,
    0x8BF0: 0x611A,
    0x8BF1: 0x865E,
    0x8BF2: 0x55B0,
    0x8BF3: 0x7A7A,
    0x8BF4: 0x5076,
    0x8BF5: 0x5BD3,
    0x8BF6: 0x9047,
    0x8BF7: 0x9685,
    0x8BF8: 0x4E32,
    0x8BF9: 0x6ADB,
    0x8BFA: 0x91E7,
    0x8BFB: 0x5C51,
    0x8BFC: 0x5C48,
    0x8C40: 0x6398,
    0x8C41: 0x7A9F,
    0x8C42: 0x6C93,
    0x8C43: 0x9774,
    0x8C44: 0x8F61,
    0x8C45: 0x7AAA,
    0x8C46: 0x718A,
    0x8C47: 0x9688,
    0x8C48: 0x7C82,
    0x8C49: 0x6817,
    0x8C4A: 0x7E70,
    0x8C4B: 0x6851,
    0x8C4C: 0x936C,
    0x8C4D: 0x52F2,
    0x8C4E: 0x541B,
    0x8C4F: 0x85AB,
    0x8C50: 0x8A13,
    0x8C51: 0x7FA4,
    0x8C52: 0x8ECD,
    0x8C53: 0x90E1,
    0x8C54: 0x5366,
    0x8C55: 0x8888,
    0x8C56: 0x7941,
    0x8C57: 0x4FC2,
    0x8C58: 0x50BE,
    0x8C59: 0x5211,
    0x8C5A: 0x5144,
    0x8C5B: 0x5553,
    0x8C5C: 0x572D,
    0x8C5D: 0x73EA,
    0x8C5E: 0x578B,
    0x8C5F: 0x5951,
    0x8C60: 0x5F62,
    0x8C61: 0x5F84,
    0x8C62: 0x6075,
    0x8C63: 0x6176,
    0x8C64: 0x6167,
    0x8C65: 0x61A9,
    0x8C66: 0x63B2,
    0x8C67: 0x643A,
    0x8C68: 0x656C,
    0x8C69: 0x666F,
    0x8C6A: 0x6842,
    0x8C6B: 0x6E13,
    0x8C6C: 0x7566,
    0x8C6D: 0x7A3D,
    0x8C6E: 0x7CFB,
    0x8C6F: 0x7D4C,
    0x8C70: 0x7D99,
    0x8C71: 0x7E4B,
    0x8C72: 0x7F6B,
    0x8C73: 0x830E,
    0x8C74: 0x834A,
    0x8C75: 0x86CD,
    0x8C76: 0x8A08,
    0x8C77: 0x8A63,
    0x8C78: 0x8B66,
    0x8C79: 0x8EFD,
    0x8C7A: 0x981A,
    0x8C7B: 0x9D8F,
    0x8C7C: 0x82B8,
    0x8C7D: 0x8FCE,
    0x8C7E: 0x9BE8,
    0x8C80: 0x5287,
    0x8C81: 0x621F,
    0x8C82: 0x6483,
    0x8C83: 0x6FC0,
    0x8C84: 0x9699,
    0x8C85: 0x6841,
    0x8C86: 0x5091,
    0x8C87: 0x6B20,
    0x8C88: 0x6C7A,
    0x8C89: 0x6F54,
    0x8C8A: 0x7A74,
    0x8C8B: 0x7D50,
    0x8C8C: 0x8840,
    0x8C8D: 0x8A23,
    0x8C8E: 0x6708,
    0x8C8F: 0x4EF6,
    0x8C90: 0x5039,
    0x8C91: 0x5026,
    0x8C92: 0x5065,
    0x8C93: 0x517C,
    0x8C94: 0x5238,
    0x8C95: 0x5263,
    0x8C96: 0x55A7,
    0x8C97: 0x570F,
    0x8C98: 0x5805,
    0x8C99: 0x5ACC,
    0x8C9A: 0x5EFA,
    0x8C9B: 0x61B2,
    0x8C9C: 0x61F8,
    0x8C9D: 0x62F3,
    0x8C9E: 0x6372,
    0x8C9F: 0x691C,
    0x8CA0: 0x6A29,
    0x8CA1: 0x727D,
    0x8CA2: 0x72AC,
    0x8CA3: 0x732E,
    0x8CA4: 0x7814,
    0x8CA5: 0x786F,
    0x8CA6: 0x7D79,
    0x8CA7: 0x770C,
    0x8CA8: 0x80A9,
    0x8CA9: 0x898B,
    0x8CAA: 0x8B19,
    0x8CAB: 0x8CE2,
    0x8CAC: 0x8ED2,
    0x8CAD: 0x9063,
    0x8CAE: 0x9375,
    0x8CAF: 0x967A,
    0x8CB0: 0x9855,
    0x8CB1: 0x9A13,
    0x8CB2: 0x9E78,
    0x8CB3: 0x5143,
    0x8CB4: 0x539F,
    0x8CB5: 0x53B3,
    0x8CB6: 0x5E7B,
    0x8CB7: 0x5F26,
    0x8CB8: 0x6E1B,
    0x8CB9: 0x6E90,
    0x8CBA: 0x7384,
    0x8CBB: 0x73FE,
    0x8CBC: 0x7D43,
    0x8CBD: 0x8237,
    0x8CBE: 0x8A00,
    0x8CBF: 0x8AFA,
    0x8CC0: 0x9650,
    0x8CC1: 0x4E4E,
    0x8CC2: 0x500B,
    0x8CC3: 0x53E4,
    0x8CC4: 0x547C,
    0x8CC5: 0x56FA,
    0x8CC6: 0x59D1,
    0x8CC7: 0x5B64,
    0x8CC8: 0x5DF1,
    0x8CC9: 0x5EAB,
    0x8CCA: 0x5F27,
    0x8CCB: 0x6238,
    0x8CCC: 0x6545,
    0x8CCD: 0x67AF,
    0x8CCE: 0x6E56,
    0x8CCF: 0x72D0,
    0x8CD0: 0x7CCA,
    0x8CD1: 0x88B4,
    0x8CD2: 0x80A1,
    0x8CD3: 0x80E1,
    0x8CD4: 0x83F0,
    0x8CD5: 0x864E,
    0x8CD6: 0x8A87,
    0x8CD7: 0x8DE8,
    0x8CD8: 0x9237,
    0x8CD9: 0x96C7,
    0x8CDA: 0x9867,
    0x8CDB: 0x9F13,
    0x8CDC: 0x4E94,
    0x8CDD: 0x4E92,
    0x8CDE: 0x4F0D,
    0x8CDF: 0x5348,
    0x8CE0: 0x5449,
    0x8CE1: 0x543E,
    0x8CE2: 0x5A2F,
    0x8CE3: 0x5F8C,
    0x8CE4: 0x5FA1,
    0x8CE5: 0x609F,
    0x8CE6: 0x68A7,
    0x8CE7: 0x6A8E,
    0x8CE8: 0x745A,
    0x8CE9: 0x7881,
    0x8CEA: 0x8A9E,
    0x8CEB: 0x8AA4,
    0x8CEC: 0x8B77,
    0x8CED: 0x9190,
    0x8CEE: 0x4E5E,
    0x8CEF: 0x9BC9,
    0x8CF0: 0x4EA4,
    0x8CF1: 0x4F7C,
    0x8CF2: 0x4FAF,
    0x8CF3: 0x5019,
    0x8CF4: 0x5016,
    0x8CF5: 0x5149,
    0x8CF6: 0x516C,
    0x8CF7: 0x529F,
    0x8CF8: 0x52B9,
    0x8CF9: 0x52FE,
    0x8CFA: 0x539A,
    0x8CFB: 0x53E3,
    0x8CFC: 0x5411,
    0x8D40: 0x540E,
    0x8D41: 0x5589,
    0x8D42: 0x5751,
    0x8D43: 0x57A2,
    0x8D44: 0x597D,
    0x8D45: 0x5B54,
    0x8D46: 0x5B5D,
    0x8D47: 0x5B8F,
    0x8D48: 0x5DE5,
    0x8D49: 0x5DE7,
    0x8D4A: 0x5DF7,
    0x8D4B: 0x5E78,
    0x8D4C: 0x5E83,
    0x8D4D: 0x5E9A,
    0x8D4E: 0x5EB7,
    0x8D4F: 0x5F18,
    0x8D50: 0x6052,
    0x8D51: 0x614C,
    0x8D52: 0x6297,
    0x8D53: 0x62D8,
    0x8D54: 0x63A7,
    0x8D55: 0x653B,
    0x8D56: 0x6602,
    0x8D57: 0x6643,
    0x8D58: 0x66F4,
    0x8D59: 0x676D,
    0x8D5A: 0x6821,
    0x8D5B: 0x6897,
    0x8D5C: 0x69CB,
    0x8D5D: 0x6C5F,
    0x8D5E: 0x6D2A,
    0x8D5F: 0x6D69,
    0x8D60: 0x6E2F,
    0x8D61: 0x6E9D,
    0x8D62: 0x7532,
    0x8D63: 0x7687,
    0x8D64: 0x786C,
    0x8D65: 0x7A3F,
    0x8D66: 0x7CE0,
    0x8D67: 0x7D05,
    0x8D68: 0x7D18,
    0x8D69: 0x7D5E,
    0x8D6A: 0x7DB1,
    0x8D6B: 0x8015,
    0x8D6C: 0x8003,
    0x8D6D: 0x80AF,
    0x8D6E: 0x80B1,
    0x8D6F: 0x8154,
    0x8D70: 0x818F,
    0x8D71: 0x822A,
    0x8D72: 0x8352,
    0x8D73: 0x884C,
    0x8D74: 0x8861,
    0x8D75: 0x8B1B,
    0x8D76: 0x8CA2,
    0x8D77: 0x8CFC,
    0x8D78: 0x90CA,
    0x8D79: 0x9175,
    0x8D7A: 0x9271,
    0x8D7B: 0x783F,
    0x8D7C: 0x92FC,
    0x8D7D: 0x95A4,
    0x8D7E: 0x964D,
    0x8D80: 0x9805,
    0x8D81: 0x9999,
    0x8D82: 0x9AD8,
    0x8D83: 0x9D3B,
    0x8D84: 0x525B,
    0x8D85: 0x52AB,
    0x8D86: 0x53F7,
    0x8D87: 0x5408,
    0x8D88: 0x58D5,
    0x8D89: 0x62F7,
    0x8D8A: 0x6FE0,
    0x8D8B: 0x8C6A,
    0x8D8C: 0x8F5F,
    0x8D8D: 0x9EB9,
    0x8D8E: 0x514B,
    0x8D8F: 0x523B,
    0x8D90: 0x544A,
    0x8D91: 0x56FD,
    0x8D92: 0x7A40,
    0x8D93: 0x9177,
    0x8D94: 0x9D60,
    0x8D95: 0x9ED2,
    0x8D96: 0x7344,
    0x8D97: 0x6F09,
    0x8D98: 0x8170,
    0x8D99: 0x7511,
    0x8D9A: 0x5FFD,
    0x8D9B: 0x60DA,
    0x8D9C: 0x9AA8,
    0x8D9D: 0x72DB,
    0x8D9E: 0x8FBC,
    0x8D9F: 0x6B64,
    0x8DA0: 0x9803,
    0x8DA1: 0x4ECA,
    0x8DA2: 0x56F0,
    0x8DA3: 0x5764,
    0x8DA4: 0x58BE,
    0x8DA5: 0x5A5A,
    0x8DA6: 0x6068,
    0x8DA7: 0x61C7,
    0x8DA8: 0x660F,
    0x8DA9: 0x6606,
    0x8DAA: 0x6839,
    0x8DAB: 0x68B1,
    0x8DAC: 0x6DF7,
    0x8DAD: 0x75D5,
    0x8DAE: 0x7D3A,
    0x8DAF: 0x826E,
    0x8DB0: 0x9B42,
    0x8DB1: 0x4E9B,
    0x8DB2: 0x4F50,
    0x8DB3: 0x53C9,
    0x8DB4: 0x5506,
    0x8DB5: 0x5D6F,
    0x8DB6: 0x5DE6,
    0x8DB7: 0x5DEE,
    0x8DB8: 0x67FB,
    0x8DB9: 0x6C99,
    0x8DBA: 0x7473,
    0x8DBB: 0x7802,
    0x8DBC: 0x8A50,
    0x8DBD: 0x9396,
    0x8DBE: 0x88DF,
    0x8DBF: 0x5750,
    0x8DC0: 0x5EA7,
    0x8DC1: 0x632B,
    0x8DC2: 0x50B5,
    0x8DC3: 0x50AC,
    0x8DC4: 0x518D,
    0x8DC5: 0x6700,
    0x8DC6: 0x54C9,
    0x8DC7: 0x585E,
    0x8DC8: 0x59BB,
    0x8DC9: 0x5BB0,
    0x8DCA: 0x5F69,
    0x8DCB: 0x624D,
    0x8DCC: 0x63A1,
    0x8DCD: 0x683D,
    0x8DCE: 0x6B73,
    0x8DCF: 0x6E08,
    0x8DD0: 0x707D,
    0x8DD1: 0x91C7,
    0x8DD2: 0x7280,
    0x8DD3: 0x7815,
    0x8DD4: 0x7826,
    0x8DD5: 0x796D,
    0x8DD6: 0x658E,
    0x8DD7: 0x7D30,
    0x8DD8: 0x83DC,
    0x8DD9: 0x88C1,
    0x8DDA: 0x8F09,
    0x8DDB: 0x969B,
    0x8DDC: 0x5264,
    0x8DDD: 0x5728,
    0x8DDE: 0x6750,
    0x8DDF: 0x7F6A,
    0x8DE0: 0x8CA1,
    0x8DE1: 0x51B4,
    0x8DE2: 0x5742,
    0x8DE3: 0x962A,
    0x8DE4: 0x583A,
    0x8DE5: 0x698A,
    0x8DE6: 0x80B4,
    0x8DE7: 0x54B2,
    0x8DE8: 0x5D0E,
    0x8DE9: 0x57FC,
    0x8DEA: 0x7895,
    0x8DEB: 0x9DFA,
    0x8DEC: 0x4F5C,
    0x8DED: 0x524A,
    0x8DEE: 0x548B,
    0x8DEF: 0x643E,
    0x8DF0: 0x6628,
    0x8DF1: 0x6714,
    0x8DF2: 0x67F5,
    0x8DF3: 0x7A84,
    0x8DF4: 0x7B56,
    0x8DF5: 0x7D22,
    0x8DF6: 0x932F,
    0x8DF7: 0x685C,
    0x8DF8: 0x9BAD,
    0x8DF9: 0x7B39,
    0x8DFA: 0x5319,
    0x8DFB: 0x518A,
    0x8DFC: 0x5237,
    0x8E40: 0x5BDF,
    0x8E41: 0x62F6,
    0x8E42: 0x64AE,
    0x8E43: 0x64E6,
    0x8E44: 0x672D,
    0x8E45: 0x6BBA,
    0x8E46: 0x85A9,
    0x8E47: 0x96D1,
    0x8E48: 0x7690,
    0x8E49: 0x9BD6,
    0x8E4A: 0x634C,
    0x8E4B: 0x9306,
    0x8E4C: 0x9BAB,
    0x8E4D: 0x76BF,
    0x8E4E: 0x6652,
    0x8E4F: 0x4E09,
    0x8E50: 0x5098,
    0x8E51: 0x53C2,
    0x8E52: 0x5C71,
    0x8E53: 0x60E8,
    0x8E54: 0x6492,
    0x8E55: 0x6563,
    0x8E56: 0x685F,
    0x8E57: 0x71E6,
    0x8E58: 0x73CA,
    0x8E59: 0x7523,
    0x8E5A: 0x7B97,
    0x8E5B: 0x7E82,
    0x8E5C: 0x8695,
    0x8E5D: 0x8B83,
    0x8E5E: 0x8CDB,
    0x8E5F: 0x9178,
    0x8E60: 0x9910,
    0x8E61: 0x65AC,
    0x8E62: 0x66AB,
    0x8E63: 0x6B8B,
    0x8E64: 0x4ED5,
    0x8E65: 0x4ED4,
    0x8E66: 0x4F3A,
    0x8E67: 0x4F7F,
    0x8E68: 0x523A,
    0x8E69: 0x53F8,
    0x8E6A: 0x53F2,
    0x8E6B: 0x55E3,
    0x8E6C: 0x56DB,
    0x8E6D: 0x58EB,
    0x8E6E: 0x59CB,
    0x8E6F: 0x59C9,
    0x8E70: 0x59FF,
    0x8E71: 0x5B50,
    0x8E72: 0x5C4D,
    0x8E73: 0x5E02,
    0x8E74: 0x5E2B,
    0x8E75: 0x5FD7,
    0x8E76: 0x601D,
    0x8E77: 0x6307,
    0x8E78: 0x652F,
    0x8E79: 0x5B5C,
    0x8E7A: 0x65AF,
    0x8E7B: 0x65BD,
    0x8E7C: 0x65E8,
    0x8E7D: 0x679D,
    0x8E7E: 0x6B62,
    0x8E80: 0x6B7B,
    0x8E81: 0x6C0F,
    0x8E82: 0x7345,
    0x8E83: 0x7949,
    0x8E84: 0x79C1,
    0x8E85: 0x7CF8,
    0x8E86: 0x7D19,
    0x8E87: 0x7D2B,
    0x8E88: 0x80A2,
    0x8E89: 0x8102,
    0x8E8A: 0x81F3,
    0x8E8B: 0x8996,
    0x8E8C: 0x8A5E,
    0x8E8D: 0x8A69,
    0x8E8E: 0x8A66,
    0x8E8F: 0x8A8C,
    0x8E90: 0x8AEE,
    0x8E91: 0x8CC7,
    0x8E92: 0x8CDC,
    0x8E93: 0x96CC,
    0x8E94: 0x98FC,
    0x8E95: 0x6B6F,
    0x8E96: 0x4E8B,
    0x8E97: 0x4F3C,
    0x8E98: 0x4F8D,
    0x8E99: 0x5150,
    0x8E9A: 0x5B57,
    0x8E9B: 0x5BFA,
    0x8E9C: 0x6148,
    0x8E9D: 0x6301,
    0x8E9E: 0x6642,
    0x8E9F: 0x6B21,
    0x8EA0: 0x6ECB,
    0x8EA1: 0x6CBB,
    0x8EA2: 0x723E,
    0x8EA3: 0x74BD,
    0x8EA4: 0x75D4,
    0x8EA5: 0x78C1,
    0x8EA6: 0x793A,
    0x8EA7: 0x800C,
    0x8EA8: 0x8033,
    0x8EA9: 0x81EA,
    0x8EAA: 0x8494,
    0x8EAB: 0x8F9E,
    0x8EAC: 0x6C50,
    0x8EAD: 0x9E7F,
    0x8EAE: 0x5F0F,
    0x8EAF: 0x8B58,
    0x8EB0: 0x9D2B,
    0x8EB1: 0x7AFA,
    0x8EB2: 0x8EF8,
    0x8EB3: 0x5B8D,
    0x8EB4: 0x96EB,
    0x8EB5: 0x4E03,
    0x8EB6: 0x53F1,
    0x8EB7: 0x57F7,
    0x8EB8: 0x5931,
    0x8EB9: 0x5AC9,
    0x8EBA: 0x5BA4,
    0x8EBB: 0x6089,
    0x8EBC: 0x6E7F,
    0x8EBD: 0x6F06,
    0x8EBE: 0x75BE,
    0x8EBF: 0x8CEA,
    0x8EC0: 0x5B9F,
    0x8EC1: 0x8500,
    0x8EC2: 0x7BE0,
    0x8EC3: 0x5072,
    0x8EC4: 0x67F4,
    0x8EC5: 0x829D,
    0x8EC6: 0x5C61,
    0x8EC7: 0x854A,
    0x8EC8: 0x7E1E,
    0x8EC9: 0x820E,
    0x8ECA: 0x5199,
    0x8ECB: 0x5C04,
    0x8ECC: 0x6368,
    0x8ECD: 0x8D66,
    0x8ECE: 0x659C,
    0x8ECF: 0x716E,
    0x8ED0: 0x793E,
    0x8ED1: 0x7D17,
    0x8ED2: 0x8005,
    0x8ED3: 0x8B1D,
    0x8ED4: 0x8ECA,
    0x8ED5: 0x906E,
    0x8ED6: 0x86C7,
    0x8ED7: 0x90AA,
    0x8ED8: 0x501F,
    0x8ED9: 0x52FA,
    0x8EDA: 0x5C3A,
    0x8EDB: 0x6753,
    0x8EDC: 0x707C,
    0x8EDD: 0x7235,
    0x8EDE: 0x914C,
    0x8EDF: 0x91C8,
    0x8EE0: 0x932B,
    0x8EE1: 0x82E5,
    0x8EE2: 0x5BC2,
    0x8EE3: 0x5F31,
    0x8EE4: 0x60F9,
    0x8EE5: 0x4E3B,
    0x8EE6: 0x53D6,
    0x8EE7: 0x5B88,
    0x8EE8: 0x624B,
    0x8EE9: 0x6731,
    0x8EEA: 0x6B8A,
    0x8EEB: 0x72E9,
    0x8EEC: 0x73E0,
    0x8EED: 0x7A2E,
    0x8EEE: 0x816B,
    0x8EEF: 0x8DA3,
    0x8EF0: 0x9152,
    0x8EF1: 0x9996,
    0x8EF2: 0x5112,
    0x8EF3: 0x53D7,
    0x8EF4: 0x546A,
    0x8EF5: 0x5BFF,
    0x8EF6: 0x6388,
    0x8EF7: 0x6A39,
    0x8EF8: 0x7DAC,
    0x8EF9: 0x9700,
    0x8EFA: 0x56DA,
    0x8EFB: 0x53CE,
    0x8EFC: 0x5468,
    0x8F40: 0x5B97,
    0x8F41: 0x5C31,
    0x8F42: 0x5DDE,
    0x8F43: 0x4FEE,
    0x8F44: 0x6101,
    0x8F45: 0x62FE,
    0x8F46: 0x6D32,
    0x8F47: 0x79C0,
    0x8F48: 0x79CB,
    0x8F49: 0x7D42,
    0x8F4A: 0x7E4D,
    0x8F4B: 0x7FD2,
    0x8F4C: 0x81ED,
    0x8F4D: 0x821F,
    0x8F4E: 0x8490,
    0x8F4F: 0x8846,
    0x8F50: 0x8972,
    0x8F51: 0x8B90,
    0x8F52: 0x8E74,
    0x8F53: 0x8F2F,
    0x8F54: 0x9031,
    0x8F55: 0x914B,
    0x8F56: 0x916C,
    0x8F57: 0x96C6,
    0x8F58: 0x919C,
    0x8F59: 0x4EC0,
    0x8F5A: 0x4F4F,
    0x8F5B: 0x5145,
    0x8F5C: 0x5341,
    0x8F5D: 0x5F93,
    0x8F5E: 0x620E,
    0x8F5F: 0x67D4,
    0x8F60: 0x6C41,
    0x8F61: 0x6E0B,
    0x8F62: 0x7363,
    0x8F63: 0x7E26,
    0x8F64: 0x91CD,
    0x8F65: 0x9283,
    0x8F66: 0x53D4,
    0x8F67: 0x5919,
    0x8F68: 0x5BBF,
    0x8F69: 0x6DD1,
    0x8F6A: 0x795D,
    0x8F6B: 0x7E2E,
    0x8F6C: 0x7C9B,
    0x8F6D: 0x587E,
    0x8F6E: 0x719F,
    0x8F6F: 0x51FA,
    0x8F70: 0x8853,
    0x8F71: 0x8FF0,
    0x8F72: 0x4FCA,
    0x8F73: 0x5CFB,
    0x8F74: 0x6625,
    0x8F75: 0x77AC,
    0x8F76: 0x7AE3,
    0x8F77: 0x821C,
    0x8F78: 0x99FF,
    0x8F79: 0x51C6,
    0x8F7A: 0x5FAA,
    0x8F7B: 0x65EC,
    0x8F7C: 0x696F,
    0x8F7D: 0x6B89,
    0x8F7E: 0x6DF3,
    0x8F80: 0x6E96,
    0x8F81: 0x6F64,
    0x8F82: 0x76FE,
    0x8F83: 0x7D14,
    0x8F84: 0x5DE1,
    0x8F85: 0x9075,
    0x8F86: 0x9187,
    0x8F87: 0x9806,
    0x8F88: 0x51E6,
    0x8F89: 0x521D,
    0x8F8A: 0x6240,
    0x8F8B: 0x6691,
    0x8F8C: 0x66D9,
    0x8F8D: 0x6E1A,
    0x8F8E: 0x5EB6,
    0x8F8F: 0x7DD2,
    0x8F90: 0x7F72,
    0x8F91: 0x66F8,
    0x8F92: 0x85AF,
    0x8F93: 0x85F7,
    0x8F94: 0x8AF8,
    0x8F95: 0x52A9,
    0x8F96: 0x53D9,
    0x8F97: 0x5973,
    0x8F98: 0x5E8F,
    0x8F99: 0x5F90,
    0x8F9A: 0x6055,
    0x8F9B: 0x92E4,
    0x8F9C: 0x9664,
    0x8F9D: 0x50B7,
    0x8F9E: 0x511F,
    0x8F9F: 0x52DD,
    0x8FA0: 0x5320,
    0x8FA1: 0x5347,
    0x8FA2: 0x53EC,
    0x8FA3: 0x54E8,
    0x8FA4: 0x5546,
    0x8FA5: 0x5531,
    0x8FA6: 0x5617,
    0x8FA7: 0x5968,
    0x8FA8: 0x59BE,
    0x8FA9: 0x5A3C,
    0x8FAA: 0x5BB5,
    0x8FAB: 0x5C06,
    0x8FAC: 0x5C0F,
    0x8FAD: 0x5C11,
    0x8FAE: 0x5C1A,
    0x8FAF: 0x5E84,
    0x8FB0: 0x5E8A,
    0x8FB1: 0x5EE0,
    0x8FB2: 0x5F70,
    0x8FB3: 0x627F,
    0x8FB4: 0x6284,
    0x8FB5: 0x62DB,
    0x8FB6: 0x638C,
    0x8FB7: 0x6377,
    0x8FB8: 0x6607,
    0x8FB9: 0x660C,
    0x8FBA: 0x662D,
    0x8FBB: 0x6676,
    0x8FBC: 0x677E,
    0x8FBD: 0x68A2,
    0x8FBE: 0x6A1F,
    0x8FBF: 0x6A35,
    0x8FC0: 0x6CBC,
    0x8FC1: 0x6D88,
    0x8FC2: 0x6E09,
    0x8FC3: 0x6E58,
    0x8FC4: 0x713C,
    0x8FC5: 0x7126,
    0x8FC6: 0x7167,
    0x8FC7: 0x75C7,
    0x8FC8: 0x7701,
    0x8FC9: 0x785D,
    0x8FCA: 0x7901,
    0x8FCB: 0x7965,
    0x8FCC: 0x79F0,
    0x8FCD: 0x7AE0,
    0x8FCE: 0x7B11,
    0x8FCF: 0x7CA7,
    0x8FD0: 0x7D39,
    0x8FD1: 0x8096,
    0x8FD2: 0x83D6,
    0x8FD3: 0x848B,
    0x8FD4: 0x8549,
    0x8FD5: 0x885D,
    0x8FD6: 0x88F3,
    0x8FD7: 0x8A1F,
    0x8FD8: 0x8A3C,
    0x8FD9: 0x8A54,
    0x8FDA: 0x8A73,
    0x8FDB: 0x8C61,
    0x8FDC: 0x8CDE,
    0x8FDD: 0x91A4,
    0x8FDE: 0x9266,
    0x8FDF: 0x937E,
    0x8FE0: 0x9418,
    0x8FE1: 0x969C,
    0x8FE2: 0x9798,
    0x8FE3: 0x4E0A,
    0x8FE4: 0x4E08,
    0x8FE5: 0x4E1E,
    0x8FE6: 0x4E57,
    0x8FE7: 0x5197,
    0x8FE8: 0x5270,
    0x8FE9: 0x57CE,
    0x8FEA: 0x5834,
    0x8FEB: 0x58CC,
    0x8FEC: 0x5B22,
    0x8FED: 0x5E38,
    0x8FEE: 0x60C5,
    0x8FEF: 0x64FE,
    0x8FF0: 0x6761,
    0x8FF1: 0x6756,
    0x8FF2: 0x6D44,
    0x8FF3: 0x72B6,
    0x8FF4: 0x7573,
    0x8FF5: 0x7A63,
    0x8FF6: 0x84B8,
    0x8FF7: 0x8B72,
    0x8FF8: 0x91B8,
    0x8FF9: 0x9320,
    0x8FFA: 0x5631,
    0x8FFB: 0x57F4,
    0x8FFC: 0x98FE,
    0x9040: 0x62ED,
    0x9041: 0x690D,
    0x9042: 0x6B96,
    0x9043: 0x71ED,
    0x9044: 0x7E54,
    0x9045: 0x8077,
    0x9046: 0x8272,
    0x9047: 0x89E6,
    0x9048: 0x98DF,
    0x9049: 0x8755,
    0x904A: 0x8FB1,
    0x904B: 0x5C3B,
    0x904C: 0x4F38,
    0x904D: 0x4FE1,
    0x904E: 0x4FB5,
    0x904F: 0x5507,
    0x9050: 0x5A20,
    0x9051: 0x5BDD,
    0x9052: 0x5BE9,
    0x9053: 0x5FC3,
    0x9054: 0x614E,
    0x9055: 0x632F,
    0x9056: 0x65B0,
    0x9057: 0x664B,
    0x9058: 0x68EE,
    0x9059: 0x699B,
    0x905A: 0x6D78,
    0x905B: 0x6DF1,
    0x905C: 0x7533,
    0x905D: 0x75B9,
    0x905E: 0x771F,
    0x905F: 0x795E,
    0x9060: 0x79E6,
    0x9061: 0x7D33,
    0x9062: 0x81E3,
    0x9063: 0x82AF,
    0x9064: 0x85AA,
    0x9065: 0x89AA,
    0x9066: 0x8A3A,
    0x9067: 0x8EAB,
    0x9068: 0x8F9B,
    0x9069: 0x9032,
    0x906A: 0x91DD,
    0x906B: 0x9707,
    0x906C: 0x4EBA,
    0x906D: 0x4EC1,
    0x906E: 0x5203,
    0x906F: 0x5875,
    0x9070: 0x58EC,
    0x9071: 0x5C0B,
    0x9072: 0x751A,
    0x9073: 0x5C3D,
    0x9074: 0x814E,
    0x9075: 0x8A0A,
    0x9076: 0x8FC5,
    0x9077: 0x9663,
    0x9078: 0x976D,
    0x9079: 0x7B25,
    0x907A: 0x8ACF,
    0x907B: 0x9808,
    0x907C: 0x9162,
    0x907D: 0x56F3,
    0x907E: 0x53A8,
    0x9080: 0x9017,
    0x9081: 0x5439,
    0x9082: 0x5782,
    0x9083: 0x5E25,
    0x9084: 0x63A8,
    0x9085: 0x6C34,
    0x9086: 0x708A,
    0x9087: 0x7761,
    0x9088: 0x7C8B,
    0x9089: 0x7FE0,
    0x908A: 0x8870,
    0x908B: 0x9042,
    0x908C: 0x9154,
    0x908D: 0x9310,
    0x908E: 0x9318,
    0x908F: 0x968F,
    0x9090: 0x745E,
    0x9091: 0x9AC4,
    0x9092: 0x5D07,
    0x9093: 0x5D69,
    0x9094: 0x6570,
    0x9095: 0x67A2,
    0x9096: 0x8DA8,
    0x9097: 0x96DB,
    0x9098: 0x636E,
    0x9099: 0x6749,
    0x909A: 0x6919,
    0x909B: 0x83C5,
    0x909C: 0x9817,
    0x909D: 0x96C0,
    0x909E: 0x88FE,
    0x909F: 0x6F84,
    0x90A0: 0x647A,
    0x90A1: 0x5BF8,
    0x90A2: 0x4E16,
    0x90A3: 0x702C,
    0x90A4: 0x755D,
    0x90A5: 0x662F,
    0x90A6: 0x51C4,
    0x90A7: 0x5236,
    0x90A8: 0x52E2,
    0x90A9: 0x59D3,
    0x90AA: 0x5F81,
    0x90AB: 0x6027,
    0x90AC: 0x6210,
    0x90AD: 0x653F,
    0x90AE: 0x6574,
    0x90AF: 0x661F,
    0x90B0: 0x6674,
    0x90B1: 0x68F2,
    0x90B2: 0x6816,
    0x90B3: 0x6B63,
    0x90B4: 0x6E05,
    0x90B5: 0x7272,
    0x90B6: 0x751F,
    0x90B7: 0x76DB,
    0x90B8: 0x7CBE,
    0x90B9: 0x8056,
    0x90BA: 0x58F0,
    0x90BB: 0x88FD,
    0x90BC: 0x897F,
    0x90BD: 0x8AA0,
    0x90BE: 0x8A93,
    0x90BF: 0x8ACB,
    0x90C0: 0x901D,
    0x90C1: 0x9192,
    0x90C2: 0x9752,
    0x90C3: 0x9759,
    0x90C4: 0x6589,
    0x90C5: 0x7A0E,
    0x90C6: 0x8106,
    0x90C7: 0x96BB,
    0x90C8: 0x5E2D,
    0x90C9: 0x60DC,
    0x90CA: 0x621A,
    0x90CB: 0x65A5,
    0x90CC: 0x6614,
    0x90CD: 0x6790,
    0x90CE: 0x77F3,
    0x90CF: 0x7A4D,
    0x90D0: 0x7C4D,
    0x90D1: 0x7E3E,
    0x90D2: 0x810A,
    0x90D3: 0x8CAC,
    0x90D4: 0x8D64,
    0x90D5: 0x8DE1,
    0x90D6: 0x8E5F,
    0x90D7: 0x78A9,
    0x90D8: 0x5207,
    0x90D9: 0x62D9,
    0x90DA: 0x63A5,
    0x90DB: 0x6442,
    0x90DC: 0x6298,
    0x90DD: 0x8A2D,
    0x90DE: 0x7A83,
    0x90DF: 0x7BC0,
    0x90E0: 0x8AAC,
    0x90E1: 0x96EA,
    0x90E2: 0x7D76,
    0x90E3: 0x820C,
    0x90E4: 0x8749,
    0x90E5: 0x4ED9,
    0x90E6: 0x5148,
    0x90E7: 0x5343,
    0x90E8: 0x5360,
    0x90E9: 0x5BA3,
    0x90EA: 0x5C02,
    0x90EB: 0x5C16,
    0x90EC: 0x5DDD,
    0x90ED: 0x6226,
    0x90EE: 0x6247,
    0x90EF: 0x64B0,
    0x90F0: 0x6813,
    0x90F1: 0x6834,
    0x90F2: 0x6CC9,
    0x90F3: 0x6D45,
    0x90F4: 0x6D17,
    0x90F5: 0x67D3,
    0x90F6: 0x6F5C,
    0x90F7: 0x714E,
    0x90F8: 0x717D,
    0x90F9: 0x65CB,
    0x90FA: 0x7A7F,
    0x90FB: 0x7BAD,
    0x90FC: 0x7DDA,
    0x9140: 0x7E4A,
    0x9141: 0x7FA8,
    0x9142: 0x817A,
    0x9143: 0x821B,
    0x9144: 0x8239,
    0x9145: 0x85A6,
    0x9146: 0x8A6E,
    0x9147: 0x8CCE,
    0x9148: 0x8DF5,
    0x9149: 0x9078,
    0x914A: 0x9077,
    0x914B: 0x92AD,
    0x914C: 0x9291,
    0x914D: 0x9583,
    0x914E: 0x9BAE,
    0x914F: 0x524D,
    0x9150: 0x5584,
    0x9151: 0x6F38,
    0x9152: 0x7136,
    0x9153: 0x5168,
    0x9154: 0x7985,
    0x9155: 0x7E55,
    0x9156: 0x81B3,
    0x9157: 0x7CCE,
    0x9158: 0x564C,
    0x9159: 0x5851,
    0x915A: 0x5CA8,
    0x915B: 0x63AA,
    0x915C: 0x66FE,
    0x915D: 0x66FD,
    0x915E: 0x695A,
    0x915F: 0x72D9,
    0x9160: 0x758F,
    0x9161: 0x758E,
    0x9162: 0x790E,
    0x9163: 0x7956,
    0x9164: 0x79DF,
    0x9165: 0x7C97,
    0x9166: 0x7D20,
    0x9167: 0x7D44,
    0x9168: 0x8607,
    0x9169: 0x8A34,
    0x916A: 0x963B,
    0x916B: 0x9061,
    0x916C: 0x9F20,
    0x916D: 0x50E7,
    0x916E: 0x5275,
    0x916F: 0x53CC,
    0x9170: 0x53E2,
    0x9171: 0x5009,
    0x9172: 0x55AA,
    0x9173: 0x58EE,
    0x9174: 0x594F,
    0x9175: 0x723D,
    0x9176: 0x5B8B,
    0x9177: 0x5C64,
    0x9178: 0x531D,
    0x9179: 0x60E3,
    0x917A: 0x60F3,
    0x917B: 0x635C,
    0x917C: 0x6383,
    0x917D: 0x633F,
    0x917E: 0x63BB,
    0x9180: 0x64CD,
    0x9181: 0x65E9,
    0x9182: 0x66F9,
    0x9183: 0x5DE3,
    0x9184: 0x69CD,
    0x9185: 0x69FD,
    0x9186: 0x6F15,
    0x9187: 0x71E5,
    0x9188: 0x4E89,
    0x9189: 0x75E9,
    0x918A: 0x76F8,
    0x918B: 0x7A93,
    0x918C: 0x7CDF,
    0x918D: 0x7DCF,
    0x918E: 0x7D9C,
    0x918F: 0x8061,
    0x9190: 0x8349,
    0x9191: 0x8358,
    0x9192: 0x846C,
    0x9193: 0x84BC,
    0x9194: 0x85FB,
    0x9195: 0x88C5,
    0x9196: 0x8D70,
    0x9197: 0x9001,
    0x9198: 0x906D,
    0x9199: 0x9397,
    0x919A: 0x971C,
    0x919B: 0x9A12,
    0x919C: 0x50CF,
    0x919D: 0x5897,
    0x919E: 0x618E,
    0x919F: 0x81D3,
    0x91A0: 0x8535,
    0x91A1: 0x8D08,
    0x91A2: 0x9020,
    0x91A3: 0x4FC3,
    0x91A4: 0x5074,
    0x91A5: 0x5247,
    0x91A6: 0x5373,
    0x91A7: 0x606F,
    0x91A8: 0x6349,
    0x91A9: 0x675F,
    0x91AA: 0x6E2C,
    0x91AB: 0x8DB3,
    0x91AC: 0x901F,
    0x91AD: 0x4FD7,
    0x91AE: 0x5C5E,
    0x91AF: 0x8CCA,
    0x91B0: 0x65CF,
    0x91B1: 0x7D9A,
    0x91B2: 0x5352,
    0x91B3: 0x8896,
    0x91B4: 0x5176,
    0x91B5: 0x63C3,
    0x91B6: 0x5B58,
    0x91B7: 0x5B6B,
    0x91B8: 0x5C0A,
    0x91B9: 0x640D,
    0x91BA: 0x6751,
    0x91BB: 0x905C,
    0x91BC: 0x4ED6,
    0x91BD: 0x591A,
    0x91BE: 0x592A,
    0x91BF: 0x6C70,
    0x91C0: 0x8A51,
    0x91C1: 0x553E,
    0x91C2: 0x5815,
    0x91C3: 0x59A5,
    0x91C4: 0x60F0,
    0x91C5: 0x6253,
    0x91C6: 0x67C1,
    0x91C7: 0x8235,
    0x91C8: 0x6955,
    0x91C9: 0x9640,
    0x91CA: 0x99C4,
    0x91CB: 0x9A28,
    0x91CC: 0x4F53,
    0x91CD: 0x5806,
    0x91CE: 0x5BFE,
    0x91CF: 0x8010,
    0x91D0: 0x5CB1,
    0x91D1: 0x5E2F,
    0x91D2: 0x5F85,
    0x91D3: 0x6020,
    0x91D4: 0x614B,
    0x91D5: 0x6234,
    0x91D6: 0x66FF,
    0x91D7: 0x6CF0,
    0x91D8: 0x6EDE,
    0x91D9: 0x80CE,
    0x91DA: 0x817F,
    0x91DB: 0x82D4,
    0x91DC: 0x888B,
    0x91DD: 0x8CB8,
    0x91DE: 0x9000,
    0x91DF: 0x902E,
    0x91E0: 0x968A,
    0x91E1: 0x9EDB,
    0x91E2: 0x9BDB,
    0x91E3: 0x4EE3,
    0x91E4: 0x53F0,
    0x91E5: 0x5927,
    0x91E6: 0x7B2C,
    0x91E7: 0x918D,
    0x91E8: 0x984C,
    0x91E9: 0x9DF9,
    0x91EA: 0x6EDD,
    0x91EB: 0x7027,
    0x91EC: 0x5353,
    0x91ED: 0x5544,
    0x91EE: 0x5B85,
    0x91EF: 0x6258,
    0x91F0: 0x629E,
    0x91F1: 0x62D3,
    0x91F2: 0x6CA2,
    0x91F3: 0x6FEF,
    0x91F4: 0x7422,
    0x91F5: 0x8A17,
    0x91F6: 0x9438,
    0x91F7: 0x6FC1,
    0x91F8: 0x8AFE,
    0x91F9: 0x8338,
    0x91FA: 0x51E7,
    0x91FB: 0x86F8,
    0x91FC: 0x53EA,
    0x9240: 0x53E9,
    0x9241: 0x4F46,
    0x9242: 0x9054,
    0x9243: 0x8FB0,
    0x9244: 0x596A,
    0x9245: 0x8131,
    0x9246: 0x5DFD,
    0x9247: 0x7AEA,
    0x9248: 0x8FBF,
    0x9249: 0x68DA,
    0x924A: 0x8C37,
    0x924B: 0x72F8,
    0x924C: 0x9C48,
    0x924D: 0x6A3D,
    0x924E: 0x8AB0,
    0x924F: 0x4E39,
    0x9250: 0x5358,
    0x9251: 0x5606,
    0x9252: 0x5766,
    0x9253: 0x62C5,
    0x9254: 0x63A2,
    0x9255: 0x65E6,
    0x9256: 0x6B4E,
    0x9257: 0x6DE1,
    0x9258: 0x6E5B,
    0x9259: 0x70AD,
    0x925A: 0x77ED,
    0x925B: 0x7AEF,
    0x925C: 0x7BAA,
    0x925D: 0x7DBB,
    0x925E: 0x803D,
    0x925F: 0x80C6,
    0x9260: 0x86CB,
    0x9261: 0x8A95,
    0x9262: 0x935B,
    0x9263: 0x56E3,
    0x9264: 0x58C7,
    0x9265: 0x5F3E,
    0x9266: 0x65AD,
    0x9267: 0x6696,
    0x9268: 0x6A80,
    0x9269: 0x6BB5,
    0x926A: 0x7537,
    0x926B: 0x8AC7,
    0x926C: 0x5024,
    0x926D: 0x77E5,
    0x926E: 0x5730,
    0x926F: 0x5F1B,
    0x9270: 0x6065,
    0x9271: 0x667A,
    0x9272: 0x6C60,
    0x9273: 0x75F4,
    0x9274: 0x7A1A,
    0x9275: 0x7F6E,
    0x9276: 0x81F4,
    0x9277: 0x8718,
    0x9278: 0x9045,
    0x9279: 0x99B3,
    0x927A: 0x7BC9,
    0x927B: 0x755C,
    0x927C: 0x7AF9,
    0x927D: 0x7B51,
    0x927E: 0x84C4,
    0x9280: 0x9010,
    0x9281: 0x79E9,
    0x9282: 0x7A92,
    0x9283: 0x8336,
    0x9284: 0x5AE1,
    0x9285: 0x7740,
    0x9286: 0x4E2D,
    0x9287: 0x4EF2,
    0x9288: 0x5B99,
    0x9289: 0x5FE0,
    0x928A: 0x62BD,
    0x928B: 0x663C,
    0x928C: 0x67F1,
    0x928D: 0x6CE8,
    0x928E: 0x866B,
    0x928F: 0x8877,
    0x9290: 0x8A3B,
    0x9291: 0x914E,
    0x9292: 0x92F3,
    0x9293: 0x99D0,
    0x9294: 0x6A17,
    0x9295: 0x7026,
    0x9296: 0x732A,
    0x9297: 0x82E7,
    0x9298: 0x8457,
    0x9299: 0x8CAF,
    0x929A: 0x4E01,
    0x929B: 0x5146,
    0x929C: 0x51CB,
    0x929D: 0x558B,
    0x929E: 0x5BF5,
    0x929F: 0x5E16,
    0x92A0: 0x5E33,
    0x92A1: 0x5E81,
    0x92A2: 0x5F14,
    0x92A3: 0x5F35,
    0x92A4: 0x5F6B,
    0x92A5: 0x5FB4,
    0x92A6: 0x61F2,
    0x92A7: 0x6311,
    0x92A8: 0x66A2,
    0x92A9: 0x671D,
    0x92AA: 0x6F6E,
    0x92AB: 0x7252,
    0x92AC: 0x753A,
    0x92AD: 0x773A,
    0x92AE: 0x8074,
    0x92AF: 0x8139,
    0x92B0: 0x8178,
    0x92B1: 0x8776,
    0x92B2: 0x8ABF,
    0x92B3: 0x8ADC,
    0x92B4: 0x8D85,
    0x92B5: 0x8DF3,
    0x92B6: 0x929A,
    0x92B7: 0x9577,
    0x92B8: 0x9802,
    0x92B9: 0x9CE5,
    0x92BA: 0x52C5,
    0x92BB: 0x6357,
    0x92BC: 0x76F4,
    0x92BD: 0x6715,
    0x92BE: 0x6C88,
    0x92BF: 0x73CD,
    0x92C0: 0x8CC3,
    0x92C1: 0x93AE,
    0x92C2: 0x9673,
    0x92C3: 0x6D25,
    0x92C4: 0x589C,
    0x92C5: 0x690E,
    0x92C6: 0x69CC,
    0x92C7: 0x8FFD,
    0x92C8: 0x939A,
    0x92C9: 0x75DB,
    0x92CA: 0x901A,
    0x92CB: 0x585A,
    0x92CC: 0x6802,
    0x92CD: 0x63B4,
    0x92CE: 0x69FB,
    0x92CF: 0x4F43,
    0x92D0: 0x6F2C,
    0x92D1: 0x67D8,
    0x92D2: 0x8FBB,
    0x92D3: 0x8526,
    0x92D4: 0x7DB4,
    0x92D5: 0x9354,
    0x92D6: 0x693F,
    0x92D7: 0x6F70,
    0x92D8: 0x576A,
    0x92D9: 0x58F7,
    0x92DA: 0x5B2C,
    0x92DB: 0x7D2C,
    0x92DC: 0x722A,
    0x92DD: 0x540A,
    0x92DE: 0x91E3,
    0x92DF: 0x9DB4,
    0x92E0: 0x4EAD,
    0x92E1: 0x4F4E,
    0x92E2: 0x505C,
    0x92E3: 0x5075,
    0x92E4: 0x5243,
    0x92E5: 0x8C9E,
    0x92E6: 0x5448,
    0x92E7: 0x5824,
    0x92E8: 0x5B9A,
    0x92E9: 0x5E1D,
    0x92EA: 0x5E95,
    0x92EB: 0x5EAD,
    0x92EC: 0x5EF7,
    0x92ED: 0x5F1F,
    0x92EE: 0x608C,
    0x92EF: 0x62B5,
    0x92F0: 0x633A,
    0x92F1: 0x63D0,
    0x92F2: 0x68AF,
    0x92F3: 0x6C40,
    0x92F4: 0x7887,
    0x92F5: 0x798E,
    0x92F6: 0x7A0B,
    0x92F7: 0x7DE0,
    0x92F8: 0x8247,
    0x92F9: 0x8A02,
    0x92FA: 0x8AE6,
    0x92FB: 0x8E44,
    0x92FC: 0x9013,
    0x9340: 0x90B8,
    0x9341: 0x912D,
    0x9342: 0x91D8,
    0x9343: 0x9F0E,
    0x9344: 0x6CE5,
    0x9345: 0x6458,
    0x9346: 0x64E2,
    0x9347: 0x6575,
    0x9348: 0x6EF4,
    0x9349: 0x7684,
    0x934A: 0x7B1B,
    0x934B: 0x9069,
    0x934C: 0x93D1,
    0x934D: 0x6EBA,
    0x934E: 0x54F2,
    0x934F: 0x5FB9,
    0x9350: 0x64A4,
    0x9351: 0x8F4D,
    0x9352: 0x8FED,
    0x9353: 0x9244,
    0x9354: 0x5178,
    0x9355: 0x586B,
    0x9356: 0x5929,
    0x9357: 0x5C55,
    0x9358: 0x5E97,
    0x9359: 0x6DFB,
    0x935A: 0x7E8F,
    0x935B: 0x751C,
    0x935C: 0x8CBC,
    0x935D: 0x8EE2,
    0x935E: 0x985B,
    0x935F: 0x70B9,
    0x9360: 0x4F1D,
    0x9361: 0x6BBF,
    0x9362: 0x6FB1,
    0x9363: 0x7530,
    0x9364: 0x96FB,
    0x9365: 0x514E,
    0x9366: 0x5410,
    0x9367: 0x5835,
    0x9368: 0x5857,
    0x9369: 0x59AC,
    0x936A: 0x5C60,
    0x936B: 0x5F92,
    0x936C: 0x6597,
    0x936D: 0x675C,
    0x936E: 0x6E21,
    0x936F: 0x767B,
    0x9370: 0x83DF,
    0x9371: 0x8CED,
    0x9372: 0x9014,
    0x9373: 0x90FD,
    0x9374: 0x934D,
    0x9375: 0x7825,
    0x9376: 0x783A,
    0x9377: 0x52AA,
    0x9378: 0x5EA6,
    0x9379: 0x571F,
    0x937A: 0x5974,
    0x937B: 0x6012,
    0x937C: 0x5012,
    0x937D: 0x515A,
    0x937E: 0x51AC,
    0x9380: 0x51CD,
    0x9381: 0x5200,
    0x9382: 0x5510,
    0x9383: 0x5854,
    0x9384: 0x5858,
    0x9385: 0x5957,
    0x9386: 0x5B95,
    0x9387: 0x5CF6,
    0x9388: 0x5D8B,
    0x9389: 0x60BC,
    0x938A: 0x6295,
    0x938B: 0x642D,
    0x938C: 0x6771,
    0x938D: 0x6843,
    0x938E: 0x68BC,
    0x938F: 0x68DF,
    0x9390: 0x76D7,
    0x9391: 0x6DD8,
    0x9392: 0x6E6F,
    0x9393: 0x6D9B,
    0x9394: 0x706F,
    0x9395: 0x71C8,
    0x9396: 0x5F53,
    0x9397: 0x75D8,
    0x9398: 0x7977,
    0x9399: 0x7B49,
    0x939A: 0x7B54,
    0x939B: 0x7B52,
    0x939C: 0x7CD6,
    0x939D: 0x7D71,
    0x939E: 0x5230,
    0x939F: 0x8463,
    0x93A0: 0x8569,
    0x93A1: 0x85E4,
    0x93A2: 0x8A0E,
    0x93A3: 0x8B04,
    0x93A4: 0x8C46,
    0x93A5: 0x8E0F,
    0x93A6: 0x9003,
    0x93A7: 0x900F,
    0x93A8: 0x9419,
    0x93A9: 0x9676,
    0x93AA: 0x982D,
    0x93AB: 0x9A30,
    0x93AC: 0x95D8,
    0x93AD: 0x50CD,
    0x93AE: 0x52D5,
    0x93AF: 0x540C,
    0x93B0: 0x5802,
    0x93B1: 0x5C0E,
    0x93B2: 0x61A7,
    0x93B3: 0x649E,
    0x93B4: 0x6D1E,
    0x93B5: 0x77B3,
    0x93B6: 0x7AE5,
    0x93B7: 0x80F4,
    0x93B8: 0x8404,
    0x93B9: 0x9053,
    0x93BA: 0x9285,
    0x93BB: 0x5CE0,
    0x93BC: 0x9D07,
    0x93BD: 0x533F,
    0x93BE: 0x5F97,
    0x93BF: 0x5FB3,
    0x93C0: 0x6D9C,
    0x93C1: 0x7279,
    0x93C2: 0x7763,
    0x93C3: 0x79BF,
    0x93C4: 0x7BE4,
    0x93C5: 0x6BD2,
    0x93C6: 0x72EC,
    0x93C7: 0x8AAD,
    0x93C8: 0x6803,
    0x93C9: 0x6A61,
    0x93CA: 0x51F8,
    0x93CB: 0x7A81,
    0x93CC: 0x6934,
    0x93CD: 0x5C4A,
    0x93CE: 0x9CF6,
    0x93CF: 0x82EB,
    0x93D0: 0x5BC5,
    0x93D1: 0x9149,
    0x93D2: 0x701E,
    0x93D3: 0x5678,
    0x93D4: 0x5C6F,
    0x93D5: 0x60C7,
    0x93D6: 0x6566,
    0x93D7: 0x6C8C,
    0x93D8: 0x8C5A,
    0x93D9: 0x9041,
    0x93DA: 0x9813,
    0x93DB: 0x5451,
    0x93DC: 0x66C7,
    0x93DD: 0x920D,
    0x93DE: 0x5948,
    0x93DF: 0x90A3,
    0x93E0: 0x5185,
    0x93E1: 0x4E4D,
    0x93E2: 0x51EA,
    0x93E3: 0x8599,
    0x93E4: 0x8B0E,
    0x93E5: 0x7058,
    0x93E6: 0x637A,
    0x93E7: 0x934B,
    0x93E8: 0x6962,
    0x93E9: 0x99B4,
    0x93EA: 0x7E04,
    0x93EB: 0x7577,
    0x93EC: 0x5357,
    0x93ED: 0x6960,
    0x93EE: 0x8EDF,
    0x93EF: 0x96E3,
    0x93F0: 0x6C5D,
    0x93F1: 0x4E8C,
    0x93F2: 0x5C3C,
    0x93F3: 0x5F10,
    0x93F4: 0x8FE9,
    0x93F5: 0x5302,
    0x93F6: 0x8CD1,
    0x93F7: 0x8089,
    0x93F8: 0x8679,
    0x93F9: 0x5EFF,
    0x93FA: 0x65E5,
    0x93FB: 0x4E73,
    0x93FC: 0x5165,
    0x9440: 0x5982,
    0x9441: 0x5C3F,
    0x9442: 0x97EE,
    0x9443: 0x4EFB,
    0x9444: 0x598A,
    0x9445: 0x5FCD,
    0x9446: 0x8A8D,
    0x9447: 0x6FE1,
    0x9448: 0x79B0,
    0x9449: 0x7962,
    0x944A: 0x5BE7,
    0x944B: 0x8471,
    0x944C: 0x732B,
    0x944D: 0x71B1,
    0x944E: 0x5E74,
    0x944F: 0x5FF5,
    0x9450: 0x637B,
    0x9451: 0x649A,
    0x9452: 0x71C3,
    0x9453: 0x7C98,
    0x9454: 0x4E43,
    0x9455: 0x5EFC,
    0x9456: 0x4E4B,
    0x9457: 0x57DC,
    0x9458: 0x56A2,
    0x9459: 0x60A9,
    0x945A: 0x6FC3,
    0x945B: 0x7D0D,
    0x945C: 0x80FD,
    0x945D: 0x8133,
    0x945E: 0x81BF,
    0x945F: 0x8FB2,
    0x9460: 0x8997,
    0x9461: 0x86A4,
    0x9462: 0x5DF4,
    0x9463: 0x628A,
    0x9464: 0x64AD,
    0x9465: 0x8987,
    0x9466: 0x6777,
    0x9467: 0x6CE2,
    0x9468: 0x6D3E,
    0x9469: 0x7436,
    0x946A: 0x7834,
    0x946B: 0x5A46,
    0x946C: 0x7F75,
    0x946D: 0x82AD,
    0x946E: 0x99AC,
    0x946F: 0x4FF3,
    0x9470: 0x5EC3,
    0x9471: 0x62DD,
    0x9472: 0x6392,
    0x9473: 0x6557,
    0x9474: 0x676F,
    0x9475: 0x76C3,
    0x9476: 0x724C,
    0x9477: 0x80CC,
    0x9478: 0x80BA,
    0x9479: 0x8F29,
    0x947A: 0x914D,
    0x947B: 0x500D,
    0x947C: 0x57F9,
    0x947D: 0x5A92,
    0x947E: 0x6885,
    0x9480: 0x6973,
    0x9481: 0x7164,
    0x9482: 0x72FD,
    0x9483: 0x8CB7,
    0x9484: 0x58F2,
    0x9485: 0x8CE0,
    0x9486: 0x966A,
    0x9487: 0x9019,
    0x9488: 0x877F,
    0x9489: 0x79E4,
    0x948A: 0x77E7,
    0x948B: 0x8429,
    0x948C: 0x4F2F,
    0x948D: 0x5265,
    0x948E: 0x535A,
    0x948F: 0x62CD,
    0x9490: 0x67CF,
    0x9491: 0x6CCA,
    0x9492: 0x767D,
    0x9493: 0x7B94,
    0x9494: 0x7C95,
    0x9495: 0x8236,
    0x9496: 0x8584,
    0x9497: 0x8FEB,
    0x9498: 0x66DD,
    0x9499: 0x6F20,
    0x949A: 0x7206,
    0x949B: 0x7E1B,
    0x949C: 0x83AB,
    0x949D: 0x99C1,
    0x949E: 0x9EA6,
    0x949F: 0x51FD,
    0x94A0: 0x7BB1,
    0x94A1: 0x7872,
    0x94A2: 0x7BB8,
    0x94A3: 0x8087,
    0x94A4: 0x7B48,
    0x94A5: 0x6AE8,
    0x94A6: 0x5E61,
    0x94A7: 0x808C,
    0x94A8: 0x7551,
    0x94A9: 0x7560,
    0x94AA: 0x516B,
    0x94AB: 0x9262,
    0x94AC: 0x6E8C,
    0x94AD: 0x767A,
    0x94AE: 0x9197,
    0x94AF: 0x9AEA,
    0x94B0: 0x4F10,
    0x94B1: 0x7F70,
    0x94B2: 0x629C,
    0x94B3: 0x7B4F,
    0x94B4: 0x95A5,
    0x94B5: 0x9CE9,
    0x94B6: 0x567A,
    0x94B7: 0x5859,
    0x94B8: 0x86E4,
    0x94B9: 0x96BC,
    0x94BA: 0x4F34,
    0x94BB: 0x5224,
    0x94BC: 0x534A,
    0x94BD: 0x53CD,
    0x94BE: 0x53DB,
    0x94BF: 0x5E06,
    0x94C0: 0x642C,
    0x94C1: 0x6591,
    0x94C2: 0x677F,
    0x94C3: 0x6C3E,
    0x94C4: 0x6C4E,
    0x94C5: 0x7248,
    0x94C6: 0x72AF,
    0x94C7: 0x73ED,
    0x94C8: 0x7554,
    0x94C9: 0x7E41,
    0x94CA: 0x822C,
    0x94CB: 0x85E9,
    0x94CC: 0x8CA9,
    0x94CD: 0x7BC4,
    0x94CE: 0x91C6,
    0x94CF: 0x7169,
    0x94D0: 0x9812,
    0x94D1: 0x98EF,
    0x94D2: 0x633D,
    0x94D3: 0x6669,
    0x94D4: 0x756A,
    0x94D5: 0x76E4,
    0x94D6: 0x78D0,
    0x94D7: 0x8543,
    0x94D8: 0x86EE,
    0x94D9: 0x532A,
    0x94DA: 0x5351,
    0x94DB: 0x5426,
    0x94DC: 0x5983,
    0x94DD: 0x5E87,
    0x94DE: 0x5F7C,
    0x94DF: 0x60B2,
    0x94E0: 0x6249,
    0x94E1: 0x6279,
    0x94E2: 0x62AB,
    0x94E3: 0x6590,
    0x94E4: 0x6BD4,
    0x94E5: 0x6CCC,
    0x94E6: 0x75B2,
    0x94E7: 0x76AE,
    0x94E8: 0x7891,
    0x94E9: 0x79D8,
    0x94EA: 0x7DCB,
    0x94EB: 0x7F77,
    0x94EC: 0x80A5,
    0x94ED: 0x88AB,
    0x94EE: 0x8AB9,
    0x94EF: 0x8CBB,
    0x94F0: 0x907F,
    0x94F1: 0x975E,
    0x94F2: 0x98DB,
    0x94F3: 0x6A0B,
    0x94F4: 0x7C38,
    0x94F5: 0x5099,
    0x94F6: 0x5C3E,
    0x94F7: 0x5FAE,
    0x94F8: 0x6787,
    0x94F9: 0x6BD8,
    0x94FA: 0x7435,
    0x94FB: 0x7709,
    0x94FC: 0x7F8E,
    0x9540: 0x9F3B,
    0x9541: 0x67CA,
    0x9542: 0x7A17,
    0x9543: 0x5339,
    0x9544: 0x758B,
    0x9545: 0x9AED,
    0x9546: 0x5F66,
    0x9547: 0x819D,
    0x9548: 0x83F1,
    0x9549: 0x8098,
    0x954A: 0x5F3C,
    0x954B: 0x5FC5,
    0x954C: 0x7562,
    0x954D: 0x7B46,
    0x954E: 0x903C,
    0x954F: 0x6867,
    0x9550: 0x59EB,
    0x9551: 0x5A9B,
    0x9552: 0x7D10,
    0x9553: 0x767E,
    0x9554: 0x8B2C,
    0x9555: 0x4FF5,
    0x9556: 0x5F6A,
    0x9557: 0x6A19,
    0x9558: 0x6C37,
    0x9559: 0x6F02,
    0x955A: 0x74E2,
    0x955B: 0x7968,
    0x955C: 0x8868,
    0x955D: 0x8A55,
    0x955E: 0x8C79,
    0x955F: 0x5EDF,
    0x9560: 0x63CF,
    0x9561: 0x75C5,
    0x9562: 0x79D2,
    0x9563: 0x82D7,
    0x9564: 0x9328,
    0x9565: 0x92F2,
    0x9566: 0x849C,
    0x9567: 0x86ED,
    0x9568: 0x9C2D,
    0x9569: 0x54C1,
    0x956A: 0x5F6C,
    0x956B: 0x658C,
    0x956C: 0x6D5C,
    0x956D: 0x7015,
    0x956E: 0x8CA7,
    0x956F: 0x8CD3,
    0x9570: 0x983B,
    0x9571: 0x654F,
    0x9572: 0x74F6,
    0x9573: 0x4E0D,
    0x9574: 0x4ED8,
    0x9575: 0x57E0,
    0x9576: 0x592B,
    0x9577: 0x5A66,
    0x9578: 0x5BCC,
    0x9579: 0x51A8,
    0x957A: 0x5E03,
    0x957B: 0x5E9C,
    0x957C: 0x6016,
    0x957D: 0x6276,
    0x957E: 0x6577,
    0x9580: 0x65A7,
    0x9581: 0x666E,
    0x9582: 0x6D6E,
    0x9583: 0x7236,
    0x9584: 0x7B26,
    0x9585: 0x8150,
    0x9586: 0x819A,
    0x9587: 0x8299,
    0x9588: 0x8B5C,
    0x9589: 0x8CA0,
    0x958A: 0x8CE6,
    0x958B: 0x8D74,
    0x958C: 0x961C,
    0x958D: 0x9644,
    0x958E: 0x4FAE,
    0x958F: 0x64AB,
    0x9590: 0x6B66,
    0x9591: 0x821E,
    0x9592: 0x8461,
    0x9593: 0x856A,
    0x9594: 0x90E8,
    0x9595: 0x5C01,
    0x9596: 0x6953,
    0x9597: 0x98A8,
    0x9598: 0x847A,
    0x9599: 0x8557,
    0x959A: 0x4F0F,
    0x959B: 0x526F,
    0x959C: 0x5FA9,
    0x959D: 0x5E45,
    0x959E: 0x670D,
    0x959F: 0x798F,
    0x95A0: 0x8179,
    0x95A1: 0x8907,
    0x95A2: 0x8986,
    0x95A3: 0x6DF5,
    0x95A4: 0x5F17,
    0x95A5: 0x6255,
    0x95A6: 0x6CB8,
    0x95A7: 0x4ECF,
    0x95A8: 0x7269,
    0x95A9: 0x9B92,
    0x95AA: 0x5206,
    0x95AB: 0x543B,
    0x95AC: 0x5674,
    0x95AD: 0x58B3,
    0x95AE: 0x61A4,
    0x95AF: 0x626E,
    0x95B0: 0x711A,
    0x95B1: 0x596E,
    0x95B2: 0x7C89,
    0x95B3: 0x7CDE,
    0x95B4: 0x7D1B,
    0x95B5: 0x96F0,
    0x95B6: 0x6587,
    0x95B7: 0x805E,
    0x95B8: 0x4E19,
    0x95B9: 0x4F75,
    0x95BA: 0x5175,
    0x95BB: 0x5840,
    0x95BC: 0x5E63,
    0x95BD: 0x5E73,
    0x95BE: 0x5F0A,
    0x95BF: 0x67C4,
    0x95C0: 0x4E26,
    0x95C1: 0x853D,
    0x95C2: 0x9589,
    0x95C3: 0x965B,
    0x95C4: 0x7C73,
    0x95C5: 0x9801,
    0x95C6: 0x50FB,
    0x95C7: 0x58C1,
    0x95C8: 0x7656,
    0x95C9: 0x78A7,
    0x95CA: 0x5225,
    0x95CB: 0x77A5,
    0x95CC: 0x8511,
    0x95CD: 0x7B86,
    0x95CE: 0x504F,
    0x95CF: 0x5909,
    0x95D0: 0x7247,
    0x95D1: 0x7BC7,
    0x95D2: 0x7DE8,
    0x95D3: 0x8FBA,
    0x95D4: 0x8FD4,
    0x95D5: 0x904D,
    0x95D6: 0x4FBF,
    0x95D7: 0x52C9,
    0x95D8: 0x5A29,
    0x95D9: 0x5F01,
    0x95DA: 0x97AD,
    0x95DB: 0x4FDD,
    0x95DC: 0x8217,
    0x95DD: 0x92EA,
    0x95DE: 0x5703,
    0x95DF: 0x6355,
    0x95E0: 0x6B69,
    0x95E1: 0x752B,
    0x95E2: 0x88DC,
    0x95E3: 0x8F14,
    0x95E4: 0x7A42,
    0x95E5: 0x52DF,
    0x95E6: 0x5893,
    0x95E7: 0x6155,
    0x95E8: 0x620A,
    0x95E9: 0x66AE,
    0x95EA: 0x6BCD,
    0x95EB: 0x7C3F,
    0x95EC: 0x83E9,
    0x95ED: 0x5023,
    0x95EE: 0x4FF8,
    0x95EF: 0x5305,
    0x95F0: 0x5446,
    0x95F1: 0x5831,
    0x95F2: 0x5949,
    0x95F3: 0x5B9D,
    0x95F4: 0x5CF0,
    0x95F5: 0x5CEF,
    0x95F6: 0x5D29,
    0x95F7: 0x5E96,
    0x95F8: 0x62B1,
    0x95F9: 0x6367,
    0x95FA: 0x653E,
    0x95FB: 0x65B9,
    0x95FC: 0x670B,
    0x9640: 0x6CD5,
    0x9641: 0x6CE1,
    0x9642: 0x70F9,
    0x9643: 0x7832,
    0x9644: 0x7E2B,
    0x9645: 0x80DE,
    0x9646: 0x82B3,
    0x9647: 0x840C,
    0x9648: 0x84EC,
    0x9649: 0x8702,
    0x964A: 0x8912,
    0x964B: 0x8A2A,
    0x964C: 0x8C4A,
    0x964D: 0x90A6,
    0x964E: 0x92D2,
    0x964F: 0x98FD,
    0x9650: 0x9CF3,
    0x9651: 0x9D6C,
    0x9652: 0x4E4F,
    0x9653: 0x4EA1,
    0x9654: 0x508D,
    0x9655: 0x5256,
    0x9656: 0x574A,
    0x9657: 0x59A8,
    0x9658: 0x5E3D,
    0x9659: 0x5FD8,
    0x965A: 0x5FD9,
    0x965B: 0x623F,
    0x965C: 0x66B4,
    0x965D: 0x671B,
    0x965E: 0x67D0,
    0x965F: 0x68D2,
    0x9660: 0x5192,
    0x9661: 0x7D21,
    0x9662: 0x80AA,
    0x9663: 0x81A8,
    0x9664: 0x8B00,
    0x9665: 0x8C8C,
    0x9666: 0x8CBF,
    0x9667: 0x927E,
    0x9668: 0x9632,
    0x9669: 0x5420,
    0x966A: 0x982C,
    0x966B: 0x5317,
    0x966C: 0x50D5,
    0x966D: 0x535C,
    0x966E: 0x58A8,
    0x966F: 0x64B2,
    0x9670: 0x6734,
    0x9671: 0x7267,
    0x9672: 0x7766,
    0x9673: 0x7A46,
    0x9674: 0x91E6,
    0x9675: 0x52C3,
    0x9676: 0x6CA1,
    0x9677: 0x6B86,
    0x9678: 0x5800,
    0x9679: 0x5E4C,
    0x967A: 0x5954,
    0x967B: 0x672C,
    0x967C: 0x7FFB,
    0x967D: 0x51E1,
    0x967E: 0x76C6,
    0x9680: 0x6469,
    0x9681: 0x78E8,
    0x9682: 0x9B54,
    0x9683: 0x9EBB,
    0x9684: 0x57CB,
    0x9685: 0x59B9,
    0x9686: 0x6627,
    0x9687: 0x679A,
    0x9688: 0x6BCE,
    0x9689: 0x54E9,
    0x968A: 0x69D9,
    0x968B: 0x5E55,
    0x968C: 0x819C,
    0x968D: 0x6795,
    0x968E: 0x9BAA,
    0x968F: 0x67FE,
    0x9690: 0x9C52,
    0x9691: 0x685D,
    0x9692: 0x4EA6,
    0x9693: 0x4FE3,
    0x9694: 0x53C8,
    0x9695: 0x62B9,
    0x9696: 0x672B,
    0x9697: 0x6CAB,
    0x9698: 0x8FC4,
    0x9699: 0x4FAD,
    0x969A: 0x7E6D,
    0x969B: 0x9EBF,
    0x969C: 0x4E07,
    0x969D: 0x6162,
    0x969E: 0x6E80,
    0x969F: 0x6F2B,
    0x96A0: 0x8513,
    0x96A1: 0x5473,
    0x96A2: 0x672A,
    0x96A3: 0x9B45,
    0x96A4: 0x5DF3,
    0x96A5: 0x7B95,
    0x96A6: 0x5CAC,
    0x96A7: 0x5BC6,
    0x96A8: 0x871C,
    0x96A9: 0x6E4A,
    0x96AA: 0x84D1,
    0x96AB: 0x7A14,
    0x96AC: 0x8108,
    0x96AD: 0x5999,
    0x96AE: 0x7C8D,
    0x96AF: 0x6C11,
    0x96B0: 0x7720,
    0x96B1: 0x52D9,
    0x96B2: 0x5922,
    0x96B3: 0x7121,
    0x96B4: 0x725F,
    0x96B5: 0x77DB,
    0x96B6: 0x9727,
    0x96B7: 0x9D61,
    0x96B8: 0x690B,
    0x96B9: 0x5A7F,
    0x96BA: 0x5A18,
    0x96BB: 0x51A5,
    0x96BC: 0x540D,
    0x96BD: 0x547D,
    0x96BE: 0x660E,
    0x96BF: 0x76DF,
    0x96C0: 0x8FF7,
    0x96C1: 0x9298,
    0x96C2: 0x9CF4,
    0x96C3: 0x59EA,
    0x96C4: 0x725D,
    0x96C5: 0x6EC5,
    0x96C6: 0x514D,
    0x96C7: 0x68C9,
    0x96C8: 0x7DBF,
    0x96C9: 0x7DEC,
    0x96CA: 0x9762,
    0x96CB: 0x9EBA,
    0x96CC: 0x6478,
    0x96CD: 0x6A21,
    0x96CE: 0x8302,
    0x96CF: 0x5984,
    0x96D0: 0x5B5F,
    0x96D1: 0x6BDB,
    0x96D2: 0x731B,
    0x96D3: 0x76F2,
    0x96D4: 0x7DB2,
    0x96D5: 0x8017,
    0x96D6: 0x8499,
    0x96D7: 0x5132,
    0x96D8: 0x6728,
    0x96D9: 0x9ED9,
    0x96DA: 0x76EE,
    0x96DB: 0x6762,
    0x96DC: 0x52FF,
    0x96DD: 0x9905,
    0x96DE: 0x5C24,
    0x96DF: 0x623B,
    0x96E0: 0x7C7E,
    0x96E1: 0x8CB0,
    0x96E2: 0x554F,
    0x96E3: 0x60B6,
    0x96E4: 0x7D0B,
    0x96E5: 0x9580,
    0x96E6: 0x5301,
    0x96E7: 0x4E5F,
    0x96E8: 0x51B6,
    0x96E9: 0x591C,
    0x96EA: 0x723A,
    0x96EB: 0x8036,
    0x96EC: 0x91CE,
    0x96ED: 0x5F25,
    0x96EE: 0x77E2,
    0x96EF: 0x5384,
    0x96F0: 0x5F79,
    0x96F1: 0x7D04,
    0x96F2: 0x85AC,
    0x96F3: 0x8A33,
    0x96F4: 0x8E8D,
    0x96F5: 0x9756,
    0x96F6: 0x67F3,
    0x96F7: 0x85AE,
    0x96F8: 0x9453,
    0x96F9: 0x6109,
    0x96FA: 0x6108,
    0x96FB: 0x6CB9,
    0x96FC: 0x7652,
    0x9740: 0x8AED,
    0x9741: 0x8F38,
    0x9742: 0x552F,
    0x9743: 0x4F51,
    0x9744: 0x512A,
    0x9745: 0x52C7,
    0x9746: 0x53CB,
    0x9747: 0x5BA5,
    0x9748: 0x5E7D,
    0x9749: 0x60A0,
    0x974A: 0x6182,
    0x974B: 0x63D6,
    0x974C: 0x6709,
    0x974D: 0x67DA,
    0x974E: 0x6E67,
    0x974F: 0x6D8C,
    0x9750: 0x7336,
    0x9751: 0x7337,
    0x9752: 0x7531,
    0x9753: 0x7950,
    0x9754: 0x88D5,
    0x9755: 0x8A98,
    0x9756: 0x904A,
    0x9757: 0x9091,
    0x9758: 0x90F5,
    0x9759: 0x96C4,
    0x975A: 0x878D,
    0x975B: 0x5915,
    0x975C: 0x4E88,
    0x975D: 0x4F59,
    0x975E: 0x4E0E,
    0x975F: 0x8A89,
    0x9760: 0x8F3F,
    0x9761: 0x9810,
    0x9762: 0x50AD,
    0x9763: 0x5E7C,
    0x9764: 0x5996,
    0x9765: 0x5BB9,
    0x9766: 0x5EB8,
    0x9767: 0x63DA,
    0x9768: 0x63FA,
    0x9769: 0x64C1,
    0x976A: 0x66DC,
    0x976B: 0x694A,
    0x976C: 0x69D8,
    0x976D: 0x6D0B,
    0x976E: 0x6EB6,
    0x976F: 0x7194,
    0x9770: 0x7528,
    0x9771: 0x7AAF,
    0x9772: 0x7F8A,
    0x9773: 0x8000,
    0x9774: 0x8449,
    0x9775: 0x84C9,
    0x9776: 0x8981,
    0x9777: 0x8B21,
    0x9778: 0x8E0A,
    0x9779: 0x9065,
    0x977A: 0x967D,
    0x977B: 0x990A,
    0x977C: 0x617E,
    0x977D: 0x6291,
    0x977E: 0x6B32,
    0x9780: 0x6C83,
    0x9781: 0x6D74,
    0x9782: 0x7FCC,
    0x9783: 0x7FFC,
    0x9784: 0x6DC0,
    0x9785: 0x7F85,
    0x9786: 0x87BA,
    0x9787: 0x88F8,
    0x9788: 0x6765,
    0x9789: 0x83B1,
    0x978A: 0x983C,
    0x978B: 0x96F7,
    0x978C: 0x6D1B,
    0x978D: 0x7D61,
    0x978E: 0x843D,
    0x978F: 0x916A,
    0x9790: 0x4E71,
    0x9791: 0x5375,
    0x9792: 0x5D50,
    0x9793: 0x6B04,
    0x9794: 0x6FEB,
    0x9795: 0x85CD,
    0x9796: 0x862D,
    0x9797: 0x89A7,
    0x9798: 0x5229,
    0x9799: 0x540F,
    0x979A: 0x5C65,
    0x979B: 0x674E,
    0x979C: 0x68A8,
    0x979D: 0x7406,
    0x979E: 0x7483,
    0x979F: 0x75E2,
    0x97A0: 0x88CF,
    0x97A1: 0x88E1,
    0x97A2: 0x91CC,
    0x97A3: 0x96E2,
    0x97A4: 0x9678,
    0x97A5: 0x5F8B,
    0x97A6: 0x7387,
    0x97A7: 0x7ACB,
    0x97A8: 0x844E,
    0x97A9: 0x63A0,
    0x97AA: 0x7565,
    0x97AB: 0x5289,
    0x97AC: 0x6D41,
    0x97AD: 0x6E9C,
    0x97AE: 0x7409,
    0x97AF: 0x7559,
    0x97B0: 0x786B,
    0x97B1: 0x7C92,
    0x97B2: 0x9686,
    0x97B3: 0x7ADC,
    0x97B4: 0x9F8D,
    0x97B5: 0x4FB6,
    0x97B6: 0x616E,
    0x97B7: 0x65C5,
    0x97B8: 0x865C,
    0x97B9: 0x4E86,
    0x97BA: 0x4EAE,
    0x97BB: 0x50DA,
    0x97BC: 0x4E21,
    0x97BD: 0x51CC,
    0x97BE: 0x5BEE,
    0x97BF: 0x6599,
    0x97C0: 0x6881,
    0x97C1: 0x6DBC,
    0x97C2: 0x731F,
    0x97C3: 0x7642,
    0x97C4: 0x77AD,
    0x97C5: 0x7A1C,
    0x97C6: 0x7CE7,
    0x97C7: 0x826F,
    0x97C8: 0x8AD2,
    0x97C9: 0x907C,
    0x97CA: 0x91CF,
    0x97CB: 0x9675,
    0x97CC: 0x9818,
    0x97CD: 0x529B,
    0x97CE: 0x7DD1,
    0x97CF: 0x502B,
    0x97D0: 0x5398,
    0x97D1: 0x6797,
    0x97D2: 0x6DCB,
    0x97D3: 0x71D0,
    0x97D4: 0x7433,
    0x97D5: 0x81E8,
    0x97D6: 0x8F2A,
    0x97D7: 0x96A3,
    0x97D8: 0x9C57,
    0x97D9: 0x9E9F,
    0x97DA: 0x7460,
    0x97DB: 0x5841,
    0x97DC: 0x6D99,
    0x97DD: 0x7D2F,
    0x97DE: 0x985E,
    0x97DF: 0x4EE4,
    0x97E0: 0x4F36,
    0x97E1: 0x4F8B,
    0x97E2: 0x51B7,
    0x97E3: 0x52B1,
    0x97E4: 0x5DBA,
    0x97E5: 0x601C,
    0x97E6: 0x73B2,
    0x97E7: 0x793C,
    0x97E8: 0x82D3,
    0x97E9: 0x9234,
    0x97EA: 0x96B7,
    0x97EB: 0x96F6,
    0x97EC: 0x970A,
    0x97ED: 0x9E97,
    0x97EE: 0x9F62,
    0x97EF: 0x66A6,
    0x97F0: 0x6B74,
    0x97F1: 0x5217,
    0x97F2: 0x52A3,
    0x97F3: 0x70C8,
    0x97F4: 0x88C2,
    0x97F5: 0x5EC9,
    0x97F6: 0x604B,
    0x97F7: 0x6190,
    0x97F8: 0x6F23,
    0x97F9: 0x7149,
    0x97FA: 0x7C3E,
    0x97FB: 0x7DF4,
    0x97FC: 0x806F,
    0x9840: 0x84EE,
    0x9841: 0x9023,
    0x9842: 0x932C,
    0x9843: 0x5442,
    0x9844: 0x9B6F,
    0x9845: 0x6AD3,
    0x9846: 0x7089,
    0x9847: 0x8CC2,
    0x9848: 0x8DEF,
    0x9849: 0x9732,
    0x984A: 0x52B4,
    0x984B: 0x5A41,
    0x984C: 0x5ECA,
    0x984D: 0x5F04,
    0x984E: 0x6717,
    0x984F: 0x697C,
    0x9850: 0x6994,
    0x9851: 0x6D6A,
    0x9852: 0x6F0F,
    0x9853: 0x7262,
    0x9854: 0x72FC,
    0x9855: 0x7BED,
    0x9856: 0x8001,
    0x9857: 0x807E,
    0x9858: 0x874B,
    0x9859: 0x90CE,
    0x985A: 0x516D,
    0x985B: 0x9E93,
    0x985C: 0x7984,
    0x985D: 0x808B,
    0x985E: 0x9332,
    0x985F: 0x8AD6,
    0x9860: 0x502D,
    0x9861: 0x548C,
    0x9862: 0x8A71,
    0x9863: 0x6B6A,
    0x9864: 0x8CC4,
    0x9865: 0x8107,
    0x9866: 0x60D1,
    0x9867: 0x67A0,
    0x9868: 0x9DF2,
    0x9869: 0x4E99,
    0x986A: 0x4E98,
    0x986B: 0x9C10,
    0x986C: 0x8A6B,
    0x986D: 0x85C1,
    0x986E: 0x8568,
    0x986F: 0x6900,
    0x9870: 0x6E7E,
    0x9871: 0x7897,
    0x9872: 0x8155,
    0x989F: 0x5F0C,
    0x98A0: 0x4E10,
    0x98A1: 0x4E15,
    0x98A2: 0x4E2A,
    0x98A3: 0x4E31,
    0x98A4: 0x4E36,
    0x98A5: 0x4E3C,
    0x98A6: 0x4E3F,
    0x98A7: 0x4E42,
    0x98A8: 0x4E56,
    0x98A9: 0x4E58,
    0x98AA: 0x4E82,
    0x98AB: 0x4E85,
    0x98AC: 0x8C6B,
    0x98AD: 0x4E8A,
    0x98AE: 0x8212,
    0x98AF: 0x5F0D,
    0x98B0: 0x4E8E,
    0x98B1: 0x4E9E,
    0x98B2: 0x4E9F,
    0x98B3: 0x4EA0,
    0x98B4: 0x4EA2,
    0x98B5: 0x4EB0,
    0x98B6: 0x4EB3,
    0x98B7: 0x4EB6,
    0x98B8: 0x4ECE,
    0x98B9: 0x4ECD,
    0x98BA: 0x4EC4,
    0x98BB: 0x4EC6,
    0x98BC: 0x4EC2,
    0x98BD: 0x4ED7,
    0x98BE: 0x4EDE,
    0x98BF: 0x4EED,
    0x98C0: 0x4EDF,
    0x98C1: 0x4EF7,
    0x98C2: 0x4F09,
    0x98C3: 0x4F5A,
    0x98C4: 0x4F30,
    0x98C5: 0x4F5B,
    0x98C6: 0x4F5D,
    0x98C7: 0x4F57,
    0x98C8: 0x4F47,
    0x98C9: 0x4F76,
    0x98CA: 0x4F88,
    0x98CB: 0x4F8F,
    0x98CC: 0x4F98,
    0x98CD: 0x4F7B,
    0x98CE: 0x4F69,
    0x98CF: 0x4F70,
    0x98D0: 0x4F91,
    0x98D1: 0x4F6F,
    0x98D2: 0x4F86,
    0x98D3: 0x4F96,
    0x98D4: 0x5118,
    0x98D5: 0x4FD4,
    0x98D6: 0x4FDF,
    0x98D7: 0x4FCE,
    0x98D8: 0x4FD8,
    0x98D9: 0x4FDB,
    0x98DA: 0x4FD1,
    0x98DB: 0x4FDA,
    0x98DC: 0x4FD0,
    0x98DD: 0x4FE4,
    0x98DE: 0x4FE5,
    0x98DF: 0x501A,
    0x98E0: 0x5028,
    0x98E1: 0x5014,
    0x98E2: 0x502A,
    0x98E3: 0x5025,
    0x98E4: 0x5005,
    0x98E5: 0x4F1C,
    0x98E6: 0x4FF6,
    0x98E7: 0x5021,
    0x98E8: 0x5029,
    0x98E9: 0x502C,
    0x98EA: 0x4FFE,
    0x98EB: 0x4FEF,
    0x98EC: 0x5011,
    0x98ED: 0x5006,
    0x98EE: 0x5043,
    0x98EF: 0x5047,
    0x98F0: 0x6703,
    0x98F1: 0x5055,
    0x98F2: 0x5050,
    0x98F3: 0x5048,
    0x98F4: 0x505A,
    0x98F5: 0x5056,
    0x98F6: 0x506C,
    0x98F7: 0x5078,
    0x98F8: 0x5080,
    0x98F9: 0x509A,
    0x98FA: 0x5085,
    0x98FB: 0x50B4,
    0x98FC: 0x50B2,
    0x9940: 0x50C9,
    0x9941: 0x50CA,
    0x9942: 0x50B3,
    0x9943: 0x50C2,
    0x9944: 0x50D6,
    0x9945: 0x50DE,
    0x9946: 0x50E5,
    0x9947: 0x50ED,
    0x9948: 0x50E3,
    0x9949: 0x50EE,
    0x994A: 0x50F9,
    0x994B: 0x50F5,
    0x994C: 0x5109,
    0x994D: 0x5101,
    0x994E: 0x5102,
    0x994F: 0x5116,
    0x9950: 0x5115,
    0x9951: 0x5114,
    0x9952: 0x511A,
    0x9953: 0x5121,
    0x9954: 0x513A,
    0x9955: 0x5137,
    0x9956: 0x513C,
    0x9957: 0x513B,
    0x9958: 0x513F,
    0x9959: 0x5140,
    0x995A: 0x5152,
    0x995B: 0x514C,
    0x995C: 0x5154,
    0x995D: 0x5162,
    0x995E: 0x7AF8,
    0x995F: 0x5169,
    0x9960: 0x516A,
    0x9961: 0x516E,
    0x9962: 0x5180,
    0x9963: 0x5182,
    0x9964: 0x56D8,
    0x9965: 0x518C,
    0x9966: 0x5189,
    0x9967: 0x518F,
    0x9968: 0x5191,
    0x9969: 0x5193,
    0x996A: 0x5195,
    0x996B: 0x5196,
    0x996C: 0x51A4,
    0x996D: 0x51A6,
    0x996E: 0x51A2,
    0x996F: 0x51A9,
    0x9970: 0x51AA,
    0x9971: 0x51AB,
    0x9972: 0x51B3,
    0x9973: 0x51B1,
    0x9974: 0x51B2,
    0x9975: 0x51B0,
    0x9976: 0x51B5,
    0x9977: 0x51BD,
    0x9978: 0x51C5,
    0x9979: 0x51C9,
    0x997A: 0x51DB,
    0x997B: 0x51E0,
    0x997C: 0x8655,
    0x997D: 0x51E9,
    0x997E: 0x51ED,
    0x9980: 0x51F0,
    0x9981: 0x51F5,
    0x9982: 0x51FE,
    0x9983: 0x5204,
    0x9984: 0x520B,
    0x9985: 0x5214,
    0x9986: 0x520E,
    0x9987: 0x5227,
    0x9988: 0x522A,
    0x9989: 0x522E,
    0x998A: 0x5233,
    0x998B: 0x5239,
    0x998C: 0x524F,
    0x998D: 0x5244,
    0x998E: 0x524B,
    0x998F: 0x524C,
    0x9990: 0x525E,
    0x9991: 0x5254,
    0x9992: 0x526A,
    0x9993: 0x5274,
    0x9994: 0x5269,
    0x9995: 0x5273,
    0x9996: 0x527F,
    0x9997: 0x527D,
    0x9998: 0x528D,
    0x9999: 0x5294,
    0x999A: 0x5292,
    0x999B: 0x5271,
    0x999C: 0x5288,
    0x999D: 0x5291,
    0x999E: 0x8FA8,
    0x999F: 0x8FA7,
    0x99A0: 0x52AC,
    0x99A1: 0x52AD,
    0x99A2: 0x52BC,
    0x99A3: 0x52B5,
    0x99A4: 0x52C1,
    0x99A5: 0x52CD,
    0x99A6: 0x52D7,
    0x99A7: 0x52DE,
    0x99A8: 0x52E3,
    0x99A9: 0x52E6,
    0x99AA: 0x98ED,
    0x99AB: 0x52E0,
    0x99AC: 0x52F3,
    0x99AD: 0x52F5,
    0x99AE: 0x52F8,
    0x99AF: 0x52F9,
    0x99B0: 0x5306,
    0x99B1: 0x5308,
    0x99B2: 0x7538,
    0x99B3: 0x530D,
    0x99B4: 0x5310,
    0x99B5: 0x530F,
    0x99B6: 0x5315,
    0x99B7: 0x531A,
    0x99B8: 0x5323,
    0x99B9: 0x532F,
    0x99BA: 0x5331,
    0x99BB: 0x5333,
    0x99BC: 0x5338,
    0x99BD: 0x5340,
    0x99BE: 0x5346,
    0x99BF: 0x5345,
    0x99C0: 0x4E17,
    0x99C1: 0x5349,
    0x99C2: 0x534D,
    0x99C3: 0x51D6,
    0x99C4: 0x535E,
    0x99C5: 0x5369,
    0x99C6: 0x536E,
    0x99C7: 0x5918,
    0x99C8: 0x537B,
    0x99C9: 0x5377,
    0x99CA: 0x5382,
    0x99CB: 0x5396,
    0x99CC: 0x53A0,
    0x99CD: 0x53A6,
    0x99CE: 0x53A5,
    0x99CF: 0x53AE,
    0x99D0: 0x53B0,
    0x99D1: 0x53B6,
    0x99D2: 0x53C3,
    0x99D3: 0x7C12,
    0x99D4: 0x96D9,
    0x99D5: 0x53DF,
    0x99D6: 0x66FC,
    0x99D7: 0x71EE,
    0x99D8: 0x53EE,
    0x99D9: 0x53E8,
    0x99DA: 0x53ED,
    0x99DB: 0x53FA,
    0x99DC: 0x5401,
    0x99DD: 0x543D,
    0x99DE: 0x5440,
    0x99DF: 0x542C,
    0x99E0: 0x542D,
    0x99E1: 0x543C,
    0x99E2: 0x542E,
    0x99E3: 0x5436,
    0x99E4: 0x5429,
    0x99E5: 0x541D,
    0x99E6: 0x544E,
    0x99E7: 0x548F,
    0x99E8: 0x5475,
    0x99E9: 0x548E,
    0x99EA: 0x545F,
    0x99EB: 0x5471,
    0x99EC: 0x5477,
    0x99ED: 0x5470,
    0x99EE: 0x5492,
    0x99EF: 0x547B,
    0x99F0: 0x5480,
    0x99F1: 0x5476,
    0x99F2: 0x5484,
    0x99F3: 0x5490,
    0x99F4: 0x5486,
    0x99F5: 0x54C7,
    0x99F6: 0x54A2,
    0x99F7: 0x54B8,
    0x99F8: 0x54A5,
    0x99F9: 0x54AC,
    0x99FA: 0x54C4,
    0x99FB: 0x54C8,
    0x99FC: 0x54A8,
    0x9A40: 0x54AB,
    0x9A41: 0x54C2,
    0x9A42: 0x54A4,
    0x9A43: 0x54BE,
    0x9A44: 0x54BC,
    0x9A45: 0x54D8,
    0x9A46: 0x54E5,
    0x9A47: 0x54E6,
    0x9A48: 0x550F,
    0x9A49: 0x5514,
    0x9A4A: 0x54FD,
    0x9A4B: 0x54EE,
    0x9A4C: 0x54ED,
    0x9A4D: 0x54FA,
    0x9A4E: 0x54E2,
    0x9A4F: 0x5539,
    0x9A50: 0x5540,
    0x9A51: 0x5563,
    0x9A52: 0x554C,
    0x9A53: 0x552E,
    0x9A54: 0x555C,
    0x9A55: 0x5545,
    0x9A56: 0x5556,
    0x9A57: 0x5557,
    0x9A58: 0x5538,
    0x9A59: 0x5533,
    0x9A5A: 0x555D,
    0x9A5B: 0x5599,
    0x9A5C: 0x5580,
    0x9A5D: 0x54AF,
    0x9A5E: 0x558A,
    0x9A5F: 0x559F,
    0x9A60: 0x557B,
    0x9A61: 0x557E,
    0x9A62: 0x5598,
    0x9A63: 0x559E,
    0x9A64: 0x55AE,
    0x9A65: 0x557C,
    0x9A66: 0x5583,
    0x9A67: 0x55A9,
    0x9A68: 0x5587,
    0x9A69: 0x55A8,
    0x9A6A: 0x55DA,
    0x9A6B: 0x55C5,
    0x9A6C: 0x55DF,
    0x9A6D: 0x55C4,
    0x9A6E: 0x55DC,
    0x9A6F: 0x55E4,
    0x9A70: 0x55D4,
    0x9A71: 0x5614,
    0x9A72: 0x55F7,
    0x9A73: 0x5616,
    0x9A74: 0x55FE,
    0x9A75: 0x55FD,
    0x9A76: 0x561B,
    0x9A77: 0x55F9,
    0x9A78: 0x564E,
    0x9A79: 0x5650,
    0x9A7A: 0x71DF,
    0x9A7B: 0x5634,
    0x9A7C: 0x5636,
    0x9A7D: 0x5632,
    0x9A7E: 0x5638,
    0x9A80: 0x566B,
    0x9A81: 0x5664,
    0x9A82: 0x562F,
    0x9A83: 0x566C,
    0x9A84: 0x566A,
    0x9A85: 0x5686,
    0x9A86: 0x5680,
    0x9A87: 0x568A,
    0x9A88: 0x56A0,
    0x9A89: 0x5694,
    0x9A8A: 0x568F,
    0x9A8B: 0x56A5,
    0x9A8C: 0x56AE,
    0x9A8D: 0x56B6,
    0x9A8E: 0x56B4,
    0x9A8F: 0x56C2,
    0x9A90: 0x56BC,
    0x9A91: 0x56C1,
    0x9A92: 0x56C3,
    0x9A93: 0x56C0,
    0x9A94: 0x56C8,
    0x9A95: 0x56CE,
    0x9A96: 0x56D1,
    0x9A97: 0x56D3,
    0x9A98: 0x56D7,
    0x9A99: 0x56EE,
    0x9A9A: 0x56F9,
    0x9A9B: 0x5700,
    0x9A9C: 0x56FF,
    0x9A9D: 0x5704,
    0x9A9E: 0x5709,
    0x9A9F: 0x5708,
    0x9AA0: 0x570B,
    0x9AA1: 0x570D,
    0x9AA2: 0x5713,
    0x9AA3: 0x5718,
    0x9AA4: 0x5716,
    0x9AA5: 0x55C7,
    0x9AA6: 0x571C,
    0x9AA7: 0x5726,
    0x9AA8: 0x5737,
    0x9AA9: 0x5738,
    0x9AAA: 0x574E,
    0x9AAB: 0x573B,
    0x9AAC: 0x5740,
    0x9AAD: 0x574F,
    0x9AAE: 0x5769,
    0x9AAF: 0x57C0,
    0x9AB0: 0x5788,
    0x9AB1: 0x5761,
    0x9AB2: 0x577F,
    0x9AB3: 0x5789,
    0x9AB4: 0x5793,
    0x9AB5: 0x57A0,
    0x9AB6: 0x57B3,
    0x9AB7: 0x57A4,
    0x9AB8: 0x57AA,
    0x9AB9: 0x57B0,
    0x9ABA: 0x57C3,
    0x9ABB: 0x57C6,
    0x9ABC: 0x57D4,
    0x9ABD: 0x57D2,
    0x9ABE: 0x57D3,
    0x9ABF: 0x580A,
    0x9AC0: 0x57D6,
    0x9AC1: 0x57E3,
    0x9AC2: 0x580B,
    0x9AC3: 0x5819,
    0x9AC4: 0x581D,
    0x9AC5: 0x5872,
    0x9AC6: 0x5821,
    0x9AC7: 0x5862,
    0x9AC8: 0x584B,
    0x9AC9: 0x5870,
    0x9ACA: 0x6BC0,
    0x9ACB: 0x5852,
    0x9ACC: 0x583D,
    0x9ACD: 0x5879,
    0x9ACE: 0x5885,
    0x9ACF: 0x58B9,
    0x9AD0: 0x589F,
    0x9AD1: 0x58AB,
    0x9AD2: 0x58BA,
    0x9AD3: 0x58DE,
    0x9AD4: 0x58BB,
    0x9AD5: 0x58B8,
    0x9AD6: 0x58AE,
    0x9AD7: 0x58C5,
    0x9AD8: 0x58D3,
    0x9AD9: 0x58D1,
    0x9ADA: 0x58D7,
    0x9ADB: 0x58D9,
    0x9ADC: 0x58D8,
    0x9ADD: 0x58E5,
    0x9ADE: 0x58DC,
    0x9ADF: 0x58E4,
    0x9AE0: 0x58DF,
    0x9AE1: 0x58EF,
    0x9AE2: 0x58FA,
    0x9AE3: 0x58F9,
    0x9AE4: 0x58FB,
    0x9AE5: 0x58FC,
    0x9AE6: 0x58FD,
    0x9AE7: 0x5902,
    0x9AE8: 0x590A,
    0x9AE9: 0x5910,
    0x9AEA: 0x591B,
    0x9AEB: 0x68A6,
    0x9AEC: 0x5925,
    0x9AED: 0x592C,
    0x9AEE: 0x592D,
    0x9AEF: 0x5932,
    0x9AF0: 0x5938,
    0x9AF1: 0x593E,
    0x9AF2: 0x7AD2,
    0x9AF3: 0x5955,
    0x9AF4: 0x5950,
    0x9AF5: 0x594E,
    0x9AF6: 0x595A,
    0x9AF7: 0x5958,
    0x9AF8: 0x5962,
    0x9AF9: 0x5960,
    0x9AFA: 0x5967,
    0x9AFB: 0x596C,
    0x9AFC: 0x5969,
    0x9B40: 0x5978,
    0x9B41: 0x5981,
    0x9B42: 0x599D,
    0x9B43: 0x4F5E,
    0x9B44: 0x4FAB,
    0x9B45: 0x59A3,
    0x9B46: 0x59B2,
    0x9B47: 0x59C6,
    0x9B48: 0x59E8,
    0x9B49: 0x59DC,
    0x9B4A: 0x598D,
    0x9B4B: 0x59D9,
    0x9B4C: 0x59DA,
    0x9B4D: 0x5A25,
    0x9B4E: 0x5A1F,
    0x9B4F: 0x5A11,
    0x9B50: 0x5A1C,
    0x9B51: 0x5A09,
    0x9B52: 0x5A1A,
    0x9B53: 0x5A40,
    0x9B54: 0x5A6C,
    0x9B55: 0x5A49,
    0x9B56: 0x5A35,
    0x9B57: 0x5A36,
    0x9B58: 0x5A62,
    0x9B59: 0x5A6A,
    0x9B5A: 0x5A9A,
    0x9B5B: 0x5ABC,
    0x9B5C: 0x5ABE,
    0x9B5D: 0x5ACB,
    0x9B5E: 0x5AC2,
    0x9B5F: 0x5ABD,
    0x9B60: 0x5AE3,
    0x9B61: 0x5AD7,
    0x9B62: 0x5AE6,
    0x9B63: 0x5AE9,
    0x9B64: 0x5AD6,
    0x9B65: 0x5AFA,
    0x9B66: 0x5AFB,
    0x9B67: 0x5B0C,
    0x9B68: 0x5B0B,
    0x9B69: 0x5B16,
    0x9B6A: 0x5B32,
    0x9B6B: 0x5AD0,
    0x9B6C: 0x5B2A,
    0x9B6D: 0x5B36,
    0x9B6E: 0x5B3E,
    0x9B6F: 0x5B43,
    0x9B70: 0x5B45,
    0x9B71: 0x5B40,
    0x9B72: 0x5B51,
    0x9B73: 0x5B55,
    0x9B74: 0x5B5A,
    0x9B75: 0x5B5B,
    0x9B76: 0x5B65,
    0x9B77: 0x5B69,
    0x9B78: 0x5B70,
    0x9B79: 0x5B73,
    0x9B7A: 0x5B75,
    0x9B7B: 0x5B78,
    0x9B7C: 0x6588,
    0x9B7D: 0x5B7A,
    0x9B7E: 0x5B80,
    0x9B80: 0x5B83,
    0x9B81: 0x5BA6,
    0x9B82: 0x5BB8,
    0x9B83: 0x5BC3,
    0x9B84: 0x5BC7,
    0x9B85: 0x5BC9,
    0x9B86: 0x5BD4,
    0x9B87: 0x5BD0,
    0x9B88: 0x5BE4,
    0x9B89: 0x5BE6,
    0x9B8A: 0x5BE2,
    0x9B8B: 0x5BDE,
    0x9B8C: 0x5BE5,
    0x9B8D: 0x5BEB,
    0x9B8E: 0x5BF0,
    0x9B8F: 0x5BF6,
    0x9B90: 0x5BF3,
    0x9B91: 0x5C05,
    0x9B92: 0x5C07,
    0x9B93: 0x5C08,
    0x9B94: 0x5C0D,
    0x9B95: 0x5C13,
    0x9B96: 0x5C20,
    0x9B97: 0x5C22,
    0x9B98: 0x5C28,
    0x9B99: 0x5C38,
    0x9B9A: 0x5C39,
    0x9B9B: 0x5C41,
    0x9B9C: 0x5C46,
    0x9B9D: 0x5C4E,
    0x9B9E: 0x5C53,
    0x9B9F: 0x5C50,
    0x9BA0: 0x5C4F,
    0x9BA1: 0x5B71,
    0x9BA2: 0x5C6C,
    0x9BA3: 0x5C6E,
    0x9BA4: 0x4E62,
    0x9BA5: 0x5C76,
    0x9BA6: 0x5C79,
    0x9BA7: 0x5C8C,
    0x9BA8: 0x5C91,
    0x9BA9: 0x5C94,
    0x9BAA: 0x599B,
    0x9BAB: 0x5CAB,
    0x9BAC: 0x5CBB,
    0x9BAD: 0x5CB6,
    0x9BAE: 0x5CBC,
    0x9BAF: 0x5CB7,
    0x9BB0: 0x5CC5,
    0x9BB1: 0x5CBE,
    0x9BB2: 0x5CC7,
    0x9BB3: 0x5CD9,
    0x9BB4: 0x5CE9,
    0x9BB5: 0x5CFD,
    0x9BB6: 0x5CFA,
    0x9BB7: 0x5CED,
    0x9BB8: 0x5D8C,
    0x9BB9: 0x5CEA,
    0x9BBA: 0x5D0B,
    0x9BBB: 0x5D15,
    0x9BBC: 0x5D17,
    0x9BBD: 0x5D5C,
    0x9BBE: 0x5D1F,
    0x9BBF: 0x5D1B,
    0x9BC0: 0x5D11,
    0x9BC1: 0x5D14,
    0x9BC2: 0x5D22,
    0x9BC3: 0x5D1A,
    0x9BC4: 0x5D19,
    0x9BC5: 0x5D18,
    0x9BC6: 0x5D4C,
    0x9BC7: 0x5D52,
    0x9BC8: 0x5D4E,
    0x9BC9: 0x5D4B,
    0x9BCA: 0x5D6C,
    0x9BCB: 0x5D73,
    0x9BCC: 0x5D76,
    0x9BCD: 0x5D87,
    0x9BCE: 0x5D84,
    0x9BCF: 0x5D82,
    0x9BD0: 0x5DA2,
    0x9BD1: 0x5D9D,
    0x9BD2: 0x5DAC,
    0x9BD3: 0x5DAE,
    0x9BD4: 0x5DBD,
    0x9BD5: 0x5D90,
    0x9BD6: 0x5DB7,
    0x9BD7: 0x5DBC,
    0x9BD8: 0x5DC9,
    0x9BD9: 0x5DCD,
    0x9BDA: 0x5DD3,
    0x9BDB: 0x5DD2,
    0x9BDC: 0x5DD6,
    0x9BDD: 0x5DDB,
    0x9BDE: 0x5DEB,
    0x9BDF: 0x5DF2,
    0x9BE0: 0x5DF5,
    0x9BE1: 0x5E0B,
    0x9BE2: 0x5E1A,
    0x9BE3: 0x5E19,
    0x9BE4: 0x5E11,
    0x9BE5: 0x5E1B,
    0x9BE6: 0x5E36,
    0x9BE7: 0x5E37,
    0x9BE8: 0x5E44,
    0x9BE9: 0x5E43,
    0x9BEA: 0x5E40,
    0x9BEB: 0x5E4E,
    0x9BEC: 0x5E57,
    0x9BED: 0x5E54,
    0x9BEE: 0x5E5F,
    0x9BEF: 0x5E62,
    0x9BF0: 0x5E64,
    0x9BF1: 0x5E47,
    0x9BF2: 0x5E75,
    0x9BF3: 0x5E76,
    0x9BF4: 0x5E7A,
    0x9BF5: 0x9EBC,
    0x9BF6: 0x5E7F,
    0x9BF7: 0x5EA0,
    0x9BF8: 0x5EC1,
    0x9BF9: 0x5EC2,
    0x9BFA: 0x5EC8,
    0x9BFB: 0x5ED0,
    0x9BFC: 0x5ECF,
    0x9C40: 0x5ED6,
    0x9C41: 0x5EE3,
    0x9C42: 0x5EDD,
    0x9C43: 0x5EDA,
    0x9C44: 0x5EDB,
    0x9C45: 0x5EE2,
    0x9C46: 0x5EE1,
    0x9C47: 0x5EE8,
    0x9C48: 0x5EE9,
    0x9C49: 0x5EEC,
    0x9C4A: 0x5EF1,
    0x9C4B: 0x5EF3,
    0x9C4C: 0x5EF0,
    0x9C4D: 0x5EF4,
    0x9C4E: 0x5EF8,
    0x9C4F: 0x5EFE,
    0x9C50: 0x5F03,
    0x9C51: 0x5F09,
    0x9C52: 0x5F5D,
    0x9C53: 0x5F5C,
    0x9C54: 0x5F0B,
    0x9C55: 0x5F11,
    0x9C56: 0x5F16,
    0x9C57: 0x5F29,
    0x9C58: 0x5F2D,
    0x9C59: 0x5F38,
    0x9C5A: 0x5F41,
    0x9C5B: 0x5F48,
    0x9C5C: 0x5F4C,
    0x9C5D: 0x5F4E,
    0x9C5E: 0x5F2F,
    0x9C5F: 0x5F51,
    0x9C60: 0x5F56,
    0x9C61: 0x5F57,
    0x9C62: 0x5F59,
    0x9C63: 0x5F61,
    0x9C64: 0x5F6D,
    0x9C65: 0x5F73,
    0x9C66: 0x5F77,
    0x9C67: 0x5F83,
    0x9C68: 0x5F82,
    0x9C69: 0x5F7F,
    0x9C6A: 0x5F8A,
    0x9C6B: 0x5F88,
    0x9C6C: 0x5F91,
    0x9C6D: 0x5F87,
    0x9C6E: 0x5F9E,
    0x9C6F: 0x5F99,
    0x9C70: 0x5F98,
    0x9C71: 0x5FA0,
    0x9C72: 0x5FA8,
    0x9C73: 0x5FAD,
    0x9C74: 0x5FBC,
    0x9C75: 0x5FD6,
    0x9C76: 0x5FFB,
    0x9C77: 0x5FE4,
    0x9C78: 0x5FF8,
    0x9C79: 0x5FF1,
    0x9C7A: 0x5FDD,
    0x9C7B: 0x60B3,
    0x9C7C: 0x5FFF,
    0x9C7D: 0x6021,
    0x9C7E: 0x6060,
    0x9C80: 0x6019,
    0x9C81: 0x6010,
    0x9C82: 0x6029,
    0x9C83: 0x600E,
    0x9C84: 0x6031,
    0x9C85: 0x601B,
    0x9C86: 0x6015,
    0x9C87: 0x602B,
    0x9C88: 0x6026,
    0x9C89: 0x600F,
    0x9C8A: 0x603A,
    0x9C8B: 0x605A,
    0x9C8C: 0x6041,
    0x9C8D: 0x606A,
    0x9C8E: 0x6077,
    0x9C8F: 0x605F,
    0x9C90: 0x604A,
    0x9C91: 0x6046,
    0x9C92: 0x604D,
    0x9C93: 0x6063,
    0x9C94: 0x6043,
    0x9C95: 0x6064,
    0x9C96: 0x6042,
    0x9C97: 0x606C,
    0x9C98: 0x606B,
    0x9C99: 0x6059,
    0x9C9A: 0x6081,
    0x9C9B: 0x608D,
    0x9C9C: 0x60E7,
    0x9C9D: 0x6083,
    0x9C9E: 0x609A,
    0x9C9F: 0x6084,
    0x9CA0: 0x609B,
    0x9CA1: 0x6096,
    0x9CA2: 0x6097,
    0x9CA3: 0x6092,
    0x9CA4: 0x60A7,
    0x9CA5: 0x608B,
    0x9CA6: 0x60E1,
    0x9CA7: 0x60B8,
    0x9CA8: 0x60E0,
    0x9CA9: 0x60D3,
    0x9CAA: 0x60B4,
    0x9CAB: 0x5FF0,
    0x9CAC: 0x60BD,
    0x9CAD: 0x60C6,
    0x9CAE: 0x60B5,
    0x9CAF: 0x60D8,
    0x9CB0: 0x614D,
    0x9CB1: 0x6115,
    0x9CB2: 0x6106,
    0x9CB3: 0x60F6,
    0x9CB4: 0x60F7,
    0x9CB5: 0x6100,
    0x9CB6: 0x60F4,
    0x9CB7: 0x60FA,
    0x9CB8: 0x6103,
    0x9CB9: 0x6121,
    0x9CBA: 0x60FB,
    0x9CBB: 0x60F1,
    0x9CBC: 0x610D,
    0x9CBD: 0x610E,
    0x9CBE: 0x6147,
    0x9CBF: 0x613E,
    0x9CC0: 0x6128,
    0x9CC1: 0x6127,
    0x9CC2: 0x614A,
    0x9CC3: 0x613F,
    0x9CC4: 0x613C,
    0x9CC5: 0x612C,
    0x9CC6: 0x6134,
    0x9CC7: 0x613D,
    0x9CC8: 0x6142,
    0x9CC9: 0x6144,
    0x9CCA: 0x6173,
    0x9CCB: 0x6177,
    0x9CCC: 0x6158,
    0x9CCD: 0x6159,
    0x9CCE: 0x615A,
    0x9CCF: 0x616B,
    0x9CD0: 0x6174,
    0x9CD1: 0x616F,
    0x9CD2: 0x6165,
    0x9CD3: 0x6171,
    0x9CD4: 0x615F,
    0x9CD5: 0x615D,
    0x9CD6: 0x6153,
    0x9CD7: 0x6175,
    0x9CD8: 0x6199,
    0x9CD9: 0x6196,
    0x9CDA: 0x6187,
    0x9CDB: 0x61AC,
    0x9CDC: 0x6194,
    0x9CDD: 0x619A,
    0x9CDE: 0x618A,
    0x9CDF: 0x6191,
    0x9CE0: 0x61AB,
    0x9CE1: 0x61AE,
    0x9CE2: 0x61CC,
    0x9CE3: 0x61CA,
    0x9CE4: 0x61C9,
    0x9CE5: 0x61F7,
    0x9CE6: 0x61C8,
    0x9CE7: 0x61C3,
    0x9CE8: 0x61C6,
    0x9CE9: 0x61BA,
    0x9CEA: 0x61CB,
    0x9CEB: 0x7F79,
    0x9CEC: 0x61CD,
    0x9CED: 0x61E6,
    0x9CEE: 0x61E3,
    0x9CEF: 0x61F6,
    0x9CF0: 0x61FA,
    0x9CF1: 0x61F4,
    0x9CF2: 0x61FF,
    0x9CF3: 0x61FD,
    0x9CF4: 0x61FC,
    0x9CF5: 0x61FE,
    0x9CF6: 0x6200,
    0x9CF7: 0x6208,
    0x9CF8: 0x6209,
    0x9CF9: 0x620D,
    0x9CFA: 0x620C,
    0x9CFB: 0x6214,
    0x9CFC: 0x621B,
    0x9D40: 0x621E,
    0x9D41: 0x6221,
    0x9D42: 0x622A,
    0x9D43: 0x622E,
    0x9D44: 0x6230,
    0x9D45: 0x6232,
    0x9D46: 0x6233,
    0x9D47: 0x6241,
    0x9D48: 0x624E,
    0x9D49: 0x625E,
    0x9D4A: 0x6263,
    0x9D4B: 0x625B,
    0x9D4C: 0x6260,
    0x9D4D: 0x6268,
    0x9D4E: 0x627C,
    0x9D4F: 0x6282,
    0x9D50: 0x6289,
    0x9D51: 0x627E,
    0x9D52: 0x6292,
    0x9D53: 0x6293,
    0x9D54: 0x6296,
    0x9D55: 0x62D4,
    0x9D56: 0x6283,
    0x9D57: 0x6294,
    0x9D58: 0x62D7,
    0x9D59: 0x62D1,
    0x9D5A: 0x62BB,
    0x9D5B: 0x62CF,
    0x9D5C: 0x62FF,
    0x9D5D: 0x62C6,
    0x9D5E: 0x64D4,
    0x9D5F: 0x62C8,
    0x9D60: 0x62DC,
    0x9D61: 0x62CC,
    0x9D62: 0x62CA,
    0x9D63: 0x62C2,
    0x9D64: 0x62C7,
    0x9D65: 0x629B,
    0x9D66: 0x62C9,
    0x9D67: 0x630C,
    0x9D68: 0x62EE,
    0x9D69: 0x62F1,
    0x9D6A: 0x6327,
    0x9D6B: 0x6302,
    0x9D6C: 0x6308,
    0x9D6D: 0x62EF,
    0x9D6E: 0x62F5,
    0x9D6F: 0x6350,
    0x9D70: 0x633E,
    0x9D71: 0x634D,
    0x9D72: 0x641C,
    0x9D73: 0x634F,
    0x9D74: 0x6396,
    0x9D75: 0x638E,
    0x9D76: 0x6380,
    0x9D77: 0x63AB,
    0x9D78: 0x6376,
    0x9D79: 0x63A3,
    0x9D7A: 0x638F,
    0x9D7B: 0x6389,
    0x9D7C: 0x639F,
    0x9D7D: 0x63B5,
    0x9D7E: 0x636B,
    0x9D80: 0x6369,
    0x9D81: 0x63BE,
    0x9D82: 0x63E9,
    0x9D83: 0x63C0,
    0x9D84: 0x63C6,
    0x9D85: 0x63E3,
    0x9D86: 0x63C9,
    0x9D87: 0x63D2,
    0x9D88: 0x63F6,
    0x9D89: 0x63C4,
    0x9D8A: 0x6416,
    0x9D8B: 0x6434,
    0x9D8C: 0x6406,
    0x9D8D: 0x6413,
    0x9D8E: 0x6426,
    0x9D8F: 0x6436,
    0x9D90: 0x651D,
    0x9D91: 0x6417,
    0x9D92: 0x6428,
    0x9D93: 0x640F,
    0x9D94: 0x6467,
    0x9D95: 0x646F,
    0x9D96: 0x6476,
    0x9D97: 0x644E,
    0x9D98: 0x652A,
    0x9D99: 0x6495,
    0x9D9A: 0x6493,
    0x9D9B: 0x64A5,
    0x9D9C: 0x64A9,
    0x9D9D: 0x6488,
    0x9D9E: 0x64BC,
    0x9D9F: 0x64DA,
    0x9DA0: 0x64D2,
    0x9DA1: 0x64C5,
    0x9DA2: 0x64C7,
    0x9DA3: 0x64BB,
    0x9DA4: 0x64D8,
    0x9DA5: 0x64C2,
    0x9DA6: 0x64F1,
    0x9DA7: 0x64E7,
    0x9DA8: 0x8209,
    0x9DA9: 0x64E0,
    0x9DAA: 0x64E1,
    0x9DAB: 0x62AC,
    0x9DAC: 0x64E3,
    0x9DAD: 0x64EF,
    0x9DAE: 0x652C,
    0x9DAF: 0x64F6,
    0x9DB0: 0x64F4,
    0x9DB1: 0x64F2,
    0x9DB2: 0x64FA,
    0x9DB3: 0x6500,
    0x9DB4: 0x64FD,
    0x9DB5: 0x6518,
    0x9DB6: 0x651C,
    0x9DB7: 0x6505,
    0x9DB8: 0x6524,
    0x9DB9: 0x6523,
    0x9DBA: 0x652B,
    0x9DBB: 0x6534,
    0x9DBC: 0x6535,
    0x9DBD: 0x6537,
    0x9DBE: 0x6536,
    0x9DBF: 0x6538,
    0x9DC0: 0x754B,
    0x9DC1: 0x6548,
    0x9DC2: 0x6556,
    0x9DC3: 0x6555,
    0x9DC4: 0x654D,
    0x9DC5: 0x6558,
    0x9DC6: 0x655E,
    0x9DC7: 0x655D,
    0x9DC8: 0x6572,
    0x9DC9: 0x6578,
    0x9DCA: 0x6582,
    0x9DCB: 0x6583,
    0x9DCC: 0x8B8A,
    0x9DCD: 0x659B,
    0x9DCE: 0x659F,
    0x9DCF: 0x65AB,
    0x9DD0: 0x65B7,
    0x9DD1: 0x65C3,
    0x9DD2: 0x65C6,
    0x9DD3: 0x65C1,
    0x9DD4: 0x65C4,
    0x9DD5: 0x65CC,
    0x9DD6: 0x65D2,
    0x9DD7: 0x65DB,
    0x9DD8: 0x65D9,
    0x9DD9: 0x65E0,
    0x9DDA: 0x65E1,
    0x9DDB: 0x65F1,
    0x9DDC: 0x6772,
    0x9DDD: 0x660A,
    0x9DDE: 0x6603,
    0x9DDF: 0x65FB,
    0x9DE0: 0x6773,
    0x9DE1: 0x6635,
    0x9DE2: 0x6636,
    0x9DE3: 0x6634,
    0x9DE4: 0x661C,
    0x9DE5: 0x664F,
    0x9DE6: 0x6644,
    0x9DE7: 0x6649,
    0x9DE8: 0x6641,
    0x9DE9: 0x665E,
    0x9DEA: 0x665D,
    0x9DEB: 0x6664,
    0x9DEC: 0x6667,
    0x9DED: 0x6668,
    0x9DEE: 0x665F,
    0x9DEF: 0x6662,
    0x9DF0: 0x6670,
    0x9DF1: 0x6683,
    0x9DF2: 0x6688,
    0x9DF3: 0x668E,
    0x9DF4: 0x6689,
    0x9DF5: 0x6684,
    0x9DF6: 0x6698,
    0x9DF7: 0x669D,
    0x9DF8: 0x66C1,
    0x9DF9: 0x66B9,
    0x9DFA: 0x66C9,
    0x9DFB: 0x66BE,
    0x9DFC: 0x66BC,
    0x9E40: 0x66C4,
    0x9E41: 0x66B8,
    0x9E42: 0x66D6,
    0x9E43: 0x66DA,
    0x9E44: 0x66E0,
    0x9E45: 0x663F,
    0x9E46: 0x66E6,
    0x9E47: 0x66E9,
    0x9E48: 0x66F0,
    0x9E49: 0x66F5,
    0x9E4A: 0x66F7,
    0x9E4B: 0x670F,
    0x9E4C: 0x6716,
    0x9E4D: 0x671E,
    0x9E4E: 0x6726,
    0x9E4F: 0x6727,
    0x9E50: 0x9738,
    0x9E51: 0x672E,
    0x9E52: 0x673F,
    0x9E53: 0x6736,
    0x9E54: 0x6741,
    0x9E55: 0x6738,
    0x9E56: 0x6737,
    0x9E57: 0x6746,
    0x9E58: 0x675E,
    0x9E59: 0x6760,
    0x9E5A: 0x6759,
    0x9E5B: 0x6763,
    0x9E5C: 0x6764,
    0x9E5D: 0x6789,
    0x9E5E: 0x6770,
    0x9E5F: 0x67A9,
    0x9E60: 0x677C,
    0x9E61: 0x676A,
    0x9E62: 0x678C,
    0x9E63: 0x678B,
    0x9E64: 0x67A6,
    0x9E65: 0x67A1,
    0x9E66: 0x6785,
    0x9E67: 0x67B7,
    0x9E68: 0x67EF,
    0x9E69: 0x67B4,
    0x9E6A: 0x67EC,
    0x9E6B: 0x67B3,
    0x9E6C: 0x67E9,
    0x9E6D: 0x67B8,
    0x9E6E: 0x67E4,
    0x9E6F: 0x67DE,
    0x9E70: 0x67DD,
    0x9E71: 0x67E2,
    0x9E72: 0x67EE,
    0x9E73: 0x67B9,
    0x9E74: 0x67CE,
    0x9E75: 0x67C6,
    0x9E76: 0x67E7,
    0x9E77: 0x6A9C,
    0x9E78: 0x681E,
    0x9E79: 0x6846,
    0x9E7A: 0x6829,
    0x9E7B: 0x6840,
    0x9E7C: 0x684D,
    0x9E7D: 0x6832,
    0x9E7E: 0x684E,
    0x9E80: 0x68B3,
    0x9E81: 0x682B,
    0x9E82: 0x6859,
    0x9E83: 0x6863,
    0x9E84: 0x6877,
    0x9E85: 0x687F,
    0x9E86: 0x689F,
    0x9E87: 0x688F,
    0x9E88: 0x68AD,
    0x9E89: 0x6894,
    0x9E8A: 0x689D,
    0x9E8B: 0x689B,
    0x9E8C: 0x6883,
    0x9E8D: 0x6AAE,
    0x9E8E: 0x68B9,
    0x9E8F: 0x6874,
    0x9E90: 0x68B5,
    0x9E91: 0x68A0,
    0x9E92: 0x68BA,
    0x9E93: 0x690F,
    0x9E94: 0x688D,
    0x9E95: 0x687E,
    0x9E96: 0x6901,
    0x9E97: 0x68CA,
    0x9E98: 0x6908,
    0x9E99: 0x68D8,
    0x9E9A: 0x6922,
    0x9E9B: 0x6926,
    0x9E9C: 0x68E1,
    0x9E9D: 0x690C,
    0x9E9E: 0x68CD,
    0x9E9F: 0x68D4,
    0x9EA0: 0x68E7,
    0x9EA1: 0x68D5,
    0x9EA2: 0x6936,
    0x9EA3: 0x6912,
    0x9EA4: 0x6904,
    0x9EA5: 0x68D7,
    0x9EA6: 0x68E3,
    0x9EA7: 0x6925,
    0x9EA8: 0x68F9,
    0x9EA9: 0x68E0,
    0x9EAA: 0x68EF,
    0x9EAB: 0x6928,
    0x9EAC: 0x692A,
    0x9EAD: 0x691A,
    0x9EAE: 0x6923,
    0x9EAF: 0x6921,
    0x9EB0: 0x68C6,
    0x9EB1: 0x6979,
    0x9EB2: 0x6977,
    0x9EB3: 0x695C,
    0x9EB4: 0x6978,
    0x9EB5: 0x696B,
    0x9EB6: 0x6954,
    0x9EB7: 0x697E,
    0x9EB8: 0x696E,
    0x9EB9: 0x6939,
    0x9EBA: 0x6974,
    0x9EBB: 0x693D,
    0x9EBC: 0x6959,
    0x9EBD: 0x6930,
    0x9EBE: 0x6961,
    0x9EBF: 0x695E,
    0x9EC0: 0x695D,
    0x9EC1: 0x6981,
    0x9EC2: 0x696A,
    0x9EC3: 0x69B2,
    0x9EC4: 0x69AE,
    0x9EC5: 0x69D0,
    0x9EC6: 0x69BF,
    0x9EC7: 0x69C1,
    0x9EC8: 0x69D3,
    0x9EC9: 0x69BE,
    0x9ECA: 0x69CE,
    0x9ECB: 0x5BE8,
    0x9ECC: 0x69CA,
    0x9ECD: 0x69DD,
    0x9ECE: 0x69BB,
    0x9ECF: 0x69C3,
    0x9ED0: 0x69A7,
    0x9ED1: 0x6A2E,
    0x9ED2: 0x6991,
    0x9ED3: 0x69A0,
    0x9ED4: 0x699C,
    0x9ED5: 0x6995,
    0x9ED6: 0x69B4,
    0x9ED7: 0x69DE,
    0x9ED8: 0x69E8,
    0x9ED9: 0x6A02,
    0x9EDA: 0x6A1B,
    0x9EDB: 0x69FF,
    0x9EDC: 0x6B0A,
    0x9EDD: 0x69F9,
    0x9EDE: 0x69F2,
    0x9EDF: 0x69E7,
    0x9EE0: 0x6A05,
    0x9EE1: 0x69B1,
    0x9EE2: 0x6A1E,
    0x9EE3: 0x69ED,
    0x9EE4: 0x6A14,
    0x9EE5: 0x69EB,
    0x9EE6: 0x6A0A,
    0x9EE7: 0x6A12,
    0x9EE8: 0x6AC1,
    0x9EE9: 0x6A23,
    0x9EEA: 0x6A13,
    0x9EEB: 0x6A44,
    0x9EEC: 0x6A0C,
    0x9EED: 0x6A72,
    0x9EEE: 0x6A36,
    0x9EEF: 0x6A78,
    0x9EF0: 0x6A47,
    0x9EF1: 0x6A62,
    0x9EF2: 0x6A59,
    0x9EF3: 0x6A66,
    0x9EF4: 0x6A48,
    0x9EF5: 0x6A38,
    0x9EF6: 0x6A22,
    0x9EF7: 0x6A90,
    0x9EF8: 0x6A8D,
    0x9EF9: 0x6AA0,
    0x9EFA: 0x6A84,
    0x9EFB: 0x6AA2,
    0x9EFC: 0x6AA3,
    0x9F40: 0x6A97,
    0x9F41: 0x8617,
    0x9F42: 0x6ABB,
    0x9F43: 0x6AC3,
    0x9F44: 0x6AC2,
    0x9F45: 0x6AB8,
    0x9F46: 0x6AB3,
    0x9F47: 0x6AAC,
    0x9F48: 0x6ADE,
    0x9F49: 0x6AD1,
    0x9F4A: 0x6ADF,
    0x9F4B: 0x6AAA,
    0x9F4C: 0x6ADA,
    0x9F4D: 0x6AEA,
    0x9F4E: 0x6AFB,
    0x9F4F: 0x6B05,
    0x9F50: 0x8616,
    0x9F51: 0x6AFA,
    0x9F52: 0x6B12,
    0x9F53: 0x6B16,
    0x9F54: 0x9B31,
    0x9F55: 0x6B1F,
    0x9F56: 0x6B38,
    0x9F57: 0x6B37,
    0x9F58: 0x76DC,
    0x9F59: 0x6B39,
    0x9F5A: 0x98EE,
    0x9F5B: 0x6B47,
    0x9F5C: 0x6B43,
    0x9F5D: 0x6B49,
    0x9F5E: 0x6B50,
    0x9F5F: 0x6B59,
    0x9F60: 0x6B54,
    0x9F61: 0x6B5B,
    0x9F62: 0x6B5F,
    0x9F63: 0x6B61,
    0x9F64: 0x6B78,
    0x9F65: 0x6B79,
    0x9F66: 0x6B7F,
    0x9F67: 0x6B80,
    0x9F68: 0x6B84,
    0x9F69: 0x6B83,
    0x9F6A: 0x6B8D,
    0x9F6B: 0x6B98,
    0x9F6C: 0x6B95,
    0x9F6D: 0x6B9E,
    0x9F6E: 0x6BA4,
    0x9F6F: 0x6BAA,
    0x9F70: 0x6BAB,
    0x9F71: 0x6BAF,
    0x9F72: 0x6BB2,
    0x9F73: 0x6BB1,
    0x9F74: 0x6BB3,
    0x9F75: 0x6BB7,
    0x9F76: 0x6BBC,
    0x9F77: 0x6BC6,
    0x9F78: 0x6BCB,
    0x9F79: 0x6BD3,
    0x9F7A: 0x6BDF,
    0x9F7B: 0x6BEC,
    0x9F7C: 0x6BEB,
    0x9F7D: 0x6BF3,
    0x9F7E: 0x6BEF,
    0x9F80: 0x9EBE,
    0x9F81: 0x6C08,
    0x9F82: 0x6C13,
    0x9F83: 0x6C14,
    0x9F84: 0x6C1B,
    0x9F85: 0x6C24,
    0x9F86: 0x6C23,
    0x9F87: 0x6C5E,
    0x9F88: 0x6C55,
    0x9F89: 0x6C62,
    0x9F8A: 0x6C6A,
    0x9F8B: 0x6C82,
    0x9F8C: 0x6C8D,
    0x9F8D: 0x6C9A,
    0x9F8E: 0x6C81,
    0x9F8F: 0x6C9B,
    0x9F90: 0x6C7E,
    0x9F91: 0x6C68,
    0x9F92: 0x6C73,
    0x9F93: 0x6C92,
    0x9F94: 0x6C90,
    0x9F95: 0x6CC4,
    0x9F96: 0x6CF1,
    0x9F97: 0x6CD3,
    0x9F98: 0x6CBD,
    0x9F99: 0x6CD7,
    0x9F9A: 0x6CC5,
    0x9F9B: 0x6CDD,
    0x9F9C: 0x6CAE,
    0x9F9D: 0x6CB1,
    0x9F9E: 0x6CBE,
    0x9F9F: 0x6CBA,
    0x9FA0: 0x6CDB,
    0x9FA1: 0x6CEF,
    0x9FA2: 0x6CD9,
    0x9FA3: 0x6CEA,
    0x9FA4: 0x6D1F,
    0x9FA5: 0x884D,
    0x9FA6: 0x6D36,
    0x9FA7: 0x6D2B,
    0x9FA8: 0x6D3D,
    0x9FA9: 0x6D38,
    0x9FAA: 0x6D19,
    0x9FAB: 0x6D35,
    0x9FAC: 0x6D33,
    0x9FAD: 0x6D12,
    0x9FAE: 0x6D0C,
    0x9FAF: 0x6D63,
    0x9FB0: 0x6D93,
    0x9FB1: 0x6D64,
    0x9FB2: 0x6D5A,
    0x9FB3: 0x6D79,
    0x9FB4: 0x6D59,
    0x9FB5: 0x6D8E,
    0x9FB6: 0x6D95,
    0x9FB7: 0x6FE4,
    0x9FB8: 0x6D85,
    0x9FB9: 0x6DF9,
    0x9FBA: 0x6E15,
    0x9FBB: 0x6E0A,
    0x9FBC: 0x6DB5,
    0x9FBD: 0x6DC7,
    0x9FBE: 0x6DE6,
    0x9FBF: 0x6DB8,
    0x9FC0: 0x6DC6,
    0x9FC1: 0x6DEC,
    0x9FC2: 0x6DDE,
    0x9FC3: 0x6DCC,
    0x9FC4: 0x6DE8,
    0x9FC5: 0x6DD2,
    0x9FC6: 0x6DC5,
    0x9FC7: 0x6DFA,
    0x9FC8: 0x6DD9,
    0x9FC9: 0x6DE4,
    0x9FCA: 0x6DD5,
    0x9FCB: 0x6DEA,
    0x9FCC: 0x6DEE,
    0x9FCD: 0x6E2D,
    0x9FCE: 0x6E6E,
    0x9FCF: 0x6E2E,
    0x9FD0: 0x6E19,
    0x9FD1: 0x6E72,
    0x9FD2: 0x6E5F,
    0x9FD3: 0x6E3E,
    0x9FD4: 0x6E23,
    0x9FD5: 0x6E6B,
    0x9FD6: 0x6E2B,
    0x9FD7: 0x6E76,
    0x9FD8: 0x6E4D,
    0x9FD9: 0x6E1F,
    0x9FDA: 0x6E43,
    0x9FDB: 0x6E3A,
    0x9FDC: 0x6E4E,
    0x9FDD: 0x6E24,
    0x9FDE: 0x6EFF,
    0x9FDF: 0x6E1D,
    0x9FE0: 0x6E38,
    0x9FE1: 0x6E82,
    0x9FE2: 0x6EAA,
    0x9FE3: 0x6E98,
    0x9FE4: 0x6EC9,
    0x9FE5: 0x6EB7,
    0x9FE6: 0x6ED3,
    0x9FE7: 0x6EBD,
    0x9FE8: 0x6EAF,
    0x9FE9: 0x6EC4,
    0x9FEA: 0x6EB2,
    0x9FEB: 0x6ED4,
    0x9FEC: 0x6ED5,
    0x9FED: 0x6E8F,
    0x9FEE: 0x6EA5,
    0x9FEF: 0x6EC2,
    0x9FF0: 0x6E9F,
    0x9FF1: 0x6F41,
    0x9FF2: 0x6F11,
    0x9FF3: 0x704C,
    0x9FF4: 0x6EEC,
    0x9FF5: 0x6EF8,
    0x9FF6: 0x6EFE,
    0x9FF7: 0x6F3F,
    0x9FF8: 0x6EF2,
    0x9FF9: 0x6F31,
    0x9FFA: 0x6EEF,
    0x9FFB: 0x6F32,
    0x9FFC: 0x6ECC,
    0xA1: 0xFF61,
    0xA2: 0xFF62,
    0xA3: 0xFF63,
    0xA4: 0xFF64,
    0xA5: 0xFF65,
    0xA6: 0xFF66,
    0xA7: 0xFF67,
    0xA8: 0xFF68,
    0xA9: 0xFF69,
    0xAA: 0xFF6A,
    0xAB: 0xFF6B,
    0xAC: 0xFF6C,
    0xAD: 0xFF6D,
    0xAE: 0xFF6E,
    0xAF: 0xFF6F,
    0xB0: 0xFF70,
    0xB1: 0xFF71,
    0xB2: 0xFF72,
    0xB3: 0xFF73,
    0xB4: 0xFF74,
    0xB5: 0xFF75,
    0xB6: 0xFF76,
    0xB7: 0xFF77,
    0xB8: 0xFF78,
    0xB9: 0xFF79,
    0xBA: 0xFF7A,
    0xBB: 0xFF7B,
    0xBC: 0xFF7C,
    0xBD: 0xFF7D,
    0xBE: 0xFF7E,
    0xBF: 0xFF7F,
    0xC0: 0xFF80,
    0xC1: 0xFF81,
    0xC2: 0xFF82,
    0xC3: 0xFF83,
    0xC4: 0xFF84,
    0xC5: 0xFF85,
    0xC6: 0xFF86,
    0xC7: 0xFF87,
    0xC8: 0xFF88,
    0xC9: 0xFF89,
    0xCA: 0xFF8A,
    0xCB: 0xFF8B,
    0xCC: 0xFF8C,
    0xCD: 0xFF8D,
    0xCE: 0xFF8E,
    0xCF: 0xFF8F,
    0xD0: 0xFF90,
    0xD1: 0xFF91,
    0xD2: 0xFF92,
    0xD3: 0xFF93,
    0xD4: 0xFF94,
    0xD5: 0xFF95,
    0xD6: 0xFF96,
    0xD7: 0xFF97,
    0xD8: 0xFF98,
    0xD9: 0xFF99,
    0xDA: 0xFF9A,
    0xDB: 0xFF9B,
    0xDC: 0xFF9C,
    0xDD: 0xFF9D,
    0xDE: 0xFF9E,
    0xDF: 0xFF9F,
    0xE040: 0x6F3E,
    0xE041: 0x6F13,
    0xE042: 0x6EF7,
    0xE043: 0x6F86,
    0xE044: 0x6F7A,
    0xE045: 0x6F78,
    0xE046: 0x6F81,
    0xE047: 0x6F80,
    0xE048: 0x6F6F,
    0xE049: 0x6F5B,
    0xE04A: 0x6FF3,
    0xE04B: 0x6F6D,
    0xE04C: 0x6F82,
    0xE04D: 0x6F7C,
    0xE04E: 0x6F58,
    0xE04F: 0x6F8E,
    0xE050: 0x6F91,
    0xE051: 0x6FC2,
    0xE052: 0x6F66,
    0xE053: 0x6FB3,
    0xE054: 0x6FA3,
    0xE055: 0x6FA1,
    0xE056: 0x6FA4,
    0xE057: 0x6FB9,
    0xE058: 0x6FC6,
    0xE059: 0x6FAA,
    0xE05A: 0x6FDF,
    0xE05B: 0x6FD5,
    0xE05C: 0x6FEC,
    0xE05D: 0x6FD4,
    0xE05E: 0x6FD8,
    0xE05F: 0x6FF1,
    0xE060: 0x6FEE,
    0xE061: 0x6FDB,
    0xE062: 0x7009,
    0xE063: 0x700B,
    0xE064: 0x6FFA,
    0xE065: 0x7011,
    0xE066: 0x7001,
    0xE067: 0x700F,
    0xE068: 0x6FFE,
    0xE069: 0x701B,
    0xE06A: 0x701A,
    0xE06B: 0x6F74,
    0xE06C: 0x701D,
    0xE06D: 0x7018,
    0xE06E: 0x701F,
    0xE06F: 0x7030,
    0xE070: 0x703E,
    0xE071: 0x7032,
    0xE072: 0x7051,
    0xE073: 0x7063,
    0xE074: 0x7099,
    0xE075: 0x7092,
    0xE076: 0x70AF,
    0xE077: 0x70F1,
    0xE078: 0x70AC,
    0xE079: 0x70B8,
    0xE07A: 0x70B3,
    0xE07B: 0x70AE,
    0xE07C: 0x70DF,
    0xE07D: 0x70CB,
    0xE07E: 0x70DD,
    0xE080: 0x70D9,
    0xE081: 0x7109,
    0xE082: 0x70FD,
    0xE083: 0x711C,
    0xE084: 0x7119,
    0xE085: 0x7165,
    0xE086: 0x7155,
    0xE087: 0x7188,
    0xE088: 0x7166,
    0xE089: 0x7162,
    0xE08A: 0x714C,
    0xE08B: 0x7156,
    0xE08C: 0x716C,
    0xE08D: 0x718F,
    0xE08E: 0x71FB,
    0xE08F: 0x7184,
    0xE090: 0x7195,
    0xE091: 0x71A8,
    0xE092: 0x71AC,
    0xE093: 0x71D7,
    0xE094: 0x71B9,
    0xE095: 0x71BE,
    0xE096: 0x71D2,
    0xE097: 0x71C9,
    0xE098: 0x71D4,
    0xE099: 0x71CE,
    0xE09A: 0x71E0,
    0xE09B: 0x71EC,
    0xE09C: 0x71E7,
    0xE09D: 0x71F5,
    0xE09E: 0x71FC,
    0xE09F: 0x71F9,
    0xE0A0: 0x71FF,
    0xE0A1: 0x720D,
    0xE0A2: 0x7210,
    0xE0A3: 0x721B,
    0xE0A4: 0x7228,
    0xE0A5: 0x722D,
    0xE0A6: 0x722C,
    0xE0A7: 0x7230,
    0xE0A8: 0x7232,
    0xE0A9: 0x723B,
    0xE0AA: 0x723C,
    0xE0AB: 0x723F,
    0xE0AC: 0x7240,
    0xE0AD: 0x7246,
    0xE0AE: 0x724B,
    0xE0AF: 0x7258,
    0xE0B0: 0x7274,
    0xE0B1: 0x727E,
    0xE0B2: 0x7282,
    0xE0B3: 0x7281,
    0xE0B4: 0x7287,
    0xE0B5: 0x7292,
    0xE0B6: 0x7296,
    0xE0B7: 0x72A2,
    0xE0B8: 0x72A7,
    0xE0B9: 0x72B9,
    0xE0BA: 0x72B2,
    0xE0BB: 0x72C3,
    0xE0BC: 0x72C6,
    0xE0BD: 0x72C4,
    0xE0BE: 0x72CE,
    0xE0BF: 0x72D2,
    0xE0C0: 0x72E2,
    0xE0C1: 0x72E0,
    0xE0C2: 0x72E1,
    0xE0C3: 0x72F9,
    0xE0C4: 0x72F7,
    0xE0C5: 0x500F,
    0xE0C6: 0x7317,
    0xE0C7: 0x730A,
    0xE0C8: 0x731C,
    0xE0C9: 0x7316,
    0xE0CA: 0x731D,
    0xE0CB: 0x7334,
    0xE0CC: 0x732F,
    0xE0CD: 0x7329,
    0xE0CE: 0x7325,
    0xE0CF: 0x733E,
    0xE0D0: 0x734E,
    0xE0D1: 0x734F,
    0xE0D2: 0x9ED8,
    0xE0D3: 0x7357,
    0xE0D4: 0x736A,
    0xE0D5: 0x7368,
    0xE0D6: 0x7370,
    0xE0D7: 0x7378,
    0xE0D8: 0x7375,
    0xE0D9: 0x737B,
    0xE0DA: 0x737A,
    0xE0DB: 0x73C8,
    0xE0DC: 0x73B3,
    0xE0DD: 0x73CE,
    0xE0DE: 0x73BB,
    0xE0DF: 0x73C0,
    0xE0E0: 0x73E5,
    0xE0E1: 0x73EE,
    0xE0E2: 0x73DE,
    0xE0E3: 0x74A2,
    0xE0E4: 0x7405,
    0xE0E5: 0x746F,
    0xE0E6: 0x7425,
    0xE0E7: 0x73F8,
    0xE0E8: 0x7432,
    0xE0E9: 0x743A,
    0xE0EA: 0x7455,
    0xE0EB: 0x743F,
    0xE0EC: 0x745F,
    0xE0ED: 0x7459,
    0xE0EE: 0x7441,
    0xE0EF: 0x745C,
    0xE0F0: 0x7469,
    0xE0F1: 0x7470,
    0xE0F2: 0x7463,
    0xE0F3: 0x746A,
    0xE0F4: 0x7476,
    0xE0F5: 0x747E,
    0xE0F6: 0x748B,
    0xE0F7: 0x749E,
    0xE0F8: 0x74A7,
    0xE0F9: 0x74CA,
    0xE0FA: 0x74CF,
    0xE0FB: 0x74D4,
    0xE0FC: 0x73F1,
    0xE140: 0x74E0,
    0xE141: 0x74E3,
    0xE142: 0x74E7,
    0xE143: 0x74E9,
    0xE144: 0x74EE,
    0xE145: 0x74F2,
    0xE146: 0x74F0,
    0xE147: 0x74F1,
    0xE148: 0x74F8,
    0xE149: 0x74F7,
    0xE14A: 0x7504,
    0xE14B: 0x7503,
    0xE14C: 0x7505,
    0xE14D: 0x750C,
    0xE14E: 0x750E,
    0xE14F: 0x750D,
    0xE150: 0x7515,
    0xE151: 0x7513,
    0xE152: 0x751E,
    0xE153: 0x7526,
    0xE154: 0x752C,
    0xE155: 0x753C,
    0xE156: 0x7544,
    0xE157: 0x754D,
    0xE158: 0x754A,
    0xE159: 0x7549,
    0xE15A: 0x755B,
    0xE15B: 0x7546,
    0xE15C: 0x755A,
    0xE15D: 0x7569,
    0xE15E: 0x7564,
    0xE15F: 0x7567,
    0xE160: 0x756B,
    0xE161: 0x756D,
    0xE162: 0x7578,
    0xE163: 0x7576,
    0xE164: 0x7586,
    0xE165: 0x7587,
    0xE166: 0x7574,
    0xE167: 0x758A,
    0xE168: 0x7589,
    0xE169: 0x7582,
    0xE16A: 0x7594,
    0xE16B: 0x759A,
    0xE16C: 0x759D,
    0xE16D: 0x75A5,
    0xE16E: 0x75A3,
    0xE16F: 0x75C2,
    0xE170: 0x75B3,
    0xE171: 0x75C3,
    0xE172: 0x75B5,
    0xE173: 0x75BD,
    0xE174: 0x75B8,
    0xE175: 0x75BC,
    0xE176: 0x75B1,
    0xE177: 0x75CD,
    0xE178: 0x75CA,
    0xE179: 0x75D2,
    0xE17A: 0x75D9,
    0xE17B: 0x75E3,
    0xE17C: 0x75DE,
    0xE17D: 0x75FE,
    0xE17E: 0x75FF,
    0xE180: 0x75FC,
    0xE181: 0x7601,
    0xE182: 0x75F0,
    0xE183: 0x75FA,
    0xE184: 0x75F2,
    0xE185: 0x75F3,
    0xE186: 0x760B,
    0xE187: 0x760D,
    0xE188: 0x7609,
    0xE189: 0x761F,
    0xE18A: 0x7627,
    0xE18B: 0x7620,
    0xE18C: 0x7621,
    0xE18D: 0x7622,
    0xE18E: 0x7624,
    0xE18F: 0x7634,
    0xE190: 0x7630,
    0xE191: 0x763B,
    0xE192: 0x7647,
    0xE193: 0x7648,
    0xE194: 0x7646,
    0xE195: 0x765C,
    0xE196: 0x7658,
    0xE197: 0x7661,
    0xE198: 0x7662,
    0xE199: 0x7668,
    0xE19A: 0x7669,
    0xE19B: 0x766A,
    0xE19C: 0x7667,
    0xE19D: 0x766C,
    0xE19E: 0x7670,
    0xE19F: 0x7672,
    0xE1A0: 0x7676,
    0xE1A1: 0x7678,
    0xE1A2: 0x767C,
    0xE1A3: 0x7680,
    0xE1A4: 0x7683,
    0xE1A5: 0x7688,
    0xE1A6: 0x768B,
    0xE1A7: 0x768E,
    0xE1A8: 0x7696,
    0xE1A9: 0x7693,
    0xE1AA: 0x7699,
    0xE1AB: 0x769A,
    0xE1AC: 0x76B0,
    0xE1AD: 0x76B4,
    0xE1AE: 0x76B8,
    0xE1AF: 0x76B9,
    0xE1B0: 0x76BA,
    0xE1B1: 0x76C2,
    0xE1B2: 0x76CD,
    0xE1B3: 0x76D6,
    0xE1B4: 0x76D2,
    0xE1B5: 0x76DE,
    0xE1B6: 0x76E1,
    0xE1B7: 0x76E5,
    0xE1B8: 0x76E7,
    0xE1B9: 0x76EA,
    0xE1BA: 0x862F,
    0xE1BB: 0x76FB,
    0xE1BC: 0x7708,
    0xE1BD: 0x7707,
    0xE1BE: 0x7704,
    0xE1BF: 0x7729,
    0xE1C0: 0x7724,
    0xE1C1: 0x771E,
    0xE1C2: 0x7725,
    0xE1C3: 0x7726,
    0xE1C4: 0x771B,
    0xE1C5: 0x7737,
    0xE1C6: 0x7738,
    0xE1C7: 0x7747,
    0xE1C8: 0x775A,
    0xE1C9: 0x7768,
    0xE1CA: 0x776B,
    0xE1CB: 0x775B,
    0xE1CC: 0x7765,
    0xE1CD: 0x777F,
    0xE1CE: 0x777E,
    0xE1CF: 0x7779,
    0xE1D0: 0x778E,
    0xE1D1: 0x778B,
    0xE1D2: 0x7791,
    0xE1D3: 0x77A0,
    0xE1D4: 0x779E,
    0xE1D5: 0x77B0,
    0xE1D6: 0x77B6,
    0xE1D7: 0x77B9,
    0xE1D8: 0x77BF,
    0xE1D9: 0x77BC,
    0xE1DA: 0x77BD,
    0xE1DB: 0x77BB,
    0xE1DC: 0x77C7,
    0xE1DD: 0x77CD,
    0xE1DE: 0x77D7,
    0xE1DF: 0x77DA,
    0xE1E0: 0x77DC,
    0xE1E1: 0x77E3,
    0xE1E2: 0x77EE,
    0xE1E3: 0x77FC,
    0xE1E4: 0x780C,
    0xE1E5: 0x7812,
    0xE1E6: 0x7926,
    0xE1E7: 0x7820,
    0xE1E8: 0x792A,
    0xE1E9: 0x7845,
    0xE1EA: 0x788E,
    0xE1EB: 0x7874,
    0xE1EC: 0x7886,
    0xE1ED: 0x787C,
    0xE1EE: 0x789A,
    0xE1EF: 0x788C,
    0xE1F0: 0x78A3,
    0xE1F1: 0x78B5,
    0xE1F2: 0x78AA,
    0xE1F3: 0x78AF,
    0xE1F4: 0x78D1,
    0xE1F5: 0x78C6,
    0xE1F6: 0x78CB,
    0xE1F7: 0x78D4,
    0xE1F8: 0x78BE,
    0xE1F9: 0x78BC,
    0xE1FA: 0x78C5,
    0xE1FB: 0x78CA,
    0xE1FC: 0x78EC,
    0xE240: 0x78E7,
    0xE241: 0x78DA,
    0xE242: 0x78FD,
    0xE243: 0x78F4,
    0xE244: 0x7907,
    0xE245: 0x7912,
    0xE246: 0x7911,
    0xE247: 0x7919,
    0xE248: 0x792C,
    0xE249: 0x792B,
    0xE24A: 0x7940,
    0xE24B: 0x7960,
    0xE24C: 0x7957,
    0xE24D: 0x795F,
    0xE24E: 0x795A,
    0xE24F: 0x7955,
    0xE250: 0x7953,
    0xE251: 0x797A,
    0xE252: 0x797F,
    0xE253: 0x798A,
    0xE254: 0x799D,
    0xE255: 0x79A7,
    0xE256: 0x9F4B,
    0xE257: 0x79AA,
    0xE258: 0x79AE,
    0xE259: 0x79B3,
    0xE25A: 0x79B9,
    0xE25B: 0x79BA,
    0xE25C: 0x79C9,
    0xE25D: 0x79D5,
    0xE25E: 0x79E7,
    0xE25F: 0x79EC,
    0xE260: 0x79E1,
    0xE261: 0x79E3,
    0xE262: 0x7A08,
    0xE263: 0x7A0D,
    0xE264: 0x7A18,
    0xE265: 0x7A19,
    0xE266: 0x7A20,
    0xE267: 0x7A1F,
    0xE268: 0x7980,
    0xE269: 0x7A31,
    0xE26A: 0x7A3B,
    0xE26B: 0x7A3E,
    0xE26C: 0x7A37,
    0xE26D: 0x7A43,
    0xE26E: 0x7A57,
    0xE26F: 0x7A49,
    0xE270: 0x7A61,
    0xE271: 0x7A62,
    0xE272: 0x7A69,
    0xE273: 0x9F9D,
    0xE274: 0x7A70,
    0xE275: 0x7A79,
    0xE276: 0x7A7D,
    0xE277: 0x7A88,
    0xE278: 0x7A97,
    0xE279: 0x7A95,
    0xE27A: 0x7A98,
    0xE27B: 0x7A96,
    0xE27C: 0x7AA9,
    0xE27D: 0x7AC8,
    0xE27E: 0x7AB0,
    0xE280: 0x7AB6,
    0xE281: 0x7AC5,
    0xE282: 0x7AC4,
    0xE283: 0x7ABF,
    0xE284: 0x9083,
    0xE285: 0x7AC7,
    0xE286: 0x7ACA,
    0xE287: 0x7ACD,
    0xE288: 0x7ACF,
    0xE289: 0x7AD5,
    0xE28A: 0x7AD3,
    0xE28B: 0x7AD9,
    0xE28C: 0x7ADA,
    0xE28D: 0x7ADD,
    0xE28E: 0x7AE1,
    0xE28F: 0x7AE2,
    0xE290: 0x7AE6,
    0xE291: 0x7AED,
    0xE292: 0x7AF0,
    0xE293: 0x7B02,
    0xE294: 0x7B0F,
    0xE295: 0x7B0A,
    0xE296: 0x7B06,
    0xE297: 0x7B33,
    0xE298: 0x7B18,
    0xE299: 0x7B19,
    0xE29A: 0x7B1E,
    0xE29B: 0x7B35,
    0xE29C: 0x7B28,
    0xE29D: 0x7B36,
    0xE29E: 0x7B50,
    0xE29F: 0x7B7A,
    0xE2A0: 0x7B04,
    0xE2A1: 0x7B4D,
    0xE2A2: 0x7B0B,
    0xE2A3: 0x7B4C,
    0xE2A4: 0x7B45,
    0xE2A5: 0x7B75,
    0xE2A6: 0x7B65,
    0xE2A7: 0x7B74,
    0xE2A8: 0x7B67,
    0xE2A9: 0x7B70,
    0xE2AA: 0x7B71,
    0xE2AB: 0x7B6C,
    0xE2AC: 0x7B6E,
    0xE2AD: 0x7B9D,
    0xE2AE: 0x7B98,
    0xE2AF: 0x7B9F,
    0xE2B0: 0x7B8D,
    0xE2B1: 0x7B9C,
    0xE2B2: 0x7B9A,
    0xE2B3: 0x7B8B,
    0xE2B4: 0x7B92,
    0xE2B5: 0x7B8F,
    0xE2B6: 0x7B5D,
    0xE2B7: 0x7B99,
    0xE2B8: 0x7BCB,
    0xE2B9: 0x7BC1,
    0xE2BA: 0x7BCC,
    0xE2BB: 0x7BCF,
    0xE2BC: 0x7BB4,
    0xE2BD: 0x7BC6,
    0xE2BE: 0x7BDD,
    0xE2BF: 0x7BE9,
    0xE2C0: 0x7C11,
    0xE2C1: 0x7C14,
    0xE2C2: 0x7BE6,
    0xE2C3: 0x7BE5,
    0xE2C4: 0x7C60,
    0xE2C5: 0x7C00,
    0xE2C6: 0x7C07,
    0xE2C7: 0x7C13,
    0xE2C8: 0x7BF3,
    0xE2C9: 0x7BF7,
    0xE2CA: 0x7C17,
    0xE2CB: 0x7C0D,
    0xE2CC: 0x7BF6,
    0xE2CD: 0x7C23,
    0xE2CE: 0x7C27,
    0xE2CF: 0x7C2A,
    0xE2D0: 0x7C1F,
    0xE2D1: 0x7C37,
    0xE2D2: 0x7C2B,
    0xE2D3: 0x7C3D,
    0xE2D4: 0x7C4C,
    0xE2D5: 0x7C43,
    0xE2D6: 0x7C54,
    0xE2D7: 0x7C4F,
    0xE2D8: 0x7C40,
    0xE2D9: 0x7C50,
    0xE2DA: 0x7C58,
    0xE2DB: 0x7C5F,
    0xE2DC: 0x7C64,
    0xE2DD: 0x7C56,
    0xE2DE: 0x7C65,
    0xE2DF: 0x7C6C,
    0xE2E0: 0x7C75,
    0xE2E1: 0x7C83,
    0xE2E2: 0x7C90,
    0xE2E3: 0x7CA4,
    0xE2E4: 0x7CAD,
    0xE2E5: 0x7CA2,
    0xE2E6: 0x7CAB,
    0xE2E7: 0x7CA1,
    0xE2E8: 0x7CA8,
    0xE2E9: 0x7CB3,
    0xE2EA: 0x7CB2,
    0xE2EB: 0x7CB1,
    0xE2EC: 0x7CAE,
    0xE2ED: 0x7CB9,
    0xE2EE: 0x7CBD,
    0xE2EF: 0x7CC0,
    0xE2F0: 0x7CC5,
    0xE2F1: 0x7CC2,
    0xE2F2: 0x7CD8,
    0xE2F3: 0x7CD2,
    0xE2F4: 0x7CDC,
    0xE2F5: 0x7CE2,
    0xE2F6: 0x9B3B,
    0xE2F7: 0x7CEF,
    0xE2F8: 0x7CF2,
    0xE2F9: 0x7CF4,
    0xE2FA: 0x7CF6,
    0xE2FB: 0x7CFA,
    0xE2FC: 0x7D06,
    0xE340: 0x7D02,
    0xE341: 0x7D1C,
    0xE342: 0x7D15,
    0xE343: 0x7D0A,
    0xE344: 0x7D45,
    0xE345: 0x7D4B,
    0xE346: 0x7D2E,
    0xE347: 0x7D32,
    0xE348: 0x7D3F,
    0xE349: 0x7D35,
    0xE34A: 0x7D46,
    0xE34B: 0x7D73,
    0xE34C: 0x7D56,
    0xE34D: 0x7D4E,
    0xE34E: 0x7D72,
    0xE34F: 0x7D68,
    0xE350: 0x7D6E,
    0xE351: 0x7D4F,
    0xE352: 0x7D63,
    0xE353: 0x7D93,
    0xE354: 0x7D89,
    0xE355: 0x7D5B,
    0xE356: 0x7D8F,
    0xE357: 0x7D7D,
    0xE358: 0x7D9B,
    0xE359: 0x7DBA,
    0xE35A: 0x7DAE,
    0xE35B: 0x7DA3,
    0xE35C: 0x7DB5,
    0xE35D: 0x7DC7,
    0xE35E: 0x7DBD,
    0xE35F: 0x7DAB,
    0xE360: 0x7E3D,
    0xE361: 0x7DA2,
    0xE362: 0x7DAF,
    0xE363: 0x7DDC,
    0xE364: 0x7DB8,
    0xE365: 0x7D9F,
    0xE366: 0x7DB0,
    0xE367: 0x7DD8,
    0xE368: 0x7DDD,
    0xE369: 0x7DE4,
    0xE36A: 0x7DDE,
    0xE36B: 0x7DFB,
    0xE36C: 0x7DF2,
    0xE36D: 0x7DE1,
    0xE36E: 0x7E05,
    0xE36F: 0x7E0A,
    0xE370: 0x7E23,
    0xE371: 0x7E21,
    0xE372: 0x7E12,
    0xE373: 0x7E31,
    0xE374: 0x7E1F,
    0xE375: 0x7E09,
    0xE376: 0x7E0B,
    0xE377: 0x7E22,
    0xE378: 0x7E46,
    0xE379: 0x7E66,
    0xE37A: 0x7E3B,
    0xE37B: 0x7E35,
    0xE37C: 0x7E39,
    0xE37D: 0x7E43,
    0xE37E: 0x7E37,
    0xE380: 0x7E32,
    0xE381: 0x7E3A,
    0xE382: 0x7E67,
    0xE383: 0x7E5D,
    0xE384: 0x7E56,
    0xE385: 0x7E5E,
    0xE386: 0x7E59,
    0xE387: 0x7E5A,
    0xE388: 0x7E79,
    0xE389: 0x7E6A,
    0xE38A: 0x7E69,
    0xE38B: 0x7E7C,
    0xE38C: 0x7E7B,
    0xE38D: 0x7E83,
    0xE38E: 0x7DD5,
    0xE38F: 0x7E7D,
    0xE390: 0x8FAE,
    0xE391: 0x7E7F,
    0xE392: 0x7E88,
    0xE393: 0x7E89,
    0xE394: 0x7E8C,
    0xE395: 0x7E92,
    0xE396: 0x7E90,
    0xE397: 0x7E93,
    0xE398: 0x7E94,
    0xE399: 0x7E96,
    0xE39A: 0x7E8E,
    0xE39B: 0x7E9B,
    0xE39C: 0x7E9C,
    0xE39D: 0x7F38,
    0xE39E: 0x7F3A,
    0xE39F: 0x7F45,
    0xE3A0: 0x7F4C,
    0xE3A1: 0x7F4D,
    0xE3A2: 0x7F4E,
    0xE3A3: 0x7F50,
    0xE3A4: 0x7F51,
    0xE3A5: 0x7F55,
    0xE3A6: 0x7F54,
    0xE3A7: 0x7F58,
    0xE3A8: 0x7F5F,
    0xE3A9: 0x7F60,
    0xE3AA: 0x7F68,
    0xE3AB: 0x7F69,
    0xE3AC: 0x7F67,
    0xE3AD: 0x7F78,
    0xE3AE: 0x7F82,
    0xE3AF: 0x7F86,
    0xE3B0: 0x7F83,
    0xE3B1: 0x7F88,
    0xE3B2: 0x7F87,
    0xE3B3: 0x7F8C,
    0xE3B4: 0x7F94,
    0xE3B5: 0x7F9E,
    0xE3B6: 0x7F9D,
    0xE3B7: 0x7F9A,
    0xE3B8: 0x7FA3,
    0xE3B9: 0x7FAF,
    0xE3BA: 0x7FB2,
    0xE3BB: 0x7FB9,
    0xE3BC: 0x7FAE,
    0xE3BD: 0x7FB6,
    0xE3BE: 0x7FB8,
    0xE3BF: 0x8B71,
    0xE3C0: 0x7FC5,
    0xE3C1: 0x7FC6,
    0xE3C2: 0x7FCA,
    0xE3C3: 0x7FD5,
    0xE3C4: 0x7FD4,
    0xE3C5: 0x7FE1,
    0xE3C6: 0x7FE6,
    0xE3C7: 0x7FE9,
    0xE3C8: 0x7FF3,
    0xE3C9: 0x7FF9,
    0xE3CA: 0x98DC,
    0xE3CB: 0x8006,
    0xE3CC: 0x8004,
    0xE3CD: 0x800B,
    0xE3CE: 0x8012,
    0xE3CF: 0x8018,
    0xE3D0: 0x8019,
    0xE3D1: 0x801C,
    0xE3D2: 0x8021,
    0xE3D3: 0x8028,
    0xE3D4: 0x803F,
    0xE3D5: 0x803B,
    0xE3D6: 0x804A,
    0xE3D7: 0x8046,
    0xE3D8: 0x8052,
    0xE3D9: 0x8058,
    0xE3DA: 0x805A,
    0xE3DB: 0x805F,
    0xE3DC: 0x8062,
    0xE3DD: 0x8068,
    0xE3DE: 0x8073,
    0xE3DF: 0x8072,
    0xE3E0: 0x8070,
    0xE3E1: 0x8076,
    0xE3E2: 0x8079,
    0xE3E3: 0x807D,
    0xE3E4: 0x807F,
    0xE3E5: 0x8084,
    0xE3E6: 0x8086,
    0xE3E7: 0x8085,
    0xE3E8: 0x809B,
    0xE3E9: 0x8093,
    0xE3EA: 0x809A,
    0xE3EB: 0x80AD,
    0xE3EC: 0x5190,
    0xE3ED: 0x80AC,
    0xE3EE: 0x80DB,
    0xE3EF: 0x80E5,
    0xE3F0: 0x80D9,
    0xE3F1: 0x80DD,
    0xE3F2: 0x80C4,
    0xE3F3: 0x80DA,
    0xE3F4: 0x80D6,
    0xE3F5: 0x8109,
    0xE3F6: 0x80EF,
    0xE3F7: 0x80F1,
    0xE3F8: 0x811B,
    0xE3F9: 0x8129,
    0xE3FA: 0x8123,
    0xE3FB: 0x812F,
    0xE3FC: 0x814B,
    0xE440: 0x968B,
    0xE441: 0x8146,
    0xE442: 0x813E,
    0xE443: 0x8153,
    0xE444: 0x8151,
    0xE445: 0x80FC,
    0xE446: 0x8171,
    0xE447: 0x816E,
    0xE448: 0x8165,
    0xE449: 0x8166,
    0xE44A: 0x8174,
    0xE44B: 0x8183,
    0xE44C: 0x8188,
    0xE44D: 0x818A,
    0xE44E: 0x8180,
    0xE44F: 0x8182,
    0xE450: 0x81A0,
    0xE451: 0x8195,
    0xE452: 0x81A4,
    0xE453: 0x81A3,
    0xE454: 0x815F,
    0xE455: 0x8193,
    0xE456: 0x81A9,
    0xE457: 0x81B0,
    0xE458: 0x81B5,
    0xE459: 0x81BE,
    0xE45A: 0x81B8,
    0xE45B: 0x81BD,
    0xE45C: 0x81C0,
    0xE45D: 0x81C2,
    0xE45E: 0x81BA,
    0xE45F: 0x81C9,
    0xE460: 0x81CD,
    0xE461: 0x81D1,
    0xE462: 0x81D9,
    0xE463: 0x81D8,
    0xE464: 0x81C8,
    0xE465: 0x81DA,
    0xE466: 0x81DF,
    0xE467: 0x81E0,
    0xE468: 0x81E7,
    0xE469: 0x81FA,
    0xE46A: 0x81FB,
    0xE46B: 0x81FE,
    0xE46C: 0x8201,
    0xE46D: 0x8202,
    0xE46E: 0x8205,
    0xE46F: 0x8207,
    0xE470: 0x820A,
    0xE471: 0x820D,
    0xE472: 0x8210,
    0xE473: 0x8216,
    0xE474: 0x8229,
    0xE475: 0x822B,
    0xE476: 0x8238,
    0xE477: 0x8233,
    0xE478: 0x8240,
    0xE479: 0x8259,
    0xE47A: 0x8258,
    0xE47B: 0x825D,
    0xE47C: 0x825A,
    0xE47D: 0x825F,
    0xE47E: 0x8264,
    0xE480: 0x8262,
    0xE481: 0x8268,
    0xE482: 0x826A,
    0xE483: 0x826B,
    0xE484: 0x822E,
    0xE485: 0x8271,
    0xE486: 0x8277,
    0xE487: 0x8278,
    0xE488: 0x827E,
    0xE489: 0x828D,
    0xE48A: 0x8292,
    0xE48B: 0x82AB,
    0xE48C: 0x829F,
    0xE48D: 0x82BB,
    0xE48E: 0x82AC,
    0xE48F: 0x82E1,
    0xE490: 0x82E3,
    0xE491: 0x82DF,
    0xE492: 0x82D2,
    0xE493: 0x82F4,
    0xE494: 0x82F3,
    0xE495: 0x82FA,
    0xE496: 0x8393,
    0xE497: 0x8303,
    0xE498: 0x82FB,
    0xE499: 0x82F9,
    0xE49A: 0x82DE,
    0xE49B: 0x8306,
    0xE49C: 0x82DC,
    0xE49D: 0x8309,
    0xE49E: 0x82D9,
    0xE49F: 0x8335,
    0xE4A0: 0x8334,
    0xE4A1: 0x8316,
    0xE4A2: 0x8332,
    0xE4A3: 0x8331,
    0xE4A4: 0x8340,
    0xE4A5: 0x8339,
    0xE4A6: 0x8350,
    0xE4A7: 0x8345,
    0xE4A8: 0x832F,
    0xE4A9: 0x832B,
    0xE4AA: 0x8317,
    0xE4AB: 0x8318,
    0xE4AC: 0x8385,
    0xE4AD: 0x839A,
    0xE4AE: 0x83AA,
    0xE4AF: 0x839F,
    0xE4B0: 0x83A2,
    0xE4B1: 0x8396,
    0xE4B2: 0x8323,
    0xE4B3: 0x838E,
    0xE4B4: 0x8387,
    0xE4B5: 0x838A,
    0xE4B6: 0x837C,
    0xE4B7: 0x83B5,
    0xE4B8: 0x8373,
    0xE4B9: 0x8375,
    0xE4BA: 0x83A0,
    0xE4BB: 0x8389,
    0xE4BC: 0x83A8,
    0xE4BD: 0x83F4,
    0xE4BE: 0x8413,
    0xE4BF: 0x83EB,
    0xE4C0: 0x83CE,
    0xE4C1: 0x83FD,
    0xE4C2: 0x8403,
    0xE4C3: 0x83D8,
    0xE4C4: 0x840B,
    0xE4C5: 0x83C1,
    0xE4C6: 0x83F7,
    0xE4C7: 0x8407,
    0xE4C8: 0x83E0,
    0xE4C9: 0x83F2,
    0xE4CA: 0x840D,
    0xE4CB: 0x8422,
    0xE4CC: 0x8420,
    0xE4CD: 0x83BD,
    0xE4CE: 0x8438,
    0xE4CF: 0x8506,
    0xE4D0: 0x83FB,
    0xE4D1: 0x846D,
    0xE4D2: 0x842A,
    0xE4D3: 0x843C,
    0xE4D4: 0x855A,
    0xE4D5: 0x8484,
    0xE4D6: 0x8477,
    0xE4D7: 0x846B,
    0xE4D8: 0x84AD,
    0xE4D9: 0x846E,
    0xE4DA: 0x8482,
    0xE4DB: 0x8469,
    0xE4DC: 0x8446,
    0xE4DD: 0x842C,
    0xE4DE: 0x846F,
    0xE4DF: 0x8479,
    0xE4E0: 0x8435,
    0xE4E1: 0x84CA,
    0xE4E2: 0x8462,
    0xE4E3: 0x84B9,
    0xE4E4: 0x84BF,
    0xE4E5: 0x849F,
    0xE4E6: 0x84D9,
    0xE4E7: 0x84CD,
    0xE4E8: 0x84BB,
    0xE4E9: 0x84DA,
    0xE4EA: 0x84D0,
    0xE4EB: 0x84C1,
    0xE4EC: 0x84C6,
    0xE4ED: 0x84D6,
    0xE4EE: 0x84A1,
    0xE4EF: 0x8521,
    0xE4F0: 0x84FF,
    0xE4F1: 0x84F4,
    0xE4F2: 0x8517,
    0xE4F3: 0x8518,
    0xE4F4: 0x852C,
    0xE4F5: 0x851F,
    0xE4F6: 0x8515,
    0xE4F7: 0x8514,
    0xE4F8: 0x84FC,
    0xE4F9: 0x8540,
    0xE4FA: 0x8563,
    0xE4FB: 0x8558,
    0xE4FC: 0x8548,
    0xE540: 0x8541,
    0xE541: 0x8602,
    0xE542: 0x854B,
    0xE543: 0x8555,
    0xE544: 0x8580,
    0xE545: 0x85A4,
    0xE546: 0x8588,
    0xE547: 0x8591,
    0xE548: 0x858A,
    0xE549: 0x85A8,
    0xE54A: 0x856D,
    0xE54B: 0x8594,
    0xE54C: 0x859B,
    0xE54D: 0x85EA,
    0xE54E: 0x8587,
    0xE54F: 0x859C,
    0xE550: 0x8577,
    0xE551: 0x857E,
    0xE552: 0x8590,
    0xE553: 0x85C9,
    0xE554: 0x85BA,
    0xE555: 0x85CF,
    0xE556: 0x85B9,
    0xE557: 0x85D0,
    0xE558: 0x85D5,
    0xE559: 0x85DD,
    0xE55A: 0x85E5,
    0xE55B: 0x85DC,
    0xE55C: 0x85F9,
    0xE55D: 0x860A,
    0xE55E: 0x8613,
    0xE55F: 0x860B,
    0xE560: 0x85FE,
    0xE561: 0x85FA,
    0xE562: 0x8606,
    0xE563: 0x8622,
    0xE564: 0x861A,
    0xE565: 0x8630,
    0xE566: 0x863F,
    0xE567: 0x864D,
    0xE568: 0x4E55,
    0xE569: 0x8654,
    0xE56A: 0x865F,
    0xE56B: 0x8667,
    0xE56C: 0x8671,
    0xE56D: 0x8693,
    0xE56E: 0x86A3,
    0xE56F: 0x86A9,
    0xE570: 0x86AA,
    0xE571: 0x868B,
    0xE572: 0x868C,
    0xE573: 0x86B6,
    0xE574: 0x86AF,
    0xE575: 0x86C4,
    0xE576: 0x86C6,
    0xE577: 0x86B0,
    0xE578: 0x86C9,
    0xE579: 0x8823,
    0xE57A: 0x86AB,
    0xE57B: 0x86D4,
    0xE57C: 0x86DE,
    0xE57D: 0x86E9,
    0xE57E: 0x86EC,
    0xE580: 0x86DF,
    0xE581: 0x86DB,
    0xE582: 0x86EF,
    0xE583: 0x8712,
    0xE584: 0x8706,
    0xE585: 0x8708,
    0xE586: 0x8700,
    0xE587: 0x8703,
    0xE588: 0x86FB,
    0xE589: 0x8711,
    0xE58A: 0x8709,
    0xE58B: 0x870D,
    0xE58C: 0x86F9,
    0xE58D: 0x870A,
    0xE58E: 0x8734,
    0xE58F: 0x873F,
    0xE590: 0x8737,
    0xE591: 0x873B,
    0xE592: 0x8725,
    0xE593: 0x8729,
    0xE594: 0x871A,
    0xE595: 0x8760,
    0xE596: 0x875F,
    0xE597: 0x8778,
    0xE598: 0x874C,
    0xE599: 0x874E,
    0xE59A: 0x8774,
    0xE59B: 0x8757,
    0xE59C: 0x8768,
    0xE59D: 0x876E,
    0xE59E: 0x8759,
    0xE59F: 0x8753,
    0xE5A0: 0x8763,
    0xE5A1: 0x876A,
    0xE5A2: 0x8805,
    0xE5A3: 0x87A2,
    0xE5A4: 0x879F,
    0xE5A5: 0x8782,
    0xE5A6: 0x87AF,
    0xE5A7: 0x87CB,
    0xE5A8: 0x87BD,
    0xE5A9: 0x87C0,
    0xE5AA: 0x87D0,
    0xE5AB: 0x96D6,
    0xE5AC: 0x87AB,
    0xE5AD: 0x87C4,
    0xE5AE: 0x87B3,
    0xE5AF: 0x87C7,
    0xE5B0: 0x87C6,
    0xE5B1: 0x87BB,
    0xE5B2: 0x87EF,
    0xE5B3: 0x87F2,
    0xE5B4: 0x87E0,
    0xE5B5: 0x880F,
    0xE5B6: 0x880D,
    0xE5B7: 0x87FE,
    0xE5B8: 0x87F6,
    0xE5B9: 0x87F7,
    0xE5BA: 0x880E,
    0xE5BB: 0x87D2,
    0xE5BC: 0x8811,
    0xE5BD: 0x8816,
    0xE5BE: 0x8815,
    0xE5BF: 0x8822,
    0xE5C0: 0x8821,
    0xE5C1: 0x8831,
    0xE5C2: 0x8836,
    0xE5C3: 0x8839,
    0xE5C4: 0x8827,
    0xE5C5: 0x883B,
    0xE5C6: 0x8844,
    0xE5C7: 0x8842,
    0xE5C8: 0x8852,
    0xE5C9: 0x8859,
    0xE5CA: 0x885E,
    0xE5CB: 0x8862,
    0xE5CC: 0x886B,
    0xE5CD: 0x8881,
    0xE5CE: 0x887E,
    0xE5CF: 0x889E,
    0xE5D0: 0x8875,
    0xE5D1: 0x887D,
    0xE5D2: 0x88B5,
    0xE5D3: 0x8872,
    0xE5D4: 0x8882,
    0xE5D5: 0x8897,
    0xE5D6: 0x8892,
    0xE5D7: 0x88AE,
    0xE5D8: 0x8899,
    0xE5D9: 0x88A2,
    0xE5DA: 0x888D,
    0xE5DB: 0x88A4,
    0xE5DC: 0x88B0,
    0xE5DD: 0x88BF,
    0xE5DE: 0x88B1,
    0xE5DF: 0x88C3,
    0xE5E0: 0x88C4,
    0xE5E1: 0x88D4,
    0xE5E2: 0x88D8,
    0xE5E3: 0x88D9,
    0xE5E4: 0x88DD,
    0xE5E5: 0x88F9,
    0xE5E6: 0x8902,
    0xE5E7: 0x88FC,
    0xE5E8: 0x88F4,
    0xE5E9: 0x88E8,
    0xE5EA: 0x88F2,
    0xE5EB: 0x8904,
    0xE5EC: 0x890C,
    0xE5ED: 0x890A,
    0xE5EE: 0x8913,
    0xE5EF: 0x8943,
    0xE5F0: 0x891E,
    0xE5F1: 0x8925,
    0xE5F2: 0x892A,
    0xE5F3: 0x892B,
    0xE5F4: 0x8941,
    0xE5F5: 0x8944,
    0xE5F6: 0x893B,
    0xE5F7: 0x8936,
    0xE5F8: 0x8938,
    0xE5F9: 0x894C,
    0xE5FA: 0x891D,
    0xE5FB: 0x8960,
    0xE5FC: 0x895E,
    0xE640: 0x8966,
    0xE641: 0x8964,
    0xE642: 0x896D,
    0xE643: 0x896A,
    0xE644: 0x896F,
    0xE645: 0x8974,
    0xE646: 0x8977,
    0xE647: 0x897E,
    0xE648: 0x8983,
    0xE649: 0x8988,
    0xE64A: 0x898A,
    0xE64B: 0x8993,
    0xE64C: 0x8998,
    0xE64D: 0x89A1,
    0xE64E: 0x89A9,
    0xE64F: 0x89A6,
    0xE650: 0x89AC,
    0xE651: 0x89AF,
    0xE652: 0x89B2,
    0xE653: 0x89BA,
    0xE654: 0x89BD,
    0xE655: 0x89BF,
    0xE656: 0x89C0,
    0xE657: 0x89DA,
    0xE658: 0x89DC,
    0xE659: 0x89DD,
    0xE65A: 0x89E7,
    0xE65B: 0x89F4,
    0xE65C: 0x89F8,
    0xE65D: 0x8A03,
    0xE65E: 0x8A16,
    0xE65F: 0x8A10,
    0xE660: 0x8A0C,
    0xE661: 0x8A1B,
    0xE662: 0x8A1D,
    0xE663: 0x8A25,
    0xE664: 0x8A36,
    0xE665: 0x8A41,
    0xE666: 0x8A5B,
    0xE667: 0x8A52,
    0xE668: 0x8A46,
    0xE669: 0x8A48,
    0xE66A: 0x8A7C,
    0xE66B: 0x8A6D,
    0xE66C: 0x8A6C,
    0xE66D: 0x8A62,
    0xE66E: 0x8A85,
    0xE66F: 0x8A82,
    0xE670: 0x8A84,
    0xE671: 0x8AA8,
    0xE672: 0x8AA1,
    0xE673: 0x8A91,
    0xE674: 0x8AA5,
    0xE675: 0x8AA6,
    0xE676: 0x8A9A,
    0xE677: 0x8AA3,
    0xE678: 0x8AC4,
    0xE679: 0x8ACD,
    0xE67A: 0x8AC2,
    0xE67B: 0x8ADA,
    0xE67C: 0x8AEB,
    0xE67D: 0x8AF3,
    0xE67E: 0x8AE7,
    0xE680: 0x8AE4,
    0xE681: 0x8AF1,
    0xE682: 0x8B14,
    0xE683: 0x8AE0,
    0xE684: 0x8AE2,
    0xE685: 0x8AF7,
    0xE686: 0x8ADE,
    0xE687: 0x8ADB,
    0xE688: 0x8B0C,
    0xE689: 0x8B07,
    0xE68A: 0x8B1A,
    0xE68B: 0x8AE1,
    0xE68C: 0x8B16,
    0xE68D: 0x8B10,
    0xE68E: 0x8B17,
    0xE68F: 0x8B20,
    0xE690: 0x8B33,
    0xE691: 0x97AB,
    0xE692: 0x8B26,
    0xE693: 0x8B2B,
    0xE694: 0x8B3E,
    0xE695: 0x8B28,
    0xE696: 0x8B41,
    0xE697: 0x8B4C,
    0xE698: 0x8B4F,
    0xE699: 0x8B4E,
    0xE69A: 0x8B49,
    0xE69B: 0x8B56,
    0xE69C: 0x8B5B,
    0xE69D: 0x8B5A,
    0xE69E: 0x8B6B,
    0xE69F: 0x8B5F,
    0xE6A0: 0x8B6C,
    0xE6A1: 0x8B6F,
    0xE6A2: 0x8B74,
    0xE6A3: 0x8B7D,
    0xE6A4: 0x8B80,
    0xE6A5: 0x8B8C,
    0xE6A6: 0x8B8E,
    0xE6A7: 0x8B92,
    0xE6A8: 0x8B93,
    0xE6A9: 0x8B96,
    0xE6AA: 0x8B99,
    0xE6AB: 0x8B9A,
    0xE6AC: 0x8C3A,
    0xE6AD: 0x8C41,
    0xE6AE: 0x8C3F,
    0xE6AF: 0x8C48,
    0xE6B0: 0x8C4C,
    0xE6B1: 0x8C4E,
    0xE6B2: 0x8C50,
    0xE6B3: 0x8C55,
    0xE6B4: 0x8C62,
    0xE6B5: 0x8C6C,
    0xE6B6: 0x8C78,
    0xE6B7: 0x8C7A,
    0xE6B8: 0x8C82,
    0xE6B9: 0x8C89,
    0xE6BA: 0x8C85,
    0xE6BB: 0x8C8A,
    0xE6BC: 0x8C8D,
    0xE6BD: 0x8C8E,
    0xE6BE: 0x8C94,
    0xE6BF: 0x8C7C,
    0xE6C0: 0x8C98,
    0xE6C1: 0x621D,
    0xE6C2: 0x8CAD,
    0xE6C3: 0x8CAA,
    0xE6C4: 0x8CBD,
    0xE6C5: 0x8CB2,
    0xE6C6: 0x8CB3,
    0xE6C7: 0x8CAE,
    0xE6C8: 0x8CB6,
    0xE6C9: 0x8CC8,
    0xE6CA: 0x8CC1,
    0xE6CB: 0x8CE4,
    0xE6CC: 0x8CE3,
    0xE6CD: 0x8CDA,
    0xE6CE: 0x8CFD,
    0xE6CF: 0x8CFA,
    0xE6D0: 0x8CFB,
    0xE6D1: 0x8D04,
    0xE6D2: 0x8D05,
    0xE6D3: 0x8D0A,
    0xE6D4: 0x8D07,
    0xE6D5: 0x8D0F,
    0xE6D6: 0x8D0D,
    0xE6D7: 0x8D10,
    0xE6D8: 0x9F4E,
    0xE6D9: 0x8D13,
    0xE6DA: 0x8CCD,
    0xE6DB: 0x8D14,
    0xE6DC: 0x8D16,
    0xE6DD: 0x8D67,
    0xE6DE: 0x8D6D,
    0xE6DF: 0x8D71,
    0xE6E0: 0x8D73,
    0xE6E1: 0x8D81,
    0xE6E2: 0x8D99,
    0xE6E3: 0x8DC2,
    0xE6E4: 0x8DBE,
    0xE6E5: 0x8DBA,
    0xE6E6: 0x8DCF,
    0xE6E7: 0x8DDA,
    0xE6E8: 0x8DD6,
    0xE6E9: 0x8DCC,
    0xE6EA: 0x8DDB,
    0xE6EB: 0x8DCB,
    0xE6EC: 0x8DEA,
    0xE6ED: 0x8DEB,
    0xE6EE: 0x8DDF,
    0xE6EF: 0x8DE3,
    0xE6F0: 0x8DFC,
    0xE6F1: 0x8E08,
    0xE6F2: 0x8E09,
    0xE6F3: 0x8DFF,
    0xE6F4: 0x8E1D,
    0xE6F5: 0x8E1E,
    0xE6F6: 0x8E10,
    0xE6F7: 0x8E1F,
    0xE6F8: 0x8E42,
    0xE6F9: 0x8E35,
    0xE6FA: 0x8E30,
    0xE6FB: 0x8E34,
    0xE6FC: 0x8E4A,
    0xE740: 0x8E47,
    0xE741: 0x8E49,
    0xE742: 0x8E4C,
    0xE743: 0x8E50,
    0xE744: 0x8E48,
    0xE745: 0x8E59,
    0xE746: 0x8E64,
    0xE747: 0x8E60,
    0xE748: 0x8E2A,
    0xE749: 0x8E63,
    0xE74A: 0x8E55,
    0xE74B: 0x8E76,
    0xE74C: 0x8E72,
    0xE74D: 0x8E7C,
    0xE74E: 0x8E81,
    0xE74F: 0x8E87,
    0xE750: 0x8E85,
    0xE751: 0x8E84,
    0xE752: 0x8E8B,
    0xE753: 0x8E8A,
    0xE754: 0x8E93,
    0xE755: 0x8E91,
    0xE756: 0x8E94,
    0xE757: 0x8E99,
    0xE758: 0x8EAA,
    0xE759: 0x8EA1,
    0xE75A: 0x8EAC,
    0xE75B: 0x8EB0,
    0xE75C: 0x8EC6,
    0xE75D: 0x8EB1,
    0xE75E: 0x8EBE,
    0xE75F: 0x8EC5,
    0xE760: 0x8EC8,
    0xE761: 0x8ECB,
    0xE762: 0x8EDB,
    0xE763: 0x8EE3,
    0xE764: 0x8EFC,
    0xE765: 0x8EFB,
    0xE766: 0x8EEB,
    0xE767: 0x8EFE,
    0xE768: 0x8F0A,
    0xE769: 0x8F05,
    0xE76A: 0x8F15,
    0xE76B: 0x8F12,
    0xE76C: 0x8F19,
    0xE76D: 0x8F13,
    0xE76E: 0x8F1C,
    0xE76F: 0x8F1F,
    0xE770: 0x8F1B,
    0xE771: 0x8F0C,
    0xE772: 0x8F26,
    0xE773: 0x8F33,
    0xE774: 0x8F3B,
    0xE775: 0x8F39,
    0xE776: 0x8F45,
    0xE777: 0x8F42,
    0xE778: 0x8F3E,
    0xE779: 0x8F4C,
    0xE77A: 0x8F49,
    0xE77B: 0x8F46,
    0xE77C: 0x8F4E,
    0xE77D: 0x8F57,
    0xE77E: 0x8F5C,
    0xE780: 0x8F62,
    0xE781: 0x8F63,
    0xE782: 0x8F64,
    0xE783: 0x8F9C,
    0xE784: 0x8F9F,
    0xE785: 0x8FA3,
    0xE786: 0x8FAD,
    0xE787: 0x8FAF,
    0xE788: 0x8FB7,
    0xE789: 0x8FDA,
    0xE78A: 0x8FE5,
    0xE78B: 0x8FE2,
    0xE78C: 0x8FEA,
    0xE78D: 0x8FEF,
    0xE78E: 0x9087,
    0xE78F: 0x8FF4,
    0xE790: 0x9005,
    0xE791: 0x8FF9,
    0xE792: 0x8FFA,
    0xE793: 0x9011,
    0xE794: 0x9015,
    0xE795: 0x9021,
    0xE796: 0x900D,
    0xE797: 0x901E,
    0xE798: 0x9016,
    0xE799: 0x900B,
    0xE79A: 0x9027,
    0xE79B: 0x9036,
    0xE79C: 0x9035,
    0xE79D: 0x9039,
    0xE79E: 0x8FF8,
    0xE79F: 0x904F,
    0xE7A0: 0x9050,
    0xE7A1: 0x9051,
    0xE7A2: 0x9052,
    0xE7A3: 0x900E,
    0xE7A4: 0x9049,
    0xE7A5: 0x903E,
    0xE7A6: 0x9056,
    0xE7A7: 0x9058,
    0xE7A8: 0x905E,
    0xE7A9: 0x9068,
    0xE7AA: 0x906F,
    0xE7AB: 0x9076,
    0xE7AC: 0x96A8,
    0xE7AD: 0x9072,
    0xE7AE: 0x9082,
    0xE7AF: 0x907D,
    0xE7B0: 0x9081,
    0xE7B1: 0x9080,
    0xE7B2: 0x908A,
    0xE7B3: 0x9089,
    0xE7B4: 0x908F,
    0xE7B5: 0x90A8,
    0xE7B6: 0x90AF,
    0xE7B7: 0x90B1,
    0xE7B8: 0x90B5,
    0xE7B9: 0x90E2,
    0xE7BA: 0x90E4,
    0xE7BB: 0x6248,
    0xE7BC: 0x90DB,
    0xE7BD: 0x9102,
    0xE7BE: 0x9112,
    0xE7BF: 0x9119,
    0xE7C0: 0x9132,
    0xE7C1: 0x9130,
    0xE7C2: 0x914A,
    0xE7C3: 0x9156,
    0xE7C4: 0x9158,
    0xE7C5: 0x9163,
    0xE7C6: 0x9165,
    0xE7C7: 0x9169,
    0xE7C8: 0x9173,
    0xE7C9: 0x9172,
    0xE7CA: 0x918B,
    0xE7CB: 0x9189,
    0xE7CC: 0x9182,
    0xE7CD: 0x91A2,
    0xE7CE: 0x91AB,
    0xE7CF: 0x91AF,
    0xE7D0: 0x91AA,
    0xE7D1: 0x91B5,
    0xE7D2: 0x91B4,
    0xE7D3: 0x91BA,
    0xE7D4: 0x91C0,
    0xE7D5: 0x91C1,
    0xE7D6: 0x91C9,
    0xE7D7: 0x91CB,
    0xE7D8: 0x91D0,
    0xE7D9: 0x91D6,
    0xE7DA: 0x91DF,
    0xE7DB: 0x91E1,
    0xE7DC: 0x91DB,
    0xE7DD: 0x91FC,
    0xE7DE: 0x91F5,
    0xE7DF: 0x91F6,
    0xE7E0: 0x921E,
    0xE7E1: 0x91FF,
    0xE7E2: 0x9214,
    0xE7E3: 0x922C,
    0xE7E4: 0x9215,
    0xE7E5: 0x9211,
    0xE7E6: 0x925E,
    0xE7E7: 0x9257,
    0xE7E8: 0x9245,
    0xE7E9: 0x9249,
    0xE7EA: 0x9264,
    0xE7EB: 0x9248,
    0xE7EC: 0x9295,
    0xE7ED: 0x923F,
    0xE7EE: 0x924B,
    0xE7EF: 0x9250,
    0xE7F0: 0x929C,
    0xE7F1: 0x9296,
    0xE7F2: 0x9293,
    0xE7F3: 0x929B,
    0xE7F4: 0x925A,
    0xE7F5: 0x92CF,
    0xE7F6: 0x92B9,
    0xE7F7: 0x92B7,
    0xE7F8: 0x92E9,
    0xE7F9: 0x930F,
    0xE7FA: 0x92FA,
    0xE7FB: 0x9344,
    0xE7FC: 0x932E,
    0xE840: 0x9319,
    0xE841: 0x9322,
    0xE842: 0x931A,
    0xE843: 0x9323,
    0xE844: 0x933A,
    0xE845: 0x9335,
    0xE846: 0x933B,
    0xE847: 0x935C,
    0xE848: 0x9360,
    0xE849: 0x937C,
    0xE84A: 0x936E,
    0xE84B: 0x9356,
    0xE84C: 0x93B0,
    0xE84D: 0x93AC,
    0xE84E: 0x93AD,
    0xE84F: 0x9394,
    0xE850: 0x93B9,
    0xE851: 0x93D6,
    0xE852: 0x93D7,
    0xE853: 0x93E8,
    0xE854: 0x93E5,
    0xE855: 0x93D8,
    0xE856: 0x93C3,
    0xE857: 0x93DD,
    0xE858: 0x93D0,
    0xE859: 0x93C8,
    0xE85A: 0x93E4,
    0xE85B: 0x941A,
    0xE85C: 0x9414,
    0xE85D: 0x9413,
    0xE85E: 0x9403,
    0xE85F: 0x9407,
    0xE860: 0x9410,
    0xE861: 0x9436,
    0xE862: 0x942B,
    0xE863: 0x9435,
    0xE864: 0x9421,
    0xE865: 0x943A,
    0xE866: 0x9441,
    0xE867: 0x9452,
    0xE868: 0x9444,
    0xE869: 0x945B,
    0xE86A: 0x9460,
    0xE86B: 0x9462,
    0xE86C: 0x945E,
    0xE86D: 0x946A,
    0xE86E: 0x9229,
    0xE86F: 0x9470,
    0xE870: 0x9475,
    0xE871: 0x9477,
    0xE872: 0x947D,
    0xE873: 0x945A,
    0xE874: 0x947C,
    0xE875: 0x947E,
    0xE876: 0x9481,
    0xE877: 0x947F,
    0xE878: 0x9582,
    0xE879: 0x9587,
    0xE87A: 0x958A,
    0xE87B: 0x9594,
    0xE87C: 0x9596,
    0xE87D: 0x9598,
    0xE87E: 0x9599,
    0xE880: 0x95A0,
    0xE881: 0x95A8,
    0xE882: 0x95A7,
    0xE883: 0x95AD,
    0xE884: 0x95BC,
    0xE885: 0x95BB,
    0xE886: 0x95B9,
    0xE887: 0x95BE,
    0xE888: 0x95CA,
    0xE889: 0x6FF6,
    0xE88A: 0x95C3,
    0xE88B: 0x95CD,
    0xE88C: 0x95CC,
    0xE88D: 0x95D5,
    0xE88E: 0x95D4,
    0xE88F: 0x95D6,
    0xE890: 0x95DC,
    0xE891: 0x95E1,
    0xE892: 0x95E5,
    0xE893: 0x95E2,
    0xE894: 0x9621,
    0xE895: 0x9628,
    0xE896: 0x962E,
    0xE897: 0x962F,
    0xE898: 0x9642,
    0xE899: 0x964C,
    0xE89A: 0x964F,
    0xE89B: 0x964B,
    0xE89C: 0x9677,
    0xE89D: 0x965C,
    0xE89E: 0x965E,
    0xE89F: 0x965D,
    0xE8A0: 0x965F,
    0xE8A1: 0x9666,
    0xE8A2: 0x9672,
    0xE8A3: 0x966C,
    0xE8A4: 0x968D,
    0xE8A5: 0x9698,
    0xE8A6: 0x9695,
    0xE8A7: 0x9697,
    0xE8A8: 0x96AA,
    0xE8A9: 0x96A7,
    0xE8AA: 0x96B1,
    0xE8AB: 0x96B2,
    0xE8AC: 0x96B0,
    0xE8AD: 0x96B4,
    0xE8AE: 0x96B6,
    0xE8AF: 0x96B8,
    0xE8B0: 0x96B9,
    0xE8B1: 0x96CE,
    0xE8B2: 0x96CB,
    0xE8B3: 0x96C9,
    0xE8B4: 0x96CD,
    0xE8B5: 0x894D,
    0xE8B6: 0x96DC,
    0xE8B7: 0x970D,
    0xE8B8: 0x96D5,
    0xE8B9: 0x96F9,
    0xE8BA: 0x9704,
    0xE8BB: 0x9706,
    0xE8BC: 0x9708,
    0xE8BD: 0x9713,
    0xE8BE: 0x970E,
    0xE8BF: 0x9711,
    0xE8C0: 0x970F,
    0xE8C1: 0x9716,
    0xE8C2: 0x9719,
    0xE8C3: 0x9724,
    0xE8C4: 0x972A,
    0xE8C5: 0x9730,
    0xE8C6: 0x9739,
    0xE8C7: 0x973D,
    0xE8C8: 0x973E,
    0xE8C9: 0x9744,
    0xE8CA: 0x9746,
    0xE8CB: 0x9748,
    0xE8CC: 0x9742,
    0xE8CD: 0x9749,
    0xE8CE: 0x975C,
    0xE8CF: 0x9760,
    0xE8D0: 0x9764,
    0xE8D1: 0x9766,
    0xE8D2: 0x9768,
    0xE8D3: 0x52D2,
    0xE8D4: 0x976B,
    0xE8D5: 0x9771,
    0xE8D6: 0x9779,
    0xE8D7: 0x9785,
    0xE8D8: 0x977C,
    0xE8D9: 0x9781,
    0xE8DA: 0x977A,
    0xE8DB: 0x9786,
    0xE8DC: 0x978B,
    0xE8DD: 0x978F,
    0xE8DE: 0x9790,
    0xE8DF: 0x979C,
    0xE8E0: 0x97A8,
    0xE8E1: 0x97A6,
    0xE8E2: 0x97A3,
    0xE8E3: 0x97B3,
    0xE8E4: 0x97B4,
    0xE8E5: 0x97C3,
    0xE8E6: 0x97C6,
    0xE8E7: 0x97C8,
    0xE8E8: 0x97CB,
    0xE8E9: 0x97DC,
    0xE8EA: 0x97ED,
    0xE8EB: 0x9F4F,
    0xE8EC: 0x97F2,
    0xE8ED: 0x7ADF,
    0xE8EE: 0x97F6,
    0xE8EF: 0x97F5,
    0xE8F0: 0x980F,
    0xE8F1: 0x980C,
    0xE8F2: 0x9838,
    0xE8F3: 0x9824,
    0xE8F4: 0x9821,
    0xE8F5: 0x9837,
    0xE8F6: 0x983D,
    0xE8F7: 0x9846,
    0xE8F8: 0x984F,
    0xE8F9: 0x984B,
    0xE8FA: 0x986B,
    0xE8FB: 0x986F,
    0xE8FC: 0x9870,
    0xE940: 0x9871,
    0xE941: 0x9874,
    0xE942: 0x9873,
    0xE943: 0x98AA,
    0xE944: 0x98AF,
    0xE945: 0x98B1,
    0xE946: 0x98B6,
    0xE947: 0x98C4,
    0xE948: 0x98C3,
    0xE949: 0x98C6,
    0xE94A: 0x98E9,
    0xE94B: 0x98EB,
    0xE94C: 0x9903,
    0xE94D: 0x9909,
    0xE94E: 0x9912,
    0xE94F: 0x9914,
    0xE950: 0x9918,
    0xE951: 0x9921,
    0xE952: 0x991D,
    0xE953: 0x991E,
    0xE954: 0x9924,
    0xE955: 0x9920,
    0xE956: 0x992C,
    0xE957: 0x992E,
    0xE958: 0x993D,
    0xE959: 0x993E,
    0xE95A: 0x9942,
    0xE95B: 0x9949,
    0xE95C: 0x9945,
    0xE95D: 0x9950,
    0xE95E: 0x994B,
    0xE95F: 0x9951,
    0xE960: 0x9952,
    0xE961: 0x994C,
    0xE962: 0x9955,
    0xE963: 0x9997,
    0xE964: 0x9998,
    0xE965: 0x99A5,
    0xE966: 0x99AD,
    0xE967: 0x99AE,
    0xE968: 0x99BC,
    0xE969: 0x99DF,
    0xE96A: 0x99DB,
    0xE96B: 0x99DD,
    0xE96C: 0x99D8,
    0xE96D: 0x99D1,
    0xE96E: 0x99ED,
    0xE96F: 0x99EE,
    0xE970: 0x99F1,
    0xE971: 0x99F2,
    0xE972: 0x99FB,
    0xE973: 0x99F8,
    0xE974: 0x9A01,
    0xE975: 0x9A0F,
    0xE976: 0x9A05,
    0xE977: 0x99E2,
    0xE978: 0x9A19,
    0xE979: 0x9A2B,
    0xE97A: 0x9A37,
    0xE97B: 0x9A45,
    0xE97C: 0x9A42,
    0xE97D: 0x9A40,
    0xE97E: 0x9A43,
    0xE980: 0x9A3E,
    0xE981: 0x9A55,
    0xE982: 0x9A4D,
    0xE983: 0x9A5B,
    0xE984: 0x9A57,
    0xE985: 0x9A5F,
    0xE986: 0x9A62,
    0xE987: 0x9A65,
    0xE988: 0x9A64,
    0xE989: 0x9A69,
    0xE98A: 0x9A6B,
    0xE98B: 0x9A6A,
    0xE98C: 0x9AAD,
    0xE98D: 0x9AB0,
    0xE98E: 0x9ABC,
    0xE98F: 0x9AC0,
    0xE990: 0x9ACF,
    0xE991: 0x9AD1,
    0xE992: 0x9AD3,
    0xE993: 0x9AD4,
    0xE994: 0x9ADE,
    0xE995: 0x9ADF,
    0xE996: 0x9AE2,
    0xE997: 0x9AE3,
    0xE998: 0x9AE6,
    0xE999: 0x9AEF,
    0xE99A: 0x9AEB,
    0xE99B: 0x9AEE,
    0xE99C: 0x9AF4,
    0xE99D: 0x9AF1,
    0xE99E: 0x9AF7,
    0xE99F: 0x9AFB,
    0xE9A0: 0x9B06,
    0xE9A1: 0x9B18,
    0xE9A2: 0x9B1A,
    0xE9A3: 0x9B1F,
    0xE9A4: 0x9B22,
    0xE9A5: 0x9B23,
    0xE9A6: 0x9B25,
    0xE9A7: 0x9B27,
    0xE9A8: 0x9B28,
    0xE9A9: 0x9B29,
    0xE9AA: 0x9B2A,
    0xE9AB: 0x9B2E,
    0xE9AC: 0x9B2F,
    0xE9AD: 0x9B32,
    0xE9AE: 0x9B44,
    0xE9AF: 0x9B43,
    0xE9B0: 0x9B4F,
    0xE9B1: 0x9B4D,
    0xE9B2: 0x9B4E,
    0xE9B3: 0x9B51,
    0xE9B4: 0x9B58,
    0xE9B5: 0x9B74,
    0xE9B6: 0x9B93,
    0xE9B7: 0x9B83,
    0xE9B8: 0x9B91,
    0xE9B9: 0x9B96,
    0xE9BA: 0x9B97,
    0xE9BB: 0x9B9F,
    0xE9BC: 0x9BA0,
    0xE9BD: 0x9BA8,
    0xE9BE: 0x9BB4,
    0xE9BF: 0x9BC0,
    0xE9C0: 0x9BCA,
    0xE9C1: 0x9BB9,
    0xE9C2: 0x9BC6,
    0xE9C3: 0x9BCF,
    0xE9C4: 0x9BD1,
    0xE9C5: 0x9BD2,
    0xE9C6: 0x9BE3,
    0xE9C7: 0x9BE2,
    0xE9C8: 0x9BE4,
    0xE9C9: 0x9BD4,
    0xE9CA: 0x9BE1,
    0xE9CB: 0x9C3A,
    0xE9CC: 0x9BF2,
    0xE9CD: 0x9BF1,
    0xE9CE: 0x9BF0,
    0xE9CF: 0x9C15,
    0xE9D0: 0x9C14,
    0xE9D1: 0x9C09,
    0xE9D2: 0x9C13,
    0xE9D3: 0x9C0C,
    0xE9D4: 0x9C06,
    0xE9D5: 0x9C08,
    0xE9D6: 0x9C12,
    0xE9D7: 0x9C0A,
    0xE9D8: 0x9C04,
    0xE9D9: 0x9C2E,
    0xE9DA: 0x9C1B,
    0xE9DB: 0x9C25,
    0xE9DC: 0x9C24,
    0xE9DD: 0x9C21,
    0xE9DE: 0x9C30,
    0xE9DF: 0x9C47,
    0xE9E0: 0x9C32,
    0xE9E1: 0x9C46,
    0xE9E2: 0x9C3E,
    0xE9E3: 0x9C5A,
    0xE9E4: 0x9C60,
    0xE9E5: 0x9C67,
    0xE9E6: 0x9C76,
    0xE9E7: 0x9C78,
    0xE9E8: 0x9CE7,
    0xE9E9: 0x9CEC,
    0xE9EA: 0x9CF0,
    0xE9EB: 0x9D09,
    0xE9EC: 0x9D08,
    0xE9ED: 0x9CEB,
    0xE9EE: 0x9D03,
    0xE9EF: 0x9D06,
    0xE9F0: 0x9D2A,
    0xE9F1: 0x9D26,
    0xE9F2: 0x9DAF,
    0xE9F3: 0x9D23,
    0xE9F4: 0x9D1F,
    0xE9F5: 0x9D44,
    0xE9F6: 0x9D15,
    0xE9F7: 0x9D12,
    0xE9F8: 0x9D41,
    0xE9F9: 0x9D3F,
    0xE9FA: 0x9D3E,
    0xE9FB: 0x9D46,
    0xE9FC: 0x9D48,
    0xEA40: 0x9D5D,
    0xEA41: 0x9D5E,
    0xEA42: 0x9D64,
    0xEA43: 0x9D51,
    0xEA44: 0x9D50,
    0xEA45: 0x9D59,
    0xEA46: 0x9D72,
    0xEA47: 0x9D89,
    0xEA48: 0x9D87,
    0xEA49: 0x9DAB,
    0xEA4A: 0x9D6F,
    0xEA4B: 0x9D7A,
    0xEA4C: 0x9D9A,
    0xEA4D: 0x9DA4,
    0xEA4E: 0x9DA9,
    0xEA4F: 0x9DB2,
    0xEA50: 0x9DC4,
    0xEA51: 0x9DC1,
    0xEA52: 0x9DBB,
    0xEA53: 0x9DB8,
    0xEA54: 0x9DBA,
    0xEA55: 0x9DC6,
    0xEA56: 0x9DCF,
    0xEA57: 0x9DC2,
    0xEA58: 0x9DD9,
    0xEA59: 0x9DD3,
    0xEA5A: 0x9DF8,
    0xEA5B: 0x9DE6,
    0xEA5C: 0x9DED,
    0xEA5D: 0x9DEF,
    0xEA5E: 0x9DFD,
    0xEA5F: 0x9E1A,
    0xEA60: 0x9E1B,
    0xEA61: 0x9E1E,
    0xEA62: 0x9E75,
    0xEA63: 0x9E79,
    0xEA64: 0x9E7D,
    0xEA65: 0x9E81,
    0xEA66: 0x9E88,
    0xEA67: 0x9E8B,
    0xEA68: 0x9E8C,
    0xEA69: 0x9E92,
    0xEA6A: 0x9E95,
    0xEA6B: 0x9E91,
    0xEA6C: 0x9E9D,
    0xEA6D: 0x9EA5,
    0xEA6E: 0x9EA9,
    0xEA6F: 0x9EB8,
    0xEA70: 0x9EAA,
    0xEA71: 0x9EAD,
    0xEA72: 0x9761,
    0xEA73: 0x9ECC,
    0xEA74: 0x9ECE,
    0xEA75: 0x9ECF,
    0xEA76: 0x9ED0,
    0xEA77: 0x9ED4,
    0xEA78: 0x9EDC,
    0xEA79: 0x9EDE,
    0xEA7A: 0x9EDD,
    0xEA7B: 0x9EE0,
    0xEA7C: 0x9EE5,
    0xEA7D: 0x9EE8,
    0xEA7E: 0x9EEF,
    0xEA80: 0x9EF4,
    0xEA81: 0x9EF6,
    0xEA82: 0x9EF7,
    0xEA83: 0x9EF9,
    0xEA84: 0x9EFB,
    0xEA85: 0x9EFC,
    0xEA86: 0x9EFD,
    0xEA87: 0x9F07,
    0xEA88: 0x9F08,
    0xEA89: 0x76B7,
    0xEA8A: 0x9F15,
    0xEA8B: 0x9F21,
    0xEA8C: 0x9F2C,
    0xEA8D: 0x9F3E,
    0xEA8E: 0x9F4A,
    0xEA8F: 0x9F52,
    0xEA90: 0x9F54,
    0xEA91: 0x9F63,
    0xEA92: 0x9F5F,
    0xEA93: 0x9F60,
    0xEA94: 0x9F61,
    0xEA95: 0x9F66,
    0xEA96: 0x9F67,
    0xEA97: 0x9F6C,
    0xEA98: 0x9F6A,
    0xEA99: 0x9F77,
    0xEA9A: 0x9F72,
    0xEA9B: 0x9F76,
    0xEA9C: 0x9F95,
    0xEA9D: 0x9F9C,
    0xEA9E: 0x9FA0,
    0xEA9F: 0x582F,
    0xEAA0: 0x69C7,
    0xEAA1: 0x9059,
    0xEAA2: 0x7464,
    0xEAA3: 0x51DC,
    0xEAA4: 0x7199,
};


/***/ }),
/* 9 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

Object.defineProperty(exports, "__esModule", { value: true });
var GenericGF_1 = __webpack_require__(1);
var GenericGFPoly_1 = __webpack_require__(2);
function runEuclideanAlgorithm(field, a, b, R) {
    var _a;
    // Assume a's degree is >= b's
    if (a.degree() < b.degree()) {
        _a = [b, a], a = _a[0], b = _a[1];
    }
    var rLast = a;
    var r = b;
    var tLast = field.zero;
    var t = field.one;
    // Run Euclidean algorithm until r's degree is less than R/2
    while (r.degree() >= R / 2) {
        var rLastLast = rLast;
        var tLastLast = tLast;
        rLast = r;
        tLast = t;
        // Divide rLastLast by rLast, with quotient in q and remainder in r
        if (rLast.isZero()) {
            // Euclidean algorithm already terminated?
            return null;
        }
        r = rLastLast;
        var q = field.zero;
        var denominatorLeadingTerm = rLast.getCoefficient(rLast.degree());
        var dltInverse = field.inverse(denominatorLeadingTerm);
        while (r.degree() >= rLast.degree() && !r.isZero()) {
            var degreeDiff = r.degree() - rLast.degree();
            var scale = field.multiply(r.getCoefficient(r.degree()), dltInverse);
            q = q.addOrSubtract(field.buildMonomial(degreeDiff, scale));
            r = r.addOrSubtract(rLast.multiplyByMonomial(degreeDiff, scale));
        }
        t = q.multiplyPoly(tLast).addOrSubtract(tLastLast);
        if (r.degree() >= rLast.degree()) {
            return null;
        }
    }
    var sigmaTildeAtZero = t.getCoefficient(0);
    if (sigmaTildeAtZero === 0) {
        return null;
    }
    var inverse = field.inverse(sigmaTildeAtZero);
    return [t.multiply(inverse), r.multiply(inverse)];
}
function findErrorLocations(field, errorLocator) {
    // This is a direct application of Chien's search
    var numErrors = errorLocator.degree();
    if (numErrors === 1) {
        return [errorLocator.getCoefficient(1)];
    }
    var result = new Array(numErrors);
    var errorCount = 0;
    for (var i = 1; i < field.size && errorCount < numErrors; i++) {
        if (errorLocator.evaluateAt(i) === 0) {
            result[errorCount] = field.inverse(i);
            errorCount++;
        }
    }
    if (errorCount !== numErrors) {
        return null;
    }
    return result;
}
function findErrorMagnitudes(field, errorEvaluator, errorLocations) {
    // This is directly applying Forney's Formula
    var s = errorLocations.length;
    var result = new Array(s);
    for (var i = 0; i < s; i++) {
        var xiInverse = field.inverse(errorLocations[i]);
        var denominator = 1;
        for (var j = 0; j < s; j++) {
            if (i !== j) {
                denominator = field.multiply(denominator, GenericGF_1.addOrSubtractGF(1, field.multiply(errorLocations[j], xiInverse)));
            }
        }
        result[i] = field.multiply(errorEvaluator.evaluateAt(xiInverse), field.inverse(denominator));
        if (field.generatorBase !== 0) {
            result[i] = field.multiply(result[i], xiInverse);
        }
    }
    return result;
}
function decode(bytes, twoS) {
    var outputBytes = new Uint8ClampedArray(bytes.length);
    outputBytes.set(bytes);
    var field = new GenericGF_1.default(0x011D, 256, 0); // x^8 + x^4 + x^3 + x^2 + 1
    var poly = new GenericGFPoly_1.default(field, outputBytes);
    var syndromeCoefficients = new Uint8ClampedArray(twoS);
    var error = false;
    for (var s = 0; s < twoS; s++) {
        var evaluation = poly.evaluateAt(field.exp(s + field.generatorBase));
        syndromeCoefficients[syndromeCoefficients.length - 1 - s] = evaluation;
        if (evaluation !== 0) {
            error = true;
        }
    }
    if (!error) {
        return outputBytes;
    }
    var syndrome = new GenericGFPoly_1.default(field, syndromeCoefficients);
    var sigmaOmega = runEuclideanAlgorithm(field, field.buildMonomial(twoS, 1), syndrome, twoS);
    if (sigmaOmega === null) {
        return null;
    }
    var errorLocations = findErrorLocations(field, sigmaOmega[0]);
    if (errorLocations == null) {
        return null;
    }
    var errorMagnitudes = findErrorMagnitudes(field, sigmaOmega[1], errorLocations);
    for (var i = 0; i < errorLocations.length; i++) {
        var position = outputBytes.length - 1 - field.log(errorLocations[i]);
        if (position < 0) {
            return null;
        }
        outputBytes[position] = GenericGF_1.addOrSubtractGF(outputBytes[position], errorMagnitudes[i]);
    }
    return outputBytes;
}
exports.decode = decode;


/***/ }),
/* 10 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.VERSIONS = [
    {
        infoBits: null,
        versionNumber: 1,
        alignmentPatternCenters: [],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 7,
                ecBlocks: [{ numBlocks: 1, dataCodewordsPerBlock: 19 }],
            },
            {
                ecCodewordsPerBlock: 10,
                ecBlocks: [{ numBlocks: 1, dataCodewordsPerBlock: 16 }],
            },
            {
                ecCodewordsPerBlock: 13,
                ecBlocks: [{ numBlocks: 1, dataCodewordsPerBlock: 13 }],
            },
            {
                ecCodewordsPerBlock: 17,
                ecBlocks: [{ numBlocks: 1, dataCodewordsPerBlock: 9 }],
            },
        ],
    },
    {
        infoBits: null,
        versionNumber: 2,
        alignmentPatternCenters: [6, 18],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 10,
                ecBlocks: [{ numBlocks: 1, dataCodewordsPerBlock: 34 }],
            },
            {
                ecCodewordsPerBlock: 16,
                ecBlocks: [{ numBlocks: 1, dataCodewordsPerBlock: 28 }],
            },
            {
                ecCodewordsPerBlock: 22,
                ecBlocks: [{ numBlocks: 1, dataCodewordsPerBlock: 22 }],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [{ numBlocks: 1, dataCodewordsPerBlock: 16 }],
            },
        ],
    },
    {
        infoBits: null,
        versionNumber: 3,
        alignmentPatternCenters: [6, 22],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 15,
                ecBlocks: [{ numBlocks: 1, dataCodewordsPerBlock: 55 }],
            },
            {
                ecCodewordsPerBlock: 26,
                ecBlocks: [{ numBlocks: 1, dataCodewordsPerBlock: 44 }],
            },
            {
                ecCodewordsPerBlock: 18,
                ecBlocks: [{ numBlocks: 2, dataCodewordsPerBlock: 17 }],
            },
            {
                ecCodewordsPerBlock: 22,
                ecBlocks: [{ numBlocks: 2, dataCodewordsPerBlock: 13 }],
            },
        ],
    },
    {
        infoBits: null,
        versionNumber: 4,
        alignmentPatternCenters: [6, 26],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 20,
                ecBlocks: [{ numBlocks: 1, dataCodewordsPerBlock: 80 }],
            },
            {
                ecCodewordsPerBlock: 18,
                ecBlocks: [{ numBlocks: 2, dataCodewordsPerBlock: 32 }],
            },
            {
                ecCodewordsPerBlock: 26,
                ecBlocks: [{ numBlocks: 2, dataCodewordsPerBlock: 24 }],
            },
            {
                ecCodewordsPerBlock: 16,
                ecBlocks: [{ numBlocks: 4, dataCodewordsPerBlock: 9 }],
            },
        ],
    },
    {
        infoBits: null,
        versionNumber: 5,
        alignmentPatternCenters: [6, 30],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 26,
                ecBlocks: [{ numBlocks: 1, dataCodewordsPerBlock: 108 }],
            },
            {
                ecCodewordsPerBlock: 24,
                ecBlocks: [{ numBlocks: 2, dataCodewordsPerBlock: 43 }],
            },
            {
                ecCodewordsPerBlock: 18,
                ecBlocks: [
                    { numBlocks: 2, dataCodewordsPerBlock: 15 },
                    { numBlocks: 2, dataCodewordsPerBlock: 16 },
                ],
            },
            {
                ecCodewordsPerBlock: 22,
                ecBlocks: [
                    { numBlocks: 2, dataCodewordsPerBlock: 11 },
                    { numBlocks: 2, dataCodewordsPerBlock: 12 },
                ],
            },
        ],
    },
    {
        infoBits: null,
        versionNumber: 6,
        alignmentPatternCenters: [6, 34],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 18,
                ecBlocks: [{ numBlocks: 2, dataCodewordsPerBlock: 68 }],
            },
            {
                ecCodewordsPerBlock: 16,
                ecBlocks: [{ numBlocks: 4, dataCodewordsPerBlock: 27 }],
            },
            {
                ecCodewordsPerBlock: 24,
                ecBlocks: [{ numBlocks: 4, dataCodewordsPerBlock: 19 }],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [{ numBlocks: 4, dataCodewordsPerBlock: 15 }],
            },
        ],
    },
    {
        infoBits: 0x07C94,
        versionNumber: 7,
        alignmentPatternCenters: [6, 22, 38],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 20,
                ecBlocks: [{ numBlocks: 2, dataCodewordsPerBlock: 78 }],
            },
            {
                ecCodewordsPerBlock: 18,
                ecBlocks: [{ numBlocks: 4, dataCodewordsPerBlock: 31 }],
            },
            {
                ecCodewordsPerBlock: 18,
                ecBlocks: [
                    { numBlocks: 2, dataCodewordsPerBlock: 14 },
                    { numBlocks: 4, dataCodewordsPerBlock: 15 },
                ],
            },
            {
                ecCodewordsPerBlock: 26,
                ecBlocks: [
                    { numBlocks: 4, dataCodewordsPerBlock: 13 },
                    { numBlocks: 1, dataCodewordsPerBlock: 14 },
                ],
            },
        ],
    },
    {
        infoBits: 0x085BC,
        versionNumber: 8,
        alignmentPatternCenters: [6, 24, 42],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 24,
                ecBlocks: [{ numBlocks: 2, dataCodewordsPerBlock: 97 }],
            },
            {
                ecCodewordsPerBlock: 22,
                ecBlocks: [
                    { numBlocks: 2, dataCodewordsPerBlock: 38 },
                    { numBlocks: 2, dataCodewordsPerBlock: 39 },
                ],
            },
            {
                ecCodewordsPerBlock: 22,
                ecBlocks: [
                    { numBlocks: 4, dataCodewordsPerBlock: 18 },
                    { numBlocks: 2, dataCodewordsPerBlock: 19 },
                ],
            },
            {
                ecCodewordsPerBlock: 26,
                ecBlocks: [
                    { numBlocks: 4, dataCodewordsPerBlock: 14 },
                    { numBlocks: 2, dataCodewordsPerBlock: 15 },
                ],
            },
        ],
    },
    {
        infoBits: 0x09A99,
        versionNumber: 9,
        alignmentPatternCenters: [6, 26, 46],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [{ numBlocks: 2, dataCodewordsPerBlock: 116 }],
            },
            {
                ecCodewordsPerBlock: 22,
                ecBlocks: [
                    { numBlocks: 3, dataCodewordsPerBlock: 36 },
                    { numBlocks: 2, dataCodewordsPerBlock: 37 },
                ],
            },
            {
                ecCodewordsPerBlock: 20,
                ecBlocks: [
                    { numBlocks: 4, dataCodewordsPerBlock: 16 },
                    { numBlocks: 4, dataCodewordsPerBlock: 17 },
                ],
            },
            {
                ecCodewordsPerBlock: 24,
                ecBlocks: [
                    { numBlocks: 4, dataCodewordsPerBlock: 12 },
                    { numBlocks: 4, dataCodewordsPerBlock: 13 },
                ],
            },
        ],
    },
    {
        infoBits: 0x0A4D3,
        versionNumber: 10,
        alignmentPatternCenters: [6, 28, 50],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 18,
                ecBlocks: [
                    { numBlocks: 2, dataCodewordsPerBlock: 68 },
                    { numBlocks: 2, dataCodewordsPerBlock: 69 },
                ],
            },
            {
                ecCodewordsPerBlock: 26,
                ecBlocks: [
                    { numBlocks: 4, dataCodewordsPerBlock: 43 },
                    { numBlocks: 1, dataCodewordsPerBlock: 44 },
                ],
            },
            {
                ecCodewordsPerBlock: 24,
                ecBlocks: [
                    { numBlocks: 6, dataCodewordsPerBlock: 19 },
                    { numBlocks: 2, dataCodewordsPerBlock: 20 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 6, dataCodewordsPerBlock: 15 },
                    { numBlocks: 2, dataCodewordsPerBlock: 16 },
                ],
            },
        ],
    },
    {
        infoBits: 0x0BBF6,
        versionNumber: 11,
        alignmentPatternCenters: [6, 30, 54],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 20,
                ecBlocks: [{ numBlocks: 4, dataCodewordsPerBlock: 81 }],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 1, dataCodewordsPerBlock: 50 },
                    { numBlocks: 4, dataCodewordsPerBlock: 51 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 4, dataCodewordsPerBlock: 22 },
                    { numBlocks: 4, dataCodewordsPerBlock: 23 },
                ],
            },
            {
                ecCodewordsPerBlock: 24,
                ecBlocks: [
                    { numBlocks: 3, dataCodewordsPerBlock: 12 },
                    { numBlocks: 8, dataCodewordsPerBlock: 13 },
                ],
            },
        ],
    },
    {
        infoBits: 0x0C762,
        versionNumber: 12,
        alignmentPatternCenters: [6, 32, 58],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 24,
                ecBlocks: [
                    { numBlocks: 2, dataCodewordsPerBlock: 92 },
                    { numBlocks: 2, dataCodewordsPerBlock: 93 },
                ],
            },
            {
                ecCodewordsPerBlock: 22,
                ecBlocks: [
                    { numBlocks: 6, dataCodewordsPerBlock: 36 },
                    { numBlocks: 2, dataCodewordsPerBlock: 37 },
                ],
            },
            {
                ecCodewordsPerBlock: 26,
                ecBlocks: [
                    { numBlocks: 4, dataCodewordsPerBlock: 20 },
                    { numBlocks: 6, dataCodewordsPerBlock: 21 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 7, dataCodewordsPerBlock: 14 },
                    { numBlocks: 4, dataCodewordsPerBlock: 15 },
                ],
            },
        ],
    },
    {
        infoBits: 0x0D847,
        versionNumber: 13,
        alignmentPatternCenters: [6, 34, 62],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 26,
                ecBlocks: [{ numBlocks: 4, dataCodewordsPerBlock: 107 }],
            },
            {
                ecCodewordsPerBlock: 22,
                ecBlocks: [
                    { numBlocks: 8, dataCodewordsPerBlock: 37 },
                    { numBlocks: 1, dataCodewordsPerBlock: 38 },
                ],
            },
            {
                ecCodewordsPerBlock: 24,
                ecBlocks: [
                    { numBlocks: 8, dataCodewordsPerBlock: 20 },
                    { numBlocks: 4, dataCodewordsPerBlock: 21 },
                ],
            },
            {
                ecCodewordsPerBlock: 22,
                ecBlocks: [
                    { numBlocks: 12, dataCodewordsPerBlock: 11 },
                    { numBlocks: 4, dataCodewordsPerBlock: 12 },
                ],
            },
        ],
    },
    {
        infoBits: 0x0E60D,
        versionNumber: 14,
        alignmentPatternCenters: [6, 26, 46, 66],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 3, dataCodewordsPerBlock: 115 },
                    { numBlocks: 1, dataCodewordsPerBlock: 116 },
                ],
            },
            {
                ecCodewordsPerBlock: 24,
                ecBlocks: [
                    { numBlocks: 4, dataCodewordsPerBlock: 40 },
                    { numBlocks: 5, dataCodewordsPerBlock: 41 },
                ],
            },
            {
                ecCodewordsPerBlock: 20,
                ecBlocks: [
                    { numBlocks: 11, dataCodewordsPerBlock: 16 },
                    { numBlocks: 5, dataCodewordsPerBlock: 17 },
                ],
            },
            {
                ecCodewordsPerBlock: 24,
                ecBlocks: [
                    { numBlocks: 11, dataCodewordsPerBlock: 12 },
                    { numBlocks: 5, dataCodewordsPerBlock: 13 },
                ],
            },
        ],
    },
    {
        infoBits: 0x0F928,
        versionNumber: 15,
        alignmentPatternCenters: [6, 26, 48, 70],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 22,
                ecBlocks: [
                    { numBlocks: 5, dataCodewordsPerBlock: 87 },
                    { numBlocks: 1, dataCodewordsPerBlock: 88 },
                ],
            },
            {
                ecCodewordsPerBlock: 24,
                ecBlocks: [
                    { numBlocks: 5, dataCodewordsPerBlock: 41 },
                    { numBlocks: 5, dataCodewordsPerBlock: 42 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 5, dataCodewordsPerBlock: 24 },
                    { numBlocks: 7, dataCodewordsPerBlock: 25 },
                ],
            },
            {
                ecCodewordsPerBlock: 24,
                ecBlocks: [
                    { numBlocks: 11, dataCodewordsPerBlock: 12 },
                    { numBlocks: 7, dataCodewordsPerBlock: 13 },
                ],
            },
        ],
    },
    {
        infoBits: 0x10B78,
        versionNumber: 16,
        alignmentPatternCenters: [6, 26, 50, 74],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 24,
                ecBlocks: [
                    { numBlocks: 5, dataCodewordsPerBlock: 98 },
                    { numBlocks: 1, dataCodewordsPerBlock: 99 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 7, dataCodewordsPerBlock: 45 },
                    { numBlocks: 3, dataCodewordsPerBlock: 46 },
                ],
            },
            {
                ecCodewordsPerBlock: 24,
                ecBlocks: [
                    { numBlocks: 15, dataCodewordsPerBlock: 19 },
                    { numBlocks: 2, dataCodewordsPerBlock: 20 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 3, dataCodewordsPerBlock: 15 },
                    { numBlocks: 13, dataCodewordsPerBlock: 16 },
                ],
            },
        ],
    },
    {
        infoBits: 0x1145D,
        versionNumber: 17,
        alignmentPatternCenters: [6, 30, 54, 78],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 1, dataCodewordsPerBlock: 107 },
                    { numBlocks: 5, dataCodewordsPerBlock: 108 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 10, dataCodewordsPerBlock: 46 },
                    { numBlocks: 1, dataCodewordsPerBlock: 47 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 1, dataCodewordsPerBlock: 22 },
                    { numBlocks: 15, dataCodewordsPerBlock: 23 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 2, dataCodewordsPerBlock: 14 },
                    { numBlocks: 17, dataCodewordsPerBlock: 15 },
                ],
            },
        ],
    },
    {
        infoBits: 0x12A17,
        versionNumber: 18,
        alignmentPatternCenters: [6, 30, 56, 82],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 5, dataCodewordsPerBlock: 120 },
                    { numBlocks: 1, dataCodewordsPerBlock: 121 },
                ],
            },
            {
                ecCodewordsPerBlock: 26,
                ecBlocks: [
                    { numBlocks: 9, dataCodewordsPerBlock: 43 },
                    { numBlocks: 4, dataCodewordsPerBlock: 44 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 17, dataCodewordsPerBlock: 22 },
                    { numBlocks: 1, dataCodewordsPerBlock: 23 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 2, dataCodewordsPerBlock: 14 },
                    { numBlocks: 19, dataCodewordsPerBlock: 15 },
                ],
            },
        ],
    },
    {
        infoBits: 0x13532,
        versionNumber: 19,
        alignmentPatternCenters: [6, 30, 58, 86],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 3, dataCodewordsPerBlock: 113 },
                    { numBlocks: 4, dataCodewordsPerBlock: 114 },
                ],
            },
            {
                ecCodewordsPerBlock: 26,
                ecBlocks: [
                    { numBlocks: 3, dataCodewordsPerBlock: 44 },
                    { numBlocks: 11, dataCodewordsPerBlock: 45 },
                ],
            },
            {
                ecCodewordsPerBlock: 26,
                ecBlocks: [
                    { numBlocks: 17, dataCodewordsPerBlock: 21 },
                    { numBlocks: 4, dataCodewordsPerBlock: 22 },
                ],
            },
            {
                ecCodewordsPerBlock: 26,
                ecBlocks: [
                    { numBlocks: 9, dataCodewordsPerBlock: 13 },
                    { numBlocks: 16, dataCodewordsPerBlock: 14 },
                ],
            },
        ],
    },
    {
        infoBits: 0x149A6,
        versionNumber: 20,
        alignmentPatternCenters: [6, 34, 62, 90],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 3, dataCodewordsPerBlock: 107 },
                    { numBlocks: 5, dataCodewordsPerBlock: 108 },
                ],
            },
            {
                ecCodewordsPerBlock: 26,
                ecBlocks: [
                    { numBlocks: 3, dataCodewordsPerBlock: 41 },
                    { numBlocks: 13, dataCodewordsPerBlock: 42 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 15, dataCodewordsPerBlock: 24 },
                    { numBlocks: 5, dataCodewordsPerBlock: 25 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 15, dataCodewordsPerBlock: 15 },
                    { numBlocks: 10, dataCodewordsPerBlock: 16 },
                ],
            },
        ],
    },
    {
        infoBits: 0x15683,
        versionNumber: 21,
        alignmentPatternCenters: [6, 28, 50, 72, 94],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 4, dataCodewordsPerBlock: 116 },
                    { numBlocks: 4, dataCodewordsPerBlock: 117 },
                ],
            },
            {
                ecCodewordsPerBlock: 26,
                ecBlocks: [{ numBlocks: 17, dataCodewordsPerBlock: 42 }],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 17, dataCodewordsPerBlock: 22 },
                    { numBlocks: 6, dataCodewordsPerBlock: 23 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 19, dataCodewordsPerBlock: 16 },
                    { numBlocks: 6, dataCodewordsPerBlock: 17 },
                ],
            },
        ],
    },
    {
        infoBits: 0x168C9,
        versionNumber: 22,
        alignmentPatternCenters: [6, 26, 50, 74, 98],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 2, dataCodewordsPerBlock: 111 },
                    { numBlocks: 7, dataCodewordsPerBlock: 112 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [{ numBlocks: 17, dataCodewordsPerBlock: 46 }],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 7, dataCodewordsPerBlock: 24 },
                    { numBlocks: 16, dataCodewordsPerBlock: 25 },
                ],
            },
            {
                ecCodewordsPerBlock: 24,
                ecBlocks: [{ numBlocks: 34, dataCodewordsPerBlock: 13 }],
            },
        ],
    },
    {
        infoBits: 0x177EC,
        versionNumber: 23,
        alignmentPatternCenters: [6, 30, 54, 74, 102],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 4, dataCodewordsPerBlock: 121 },
                    { numBlocks: 5, dataCodewordsPerBlock: 122 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 4, dataCodewordsPerBlock: 47 },
                    { numBlocks: 14, dataCodewordsPerBlock: 48 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 11, dataCodewordsPerBlock: 24 },
                    { numBlocks: 14, dataCodewordsPerBlock: 25 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 16, dataCodewordsPerBlock: 15 },
                    { numBlocks: 14, dataCodewordsPerBlock: 16 },
                ],
            },
        ],
    },
    {
        infoBits: 0x18EC4,
        versionNumber: 24,
        alignmentPatternCenters: [6, 28, 54, 80, 106],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 6, dataCodewordsPerBlock: 117 },
                    { numBlocks: 4, dataCodewordsPerBlock: 118 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 6, dataCodewordsPerBlock: 45 },
                    { numBlocks: 14, dataCodewordsPerBlock: 46 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 11, dataCodewordsPerBlock: 24 },
                    { numBlocks: 16, dataCodewordsPerBlock: 25 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 30, dataCodewordsPerBlock: 16 },
                    { numBlocks: 2, dataCodewordsPerBlock: 17 },
                ],
            },
        ],
    },
    {
        infoBits: 0x191E1,
        versionNumber: 25,
        alignmentPatternCenters: [6, 32, 58, 84, 110],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 26,
                ecBlocks: [
                    { numBlocks: 8, dataCodewordsPerBlock: 106 },
                    { numBlocks: 4, dataCodewordsPerBlock: 107 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 8, dataCodewordsPerBlock: 47 },
                    { numBlocks: 13, dataCodewordsPerBlock: 48 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 7, dataCodewordsPerBlock: 24 },
                    { numBlocks: 22, dataCodewordsPerBlock: 25 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 22, dataCodewordsPerBlock: 15 },
                    { numBlocks: 13, dataCodewordsPerBlock: 16 },
                ],
            },
        ],
    },
    {
        infoBits: 0x1AFAB,
        versionNumber: 26,
        alignmentPatternCenters: [6, 30, 58, 86, 114],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 10, dataCodewordsPerBlock: 114 },
                    { numBlocks: 2, dataCodewordsPerBlock: 115 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 19, dataCodewordsPerBlock: 46 },
                    { numBlocks: 4, dataCodewordsPerBlock: 47 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 28, dataCodewordsPerBlock: 22 },
                    { numBlocks: 6, dataCodewordsPerBlock: 23 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 33, dataCodewordsPerBlock: 16 },
                    { numBlocks: 4, dataCodewordsPerBlock: 17 },
                ],
            },
        ],
    },
    {
        infoBits: 0x1B08E,
        versionNumber: 27,
        alignmentPatternCenters: [6, 34, 62, 90, 118],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 8, dataCodewordsPerBlock: 122 },
                    { numBlocks: 4, dataCodewordsPerBlock: 123 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 22, dataCodewordsPerBlock: 45 },
                    { numBlocks: 3, dataCodewordsPerBlock: 46 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 8, dataCodewordsPerBlock: 23 },
                    { numBlocks: 26, dataCodewordsPerBlock: 24 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 12, dataCodewordsPerBlock: 15 },
                    { numBlocks: 28, dataCodewordsPerBlock: 16 },
                ],
            },
        ],
    },
    {
        infoBits: 0x1CC1A,
        versionNumber: 28,
        alignmentPatternCenters: [6, 26, 50, 74, 98, 122],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 3, dataCodewordsPerBlock: 117 },
                    { numBlocks: 10, dataCodewordsPerBlock: 118 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 3, dataCodewordsPerBlock: 45 },
                    { numBlocks: 23, dataCodewordsPerBlock: 46 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 4, dataCodewordsPerBlock: 24 },
                    { numBlocks: 31, dataCodewordsPerBlock: 25 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 11, dataCodewordsPerBlock: 15 },
                    { numBlocks: 31, dataCodewordsPerBlock: 16 },
                ],
            },
        ],
    },
    {
        infoBits: 0x1D33F,
        versionNumber: 29,
        alignmentPatternCenters: [6, 30, 54, 78, 102, 126],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 7, dataCodewordsPerBlock: 116 },
                    { numBlocks: 7, dataCodewordsPerBlock: 117 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 21, dataCodewordsPerBlock: 45 },
                    { numBlocks: 7, dataCodewordsPerBlock: 46 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 1, dataCodewordsPerBlock: 23 },
                    { numBlocks: 37, dataCodewordsPerBlock: 24 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 19, dataCodewordsPerBlock: 15 },
                    { numBlocks: 26, dataCodewordsPerBlock: 16 },
                ],
            },
        ],
    },
    {
        infoBits: 0x1ED75,
        versionNumber: 30,
        alignmentPatternCenters: [6, 26, 52, 78, 104, 130],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 5, dataCodewordsPerBlock: 115 },
                    { numBlocks: 10, dataCodewordsPerBlock: 116 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 19, dataCodewordsPerBlock: 47 },
                    { numBlocks: 10, dataCodewordsPerBlock: 48 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 15, dataCodewordsPerBlock: 24 },
                    { numBlocks: 25, dataCodewordsPerBlock: 25 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 23, dataCodewordsPerBlock: 15 },
                    { numBlocks: 25, dataCodewordsPerBlock: 16 },
                ],
            },
        ],
    },
    {
        infoBits: 0x1F250,
        versionNumber: 31,
        alignmentPatternCenters: [6, 30, 56, 82, 108, 134],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 13, dataCodewordsPerBlock: 115 },
                    { numBlocks: 3, dataCodewordsPerBlock: 116 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 2, dataCodewordsPerBlock: 46 },
                    { numBlocks: 29, dataCodewordsPerBlock: 47 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 42, dataCodewordsPerBlock: 24 },
                    { numBlocks: 1, dataCodewordsPerBlock: 25 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 23, dataCodewordsPerBlock: 15 },
                    { numBlocks: 28, dataCodewordsPerBlock: 16 },
                ],
            },
        ],
    },
    {
        infoBits: 0x209D5,
        versionNumber: 32,
        alignmentPatternCenters: [6, 34, 60, 86, 112, 138],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [{ numBlocks: 17, dataCodewordsPerBlock: 115 }],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 10, dataCodewordsPerBlock: 46 },
                    { numBlocks: 23, dataCodewordsPerBlock: 47 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 10, dataCodewordsPerBlock: 24 },
                    { numBlocks: 35, dataCodewordsPerBlock: 25 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 19, dataCodewordsPerBlock: 15 },
                    { numBlocks: 35, dataCodewordsPerBlock: 16 },
                ],
            },
        ],
    },
    {
        infoBits: 0x216F0,
        versionNumber: 33,
        alignmentPatternCenters: [6, 30, 58, 86, 114, 142],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 17, dataCodewordsPerBlock: 115 },
                    { numBlocks: 1, dataCodewordsPerBlock: 116 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 14, dataCodewordsPerBlock: 46 },
                    { numBlocks: 21, dataCodewordsPerBlock: 47 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 29, dataCodewordsPerBlock: 24 },
                    { numBlocks: 19, dataCodewordsPerBlock: 25 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 11, dataCodewordsPerBlock: 15 },
                    { numBlocks: 46, dataCodewordsPerBlock: 16 },
                ],
            },
        ],
    },
    {
        infoBits: 0x228BA,
        versionNumber: 34,
        alignmentPatternCenters: [6, 34, 62, 90, 118, 146],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 13, dataCodewordsPerBlock: 115 },
                    { numBlocks: 6, dataCodewordsPerBlock: 116 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 14, dataCodewordsPerBlock: 46 },
                    { numBlocks: 23, dataCodewordsPerBlock: 47 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 44, dataCodewordsPerBlock: 24 },
                    { numBlocks: 7, dataCodewordsPerBlock: 25 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 59, dataCodewordsPerBlock: 16 },
                    { numBlocks: 1, dataCodewordsPerBlock: 17 },
                ],
            },
        ],
    },
    {
        infoBits: 0x2379F,
        versionNumber: 35,
        alignmentPatternCenters: [6, 30, 54, 78, 102, 126, 150],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 12, dataCodewordsPerBlock: 121 },
                    { numBlocks: 7, dataCodewordsPerBlock: 122 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 12, dataCodewordsPerBlock: 47 },
                    { numBlocks: 26, dataCodewordsPerBlock: 48 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 39, dataCodewordsPerBlock: 24 },
                    { numBlocks: 14, dataCodewordsPerBlock: 25 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 22, dataCodewordsPerBlock: 15 },
                    { numBlocks: 41, dataCodewordsPerBlock: 16 },
                ],
            },
        ],
    },
    {
        infoBits: 0x24B0B,
        versionNumber: 36,
        alignmentPatternCenters: [6, 24, 50, 76, 102, 128, 154],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 6, dataCodewordsPerBlock: 121 },
                    { numBlocks: 14, dataCodewordsPerBlock: 122 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 6, dataCodewordsPerBlock: 47 },
                    { numBlocks: 34, dataCodewordsPerBlock: 48 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 46, dataCodewordsPerBlock: 24 },
                    { numBlocks: 10, dataCodewordsPerBlock: 25 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 2, dataCodewordsPerBlock: 15 },
                    { numBlocks: 64, dataCodewordsPerBlock: 16 },
                ],
            },
        ],
    },
    {
        infoBits: 0x2542E,
        versionNumber: 37,
        alignmentPatternCenters: [6, 28, 54, 80, 106, 132, 158],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 17, dataCodewordsPerBlock: 122 },
                    { numBlocks: 4, dataCodewordsPerBlock: 123 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 29, dataCodewordsPerBlock: 46 },
                    { numBlocks: 14, dataCodewordsPerBlock: 47 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 49, dataCodewordsPerBlock: 24 },
                    { numBlocks: 10, dataCodewordsPerBlock: 25 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 24, dataCodewordsPerBlock: 15 },
                    { numBlocks: 46, dataCodewordsPerBlock: 16 },
                ],
            },
        ],
    },
    {
        infoBits: 0x26A64,
        versionNumber: 38,
        alignmentPatternCenters: [6, 32, 58, 84, 110, 136, 162],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 4, dataCodewordsPerBlock: 122 },
                    { numBlocks: 18, dataCodewordsPerBlock: 123 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 13, dataCodewordsPerBlock: 46 },
                    { numBlocks: 32, dataCodewordsPerBlock: 47 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 48, dataCodewordsPerBlock: 24 },
                    { numBlocks: 14, dataCodewordsPerBlock: 25 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 42, dataCodewordsPerBlock: 15 },
                    { numBlocks: 32, dataCodewordsPerBlock: 16 },
                ],
            },
        ],
    },
    {
        infoBits: 0x27541,
        versionNumber: 39,
        alignmentPatternCenters: [6, 26, 54, 82, 110, 138, 166],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 20, dataCodewordsPerBlock: 117 },
                    { numBlocks: 4, dataCodewordsPerBlock: 118 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 40, dataCodewordsPerBlock: 47 },
                    { numBlocks: 7, dataCodewordsPerBlock: 48 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 43, dataCodewordsPerBlock: 24 },
                    { numBlocks: 22, dataCodewordsPerBlock: 25 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 10, dataCodewordsPerBlock: 15 },
                    { numBlocks: 67, dataCodewordsPerBlock: 16 },
                ],
            },
        ],
    },
    {
        infoBits: 0x28C69,
        versionNumber: 40,
        alignmentPatternCenters: [6, 30, 58, 86, 114, 142, 170],
        errorCorrectionLevels: [
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 19, dataCodewordsPerBlock: 118 },
                    { numBlocks: 6, dataCodewordsPerBlock: 119 },
                ],
            },
            {
                ecCodewordsPerBlock: 28,
                ecBlocks: [
                    { numBlocks: 18, dataCodewordsPerBlock: 47 },
                    { numBlocks: 31, dataCodewordsPerBlock: 48 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 34, dataCodewordsPerBlock: 24 },
                    { numBlocks: 34, dataCodewordsPerBlock: 25 },
                ],
            },
            {
                ecCodewordsPerBlock: 30,
                ecBlocks: [
                    { numBlocks: 20, dataCodewordsPerBlock: 15 },
                    { numBlocks: 61, dataCodewordsPerBlock: 16 },
                ],
            },
        ],
    },
];


/***/ }),
/* 11 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

Object.defineProperty(exports, "__esModule", { value: true });
var BitMatrix_1 = __webpack_require__(0);
function squareToQuadrilateral(p1, p2, p3, p4) {
    var dx3 = p1.x - p2.x + p3.x - p4.x;
    var dy3 = p1.y - p2.y + p3.y - p4.y;
    if (dx3 === 0 && dy3 === 0) { // Affine
        return {
            a11: p2.x - p1.x,
            a12: p2.y - p1.y,
            a13: 0,
            a21: p3.x - p2.x,
            a22: p3.y - p2.y,
            a23: 0,
            a31: p1.x,
            a32: p1.y,
            a33: 1,
        };
    }
    else {
        var dx1 = p2.x - p3.x;
        var dx2 = p4.x - p3.x;
        var dy1 = p2.y - p3.y;
        var dy2 = p4.y - p3.y;
        var denominator = dx1 * dy2 - dx2 * dy1;
        var a13 = (dx3 * dy2 - dx2 * dy3) / denominator;
        var a23 = (dx1 * dy3 - dx3 * dy1) / denominator;
        return {
            a11: p2.x - p1.x + a13 * p2.x,
            a12: p2.y - p1.y + a13 * p2.y,
            a13: a13,
            a21: p4.x - p1.x + a23 * p4.x,
            a22: p4.y - p1.y + a23 * p4.y,
            a23: a23,
            a31: p1.x,
            a32: p1.y,
            a33: 1,
        };
    }
}
function quadrilateralToSquare(p1, p2, p3, p4) {
    // Here, the adjoint serves as the inverse:
    var sToQ = squareToQuadrilateral(p1, p2, p3, p4);
    return {
        a11: sToQ.a22 * sToQ.a33 - sToQ.a23 * sToQ.a32,
        a12: sToQ.a13 * sToQ.a32 - sToQ.a12 * sToQ.a33,
        a13: sToQ.a12 * sToQ.a23 - sToQ.a13 * sToQ.a22,
        a21: sToQ.a23 * sToQ.a31 - sToQ.a21 * sToQ.a33,
        a22: sToQ.a11 * sToQ.a33 - sToQ.a13 * sToQ.a31,
        a23: sToQ.a13 * sToQ.a21 - sToQ.a11 * sToQ.a23,
        a31: sToQ.a21 * sToQ.a32 - sToQ.a22 * sToQ.a31,
        a32: sToQ.a12 * sToQ.a31 - sToQ.a11 * sToQ.a32,
        a33: sToQ.a11 * sToQ.a22 - sToQ.a12 * sToQ.a21,
    };
}
function times(a, b) {
    return {
        a11: a.a11 * b.a11 + a.a21 * b.a12 + a.a31 * b.a13,
        a12: a.a12 * b.a11 + a.a22 * b.a12 + a.a32 * b.a13,
        a13: a.a13 * b.a11 + a.a23 * b.a12 + a.a33 * b.a13,
        a21: a.a11 * b.a21 + a.a21 * b.a22 + a.a31 * b.a23,
        a22: a.a12 * b.a21 + a.a22 * b.a22 + a.a32 * b.a23,
        a23: a.a13 * b.a21 + a.a23 * b.a22 + a.a33 * b.a23,
        a31: a.a11 * b.a31 + a.a21 * b.a32 + a.a31 * b.a33,
        a32: a.a12 * b.a31 + a.a22 * b.a32 + a.a32 * b.a33,
        a33: a.a13 * b.a31 + a.a23 * b.a32 + a.a33 * b.a33,
    };
}
function extract(image, location) {
    var qToS = quadrilateralToSquare({ x: 3.5, y: 3.5 }, { x: location.dimension - 3.5, y: 3.5 }, { x: location.dimension - 6.5, y: location.dimension - 6.5 }, { x: 3.5, y: location.dimension - 3.5 });
    var sToQ = squareToQuadrilateral(location.topLeft, location.topRight, location.alignmentPattern, location.bottomLeft);
    var transform = times(sToQ, qToS);
    var matrix = BitMatrix_1.BitMatrix.createEmpty(location.dimension, location.dimension);
    var mappingFunction = function (x, y) {
        var denominator = transform.a13 * x + transform.a23 * y + transform.a33;
        return {
            x: (transform.a11 * x + transform.a21 * y + transform.a31) / denominator,
            y: (transform.a12 * x + transform.a22 * y + transform.a32) / denominator,
        };
    };
    for (var y = 0; y < location.dimension; y++) {
        for (var x = 0; x < location.dimension; x++) {
            var xValue = x + 0.5;
            var yValue = y + 0.5;
            var sourcePixel = mappingFunction(xValue, yValue);
            matrix.set(x, y, image.get(Math.floor(sourcePixel.x), Math.floor(sourcePixel.y)));
        }
    }
    return {
        matrix: matrix,
        mappingFunction: mappingFunction,
    };
}
exports.extract = extract;


/***/ }),
/* 12 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

Object.defineProperty(exports, "__esModule", { value: true });
var MAX_FINDERPATTERNS_TO_SEARCH = 4;
var MIN_QUAD_RATIO = 0.5;
var MAX_QUAD_RATIO = 1.5;
var distance = function (a, b) { return Math.sqrt(Math.pow((b.x - a.x), 2) + Math.pow((b.y - a.y), 2)); };
function sum(values) {
    return values.reduce(function (a, b) { return a + b; });
}
// Takes three finder patterns and organizes them into topLeft, topRight, etc
function reorderFinderPatterns(pattern1, pattern2, pattern3) {
    var _a, _b, _c, _d;
    // Find distances between pattern centers
    var oneTwoDistance = distance(pattern1, pattern2);
    var twoThreeDistance = distance(pattern2, pattern3);
    var oneThreeDistance = distance(pattern1, pattern3);
    var bottomLeft;
    var topLeft;
    var topRight;
    // Assume one closest to other two is B; A and C will just be guesses at first
    if (twoThreeDistance >= oneTwoDistance && twoThreeDistance >= oneThreeDistance) {
        _a = [pattern2, pattern1, pattern3], bottomLeft = _a[0], topLeft = _a[1], topRight = _a[2];
    }
    else if (oneThreeDistance >= twoThreeDistance && oneThreeDistance >= oneTwoDistance) {
        _b = [pattern1, pattern2, pattern3], bottomLeft = _b[0], topLeft = _b[1], topRight = _b[2];
    }
    else {
        _c = [pattern1, pattern3, pattern2], bottomLeft = _c[0], topLeft = _c[1], topRight = _c[2];
    }
    // Use cross product to figure out whether bottomLeft (A) and topRight (C) are correct or flipped in relation to topLeft (B)
    // This asks whether BC x BA has a positive z component, which is the arrangement we want. If it's negative, then
    // we've got it flipped around and should swap topRight and bottomLeft.
    if (((topRight.x - topLeft.x) * (bottomLeft.y - topLeft.y)) - ((topRight.y - topLeft.y) * (bottomLeft.x - topLeft.x)) < 0) {
        _d = [topRight, bottomLeft], bottomLeft = _d[0], topRight = _d[1];
    }
    return { bottomLeft: bottomLeft, topLeft: topLeft, topRight: topRight };
}
// Computes the dimension (number of modules on a side) of the QR Code based on the position of the finder patterns
function computeDimension(topLeft, topRight, bottomLeft, matrix) {
    var moduleSize = (sum(countBlackWhiteRun(topLeft, bottomLeft, matrix, 5)) / 7 + // Divide by 7 since the ratio is 1:1:3:1:1
        sum(countBlackWhiteRun(topLeft, topRight, matrix, 5)) / 7 +
        sum(countBlackWhiteRun(bottomLeft, topLeft, matrix, 5)) / 7 +
        sum(countBlackWhiteRun(topRight, topLeft, matrix, 5)) / 7) / 4;
    if (moduleSize < 1) {
        throw new Error("Invalid module size");
    }
    var topDimension = Math.round(distance(topLeft, topRight) / moduleSize);
    var sideDimension = Math.round(distance(topLeft, bottomLeft) / moduleSize);
    var dimension = Math.floor((topDimension + sideDimension) / 2) + 7;
    switch (dimension % 4) {
        case 0:
            dimension++;
            break;
        case 2:
            dimension--;
            break;
    }
    return { dimension: dimension, moduleSize: moduleSize };
}
// Takes an origin point and an end point and counts the sizes of the black white run from the origin towards the end point.
// Returns an array of elements, representing the pixel size of the black white run.
// Uses a variant of http://en.wikipedia.org/wiki/Bresenham's_line_algorithm
function countBlackWhiteRunTowardsPoint(origin, end, matrix, length) {
    var switchPoints = [{ x: Math.floor(origin.x), y: Math.floor(origin.y) }];
    var steep = Math.abs(end.y - origin.y) > Math.abs(end.x - origin.x);
    var fromX;
    var fromY;
    var toX;
    var toY;
    if (steep) {
        fromX = Math.floor(origin.y);
        fromY = Math.floor(origin.x);
        toX = Math.floor(end.y);
        toY = Math.floor(end.x);
    }
    else {
        fromX = Math.floor(origin.x);
        fromY = Math.floor(origin.y);
        toX = Math.floor(end.x);
        toY = Math.floor(end.y);
    }
    var dx = Math.abs(toX - fromX);
    var dy = Math.abs(toY - fromY);
    var error = Math.floor(-dx / 2);
    var xStep = fromX < toX ? 1 : -1;
    var yStep = fromY < toY ? 1 : -1;
    var currentPixel = true;
    // Loop up until x == toX, but not beyond
    for (var x = fromX, y = fromY; x !== toX + xStep; x += xStep) {
        // Does current pixel mean we have moved white to black or vice versa?
        // Scanning black in state 0,2 and white in state 1, so if we find the wrong
        // color, advance to next state or end if we are in state 2 already
        var realX = steep ? y : x;
        var realY = steep ? x : y;
        if (matrix.get(realX, realY) !== currentPixel) {
            currentPixel = !currentPixel;
            switchPoints.push({ x: realX, y: realY });
            if (switchPoints.length === length + 1) {
                break;
            }
        }
        error += dy;
        if (error > 0) {
            if (y === toY) {
                break;
            }
            y += yStep;
            error -= dx;
        }
    }
    var distances = [];
    for (var i = 0; i < length; i++) {
        if (switchPoints[i] && switchPoints[i + 1]) {
            distances.push(distance(switchPoints[i], switchPoints[i + 1]));
        }
        else {
            distances.push(0);
        }
    }
    return distances;
}
// Takes an origin point and an end point and counts the sizes of the black white run in the origin point
// along the line that intersects with the end point. Returns an array of elements, representing the pixel sizes
// of the black white run. Takes a length which represents the number of switches from black to white to look for.
function countBlackWhiteRun(origin, end, matrix, length) {
    var _a;
    var rise = end.y - origin.y;
    var run = end.x - origin.x;
    var towardsEnd = countBlackWhiteRunTowardsPoint(origin, end, matrix, Math.ceil(length / 2));
    var awayFromEnd = countBlackWhiteRunTowardsPoint(origin, { x: origin.x - run, y: origin.y - rise }, matrix, Math.ceil(length / 2));
    var middleValue = towardsEnd.shift() + awayFromEnd.shift() - 1; // Substract one so we don't double count a pixel
    return (_a = awayFromEnd.concat(middleValue)).concat.apply(_a, towardsEnd);
}
// Takes in a black white run and an array of expected ratios. Returns the average size of the run as well as the "error" -
// that is the amount the run diverges from the expected ratio
function scoreBlackWhiteRun(sequence, ratios) {
    var averageSize = sum(sequence) / sum(ratios);
    var error = 0;
    ratios.forEach(function (ratio, i) {
        error += Math.pow((sequence[i] - ratio * averageSize), 2);
    });
    return { averageSize: averageSize, error: error };
}
// Takes an X,Y point and an array of sizes and scores the point against those ratios.
// For example for a finder pattern takes the ratio list of 1:1:3:1:1 and checks horizontal, vertical and diagonal ratios
// against that.
function scorePattern(point, ratios, matrix) {
    try {
        var horizontalRun = countBlackWhiteRun(point, { x: -1, y: point.y }, matrix, ratios.length);
        var verticalRun = countBlackWhiteRun(point, { x: point.x, y: -1 }, matrix, ratios.length);
        var topLeftPoint = {
            x: Math.max(0, point.x - point.y) - 1,
            y: Math.max(0, point.y - point.x) - 1,
        };
        var topLeftBottomRightRun = countBlackWhiteRun(point, topLeftPoint, matrix, ratios.length);
        var bottomLeftPoint = {
            x: Math.min(matrix.width, point.x + point.y) + 1,
            y: Math.min(matrix.height, point.y + point.x) + 1,
        };
        var bottomLeftTopRightRun = countBlackWhiteRun(point, bottomLeftPoint, matrix, ratios.length);
        var horzError = scoreBlackWhiteRun(horizontalRun, ratios);
        var vertError = scoreBlackWhiteRun(verticalRun, ratios);
        var diagDownError = scoreBlackWhiteRun(topLeftBottomRightRun, ratios);
        var diagUpError = scoreBlackWhiteRun(bottomLeftTopRightRun, ratios);
        var ratioError = Math.sqrt(horzError.error * horzError.error +
            vertError.error * vertError.error +
            diagDownError.error * diagDownError.error +
            diagUpError.error * diagUpError.error);
        var avgSize = (horzError.averageSize + vertError.averageSize + diagDownError.averageSize + diagUpError.averageSize) / 4;
        var sizeError = (Math.pow((horzError.averageSize - avgSize), 2) +
            Math.pow((vertError.averageSize - avgSize), 2) +
            Math.pow((diagDownError.averageSize - avgSize), 2) +
            Math.pow((diagUpError.averageSize - avgSize), 2)) / avgSize;
        return ratioError + sizeError;
    }
    catch (_a) {
        return Infinity;
    }
}
function recenterLocation(matrix, p) {
    var leftX = Math.round(p.x);
    while (matrix.get(leftX, Math.round(p.y))) {
        leftX--;
    }
    var rightX = Math.round(p.x);
    while (matrix.get(rightX, Math.round(p.y))) {
        rightX++;
    }
    var x = (leftX + rightX) / 2;
    var topY = Math.round(p.y);
    while (matrix.get(Math.round(x), topY)) {
        topY--;
    }
    var bottomY = Math.round(p.y);
    while (matrix.get(Math.round(x), bottomY)) {
        bottomY++;
    }
    var y = (topY + bottomY) / 2;
    return { x: x, y: y };
}
function locate(matrix) {
    var finderPatternQuads = [];
    var activeFinderPatternQuads = [];
    var alignmentPatternQuads = [];
    var activeAlignmentPatternQuads = [];
    var _loop_1 = function (y) {
        var length_1 = 0;
        var lastBit = false;
        var scans = [0, 0, 0, 0, 0];
        var _loop_2 = function (x) {
            var v = matrix.get(x, y);
            if (v === lastBit) {
                length_1++;
            }
            else {
                scans = [scans[1], scans[2], scans[3], scans[4], length_1];
                length_1 = 1;
                lastBit = v;
                // Do the last 5 color changes ~ match the expected ratio for a finder pattern? 1:1:3:1:1 of b:w:b:w:b
                var averageFinderPatternBlocksize = sum(scans) / 7;
                var validFinderPattern = Math.abs(scans[0] - averageFinderPatternBlocksize) < averageFinderPatternBlocksize &&
                    Math.abs(scans[1] - averageFinderPatternBlocksize) < averageFinderPatternBlocksize &&
                    Math.abs(scans[2] - 3 * averageFinderPatternBlocksize) < 3 * averageFinderPatternBlocksize &&
                    Math.abs(scans[3] - averageFinderPatternBlocksize) < averageFinderPatternBlocksize &&
                    Math.abs(scans[4] - averageFinderPatternBlocksize) < averageFinderPatternBlocksize &&
                    !v; // And make sure the current pixel is white since finder patterns are bordered in white
                // Do the last 3 color changes ~ match the expected ratio for an alignment pattern? 1:1:1 of w:b:w
                var averageAlignmentPatternBlocksize = sum(scans.slice(-3)) / 3;
                var validAlignmentPattern = Math.abs(scans[2] - averageAlignmentPatternBlocksize) < averageAlignmentPatternBlocksize &&
                    Math.abs(scans[3] - averageAlignmentPatternBlocksize) < averageAlignmentPatternBlocksize &&
                    Math.abs(scans[4] - averageAlignmentPatternBlocksize) < averageAlignmentPatternBlocksize &&
                    v; // Is the current pixel black since alignment patterns are bordered in black
                if (validFinderPattern) {
                    // Compute the start and end x values of the large center black square
                    var endX_1 = x - scans[3] - scans[4];
                    var startX_1 = endX_1 - scans[2];
                    var line = { startX: startX_1, endX: endX_1, y: y };
                    // Is there a quad directly above the current spot? If so, extend it with the new line. Otherwise, create a new quad with
                    // that line as the starting point.
                    var matchingQuads = activeFinderPatternQuads.filter(function (q) {
                        return (startX_1 >= q.bottom.startX && startX_1 <= q.bottom.endX) ||
                            (endX_1 >= q.bottom.startX && startX_1 <= q.bottom.endX) ||
                            (startX_1 <= q.bottom.startX && endX_1 >= q.bottom.endX && ((scans[2] / (q.bottom.endX - q.bottom.startX)) < MAX_QUAD_RATIO &&
                                (scans[2] / (q.bottom.endX - q.bottom.startX)) > MIN_QUAD_RATIO));
                    });
                    if (matchingQuads.length > 0) {
                        matchingQuads[0].bottom = line;
                    }
                    else {
                        activeFinderPatternQuads.push({ top: line, bottom: line });
                    }
                }
                if (validAlignmentPattern) {
                    // Compute the start and end x values of the center black square
                    var endX_2 = x - scans[4];
                    var startX_2 = endX_2 - scans[3];
                    var line = { startX: startX_2, y: y, endX: endX_2 };
                    // Is there a quad directly above the current spot? If so, extend it with the new line. Otherwise, create a new quad with
                    // that line as the starting point.
                    var matchingQuads = activeAlignmentPatternQuads.filter(function (q) {
                        return (startX_2 >= q.bottom.startX && startX_2 <= q.bottom.endX) ||
                            (endX_2 >= q.bottom.startX && startX_2 <= q.bottom.endX) ||
                            (startX_2 <= q.bottom.startX && endX_2 >= q.bottom.endX && ((scans[2] / (q.bottom.endX - q.bottom.startX)) < MAX_QUAD_RATIO &&
                                (scans[2] / (q.bottom.endX - q.bottom.startX)) > MIN_QUAD_RATIO));
                    });
                    if (matchingQuads.length > 0) {
                        matchingQuads[0].bottom = line;
                    }
                    else {
                        activeAlignmentPatternQuads.push({ top: line, bottom: line });
                    }
                }
            }
        };
        for (var x = -1; x <= matrix.width; x++) {
            _loop_2(x);
        }
        finderPatternQuads.push.apply(finderPatternQuads, activeFinderPatternQuads.filter(function (q) { return q.bottom.y !== y && q.bottom.y - q.top.y >= 2; }));
        activeFinderPatternQuads = activeFinderPatternQuads.filter(function (q) { return q.bottom.y === y; });
        alignmentPatternQuads.push.apply(alignmentPatternQuads, activeAlignmentPatternQuads.filter(function (q) { return q.bottom.y !== y; }));
        activeAlignmentPatternQuads = activeAlignmentPatternQuads.filter(function (q) { return q.bottom.y === y; });
    };
    for (var y = 0; y <= matrix.height; y++) {
        _loop_1(y);
    }
    finderPatternQuads.push.apply(finderPatternQuads, activeFinderPatternQuads.filter(function (q) { return q.bottom.y - q.top.y >= 2; }));
    alignmentPatternQuads.push.apply(alignmentPatternQuads, activeAlignmentPatternQuads);
    var finderPatternGroups = finderPatternQuads
        .filter(function (q) { return q.bottom.y - q.top.y >= 2; }) // All quads must be at least 2px tall since the center square is larger than a block
        .map(function (q) {
        var x = (q.top.startX + q.top.endX + q.bottom.startX + q.bottom.endX) / 4;
        var y = (q.top.y + q.bottom.y + 1) / 2;
        if (!matrix.get(Math.round(x), Math.round(y))) {
            return;
        }
        var lengths = [q.top.endX - q.top.startX, q.bottom.endX - q.bottom.startX, q.bottom.y - q.top.y + 1];
        var size = sum(lengths) / lengths.length;
        var score = scorePattern({ x: Math.round(x), y: Math.round(y) }, [1, 1, 3, 1, 1], matrix);
        return { score: score, x: x, y: y, size: size };
    })
        .filter(function (q) { return !!q; }) // Filter out any rejected quads from above
        .sort(function (a, b) { return a.score - b.score; })
        // Now take the top finder pattern options and try to find 2 other options with a similar size.
        .map(function (point, i, finderPatterns) {
        if (i > MAX_FINDERPATTERNS_TO_SEARCH) {
            return null;
        }
        var otherPoints = finderPatterns
            .filter(function (p, ii) { return i !== ii; })
            .map(function (p) { return ({ x: p.x, y: p.y, score: p.score + (Math.pow((p.size - point.size), 2)) / point.size, size: p.size }); })
            .sort(function (a, b) { return a.score - b.score; });
        if (otherPoints.length < 2) {
            return null;
        }
        var score = point.score + otherPoints[0].score + otherPoints[1].score;
        return { points: [point].concat(otherPoints.slice(0, 2)), score: score };
    })
        .filter(function (q) { return !!q; }) // Filter out any rejected finder patterns from above
        .sort(function (a, b) { return a.score - b.score; });
    if (finderPatternGroups.length === 0) {
        return null;
    }
    var _a = reorderFinderPatterns(finderPatternGroups[0].points[0], finderPatternGroups[0].points[1], finderPatternGroups[0].points[2]), topRight = _a.topRight, topLeft = _a.topLeft, bottomLeft = _a.bottomLeft;
    var alignment = findAlignmentPattern(matrix, alignmentPatternQuads, topRight, topLeft, bottomLeft);
    var result = [];
    if (alignment) {
        result.push({
            alignmentPattern: { x: alignment.alignmentPattern.x, y: alignment.alignmentPattern.y },
            bottomLeft: { x: bottomLeft.x, y: bottomLeft.y },
            dimension: alignment.dimension,
            topLeft: { x: topLeft.x, y: topLeft.y },
            topRight: { x: topRight.x, y: topRight.y },
        });
    }
    // We normally use the center of the quads as the location of the tracking points, which is optimal for most cases and will account
    // for a skew in the image. However, In some cases, a slight skew might not be real and instead be caused by image compression
    // errors and/or low resolution. For those cases, we'd be better off centering the point exactly in the middle of the black area. We
    // compute and return the location data for the naively centered points as it is little additional work and allows for multiple
    // attempts at decoding harder images.
    var midTopRight = recenterLocation(matrix, topRight);
    var midTopLeft = recenterLocation(matrix, topLeft);
    var midBottomLeft = recenterLocation(matrix, bottomLeft);
    var centeredAlignment = findAlignmentPattern(matrix, alignmentPatternQuads, midTopRight, midTopLeft, midBottomLeft);
    if (centeredAlignment) {
        result.push({
            alignmentPattern: { x: centeredAlignment.alignmentPattern.x, y: centeredAlignment.alignmentPattern.y },
            bottomLeft: { x: midBottomLeft.x, y: midBottomLeft.y },
            topLeft: { x: midTopLeft.x, y: midTopLeft.y },
            topRight: { x: midTopRight.x, y: midTopRight.y },
            dimension: centeredAlignment.dimension,
        });
    }
    if (result.length === 0) {
        return null;
    }
    return result;
}
exports.locate = locate;
function findAlignmentPattern(matrix, alignmentPatternQuads, topRight, topLeft, bottomLeft) {
    var _a;
    // Now that we've found the three finder patterns we can determine the blockSize and the size of the QR code.
    // We'll use these to help find the alignment pattern but also later when we do the extraction.
    var dimension;
    var moduleSize;
    try {
        (_a = computeDimension(topLeft, topRight, bottomLeft, matrix), dimension = _a.dimension, moduleSize = _a.moduleSize);
    }
    catch (e) {
        return null;
    }
    // Now find the alignment pattern
    var bottomRightFinderPattern = {
        x: topRight.x - topLeft.x + bottomLeft.x,
        y: topRight.y - topLeft.y + bottomLeft.y,
    };
    var modulesBetweenFinderPatterns = ((distance(topLeft, bottomLeft) + distance(topLeft, topRight)) / 2 / moduleSize);
    var correctionToTopLeft = 1 - (3 / modulesBetweenFinderPatterns);
    var expectedAlignmentPattern = {
        x: topLeft.x + correctionToTopLeft * (bottomRightFinderPattern.x - topLeft.x),
        y: topLeft.y + correctionToTopLeft * (bottomRightFinderPattern.y - topLeft.y),
    };
    var alignmentPatterns = alignmentPatternQuads
        .map(function (q) {
        var x = (q.top.startX + q.top.endX + q.bottom.startX + q.bottom.endX) / 4;
        var y = (q.top.y + q.bottom.y + 1) / 2;
        if (!matrix.get(Math.floor(x), Math.floor(y))) {
            return;
        }
        var lengths = [q.top.endX - q.top.startX, q.bottom.endX - q.bottom.startX, (q.bottom.y - q.top.y + 1)];
        var size = sum(lengths) / lengths.length;
        var sizeScore = scorePattern({ x: Math.floor(x), y: Math.floor(y) }, [1, 1, 1], matrix);
        var score = sizeScore + distance({ x: x, y: y }, expectedAlignmentPattern);
        return { x: x, y: y, score: score };
    })
        .filter(function (v) { return !!v; })
        .sort(function (a, b) { return a.score - b.score; });
    // If there are less than 15 modules between finder patterns it's a version 1 QR code and as such has no alignmemnt pattern
    // so we can only use our best guess.
    var alignmentPattern = modulesBetweenFinderPatterns >= 15 && alignmentPatterns.length ? alignmentPatterns[0] : expectedAlignmentPattern;
    return { alignmentPattern: alignmentPattern, dimension: dimension };
}


/***/ })
/******/ ])["default"];
});
/*! @preserve
 * numeral.js
 * version : 2.0.6
 * author : Adam Draper
 * license : MIT
 * http://adamwdraper.github.com/Numeral-js/
 */

(function (global, factory) {
    if (typeof define === 'function' && define.amd) {
        define(factory);
    } else if (typeof module === 'object' && module.exports) {
        module.exports = factory();
    } else {
        global.numeral = factory();
    }
}(this, function () {
    /************************************
        Variables
    ************************************/

    var numeral,
        _,
        VERSION = '2.0.6',
        formats = {},
        locales = {},
        defaults = {
            currentLocale: 'en',
            zeroFormat: null,
            nullFormat: null,
            defaultFormat: '0,0',
            scalePercentBy100: true
        },
        options = {
            currentLocale: defaults.currentLocale,
            zeroFormat: defaults.zeroFormat,
            nullFormat: defaults.nullFormat,
            defaultFormat: defaults.defaultFormat,
            scalePercentBy100: defaults.scalePercentBy100
        };


    /************************************
        Constructors
    ************************************/

    // Numeral prototype object
    function Numeral(input, number) {
        this._input = input;

        this._value = number;
    }

    numeral = function(input) {
        var value,
            kind,
            unformatFunction,
            regexp;

        if (numeral.isNumeral(input)) {
            value = input.value();
        } else if (input === 0 || typeof input === 'undefined') {
            value = 0;
        } else if (input === null || _.isNaN(input)) {
            value = null;
        } else if (typeof input === 'string') {
            if (options.zeroFormat && input === options.zeroFormat) {
                value = 0;
            } else if (options.nullFormat && input === options.nullFormat || !input.replace(/[^0-9]+/g, '').length) {
                value = null;
            } else {
                for (kind in formats) {
                    regexp = typeof formats[kind].regexps.unformat === 'function' ? formats[kind].regexps.unformat() : formats[kind].regexps.unformat;

                    if (regexp && input.match(regexp)) {
                        unformatFunction = formats[kind].unformat;

                        break;
                    }
                }

                unformatFunction = unformatFunction || numeral._.stringToNumber;

                value = unformatFunction(input);
            }
        } else {
            value = Number(input)|| null;
        }

        return new Numeral(input, value);
    };

    // version number
    numeral.version = VERSION;

    // compare numeral object
    numeral.isNumeral = function(obj) {
        return obj instanceof Numeral;
    };

    // helper functions
    numeral._ = _ = {
        // formats numbers separators, decimals places, signs, abbreviations
        numberToFormat: function(value, format, roundingFunction) {
            var locale = locales[numeral.options.currentLocale],
                negP = false,
                optDec = false,
                leadingCount = 0,
                abbr = '',
                trillion = 1000000000000,
                billion = 1000000000,
                million = 1000000,
                thousand = 1000,
                decimal = '',
                neg = false,
                abbrForce, // force abbreviation
                abs,
                min,
                max,
                power,
                int,
                precision,
                signed,
                thousands,
                output;

            // make sure we never format a null value
            value = value || 0;

            abs = Math.abs(value);

            // see if we should use parentheses for negative number or if we should prefix with a sign
            // if both are present we default to parentheses
            if (numeral._.includes(format, '(')) {
                negP = true;
                format = format.replace(/[\(|\)]/g, '');
            } else if (numeral._.includes(format, '+') || numeral._.includes(format, '-')) {
                signed = numeral._.includes(format, '+') ? format.indexOf('+') : value < 0 ? format.indexOf('-') : -1;
                format = format.replace(/[\+|\-]/g, '');
            }

            // see if abbreviation is wanted
            if (numeral._.includes(format, 'a')) {
                abbrForce = format.match(/a(k|m|b|t)?/);

                abbrForce = abbrForce ? abbrForce[1] : false;

                // check for space before abbreviation
                if (numeral._.includes(format, ' a')) {
                    abbr = ' ';
                }

                format = format.replace(new RegExp(abbr + 'a[kmbt]?'), '');

                if (abs >= trillion && !abbrForce || abbrForce === 't') {
                    // trillion
                    abbr += locale.abbreviations.trillion;
                    value = value / trillion;
                } else if (abs < trillion && abs >= billion && !abbrForce || abbrForce === 'b') {
                    // billion
                    abbr += locale.abbreviations.billion;
                    value = value / billion;
                } else if (abs < billion && abs >= million && !abbrForce || abbrForce === 'm') {
                    // million
                    abbr += locale.abbreviations.million;
                    value = value / million;
                } else if (abs < million && abs >= thousand && !abbrForce || abbrForce === 'k') {
                    // thousand
                    abbr += locale.abbreviations.thousand;
                    value = value / thousand;
                }
            }

            // check for optional decimals
            if (numeral._.includes(format, '[.]')) {
                optDec = true;
                format = format.replace('[.]', '.');
            }

            // break number and format
            int = value.toString().split('.')[0];
            precision = format.split('.')[1];
            thousands = format.indexOf(',');
            leadingCount = (format.split('.')[0].split(',')[0].match(/0/g) || []).length;

            if (precision) {
                if (numeral._.includes(precision, '[')) {
                    precision = precision.replace(']', '');
                    precision = precision.split('[');
                    decimal = numeral._.toFixed(value, (precision[0].length + precision[1].length), roundingFunction, precision[1].length);
                } else {
                    decimal = numeral._.toFixed(value, precision.length, roundingFunction);
                }

                int = decimal.split('.')[0];

                if (numeral._.includes(decimal, '.')) {
                    decimal = locale.delimiters.decimal + decimal.split('.')[1];
                } else {
                    decimal = '';
                }

                if (optDec && Number(decimal.slice(1)) === 0) {
                    decimal = '';
                }
            } else {
                int = numeral._.toFixed(value, 0, roundingFunction);
            }

            // check abbreviation again after rounding
            if (abbr && !abbrForce && Number(int) >= 1000 && abbr !== locale.abbreviations.trillion) {
                int = String(Number(int) / 1000);

                switch (abbr) {
                    case locale.abbreviations.thousand:
                        abbr = locale.abbreviations.million;
                        break;
                    case locale.abbreviations.million:
                        abbr = locale.abbreviations.billion;
                        break;
                    case locale.abbreviations.billion:
                        abbr = locale.abbreviations.trillion;
                        break;
                }
            }


            // format number
            if (numeral._.includes(int, '-')) {
                int = int.slice(1);
                neg = true;
            }

            if (int.length < leadingCount) {
                for (var i = leadingCount - int.length; i > 0; i--) {
                    int = '0' + int;
                }
            }

            if (thousands > -1) {
                int = int.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1' + locale.delimiters.thousands);
            }

            if (format.indexOf('.') === 0) {
                int = '';
            }

            output = int + decimal + (abbr ? abbr : '');

            if (negP) {
                output = (negP && neg ? '(' : '') + output + (negP && neg ? ')' : '');
            } else {
                if (signed >= 0) {
                    output = signed === 0 ? (neg ? '-' : '+') + output : output + (neg ? '-' : '+');
                } else if (neg) {
                    output = '-' + output;
                }
            }

            return output;
        },
        // unformats numbers separators, decimals places, signs, abbreviations
        stringToNumber: function(string) {
            var locale = locales[options.currentLocale],
                stringOriginal = string,
                abbreviations = {
                    thousand: 3,
                    million: 6,
                    billion: 9,
                    trillion: 12
                },
                abbreviation,
                value,
                i,
                regexp;

            if (options.zeroFormat && string === options.zeroFormat) {
                value = 0;
            } else if (options.nullFormat && string === options.nullFormat || !string.replace(/[^0-9]+/g, '').length) {
                value = null;
            } else {
                value = 1;

                if (locale.delimiters.decimal !== '.') {
                    string = string.replace(/\./g, '').replace(locale.delimiters.decimal, '.');
                }

                for (abbreviation in abbreviations) {
                    regexp = new RegExp('[^a-zA-Z]' + locale.abbreviations[abbreviation] + '(?:\\)|(\\' + locale.currency.symbol + ')?(?:\\))?)?$');

                    if (stringOriginal.match(regexp)) {
                        value *= Math.pow(10, abbreviations[abbreviation]);
                        break;
                    }
                }

                // check for negative number
                value *= (string.split('-').length + Math.min(string.split('(').length - 1, string.split(')').length - 1)) % 2 ? 1 : -1;

                // remove non numbers
                string = string.replace(/[^0-9\.]+/g, '');

                value *= Number(string);
            }

            return value;
        },
        isNaN: function(value) {
            return typeof value === 'number' && isNaN(value);
        },
        includes: function(string, search) {
            return string.indexOf(search) !== -1;
        },
        insert: function(string, subString, start) {
            return string.slice(0, start) + subString + string.slice(start);
        },
        reduce: function(array, callback /*, initialValue*/) {
            if (this === null) {
                throw new TypeError('Array.prototype.reduce called on null or undefined');
            }

            if (typeof callback !== 'function') {
                throw new TypeError(callback + ' is not a function');
            }

            var t = Object(array),
                len = t.length >>> 0,
                k = 0,
                value;

            if (arguments.length === 3) {
                value = arguments[2];
            } else {
                while (k < len && !(k in t)) {
                    k++;
                }

                if (k >= len) {
                    throw new TypeError('Reduce of empty array with no initial value');
                }

                value = t[k++];
            }
            for (; k < len; k++) {
                if (k in t) {
                    value = callback(value, t[k], k, t);
                }
            }
            return value;
        },
        /**
         * Computes the multiplier necessary to make x >= 1,
         * effectively eliminating miscalculations caused by
         * finite precision.
         */
        multiplier: function (x) {
            var parts = x.toString().split('.');

            return parts.length < 2 ? 1 : Math.pow(10, parts[1].length);
        },
        /**
         * Given a variable number of arguments, returns the maximum
         * multiplier that must be used to normalize an operation involving
         * all of them.
         */
        correctionFactor: function () {
            var args = Array.prototype.slice.call(arguments);

            return args.reduce(function(accum, next) {
                var mn = _.multiplier(next);
                return accum > mn ? accum : mn;
            }, 1);
        },
        /**
         * Implementation of toFixed() that treats floats more like decimals
         *
         * Fixes binary rounding issues (eg. (0.615).toFixed(2) === '0.61') that present
         * problems for accounting- and finance-related software.
         */
        toFixed: function(value, maxDecimals, roundingFunction, optionals) {
            var splitValue = value.toString().split('.'),
                minDecimals = maxDecimals - (optionals || 0),
                boundedPrecision,
                optionalsRegExp,
                power,
                output;

            // Use the smallest precision value possible to avoid errors from floating point representation
            if (splitValue.length === 2) {
              boundedPrecision = Math.min(Math.max(splitValue[1].length, minDecimals), maxDecimals);
            } else {
              boundedPrecision = minDecimals;
            }

            power = Math.pow(10, boundedPrecision);

            // Multiply up by precision, round accurately, then divide and use native toFixed():
            output = (roundingFunction(value + 'e+' + boundedPrecision) / power).toFixed(boundedPrecision);

            if (optionals > maxDecimals - boundedPrecision) {
                optionalsRegExp = new RegExp('\\.?0{1,' + (optionals - (maxDecimals - boundedPrecision)) + '}$');
                output = output.replace(optionalsRegExp, '');
            }

            return output;
        }
    };

    // avaliable options
    numeral.options = options;

    // avaliable formats
    numeral.formats = formats;

    // avaliable formats
    numeral.locales = locales;

    // This function sets the current locale.  If
    // no arguments are passed in, it will simply return the current global
    // locale key.
    numeral.locale = function(key) {
        if (key) {
            options.currentLocale = key.toLowerCase();
        }

        return options.currentLocale;
    };

    // This function provides access to the loaded locale data.  If
    // no arguments are passed in, it will simply return the current
    // global locale object.
    numeral.localeData = function(key) {
        if (!key) {
            return locales[options.currentLocale];
        }

        key = key.toLowerCase();

        if (!locales[key]) {
            throw new Error('Unknown locale : ' + key);
        }

        return locales[key];
    };

    numeral.reset = function() {
        for (var property in defaults) {
            options[property] = defaults[property];
        }
    };

    numeral.zeroFormat = function(format) {
        options.zeroFormat = typeof(format) === 'string' ? format : null;
    };

    numeral.nullFormat = function (format) {
        options.nullFormat = typeof(format) === 'string' ? format : null;
    };

    numeral.defaultFormat = function(format) {
        options.defaultFormat = typeof(format) === 'string' ? format : '0.0';
    };

    numeral.register = function(type, name, format) {
        name = name.toLowerCase();

        if (this[type + 's'][name]) {
            throw new TypeError(name + ' ' + type + ' already registered.');
        }

        this[type + 's'][name] = format;

        return format;
    };


    numeral.validate = function(val, culture) {
        var _decimalSep,
            _thousandSep,
            _currSymbol,
            _valArray,
            _abbrObj,
            _thousandRegEx,
            localeData,
            temp;

        //coerce val to string
        if (typeof val !== 'string') {
            val += '';

            if (console.warn) {
                console.warn('Numeral.js: Value is not string. It has been co-erced to: ', val);
            }
        }

        //trim whitespaces from either sides
        val = val.trim();

        //if val is just digits return true
        if (!!val.match(/^\d+$/)) {
            return true;
        }

        //if val is empty return false
        if (val === '') {
            return false;
        }

        //get the decimal and thousands separator from numeral.localeData
        try {
            //check if the culture is understood by numeral. if not, default it to current locale
            localeData = numeral.localeData(culture);
        } catch (e) {
            localeData = numeral.localeData(numeral.locale());
        }

        //setup the delimiters and currency symbol based on culture/locale
        _currSymbol = localeData.currency.symbol;
        _abbrObj = localeData.abbreviations;
        _decimalSep = localeData.delimiters.decimal;
        if (localeData.delimiters.thousands === '.') {
            _thousandSep = '\\.';
        } else {
            _thousandSep = localeData.delimiters.thousands;
        }

        // validating currency symbol
        temp = val.match(/^[^\d]+/);
        if (temp !== null) {
            val = val.substr(1);
            if (temp[0] !== _currSymbol) {
                return false;
            }
        }

        //validating abbreviation symbol
        temp = val.match(/[^\d]+$/);
        if (temp !== null) {
            val = val.slice(0, -1);
            if (temp[0] !== _abbrObj.thousand && temp[0] !== _abbrObj.million && temp[0] !== _abbrObj.billion && temp[0] !== _abbrObj.trillion) {
                return false;
            }
        }

        _thousandRegEx = new RegExp(_thousandSep + '{2}');

        if (!val.match(/[^\d.,]/g)) {
            _valArray = val.split(_decimalSep);
            if (_valArray.length > 2) {
                return false;
            } else {
                if (_valArray.length < 2) {
                    return ( !! _valArray[0].match(/^\d+.*\d$/) && !_valArray[0].match(_thousandRegEx));
                } else {
                    if (_valArray[0].length === 1) {
                        return ( !! _valArray[0].match(/^\d+$/) && !_valArray[0].match(_thousandRegEx) && !! _valArray[1].match(/^\d+$/));
                    } else {
                        return ( !! _valArray[0].match(/^\d+.*\d$/) && !_valArray[0].match(_thousandRegEx) && !! _valArray[1].match(/^\d+$/));
                    }
                }
            }
        }

        return false;
    };


    /************************************
        Numeral Prototype
    ************************************/

    numeral.fn = Numeral.prototype = {
        clone: function() {
            return numeral(this);
        },
        format: function(inputString, roundingFunction) {
            var value = this._value,
                format = inputString || options.defaultFormat,
                kind,
                output,
                formatFunction;

            // make sure we have a roundingFunction
            roundingFunction = roundingFunction || Math.round;

            // format based on value
            if (value === 0 && options.zeroFormat !== null) {
                output = options.zeroFormat;
            } else if (value === null && options.nullFormat !== null) {
                output = options.nullFormat;
            } else {
                for (kind in formats) {
                    if (format.match(formats[kind].regexps.format)) {
                        formatFunction = formats[kind].format;

                        break;
                    }
                }

                formatFunction = formatFunction || numeral._.numberToFormat;

                output = formatFunction(value, format, roundingFunction);
            }

            return output;
        },
        value: function() {
            return this._value;
        },
        input: function() {
            return this._input;
        },
        set: function(value) {
            this._value = Number(value);

            return this;
        },
        add: function(value) {
            var corrFactor = _.correctionFactor.call(null, this._value, value);

            function cback(accum, curr, currI, O) {
                return accum + Math.round(corrFactor * curr);
            }

            this._value = _.reduce([this._value, value], cback, 0) / corrFactor;

            return this;
        },
        subtract: function(value) {
            var corrFactor = _.correctionFactor.call(null, this._value, value);

            function cback(accum, curr, currI, O) {
                return accum - Math.round(corrFactor * curr);
            }

            this._value = _.reduce([value], cback, Math.round(this._value * corrFactor)) / corrFactor;

            return this;
        },
        multiply: function(value) {
            function cback(accum, curr, currI, O) {
                var corrFactor = _.correctionFactor(accum, curr);
                return Math.round(accum * corrFactor) * Math.round(curr * corrFactor) / Math.round(corrFactor * corrFactor);
            }

            this._value = _.reduce([this._value, value], cback, 1);

            return this;
        },
        divide: function(value) {
            function cback(accum, curr, currI, O) {
                var corrFactor = _.correctionFactor(accum, curr);
                return Math.round(accum * corrFactor) / Math.round(curr * corrFactor);
            }

            this._value = _.reduce([this._value, value], cback);

            return this;
        },
        difference: function(value) {
            return Math.abs(numeral(this._value).subtract(value).value());
        }
    };

    /************************************
        Default Locale && Format
    ************************************/

    numeral.register('locale', 'en', {
        delimiters: {
            thousands: ',',
            decimal: '.'
        },
        abbreviations: {
            thousand: 'k',
            million: 'm',
            billion: 'b',
            trillion: 't'
        },
        ordinal: function(number) {
            var b = number % 10;
            return (~~(number % 100 / 10) === 1) ? 'th' :
                (b === 1) ? 'st' :
                (b === 2) ? 'nd' :
                (b === 3) ? 'rd' : 'th';
        },
        currency: {
            symbol: '$'
        }
    });

    return numeral;
}));

(function() {
window.clockSyncWorker = function() {
/*
 * @overview es6-promise - a tiny implementation of Promises/A+.
 * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
 * @license   Licensed under MIT license
 *            See https://raw.githubusercontent.com/stefanpenner/es6-promise/master/LICENSE
 * @version   v4.2.8+1e68dce6
 */

// eslint-disable-next-line
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.ES6Promise=e()}(this,function(){"use strict";function t(t){var e=typeof t;return null!==t&&("object"===e||"function"===e)}function e(t){return"function"==typeof t}function n(t){W=t}function r(t){z=t}function o(){return function(){return process.nextTick(a)}}function i(){return"undefined"!=typeof U?function(){U(a)}:c()}function s(){var t=0,e=new H(a),n=document.createTextNode("");return e.observe(n,{characterData:!0}),function(){n.data=t=++t%2}}function u(){var t=new MessageChannel;return t.port1.onmessage=a,function(){return t.port2.postMessage(0)}}function c(){var t=setTimeout;return function(){return t(a,1)}}function a(){for(var t=0;t<N;t+=2){var e=Q[t],n=Q[t+1];e(n),Q[t]=void 0,Q[t+1]=void 0}N=0}function f(){try{var t=Function("return this")().require("vertx");return U=t.runOnLoop||t.runOnContext,i()}catch(e){return c()}}function l(t,e){var n=this,r=new this.constructor(p);void 0===r[V]&&x(r);var o=n._state;if(o){var i=arguments[o-1];z(function(){return T(o,r,i,n._result)})}else j(n,r,t,e);return r}function h(t){var e=this;if(t&&"object"==typeof t&&t.constructor===e)return t;var n=new e(p);return w(n,t),n}function p(){}function v(){return new TypeError("You cannot resolve a promise with itself")}function d(){return new TypeError("A promises callback cannot return that same promise.")}function _(t,e,n,r){try{t.call(e,n,r)}catch(o){return o}}function y(t,e,n){z(function(t){var r=!1,o=_(n,e,function(n){r||(r=!0,e!==n?w(t,n):A(t,n))},function(e){r||(r=!0,S(t,e))},"Settle: "+(t._label||" unknown promise"));!r&&o&&(r=!0,S(t,o))},t)}function m(t,e){e._state===Z?A(t,e._result):e._state===$?S(t,e._result):j(e,void 0,function(e){return w(t,e)},function(e){return S(t,e)})}function b(t,n,r){n.constructor===t.constructor&&r===l&&n.constructor.resolve===h?m(t,n):void 0===r?A(t,n):e(r)?y(t,n,r):A(t,n)}function w(e,n){if(e===n)S(e,v());else if(t(n)){var r=void 0;try{r=n.then}catch(o){return void S(e,o)}b(e,n,r)}else A(e,n)}function g(t){t._onerror&&t._onerror(t._result),E(t)}function A(t,e){t._state===X&&(t._result=e,t._state=Z,0!==t._subscribers.length&&z(E,t))}function S(t,e){t._state===X&&(t._state=$,t._result=e,z(g,t))}function j(t,e,n,r){var o=t._subscribers,i=o.length;t._onerror=null,o[i]=e,o[i+Z]=n,o[i+$]=r,0===i&&t._state&&z(E,t)}function E(t){var e=t._subscribers,n=t._state;if(0!==e.length){for(var r=void 0,o=void 0,i=t._result,s=0;s<e.length;s+=3)r=e[s],o=e[s+n],r?T(n,r,o,i):o(i);t._subscribers.length=0}}function T(t,n,r,o){var i=e(r),s=void 0,u=void 0,c=!0;if(i){try{s=r(o)}catch(a){c=!1,u=a}if(n===s)return void S(n,d())}else s=o;n._state!==X||(i&&c?w(n,s):c===!1?S(n,u):t===Z?A(n,s):t===$&&S(n,s))}function M(t,e){try{e(function(e){w(t,e)},function(e){S(t,e)})}catch(n){S(t,n)}}function P(){return tt++}function x(t){t[V]=tt++,t._state=void 0,t._result=void 0,t._subscribers=[]}function C(){return new Error("Array Methods must be provided an Array")}function O(t){return new et(this,t).promise}function k(t){var e=this;return new e(L(t)?function(n,r){for(var o=t.length,i=0;i<o;i++)e.resolve(t[i]).then(n,r)}:function(t,e){return e(new TypeError("You must pass an array to race."))})}function F(t){var e=this,n=new e(p);return S(n,t),n}function Y(){throw new TypeError("You must pass a resolver function as the first argument to the promise constructor")}function q(){throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.")}function D(){var t=void 0;if("undefined"!=typeof global)t=global;else if("undefined"!=typeof self)t=self;else try{t=Function("return this")()}catch(e){throw new Error("polyfill failed because global object is unavailable in this environment")}var n=t.Promise;if(n){var r=null;try{r=Object.prototype.toString.call(n.resolve())}catch(e){}if("[object Promise]"===r&&!n.cast)return}t.Promise=nt}var K=void 0;K=Array.isArray?Array.isArray:function(t){return"[object Array]"===Object.prototype.toString.call(t)};var L=K,N=0,U=void 0,W=void 0,z=function(t,e){Q[N]=t,Q[N+1]=e,N+=2,2===N&&(W?W(a):R())},B="undefined"!=typeof window?window:void 0,G=B||{},H=G.MutationObserver||G.WebKitMutationObserver,I="undefined"==typeof self&&"undefined"!=typeof process&&"[object process]"==={}.toString.call(process),J="undefined"!=typeof Uint8ClampedArray&&"undefined"!=typeof importScripts&&"undefined"!=typeof MessageChannel,Q=new Array(1e3),R=void 0;R=I?o():H?s():J?u():void 0===B&&"function"==typeof require?f():c();var V=Math.random().toString(36).substring(2),X=void 0,Z=1,$=2,tt=0,et=function(){function t(t,e){this._instanceConstructor=t,this.promise=new t(p),this.promise[V]||x(this.promise),L(e)?(this.length=e.length,this._remaining=e.length,this._result=new Array(this.length),0===this.length?A(this.promise,this._result):(this.length=this.length||0,this._enumerate(e),0===this._remaining&&A(this.promise,this._result))):S(this.promise,C())}return t.prototype._enumerate=function(t){for(var e=0;this._state===X&&e<t.length;e++)this._eachEntry(t[e],e)},t.prototype._eachEntry=function(t,e){var n=this._instanceConstructor,r=n.resolve;if(r===h){var o=void 0,i=void 0,s=!1;try{o=t.then}catch(u){s=!0,i=u}if(o===l&&t._state!==X)this._settledAt(t._state,e,t._result);else if("function"!=typeof o)this._remaining--,this._result[e]=t;else if(n===nt){var c=new n(p);s?S(c,i):b(c,t,o),this._willSettleAt(c,e)}else this._willSettleAt(new n(function(e){return e(t)}),e)}else this._willSettleAt(r(t),e)},t.prototype._settledAt=function(t,e,n){var r=this.promise;r._state===X&&(this._remaining--,t===$?S(r,n):this._result[e]=n),0===this._remaining&&A(r,this._result)},t.prototype._willSettleAt=function(t,e){var n=this;j(t,void 0,function(t){return n._settledAt(Z,e,t)},function(t){return n._settledAt($,e,t)})},t}(),nt=function(){function t(e){this[V]=P(),this._result=this._state=void 0,this._subscribers=[],p!==e&&("function"!=typeof e&&Y(),this instanceof t?M(this,e):q())}return t.prototype["catch"]=function(t){return this.then(null,t)},t.prototype["finally"]=function(t){var n=this,r=n.constructor;return e(t)?n.then(function(e){return r.resolve(t()).then(function(){return e})},function(e){return r.resolve(t()).then(function(){throw e})}):n.then(t,t)},t}();return nt.prototype.then=l,nt.all=O,nt.race=k,nt.resolve=h,nt.reject=F,nt._setScheduler=n,nt._setAsap=r,nt._asap=z,nt.polyfill=D,nt.Promise=nt,nt.polyfill(),nt});

// eslint-disable-next-line no-unused-vars
var workerHelpers = function() {
   // Helpers for web worker and window communication
   // eslint-disable-next-line no-empty-function
   var noop = function() {};

   var ret = {};

   var workerEventType = 'worker-event';

   ret.events = {
      START: 'start',
      STOP: 'stop',
      UPDATE: 'update',
      MASTER_OFFLINE: 'master_offline',
      DELTA_UPDATED: 'delta_updated',
      MASTER_ONLINE: 'master_online',
      WORKER_STOPPED: 'worker_stopped',
      PING: 'ping',
      PONG: 'pong',
      LOG: 'log',
      SIMILAR_DELTAS: 'similar_deltas',
      PING_SENT: 'pingsent'
   };

   ret.logTypes = {
      LOG: 'log',
      INFO: 'info',
      DEBUG: 'debug',
      WARN: 'warn',
      ERROR: 'error'
   };

   ret.callbacks = {};

   var getGlobal = function() {
      return self || window || {postMessage: noop};
   };

   var tryCleanObject = function(obj) {
      if (obj === undefined || typeof obj === 'function') {
         return undefined;
      }

      try {
         /* eslint-disable function-paren-newline */
         return JSON.parse(
            JSON.stringify(obj, function(key, value) {
               // NaN is the only value that does not equal itself
               // eslint-disable-next-line no-self-compare
               if (value !== value) {
                  return 'NaN';
               }

               if (value === Infinity) {
                  return 'Infinity';
               }

               if (value === -Infinity) {
                  return '-Infinity';
               }

               return value;
            })
         );
      } catch (e) {
         return obj;
      }
   };

   ret.emit = function(event, values, target) {
      if (!event) {
         return;
      }

      (target || getGlobal()).postMessage({
         type: tryCleanObject(workerEventType),
         payload: {
            event: tryCleanObject(event),
            values: tryCleanObject(values)
         }
      });
   };

   ret.on = function(event, callback) {
      if (!event || !callback || typeof callback !== 'function') {
         return;
      }

      ret.callbacks[event] = callback;
   };

   ret.onMessage = function(ev) {
      if (
         !ev ||
         !ev.data ||
         ev.data.type !== workerEventType ||
         !ev.data.payload ||
         !ev.data.payload.event ||
         !ret.callbacks[ev.data.payload.event]
      ) {
         return;
      }

      ret.callbacks[ev.data.payload.event](ev.data.payload.values);
   };

   ret.callbacks[ret.events.START] = noop;
   ret.callbacks[ret.events.STOP] = noop;
   ret.callbacks[ret.events.UPDATE] = noop;
   ret.callbacks[ret.events.PING_SENT] = noop;
   ret.callbacks[ret.events.PING] = function() {
      ret.emit(ret.events.PONG);
   };

   ret.ping = function(target) {
      ret.emit(ret.events.PING, undefined, target || getGlobal());
      ret.callbacks[ret.events.PING_SENT]();
   };

   var sendLog = function(type, message) {
      ret.emit(ret.events.LOG, {
         type: type || ret.logTypes.INFO,
         message: message
      });
   };

   ret.logger = {
      log: function(message) {
         sendLog(ret.logTypes.INFO, message);
      },
      debug: function(message) {
         sendLog(ret.logTypes.DEBUG, message);
      },
      info: function(message) {
         sendLog(ret.logTypes.INFO, message);
      },
      warn: function(message) {
         sendLog(ret.logTypes.WARN, message);
      },
      error: function(message) {
         sendLog(ret.logTypes.ERROR, message);
      }
   };

   return ret;
};

// eslint-disable-next-line no-unused-vars
var webSocketsService = function(url, slaveId, port, wss, timeoutInterval, logger) {
   if (!url) {
      throw new Error('url is required');
   }

   var ret = {};
   var opened = false;
   var defaultMessage = 'ping';
   var ws;
   var connectingThread;
   var getTimestampTimeoutThread;
   var pingTimeoutThread;

   var constructWebSocketUrl = function() {
      return (wss ? 'wss' : 'ws') + '://' + url + (port ? ':' + port : '');
   };

   ret.isSocketOpen = function() {
      return opened && ws && ws instanceof WebSocket && ws.readyState == ws.OPEN;
   };

   var onDisconnect = function() {
      opened = false;
      ws = undefined;
   };

   ret.closeSocketConnection = function() {
      if (getTimestampTimeoutThread) {
         clearTimeout(getTimestampTimeoutThread);
      }

      if (pingTimeoutThread) {
         clearTimeout(getTimestampTimeoutThread);
      }

      if (connectingThread) {
         clearTimeout(getTimestampTimeoutThread);
      }

      if (ret.isSocketOpen()) {
         ws.close();
      }

      onDisconnect();
   };

   ret.openSocketConnection = function() {
      return new Promise(function(resolve, reject) {
         if (ret.isSocketOpen()) {
            ret.closeSocketConnection();
         }

         ws = new WebSocket(constructWebSocketUrl());

         connectingThread = setTimeout(function() {
            reject('could not connect to websocket server');
         }, timeoutInterval);

         ws.onerror = reject;
         ws.onclose = onDisconnect;
         ws.onopen = function() {
            if (connectingThread) {
               clearTimeout(connectingThread);
            }

            opened = true;
            resolve('connected');
         };
      });
   };

   ret.getMasterTime = function() {
      if (!ret.isSocketOpen()) {
         return Promise.reject('socket not connected');
      }

      return new Promise(function(resolve, reject) {
         if (getTimestampTimeoutThread) {
            clearTimeout(getTimestampTimeoutThread);
            getTimestampTimeoutThread = undefined;
         }

         getTimestampTimeoutThread = setTimeout(function() {
            ret.closeSocketConnection();
            logger.warn('WebSocket - Web Socket connection timeout.');
            reject('request timedout');
         }, timeoutInterval);

         ws.onmessage = function(messageEvent) {
            if (getTimestampTimeoutThread) {
               clearTimeout(getTimestampTimeoutThread);
               getTimestampTimeoutThread = undefined;
            }

            if (!messageEvent || !messageEvent.data) {
               logger.warn('WebSocket - Web Socket response is empty.');
               reject('No Data in response');
            }

            var response = messageEvent.data.split('_');
            // Support for legacy app responses
            var altResponse = messageEvent.data.split(' ');

            if (response && response.length > 1 && !isNaN(response[1])) {
               resolve(parseInt(response[1]));
            } else if (altResponse && altResponse.length > 1 && !isNaN(altResponse[1])) {
               resolve(parseInt(altResponse[1]));
            } else {
               reject(messageEvent);
            }
         };

         ws.send(slaveId || defaultMessage);
      });
   };

   ret.isSocketObject = true;

   var init = function() {
      timeoutInterval = timeoutInterval || 500;
   };

   init();

   return ret;
};

// eslint-disable-next-line no-unused-vars
var slaveWorkerService = function(
   helpers,
   masterIp,
   port,
   playerGuid,
   refreshInterval,
   maxAcceptedDelay,
   failedRetryInterval,
   webSocketConnectionTimeoutInterval,
   maxAcceptedDeltasRange,
   isPerformanceApiSupported
) {
   /* eslint-disable function-paren-newline */
   /* eslint-disable prefer-object-spread */
   var websockets;
   var getTimestampThread;
   var stopped = true;
   var lastClockValues = {};
   var delta = null;
   var logger = helpers.logger;

   refreshInterval = refreshInterval || 20000;
   port = port || 55537;
   masterIp = masterIp || '';

   var ret = {};

   var isNullOrNotANumber = function(value) {
      return (!value || isNaN(value)) && value !== 0;
   };

   var closeWebsocket = function() {
      if (!websockets || !websockets.isSocketObject || !websockets.isSocketOpen()) {
         return;
      }

      websockets.closeSocketConnection();
   };

   var openWebsocket = function() {
      return new Promise(function(resolve, reject) {
         closeWebsocket();
         websockets.openSocketConnection().then(resolve, reject);
      });
   };

   var isWithinRange = function(newVal, oldVal, range) {
      if (isNullOrNotANumber(newVal)) {
         return false;
      }

      if (isNullOrNotANumber(oldVal)) {
         return true;
      }

      return Math.abs(newVal - oldVal) <= (range || 10);
   };

   var clearTimeoutIfActive = function(timeoutThread) {
      if (timeoutThread) {
         clearTimeout(timeoutThread);
      }
   };

   var calculateDeltaAndDelay = function(originalTimestamp, masterTimestamp, currentTimestamp) {
      var retValues = {
         websocketDelay: 0,
         websocketDelta: 0,
         isAccepted: false
      };

      if (!originalTimestamp || !masterTimestamp || !currentTimestamp) {
         return retValues;
      }

      retValues.websocketDelay = currentTimestamp - originalTimestamp;
      retValues.websocketDelta = masterTimestamp - originalTimestamp;
      // if round-trip is bigger then maximum accepted value then we reject it
      retValues.isAccepted = retValues.websocketDelay >= 0 && retValues.websocketDelay <= maxAcceptedDelay;

      return retValues;
   };

   var isSimilarToCurrentDelta = function(data) {
      if (!data) {
         return false;
      }

      return delta && isWithinRange(data.websocketDelta, delta, maxAcceptedDeltasRange);
   };

   var updateDeltaAndDelayValues = function(data) {
      if (stopped || !data || !data.isAccepted || isSimilarToCurrentDelta(data)) {
         logger.debug('Worker - Current Delta is similar to the previous one, ignoring');
         return;
      }

      delta = data.websocketDelta;
      helpers.emit(helpers.events.DELTA_UPDATED, data);
   };

   var retrySocketOnError = function(error, callback) {
      logger.warn(error.message || error);
      logger.info('Worker - Retrying to connect websocket now');
      if (!callback || typeof callback !== 'function') {
         return;
      }

      getTimestampThread = setTimeout(callback, failedRetryInterval);
   };

   var getTimeOrigin = function() {
      var timeOrigin = self.performance.timeOrigin;
      return timeOrigin == undefined ? self.performance.timing.navigationStart : timeOrigin;
   };

   var getCurrentTime = function() {
      return isPerformanceApiSupported ? getTimeOrigin() + self.performance.now() : Date.now();
   };

   var requestTimestampFromMaster = function() {
      if (!masterIp || !websockets || stopped) {
         return;
      }

      clearTimeoutIfActive(getTimestampThread);
      getTimestampThread = undefined;

      openWebsocket().then(
         function onConnected() {
            var startTimestamp = getCurrentTime();
            websockets.getMasterTime().then(
               function(masterTime) {
                  closeWebsocket();
                  helpers.emit(helpers.events.MASTER_ONLINE);

                  var currentClockValues = calculateDeltaAndDelay(startTimestamp, masterTime, getCurrentTime());

                  if (!currentClockValues.isAccepted) {
                     logger.info('Worker - roundtrip took more than minimum accepted value, retrying soon.');
                     retrySocketOnError('roundtrip too large', requestTimestampFromMaster);
                     return;
                  }

                  // Only update delta if it is different from the current delta
                  if (
                     isWithinRange(
                        currentClockValues.websocketDelta,
                        lastClockValues.websocketDelta,
                        maxAcceptedDeltasRange
                     )
                  ) {
                     logger.debug(
                        'Worker - Response from Controller with Delta: ' +
                           currentClockValues.websocketDelta +
                           ' and roundtrip:' +
                           currentClockValues.websocketDelay
                     );
                     updateDeltaAndDelayValues(currentClockValues);
                  } else {
                     logger.debug(
                        'Worker - Got an out of ordinary delta, waiting for next cycle before sending it'
                     );
                     helpers.emit(helpers.events.SIMILAR_DELTAS);
                  }

                  lastClockValues = Object.assign({}, currentClockValues);
                  getTimestampThread = setTimeout(requestTimestampFromMaster, refreshInterval);
               },
               function(error) {
                  logger.warn('Worker - Received wrong response from Controller.');
                  closeWebsocket();
                  retrySocketOnError(error, requestTimestampFromMaster);
                  helpers.emit(helpers.events.MASTER_OFFLINE);
               }
            );
         },
         function onNotConnected(error) {
            logger.warn('Worker - Web Socket could not connect.');
            retrySocketOnError(error, requestTimestampFromMaster);
            helpers.emit(helpers.events.MASTER_OFFLINE);
         }
      );
   };

   var init = function() {
      logger.debug('Worker - Starting worker and Initializing WebSocket service');

      // eslint-disable-next-line no-undef
      websockets = webSocketsService(
         masterIp,
         playerGuid,
         port,
         false,
         webSocketConnectionTimeoutInterval,
         logger
      );

      logger.debug('Worker - WebSocket Initialized');

      logger.debug('Worker - Will start syncing clock after 10 seconds');
      // Delay start by 10 seconds at start
      setTimeout(requestTimestampFromMaster, 10000);
   };

   ret.stop = function() {
      stopped = true;

      clearTimeoutIfActive(getTimestampThread);
      getTimestampThread = undefined;

      closeWebsocket();

      websockets = undefined;

      delta = undefined;
   };

   ret.start = function() {
      stopped = false;
      init();
   };

   ret.isRunning = function() {
      return !stopped;
   };

   return ret;
};

var slave;
// eslint-disable-next-line no-undef
var helpers = workerHelpers();

self.onmessage = helpers.onMessage;

helpers.on(helpers.events.START, function(payload) {
   // eslint-disable-next-line no-undef
   if (!payload) {
      return;
   }

   // eslint-disable-next-line no-undef
   slave = slaveWorkerService(
      helpers,
      payload.masterIp,
      payload.port,
      payload.playerGuid,
      payload.refreshInterval,
      payload.maxAcceptedDelay,
      payload.failedRetryInterval,
      payload.webSocketConnectionTimeoutInterval,
      payload.maxAcceptedDeltasRange,
      payload.isPerformanceApiSupported
   );
   slave.start();
   helpers.logger.debug('Worker - Worker Started.');
});

helpers.on(helpers.events.STOP, function() {
   if (!slave) {
      return;
   }

   helpers.logger.debug('Worker - Stopping Worker.');

   slave.stop();
   slave = undefined;
   helpers.emit(helpers.events.WORKER_STOPPED);
});
};
}());

(function($angular) {
    'use strict';
    var angular = $angular;
    angular.module('AppCommon', []);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('AppCommon');
    app.constant('APPSPACE_CLOUD_API_MAPPING', {
        'cloud.appspace.com': 'https://api.connect.appspace.com',
        'r8.dev.appspace.com': 'https://api-r8.dev.appspace.com',
        'cloud.dev.appspace.com': 'https://api-connect.dev.appspace.com',
        'cloud.stage.appspace.com': 'https://api-stage.connect.appspace.com',
        'cap-cloud.dev.appspace.com': 'https://cap-api-cloud.dev.appspace.com',
        'cm-cloud.dev.appspace.com': 'https://cm-api-cloud.dev.appspace.com'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('AppCommon');
    app.constant('APPSPACE_CONFIGURATION_PROPERTY', {
        DISCOVERYURL: 'discoveryurl',
        SELFUPDATEURL: 'selfupdateurl',
        NETWORKPOLICYURL: 'networkpolicyurl',
        REGISTRATIONPOLICY: 'registrationpolicy',
        INTEGRATIONURL: 'integrationurl'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('AppCommon');
    app.constant('APP_STRETCH', {
        FULL: 0,
        WIDTH: 1,
        HEIGHT: 2
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('AppCommon');
    app.constant('CHANNEL_TYPE', {
        SIGN: 'sign',
        CONTENT: 'content'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('AppCommon');
    app.constant('COMMON', {
        DEFAULT_SIGNS_CONTENT_DURATION: 12
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('AppCommon');
    app.constant('CONTENT_ACCESS_CONTEXT', {
        CONTENT_CACHING: 'contentcaching',
        WEB_PUBLISHING: 'webpublishing',
        BROADCAST: 'broadcast',
        CHANNEL_BROWSING: 'channelbrowsing',
        CHANNEL_BROWSING_IDLE: 'channelbrowsingidle',
        CHANNEL: 'channel',
        CHANNEL_PREVIEW: 'channelpreview',
        DEVICE_PREVIEW: 'devicepreview',
        CONTENT_FALLBACK: 'contentfallback',
    });
})(window.angular);

(function($angular) {
  'use strict';

  var app = $angular.module('AppCommon');
  app.constant('CURSOR_VISIBILITY', {
    HIDDEN: 'hidden',
    AUTO: 'auto',
    VISIBLE: 'visible'
  });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('AppCommon');
    app.constant('DRIVER_ID', {
        PWA: 'browser.pwa',
        MTR: 'browser.mtr',
        WEBEXNAVIGATOR: 'browser.cisco.webexnavigator',
        ZOOM_ROOMS: 'browser.zoomrooms',
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('AppCommon');
    app.constant('EDITOR_SETTINGS', {
        ANCHOR_COLOR: 'white',
        ANCHOR_WIDTH: '10px',
        ANCHOR_BORDER_WIDTH: '1px',
        ANCHOR_BORDER_STYLING: ' solid rgb(0, 150, 253)',
        CURSOR_LEFT_RIGHT_STYLE: 'ew-resize',
        CURSOR_TOP_BOTTOM_STYLE: 'ns-resize',
        CURSOR_TOP_LEFT_BOTTOM_RIGHT_STYLE: 'nwse-resize',
        CURSOR_TOP_RIGHT_BOTTOM_LEFT_SYTLE: 'nesw-resize',
        DEFAULT_ANCHOR_STYLING: 'visibility:hidden;position:absolute;',
        BORDER_WIDTH: '3px',
        BORDER_STYLING: ' solid rgb(0, 150, 253)',
        MOVE_TYPE_ANCHOR: 'move-anchor',
        MOVE_TYPE_KEYDOWN: 'move-keydown',
        RESIZE_TYPE_KEYDOWN: 'resize-keydown',
        MIN_GRID_SIZE: 10,
        MAX_GRID_SIZE: 100,
        DEFAULT_VISUAL_SCALE: 1,
        DEFAULT_SNAP_RANGE: 15
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('AppCommon');

    app.constant('INTEGRATION_TYPE', {
        MTR: 'microsoftteamsroom',
        ZOOM: 'zoom', // the old value that we use for COPS-managed stage & prod. See APC-8020. To be removed once configuration has been applied properly by zoom
        ZOOM_ROOMS: 'zoomrooms',
        WEBEX_CONTROL_HUB: 'webexcontrolhub',
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('AppCommon');
    app.constant('LAYOUT_EVENT', {
        WIDGET_POSITION_CHANGED: 'widgetpositionchanged',
        WIDGET_SELECTED: 'widgetselected',
        WIDGET_DOUBLE_CLICKED: 'widgetdoubleclicked',
        ON_CONTENT_LOADED: 'contentloaded',
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('AppCommon');
    app.constant('PLAYER_PROPERTY_KEYS', {
        MAX_VIDEO_LAYERS: 'player.device.maxvideolayers',
        WEB_RENDERER: 'player.media.web.renderer',
        CURSOR_VISIBILITY: 'player.cursor.visibility',
        CURSOR_VISIBILITY_ENABLED: 'player.cursor.visibility.enable',
        VIRTUAL_KEYBOARD_ENABLED: 'player.virtualkeyboard.enable',
        ASAM_ENABLED: 'player.ios.asam.enabled',
        ASAM_ALLOWED: 'player.ios.asam.allowed',
        DEVICE_MENU_INVOKE_PATTERN: 'player.devicemenu.invokepattern',
        DEVICE_MENU_HOT_AREA_ENABLED: 'player.devicemenu.hotarea.enable',
        DEVICE_MENU_HOT_AREA_STYLE: 'player.devicemenu.hotarea.style',
        HIDE_LOW_BANDWIDTH_MESSAGE: 'player.streaming.lowbandwidthmode.message.hide',
        PRELOAD_BEHAVIOR: 'player.content.preloadbehavior',
        DEVICE_PRINTER_TYPE: 'device.printer.type',
        DEVICE_PRINTER_OPTIONS: 'device.printer.options',
        NETWORK_ID: 'network.guid',
        NETWORK_ANCESTORS_ID: 'network.ancestors.guids',
        DEVICE_ID: 'player.guid',
        DEVICE_TAGS: 'player.tags',
        DEVICE_GROUP_ID: 'player.group.guid',
        DEVICE_GROUP_ANCESTORS_ID: 'player.group.ancestors.guids',
        DEVICE_TYPE: 'player.model.name',
        SOFTWARE_UPDATES: 'software.softwareupdates',
        SOFTWARE_UPDATE_URL: 'device.softwareupdateurl',
        SOFTWARE_UPDATE_SCHEDULE: 'software.softwareupdates.schedule',
        STATUS_LIGHT_COLOR_PALETTE: 'player.statuslight.colorpalette',
        MEDIA_RELOAD_ON_RECONNECT: 'player.media.web.reloadsonreconnect',
        CACHE_PURGE_SYNC_CYCLE: 'player.cache.purge.synccycle',
        PUSH_UPDATE_LONG_POLLING_DURATION: 'player.pushupdates.longpolling.duration',
        CHANNEL_PUBLISHING_MODE: 'channel.publishingmode',
        CHANNEL_ID: 'channel.guid',
        REACTION_SUMMARY_API_PATH_PREFIX: 'player.reactions.api.prefix',
        SOCIAL_QR_CODE_ENABLE: 'player.social.qrcode.enable',
        OPERATION_HOURS_SCHEDULE_ENABLED: 'player.configuration.operatinghours.schedule.isenabled',
        NON_OPERATION_HOURS_LAYOUT_NAME: 'player.configuration.nonoperatinghours.schedule.name',
        CARD_TIMEOUT_DURATION: 'player.media.card.timeout.duration',
        FALLBACK_CONTENT_CONFIGURATION: 'device.fallbackcontent.configuration',
        PNS_WATCHDOG_ENABLED: 'device.pns.watchdog.enabled',
        CAMERA_FRAMING_MODE: 'device.camera.framingmode',
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('AppCommon');
    app.constant('PRINTER_TYPE', {
        BROTHER: 'brother',
        GENERIC: 'generic'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('AppCommon');
    app.constant('REGISTRATION_MODE', {
        CLAIM_DEVICE: 'claimDevice'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('AppCommon');
    app.constant('STORAGE_COLLECTION_KEY', {
        APPSPACE: 'appspace',
        DEVICE: 'device',
        ADM: 'adm',
        SIGNS_REGISTRATION: 'signs.registration',
        PLAY: 'play',
        APPSPACE_INTERACTIVE: 'appspace.interactive',
        DEVICE_SYNC: 'device.sync',
        DEVICE_INFO: 'device.info',
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('AppCommon');
    app.constant('STORAGE_KEY', {
        ACCOUNT_ID: 'accountId',
        INSTANCE_ID: 'instanceGuid',
        DEVICE_ID: 'playerguid',
        APPSPACE_URL: 'appspaceURL',
        REFERENCE_DEVICE_ID: 'referenceDeviceId',
        SERIAL_NUMBER: 'serialNumber',
        REFERENCE_DEVICE_NAME: 'referenceDeviceName',
        REGISTRATION_POLICY_ID: 'registrationPolicyId',
        CACHE_VERSION: 'cacheVersion',
        CACHE_BUILD_VERSION: 'cacheBuildVersion',
        CACHE_APP_SCRIPT_PARSER_VERSION: 'cacheAppScriptParserVersion',
        LAST_PASSCODE_SUCCESS_TIME: 'lastPasscodeSucsessTime',
        API_KEY: 'apikey',
        HAS_CHANNEL_ASSIGNED: 'haschannelassigned',
        THEME: 'theme',
        DEVICE_NAME: 'devicename',
        INTEGRATION_TYPE: 'integrationType',
        SINGLE_APP_STATUS: 'singleAppStatus',
        PASSIVE_REGISTRATION: 'passiveRegistration',
        MANIFEST: 'manifest',
        CONTENT_FILE_SIZE_MAP: 'contentFileSizeMap',
        APPSPACE_CONFIGURATION: 'appspaceConfiguration',
        SERVICE_API_URL: 'serviceApiUrl',
        APP_ID: 'appid',
        PLAYER_TIMESTAMP: 'playerTimestamp',
        DEVICE_HEALTH_STATUS: 'deviceHealthStatus',
        TYPE: 'type',
        DEVICE_CLAIM_STATUS: 'deviceClaimStatus',
        ADM_QUEUE_TIME: 'queuetime',
        ADM_PENDING_RECURRING_TASKS: 'pendingRecurringTasks',
        PERSISTED_FINGERPRINT: 'persistedFingerprint',
        PERSISTED_REGISTRATION_INFO_HASH: 'persistedRegistrationInfoHash',
        TELEMETRY_QUEUE: 'telemetryQueue',
        SOFTWARE_UPDATE_EXECUTED_AT: 'softwareUpdateExecutedAt',
        SOFTWARE_ROLLBACK_EXECUTED_AT: 'softwareRollbackExecutedAt',
        LAST_SELF_UPDATE_CHECK: 'lastselfupdatecheck',
        START_UP_TIME: 'startuptime',
        FALLBACK_CONTENT_CONFIGURATION: 'fallbackContentConfiguration',
        FALLBACK_CACHE_MANIFEST: 'fallbackCacheManifest',
        DEVICE_PROPERTIES: 'deviceProperties',
        CACHE_PATH: 'cachePath',
        SERVICE_WORKER_URLS: 'serviceWorkerUrls',
        METRICS: 'metrics',
        METRICS_UPDATED_AT: 'metricsUpdatedAt',
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('AppCommon');
    app.constant('SYNC_EVENT', {
        SCRIPT_SAVED: 'script-saved',
        SERVICE_STOPPED: 'sync-service-stopped',
        NO_PLAYER_ID: 'no-player-id',
        SYNC_ERROR: 'sync-error',
        SYNC_ABORT: 'sync-abort',
        RETRIEVING_TIMESTAMP: 'syncing-timestamp',
        NO_APP: 'syncing-noapp',
        TIMESTAMP_RETRIEVED: 'timestamp-retrieved',
        RETRIEVING_SCRIPT: 'syncing-appscript',
        IN_SYNC: 'app-in-sync',
        NO_DEVICE_FOUND: 'no-device-found',
        ERROR_RETRIEVING_TIMESTAMP: 'timestamp-error',
        APP_UPDATED: 'app-updated',
        APP_UNASSIGNED: 'app-unassigned',
        MANIFEST_UPDATED: 'manifest-updated',
        SCRIPT_READY: 'script-ready',
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('AppCommon');
    app.constant('TIMESTAMP_EVENT', {
        RETRIEVED: 'retrieved',
        FAILED: 'failed',
    });
})(window.angular);

(function ($angular) {

    "use strict";

	//declare storage module and its dependencies
    var app = $angular.module('alerts',['SignalR','core','ui','appspaceAPI','media']);

})(window.angular);
(function($angular) {
    'use strict';

    var app = $angular.module('alerts');

    app.directive('asAlertsView', function() {
        return {
            restrict: 'EA',
            scope: {
                currentAlert: '=',
                onApiReady: '&',
                onAlertsLoaded: '&',
                onAlertsRemoved: '&'
            },

            template: '<div as-ui-content-presenter content-item="currentItem" style="width:100%;height:100%" on-api-ready="onPresenterReady(api)" on-item-load="onPresenterItemLoaded(item)"></div>',
            link: function(scope, element, attrs) {
                scope.$watch('currentAlert', function(newVal) {
                    // send every changes to set alert because we want to support null alert
                    scope.setAlert(newVal);
                });

                scope.api = {
                    setAlert: scope.setAlert,
                    removeAlert: scope.removeAlert,
                    getState: scope.getState
                };

                if (scope.onApiReady) {
                    scope.onApiReady({api: scope.api});
                }
            },
            controller: ['$scope', '$q', '$element', '$compile',
                function($scope, $q, $element, $compile) {
                    $scope.currentItem = null;
                    var presenterAPI;
                    var currentAlert;
                    $scope.setAlert = function(alert) {
                        if (alert) {
                            currentAlert = alert;
                            $scope.currentItem = convertToPresenterItem(alert);
                        } else if (!alert) {
                            $scope.removeAlert();
                        }
                    };

                    $scope.removeAlert = function() {
                        if ($scope.currentItem) {
                            if (presenterAPI) {
                                presenterAPI.unload();
                            }
                            $scope.currentItem = null;
                            currentAlert = null;
                            if ($scope.onAlertsRemoved) {
                                $scope.onAlertsRemoved();
                            }
                        }
                    };

                    $scope.getState = function() {
                        return ($scope.currentItem) ? 'play' : 'stop';
                    };

                    $scope.onPresenterReady = function(api) {
                        presenterAPI = api;
                    };

                    $scope.onPresenterItemLoaded = function(item) {
                        if ($scope.onAlertsLoaded) {
                            $scope.onAlertsLoaded({alert: currentAlert});
                        }
                    };

                    function convertToPresenterItem(alert) {
                        var item = {
                            mediaType: alert.mediaType.toLowerCase(),
                            mediaUrl: alert.media,
                            duration: 0,
                            data: {
                                properties: alert.playerProperties
                            }
                        };

                        return item;
                    }
                }]
        };
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('alerts');
    app.service('alertsService', ["Hub", "$timeout", "logService", "deviceRegistrationService", "coreConfigurationService", "TimeoutUtil", function(Hub, $timeout, logService, deviceRegistrationService,
        coreConfigurationService, TimeoutUtil) {
            init();

            var mainHub;
            var timerCheck;
            var currentAlert;
            var activeAlert;
            var subscribers = [];
            var globalTimestamp;
            var logkey = 'appspace.alerts.alertsservice';
            var startHandler;
            var hasConnected = false;
            var subsequentErrorCount = 0;
            var lastConnected;
            var isStarted = false;

            function init() {
                activeAlert = null;
                subscribers = [];
            }

            function delayStart(delay) {
                stop();
                cancelDelayStart();
                startHandler = $timeout(start, delay);
            }

            function cancelDelayStart() {
                TimeoutUtil.cancel(startHandler);
                startHandler = undefined;
            }

            function start() {
                stop();
                isStarted = true;
                var deviceGuid = deviceRegistrationService.getPlayerId();
                var appspaceUrl = coreConfigurationService.get('appspace', 'appspaceURL');

                logService.debug(logkey, 'Device id:' + deviceGuid);
                logService.debug(logkey, 'Appspace URL:' + appspaceUrl);

                if (!deviceGuid || !appspaceUrl) {
                    logService.warn(logkey, 'Restarting alerts service in 15 seconds as device guid/appspace URL is empty');
                    delayStart(15000);
                    return;
                }

                var clientURL = appspaceUrl + '/priority/signalr';
                logService.info(logkey, 'Alerts service started');
                logService.debug(logkey, 'Alerts service URL:' + clientURL);

                mainHub = new Hub('priorityMessagesHub', {
                    listeners: {
                        onprioritymessage: onReceivedPriorityMessage
                    },
                    methods: [],
                    queryParams: {
                        clientguid: deviceGuid
                    },
                    errorHandler: onError,
                    stateChanged: onStateChanged,
                    rootPath: clientURL,
                    useSharedConnection: false,
                    withCredentials: false
                });

                startTimer();
            }

            function stop() {
                if (!isStarted) {
                    return;
                }

                logService.info(logkey, 'Alerts service stopped');
                if (mainHub) {
                    mainHub.disconnect();
                    mainHub = null;
                }

                cancelDelayStart();
                stopTimer();
                stopActiveAlert();
            }

            function subscribe(callback) {
                if (subscribers.indexOf(callback) == -1) {
                    subscribers.push({callback: callback, timestamp: ''});
                }
            }

            function unsubscribe(callback) {
                var index = subscribers.indexOf(callback);
                if (index != -1) {
                    subscribers.splice(index, 1);
                }
            }

            function notifySubscribers() {
                for (var i = 0; i < subscribers.length; i++) {
                    var sub = subscribers[i];
                    if (sub.timestamp != globalTimestamp) {
                        sub.timestamp = globalTimestamp;
                        sub.callback(activeAlert);
                    }
                }
            }

            function startTimer() {
                stopTimer();
                timerCheck = $timeout(doTimerCheck, 1000);
            }

            function stopTimer() {
                TimeoutUtil.cancel(timerCheck);
                timerCheck = undefined;
            }

            function stopActiveAlert() {
                if (!activeAlert) {
                    return;
                }

                globalTimestamp = Date.now();
                activeAlert = null;
                // Notify as there is no longer an alert
                notifySubscribers();
            }

            function doTimerCheck() {
                if (currentAlert && currentAlert.isValid()) {
                    // if previously no active alert, update timestamp
                    if (activeAlert != currentAlert) {
                        globalTimestamp = Date.now();
                        activeAlert = currentAlert;
                        logService.debug(logkey, 'alert started ' + activeAlert.toString());
                        // Notify as there has been an update
                        notifySubscribers();
                    }
                } else if (activeAlert) { // current alert no longer valid but we have active alert
                    // only update timestamp if previously we have an active alert
                    logService.debug(logkey, 'alert stopped ' + activeAlert.toString());
                    stopActiveAlert();
                }

                startTimer();
            }

            function isAlertValidNow(current, alert) {
                return (current.getTime() >= alert.start.getTime() && current.getTime() < alert.end.getTime());
            }

            function isAlertDailyValid(current, alert) {
                // check if we're inside the alert's time frame
                // e.g. alert runs between 12pm to 2pm
                var alertTodayStart = new Date(current.getTime());

                alertTodayStart.setHours(alert.start.getHours());
                alertTodayStart.setMinutes(alert.start.getMinutes());
                alertTodayStart.setSeconds(alert.start.getSeconds());
                alertTodayStart.setMilliseconds(alert.start.getMilliseconds());

                var alertTodayEnd = new Date(alert.end.getTime());
                if (alert.duration != 0) {
                    alertTodayEnd = new Date(alertTodayStart.getTime());
                    alertTodayEnd.setSeconds(alertTodayEnd.getSeconds() + alert.duration);
                }

                return (current.getTime() >= alertTodayStart.getTime() && current.getTime() < alertTodayEnd.getTime());
            }

            function isAlertWeeklyValid(current, alert) {
                // check if we're on the same day as alert
                // e.g. alert runs on every Thursday
                var currentDay = current.getDay();
                var alertDay = alert.start.getDay();

                var res = false;
                if (currentDay == alertDay) {
                    res = isAlertDailyValid(current, alert);
                }

                return res;
            }

            function isAlertMonthlyValid(current, alert) {
                // check if we're on the same date as alert
                // e.g. alert runs on 10th on every month
                var currentDateOfMonth = current.getDate();
                var maxDayInMonth = new Date(current.getYear(), current.getMonth() - 1, 0).getDate();
                var alertDateOfMonth = alert.start.getDate();
                if (alertDateOfMonth > maxDayInMonth) {
                    alertDateOfMonth = maxDayInMonth;
                }

                var res = false;

                if (currentDateOfMonth == alertDateOfMonth) {
                    res = isAlertDailyValid(current, alert);
                }

                return res;
            }

            function onReceivedPriorityMessage(message) {
                var alert = parseDTO(message);
                currentAlert = alert;
            }

            function isAlertActive() {
                return (activeAlert != null);
            }

            function onStateChanged(state) {
                switch (state.newState) {
                    case $.signalR.connectionState.connecting:
                        logService.debug(logkey, 'Alerts connection CONNECTING');
                        break;
                    case $.signalR.connectionState.connected: //
                        subsequentErrorCount = 0;
                        hasConnected = true;
                        lastConnected = new Date().getTime();
                        logService.debug(logkey, 'Alerts connection CONNECTED');
                        break;
                    case $.signalR.connectionState.reconnecting:
                        logService.debug(logkey, 'Alerts connection RECONNECTING');
                        var reconnectTime = new Date().getTime();
                        if (reconnectTime - lastConnected < 2500) {
                            reconnect();
                        }

                        break;
                    case $.signalR.connectionState.disconnected:
                        logService.debug(logkey, 'Alerts connection DISCONNECTED');
                        break;
                }
            }

            function onError(err) {
                subsequentErrorCount++;
                logService.error(logkey, 'Alerts connection ERROR. Error count: ' + subsequentErrorCount + ' ' + err);
                if (subsequentErrorCount > 4) {
                    hasConnected = false;
                }

                reconnect();
            }

            function reconnect() {
                // If we were previously connected, then try again shortly
                // If we have never connected the try again after 10mins
                var delay = hasConnected ? 15000 : 60000 * 10;
                delayStart(delay);
            }

            function parseDTO(raw) {
                var obj = JSON.parse(raw);
                var newObject = {
                    start: new Date(obj.StartedAt),
                    end: new Date(obj.EndedAt),
                    media: obj.ActualMedia || obj.Media, // for cards content, it has the ActualMedia, whilst Media is the thumbnail
                    mediaType: obj.ActualContentType || obj.ContentType, // and also the actual content type
                    duration: obj.TotalSeconds,
                    recurrence: obj.Recurrence,
                    isValid: function() {
                        var c = new Date();
                        switch (this.recurrence.toLowerCase()) {
                            case 'weekly':
                                return isAlertWeeklyValid(c, this);
                            case 'monthly':
                                return isAlertMonthlyValid(c, this);
                            case 'daily':
                                return isAlertDailyValid(c, this);
                            case 'none':
                            default:
                                return isAlertValidNow(c, this);
                        }
                    }
                };
                return newObject;
            }

            // public interfaces
            return {
                start: start,
                stop: stop,
                subscribe: subscribe,
                unsubscribe: unsubscribe,
                isAlertActive: isAlertActive
            };
        }]);
})(window.angular);

(function ($angular) {

    "use strict";

    //declare media module and its dependencies
    var app = $angular.module('appspaceAPI', ['core','network']);

    app.filter('trusted', ['$sce', function ($sce) {
        return function (url) {
            return $sce.trustAsResourceUrl(url);
        };

    }]);

})(window.angular);
(function($angular) {
    'use strict';

    var app = $angular.module('appspaceAPI');

    app.service('admAPIService', ["$q", "HttpWrapper", "x2js", function($q, HttpWrapper, x2js) {
        // public interfaces
        return {
            isExtensionAvailable: isExtensionAvailable,
            retrieveTasks: retrieveTasks,
            acknowledgeQueue: acknowledgeQueue,
            sendResponse: sendResponse,
            sendPayload: sendPayload
        };

        function isExtensionAvailable() {
            var promise = $q.defer();

            var wrapper = new HttpWrapper();
            var handler = '{{appspace-url}}/api/v1/appspace/extensions/checkextensionexistance/a340e2fd-0f7c-438b-931c-ed411d3bc078';
            wrapper.apiGet(handler).then(function(res) {
                if (res && res.status == 200 && res.data && res.data.Status == 1) {
                    promise.resolve(true);
                } else {
                    promise.resolve(false);
                }
            }, function(err) {
                promise.reject(err);
            });

            return promise.promise;
        }

        function retrieveTasks(taskQueueTime, includeRecurringTasks) {
            var promise = $q.defer();

            if (taskQueueTime == undefined) {
                taskQueueTime = 0;
            }

            var wrapper = new HttpWrapper();
            var handler = '{{appspace-url}}/api/v1/core/tasks/queue/{{device-id}}/' + taskQueueTime.toString();
            if (includeRecurringTasks) {
                handler += '?includeRecurringTasks=true';
            }

            wrapper.apiGet(handler).then(function(res) {
                var tasks = [];
                if (res.data && res.data.Status == 1 && res.data.TaskQueues) {
                    tasks = res.data.TaskQueues;
                }

                promise.resolve(tasks);
            }, function(err) {
                promise.reject(err);
            });

            return promise.promise;
        }

        function acknowledgeQueue(queueId) {
            var promise = $q.defer();

            var wrapper = new HttpWrapper();
            var handler = '{{appspace-url}}/api/v1/core/tasks/queue/' + queueId + '/{{device-id}}/ack';
            wrapper.apiPost(handler, null, {'Accept': 'application/xml'}).then(function(res) {
                if (isXMLResponseSuccess(res)) {
                    promise.resolve(res);
                } else {
                    promise.reject(res);
                }
            }, function(err) {
                promise.reject(err);
            });

            return promise.promise;
        }

        function sendResponse(response) {
            var promise = $q.defer();

            var wrapper = new HttpWrapper();
            var handler = '{{appspace-url}}/api/v1/core/tasks/response';
            wrapper.apiPost(handler, response, {'Content-Type': 'application/json', 'Cache-Control': 'no-cache', 'Accept': 'application/xml'}).then(function(res) {
                // check failure
                if (isXMLResponseSuccess(res)) {
                    promise.resolve(res);
                } else {
                    promise.reject(res);
                }
            }, function(err) {
                promise.reject(err);
            });

            return promise.promise;
        }

        function isXMLResponseSuccess(res) {
            var result = false;
            if (res && res.data) {
                var resJson = x2js.xml_str2json(res.data);
                result = resJson && resJson.TaskResponse && resJson.TaskResponse.Status && resJson.TaskResponse.Status.toString().toLowerCase() == 'success';
            }

            return result;
        }

        function sendPayload(response, taskid, filename, type) {
            var promise = $q.defer();
            var wrapper = new HttpWrapper();
            var handler = '{{appspace-url}}/api/v1/core/tasks/payload/stream/upload?filename=' + filename;
            handler += '&clientguid={{device-id}}&deployedtaskid=' + taskid;
            var header = {'Content-Type': 'text/plain'};
            if (type) {
                header['Content-Type'] = type;
            }

            wrapper.apiPost(handler, response, header).then(function(res) {
                promise.resolve(res);
            }, function(err) {
                promise.reject(err);
            });
            return promise.promise;
        }
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('appspaceAPI');

    app.service('appspaceApiService', ["$q", "HttpWrapper", function($q, HttpWrapper) {
        function buildApiUrl(urlTemplate, appspaceUrl) {
            if (appspaceUrl) {
                return urlTemplate.replace('{{appspace-url}}', appspaceUrl);
            }

            return urlTemplate;
        }

        function getAppspaceVersionFromPlatformCore(promise, wrapper, appspaceUrl) {
            var platformCoreVersionUrl = buildApiUrl('{{appspace-url}}/api/v1/core/system/platform/version', appspaceUrl);

            wrapper.apiGet(platformCoreVersionUrl).then(function(result) {
                if (!result || !result.data) {
                    promise.reject('Received empty result');
                    return;
                }

                if (!wrapper.isSuccess(result.status)) {
                    promise.reject('Received failure response (' + result.status + ')');
                    return;
                }

                // platformCoreVersion API returns version string directly
                promise.resolve(result.data);
            }, promise.reject);
        }

        function getAppspaceVersion(appspaceUrl) {
            var promise = $q.defer();
            var platformVersionUrl = buildApiUrl('{{appspace-url}}/api/v3/platform/version', appspaceUrl);

            var wrapper = new HttpWrapper();
            var fallbackToPlatformCore = function() {
                getAppspaceVersionFromPlatformCore(promise, wrapper, appspaceUrl);
            };

            // Try platformVersion API first
            wrapper.apiGet(platformVersionUrl).then(function(result) {
                // platformVersion API returns object with version field: {"version":"8.12.0-ac.3","releaseDate":"..."}
                if (result && result.data && wrapper.isSuccess(result.status) && result.data.version) {
                    promise.resolve(result.data.version);
                    return;
                }

                // If platformVersion fails or doesn't have version field, fallback to platformCoreVersion
                fallbackToPlatformCore();
            }, fallbackToPlatformCore);

            return promise.promise;
        }

        return {
            getAppspaceVersion: getAppspaceVersion
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('appspaceAPI');

    app.service('authenticationAPIService', ["$http", "$q", "$timeout", "HttpWrapper", "coreConfigurationService", "ASERRORCODE", function($http, $q, $timeout, HttpWrapper, coreConfigurationService, ASERRORCODE) {
        var configService = coreConfigurationService;
        var httpWrapper = new HttpWrapper();
        var timerValidateTokenPromise;
        var userInfo = {
            token: null,
            apikey: null,
            networkId: null,
            roles: null
        };

        function resetUserInfo() {
            userInfo.token = null;
        }

        var privateUserInfo = {
            username: null,
            password: null
        };

        function resetPrivateInfo() {
            privateUserInfo.username = null;
            privateUserInfo.password = null;
        }

        function getUserInfo() {
            if (!userInfo.apikey && configService.get('appspace', 'apikey')) {
                userInfo.apikey = configService.get('appspace', 'apikey');
            }
            return userInfo;
        }

        function setUserInfo(config) {
            if (config.apikey) {
                userInfo.apikey = config.apikey;
                configService.set('appspace', 'apikey', userInfo.apikey);
            }
            if (config.token) {
                userInfo.token = config.token;
            }
        }

        function loginCloud(username, password) {
            var promise = $q.defer();
            var handler = '{{appspace-cloud-api-url}}/api/v1/appspace/token';

            httpWrapper.apiPost(handler, {User: {Username: username, Password: password}}, {'Content-Type': 'application/json'}).then(function(result) {
                var data = result.data;
                if (data && data.TokenKey) {
                    promise.resolve(data.TokenKey);
                } else {
                    promise.reject('invalid data structure');
                }
            }, function(err) {
                promise.reject(err);
            });

            return promise.promise;
        }

        function retrieveTokenInstances(token) {
            var promise = $q.defer();
            var handler = '{{appspace-cloud-api-url}}/api/v1/appspace/token/' + token + '/instanceaccess';
            httpWrapper.apiGet(handler).then(function(result) {
                var data = result.data;
                if (data) {
                    promise.resolve(data);
                } else {
                    promise.reject('invalid data structure');
                }
            }, function(err) {
                promise.reject(err);
            });
            return promise.promise;
        }

        function login(username, password, accountId) {
            var deferred = $q.defer();

            var handler = '{{appspace-url}}/api/v1/token/request';

            var header = {
                'Content-Type': 'application/json'
            };

            if (accountId != undefined) {
                header.currentaccountid = accountId;
            }

            httpWrapper.apiPost(handler, {Authentication: {Username: username, Password: password}}, header).then(function(result) {
                var data = result.data;

                if (data.Errors && data.Errors.length > 0) {
                    deferred.reject(data.Errors);
                    return;
                }

                if (data.SecurityToken && data.Status && data.Status == 2) {// expired
                    // 900 = license expired
                    deferred.reject({status: 200, errorCode: ASERRORCODE.LICENSE_EXPIRED, errorMessage: 'License Expired'});
                    return;
                }

                privateUserInfo.username = username;
                privateUserInfo.password = password;

                if (data.SecurityToken) userInfo.token = data.SecurityToken;

                getCurrentUser().then(function(user) {
                    user.Current = user;
                    if (user.HomeNetworkId) userInfo.networkId = user.HomeNetworkId;
                    if (user.RoleIds) userInfo.roles = user.RoleIds;
                    if (user.AccountId) userInfo.accountId = user.AccountId;
                    deferred.resolve(userInfo);
                }, function(err) {
                    deferred.reject(err);
                });
            }, function(err) {
                deferred.reject(err);
            });

            return deferred.promise;
        }

        function logout() {
            var deferred = $q.defer();

            var handler = '{{appspace-url}}/api/v1/token/request';

            httpWrapper.apiDelete(handler).then(function(result) {
                resetPrivateInfo();
                resetUserInfo();
                deferred.resolve(true);
            }, function(err) {
                deferred.reject(err);
            });
            return deferred.promise;
        }

        function retrieveApiKey(username, password) {
            var deferred = $q.defer();

            if (!username && privateUserInfo.username) {
                username = privateUserInfo.username;
            }
            if (!password && privateUserInfo.password) {
                password = privateUserInfo.password;
            }
            var handler = '';
            var get;
            var onResult;
            var onError = function(err) {
                deferred.reject(err);
            };
            if (userInfo.accountId) {
                handler = '{{appspace-url}}/api/v1/core/accounts/' + userInfo.accountId;
                get = httpWrapper.apiGet;
                onResult = function(result) {
                    if (result.data && result.data.Account && result.data.Account.APIKey) {
                        var apikey = result.data.Account.APIKey;
                        setUserInfo({apikey: apikey});
                        deferred.resolve(apikey);
                    } else {
                        deferred.reject('Unable to retrieve API key through core API');
                    }
                };
            } else {
                handler = '{{appspace-url}}/integrationwebservices/ApiHandler.ashx?operation=retrieveapikey&username=' + encodeURIComponent(username) + '&password=' + encodeURIComponent(password);
                get = httpWrapper.handlerGet;
                onResult = function(result) {
                    if (result.data != '') {
                        setUserInfo({apikey: result.data});
                        deferred.resolve(result.data);
                    } else {
                        deferred.reject('API key is empty');
                    }
                };
            }
            get(handler).then(onResult, onError);
            return deferred.promise;
        }

        function startValidateToken(interval) {
            if (timerValidateTokenPromise) {
                $timeout.cancel(timerValidateTokenPromise);
            }
            var useInterval = 30000;
            if (interval) {
                useInterval = interval;
            }
            timerValidateTokenPromise = $timeout(function() {
                validateCurrentToken().then(function(res) {
                    startValidateToken();
                }, function(err) {

                });
            }, useInterval);
        }

        function stopValidateToken() {
            if (timerValidateTokenPromise) {
                $timeout.cancel(timerValidateTokenPromise);
            }
        }

        function validateCurrentToken() {
            var promise = $q.defer();
            var handler = '{{appspace-url}}/api/v1/token/request';
            var token = userInfo.token;
            httpWrapper.apiPut(handler, {Authentication: {SecurityToken: token}}).then(function(result) {
                if (result && result.data) {
                    promise.resolve(result.data);
                } else {
                    promise.resolve(result);
                }
            }, function(err) {
                promise.reject(err);
            });
            return promise.promise;
        }

        function getUserProfilePic(userId) {
            var handler = configService.get('appspace', 'appspaceURL') + '/api/v1/core/users/' + userId + '/picture';
            return handler;
        }

        function getCurrentUser() {
            var deferred = $q.defer();
            var handler = '{{appspace-url}}/api/v1/core/users/current';

            httpWrapper.apiGet(handler).then(function(result) {
                var data = result.data;

                if (data && data.User) {
                    deferred.resolve(data.User);
                    return;
                }

                if (data.Errors && data.Errors.length > 0) {
                    deferred.reject(data.Errors);
                    return;
                }

                deferred.reject('Failed to get current user data');
            }, function(err) {
                deferred.reject(err);
            });
            return deferred.promise;
        }

        return {
            getUserInfo: getUserInfo,
            setUserInfo: setUserInfo,
            loginCloud: loginCloud,
            retrieveTokenInstances: retrieveTokenInstances,
            login: login,
            logout: logout,
            retrieveApiKey: retrieveApiKey,
            startValidateToken: startValidateToken,
            stopValidateToken: stopValidateToken,
            getUserProfilePic: getUserProfilePic,
            getCurrentUser: getCurrentUser,
            resetUserInfo: resetUserInfo
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('appspaceAPI');

    app.service('deviceAPIService', ["$q", "$injector", "dataUtil", "HttpWrapper", "authenticationLibraryService", "authenticationAPIService", "httpService", "coreConfigurationService", "STORAGE_COLLECTION_KEY", "FEATURE_CODE", "STORAGE_KEY", "halPlatformService", function(
        $q,
        $injector,
        dataUtil,
        HttpWrapper,
        authenticationLibraryService,
        authenticationAPIService,
        httpService,
        coreConfigurationService,
        STORAGE_COLLECTION_KEY,
        FEATURE_CODE,
        STORAGE_KEY,
        halPlatformService) {
        var STATUS_CODE = {
            OK: 200
        };
        var authService = authenticationAPIService;
        var httpWrapper = new HttpWrapper();
        var DELETE_DEVICE_URL = '/api/v3/devices/me';
        var UPDATE_DEVICE_NAME_URL = 'api/v3/networks/devices/';
        var licenseHandlerUrl = '{{appspace-url}}/integrationwebservices/LicenseHandler.ashx';
        var devicesApiUrl = '{{appspace-url}}/api/v1/core/devices/';
        var configScriptHandlerUrl = '{{appspace-url}}/integrationwebservices/ConfigScriptHandler.ashx';
        var nodeServiceUrl = '{{appspace-url}}/medianetintegrationservice/httpnodeservices.ashx';
        var deviceIdentityNetworkApiUrl = '{{appspace-api-url}}/api/v3/networks/devices/{{device-id}}/identity';
        var deviceIdentityDeviceApiUrl = '{{appspace-api-url}}/api/v3/devices/{{device-id}}/identity';
        var deleteDeviceApiUrl = '{{appspace-api-url}}' + DELETE_DEVICE_URL;
        var constellationTokensApiUrl = '{{appspace-api-url}}/api/v3/devices/{{device-id}}/token';
        var claimDeviceApiUrl = '{{appspace-api-url}}/api/v3/devices/integrations/devices/me/claim';
        var BEARER_AUTHORIZATION_HEADER = 'Bearer ';
        var telemetryService;

        function generateUrl(url, params, ignoreBypassCache) {
            params = params || {};
            if (!ignoreBypassCache) {
                params['_noCacheId'] = new Date().getTime();
            }

            var urlParams = '';
            for (var paramsKey in params) {
                // ignore empty property
                if (!params[paramsKey]) {
                    continue;
                }

                urlParams += (!urlParams ? '?' : '&') + paramsKey + '=' + params[paramsKey];
            }

            return url + urlParams;
        }

        function retrieveDeviceGuid(machineID) {
            var promise = $q.defer();
            var handler = generateUrl(licenseHandlerUrl,
                {
                    'query': 'license.getdeviceguid',
                    'api': encodeURIComponent(authService.getUserInfo().apikey),
                    'di': encodeURIComponent(machineID)
                }
            );
            httpWrapper.handlerGet(handler).then(function(result) {
                if (result && result.data && result.data.toLowerCase().indexOf('guid should contain') >= 0) {
                    promise.reject(result.data);
                    return;
                }

                promise.resolve(result.data);
            }, promise.reject);

            return promise.promise;
        }

        function deleteDeviceByJwt(jwt) {
            if (!jwt) {
                return $q.reject('Device JWT is undefined');
            }

            return httpWrapper.handlerDelete(deleteDeviceApiUrl, undefined, {
                headers: {
                    'x-appspace-client-token': jwt
                }
            });
        }

        function deleteDeviceByConstellationTokens(tokens) {
            return authenticationLibraryService.requestConstellationService(
                DELETE_DEVICE_URL, tokens, {
                    method: 'DELETE'
                }
            );
        }

        function deleteDevice(policy) {
            policy = policy || {};
            if (policy.tokens) {
                return deleteDeviceByConstellationTokens(policy.tokens);
            }

            return deleteDeviceByJwt(policy.jwt);
        }

        function createDevice(policy) {
            if (!policy ||
                !policy.policy_id ||
                !policy.registration_url ||
                !policy.device_name) {
                return $q.reject('Invalid policy');
            }

            var deferred = $q.defer();
            var onFailure = function(error) {
                deferred.reject(error);
            };

            var registrationInfo = {
                policyId: policy.policy_id,
                deviceName: policy.device_name,
                fingerprint: policy.fingerprint,
                deviceIp: policy.device_ip
            };

            halPlatformService.getDeviceInfo().then(function(deviceInfo) {
                registrationInfo.deviceType = deviceInfo.appspace.deviceType;
                registrationInfo.applicationVersion = deviceInfo.software.appVersion;
                registrationInfo.serialNumber = deviceInfo.hardware.serialNumber;
                registrationInfo.macAddress = deviceInfo.hardware.macAddress;
                registrationInfo.referenceDeviceId = deviceInfo.appspace.referenceDeviceId;
                registrationInfo.integrationType = deviceInfo.appspace.integrationType;
                registrationInfo.includeRefreshToken = Boolean(halPlatformService.getFeatureSupport(FEATURE_CODE.REGISTRATION_TOKEN_REQUIRED));
                httpWrapper.handlerPost(policy.registration_url, registrationInfo).then(function(result) {
                    var data = (result || {}).data;
                    deferred.resolve((data || {}).result);
                }, onFailure);
            }, function() {
                onFailure('Failed to retrieve device information');
            });
            return deferred.promise;
        }

        function isDeviceBounded(deviceGuid) {
            var promise = $q.defer();
            var handler = generateUrl(licenseHandlerUrl,
                {
                    'query': 'license.isdevicebounded',
                    'api': encodeURIComponent(authService.getUserInfo().apikey),
                    'dguid': encodeURIComponent(deviceGuid)
                }
            );
            httpWrapper.handlerGet(handler).then(function(result) {
                promise.resolve(result.data && result.data.toLowerCase() == 'true');
            }, promise.reject);

            return promise.promise;
        }

        function unbindDevice(deviceGuid) {
            var promise = $q.defer();
            var handler = generateUrl(licenseHandlerUrl,
                {
                    'query': 'license.unbinddevice',
                    'api': encodeURIComponent(authService.getUserInfo().apikey),
                    'dguid': encodeURIComponent(deviceGuid)
                }
            );
            httpWrapper.handlerGet(handler).then(function(result) {
                promise.resolve(result.data);
            }, promise.reject);

            return promise.promise;
        }

        // if success, returns dei (device identifier?)
        // if error, will return text containing the word Error
        // if max number exceeded, will return empty
        function bindDevice(deviceGuid, deviceName, deviceIP) {
            var promise = $q.defer();
            var handler = generateUrl(licenseHandlerUrl,
                {
                    'query': 'license.binddevice',
                    'api': encodeURIComponent(authService.getUserInfo().apikey),
                    'dguid': encodeURIComponent(deviceGuid),
                    'dname': encodeURIComponent(deviceName),
                    'dip': encodeURIComponent(deviceIP),
                    'netid': encodeURIComponent(authService.getUserInfo().networkId || '')
                }
            );
            httpWrapper.handlerGet(handler).then(function(result) {
                handleBindDeviceResult(result, promise);
            }, promise.reject);

            return promise.promise;
        }

        function bindDeviceByPolicy(policy) {
            var promise = $q.defer();
            var handler = generateUrl(licenseHandlerUrl,
                {
                    'query': 'license.binddevice'
                }
            );
            httpWrapper.handlerPost(handler, policy).then(function(result) {
                handleBindDeviceResult(result, promise);
            }, promise.reject);

            return promise.promise;
        }

        function handleSendDeviceIdentityResult(result, promise) {
            if ((result || {}).status !== STATUS_CODE.OK) {
                promise.reject();
                return;
            }

            promise.resolve();
        }

        function sendIdentityNetworkService(deviceIdentity) {
            return sendIdentityService(deviceIdentity, deviceIdentityNetworkApiUrl);
        }

        function sendIdentityDeviceService(deviceIdentity) {
            return sendIdentityService(deviceIdentity, deviceIdentityDeviceApiUrl);
        }

        function sendIdentityService(deviceIdentity, apiUrl) {
            if (!deviceIdentity) {
                return $q.reject('Missing device identity.');
            }

            var promise = $q.defer();
            httpWrapper.handlerPut(apiUrl, deviceIdentity).then(function(result) {
                handleSendDeviceIdentityResult(result, promise);
            }, promise.reject);

            return promise.promise;
        }

        function getTelemetryService() {
            if (!telemetryService) {
                telemetryService = $injector.get('telemetryService');
            }

            return telemetryService;
        }

        function handleClaimConstellationTokensResult(result, promise, deviceIdentity) {
            result = result || {};
            var telemetryPayload = {
                module: 'device-authorization',
            };
            var reportTelemetry = function() {
                getTelemetryService().addToQueue(telemetryPayload);
            };

            if (result.status !== STATUS_CODE.OK) {
                telemetryPayload.type = 'jwt-claim-failure';
                telemetryPayload.description = 'JWT failed to claimed';
                telemetryPayload.metadata = {
                    'request_payload': JSON.stringify(deviceIdentity),
                    'response': JSON.stringify(result),
                };
                reportTelemetry();
                promise.reject('Failed due to status code ' + result.status);
                return;
            }

            telemetryPayload.type = 'jwt-claim';
            telemetryPayload.description = 'JWT claimed successfully.';
            reportTelemetry();
            promise.resolve(result.data);
        }

        function claimConstellationTokens(deviceIdentity) {
            var skipAttempt = function(message) {
                return $q.reject({
                    error: message,
                    isAttemptSkipped: true
                });
            };

            if (!deviceIdentity) {
                return skipAttempt('Missing device identity.');
            }

            if (!deviceIdentity.appspace || !deviceIdentity.appspace.accountId || !deviceIdentity.appspace.instanceGuid) {
                // Don't attempt to request for tokens if we don't have the required keys
                return skipAttempt('Missing Appspace account ID or instance GUID in device identity.');
            }

            var promise = $q.defer();
            var handleResult = function(result) {
                handleClaimConstellationTokensResult(result, promise, deviceIdentity);
            };

            httpWrapper.handlerPost(constellationTokensApiUrl, deviceIdentity).then(handleResult, handleResult);
            return promise.promise;
        }

        function handleBindDeviceResult(result, promise) {
            result.data = result.data || '';
            var data = result.data.toLowerCase();
            if (!data || data.indexOf('license limit has been reached') > -1 || data.indexOf('error') > -1 || data == 'api must not be null') {
                promise.reject(result.data || 'max exceeded');
                return;
            }

            promise.resolve(result.data);
        }

        // returns guid
        function retrievePlayerGuid(machineId, dei) {
            var promise = $q.defer();
            var handler = generateUrl(licenseHandlerUrl,
                {
                    'query': 'license.getdevicenodeid',
                    'api': encodeURIComponent(authService.getUserInfo().apikey),
                    'di': encodeURIComponent(machineId),
                    'dei': dei
                }
            );
            httpWrapper.handlerGet(handler).then(function(result) {
                if (!result.data) {
                    promise.reject('no result.data');
                    return;
                }

                if (result.data.toLowerCase().indexOf('error') > -1) {
                    promise.reject(result.data);
                    return;
                }

                promise.resolve(result.data);
            }, promise.reject);

            return promise.promise;
        }

        function retrievePlayerIntIdFromGuid(nodeId) {
            var promise = $q.defer();
            var handler = generateUrl(devicesApiUrl,
                {
                    'guids': encodeURIComponent(nodeId),
                    'includeappisnull': true
                }
            );
            httpWrapper.apiGet(handler).then(function(result) {
                if (result.Errors) {
                    promise.reject(result.Errors);
                    return;
                }

                promise.resolve(result.data);
            }, promise.reject);

            return promise.promise;
        }

        function boundDeviceToApplication(nodeidInt, applicationid) {
            var promise = $q.defer();
            var handler = generateUrl(devicesApiUrl);
            var headers = {
                'Content-Type': 'application/json'
            };

            var data = {
                Devices: [{
                    Id: nodeidInt,
                    ApplicationId: applicationid
                }]
            };

            httpWrapper.apiPut(handler, data, headers).then(function(result) {
                if (result.Errors) {
                    promise.reject(result.Errors);
                    return;
                }

                promise.resolve(result.Devices);
            }, promise.reject);

            return promise.promise;
        }

        function setProperty(nodeId, key, value) {
            var keyType = 'player';
            var promise = $q.defer();
            var handler = generateUrl(configScriptHandlerUrl,
                {
                    'operation': 'setproperty',
                    'key': encodeURIComponent(key),
                    'value': encodeURIComponent(value),
                    'type': encodeURIComponent(keyType),
                    'id': encodeURIComponent(nodeId)
                }
            );
            httpWrapper.handlerGet(handler).then(function(result) {
                promise.resolve(result.data);
            }, promise.reject);

            return promise.promise;
        }

        function sendHealthStatus(nodeId, status, nodeIp) {
            var promise = $q.defer();
            var handler = nodeServiceUrl +
                '?nodeid=' + encodeURIComponent(nodeId) +
                '&nodeip=' + encodeURIComponent(nodeIp) +
                '&nodestatus=' + encodeURIComponent(status) +
                '&a=updatestatus';
            httpService.get(handler).then(function(result) {
                promise.resolve(result.data);
            }, promise.reject);

            return promise.promise;
        }

        function sendInsyncStatus(nodeId) {
            var promise = $q.defer();
            var handler = generateUrl(configScriptHandlerUrl,
                {
                    'operation': 'insync',
                    'id': encodeURIComponent(nodeId)
                }
            );
            httpWrapper.handlerGet(handler).then(function(result) {
                promise.resolve(result.data);
            }, promise.reject);

            return promise.promise;
        }

        function sendPlayerVersion(version) {
            var promise = $q.defer();
            var handler = generateUrl(configScriptHandlerUrl,
                {
                    'operation': 'setproperty',
                    'key': encodeURIComponent('Player.Software.Version'),
                    'value': encodeURIComponent(version),
                    'type': 'player',
                    'id': '{{device-id}}'
                }
            );
            httpWrapper.handlerGet(handler).then(function(res) {
                promise.resolve(res.data);
            }, function(err) {
                promise.reject(err);
            });

            return promise.promise;
        }

        function getPlayerVersion() {
            return getProperty('Player.Software.Version', 'player');
        }

        function getPlayerServiceApi() {
            return getProperty('Player.Services.Api', 'system');
        }

        function getPlayerModelName() {
            return getProperty('Player.Model.Name', 'player');
        }

        function getProperty(key, type) {
            var promise = $q.defer();
            var handler = generateUrl(configScriptHandlerUrl,
                {
                    'operation': 'getproperty',
                    'key': encodeURIComponent(key),
                    'type': type,
                    'id': '{{device-id}}'
                }
            );
            httpWrapper.handlerGet(handler).then(function(res) {
                promise.resolve(res.data);
            }, function(err) {
                promise.reject(err);
            });

            return promise.promise;
        }

        function getDeviceTokenService() {
            return $injector.get('deviceTokenService');
        }

        function updateDeviceName(deviceName) {
            if (!deviceName) {
                return $q.reject('Undefined device name');
            }

            var deviceId = coreConfigurationService.get(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.DEVICE_ID);
            if (!deviceId) {
                return $q.reject('Device id does not exist');
            }

            var tokens = getDeviceTokenService().getConstellationTokens();
            var data = {
                name: deviceName
            };
            return authenticationLibraryService.requestConstellationService(
                UPDATE_DEVICE_NAME_URL + deviceId, tokens, {
                    method: 'PUT',
                    data: JSON.stringify(data)
                }
            );
        }

        function claimDevice(data) {
            if (!data) {
                return $q.reject('Invalid configuration.');
            }

            var deferred = $q.defer();
            var payload = {
                deviceType: data.deviceType,
                deviceName: data.deviceName,
                applicationVersion: data.applicationVersion,
                deviceip: data.deviceip,
                fingerprint: data.fingerprint,
            };
            var configuration = {
                headers: {
                    Authorization: BEARER_AUTHORIZATION_HEADER + data.accessToken
                }
            };
            httpWrapper.handlerPost(claimDeviceApiUrl, payload, undefined, configuration).then(
                deferred.resolve, function(error) {
                    if (dataUtil.isObject(error)) {
                        error.isServerError = httpWrapper.isServerError(error.status);
                    }

                    deferred.reject(error);
                }
            );
            return deferred.promise;
        }

        return {
            retrieveDeviceGuid: retrieveDeviceGuid,
            createDevice: createDevice,
            deleteDevice: deleteDevice,
            isDeviceBounded: isDeviceBounded,
            unbindDevice: unbindDevice,
            bindDevice: bindDevice,
            retrievePlayerGuid: retrievePlayerGuid,
            retrievePlayerIntIdFromGuid: retrievePlayerIntIdFromGuid,
            updateDeviceName: updateDeviceName,
            boundDeviceToApplication: boundDeviceToApplication,
            setProperty: setProperty,
            sendHealthStatus: sendHealthStatus,
            sendInsyncStatus: sendInsyncStatus,
            sendPlayerVersion: sendPlayerVersion,
            getPlayerVersion: getPlayerVersion,
            bindDeviceByPolicy: bindDeviceByPolicy,
            sendIdentityNetworkService: sendIdentityNetworkService,
            sendIdentityDeviceService: sendIdentityDeviceService,
            claimConstellationTokens: claimConstellationTokens,
            getPlayerServiceApi: getPlayerServiceApi,
            getPlayerModelName: getPlayerModelName,
            claimDevice: claimDevice,
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('appspaceAPI');

    app.service(
        'signsAPIService',
        ["$q", "HttpWrapper", "deviceRegistrationService", "telemetryService", "sessionService", "UrlUtil", "versionUtil", "ASERRORCODE", "httpService", "deviceTokenService", "embedProfileService", function(
            $q,
            HttpWrapper,
            deviceRegistrationService,
            telemetryService,
            sessionService,
            UrlUtil,
            versionUtil,
            ASERRORCODE,
            httpService,
            deviceTokenService,
            embedProfileService) {
            var httpWrapper = new HttpWrapper();
            var SUPPORTED_SCRIPT_AUTHORIZATION_VERSIONS = {
                CLOUD: {
                    MIN: '8.1.0-ac.28',
                    MAX: '8.1.0'
                },
                GLOBAL: '8.1.0'
            };

            function sendDownloadInitStatus(totalFiles) {
                var promise = $q.defer();
                var playerId = deviceRegistrationService.getPlayerId();
                if (!playerId) {
                    promise.reject('player id is empty');
                } else {
                    var url = '{{appspace-url}}/api/v1/appspace/networks/transfers/{{device-id}}/init/' + totalFiles;
                    httpWrapper.handlerGet(url).then(function(res) {
                        var data = (res && res.data) ? res.data : res;
                        promise.resolve(data);
                    }, function(err) {
                        promise.reject(err);
                    });
                }

                return promise.promise;
            }

            // currentIndex
            // status : 1-Started,2-Completed,3-Failed
            // filename
            function sendDownloadStatus(param) {
                var promise = $q.defer();
                var playerId = deviceRegistrationService.getPlayerId();
                if (!playerId) {
                    promise.reject('player id is empty');
                } else {
                    var status = 'Started';
                    switch (param.status) {
                        case 1:
                            status = 'Started';
                            break;
                        case 2:
                            status = 'Completed';
                            break;
                        case 3:
                        default:
                            status = 'Failed';
                            break;
                    }

                    var url = '{{appspace-url}}/api/v1/appspace/networks/transfers/{{device-id}}/' + param.currentIndex + ',' + param.filename + '/' + status;
                    httpWrapper.handlerGet(url).then(function(res) {
                        var data = (res && res.data) ? res.data : res;
                        promise.resolve(data);
                    }, function(err) {
                        promise.reject(err);
                    });
                }

                return promise.promise;
            }

            function retrieveSynchronizeSchedule() {
                var promise = $q.defer();

                var url = '{{appspace-url}}/integrationwebservices/configscripthandler.ashx?operation=getproperty&key=CDNAgent.SyncSchedule&type=player&id={{device-id}}';
                var onReceivedSyncSchedule = function(res) {
                    promise.resolve(res.data);
                };

                var onReceivedSyncScheduleFail = function(err) {
                    promise.reject(err);
                };

                var config = {
                    // overwrite transform response to stop auto parse json
                    transformResponse: [function(data) {
                        return data;
                    }]
                };
                httpWrapper.handlerGet(url, null, config).then(onReceivedSyncSchedule, onReceivedSyncScheduleFail);
                return promise.promise;
            }

            function constructTimestampUrl(config) {
                var customUrl = getCustomUrl('timestampurl', config);
                if (customUrl) {
                    return customUrl;
                }

                if (!config.appid && !config.deviceguid) {
                    return;
                }

                var appspaceUrl = getAppspaceUrlFromConfig(config);
                var sessionId = config.sessionid ? '&sessionid=' + config.sessionid : '';
                var url = appspaceUrl + '/integrationwebservices/configscripthandler.ashx?operation=timestamp' + sessionId;
                url += config.appid ? ('&appid=' + config.appid) : ('&id=' + config.deviceguid);
                return url;
            }

            function getAppspaceUrlFromConfig(config) {
                var configUrl = config && config.appspaceurl ? config.appspaceurl.replace(/\/$/, '') : '';
                return configUrl || '{{appspace-url}}';
            }

            function constructScriptUrl(idKey, id) {
                var customUrl = getCustomUrl('scripturl');
                if (customUrl) {
                    return customUrl;
                }

                var timestamp = new Date().getTime();
                var url = '{{appspace-url}}/integrationwebservices/configscripthandler.ashx?operation=script&_noCacheId=' + timestamp;
                url += '&' + idKey + '=' + id;
                return url;
            }

            function getCustomUrl(key, config) {
                var customUrl = sessionService.getUrlParameters()[key];
                if (!customUrl) {
                    return;
                }

                // if custom url starts with /, prepend it with appspace URL
                if (customUrl.indexOf('/') == 0) {
                    return getAppspaceUrlFromConfig(config) + customUrl;
                }

                // if custom url doesn't have http or https, prepend it with https
                return UrlUtil.fixUrlProtocol(customUrl);
            }

            function retrievePlayerTimestamp(config) {
                // 920 = generic timestamp error
                // 921 = device not found
                // 922 = empty timestamp
                var url = constructTimestampUrl(config);
                if (!url) {
                    return $q.reject({errormessage: 'no device guid'});
                }

                var deferred = $q.defer();
                httpService.get(url).then(function(res) {
                    if (!res) {
                        deferred.reject({errormessage: 'error when retrieving timestamp', errorCode: ASERRORCODE.ERROR_RETRIEVE_TIMESTAMP});
                        return;
                    }

                    if (!res.data) {
                        deferred.reject({errormessage: 'emptytimestamp', errorCode: ASERRORCODE.EMPTY_TIMESTAMP, error: res});
                        return;
                    }

                    deferred.resolve(res.data);
                }, function(err) {
                    var errormessage = 'error when retrieving timestamp';
                    var errorcode = ASERRORCODE.ERROR_RETRIEVE_TIMESTAMP;
                    if (err && err.status == 410) {
                        errormessage = 'device not found';
                        errorcode = ASERRORCODE.DEVICE_NOT_FOUND;
                    }

                    deferred.reject({errormessage: errormessage, errorCode: errorcode, error: err});
                });
                return deferred.promise;
            }

            function retrieveApplicationTimestamp(config) {
                var url = constructTimestampUrl(config);
                if (!url) {
                    return $q.reject({errormessage: 'no app id'});
                }

                var deferred = $q.defer();
                var handlerError = function(err) {
                    deferred.reject({errrormessage: 'error when retrieving timestamp', error: err, errorCode: ASERRORCODE.ERROR_RETRIEVE_TIMESTAMP});
                };

                httpService.get(url).then(function(res) {
                    if (res && res.data != '') {
                        deferred.resolve(res.data);
                        return;
                    }

                    handlerError(res);
                }, handlerError);
                return deferred.promise;
            }

            function isAuthorizationHeaderRequired(tokens) {
                var isAuthorizationSupported = versionUtil.isAppspaceVersionValid(
                    versionUtil.getAppspaceVersionFromCache(),
                    SUPPORTED_SCRIPT_AUTHORIZATION_VERSIONS);
                var isProfileSupported = !embedProfileService.isWebPublishingMode();
                return Boolean(isAuthorizationSupported && isProfileSupported && tokens && tokens.accessToken);
            }

            function retrieveScript(key, value, processResponseCallback) {
                var url = constructScriptUrl(key, value);
                var tokens = deviceTokenService.getConstellationTokens();
                var deferred = $q.defer();
                var finalize = function(success, result) {
                    if (processResponseCallback) {
                        processResponseCallback(success, result);
                    }

                    if (!success || !result || result.data === '') {
                        deferred.reject(result);
                        return;
                    }

                    deferred.resolve(result.data);
                };

                var configuration = {
                    headers: {}
                };
                if (isAuthorizationHeaderRequired(tokens)) {
                    configuration.headers['Authorization'] = 'Bearer ' + tokens.accessToken;
                }

                httpWrapper.handlerGet(url, undefined, configuration).then(function(result) {
                    finalize(true, result);
                }, function(error) {
                    finalize(false, error);
                });
                return deferred.promise;
            }

            function retrievePlayerScript(playerGuid, metadata) {
                var requestCreatedAt = moment().toISOString();
                var telemetryPayload = {
                    module: 'device-channel-synchronization',
                    type: 'device-script-retrieval',
                    description: 'Device script retrieval',
                    metadata: metadata || {},
                };
                telemetryPayload.metadata.request = {
                    createdAt: requestCreatedAt,
                };
                telemetryPayload.metadata.response = {};
                var sendTelemetryEvent = function(success, response) {
                    var requestCompletedAt = moment().toISOString();
                    var responseSize = success ? response.headers('content-length') : 0;
                    telemetryPayload.metadata.request.completedAt = requestCompletedAt;
                    telemetryPayload.metadata.response.duration = moment(requestCompletedAt).valueOf() - moment(requestCreatedAt).valueOf();
                    telemetryPayload.metadata.response.statusCode = response.status;
                    telemetryPayload.metadata.response.size = responseSize;
                    telemetryService.addToQueue(telemetryPayload);
                };

                return retrieveScript('id', playerGuid, sendTelemetryEvent);
            }

            function retrieveApplicationScript(appId) {
                return retrieveScript('appid', appId);
            }

            function sendPassiveMetric(metrics) {
                var promise = $q.defer();

                if (!metrics || metrics.length == 0) {
                    promise.reject();
                } else {
                    // convert metric object to post data
                    var param = '';
                    for (var i = 0; i < metrics.length; i++) {
                        var metric = metrics[i];
                        if (!metric.playerGuid || !metric.applicationId || !metric.startTime || !metric.endTime || !metric.contentId || !metric.scheduleId || !metric.mediaZoneDatasourceId || !metric.widgetDatasourceId) {
                            continue;
                        }

                        param += 'cid:' + metric.contentId; // contentid
                        param += ';plays:' + metric.startTime.getTime().toString(); // play start
                        param += ';playe:' + metric.endTime.getTime().toString(); // play end
                        param += ';pid:' + metric.playerGuid; // player id
                        param += ';aid:' + metric.applicationId; // app id
                        param += ';sid:' + metric.scheduleId; // schedule id
                        param += ';mzid:' + metric.mediaZoneDatasourceId; // media zone datasource ID
                        param += ';wid:' + metric.widgetDatasourceId; // Widget data source ID

                        if (i != metrics.length - 1) {
                            param += ',';
                        }
                    }

                    if (param != '') {
                        param = 'metric=' + param;
                        var url = '{{appspace-url}}/medianetintegrationservice/httpplayoutmetricsservices.ashx';
                        url += '?' + param;
                        var header = {};
                        header['Content-Type'] = 'text/plain';
                        httpWrapper.apiPost(url, '', header).then(function(res) {
                            // metric fail to add
                            if (res && res.data.trim() == 'metric successful added') {
                                promise.resolve(res);
                            } else {
                                promise.reject(res);
                            }
                        }, function(err) {
                            promise.reject(err);
                        });
                    }
                }

                return promise.promise;
            }

            function sendInteractiveMetric(metrics) {
                var promise = $q.defer();

                if (!metrics || metrics.length == 0) {
                    promise.reject();
                } else {
                    // convert metric object to post data
                    var param = '';
                    for (var i = 0; i < metrics.length; i++) {
                        var metric = metrics[i];
                        param += 'mn:' + metric.metricName; // metric name
                        param += ';pid:' + metric.playerGuid; // player id
                        param += ';aid:' + metric.applicationId; // app id
                        param += ';ed:' + metric.eventDate.getTime().toString(); // event date

                        if (metric.obj) {
                            param += ';oid:' + metric.obj.id;
                            param += ';ov:' + encodeURIComponent(metric.obj.value);
                        }

                        if (metric.parameters && metric.parameters.length > 0) {
                            var intParam = '';
                            for (var j = 0; j < metric.parameters.length; j++) {
                                var p = metric.parameters[j];
                                intParam += p.key + '=' + p.value;

                                if (j != metric.parameters.length - 1) {
                                    intParam += ';';
                                }
                            }

                            if (intParam != '') {
                                param += ';par:' + encodeURIComponent(intParam);
                            }
                        }

                        if (i != metrics.length - 1) {
                            param += ',';
                        }
                    }

                    if (param != '') {
                        param = 'metricsdata=' + param + '&query=interactivemetrics.savemetrics';
                        var url = '{{appspace-url}/integrationwebservices/metricshandler.ashx';
                        url += '?' + param;
                        var header = {};
                        header['Content-Type'] = 'text/plain';
                        httpWrapper.apiPost(url, '', header).then(function(res) {
                            promise.resolve(res);
                        }, function(err) {
                            promise.reject(err);
                        });
                    }
                }

                return promise.promise;
            }

            function retrieveApplications(query) {
                var deferred = $q.defer();

                var handler = '{{appspace-url}}/api/v1/core/applications/';

                if (query) {
                    if (query.applicationid) {// array
                        if (handler.indexOf('?') == -1) {
                            handler += '?';
                        } else {
                            handler += '&';
                        }

                        handler += 'appids=' + query.applicationid.toString();
                    }

                    if (query.appname) {// string
                        if (handler.indexOf('?') == -1) {
                            handler += '?';
                        } else {
                            handler += '&';
                        }

                        handler += 'appname=' + query.appname;
                    }

                    if (query.networkid) {// array
                        if (handler.indexOf('?') == -1) {
                            handler += '?';
                        } else {
                            handler += '&';
                        }

                        handler += 'networkids=' + query.networkid.toString();
                    }

                    if (query.max != undefined) {// max result
                        if (handler.indexOf('?') == -1) {
                            handler += '?';
                        } else {
                            handler += '&';
                        }

                        handler += 'rpp=' + parseInt(query.max, 10);
                    }
                }

                httpWrapper.apiGet(handler).then(function(result) {
                    if (result.data.Errors && result.data.Errors.length > 0) {
                        deferred.reject(result.data.Errors);
                        return;
                    }

                    deferred.resolve(result.data.Applications);
                }, function(err) {
                    deferred.reject(err);
                });
                return deferred.promise;
            }

            return {
                sendDownloadInitStatus: sendDownloadInitStatus,
                sendDownloadStatus: sendDownloadStatus,
                retrieveSynchronizeSchedule: retrieveSynchronizeSchedule,
                retrievePlayerTimestamp: retrievePlayerTimestamp,
                retrieveApplicationTimestamp: retrieveApplicationTimestamp,
                retrievePlayerScript: retrievePlayerScript,
                retrieveApplicationScript: retrieveApplicationScript,
                sendPassiveMetric: sendPassiveMetric,
                sendInteractiveMetric: sendInteractiveMetric,
                retrieveApplications: retrieveApplications,

            };
        }]);
})(window.angular);

(function($angular) {

    "use strict";

    var app = $angular.module('appspaceAPI');

    app.service("tokenAPIService", ["$http", "$q", "$timeout", "HttpWrapper", "coreConfigurationService", function($http, $q, $timeout, HttpWrapper, coreConfigurationService) {
        var configService = coreConfigurationService;
        var httpWrapper = new HttpWrapper();
        
        //SEANN to implement (deployment date to cap-cloud: 27th)
        //Retrieves the accounts (and their authentication details) by username.
        //Public method with no token required
        this.queryAccountAccess = function(username, cloudURL) {
            
            var deferred = $q.defer();
            //Sample response if user does not exist
            var doesNotExist = {
                username: username,
                accounts: []
            };
            //Sample response if user exists and has access to multiple accounts
            var ifUserExists = {
                username: username,
                accounts: [
                    {
                        instanceUrl: "https://cap-dev.dev.appspace.com",
                        authenticationUrl: "https://cap-dev.dev.appspace.com/sso?acc=XDDFDFD",
                        method: "SSO",
                        accountId: 2323
                    },
                    {
                        instanceUrl: "https://cloud.appspace.com",
                        authenticationUrl: "https://cloud.appspace.com",
                        method: "Cloud",
                        accountId: 54
                    },
                    {
                        instanceUrl: "https://cse.demo.appspace.com",
                        authenticationUrl: "https://cse.demo.appspace.com",
                        method: "Core",
                        accountId: 6645
                    }
                ]
            };
            
            if(username == "stan@appspace.com") {
                $timeout(function() {
                    deferred.resolve(ifUserExists);
                },500);
            }
            else {
                $timeout(function() {
                    deferred.resolve(doesNotExist);
                },500);
            }
            
            return deferred.promise;
        }
        
        //Pass us the iFrame we can control  (Syed: deployment date to cap-cloud: 27th)
        this.createTokenSSO = function(authenticationUrl, hostWindow, accountId) {
            var deferred = $q.defer();
            //Syed provides a URL that wraps the assertion in an iFrame
            //Syed to POST message on success or failure (including the token)
            $timeout(deferred.resolve, 1000);
            return deferred.promise;
        }
        
        //Authenticates with Core using the username, password and optionally a specific account id
        this.createCoreToken = function(username, password, accountId, coreUrl) {
           var deferred = $q.defer();

            var handler;
            if(coreUrl) {
                handler = coreUrl + "/api/v1/token/request";
            }
            else {
                handler = "{{appspace-url}}/api/v1/token/request";
            }
            var header = {
                'Content-Type': 'application/json'
            };

            //Is there a specific account we wish to login to (for users with multiple accounts on a server)
            if (accountId != undefined) {
                header.currentaccountid = accountId;
            }
            httpWrapper.apiPost(handler, { Authentication: { Username: username, Password: password } }, header).then(function(result) {
                var data = result.data;
                if (data.Errors && data.Errors.length > 0) {
                    deferred.reject(data.Errors);
                    return;
                }
                deferred.resolve(data);

            }, function(err) {
                deferred.reject(err);
            });
            return deferred.promise;
        }
        
        //Create an access token with a specific lifespan (in minutes).
        this.createCoreAccessToken = function(token, instanceUrl, lifespan) {
            var promise = $q.defer();
            var handler;
            if(instanceUrl) {
                handler = instanceUrl + "/api/v1/core/accesstokens";
            }
            else {
                handler = '{{appspace-url}}/api/v1/core/accesstokens';
            }
            var header = {
                'Token': token,
                'Content-Type': 'application/json'
            };
            
            httpWrapper.apiPost(handler, {"Lifespan" : lifespan }, header).then(function(result) {
              var data = result.data;
                if (data) {
                    promise.resolve(data);
                } else {
                    promise.reject('invalid data structure');
                }
            }, function(err) {
                promise.reject(err);
            })
            return promise.promise;
        }
        
        //Use to expire (kill) an existing long life access token
        //Already deployed
        this.expireCoreToken = function(token, instanceUrl) {
            var promise = $q.defer();
            var handler;
            if(instanceUrl) {
                handler = instanceUrl + "/api/v1/core/accesstokens/" + token;
            }
            else {
                handler = '{{appspace-url}}/api/v1/core/accesstokens/' + token;
            }
            
            httpWrapper.apiDelete(handler, null, null).then(function(result) {
              var data = result.data;
                if (data) {
                    promise.resolve(data);
                } else {
                    promise.reject('invalid data structure');
                }
            }, function(err) {
                promise.reject(err);
            })
            return promise.promise;
        }
        
        //This method will authenticate with the Appspace Cloud and return a token if successfull
        this.createCloudToken = function(username, password, cloudUrl) {
            var promise = $q.defer();
            var handler;
            if(cloudUrl) {
                 handler = cloudUrl + '/api/v1/appspace/token';
            }
            else {
                handler = '{{appspace-cloud-api-url}}/api/v1/appspace/token';
            }
            httpWrapper.apiPost(handler, { User: { Username: username, Password: password } }, { 'Content-Type': 'application/json' }).then(function(result) {
                var data = result.data;
                if (data && data.TokenKey) {
                    promise.resolve(data);
                } else {
                    promise.reject('invalid data structure');
                }
            }, function(err) {
                promise.reject(err);
            });

            return promise.promise;
        }
        
        //Use to expire (kill) an existing long life access token
        //Already deployed
        this.expireCloudToken = function(token, cloudUrl) {
            var promise = $q.defer();
            var handler;
            if(cloudUrl) {
                handler = instanceUrl + "/api/v1/appspace/token/" + token;
            }
            else {
                handler = '{{appspace-cloud-api-url}}/api/v1/appspace/token/' + token;
            }
            httpWrapper.apiDelete(handler, null, null).then(function(result) {
              var data = result.data;
                if (data) {
                    promise.resolve(data);
                } else {
                    promise.reject('invalid data structure');
                }
            }, function(err) {
                promise.reject(err);
            })
            return promise.promise;
        }
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var app = $angular.module('appspaceAPI');

    app.factory('HttpWrapper', ["$http", "$injector", "$q", "dataUtil", "coreConfigurationService", "networkContentRouterService", "AppspaceUrlUtil", function(
        $http,
        $injector,
        $q,
        dataUtil,
        coreConfigurationService, networkContentRouterService, AppspaceUrlUtil) {
        return (HttpWrapper);

        function HttpWrapper() {
            var authService = null;
            var configService = coreConfigurationService;
            var router = networkContentRouterService;
            var regService = null;
            var routerService = null;
            var networkService = null;

            var token = null;

            var defaultTimeout;
            var STATUS_CODE_RANGE = {
                SUCCESS: {
                    MIN: 200,
                    MAX: 299
                },
                SERVER_ERROR: {
                    MIN: 500,
                    MAX: 599
                }
            };

            var STATUS_CODE = {
                UNAUTHORIZED: 401,
                FORBIDDEN: 403
            };

            function init() {
                authService = $injector.get('authenticationAPIService');
                regService = $injector.get('deviceRegistrationService');
                networkService = $injector.get('halNetworkService');
                token = authService.getUserInfo().token;
                defaultTimeout = 5 * 60 * 1000;// 5 minutes
                initMapping();
            }

            function initMapping() {
                var mapAppspace = {
                    name: 'appspace-api-mapping',
                    map: function(url) {
                        if (!AppspaceUrlUtil.isAppspaceUrlReplacementRequired(url)) {
                            return $q.resolve(url);
                        }

                        var promise = $q.defer();
                        configService.isAppspaceReady().then(function() {
                            var res = AppspaceUrlUtil.replaceAppspaceUrl(url);
                            if (res) {
                                promise.resolve(res);
                            } else {
                                promise.reject('Unable to replace Appspace URL');
                            }
                        }, promise.reject);
                        return promise.promise;
                    }
                };

                var mapAppspaceContentHost = {
                    name: 'appspace-content-host-mapping',
                    map: function(url) {
                        var promise = $q.defer();
                        var res = AppspaceUrlUtil.replaceAppspaceContentHost(url);
                        if (res) {
                            promise.resolve(res);
                        } else {
                            promise.reject('Unable to replace content host mapping');
                        }

                        return promise.promise;
                    }
                };

                var mapAppspaceCloud = {
                    name: 'appspace-cloud-api-mapping',
                    map: function(url) {
                        var deferred = $q.defer();
                        if (url) {
                            if (url.indexOf('{{appspace-cloud-api-url}}') == -1) {
                                deferred.reject('does not match pattern');
                            } else {
                                url = url.split('{{appspace-cloud-api-url}}').join(configService.get('appspace', 'appspaceCloudApiURL'));
                                deferred.resolve(url);
                            }
                        } else {
                            deferred.reject('url is null or undefined');
                        }

                        return deferred.promise;
                    }
                };

                var mapDeviceid = {
                    name: 'device-id-mapping',
                    map: function(url) {
                        var deferred = $q.defer();

                        if (url) {
                            if (url.indexOf('{{device-id}}') == -1) {
                                deferred.reject('does not match pattern');
                            } else {
                                var playerId = regService.getPlayerId();
                                if (!playerId) {
                                    deferred.reject('player Id is empty');
                                } else {
                                    url = url.split('{{device-id}}').join(playerId);
                                    deferred.resolve(url);
                                }
                            }
                        } else {
                            deferred.reject('url is null or undefined');
                        }

                        return deferred.promise;
                    }
                };

                routerService = {
                    name: 'http-wrapper-routing-service',
                    maps: [mapAppspace, mapAppspaceContentHost, mapAppspaceCloud, mapDeviceid]
                };

                router.addService(routerService);
            }

            this.customHandler = function(handler, config, timeout) {
                init();
                if (!timeout) {
                    timeout = defaultTimeout;
                }

                return $q(function(resolve, reject) {
                    router.resolve(handler, routerService.name).then(function(url) {
                        var cfg = $angular.copy(config);
                        cfg.url = url;
                        if (timeout && !cfg.timeout) {
                            cfg.timeout = timeout;
                        }

                        resolve($http(cfg));
                    }, function(err) {
                        reject(err);
                    });
                });
            };

            function httpGet(handler, timeout, config, injectToken) {
                init();
                config = config || {};
                config.timeout = timeout || defaultTimeout;
                if (token && injectToken) {
                    config.headers = config.headers || {};
                    config.headers.Token = token;
                }

                var defer = $q.defer();

                router.resolve(handler, routerService.name).then(function(url) {
                    networkService.httpGet(url, config).then(defer.resolve, defer.reject);
                }, defer.reject);

                return defer.promise;
            }

            this.handlerGet = function(handler, timeout, config) {
                return httpGet(handler, timeout, config);
            };

            this.handlerHead = function(handler, timeout, config) {
                init();
                if (!timeout) {
                    timeout = defaultTimeout;
                }

                return $q(function(resolve, reject) {
                    router.resolve(handler, routerService.name).then(function(url) {
                        var cfg = {};
                        if (config) {
                            cfg = config;
                        }

                        cfg.url = url;
                        cfg.method = 'HEAD';
                        if (timeout) {
                            cfg.timeout = timeout;
                        }

                        resolve($http(cfg));
                    }, function(err) {
                        reject(err);
                    });
                });
            };

            this.apiGet = function(handler, timeout, headers) {
                var config = {};

                if (headers) {
                    config.headers = headers;
                }

                return httpGet(handler, timeout, config, true);
            };

            function httpDelete(handler, timeout, config, injectToken) {
                init();
                config = config || {};
                config.timeout = timeout || defaultTimeout;
                config.method = 'DELETE';
                if (injectToken && token) {
                    config.headers = config.headers || {};
                    config.headers.Token = token;
                }

                var promise = $q.defer();
                router.resolve(handler, routerService.name).then(function(url) {
                    config.url = url;
                    promise.resolve($http(config));
                }, promise.reject);
                return promise.promise;
            }

            function httpPost(handler, data, timeout, config, injectToken) {
                init();
                config = config || {};
                config.timeout = timeout || defaultTimeout;
                config.method = 'POST';
                config.data = data;
                if (injectToken && token) {
                    config.headers = config.headers || {};
                    config.headers.Token = token;
                }

                var promise = $q.defer();
                router.resolve(handler, routerService.name).then(function(url) {
                    config.url = url;
                    promise.resolve($http(config));
                }, promise.reject);
                return promise.promise;
            }

            function httpPut(handler, data, timeout, config, injectToken) {
                init();
                config = config || {};
                config.timeout = timeout || defaultTimeout;
                config.method = 'PUT';
                config.data = data;
                if (injectToken && token) {
                    config.headers = config.headers || {};
                    config.headers.Token = token;
                }

                var promise = $q.defer();
                router.resolve(handler, routerService.name).then(function(url) {
                    config.url = url;
                    promise.resolve($http(config));
                }, promise.reject);
                return promise.promise;
            }

            function checkStatusCode(response, expectedStatusCode) {
                return dataUtil.isNumber(response) ? response === expectedStatusCode : false;
            }

            function isWithinStatusCategoryRange(statusCode, range) {
                return dataUtil.isNumber(statusCode) ?
                    Boolean(statusCode >= range.MIN && statusCode <= range.MAX) :
                    false;
            }

            this.handlerPut = function(handler, data, timeout, config) {
                return httpPut(handler, data, timeout, config);
            };

            this.handlerPost = function(handler, data, timeout, config) {
                return httpPost(handler, data, timeout, config);
            };

            this.handlerDelete = function(handler, timeout, config) {
                return httpDelete(handler, timeout, config);
            };

            this.apiPost = function(handler, data, headers, timeout) {
                var config = {};
                if (headers) {
                    config.headers = headers;
                }

                return httpPost(handler, data, timeout, config, true);
            };

            this.apiPut = function(handler, data, headers, timeout) {
                init();
                if (!timeout) {
                    timeout = defaultTimeout;
                }

                return $q(function(resolve, reject) {
                    router.resolve(handler, routerService.name).then(function(url) {
                        var header = {};

                        if (headers) header = headers;

                        if (token) header.Token = token;

                        var cfg = {};
                        cfg.url = url;
                        cfg.method = 'PUT';
                        cfg.headers = header;
                        cfg.data = data;
                        if (timeout) {
                            cfg.timeout = timeout;
                        }

                        resolve($http(cfg));
                    }, function(err) {
                        reject(err);
                    });
                });
            };

            this.apiDelete = function(handler, timeout, headers, data) {
                init();
                if (!timeout) {
                    timeout = defaultTimeout;
                }

                return $q(function(resolve, reject) {
                    router.resolve(handler, routerService.name).then(function(url) {
                        var header = {};
                        if (headers) header = headers;
                        if (token) header.Token = token;

                        var cfg = {};
                        cfg.url = url;
                        cfg.method = 'DELETE';
                        cfg.headers = header;
                        cfg.data = data;
                        if (timeout) {
                            cfg.timeout = timeout;
                        }

                        resolve($http(cfg));
                    }, function(err) {
                        reject(err);
                    });
                });
            };

            this.isSuccess = function(statusCode) {
                return isWithinStatusCategoryRange(statusCode, STATUS_CODE_RANGE.SUCCESS);
            };

            this.isServerError = function(response) {
                return isWithinStatusCategoryRange(response, STATUS_CODE_RANGE.SERVER_ERROR);
            };

            this.isForbidden = function(response) {
                return checkStatusCode(response, STATUS_CODE.FORBIDDEN);
            };

            this.isUnauthorized = function(response) {
                return checkStatusCode(response, STATUS_CODE.UNAUTHORIZED);
            };
        }
    }]);
})(window.angular);

/*
{
  "name": "accurate-timer",
  "version": "0.0.6",
  "description": "An accurate timer using requestAnimationFrame() and performance.now(), defaulting to setInterval when needed.",
  "main": "accurate-timer.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git://github.com/Aaronik/accurate_timer.git"
  },
  "keywords": [
    "timer",
    "accurate",
    "accurate_timer",
    "aaron",
    "sullivan",
    "javascript",
    "precise",
    "precise_timer"
  ],
  "author": "Aaron Sullivan (Aaronik)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/Aaronik/accurate_timer/issues"
  },
  "homepage": "https://github.com/Aaronik/accurate_timer"
}
*/

(function(root){

	var Timer = function(callback, interval){	
		this.raf = 		window.requestAnimationFrame 
							|| 	window.mozRequestAnimationFrame
							|| 	window.webkitRequestAnimationFrame
							|| 	window.msRequestAnimationFrame;

		if (this.raf) {
			this.type = 'requestAnimationFrame';
		} else {
			this.type = 'setInterval';
		}

		this.timer = null;
		this.callback = callback;
		this.interval = interval;
	}

	Timer.prototype.start = function(){
		this.stop();
		var that = this;

		switch (this.type) {
			case 'requestAnimationFrame':
				console.log('using raf')
				that._startRAF();
				break;
			case 'setInterval':
				console.log('using SI')
				that._startSI();
				break;
		}
	}

	Timer.prototype.stop = function(){
		if (this.animationFrame) {
			window.cancelAnimationFrame(this.animationFrame);
			delete this.animationFrame;
		} else if (this.intervalTimer) {
			clearInterval(this.intervalTimer);
			delete this.intervalTimer;
		}
	}

	Timer.prototype._startRAF = function(){
		if (!this.raf) throw(new Error('Trying to use non-existent requestAnimationFrame'));

		this._lastTimestamp = performance.now()
		this.raf.call(window, this._stepRAF.bind(this))
	}

	Timer.prototype._stepRAF = function(timestamp){
		var elapsedTime = performance.now() - this._lastTimestamp

		if (elapsedTime >= this.interval){
			this._lastTimestamp = this._lastTimestamp + this.interval;
			this.callback(performance.now());
		}

		this.animationFrame = this.raf.call(window, this._stepRAF.bind(this));
	}

	Timer.prototype._startSI = function(){
		this._lastTimestamp = (new Date).getTime()
		var that = this;

		this.intervalTimer = setInterval(function(){
			var elapsedTime = (new Date).getTime() - that._lastTimestamp;

			if (elapsedTime >= that.interval) {
				that._lastTimestamp = that._lastTimestamp + that.interval;
				that.callback((new Date).getTime());
			}
		}, 1)
	}

	return Timer

})(this);
/*!
  * Bowser - a browser detector
  * https://github.com/ded/bowser
  * MIT License | (c) Dustin Diaz 2014
  */

!function (name, definition) {
  if (typeof module != 'undefined' && module.exports) module.exports['browser'] = definition()
  else if (typeof define == 'function' && define.amd) define(definition)
  else this[name] = definition()
}('bowser', function () {
  /**
    * See useragents.js for examples of navigator.userAgent
    */

  var t = true

  function detect(ua) {

    function getFirstMatch(regex) {
      var match = ua.match(regex);
      return (match && match.length > 1 && match[1]) || '';
    }

    function getSecondMatch(regex) {
      var match = ua.match(regex);
      return (match && match.length > 1 && match[2]) || '';
    }

    var iosdevice = getFirstMatch(/(ipod|iphone|ipad)/i).toLowerCase()
      , likeAndroid = /like android/i.test(ua)
      , android = !likeAndroid && /android/i.test(ua)
      , edgeVersion = getFirstMatch(/edge\/(\d+(\.\d+)?)/i)
      , versionIdentifier = getFirstMatch(/version\/(\d+(\.\d+)?)/i)
      , tablet = /tablet/i.test(ua)
      , mobile = !tablet && /[^-]mobi/i.test(ua)
      , result

    if (/opera|opr/i.test(ua)) {
      result = {
        name: 'Opera'
      , opera: t
      , version: versionIdentifier || getFirstMatch(/(?:opera|opr)[\s\/](\d+(\.\d+)?)/i)
      }
    }
    else if (/yabrowser/i.test(ua)) {
      result = {
        name: 'Yandex Browser'
      , yandexbrowser: t
      , version: versionIdentifier || getFirstMatch(/(?:yabrowser)[\s\/](\d+(\.\d+)?)/i)
      }
    }
    else if (/windows phone/i.test(ua)) {
      result = {
        name: 'Windows Phone'
      , windowsphone: t
      }
      if (edgeVersion) {
        result.msedge = t
        result.version = edgeVersion
      }
      else {
        result.msie = t
        result.version = getFirstMatch(/iemobile\/(\d+(\.\d+)?)/i)
      }
    }
    else if (/msie|trident/i.test(ua)) {
      result = {
        name: 'Internet Explorer'
      , msie: t
      , version: getFirstMatch(/(?:msie |rv:)(\d+(\.\d+)?)/i)
      }
    }
    else if (/chrome.+? edge/i.test(ua)) {
      result = {
        name: 'Microsoft Edge'
      , msedge: t
      , version: edgeVersion
      }
    }
    else if (/chrome|crios|crmo/i.test(ua)) {
      result = {
        name: 'Chrome'
      , chrome: t
      , version: getFirstMatch(/(?:chrome|crios|crmo)\/(\d+(\.\d+)?)/i)
      }
    }
    else if (iosdevice) {
      result = {
        name : iosdevice == 'iphone' ? 'iPhone' : iosdevice == 'ipad' ? 'iPad' : 'iPod'
      }
      // WTF: version is not part of user agent in web apps
      if (versionIdentifier) {
        result.version = versionIdentifier
      }
    }
    else if (/sailfish/i.test(ua)) {
      result = {
        name: 'Sailfish'
      , sailfish: t
      , version: getFirstMatch(/sailfish\s?browser\/(\d+(\.\d+)?)/i)
      }
    }
    else if (/seamonkey\//i.test(ua)) {
      result = {
        name: 'SeaMonkey'
      , seamonkey: t
      , version: getFirstMatch(/seamonkey\/(\d+(\.\d+)?)/i)
      }
    }
    else if (/firefox|iceweasel/i.test(ua)) {
      result = {
        name: 'Firefox'
      , firefox: t
      , version: getFirstMatch(/(?:firefox|iceweasel)[ \/](\d+(\.\d+)?)/i)
      }
      if (/\((mobile|tablet);[^\)]*rv:[\d\.]+\)/i.test(ua)) {
        result.firefoxos = t
      }
    }
    else if (/silk/i.test(ua)) {
      result =  {
        name: 'Amazon Silk'
      , silk: t
      , version : getFirstMatch(/silk\/(\d+(\.\d+)?)/i)
      }
    }
    else if (android) {
      result = {
        name: 'Android'
      , version: versionIdentifier
      }
    }
    else if (/phantom/i.test(ua)) {
      result = {
        name: 'PhantomJS'
      , phantom: t
      , version: getFirstMatch(/phantomjs\/(\d+(\.\d+)?)/i)
      }
    }
    else if (/blackberry|\bbb\d+/i.test(ua) || /rim\stablet/i.test(ua)) {
      result = {
        name: 'BlackBerry'
      , blackberry: t
      , version: versionIdentifier || getFirstMatch(/blackberry[\d]+\/(\d+(\.\d+)?)/i)
      }
    }
    else if (/(web|hpw)os/i.test(ua)) {
      result = {
        name: 'WebOS'
      , webos: t
      , version: versionIdentifier || getFirstMatch(/w(?:eb)?osbrowser\/(\d+(\.\d+)?)/i)
      };
      /touchpad\//i.test(ua) && (result.touchpad = t)
    }
    else if (/bada/i.test(ua)) {
      result = {
        name: 'Bada'
      , bada: t
      , version: getFirstMatch(/dolfin\/(\d+(\.\d+)?)/i)
      };
    }
    else if (/tizen/i.test(ua)) {
      result = {
        name: 'Tizen'
      , tizen: t
      , version: getFirstMatch(/(?:tizen\s?)?browser\/(\d+(\.\d+)?)/i) || versionIdentifier
      };
    }
    else if (/safari/i.test(ua)) {
      result = {
        name: 'Safari'
      , safari: t
      , version: versionIdentifier
      }
    }
    else {
      result = {
        name: getFirstMatch(/^(.*)\/(.*) /),
        version: getSecondMatch(/^(.*)\/(.*) /)
     };
   }

    // set webkit or gecko flag for browsers based on these engines
    if (!result.msedge && /(apple)?webkit/i.test(ua)) {
      result.name = result.name || "Webkit"
      result.webkit = t
      if (!result.version && versionIdentifier) {
        result.version = versionIdentifier
      }
    } else if (!result.opera && /gecko\//i.test(ua)) {
      result.name = result.name || "Gecko"
      result.gecko = t
      result.version = result.version || getFirstMatch(/gecko\/(\d+(\.\d+)?)/i)
    }

    // set OS flags for platforms that have multiple browsers
    if (!result.msedge && (android || result.silk)) {
      result.android = t
    } else if (iosdevice) {
      result[iosdevice] = t
      result.ios = t
    }

    // OS version extraction
    var osVersion = '';
    if (result.windowsphone) {
      osVersion = getFirstMatch(/windows phone (?:os)?\s?(\d+(\.\d+)*)/i);
    } else if (iosdevice) {
      osVersion = getFirstMatch(/os (\d+([_\s]\d+)*) like mac os x/i);
      osVersion = osVersion.replace(/[_\s]/g, '.');
    } else if (android) {
      osVersion = getFirstMatch(/android[ \/-](\d+(\.\d+)*)/i);
    } else if (result.webos) {
      osVersion = getFirstMatch(/(?:web|hpw)os\/(\d+(\.\d+)*)/i);
    } else if (result.blackberry) {
      osVersion = getFirstMatch(/rim\stablet\sos\s(\d+(\.\d+)*)/i);
    } else if (result.bada) {
      osVersion = getFirstMatch(/bada\/(\d+(\.\d+)*)/i);
    } else if (result.tizen) {
      osVersion = getFirstMatch(/tizen[\/\s](\d+(\.\d+)*)/i);
    }
    if (osVersion) {
      result.osversion = osVersion;
    }

    // device type extraction
    var osMajorVersion = osVersion.split('.')[0];
    if (tablet || iosdevice == 'ipad' || (android && (osMajorVersion == 3 || (osMajorVersion == 4 && !mobile))) || result.silk) {
      result.tablet = t
    } else if (mobile || iosdevice == 'iphone' || iosdevice == 'ipod' || android || result.blackberry || result.webos || result.bada) {
      result.mobile = t
    }

    // Graded Browser Support
    // http://developer.yahoo.com/yui/articles/gbs
    if (result.msedge ||
        (result.msie && result.version >= 10) ||
        (result.yandexbrowser && result.version >= 15) ||
        (result.chrome && result.version >= 20) ||
        (result.firefox && result.version >= 20.0) ||
        (result.safari && result.version >= 6) ||
        (result.opera && result.version >= 10.0) ||
        (result.ios && result.osversion && result.osversion.split(".")[0] >= 6) ||
        (result.blackberry && result.version >= 10.1)
        ) {
      result.a = t;
    }
    else if ((result.msie && result.version < 10) ||
        (result.chrome && result.version < 20) ||
        (result.firefox && result.version < 20.0) ||
        (result.safari && result.version < 6) ||
        (result.opera && result.version < 10.0) ||
        (result.ios && result.osversion && result.osversion.split(".")[0] < 6)
        ) {
      result.c = t
    } else result.x = t

    return result
  }

  var bowser = detect(typeof navigator !== 'undefined' ? navigator.userAgent : '')

  bowser.test = function (browserList) {
    for (var i = 0; i < browserList.length; ++i) {
      var browserItem = browserList[i];
      if (typeof browserItem=== 'string') {
        if (browserItem in bowser) {
          return true;
        }
      }
    }
    return false;
  }

  /*
   * Set our detect method to the main bowser object so we can
   * reuse it to test other user agents.
   * This is needed to implement future tests.
   */
  bowser._detect = detect;

  return bowser
});
(function(window) {
    /**
    * Mocked indexeddb
    * This is a modified version for Appspace App
    * The original source is from:
    * https://github.com/szimmers/mock-indexeddb
    */

    // mock saves objects here
    var mockIndexedDBItems = [];


    // used for reading objects
    var mockIndexedDB_cursorResultsIndex = 0;

    // test flags
    var mockIndexedDBTestFlags = {
        'canOpenDB': true,
        'openDBShouldBlock': false,
        'openDBShouldAbort': false,
        'upgradeNeeded': false,
        'canReadDB': true,
        'canSave': true,
        'canDelete': true,
        'canClear': true,
        'canCreateStore': true,
        'canDeleteDB': true
    };


    /**
    * the cursor works like an indexeddb one, where calling continue() will provide
    * next item. items must be saved with the commitIndexedDBMockData() method in
    * order to be returned by the cursor.
    */
    var mockIndexedDBCursor = {
        'identity': 'mockIndexedDBCursor',

        'continue': function() {
            mockIndexedDB_cursorResultsIndex++;
            mockIndexedDB_openCursorSuccess = false;

            mockIndexedDB_cursorContinueTimer = setTimeout(function() {
                mockIndexedDBCursorRequest.callSuccessHandler();
                mockIndexedDB_openCursorSuccess = true;
            }, 20);

            return mockIndexedDBCursorRequest;
        }
    };

    /**
    * with each call to continue() to get the cursor, the object will
    * have a key and value property. these are defined by the getters.
    */
    mockIndexedDBCursor.__defineGetter__('key', function() {
        if (mockIndexedDB_cursorResultsIndex < mockIndexedDBItems.length) {
            var item = mockIndexedDBItems[mockIndexedDB_cursorResultsIndex];
            return item.key;
        } else {
            return null;
        }
    });

    mockIndexedDBCursor.__defineGetter__('value', function() {
        if (mockIndexedDB_cursorResultsIndex < mockIndexedDBItems.length) {
            var item = mockIndexedDBItems[mockIndexedDB_cursorResultsIndex];
            return item.value;
        } else {
            return null;
        }
    });

    mockIndexedDBCursor.__defineGetter__('resultCount', function() {
        return mockIndexedDBItems.length;
    });

    var mockIndexedDBCursorRequest = {
        'callSuccessHandler': function() {
            if (this.onsuccess) {
                var cursorToReturn;

                if (mockIndexedDB_cursorResultsIndex < mockIndexedDBItems.length) {
                    cursorToReturn = mockIndexedDBCursor;
                    mockIndexedDB_cursorReadingDone = false;
                } else {
                    cursorToReturn = null;
                    mockIndexedDB_cursorReadingDone = true;
                }

                var event = {
                    'type': 'success',
                    'bubbles': false,
                    'cancelable': true,
                    'target': {
                        'result': cursorToReturn
                    }
                };

                this.onsuccess(event);
            }
        },

        'callErrorHandler': function() {
            if (this.onerror) {
                var event = {
                    'type': 'error',
                    'bubbles': true,
                    'cancelable': true,
                    'target': {
                        'errorCode': 1 // this is a made-up code
                    }
                };

                this.onerror(event);
            }
        }
    };

    var mockIndexedDBStoreTransaction = {
        'callSuccessHandler': function() {
            if (this.onsuccess) {
                var event = new CustomEvent('success', {bubbles: false, cancelable: true});
                this.onsuccess(event);
            }
        },

        'callErrorHandler': function() {
            if (this.onerror) {
                var event = {
                    'type': 'error',
                    'bubbles': true,
                    'cancelable': true,
                    'target': {
                        'errorCode': 1 // this is a made-up code
                    }
                };
                this.onerror(event);
            }
        }
    };

    var mockIndexedDBStore = {
        'identity': 'mockedStore',

        // add returns a different txn than delete does. in indexedDB, the listeners are
        // attached to the txn that returned the store.
        'add': function(data) {
            if (mockIndexedDBTestFlags.canSave === true) {
                mockIndexedDBItems.push(data);
                mockIndexedDB_storeAddTimer = setTimeout(function() {
                    mockIndexedDBTransaction.callCompleteHandler();
                    mockIndexedDB_saveSuccess = true;
                }, 20);
            } else {
                mockIndexedDB_storeAddTimer = setTimeout(function() {
                    mockIndexedDBTransaction.callErrorHandler();
                    mockIndexedDB_saveFail = true;
                }, 20);
            }

            return mockIndexedDBTransaction;
        },

        // for now, treating put just like an add.
        // TODO: do an update instead of adding
        'put': function(data) {
            if (mockIndexedDBTestFlags.canSave === true) {
                mockIndexedDBItems.push(data);
                mockIndexedDB_storeAddTimer = setTimeout(function() {
                    mockIndexedDBTransaction.callCompleteHandler();
                    mockIndexedDB_saveSuccess = true;
                }, 20);
            } else {
                mockIndexedDB_storeAddTimer = setTimeout(function() {
                    mockIndexedDBTransaction.callErrorHandler();
                    mockIndexedDB_saveFail = true;
                }, 20);
            }

            return mockIndexedDBTransaction;
        },

        // for delete, the listeners are attached to a request returned from the store.
        'delete': function() {
            if (mockIndexedDBTestFlags.canDelete === true) {
                mockIndexedDB_storeDeleteTimer = setTimeout(function() {
                    mockIndexedDBStoreTransaction.callSuccessHandler();
                    mockIndexedDB_deleteSuccess = true;
                }, 20);
            } else {
                mockIndexedDB_storeDeleteTimer = setTimeout(function() {
                    mockIndexedDBStoreTransaction.callErrorHandler();
                    mockIndexedDB_deleteFail = true;
                }, 20);
            }

            return mockIndexedDBStoreTransaction;
        },

        // for clear, the listeners are attached to a request returned from the store.
        'clear': function() {
            if (mockIndexedDBTestFlags.canClear === true) {
                mockIndexedDB_storeClearTimer = setTimeout(function() {
                    mockIndexedDBStoreTransaction.callSuccessHandler();
                    mockIndexedDB_clearSuccess = true;
                }, 20);
            } else {
                mockIndexedDB_storeClearTimer = setTimeout(function() {
                    mockIndexedDBStoreTransaction.callErrorHandler();
                    mockIndexedDB_clearFail = true;
                }, 20);
            }

            return mockIndexedDBStoreTransaction;
        },

        'createIndex': function() {

        },

        'callSuccessHandler': function() {
            if (this.onsuccess) {
                var event = new CustomEvent('success', {bubbles: false, cancelable: true});
                this.onsuccess(event);
            }
        },

        'callErrorHandler': function() {
            if (this.onerror) {
                var event = {
                    'type': 'error',
                    'bubbles': true,
                    'cancelable': true,
                    'target': {
                        'errorCode': 1 // this is a made-up code
                    }
                };
                this.onerror(event);
            }
        },

        'openCursor': function() {
            if (mockIndexedDBTestFlags.canReadDB === true) {
                mockIndexedDB_storeOpenCursorTimer = setTimeout(function() {
                    mockIndexedDBCursorRequest.callSuccessHandler();
                    mockIndexedDB_openCursorSuccess = true;
                }, 20);
            } else {
                mockIndexedDB_storeOpenCursorTimer = setTimeout(function() {
                    mockIndexedDBCursorRequest.callErrorHandler();
                    mockIndexedDB_openCursorFail = true;
                }, 20);
            }

            return mockIndexedDBCursorRequest;
        }
    };

    var mockIndexedDBTransaction = {
        'objectStore': function() {
            return mockIndexedDBStore;
        },

        'callCompleteHandler': function() {
            if (this.oncomplete) {
                var event = new CustomEvent('complete', {bubbles: false, cancelable: true});
                this.oncomplete(event);
            }
        },

        'callErrorHandler': function() {
            if (this.onerror) {
                var event = {
                    'type': 'error',
                    'bubbles': true,
                    'cancelable': true,
                    'target': {
                        'errorCode': 1 // this is a made-up code
                    }
                };
                this.onerror(event);
            }
        }
    };

    var mockIndexedDBDatabase = {
        'transaction': function() {
            return mockIndexedDBTransaction;
        },

        'close': function() { },

        'objectStoreNames': {
            'contains': function() {
                return false;
            }
        },

        'createObjectStore': function() {
            if (mockIndexedDBTestFlags.canCreateStore === true) {
                mockIndexedDB_createObjectStoreTimer = setTimeout(function() {
                    mockIndexedDBStore.callSuccessHandler();
                    mockIndexedDB_createStoreSuccess = true;
                }, 20);
            } else {
                mockIndexedDB_createObjectStoreTimer = setTimeout(function() {
                    mockIndexedDBStore.callErrorHandler();
                    mockIndexedDB_createStoreFail = true;
                }, 20);
            }

            return mockIndexedDBStore;
        }
    };

    var mockIndexedDBOpenDBRequest = {
        'callSuccessHandler': function() {
            if (this.onsuccess) {
                var event = document.createEvent('CustomEvent');
                event.initCustomEvent('success', false, false, {bubbles: false, cancelable: true});
                this.onsuccess(event);
            }
        },

        'callErrorHandler': function() {
            if (this.onerror) {
                var event = {
                    'type': 'error',
                    'bubbles': true,
                    'cancelable': true,
                    'target': {
                        'errorCode': 1, // this is a made-up code
                        'error': {
                            'message': 'fail' // this is a made-up message
                        }
                    }
                };
                this.onerror(event);
            }
        },

        'callAbortHandler': function() {
            if (this.onblocked) {
                var event = {
                    'type': 'error',
                    'bubbles': true,
                    'cancelable': true,
                    'target': {
                        'errorCode': 1, // this is a made-up code
                        'error': {
                            'message': 'fail' // this is a made-up message
                        }
                    }
                };
                this.onblocked(event);
            }
        },

        'callBlockedHandler': function() {
            if (this.onabort) {
                var event = {
                    'type': 'error',
                    'bubbles': true,
                    'cancelable': true,
                    'target': {
                        'errorCode': 1, // this is a made-up code
                        'error': {
                            'message': 'fail' // this is a made-up message
                        }
                    }
                };
                this.onabort(event);
            }
        },

        'callUpgradeNeeded': function() {
            if (this.onupgradeneeded) {
                var event = {
                    'type': 'upgradeneeded',
                    'bubbles': false,
                    'cancelable': true,
                    'target': {
                        'result': mockIndexedDBDatabase,
                        'transaction': {
                            'abort': function() {
                                mockIndexedDBTestFlags.openDBShouldAbort = true;
                            }
                        }
                    }
                };
                this.onupgradeneeded(event);
            }
        },

        'result': mockIndexedDBDatabase
    };

    var mockIndexedDBDeleteDBRequest = {
        'callSuccessHandler': function() {
            if (this.onsuccess) {
                var event = new CustomEvent('success', {bubbles: false, cancelable: true});
                this.onsuccess(event);
            }
        },

        'callErrorHandler': function() {
            if (this.onerror) {
                var event = {
                    'type': 'error',
                    'bubbles': true,
                    'cancelable': true,
                    'target': {
                        'errorCode': 1, // this is a made-up code
                        'error': {
                            'message': 'fail' // this is a made-up message
                        }
                    }
                };
                this.onerror(event);
            }
        },

        'callAbortHandler': function() {
            if (this.onblocked) {
                var event = {
                    'type': 'error',
                    'bubbles': true,
                    'cancelable': true,
                    'target': {
                        'errorCode': 1, // this is a made-up code
                        'error': {
                            'message': 'fail' // this is a made-up message
                        }
                    }
                };
                this.onblocked(event);
            }
        },

        'callBlockedHandler': function() {
            if (this.onabort) {
                var event = {
                    'type': 'error',
                    'bubbles': true,
                    'cancelable': true,
                    'target': {
                        'errorCode': 1, // this is a made-up code
                        'error': {
                            'message': 'fail' // this is a made-up message
                        }
                    }
                };
                this.onabort(event);
            }
        },

        'result': {}
    };

    /**
    * this mocks the window.indexedDB object. assuming a method that returns that object, this mock
    * object can be substituted like this:
    *
    * spyOn(service, 'getIndexedDBReference').andReturn(mockIndexedDB);
    */
    window.mockIndexedDB = {
        'identity': 'mockedIndexDB',

        // note: the mock does not simulate separate stores, so dbname is ignored
        'open': function() {
            if (mockIndexedDBTestFlags.openDBShouldBlock === true) {
                mockIndexedDB_openDBTimer = setTimeout(function() {
                    mockIndexedDBOpenDBRequest.callBlockedHandler();
                    mockIndexedDB_openDBBlocked = true;
                }, 20);
            } else if (mockIndexedDBTestFlags.openDBShouldAbort === true) {
                mockIndexedDB_openDBTimer = setTimeout(function() {
                    mockIndexedDBOpenDBRequest.callAbortHandler();
                    mockIndexedDB_openDBAbort = true;
                }, 20);
            } else if (mockIndexedDBTestFlags.upgradeNeeded === true) {
                mockIndexedDB_openDBTimer = setTimeout(function() {
                    mockIndexedDBOpenDBRequest.callUpgradeNeeded();
                    mockIndexedDB_openDBUpgradeNeeded = true;
                }, 20);
            }
            // these are order dependent, so we don't have to set so many
            // flags in the test. can leave 'canOpenDB' in its default
            // true state, so long as the other fail vars are checked first.
            else if (mockIndexedDBTestFlags.canOpenDB === true) {
                mockIndexedDB_openDBTimer = setTimeout(function() {
                    mockIndexedDBOpenDBRequest.callSuccessHandler();
                    mockIndexedDB_openDBSuccess = true;
                }, 20);
            } else {
                mockIndexedDB_openDBTimer = setTimeout(function() {
                    mockIndexedDBOpenDBRequest.callErrorHandler();
                    mockIndexedDB_openDBFail = true;
                }, 20);
            }

            return mockIndexedDBOpenDBRequest;
        },

        'deleteDatabase': function() {
            if (mockIndexedDBTestFlags.canDeleteDB === true) {
                mockIndexedDB_deleteDBTimer = setTimeout(function() {
                    mockIndexedDBDeleteDBRequest.callSuccessHandler();
                    mockIndexedDB_deleteDBSuccess = true;
                }, 20);
            } else {
                mockIndexedDB_deleteDBTimer = setTimeout(function() {
                    mockIndexedDBDeleteDBRequest.callErrorHandler();
                    mockIndexedDB_deleteDBFail = true;
                }, 20);
            }

            return mockIndexedDBDeleteDBRequest;
        }
    };
}(window));


var idbModules = {  // jshint ignore:line
    util: {
        cleanInterface: false
    }
};

(function () {
    'use strict';

    var testObject = {test: true};
    //Test whether Object.defineProperty really works.
    if (Object.defineProperty) {
        try {
            Object.defineProperty(testObject, 'test', { enumerable: false });
            if (testObject.test) {
                idbModules.util.cleanInterface = true;      // jshint ignore:line
            }
        } catch (e) {
        //Object.defineProperty does not work as intended.
        }
    }
})();

(function(idbModules) {
    'use strict';

    /**
     * A utility method to callback onsuccess, onerror, etc as soon as the calling function's context is over
     * @param {Object} fn
     * @param {Object} context
     * @param {Object} argArray
     */
    function callback(fn, context, event) {
        //window.setTimeout(function(){
        event.target = context;
        (typeof context[fn] === "function") && context[fn].apply(context, [event]);
        //}, 1);
    }

    /**
     * Shim the DOMStringList object.
     *
     */
    var StringList = function() {
        this.length = 0;
        this._items = [];
        //Internal functions on the prototype have been made non-enumerable below.
        if (idbModules.util.cleanInterface) {
            Object.defineProperty(this, '_items', {
                enumerable: false
            });
        }
    };
    StringList.prototype = {
        // Interface.
        contains: function(str) {
            return -1 !== this._items.indexOf(str);
        },
        item: function(key) {
            return this._items[key];
        },

        // Helpers. Should only be used internally.
        indexOf: function(str) {
            return this._items.indexOf(str);
        },
        push: function(item) {
            this._items.push(item);
            this.length += 1;
            for (var i = 0; i < this._items.length; i++) {
                this[i] = this._items[i];
            }
        },
        splice: function(/*index, howmany, item1, ..., itemX*/) {
            this._items.splice.apply(this._items, arguments);
            this.length = this._items.length;
            for (var i in this) {
                if (i === String(parseInt(i, 10))) {
                    delete this[i];
                }
            }
            for (i = 0; i < this._items.length; i++) {
                this[i] = this._items[i];
            }
        }
    };
    if (idbModules.util.cleanInterface) {
        for (var i in {
            'indexOf': false,
            'push': false,
            'splice': false
        }) {
            Object.defineProperty(StringList.prototype, i, {
                enumerable: false
            });
        }
    }

    idbModules.util.callback = callback;
    idbModules.util.StringList = StringList;
    idbModules.util.quote = function(arg) {
        return "\"" + arg + "\"";
    };

}(idbModules));

(function (idbModules) {
    'use strict';

    /**
     * Polyfills missing features in the browser's native IndexedDB implementation.
     * This is used for browsers that DON'T support WebSQL but DO support IndexedDB
     */
    function polyfill() {
        if (navigator.userAgent.match(/MSIE/) ||
            navigator.userAgent.match(/Trident/) ||
            navigator.userAgent.match(/Edge/)) {
            // Internet Explorer's native IndexedDB does not support compound keys
            compoundKeyPolyfill();
        }
    }

    /**
     * Polyfills support for compound keys
     */
    function compoundKeyPolyfill() {
        var cmp = IDBFactory.prototype.cmp;
        var createObjectStore = IDBDatabase.prototype.createObjectStore;
        var createIndex = IDBObjectStore.prototype.createIndex;
        var add = IDBObjectStore.prototype.add;
        var put = IDBObjectStore.prototype.put;
        var indexGet = IDBIndex.prototype.get;
        var indexGetKey = IDBIndex.prototype.getKey;
        var indexCursor = IDBIndex.prototype.openCursor;
        var indexKeyCursor = IDBIndex.prototype.openKeyCursor;
        var storeGet = IDBObjectStore.prototype.get;
        var storeDelete = IDBObjectStore.prototype.delete;
        var storeCursor = IDBObjectStore.prototype.openCursor;
        var storeKeyCursor = IDBObjectStore.prototype.openKeyCursor;
        var bound = IDBKeyRange.bound;
        var upperBound = IDBKeyRange.upperBound;
        var lowerBound = IDBKeyRange.lowerBound;
        var only = IDBKeyRange.only;
        var requestResult = Object.getOwnPropertyDescriptor(IDBRequest.prototype, 'result');
        var cursorPrimaryKey = Object.getOwnPropertyDescriptor(IDBCursor.prototype, 'primaryKey');
        var cursorKey = Object.getOwnPropertyDescriptor(IDBCursor.prototype, 'key');
        var cursorValue = Object.getOwnPropertyDescriptor(IDBCursorWithValue.prototype, 'value');

        IDBFactory.prototype.cmp = function(key1, key2) {
            var args = Array.prototype.slice.call(arguments);
            if (key1 instanceof Array) {
                args[0] = encodeCompoundKey(key1);
            }
            if (key2 instanceof Array) {
                args[1] = encodeCompoundKey(key2);
            }
            return cmp.apply(this, args);
        };

        IDBDatabase.prototype.createObjectStore = function(name, opts) {
            if (opts && opts.keyPath instanceof Array) {
                opts.keyPath = encodeCompoundKeyPath(opts.keyPath);
            }
            return createObjectStore.apply(this, arguments);
        };

        IDBObjectStore.prototype.createIndex = function(name, keyPath, opts) {
            var args = Array.prototype.slice.call(arguments);
            if (keyPath instanceof Array) {
                args[1] = encodeCompoundKeyPath(keyPath);
            }
            return createIndex.apply(this, args);
        };

        IDBObjectStore.prototype.add = function(value, key) {
            return this.__insertData(add, arguments);
        };

        IDBObjectStore.prototype.put = function(value, key) {
            return this.__insertData(put, arguments);
        };

        IDBObjectStore.prototype.__insertData = function(method, args) {
            args = Array.prototype.slice.call(args);
            var value = args[0];
            var key = args[1];

            // out-of-line key
            if (key instanceof Array) {
                args[1] = encodeCompoundKey(key);
            }

            if (typeof value === 'object') {
                // inline key
                if (isCompoundKey(this.keyPath)) {
                    setInlineCompoundKey(value, this.keyPath);
                }

                // inline indexes
                for (var i = 0; i < this.indexNames.length; i++) {
                    var index = this.index(this.indexNames[i]);
                    if (isCompoundKey(index.keyPath)) {
                        try {
                            setInlineCompoundKey(value, index.keyPath);
                        }
                        catch (e) {
                            // The value doesn't have a valid key for this index.
                        }
                    }
                }
            }
            return method.apply(this, args);
        };

        IDBIndex.prototype.get = function(key) {
            var args = Array.prototype.slice.call(arguments);
            if (key instanceof Array) {
                args[0] = encodeCompoundKey(key);
            }
            return indexGet.apply(this, args);
        };

        IDBIndex.prototype.getKey = function(key) {
            var args = Array.prototype.slice.call(arguments);
            if (key instanceof Array) {
                args[0] = encodeCompoundKey(key);
            }
            return indexGetKey.apply(this, args);
        };

        IDBIndex.prototype.openCursor = function(key) {
            var args = Array.prototype.slice.call(arguments);
            if (key instanceof Array) {
                args[0] = encodeCompoundKey(key);
            }
            return indexCursor.apply(this, args);
        };

        IDBIndex.prototype.openKeyCursor = function(key) {
            var args = Array.prototype.slice.call(arguments);
            if (key instanceof Array) {
                args[0] = encodeCompoundKey(key);
            }
            return indexKeyCursor.apply(this, args);
        };

        IDBObjectStore.prototype.get = function(key) {
            var args = Array.prototype.slice.call(arguments);
            if (key instanceof Array) {
                args[0] = encodeCompoundKey(key);
            }
            return storeGet.apply(this, args);
        };

        IDBObjectStore.prototype.delete = function(key) {
            var args = Array.prototype.slice.call(arguments);
            if (key instanceof Array) {
                args[0] = encodeCompoundKey(key);
            }
            return storeDelete.apply(this, args);
        };

        IDBObjectStore.prototype.openCursor = function(key) {
            var args = Array.prototype.slice.call(arguments);
            if (key instanceof Array) {
                args[0] = encodeCompoundKey(key);
            }
            return storeCursor.apply(this, args);
        };

        IDBObjectStore.prototype.openKeyCursor = function(key) {
            var args = Array.prototype.slice.call(arguments);
            if (key instanceof Array) {
                args[0] = encodeCompoundKey(key);
            }
            return storeKeyCursor.apply(this, args);
        };

        IDBKeyRange.bound = function(lower, upper, lowerOpen, upperOpen) {
            var args = Array.prototype.slice.call(arguments);
            if (lower instanceof Array) {
                args[0] = encodeCompoundKey(lower);
            }
            if (upper instanceof Array) {
                args[1] = encodeCompoundKey(upper);
            }
            return bound.apply(IDBKeyRange, args);
        };

        IDBKeyRange.upperBound = function(key, open) {
            var args = Array.prototype.slice.call(arguments);
            if (key instanceof Array) {
                args[0] = encodeCompoundKey(key);
            }
            return upperBound.apply(IDBKeyRange, args);
        };

        IDBKeyRange.lowerBound = function(key, open) {
            var args = Array.prototype.slice.call(arguments);
            if (key instanceof Array) {
                args[0] = encodeCompoundKey(key);
            }
            return lowerBound.apply(IDBKeyRange, args);
        };

        IDBKeyRange.only = function(key) {
            var args = Array.prototype.slice.call(arguments);
            if (key instanceof Array) {
                args[0] = encodeCompoundKey(key);
            }
            return only.apply(IDBKeyRange, args);
        };

        Object.defineProperty(IDBRequest.prototype, 'result', {
            enumerable: requestResult.enumerable,
            configurable: requestResult.configurable,
            get: function() {
                var result = requestResult.get.call(this);
                return removeInlineCompoundKey(result);
            }
        });

        Object.defineProperty(IDBCursor.prototype, 'primaryKey', {
            enumerable: cursorPrimaryKey.enumerable,
            configurable: cursorPrimaryKey.configurable,
            get: function() {
                var result = cursorPrimaryKey.get.call(this);
                return removeInlineCompoundKey(result);
            }
        });

        Object.defineProperty(IDBCursor.prototype, 'key', {
            enumerable: cursorKey.enumerable,
            configurable: cursorKey.configurable,
            get: function() {
                var result = cursorKey.get.call(this);
                return removeInlineCompoundKey(result);
            }
        });

        Object.defineProperty(IDBCursorWithValue.prototype, 'value', {
            enumerable: cursorValue.enumerable,
            configurable: cursorValue.configurable,
            get: function() {
                var result = cursorValue.get.call(this);
                return removeInlineCompoundKey(result);
            }
        });

        try {
            if (!IDBTransaction.VERSION_CHANGE) {
                IDBTransaction.VERSION_CHANGE = 'versionchange';
            }
        }
        catch (e) {}
    }

    var compoundKeysPropertyName = '__$$compoundKey';
    var propertySeparatorRegExp = /\$\$/g;
    var propertySeparator = '$$$$';         // "$$" after RegExp escaping
    var keySeparator = '$_$';

    function isCompoundKey(keyPath) {
        return keyPath && (keyPath.indexOf(compoundKeysPropertyName + '.') === 0);
    }

    function encodeCompoundKeyPath(keyPath) {
        // Encoded dotted properties
        // ["name.first", "name.last"] ==> ["name$$first", "name$$last"]
        for (var i = 0; i < keyPath.length; i++) {
            keyPath[i] = keyPath[i].replace(/\./g, propertySeparator);
        }

        // Encode the array as a single property
        // ["name$$first", "name$$last"] => "__$$compoundKey.name$$first$_$name$$last"
        return compoundKeysPropertyName + '.' + keyPath.join(keySeparator);
    }

    function decodeCompoundKeyPath(keyPath) {
        // Remove the "__$$compoundKey." prefix
        keyPath = keyPath.substr(compoundKeysPropertyName.length + 1);

        // Split the properties into an array
        // "name$$first$_$name$$last" ==> ["name$$first", "name$$last"]
        keyPath = keyPath.split(keySeparator);

        // Decode dotted properties
        // ["name$$first", "name$$last"] ==> ["name.first", "name.last"]
        for (var i = 0; i < keyPath.length; i++) {
            keyPath[i] = keyPath[i].replace(propertySeparatorRegExp, '.');
        }
        return keyPath;
    }

    function setInlineCompoundKey(value, encodedKeyPath) {
        // Encode the key
        var keyPath = decodeCompoundKeyPath(encodedKeyPath);
        var key = idbModules.Key.getValue(value, keyPath);
        var encodedKey = encodeCompoundKey(key);

        // Store the encoded key inline
        encodedKeyPath = encodedKeyPath.substr(compoundKeysPropertyName.length + 1);
        value[compoundKeysPropertyName] = value[compoundKeysPropertyName] || {};
        value[compoundKeysPropertyName][encodedKeyPath] = encodedKey;
    }

    function removeInlineCompoundKey(value) {
        if (typeof value === "string" && isCompoundKey(value)) {
            return decodeCompoundKey(value);
        }
        else if (value && typeof value[compoundKeysPropertyName] === "object") {
            delete value[compoundKeysPropertyName];
        }
        return value;
    }

    function encodeCompoundKey(key) {
        // Validate and encode the key
        idbModules.Key.validate(key);
        key = idbModules.Key.encode(key);

        // Prepend the "__$$compoundKey." prefix
        key = compoundKeysPropertyName + '.' + key;

        validateKeyLength(key);
        return key;
    }

    function decodeCompoundKey(key) {
        validateKeyLength(key);

        // Remove the "__$$compoundKey." prefix
        key = key.substr(compoundKeysPropertyName.length + 1);

        // Decode the key
        key = idbModules.Key.decode(key);
        return key;
    }

    function validateKeyLength(key) {
        // BUG: Internet Explorer truncates string keys at 889 characters
        if (key.length > 889) {
            throw idbModules.util.createDOMException("DataError", "The encoded key is " + key.length + " characters long, but IE only allows 889 characters. Consider replacing numeric keys with strings to reduce the encoded length.");
        }
    }

    idbModules.polyfill = polyfill;
})(idbModules);

(function(idbModules){
    'use strict';

    /**
     * Implementation of the Structured Cloning Algorithm.  Supports the
     * following object types:
     * - Blob
     * - Boolean
     * - Date object
     * - File object (deserialized as Blob object).
     * - Number object
     * - RegExp object
     * - String object
     * This is accomplished by doing the following:
     * 1) Using the cycle/decycle functions from:
     *    https://github.com/douglascrockford/JSON-js/blob/master/cycle.js
     * 2) Serializing/deserializing objects to/from string that don't work with
     *    JSON.stringify and JSON.parse by using object specific logic (eg use 
     *    the FileReader API to convert a Blob or File object to a data URL.   
     * 3) JSON.stringify and JSON.parse do the final conversion to/from string.
     */
    var Sca = (function(){
        return {
            decycle: function(object, callback) {
                //From: https://github.com/douglascrockford/JSON-js/blob/master/cycle.js
                // Contains additional logic to convert the following object types to string
                // so that they can properly be encoded using JSON.stringify:
                //  *Boolean
                //  *Date
                //  *File
                //  *Blob
                //  *Number
                //  *Regex
                // Make a deep copy of an object or array, assuring that there is at most
                // one instance of each object or array in the resulting structure. The
                // duplicate references (which might be forming cycles) are replaced with
                // an object of the form
                //      {$ref: PATH}
                // where the PATH is a JSONPath string that locates the first occurance.
                // So,
                //      var a = [];
                //      a[0] = a;
                //      return JSON.stringify(JSON.decycle(a));
                // produces the string '[{"$ref":"$"}]'.

                // JSONPath is used to locate the unique object. $ indicates the top level of
                // the object or array. [NUMBER] or [STRING] indicates a child member or
                // property.

                var objects = [],   // Keep a reference to each unique object or array
                paths = [],     // Keep the path to each unique object or array
                queuedObjects = [],
                returnCallback = callback;

                /**
                 * Check the queue to see if all objects have been processed.
                 * if they have, call the callback with the converted object.
                 */
                function checkForCompletion() {
                    if (queuedObjects.length === 0) {
                        returnCallback(derezObj);
                    }    
                }

                /**
                 * Convert a blob to a data URL.
                 * @param {Blob} blob to convert.
                 * @param {String} path of blob in object being encoded.
                 */
                function readBlobAsDataURL(blob, path) {
                    var reader = new FileReader();
                    reader.onloadend = function(loadedEvent) {
                        var dataURL = loadedEvent.target.result;
                        var blobtype = 'Blob';
                        if (blob instanceof File) {
                            //blobtype = 'File';
                        }
                        updateEncodedBlob(dataURL, path, blobtype);
                    };
                    reader.readAsDataURL(blob);
                }
                
                /**
                 * Async handler to update a blob object to a data URL for encoding.
                 * @param {String} dataURL
                 * @param {String} path
                 * @param {String} blobtype - file if the blob is a file; blob otherwise
                 */
                function updateEncodedBlob(dataURL, path, blobtype) {
                    var encoded = queuedObjects.indexOf(path);
                    path = path.replace('$','derezObj');
                    eval(path+'.$enc="'+dataURL+'"');
                    eval(path+'.$type="'+blobtype+'"');
                    queuedObjects.splice(encoded, 1);
                    checkForCompletion();
                }

                function derez(value, path) {

                    // The derez recurses through the object, producing the deep copy.

                    var i,          // The loop counter
                    name,       // Property name
                    nu;         // The new object or array

                    // typeof null === 'object', so go on if this value is really an object but not
                    // one of the weird builtin objects.

                    if (typeof value === 'object' && value !== null &&
                        !(value instanceof Boolean) &&
                        !(value instanceof Date)    &&
                        !(value instanceof Number)  &&
                        !(value instanceof RegExp)  &&
                        !(value instanceof Blob)  &&
                        !(value instanceof String)) {

                        // If the value is an object or array, look to see if we have already
                        // encountered it. If so, return a $ref/path object. This is a hard way,
                        // linear search that will get slower as the number of unique objects grows.

                        for (i = 0; i < objects.length; i += 1) {
                            if (objects[i] === value) {
                                return {$ref: paths[i]};
                            }
                        }

                        // Otherwise, accumulate the unique value and its path.

                        objects.push(value);
                        paths.push(path);

                        // If it is an array, replicate the array.

                        if (Object.prototype.toString.apply(value) === '[object Array]') {
                            nu = [];
                            for (i = 0; i < value.length; i += 1) {
                                nu[i] = derez(value[i], path + '[' + i + ']');
                            }
                        } else {
                            // If it is an object, replicate the object.
                            nu = {};
                            for (name in value) {
                                if (Object.prototype.hasOwnProperty.call(value, name)) {
                                    nu[name] = derez(value[name],
                                     path + '[' + JSON.stringify(name) + ']');
                                }
                            }
                        }

                        return nu;
                    } else if (value instanceof Blob) {
                        //Queue blob for conversion
                        queuedObjects.push(path);
                        readBlobAsDataURL(value, path);
                    } else if (value instanceof Boolean) {
                        value = {
                            '$type': 'Boolean',
                            '$enc': value.toString()
                        };
                    } else if (value instanceof Date) {
                        value = {
                            '$type': 'Date',
                            '$enc': value.getTime()
                        };
                    } else if (value instanceof Number) {
                        value = {
                            '$type': 'Number',
                            '$enc': value.toString()
                        };
                    } else if (value instanceof RegExp) {
                        value = {
                            '$type': 'RegExp',
                            '$enc': value.toString()
                        };
                    } else if (typeof value === 'number') {
                        value = {
                            '$type': 'number',
                            '$enc': value + ''  // handles NaN, Infinity, Negative Infinity
                        };
                    } else if (value === undefined) {
                        value = {
                            '$type': 'undefined'
                        };
                    }
                    return value;
                }
                var derezObj = derez(object, '$');
                checkForCompletion();
            },
                
            retrocycle: function retrocycle($) {
                //From: https://github.com/douglascrockford/JSON-js/blob/master/cycle.js
                // Contains additional logic to convert strings to the following object types 
                // so that they can properly be decoded:
                //  *Boolean
                //  *Date
                //  *File
                //  *Blob
                //  *Number
                //  *Regex
                // Restore an object that was reduced by decycle. Members whose values are
                // objects of the form
                //      {$ref: PATH}
                // are replaced with references to the value found by the PATH. This will
                // restore cycles. The object will be mutated.

                // The eval function is used to locate the values described by a PATH. The
                // root object is kept in a $ variable. A regular expression is used to
                // assure that the PATH is extremely well formed. The regexp contains nested
                // * quantifiers. That has been known to have extremely bad performance
                // problems on some browsers for very long strings. A PATH is expected to be
                // reasonably short. A PATH is allowed to belong to a very restricted subset of
                // Goessner's JSONPath.

                // So,
                //      var s = '[{"$ref":"$"}]';
                //      return JSON.retrocycle(JSON.parse(s));
                // produces an array containing a single element which is the array itself.

                var px = /^\$(?:\[(?:\d+|\"(?:[^\\\"\u0000-\u001f]|\\([\\\"\/bfnrt]|u[0-9a-zA-Z]{4}))*\")\])*$/;
                
                /**
                 * Converts the specified data URL to a Blob object
                 * @param {String} dataURL to convert to a Blob
                 * @returns {Blob} the converted Blob object
                 */
                function dataURLToBlob(dataURL) {
                    var BASE64_MARKER = ';base64,',
                        contentType,
                        parts,
                        raw;
                    if (dataURL.indexOf(BASE64_MARKER) === -1) {
                        parts = dataURL.split(',');
                        contentType = parts[0].split(':')[1];
                        raw = parts[1];

                        return new Blob([raw], {type: contentType});
                    }

                    parts = dataURL.split(BASE64_MARKER);
                    contentType = parts[0].split(':')[1];
                    raw = window.atob(parts[1]);
                    var rawLength = raw.length;
                    var uInt8Array = new Uint8Array(rawLength);

                    for (var i = 0; i < rawLength; ++i) {
                        uInt8Array[i] = raw.charCodeAt(i);
                    }
                    return new Blob([uInt8Array.buffer], {type: contentType});
                }
                
                function rez(value) {
                    // The rez function walks recursively through the object looking for $ref
                    // properties. When it finds one that has a value that is a path, then it
                    // replaces the $ref object with a reference to the value that is found by
                    // the path.

                    var i, item, name, path;

                    if (value && typeof value === 'object') {
                        if (Object.prototype.toString.apply(value) === '[object Array]') {
                            for (i = 0; i < value.length; i += 1) {
                                item = value[i];
                                if (item && typeof item === 'object') {
                                    path = item.$ref;
                                    if (typeof path === 'string' && px.test(path)) {
                                        value[i] = eval(path);
                                    } else {
                                        value[i] = rez(item);
                                    }
                                }
                            }
                        } else {
                            if (value.$type !== undefined) {
                                switch(value.$type) {
                                    case 'Blob':
                                    case 'File':
                                        value = dataURLToBlob(value.$enc);
                                        break;
                                    case 'Boolean':
                                        value = Boolean(value.$enc === 'true');
                                        break;
                                    case 'Date':
                                        value = new Date(value.$enc);
                                        break;
                                    case 'Number':
                                        value = Number(value.$enc);
                                        break;
                                    case 'RegExp':
                                        value = eval(value.$enc);
                                        break;
                                    case 'number':
                                        value = parseFloat(value.$enc);
                                        break;
                                    case 'undefined':
                                        value = undefined;
                                        break;
                                }
                            } else {
                                for (name in value) {
                                    if (typeof value[name] === 'object') {
                                        item = value[name];
                                        if (item) {
                                            path = item.$ref;
                                            if (typeof path === 'string' && px.test(path)) {
                                                value[name] = eval(path);
                                            } else {
                                                value[name] = rez(item);
                                            }
                                        }
                                    }   
                                }
                            }
                        }
                    }
                    return value;
                }
                return rez($);

            },

            /**
             * Encode the specified object as a string.  Because of the asynchronus
             * conversion of Blob/File to string, the encode function requires
             * a callback
             * @param {Object} val the value to convert.
             * @param {function} callback the function to call once conversion is
             * complete.  The callback gets called with the converted value.
             */
            "encode": function(val, callback){
                function finishEncode(val) {
                    callback(JSON.stringify(val));
                }
                this.decycle(val, finishEncode);                        
            },
                    
            /**
             * Deserialize the specified string to an object
             * @param {String} val the serialized string
             * @returns {Object} the deserialized object
             */
            "decode": function(val){
                return this.retrocycle(JSON.parse(val));
            }
        };
    }());
    idbModules.Sca = Sca;
}(idbModules));

(function(idbModules) {
    "use strict";

    /**
     * Encodes the keys based on their types. This is required to maintain collations
     */
    var collations = ["undefined", "number", "date", "string", "array"];

    /**
     * The sign values for numbers, ordered from least to greatest.
     *  - "negativeInfinity": Sorts below all other values.
     *  - "bigNegative": Negative values less than or equal to negative one.
     *  - "smallNegative": Negative values between negative one and zero, noninclusive.
     *  - "smallPositive": Positive values between zero and one, including zero but not one.
     *  - "largePositive": Positive values greater than or equal to one.
     *  - "positiveInfinity": Sorts above all other values.
     */
    var signValues = ["negativeInfinity", "bigNegative", "smallNegative", "smallPositive", "bigPositive", "positiveInfinity"];

    var types = {
        // Undefined is not a valid key type.  It's only used when there is no key.
        undefined: {
            encode: function(key) {
                return collations.indexOf("undefined") + "-";
            },
            decode: function(key) {
                return undefined;
            }
        },

        // Dates are encoded as ISO 8601 strings, in UTC time zone.
        date: {
            encode: function(key) {
                return collations.indexOf("date") + "-" + key.toJSON();
            },
            decode: function(key) {
                return new Date(key.substring(2));
            }
        },

        // Numbers are represented in a lexically sortable base-32 sign-exponent-mantissa
        // notation.
        //
        // sign: takes a value between zero and five, inclusive. Represents infinite cases
        //     and the signs of both the exponent and the fractional part of the number.
        // exponent: paded to two base-32 digits, represented by the 32's compliment in the
        //     "smallPositive" and "bigNegative" cases to ensure proper lexical sorting.
        // mantissa: also called the fractional part. Normed 11-digit base-32 representation.
        //     Represented by the 32's compliment in the "smallNegative" and "bigNegative"
        //     cases to ensure proper lexical sorting.
        number: {
            // The encode step checks for six numeric cases and generates 14-digit encoded
            // sign-exponent-mantissa strings.
            encode: function(key) {
                var key32 = Math.abs(key).toString(32);
                // Get the index of the decimal.
                var decimalIndex = key32.indexOf(".");
                // Remove the decimal.
                key32 = (decimalIndex !== -1) ? key32.replace(".", "") : key32;
                // Get the index of the first significant digit.
                var significantDigitIndex = key32.search(/[^0]/);
                // Truncate leading zeros.
                key32 = key32.slice(significantDigitIndex);
                var sign, exponent = zeros(2), mantissa = zeros(11);

                // Finite cases:
                if (isFinite(key)) {
                    // Negative cases:
                    if (key < 0) {
                        // Negative exponent case:
                        if (key > -1) {
                            sign = signValues.indexOf("smallNegative");
                            exponent = padBase32Exponent(significantDigitIndex);
                            mantissa = flipBase32(padBase32Mantissa(key32));
                        }
                        // Non-negative exponent case:
                        else {
                            sign = signValues.indexOf("bigNegative");
                            exponent = flipBase32(padBase32Exponent(
                                (decimalIndex !== -1) ? decimalIndex : key32.length
                            ));
                            mantissa = flipBase32(padBase32Mantissa(key32));
                        }
                    }
                    // Non-negative cases:
                    else {
                        // Negative exponent case:
                        if (key < 1) {
                            sign = signValues.indexOf("smallPositive");
                            exponent = flipBase32(padBase32Exponent(significantDigitIndex));
                            mantissa = padBase32Mantissa(key32);
                        }
                        // Non-negative exponent case:
                        else {
                            sign = signValues.indexOf("bigPositive");
                            exponent = padBase32Exponent(
                                (decimalIndex !== -1) ? decimalIndex : key32.length
                            );
                            mantissa = padBase32Mantissa(key32);
                        }
                    }
                }
                // Infinite cases:
                else {
                    sign = signValues.indexOf(
                        key > 0 ? "positiveInfinity" : "negativeInfinity"
                    );
                }

                return collations.indexOf("number") + "-" + sign + exponent + mantissa;
            },
            // The decode step must interpret the sign, reflip values encoded as the 32's complements,
            // apply signs to the exponent and mantissa, do the base-32 power operation, and return
            // the original JavaScript number values.
            decode: function(key) {
                var sign = +key.substr(2, 1);
                var exponent = key.substr(3, 2);
                var mantissa = key.substr(5, 11);

                switch (signValues[sign]) {
                    case "negativeInfinity":
                        return -Infinity;
                    case "positiveInfinity":
                        return Infinity;
                    case "bigPositive":
                        return pow32(mantissa, exponent);
                    case "smallPositive":
                        exponent = negate(flipBase32(exponent));
                        return pow32(mantissa, exponent);
                    case "smallNegative":
                        exponent = negate(exponent);
                        mantissa = flipBase32(mantissa);
                        return -pow32(mantissa, exponent);
                    case "bigNegative":
                        exponent = flipBase32(exponent);
                        mantissa = flipBase32(mantissa);
                        return -pow32(mantissa, exponent);
                    default:
                        throw new Error("Invalid number.");
                }
            }
        },

        // Strings are encoded as JSON strings (with quotes and unicode characters escaped).
        //
        // IF the strings are in an array, then some extra encoding is done to make sorting work correctly:
        // Since we can't force all strings to be the same length, we need to ensure that characters line-up properly
        // for sorting, while also accounting for the extra characters that are added when the array itself is encoded as JSON.
        // To do this, each character of the string is prepended with a dash ("-"), and a space is added to the end of the string.
        // This effectively doubles the size of every string, but it ensures that when two arrays of strings are compared,
        // the indexes of each string's characters line up with each other.
        string: {
            encode: function(key, inArray) {
                if (inArray) {
                    // prepend each character with a dash, and append a space to the end
                    key = key.replace(/(.)/g, '-$1') + ' ';
                }
                return collations.indexOf("string") + "-" + key;
            },
            decode: function(key, inArray) {
                key = key.substring(2);
                if (inArray) {
                    // remove the space at the end, and the dash before each character
                    key = key.substr(0, key.length - 1).replace(/-(.)/g, '$1');
                }
                return key;
            }
        },

        // Arrays are encoded as JSON strings.
        // An extra, value is added to each array during encoding to make empty arrays sort correctly.
        array: {
            encode: function(key) {
                var encoded = [];
                for (var i = 0; i < key.length; i++) {
                    var item = key[i];
                    var encodedItem = idbModules.Key.encode(item, true);        // encode the array item
                    encoded[i] = encodedItem;
                }
                encoded.push(collations.indexOf("undefined") + "-");            // append an extra item, so empty arrays sort correctly
                return collations.indexOf("array") + "-" + JSON.stringify(encoded);
            },
            decode: function(key) {
                var decoded = JSON.parse(key.substring(2));
                decoded.pop();                                                  // remove the extra item
                for (var i = 0; i < decoded.length; i++) {
                    var item = decoded[i];
                    var decodedItem = idbModules.Key.decode(item, true);        // decode the item
                    decoded[i] = decodedItem;
                }
                return decoded;
            }
        }
    };

    /**
     * Return a padded base-32 exponent value.
     * @param {number}
     * @return {string}
     */
    function padBase32Exponent(n) {
        n = n.toString(32);
        return (n.length === 1) ? "0" + n : n;
    }

    /**
     * Return a padded base-32 mantissa.
     * @param {string}
     * @return {string}
     */
    function padBase32Mantissa(s) {
        return (s + zeros(11)).slice(0, 11);
    }

    /**
     * Flips each digit of a base-32 encoded string.
     * @param {string} encoded
     */
    function flipBase32(encoded) {
        var flipped = "";
        for (var i = 0; i < encoded.length; i++) {
            flipped += (31 - parseInt(encoded[i], 32)).toString(32);
        }
        return flipped;
    }

    /**
     * Base-32 power function.
     * RESEARCH: This function does not precisely decode floats because it performs
     * floating point arithmetic to recover values. But can the original values be
     * recovered exactly?
     * Someone may have already figured out a good way to store JavaScript floats as
     * binary strings and convert back. Barring a better method, however, one route
     * may be to generate decimal strings that `parseFloat` decodes predictably.
     * @param {string}
     * @param {string}
     * @return {number}
     */
    function pow32(mantissa, exponent) {
        var whole, fraction, expansion;
        exponent = parseInt(exponent, 32);
        if (exponent < 0) {
            return roundToPrecision(
                parseInt(mantissa, 32) * Math.pow(32, exponent - 10)
            );
        }
        else {
            if (exponent < 11) {
                whole = mantissa.slice(0, exponent);
                whole = parseInt(whole, 32);
                fraction = mantissa.slice(exponent);
                fraction = parseInt(fraction, 32) * Math.pow(32, exponent - 11);
                return roundToPrecision(whole + fraction);
            }
            else {
                expansion = mantissa + zeros(exponent - 11);
                return parseInt(expansion, 32);
            }
        }
    }

    /**
     *
     */
    function roundToPrecision(num, precision) {
        precision = precision || 16;
        return parseFloat(num.toPrecision(precision));
    }

    /**
     * Returns a string of n zeros.
     * @param {number}
     * @return {string}
     */
    function zeros(n) {
        var result = "";
        while (n--) {
            result = result + "0";
        }
        return result;
    }

    /**
     * Negates numeric strings.
     * @param {string}
     * @return {string}
     */
    function negate(s) {
        return "-" + s;
    }

    /**
     * Returns the string "number", "date", "string", or "array".
     */
    function getType(key) {
        if (key instanceof Date) {
            return "date";
        }
        if (key instanceof Array) {
            return "array";
        }
        return typeof key;
    }

    /**
     * Keys must be strings, numbers, Dates, or Arrays
     */
    function validate(key) {
        var type = getType(key);
        if (type === "array") {
            for (var i = 0; i < key.length; i++) {
                validate(key[i]);
            }
        }
        else if (!types[type] || (type !== "string" && isNaN(key))) {
            throw idbModules.util.createDOMException("DataError", "Not a valid key");
        }
    }

    /**
     * Returns the value of an inline key
     * @param {object} source
     * @param {string|array} keyPath
     */
    function getValue(source, keyPath) {
        try {
            if (keyPath instanceof Array) {
                var arrayValue = [];
                for (var i = 0; i < keyPath.length; i++) {
                    arrayValue.push(eval("source." + keyPath[i]));
                }
                return arrayValue;
            } else {
                return eval("source." + keyPath);
            }
        }
        catch (e) {
            return undefined;
        }
    }

    /**
     * Sets the inline key value
     * @param {object} source
     * @param {string} keyPath
     * @param {*} value
     */
    function setValue(source, keyPath, value) {
        var props = keyPath.split('.');
        for (var i = 0; i < props.length - 1; i++) {
            var prop = props[i];
            source = source[prop] = source[prop] || {};
        }
        source[props[props.length - 1]] = value;
    }

    /**
     * Determines whether an index entry matches a multi-entry key value.
     * @param {string} encodedEntry     The entry value (already encoded)
     * @param {string} encodedKey       The full index key (already encoded)
     * @returns {boolean}
     */
    function isMultiEntryMatch(encodedEntry, encodedKey) {
        var keyType = collations[encodedKey.substring(0, 1)];

        if (keyType === "array") {
            return encodedKey.indexOf(encodedEntry) > 1;
        }
        else {
            return encodedKey === encodedEntry;
        }
    }

    function isKeyInRange(key, range) {
        var lowerMatch = range.lower === undefined;
        var upperMatch = range.upper === undefined;
        var encodedKey = idbModules.Key.encode(key, true);

        if (range.lower !== undefined) {
            if (range.lowerOpen && encodedKey > range.__lower) {
                lowerMatch = true;
            }
            if (!range.lowerOpen && encodedKey >= range.__lower) {
                lowerMatch = true;
            }
        }
        if (range.upper !== undefined) {
            if (range.upperOpen && encodedKey < range.__upper) {
                upperMatch = true;
            }
            if (!range.upperOpen && encodedKey <= range.__upper) {
                upperMatch = true;
            }
        }

        return lowerMatch && upperMatch;
    }

    function findMultiEntryMatches(keyEntry, range) {
        var matches = [];

        if (keyEntry instanceof Array) {
            for (var i = 0; i < keyEntry.length; i++) {
                var key = keyEntry[i];

                if (key instanceof Array) {
                    if (range.lower === range.upper) {
                        continue;
                    }
                    if (key.length === 1) {
                        key = key[0];
                    } else {
                        var nested = findMultiEntryMatches(key, range);
                        if (nested.length > 0) {
                            matches.push(key);
                        }
                        continue;
                    }
                }

                if (isKeyInRange(key, range)) {
                    matches.push(key);
                }
            }
        } else {
            if (isKeyInRange(keyEntry, range)) {
                matches.push(keyEntry);
            }
        }
        return matches;
    }

    idbModules.Key = {
        encode: function(key, inArray) {
            if (key === undefined) {
                return null;
            }
            return types[getType(key)].encode(key, inArray);
        },
        decode: function(key, inArray) {
            if (typeof key !== "string") {
                return undefined;
            }
            return types[collations[key.substring(0, 1)]].decode(key, inArray);
        },
        validate: validate,
        getValue: getValue,
        setValue: setValue,
        isMultiEntryMatch: isMultiEntryMatch,
        findMultiEntryMatches: findMultiEntryMatches
    };
}(idbModules));

(function(idbModules) {
    'use strict';

    /**
     * Creates a native Event object, for browsers that support it
     * @returns {Event}
     */
    function createNativeEvent(type, debug) {
        var event = new Event(type);
        event.debug = debug;

        // Make the "target" writable
        Object.defineProperty(event, 'target', {
            writable: true
        });

        return event;
    }

    /**
     * A shim Event class, for browsers that don't allow us to create native Event objects.
     * @constructor
     */
    function ShimEvent(type, debug) {
        this.type = type;
        this.debug = debug;
        this.bubbles = false;
        this.cancelable = false;
        this.eventPhase = 0;
        this.timeStamp = new Date().valueOf();
    }

    var useNativeEvent = false;
    try {
        // Test whether we can use the browser's native Event class
        var test = createNativeEvent('test type', 'test debug');
        var target = {test: 'test target'};
        test.target = target;

        if (test instanceof Event && test.type === 'test type' && test.debug === 'test debug' && test.target === target) {
            // Native events work as expected
            useNativeEvent = true;
        }
    }
    catch (e) {}

    if (useNativeEvent) {
        idbModules.Event = Event;
        idbModules.IDBVersionChangeEvent = Event;
        idbModules.util.createEvent = createNativeEvent;
    }
    else {
        idbModules.Event = ShimEvent;
        idbModules.IDBVersionChangeEvent = ShimEvent;
        idbModules.util.createEvent = function(type, debug) {
            return new ShimEvent(type, debug);
        };
    }
}(idbModules));

(function(idbModules) {
    'use strict';

    /**
     * Creates a native DOMException, for browsers that support it
     * @returns {DOMException}
     */
    function createNativeDOMException(name, message) {
        var e = new DOMException.prototype.constructor(0, message);
        e.name = name || 'DOMException';
        e.message = message;
        return e;
    }

    /**
     * Creates a native DOMError, for browsers that support it
     * @returns {DOMError}
     */
    function createNativeDOMError(name, message) {
        name = name || 'DOMError';
        var e = new DOMError(name, message);
        e.name === name || (e.name = name);
        e.message === message || (e.message = message);
        return e;
    }

    /**
     * Creates a generic Error object
     * @returns {Error}
     */
    function createError(name, message) {
        var e = new Error(message);
        e.name = name || 'DOMException';
        e.message = message;
        return e;
    }

    /**
     * Logs detailed error information to the console.
     * @param {string} name
     * @param {string} message
     * @param {string|Error|null} error
     */
    idbModules.util.logError = function(name, message, error) {
        if (idbModules.DEBUG) {
            if (error && error.message) {
                error = error.message;
            }

            var method = typeof(console.error) === 'function' ? 'error' : 'log';
            console[method](name + ': ' + message + '. ' + (error || ''));
            console.trace && console.trace();
        }
    };

    /**
     * Finds the error argument.  This is useful because some WebSQL callbacks
     * pass the error as the first argument, and some pass it as the second argument.
     * @param {array} args
     * @returns {Error|DOMException|undefined}
     */
    idbModules.util.findError = function(args) {
        var err;
        if (args) {
            if (args.length === 1) {
                return args[0];
            }
            for (var i = 0; i < args.length; i++) {
                var arg = args[i];
                if (arg instanceof Error || arg instanceof DOMException) {
                    return arg;
                }
                else if (arg && typeof arg.message === "string") {
                    err = arg;
                }
            }
        }
        return err;
    };

    var test, useNativeDOMException = false, useNativeDOMError = false;

    // Test whether we can use the browser's native DOMException class
    try {
        test = createNativeDOMException('test name', 'test message');
        if (test instanceof DOMException && test.name === 'test name' && test.message === 'test message') {
            // Native DOMException works as expected
            useNativeDOMException = true;
        }
    }
    catch (e) {}

    // Test whether we can use the browser's native DOMError class
    try {
        test = createNativeDOMError('test name', 'test message');
        if (test instanceof DOMError && test.name === 'test name' && test.message === 'test message') {
            // Native DOMError works as expected
            useNativeDOMError = true;
        }
    }
    catch (e) {}

    if (useNativeDOMException) {
        idbModules.DOMException = DOMException;
        idbModules.util.createDOMException = function(name, message, error) {
            idbModules.util.logError(name, message, error);
            return createNativeDOMException(name, message);
        };
    }
    else {
        idbModules.DOMException = Error;
        idbModules.util.createDOMException = function(name, message, error) {
            idbModules.util.logError(name, message, error);
            return createError(name, message);
        };
    }

    if (useNativeDOMError) {
        idbModules.DOMError = DOMError;
        idbModules.util.createDOMError = function(name, message, error) {
            idbModules.util.logError(name, message, error);
            return createNativeDOMError(name, message);
        };
    }
    else {
        idbModules.DOMError = Error;
        idbModules.util.createDOMError = function(name, message, error) {
            idbModules.util.logError(name, message, error);
            return createError(name, message);
        };
    }
}(idbModules));

(function(idbModules){
    'use strict';

    /**
     * The IDBRequest Object that is returns for all async calls
     * http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#request-api
     */
    function IDBRequest(){
        this.onsuccess = this.onerror = this.result = this.error = this.source = this.transaction = null;
        this.readyState = "pending";
    }

    /**
     * The IDBOpenDBRequest called when a database is opened
     */
    function IDBOpenDBRequest(){
        this.onblocked = this.onupgradeneeded = null;
    }
    IDBOpenDBRequest.prototype = new IDBRequest();
    IDBOpenDBRequest.prototype.constructor = IDBOpenDBRequest;
    
    idbModules.IDBRequest = IDBRequest;
    idbModules.IDBOpenDBRequest = IDBOpenDBRequest;
    
}(idbModules));

(function(idbModules, undefined){
    'use strict';

    /**
     * The IndexedDB KeyRange object
     * http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#dfn-key-range
     * @param {Object} lower
     * @param {Object} upper
     * @param {Object} lowerOpen
     * @param {Object} upperOpen
     */
    function IDBKeyRange(lower, upper, lowerOpen, upperOpen){
        if (lower !== undefined) {
            idbModules.Key.validate(lower);
        }
        if (upper !== undefined) {
            idbModules.Key.validate(upper);
        }

        this.lower = lower;
        this.upper = upper;
        this.lowerOpen = !!lowerOpen;
        this.upperOpen = !!upperOpen;
    }

    IDBKeyRange.only = function(value){
        return new IDBKeyRange(value, value, false, false);
    };

    IDBKeyRange.lowerBound = function(value, open){
        return new IDBKeyRange(value, undefined, open, undefined);
    };
    IDBKeyRange.upperBound = function(value, open){
        return new IDBKeyRange(undefined, value, undefined, open);
    };
    IDBKeyRange.bound = function(lower, upper, lowerOpen, upperOpen){
        return new IDBKeyRange(lower, upper, lowerOpen, upperOpen);
    };

    idbModules.IDBKeyRange = IDBKeyRange;

}(idbModules));

(function(idbModules, undefined){
    'use strict';

    /**
     * The IndexedDB Cursor Object
     * http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#idl-def-IDBCursor
     * @param {IDBKeyRange} range
     * @param {string} direction
     * @param {IDBObjectStore} store
     * @param {IDBObjectStore|IDBIndex} source
     * @param {string} keyColumnName
     * @param {string} valueColumnName
     * @param {boolean} count
     */
    function IDBCursor(range, direction, store, source, keyColumnName, valueColumnName, count){
        // Calling openCursor on an index or objectstore with null is allowed but we treat it as undefined internally
        if (range === null) {
            range = undefined;
        }
        if (range !== undefined && !(range instanceof idbModules.IDBKeyRange)) {
            range = new idbModules.IDBKeyRange(range, range, false, false);
        }
        store.transaction.__assertActive();
        if (direction !== undefined && ["next", "prev", "nextunique", "prevunique"].indexOf(direction) === -1) {
            throw new TypeError(direction + "is not a valid cursor direction");
        }

        this.source = source;
        this.direction = direction || "next";
        this.key = undefined;
        this.primaryKey = undefined;
        this.__store = store;
        this.__range = range;
        this.__req = new idbModules.IDBRequest();
        this.__keyColumnName = keyColumnName;
        this.__valueColumnName = valueColumnName;
        this.__valueDecoder = valueColumnName === "value" ? idbModules.Sca : idbModules.Key;
        this.__count = count;
        this.__offset = -1; // Setting this to -1 as continue will set it to 0 anyway
        this.__lastKeyContinued = undefined; // Used when continuing with a key
        this.__multiEntryIndex = source instanceof idbModules.IDBIndex ? source.multiEntry : false;
        this.__unique = this.direction.indexOf("unique") !== -1;

        if (range !== undefined) {
            // Encode the key range and cache the encoded values, so we don't have to re-encode them over and over
            range.__lower = range.lower !== undefined && idbModules.Key.encode(range.lower, this.__multiEntryIndex);
            range.__upper = range.upper !== undefined && idbModules.Key.encode(range.upper, this.__multiEntryIndex);
        }

        this["continue"]();
    }

    IDBCursor.prototype.__find = function (/* key, tx, success, error, recordsToLoad */) {
        var args = Array.prototype.slice.call(arguments);
        if (this.__multiEntryIndex) {
            this.__findMultiEntry.apply(this, args);
        } else {
            this.__findBasic.apply(this, args);
        }
    };

    IDBCursor.prototype.__findBasic = function (key, tx, success, error, recordsToLoad) {
        recordsToLoad = recordsToLoad || 1;

        var me = this;
        var quotedKeyColumnName = idbModules.util.quote(me.__keyColumnName);
        var sql = ["SELECT * FROM", idbModules.util.quote(me.__store.name)];
        var sqlValues = [];
        sql.push("WHERE", quotedKeyColumnName, "NOT NULL");
        if (me.__range && (me.__range.lower !== undefined || me.__range.upper !== undefined )) {
            sql.push("AND");
            if (me.__range.lower !== undefined) {
                sql.push(quotedKeyColumnName, (me.__range.lowerOpen ? ">" : ">="), "?");
                sqlValues.push(me.__range.__lower);
            }
            (me.__range.lower !== undefined && me.__range.upper !== undefined) && sql.push("AND");
            if (me.__range.upper !== undefined) {
                sql.push(quotedKeyColumnName, (me.__range.upperOpen ? "<" : "<="), "?");
                sqlValues.push(me.__range.__upper);
            }
        }
        if (typeof key !== "undefined") {
            me.__lastKeyContinued = key;
            me.__offset = 0;
        }
        if (me.__lastKeyContinued !== undefined) {
            sql.push("AND", quotedKeyColumnName, ">= ?");
            idbModules.Key.validate(me.__lastKeyContinued);
            sqlValues.push(idbModules.Key.encode(me.__lastKeyContinued));
        }

        // Determine the ORDER BY direction based on the cursor.
        var direction = me.direction === 'prev' || me.direction === 'prevunique' ? 'DESC' : 'ASC';

        if (!me.__count) {
            sql.push("ORDER BY", quotedKeyColumnName, direction);
            sql.push("LIMIT", recordsToLoad, "OFFSET", me.__offset);
        }
        sql = sql.join(" ");
        idbModules.DEBUG && console.log(sql, sqlValues);

        me.__prefetchedData = null;
        me.__prefetchedIndex = 0;
        tx.executeSql(sql, sqlValues, function (tx, data) {
            if (me.__count) {
                success(undefined, data.rows.length, undefined);
            }
            else if (data.rows.length > 1) {
                me.__prefetchedData = data.rows;
                me.__prefetchedIndex = 0;
                idbModules.DEBUG && console.log("Preloaded " + me.__prefetchedData.length + " records for cursor");
                me.__decode(data.rows.item(0), success);
            }
            else if (data.rows.length === 1) {
                me.__decode(data.rows.item(0), success);
            }
            else {
                idbModules.DEBUG && console.log("Reached end of cursors");
                success(undefined, undefined, undefined);
            }
        }, function (tx, err) {
            idbModules.DEBUG && console.log("Could not execute Cursor.continue", sql, sqlValues);
            error(err);
        });
    };

    IDBCursor.prototype.__findMultiEntry = function (key, tx, success, error) {
        var me = this;

        if (me.__prefetchedData && me.__prefetchedData.length === me.__prefetchedIndex) {
            idbModules.DEBUG && console.log("Reached end of multiEntry cursor");
            success(undefined, undefined, undefined);
            return;
        }

        var quotedKeyColumnName = idbModules.util.quote(me.__keyColumnName);
        var sql = ["SELECT * FROM", idbModules.util.quote(me.__store.name)];
        var sqlValues = [];
        sql.push("WHERE", quotedKeyColumnName, "NOT NULL");
        if (me.__range && (me.__range.lower !== undefined && me.__range.upper !== undefined)) {
            if (me.__range.upper.indexOf(me.__range.lower) === 0) {
                sql.push("AND", quotedKeyColumnName, "LIKE ?");
                sqlValues.push("%" + me.__range.__lower.slice(0, -1) + "%");
            }
        }
        if (typeof key !== "undefined") {
            me.__lastKeyContinued = key;
            me.__offset = 0;
        }
        if (me.__lastKeyContinued !== undefined) {
            sql.push("AND", quotedKeyColumnName, ">= ?");
            idbModules.Key.validate(me.__lastKeyContinued);
            sqlValues.push(idbModules.Key.encode(me.__lastKeyContinued));
        }

        // Determine the ORDER BY direction based on the cursor.
        var direction = me.direction === 'prev' || me.direction === 'prevunique' ? 'DESC' : 'ASC';

        if (!me.__count) {
            sql.push("ORDER BY key", direction);
        }
        sql = sql.join(" ");
        idbModules.DEBUG && console.log(sql, sqlValues);

        me.__prefetchedData = null;
        me.__prefetchedIndex = 0;
        tx.executeSql(sql, sqlValues, function (tx, data) {
            me.__multiEntryOffset = data.rows.length;

            if (data.rows.length > 0) {
                var rows = [];

                for (var i = 0; i < data.rows.length; i++) {
                    var rowItem = data.rows.item(i);
                    var rowKey = idbModules.Key.decode(rowItem[me.__keyColumnName], true);
                    var matches = idbModules.Key.findMultiEntryMatches(rowKey, me.__range);

                    for (var j = 0; j < matches.length; j++) {
                        var matchingKey = matches[j];
                        var clone = {
                            matchingKey: idbModules.Key.encode(matchingKey, true),
                            key: rowItem.key
                        };
                        clone[me.__keyColumnName] = rowItem[me.__keyColumnName];
                        clone[me.__valueColumnName] = rowItem[me.__valueColumnName];
                        rows.push(clone);
                    }
                }

                var reverse = me.direction.indexOf("prev") === 0;
                rows.sort(function (a, b) {
                    if (a.matchingKey.replace('[','z') < b.matchingKey.replace('[','z')) {
                        return reverse ? 1 : -1;
                    }
                    if (a.matchingKey.replace('[','z') > b.matchingKey.replace('[','z')) {
                        return reverse ? -1 : 1;
                    }
                    if (a.key < b.key) {
                        return me.direction === "prev" ? 1 : -1;
                    }
                    if (a.key > b.key) {
                        return me.direction === "prev" ? -1 : 1;
                    }
                    return 0;
                });

                me.__prefetchedData = {
                    data: rows,
                    length: rows.length,
                    item: function (index) {
                        return this.data[index];
                    }
                };
                me.__prefetchedIndex = 0;

                if (me.__count) {
                    success(undefined, rows.length, undefined);
                }
                else if (rows.length > 1) {
                    idbModules.DEBUG && console.log("Preloaded " + me.__prefetchedData.length + " records for multiEntry cursor");
                    me.__decode(rows[0], success);
                } else if (rows.length === 1) {
                    idbModules.DEBUG && console.log("Reached end of multiEntry cursor");
                    me.__decode(rows[0], success);
                } else {
                    idbModules.DEBUG && console.log("Reached end of multiEntry cursor");
                    success(undefined, undefined, undefined);
                }
            }
            else {
                idbModules.DEBUG && console.log("Reached end of multiEntry cursor");
                success(undefined, undefined, undefined);
            }
        }, function (tx, err) {
            idbModules.DEBUG && console.log("Could not execute Cursor.continue", sql, sqlValues);
            error(err);
        });
    };

    /**
     * Creates an "onsuccess" callback
     * @private
     */
    IDBCursor.prototype.__onsuccess = function(success) {
        var me = this;
        return function(key, value, primaryKey) {
            if (me.__count) {
                success(value, me.__req);
            }
            else {
                me.key = key === undefined ? null : key;
                me.value = value === undefined ? null : value;
                me.primaryKey = primaryKey === undefined ? null : primaryKey;
                var result = key === undefined ? null : me;
                success(result, me.__req);
            }
        };
    };

    IDBCursor.prototype.__decode = function (rowItem, callback) {
        if (this.__multiEntryIndex && this.__unique) {
            if (!this.__matchedKeys) {
                this.__matchedKeys = {};
            }
            if (this.__matchedKeys[rowItem.matchingKey]) {
                callback(undefined, undefined, undefined);
                return;
            }
            this.__matchedKeys[rowItem.matchingKey] = true;
        }
        var key = idbModules.Key.decode(this.__multiEntryIndex ? rowItem.matchingKey : rowItem[this.__keyColumnName], this.__multiEntryIndex);
        var val = this.__valueDecoder.decode(rowItem[this.__valueColumnName]);
        var primaryKey = idbModules.Key.decode(rowItem.key);
        callback(key, val, primaryKey);
    };

    IDBCursor.prototype["continue"] = function (key) {
        var recordsToPreloadOnContinue = idbModules.cursorPreloadPackSize || 100;
        var me = this;

        this.__store.transaction.__pushToQueue(me.__req, function cursorContinue(tx, args, success, error) {
            me.__offset++;

            if (me.__prefetchedData) {
                // We have pre-loaded data for the cursor
                me.__prefetchedIndex++;
                if (me.__prefetchedIndex < me.__prefetchedData.length) {
                    me.__decode(me.__prefetchedData.item(me.__prefetchedIndex), me.__onsuccess(success));
                    return;
                }
            }

            // No pre-fetched data, do query
            me.__find(key, tx, me.__onsuccess(success), error, recordsToPreloadOnContinue);
        });
    };

    IDBCursor.prototype.advance = function(count){
        if (count <= 0) {
            throw idbModules.util.createDOMException("Type Error", "Count is invalid - 0 or negative", count);
        }
        var me = this;
        this.__store.transaction.__pushToQueue(me.__req, function cursorAdvance(tx, args, success, error){
            me.__offset += count;
            me.__find(undefined, tx, me.__onsuccess(success), error);
        });
    };

    IDBCursor.prototype.update = function(valueToUpdate){
        var me = this;
        me.__store.transaction.__assertWritable();
        return me.__store.transaction.__addToTransactionQueue(function cursorUpdate(tx, args, success, error){
            idbModules.Sca.encode(valueToUpdate, function(encoded) {
                me.__find(undefined, tx, function(key, value, primaryKey){
                    var store = me.__store;
                    var params = [encoded];
                    var sql = ["UPDATE", idbModules.util.quote(store.name), "SET value = ?"];
                    idbModules.Key.validate(primaryKey);

                    // Also correct the indexes in the table
                    for (var i = 0; i < store.indexNames.length; i++) {
                        var index = store.__indexes[store.indexNames[i]];
                        var indexKey = idbModules.Key.getValue(valueToUpdate, index.keyPath);
                        sql.push(",", idbModules.util.quote(index.name), "= ?");
                        params.push(idbModules.Key.encode(indexKey, index.multiEntry));
                    }

                    sql.push("WHERE key = ?");
                    params.push(idbModules.Key.encode(primaryKey));

                    idbModules.DEBUG && console.log(sql.join(" "), encoded, key, primaryKey);
                    tx.executeSql(sql.join(" "), params, function(tx, data){
                        me.__prefetchedData = null;
                        me.__prefetchedIndex = 0;
                        if (data.rowsAffected === 1) {
                            success(key);
                        }
                        else {
                            error("No rows with key found" + key);
                        }
                    }, function(tx, data){
                        error(data);
                    });
                }, error);
            });
        });
    };

    IDBCursor.prototype["delete"] = function(){
        var me = this;
        me.__store.transaction.__assertWritable();
        return this.__store.transaction.__addToTransactionQueue(function cursorDelete(tx, args, success, error){
            me.__find(undefined, tx, function(key, value, primaryKey){
                var sql = "DELETE FROM  " + idbModules.util.quote(me.__store.name) + " WHERE key = ?";
                idbModules.DEBUG && console.log(sql, key, primaryKey);
                idbModules.Key.validate(primaryKey);
                tx.executeSql(sql, [idbModules.Key.encode(primaryKey)], function(tx, data){
                    me.__prefetchedData = null;
                    me.__prefetchedIndex = 0;
                    if (data.rowsAffected === 1) {
                        // lower the offset or we will miss a row
                        me.__offset--;
                        success(undefined);
                    }
                    else {
                        error("No rows with key found" + key);
                    }
                }, function(tx, data){
                    error(data);
                });
            }, error);
        });
    };

    idbModules.IDBCursor = IDBCursor;
}(idbModules));

(function(idbModules, undefined) {
    'use strict';

    /**
     * IDB Index
     * http://www.w3.org/TR/IndexedDB/#idl-def-IDBIndex
     * @param {IDBObjectStore} store
     * @param {IDBIndexProperties} indexProperties
     * @constructor
     */
    function IDBIndex(store, indexProperties) {
        this.objectStore = store;
        this.name = indexProperties.columnName;
        this.keyPath = indexProperties.keyPath;
        this.multiEntry = indexProperties.optionalParams && indexProperties.optionalParams.multiEntry;
        this.unique = indexProperties.optionalParams && indexProperties.optionalParams.unique;
        this.__deleted = !!indexProperties.__deleted;
    }

    /**
     * Clones an IDBIndex instance for a different IDBObjectStore instance.
     * @param {IDBIndex} index
     * @param {IDBObjectStore} store
     * @protected
     */
    IDBIndex.__clone = function(index, store) {
        return new IDBIndex(store, {
            columnName: index.name,
            keyPath: index.keyPath,
            optionalParams: {
                multiEntry: index.multiEntry,
                unique: index.unique
            }
        });
    };

    /**
     * Creates a new index on an object store.
     * @param {IDBObjectStore} store
     * @param {IDBIndex} index
     * @returns {IDBIndex}
     * @protected
     */
    IDBIndex.__createIndex = function(store, index) {
        var columnExists = !!store.__indexes[index.name] && store.__indexes[index.name].__deleted;

        // Add the index to the IDBObjectStore
        store.__indexes[index.name] = index;
        store.indexNames.push(index.name);

        // Create the index in WebSQL
        var transaction = store.transaction;
        transaction.__addToTransactionQueue(function createIndex(tx, args, success, failure) {
            function error(tx, err) {
                failure(idbModules.util.createDOMException(0, "Could not create index \"" + index.name + "\"", err));
            }

            function applyIndex(tx) {
                // Update the object store's index list
                IDBIndex.__updateIndexList(store, tx, function() {
                    // Add index entries for all existing records
                    tx.executeSql("SELECT * FROM " + idbModules.util.quote(store.name), [], function(tx, data) {
                        idbModules.DEBUG && console.log("Adding existing " + store.name + " records to the " + index.name + " index");
                        addIndexEntry(0);

                        function addIndexEntry(i) {
                            if (i < data.rows.length) {
                                try {
                                    var value = idbModules.Sca.decode(data.rows.item(i).value);
                                    var indexKey = idbModules.Key.getValue(value, index.keyPath);
                                    indexKey = idbModules.Key.encode(indexKey, index.multiEntry);

                                    tx.executeSql("UPDATE " + idbModules.util.quote(store.name) + " set " + idbModules.util.quote(index.name) + " = ? where key = ?", [indexKey, data.rows.item(i).key], function(tx, data) {
                                        addIndexEntry(i + 1);
                                    }, error);
                                }
                                catch (e) {
                                    // Not a valid value to insert into index, so just continue
                                    addIndexEntry(i + 1);
                                }
                            }
                            else {
                                success(store);
                            }
                        }
                    }, error);
                }, error);
            }

            if (columnExists) {
                // For a previously existing index, just update the index entries in the existing column
                applyIndex(tx);
            }
            else {
                // For a new index, add a new column to the object store, then apply the index
                var sql = ["ALTER TABLE", idbModules.util.quote(store.name), "ADD", idbModules.util.quote(index.name), "BLOB"].join(" ");
                idbModules.DEBUG && console.log(sql);
                tx.executeSql(sql, [], applyIndex, error);
            }
        });
    };

    /**
     * Deletes an index from an object store.
     * @param {IDBObjectStore} store
     * @param {IDBIndex} index
     * @protected
     */
    IDBIndex.__deleteIndex = function(store, index) {
        // Remove the index from the IDBObjectStore
        store.__indexes[index.name].__deleted = true;
        store.indexNames.splice(store.indexNames.indexOf(index.name), 1);

        // Remove the index in WebSQL
        var transaction = store.transaction;
        transaction.__addToTransactionQueue(function createIndex(tx, args, success, failure) {
            function error(tx, err) {
                failure(idbModules.util.createDOMException(0, "Could not delete index \"" + index.name + "\"", err));
            }

            // Update the object store's index list
            IDBIndex.__updateIndexList(store, tx, success, error);
        });
    };

    /**
     * Updates index list for the given object store.
     * @param {IDBObjectStore} store
     * @param {object} tx
     * @param {function} success
     * @param {function} failure
     */
    IDBIndex.__updateIndexList = function(store, tx, success, failure) {
        var indexList = {};
        for (var i = 0; i < store.indexNames.length; i++) {
            var idx = store.__indexes[store.indexNames[i]];
            /** @type {IDBIndexProperties} **/
            indexList[idx.name] = {
                columnName: idx.name,
                keyPath: idx.keyPath,
                optionalParams: {
                    unique: idx.unique,
                    multiEntry: idx.multiEntry
                },
                deleted: !!idx.deleted
            };
        }

        idbModules.DEBUG && console.log("Updating the index list for " + store.name, indexList);
        tx.executeSql("UPDATE __sys__ set indexList = ? where name = ?", [JSON.stringify(indexList), store.name], function() {
            success(store);
        }, failure);
    };

    /**
     * Retrieves index data for the given key
     * @param {*|IDBKeyRange} key
     * @param {string} opType
     * @returns {IDBRequest}
     * @private
     */
    IDBIndex.prototype.__fetchIndexData = function(key, opType) {
        var me = this;
        var hasKey, encodedKey;

        // key is optional
        if (arguments.length === 1) {
            opType = key;
            hasKey = false;
        }
        else {
            idbModules.Key.validate(key);
            encodedKey = idbModules.Key.encode(key, me.multiEntry);
            hasKey = true;
        }

        return me.objectStore.transaction.__addToTransactionQueue(function fetchIndexData(tx, args, success, error) {
            var sql = ["SELECT * FROM", idbModules.util.quote(me.objectStore.name), "WHERE", idbModules.util.quote(me.name), "NOT NULL"];
            var sqlValues = [];
            if (hasKey) {
                if (me.multiEntry) {
                    sql.push("AND", idbModules.util.quote(me.name), "LIKE ?");
                    sqlValues.push("%" + encodedKey + "%");
                }
                else {
                    sql.push("AND", idbModules.util.quote(me.name), "= ?");
                    sqlValues.push(encodedKey);
                }
            }
            idbModules.DEBUG && console.log("Trying to fetch data for Index", sql.join(" "), sqlValues);
            tx.executeSql(sql.join(" "), sqlValues, function(tx, data) {
                var recordCount = 0, record = null;
                if (me.multiEntry) {
                    for (var i = 0; i < data.rows.length; i++) {
                        var row = data.rows.item(i);
                        var rowKey = idbModules.Key.decode(row[me.name]);
                        if (hasKey && idbModules.Key.isMultiEntryMatch(encodedKey, row[me.name])) {
                            recordCount++;
                            record = record || row;
                        }
                        else if (!hasKey && rowKey !== undefined) {
                            recordCount = recordCount + (rowKey instanceof Array ? rowKey.length : 1);
                            record = record || row;
                        }
                    }
                }
                else {
                    recordCount = data.rows.length;
                    record = recordCount && data.rows.item(0);
                }

                if (opType === "count") {
                    success(recordCount);
                }
                else if (recordCount === 0) {
                    success(undefined);
                }
                else if (opType === "key") {
                    success(idbModules.Key.decode(record.key));
                }
                else { // when opType is value
                    success(idbModules.Sca.decode(record.value));
                }
            }, error);
        });
    };

    /**
     * Opens a cursor over the given key range.
     * @param {IDBKeyRange} range
     * @param {string} direction
     * @returns {IDBRequest}
     */
    IDBIndex.prototype.openCursor = function(range, direction) {
        return new idbModules.IDBCursor(range, direction, this.objectStore, this, this.name, "value").__req;
    };

    /**
     * Opens a cursor over the given key range.  The cursor only includes key values, not data.
     * @param {IDBKeyRange} range
     * @param {string} direction
     * @returns {IDBRequest}
     */
    IDBIndex.prototype.openKeyCursor = function(range, direction) {
        return new idbModules.IDBCursor(range, direction, this.objectStore, this, this.name, "key").__req;
    };

    IDBIndex.prototype.get = function(key) {
        if (arguments.length === 0) {
            throw new TypeError("No key was specified");
        }

        return this.__fetchIndexData(key, "value");
    };

    IDBIndex.prototype.getKey = function(key) {
        if (arguments.length === 0) {
            throw new TypeError("No key was specified");
        }

        return this.__fetchIndexData(key, "key");
    };

    IDBIndex.prototype.count = function(key) {
        // key is optional
        if (key === undefined) {
            return this.__fetchIndexData("count");
        }
        else if (key instanceof idbModules.IDBKeyRange) {
            return new idbModules.IDBCursor(key, "next", this.objectStore, this, this.name, "value", true).__req;
        }
        else {
            return this.__fetchIndexData(key, "count");
        }
    };

    idbModules.IDBIndex = IDBIndex;
}(idbModules));

(function(idbModules) {
    'use strict';

    /**
     * IndexedDB Object Store
     * http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#idl-def-IDBObjectStore
     * @param {IDBObjectStoreProperties} storeProperties
     * @param {IDBTransaction} transaction
     * @constructor
     */
    function IDBObjectStore(storeProperties, transaction) {
        this.name = storeProperties.name;
        this.keyPath = JSON.parse(storeProperties.keyPath);
        this.transaction = transaction;

        // autoInc is numeric (0/1) on WinPhone
        this.autoIncrement = typeof storeProperties.autoInc === "string" ? storeProperties.autoInc === "true" : !!storeProperties.autoInc;

        this.__indexes = {};
        this.indexNames = new idbModules.util.StringList();
        var indexList = JSON.parse(storeProperties.indexList);
        for (var indexName in indexList) {
            if (indexList.hasOwnProperty(indexName)) {
                var index = new idbModules.IDBIndex(this, indexList[indexName]);
                this.__indexes[index.name] = index;
                if (!index.__deleted) {
                    this.indexNames.push(index.name);
                }
            }
        }
    }

    /**
     * Clones an IDBObjectStore instance for a different IDBTransaction instance.
     * @param {IDBObjectStore} store
     * @param {IDBTransaction} transaction
     * @protected
     */
    IDBObjectStore.__clone = function(store, transaction) {
        var newStore = new IDBObjectStore({
            name: store.name,
            keyPath: JSON.stringify(store.keyPath),
            autoInc: JSON.stringify(store.autoIncrement),
            indexList: "{}"
        }, transaction);
        newStore.__indexes = store.__indexes;
        newStore.indexNames = store.indexNames;
        return newStore;
    };

    /**
     * Creates a new object store in the database.
     * @param {IDBDatabase} db
     * @param {IDBObjectStore} store
     * @protected
     */
    IDBObjectStore.__createObjectStore = function(db, store) {
        // Add the object store to the IDBDatabase
        db.__objectStores[store.name] = store;
        db.objectStoreNames.push(store.name);

        // Add the object store to WebSQL
        var transaction = db.__versionTransaction;
        idbModules.IDBTransaction.__assertVersionChange(transaction);
        transaction.__addToTransactionQueue(function createObjectStore(tx, args, success, failure) {
            function error(tx, err) {
                throw idbModules.util.createDOMException(0, "Could not create object store \"" + store.name + "\"", err);
            }

            //key INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE
            var sql = ["CREATE TABLE", idbModules.util.quote(store.name), "(key BLOB", store.autoIncrement ? "UNIQUE, inc INTEGER PRIMARY KEY AUTOINCREMENT" : "PRIMARY KEY", ", value BLOB)"].join(" ");
            idbModules.DEBUG && console.log(sql);
            tx.executeSql(sql, [], function(tx, data) {
                tx.executeSql("INSERT INTO __sys__ VALUES (?,?,?,?)", [store.name, JSON.stringify(store.keyPath), store.autoIncrement, "{}"], function() {
                    success(store);
                }, error);
            }, error);
        });
    };

    /**
     * Deletes an object store from the database.
     * @param {IDBDatabase} db
     * @param {IDBObjectStore} store
     * @protected
     */
    IDBObjectStore.__deleteObjectStore = function(db, store) {
        // Remove the object store from the IDBDatabase
        db.__objectStores[store.name] = undefined;
        db.objectStoreNames.splice(db.objectStoreNames.indexOf(store.name), 1);

        // Remove the object store from WebSQL
        var transaction = db.__versionTransaction;
        idbModules.IDBTransaction.__assertVersionChange(transaction);
        transaction.__addToTransactionQueue(function deleteObjectStore(tx, args, success, failure) {
            function error(tx, err) {
                failure(idbModules.util.createDOMException(0, "Could not delete ObjectStore", err));
            }

            tx.executeSql("SELECT * FROM __sys__ where name = ?", [store.name], function(tx, data) {
                if (data.rows.length > 0) {
                    tx.executeSql("DROP TABLE " + idbModules.util.quote(store.name), [], function() {
                        tx.executeSql("DELETE FROM __sys__ WHERE name = ?", [store.name], function() {
                            success();
                        }, error);
                    }, error);
                }
            });
        });
    };

    /**
     * Determines whether the given inline or out-of-line key is valid, according to the object store's schema.
     * @param {*} value     Used for inline keys
     * @param {*} key       Used for out-of-line keys
     * @private
     */
    IDBObjectStore.prototype.__validateKey = function(value, key) {
        if (this.keyPath) {
            if (typeof key !== "undefined") {
                throw idbModules.util.createDOMException("DataError", "The object store uses in-line keys and the key parameter was provided", this);
            }
            else if (value && typeof value === "object") {
                key = idbModules.Key.getValue(value, this.keyPath);
                if (key === undefined) {
                    if (this.autoIncrement) {
                        // A key will be generated
                        return;
                    }
                    else {
                        throw idbModules.util.createDOMException("DataError", "Could not eval key from keyPath");
                    }
                }
            }
            else {
                throw idbModules.util.createDOMException("DataError", "KeyPath was specified, but value was not an object");
            }
        }
        else {
            if (typeof key === "undefined") {
                if (this.autoIncrement) {
                    // A key will be generated
                    return;
                }
                else {
                    throw idbModules.util.createDOMException("DataError", "The object store uses out-of-line keys and has no key generator and the key parameter was not provided. ", this);
                }
            }
        }

        idbModules.Key.validate(key);
    };

    /**
     * From the store properties and object, extracts the value for the key in hte object Store
     * If the table has auto increment, get the next in sequence
     * @param {Object} tx
     * @param {Object} value
     * @param {Object} key
     * @param {function} success
     * @param {function} failure
     */
    IDBObjectStore.prototype.__deriveKey = function(tx, value, key, success, failure) {
        var me = this;

        function getNextAutoIncKey(callback) {
            tx.executeSql("SELECT * FROM sqlite_sequence where name like ?", [me.name], function(tx, data) {
                if (data.rows.length !== 1) {
                    callback(1);
                }
                else {
                    callback(data.rows.item(0).seq + 1);
                }
            }, function(tx, error) {
                failure(idbModules.util.createDOMException("DataError", "Could not get the auto increment value for key", error));
            });
        }

        if (me.keyPath) {
            var primaryKey = idbModules.Key.getValue(value, me.keyPath);
            if (primaryKey === undefined && me.autoIncrement) {
                getNextAutoIncKey(function(primaryKey) {
                    try {
                        // Update the value with the new key
                        idbModules.Key.setValue(value, me.keyPath, primaryKey);
                        success(primaryKey);
                    }
                    catch (e) {
                        failure(idbModules.util.createDOMException("DataError", "Could not assign a generated value to the keyPath", e));
                    }
                });
            }
            else {
                success(primaryKey);
            }
        }
        else {
            if (typeof key === "undefined" && me.autoIncrement) {
                // Looks like this has autoInc, so lets get the next in sequence and return that.
                getNextAutoIncKey(success);
            }
            else {
                success(key);
            }
        }
    };

    IDBObjectStore.prototype.__insertData = function(tx, encoded, value, primaryKey, success, error) {
        try {
            var paramMap = {};
            if (typeof primaryKey !== "undefined") {
                idbModules.Key.validate(primaryKey);
                paramMap.key = idbModules.Key.encode(primaryKey);
            }
            for (var i = 0; i < this.indexNames.length; i++) {
                var index = this.__indexes[this.indexNames[i]];
                paramMap[index.name] = idbModules.Key.encode(idbModules.Key.getValue(value, index.keyPath), index.multiEntry);
            }
            var sqlStart = ["INSERT INTO ", idbModules.util.quote(this.name), "("];
            var sqlEnd = [" VALUES ("];
            var sqlValues = [];
            for (var key in paramMap) {
                sqlStart.push(idbModules.util.quote(key) + ",");
                sqlEnd.push("?,");
                sqlValues.push(paramMap[key]);
            }
            // removing the trailing comma
            sqlStart.push("value )");
            sqlEnd.push("?)");
            sqlValues.push(encoded);

            var sql = sqlStart.join(" ") + sqlEnd.join(" ");

            idbModules.DEBUG && console.log("SQL for adding", sql, sqlValues);
            tx.executeSql(sql, sqlValues, function(tx, data) {
                idbModules.Sca.encode(primaryKey, function(primaryKey) {
                    primaryKey = idbModules.Sca.decode(primaryKey);
                    success(primaryKey);
                });
            }, function(tx, err) {
                error(idbModules.util.createDOMError("ConstraintError", err.message, err));
            });
        }
        catch (e) {
            error(e);
        }
    };

    IDBObjectStore.prototype.add = function(value, key) {
        var me = this;
        if (arguments.length === 0) {
            throw new TypeError("No value was specified");
        }
        this.__validateKey(value, key);
        me.transaction.__assertWritable();

        var request = me.transaction.__createRequest();
        me.transaction.__pushToQueue(request, function objectStoreAdd(tx, args, success, error) {
            me.__deriveKey(tx, value, key, function(primaryKey) {
                idbModules.Sca.encode(value, function(encoded) {
                    me.__insertData(tx, encoded, value, primaryKey, success, error);
                });
            }, error);
        });
        return request;
    };

    IDBObjectStore.prototype.put = function(value, key) {
        var me = this;
        if (arguments.length === 0) {
            throw new TypeError("No value was specified");
        }
        this.__validateKey(value, key);
        me.transaction.__assertWritable();

        var request = me.transaction.__createRequest();
        me.transaction.__pushToQueue(request, function objectStorePut(tx, args, success, error) {
            me.__deriveKey(tx, value, key, function(primaryKey) {
                idbModules.Sca.encode(value, function(encoded) {
                    // First try to delete if the record exists
                    idbModules.Key.validate(primaryKey);
                    var sql = "DELETE FROM " + idbModules.util.quote(me.name) + " where key = ?";
                    tx.executeSql(sql, [idbModules.Key.encode(primaryKey)], function(tx, data) {
                        idbModules.DEBUG && console.log("Did the row with the", primaryKey, "exist? ", data.rowsAffected);
                        me.__insertData(tx, encoded, value, primaryKey, success, error);
                    }, function(tx, err) {
                        error(err);
                    });
                });
            }, error);
        });
        return request;
    };

    IDBObjectStore.prototype.get = function(key) {
        // TODO Key should also be a key range
        var me = this;

        if (arguments.length === 0) {
            throw new TypeError("No key was specified");
        }

        idbModules.Key.validate(key);
        var primaryKey = idbModules.Key.encode(key);
        return me.transaction.__addToTransactionQueue(function objectStoreGet(tx, args, success, error) {
            idbModules.DEBUG && console.log("Fetching", me.name, primaryKey);
            tx.executeSql("SELECT * FROM " + idbModules.util.quote(me.name) + " where key = ?", [primaryKey], function(tx, data) {
                idbModules.DEBUG && console.log("Fetched data", data);
                var value;
                try {
                    // Opera can't deal with the try-catch here.
                    if (0 === data.rows.length) {
                        return success();
                    }

                    value = idbModules.Sca.decode(data.rows.item(0).value);
                }
                catch (e) {
                    // If no result is returned, or error occurs when parsing JSON
                    idbModules.DEBUG && console.log(e);
                }
                success(value);
            }, function(tx, err) {
                error(err);
            });
        });
    };

    IDBObjectStore.prototype["delete"] = function(key) {
        var me = this;

        if (arguments.length === 0) {
            throw new TypeError("No key was specified");
        }

        me.transaction.__assertWritable();
        idbModules.Key.validate(key);
        var primaryKey = idbModules.Key.encode(key);
        // TODO key should also support key ranges
        return me.transaction.__addToTransactionQueue(function objectStoreDelete(tx, args, success, error) {
            idbModules.DEBUG && console.log("Fetching", me.name, primaryKey);
            tx.executeSql("DELETE FROM " + idbModules.util.quote(me.name) + " where key = ?", [primaryKey], function(tx, data) {
                idbModules.DEBUG && console.log("Deleted from database", data.rowsAffected);
                success();
            }, function(tx, err) {
                error(err);
            });
        });
    };

    IDBObjectStore.prototype.clear = function() {
        var me = this;
        me.transaction.__assertWritable();
        return me.transaction.__addToTransactionQueue(function objectStoreClear(tx, args, success, error) {
            tx.executeSql("DELETE FROM " + idbModules.util.quote(me.name), [], function(tx, data) {
                idbModules.DEBUG && console.log("Cleared all records from database", data.rowsAffected);
                success();
            }, function(tx, err) {
                error(err);
            });
        });
    };

    IDBObjectStore.prototype.count = function(key) {
        if (key instanceof idbModules.IDBKeyRange) {
            return new idbModules.IDBCursor(key, "next", this, this, "key", "value", true).__req;
        }
        else {
            var me = this;
            var hasKey = false;

            // key is optional
            if (key !== undefined) {
                hasKey = true;
                idbModules.Key.validate(key);
            }

            return me.transaction.__addToTransactionQueue(function objectStoreCount(tx, args, success, error) {
                var sql = "SELECT * FROM " + idbModules.util.quote(me.name) + (hasKey ? " WHERE key = ?" : "");
                var sqlValues = [];
                hasKey && sqlValues.push(idbModules.Key.encode(key));
                tx.executeSql(sql, sqlValues, function(tx, data) {
                    success(data.rows.length);
                }, function(tx, err) {
                    error(err);
                });
            });
        }
    };

    IDBObjectStore.prototype.openCursor = function(range, direction) {
        return new idbModules.IDBCursor(range, direction, this, this, "key", "value").__req;
    };

    IDBObjectStore.prototype.index = function(indexName) {
        if (arguments.length === 0) {
            throw new TypeError("No index name was specified");
        }
        var index = this.__indexes[indexName];
        if (!index) {
            throw idbModules.util.createDOMException("NotFoundError", "Index \"" + indexName + "\" does not exist on " + this.name);
        }

        return idbModules.IDBIndex.__clone(index, this);
    };

    /**
     * Creates a new index on the object store.
     * @param {string} indexName
     * @param {string} keyPath
     * @param {object} optionalParameters
     * @returns {IDBIndex}
     */
    IDBObjectStore.prototype.createIndex = function(indexName, keyPath, optionalParameters) {
        if (arguments.length === 0) {
            throw new TypeError("No index name was specified");
        }
        if (arguments.length === 1) {
            throw new TypeError("No key path was specified");
        }
        if (keyPath instanceof Array && optionalParameters && optionalParameters.multiEntry) {
            throw idbModules.util.createDOMException("InvalidAccessError", "The keyPath argument was an array and the multiEntry option is true.");
        }
        if (this.__indexes[indexName] && !this.__indexes[indexName].__deleted) {
            throw idbModules.util.createDOMException("ConstraintError", "Index \"" + indexName + "\" already exists on " + this.name);
        }

        this.transaction.__assertVersionChange();

        optionalParameters = optionalParameters || {};
        /** @name IDBIndexProperties **/
        var indexProperties = {
            columnName: indexName,
            keyPath: keyPath,
            optionalParams: {
                unique: !!optionalParameters.unique,
                multiEntry: !!optionalParameters.multiEntry
            }
        };
        var index = new idbModules.IDBIndex(this, indexProperties);
        idbModules.IDBIndex.__createIndex(this, index);
        return index;
    };

    IDBObjectStore.prototype.deleteIndex = function(indexName) {
        if (arguments.length === 0) {
            throw new TypeError("No index name was specified");
        }
        var index = this.__indexes[indexName];
        if (!index) {
            throw idbModules.util.createDOMException("NotFoundError", "Index \"" + indexName + "\" does not exist on " + this.name);
        }
        this.transaction.__assertVersionChange();

        idbModules.IDBIndex.__deleteIndex(this, index);
    };

    idbModules.IDBObjectStore = IDBObjectStore;
}(idbModules));

(function(idbModules) {
    'use strict';

    var uniqueID = 0;

    /**
     * The IndexedDB Transaction
     * http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#idl-def-IDBTransaction
     * @param {IDBDatabase} db
     * @param {string[]} storeNames
     * @param {string} mode
     * @constructor
     */
    function IDBTransaction(db, storeNames, mode) {
        this.__id = ++uniqueID; // for debugging simultaneous transactions
        this.__active = true;
        this.__running = false;
        this.__errored = false;
        this.__requests = [];
        this.__storeNames = storeNames;
        this.mode = mode;
        this.db = db;
        this.error = null;
        this.onabort = this.onerror = this.oncomplete = null;

        // Kick off the transaction as soon as all synchronous code is done.
        var me = this;
        setTimeout(function() { me.__executeRequests(); }, 0);
    }

    IDBTransaction.prototype.__executeRequests = function() {
        if (this.__running) {
            idbModules.DEBUG && console.log("Looks like the request set is already running", this.mode);
            return;
        }

        this.__running = true;
        var me = this;

        me.db.__db.transaction(function executeRequests(tx) {
                me.__tx = tx;
                var q = null, i = 0;

                function success(result, req) {
                    if (req) {
                        q.req = req;// Need to do this in case of cursors
                    }
                    q.req.readyState = "done";
                    q.req.result = result;
                    delete q.req.error;
                    var e = idbModules.util.createEvent("success");
                    idbModules.util.callback("onsuccess", q.req, e);
                    i++;
                    executeNextRequest();
                }

                function error(tx, err) {
                    err = idbModules.util.findError(arguments);
                    try {
                        // Fire an error event for the current IDBRequest
                        q.req.readyState = "done";
                        q.req.error = err || "DOMError";
                        q.req.result = undefined;
                        var e = idbModules.util.createEvent("error", err);
                        idbModules.util.callback("onerror", q.req, e);
                    }
                    finally {
                        // Fire an error event for the transaction
                        transactionError(err);
                    }
                }

                function executeNextRequest() {
                    if (i >= me.__requests.length) {
                        // All requests in the transaction are done
                        me.__requests = [];
                        if (me.__active) {
                            me.__active = false;
                            transactionFinished();
                        }
                    }
                    else {
                        try {
                            q = me.__requests[i];
                            q.op(tx, q.args, success, error);
                        }
                        catch (e) {
                            error(e);
                        }
                    }
                }

                executeNextRequest();
            },

            function webSqlError(err) {
                transactionError(err);
            }
        );

        function transactionError(err) {
            idbModules.util.logError("Error", "An error occurred in a transaction", err);

            if (me.__errored) {
                // We've already called "onerror", "onabort", or thrown, so don't do it again.
                return;
            }

            me.__errored = true;

            if (!me.__active) {
                // The transaction has already completed, so we can't call "onerror" or "onabort".
                // So throw the error instead.
                throw err;
            }

            try {
                me.error = err;
                var evt = idbModules.util.createEvent("error");
                idbModules.util.callback("onerror", me, evt);
                idbModules.util.callback("onerror", me.db, evt);
            }
            finally {
                me.abort();
            }
        }

        function transactionFinished() {
            idbModules.DEBUG && console.log("Transaction completed");
            var evt = idbModules.util.createEvent("complete");
            try {
                idbModules.util.callback("oncomplete", me, evt);
                idbModules.util.callback("__oncomplete", me, evt);
            }
            catch (e) {
                // An error occurred in the "oncomplete" handler.
                // It's too late to call "onerror" or "onabort". Throw a global error instead.
                // (this may seem odd/bad, but it's how all native IndexedDB implementations work)
                me.__errored = true;
                throw e;
            }
        }
    };

    /**
     * Creates a new IDBRequest for the transaction.
     * NOTE: The transaction is not queued util you call {@link IDBTransaction#__pushToQueue}
     * @returns {IDBRequest}
     * @protected
     */
    IDBTransaction.prototype.__createRequest = function() {
        var request = new idbModules.IDBRequest();
        request.source = this.db;
        request.transaction = this;
        return request;
    };

    /**
     * Adds a callback function to the transaction queue
     * @param {function} callback
     * @param {*} args
     * @returns {IDBRequest}
     * @protected
     */
    IDBTransaction.prototype.__addToTransactionQueue = function(callback, args) {
        var request = this.__createRequest();
        this.__pushToQueue(request, callback, args);
        return request;
    };

    /**
     * Adds an IDBRequest to the transaction queue
     * @param {IDBRequest} request
     * @param {function} callback
     * @param {*} args
     * @protected
     */
    IDBTransaction.prototype.__pushToQueue = function(request, callback, args) {
        this.__assertActive();
        this.__requests.push({
            "op": callback,
            "args": args,
            "req": request
        });
    };

    IDBTransaction.prototype.__assertActive = function() {
        if (!this.__active) {
            throw idbModules.util.createDOMException("TransactionInactiveError", "A request was placed against a transaction which is currently not active, or which is finished");
        }
    };

    IDBTransaction.prototype.__assertWritable = function() {
        if (this.mode === IDBTransaction.READ_ONLY) {
            throw idbModules.util.createDOMException("ReadOnlyError", "The transaction is read only");
        }
    };

    IDBTransaction.prototype.__assertVersionChange = function() {
        IDBTransaction.__assertVersionChange(this);
    };

    IDBTransaction.__assertVersionChange = function(tx) {
        if (!tx || tx.mode !== IDBTransaction.VERSION_CHANGE) {
            throw idbModules.util.createDOMException("InvalidStateError", "Not a version transaction");
        }
    };

    /**
     * Returns the specified object store.
     * @param {string} objectStoreName
     * @returns {IDBObjectStore}
     */
    IDBTransaction.prototype.objectStore = function(objectStoreName) {
        if (arguments.length === 0) {
            throw new TypeError("No object store name was specified");
        }
        if (!this.__active) {
            throw idbModules.util.createDOMException("InvalidStateError", "A request was placed against a transaction which is currently not active, or which is finished");
        }
        if (this.__storeNames.indexOf(objectStoreName) === -1 && this.mode !== IDBTransaction.VERSION_CHANGE) {
            throw idbModules.util.createDOMException("NotFoundError", objectStoreName + " is not participating in this transaction");
        }
        var store = this.db.__objectStores[objectStoreName];
        if (!store) {
            throw idbModules.util.createDOMException("NotFoundError", objectStoreName + " does not exist in " + this.db.name);
        }

        return idbModules.IDBObjectStore.__clone(store, this);
    };

    IDBTransaction.prototype.abort = function() {
        var me = this;
        idbModules.DEBUG && console.log("The transaction was aborted", me);
        me.__active = false;
        var evt = idbModules.util.createEvent("abort");

        // Fire the "onabort" event asynchronously, so errors don't bubble
        setTimeout(function() {
            idbModules.util.callback("onabort", me, evt);
        }, 0);
    };

    IDBTransaction.READ_ONLY = "readonly";
    IDBTransaction.READ_WRITE = "readwrite";
    IDBTransaction.VERSION_CHANGE = "versionchange";

    idbModules.IDBTransaction = IDBTransaction;
}(idbModules));

(function(idbModules){
    'use strict';

    /**
     * IDB Database Object
     * http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#database-interface
     * @constructor
     */
    function IDBDatabase(db, name, version, storeProperties){
        this.__db = db;
        this.__closed = false;
        this.version = version;
        this.name = name;
        this.onabort = this.onerror = this.onversionchange = null;

        this.__objectStores = {};
        this.objectStoreNames = new idbModules.util.StringList();
        for (var i = 0; i < storeProperties.rows.length; i++) {
            var store = new idbModules.IDBObjectStore(storeProperties.rows.item(i));
            this.__objectStores[store.name] = store;
            this.objectStoreNames.push(store.name);
        }
    }

    /**
     * Creates a new object store.
     * @param {string} storeName
     * @param {object} [createOptions]
     * @returns {IDBObjectStore}
     */
    IDBDatabase.prototype.createObjectStore = function(storeName, createOptions){
        if (arguments.length === 0) {
            throw new TypeError("No object store name was specified");
        }
        if (this.__objectStores[storeName]) {
            throw idbModules.util.createDOMException("ConstraintError", "Object store \"" + storeName + "\" already exists in " + this.name);
        }
        this.__versionTransaction.__assertVersionChange();

        createOptions = createOptions || {};
        /** @name IDBObjectStoreProperties **/
        var storeProperties = {
            name: storeName,
            keyPath: JSON.stringify(createOptions.keyPath || null),
            autoInc: JSON.stringify(createOptions.autoIncrement),
            indexList: "{}"
        };
        var store = new idbModules.IDBObjectStore(storeProperties, this.__versionTransaction);
        idbModules.IDBObjectStore.__createObjectStore(this, store);
        return store;
    };

    /**
     * Deletes an object store.
     * @param {string} storeName
     */
    IDBDatabase.prototype.deleteObjectStore = function(storeName){
        if (arguments.length === 0) {
            throw new TypeError("No object store name was specified");
        }
        var store = this.__objectStores[storeName];
        if (!store) {
            throw idbModules.util.createDOMException("NotFoundError", "Object store \"" + storeName + "\" does not exist in " + this.name);
        }
        this.__versionTransaction.__assertVersionChange();

        idbModules.IDBObjectStore.__deleteObjectStore(this, store);
    };

    IDBDatabase.prototype.close = function(){
        this.__closed = true;
    };

    /**
     * Starts a new transaction.
     * @param {string|string[]} storeNames
     * @param {string} mode
     * @returns {IDBTransaction}
     */
    IDBDatabase.prototype.transaction = function(storeNames, mode){
        if (this.__closed) {
            throw idbModules.util.createDOMException("InvalidStateError", "An attempt was made to start a new transaction on a database connection that is not open");
        }

        if (typeof mode === "number") {
            mode = mode === 1 ? IDBTransaction.READ_WRITE : IDBTransaction.READ_ONLY;
            idbModules.DEBUG && console.log("Mode should be a string, but was specified as ", mode);
        }
        else {
            mode = mode || IDBTransaction.READ_ONLY;
        }

        if (mode !== IDBTransaction.READ_ONLY && mode !== IDBTransaction.READ_WRITE) {
            throw new TypeError("Invalid transaction mode: " + mode);
        }

        storeNames = typeof storeNames === "string" ? [storeNames] : storeNames;
        if (storeNames.length === 0) {
            throw idbModules.util.createDOMException("InvalidAccessError", "No object store names were specified");
        }
        for (var i = 0; i < storeNames.length; i++) {
            if (!this.objectStoreNames.contains(storeNames[i])) {
                throw idbModules.util.createDOMException("NotFoundError", "The \"" + storeNames[i] + "\" object store does not exist");
            }
        }

        var transaction = new idbModules.IDBTransaction(this, storeNames, mode);
        return transaction;
    };
    
    idbModules.IDBDatabase = IDBDatabase;
}(idbModules));

(function(idbModules) {
    'use strict';

    var DEFAULT_DB_SIZE = 4 * 1024 * 1024;
    var sysdb;

    /**
     * Craetes the sysDB to keep track of version numbers for databases
     **/
    function createSysDB(success, failure) {
        function sysDbCreateError(tx, err) {
            err = idbModules.util.findError(arguments);
            idbModules.DEBUG && console.log("Error in sysdb transaction - when creating dbVersions", err);
            failure(err);
        }

        if (sysdb) {
            success();
        }
        else {
            sysdb = window.openDatabase("__sysdb__", 1, "System Database", DEFAULT_DB_SIZE);
            sysdb.transaction(function(tx) {
                tx.executeSql("CREATE TABLE IF NOT EXISTS dbVersions (name VARCHAR(255), version INT);", [], success, sysDbCreateError);
            }, sysDbCreateError);
        }
    }

    /**
     * IDBFactory Class
     * https://w3c.github.io/IndexedDB/#idl-def-IDBFactory
     * @constructor
     */
    function IDBFactory() {
        this.modules = idbModules;
    }

    /**
     * The IndexedDB Method to create a new database and return the DB
     * @param {string} name
     * @param {number} version
     */
    IDBFactory.prototype.open = function(name, version) {
        var req = new idbModules.IDBOpenDBRequest();
        var calledDbCreateError = false;

        if (arguments.length === 0) {
            throw new TypeError('Database name is required');
        }
        else if (arguments.length === 2) {
            version = parseFloat(version);
            if (isNaN(version) || !isFinite(version) || version <= 0) {
                throw new TypeError('Invalid database version: ' + version);
            }
        }
        name = name + ''; // cast to a string

        function dbCreateError(tx, err) {
            if (calledDbCreateError) {
                return;
            }
            err = idbModules.util.findError(arguments);
            calledDbCreateError = true;
            var evt = idbModules.util.createEvent("error", arguments);
            req.readyState = "done";
            req.error = err || "DOMError";
            idbModules.util.callback("onerror", req, evt);
        }

        function openDB(oldVersion) {
            var db = window.openDatabase(name, 1, name, DEFAULT_DB_SIZE);
            req.readyState = "done";
            if (typeof version === "undefined") {
                version = oldVersion || 1;
            }
            if (version <= 0 || oldVersion > version) {
                var err = idbModules.util.createDOMError("VersionError", "An attempt was made to open a database using a lower version than the existing version.", version);
                dbCreateError(err);
                return;
            }

            db.transaction(function(tx) {
                tx.executeSql("CREATE TABLE IF NOT EXISTS __sys__ (name VARCHAR(255), keyPath VARCHAR(255), autoInc BOOLEAN, indexList BLOB)", [], function() {
                    tx.executeSql("SELECT * FROM __sys__", [], function(tx, data) {
                        var e = idbModules.util.createEvent("success");
                        req.source = req.result = new idbModules.IDBDatabase(db, name, version, data);
                        if (oldVersion < version) {
                            // DB Upgrade in progress
                            sysdb.transaction(function(systx) {
                                systx.executeSql("UPDATE dbVersions set version = ? where name = ?", [version, name], function() {
                                    var e = idbModules.util.createEvent("upgradeneeded");
                                    e.oldVersion = oldVersion;
                                    e.newVersion = version;
                                    req.transaction = req.result.__versionTransaction = new idbModules.IDBTransaction(req.source, [], idbModules.IDBTransaction.VERSION_CHANGE);
                                    req.transaction.__addToTransactionQueue(function onupgradeneeded(tx, args, success) {
                                        idbModules.util.callback("onupgradeneeded", req, e);
                                        success();
                                    });
                                    req.transaction.__oncomplete = function() {
                                        req.transaction = null;
                                        var e = idbModules.util.createEvent("success");
                                        idbModules.util.callback("onsuccess", req, e);
                                    };
                                }, dbCreateError);
                            }, dbCreateError);
                        } else {
                            idbModules.util.callback("onsuccess", req, e);
                        }
                    }, dbCreateError);
                }, dbCreateError);
            }, dbCreateError);
        }

        createSysDB(function() {
            sysdb.transaction(function(tx) {
                tx.executeSql("SELECT * FROM dbVersions where name = ?", [name], function(tx, data) {
                    if (data.rows.length === 0) {
                        // Database with this name does not exist
                        tx.executeSql("INSERT INTO dbVersions VALUES (?,?)", [name, version || 1], function() {
                            openDB(0);
                        }, dbCreateError);
                    } else {
                        openDB(data.rows.item(0).version);
                    }
                }, dbCreateError);
            }, dbCreateError);
        }, dbCreateError);

        return req;
    };

    /**
     * Deletes a database
     * @param {string} name
     * @returns {IDBOpenDBRequest}
     */
    IDBFactory.prototype.deleteDatabase = function(name) {
        var req = new idbModules.IDBOpenDBRequest();
        var calledDBError = false;
        var version = null;

        if (arguments.length === 0) {
            throw new TypeError('Database name is required');
        }
        name = name + ''; // cast to a string

        function dbError(tx, err) {
            if (calledDBError) {
                return;
            }
            err = idbModules.util.findError(arguments);
            req.readyState = "done";
            req.error = err || "DOMError";
            var e = idbModules.util.createEvent("error");
            e.debug = arguments;
            idbModules.util.callback("onerror", req, e);
            calledDBError = true;
        }

        function deleteFromDbVersions() {
            sysdb.transaction(function(systx) {
                systx.executeSql("DELETE FROM dbVersions where name = ? ", [name], function() {
                    req.result = undefined;
                    var e = idbModules.util.createEvent("success");
                    e.newVersion = null;
                    e.oldVersion = version;
                    idbModules.util.callback("onsuccess", req, e);
                }, dbError);
            }, dbError);
        }

        createSysDB(function() {
            sysdb.transaction(function(systx) {
                systx.executeSql("SELECT * FROM dbVersions where name = ?", [name], function(tx, data) {
                    if (data.rows.length === 0) {
                        req.result = undefined;
                        var e = idbModules.util.createEvent("success");
                        e.newVersion = null;
                        e.oldVersion = version;
                        idbModules.util.callback("onsuccess", req, e);
                        return;
                    }
                    version = data.rows.item(0).version;
                    var db = window.openDatabase(name, 1, name, DEFAULT_DB_SIZE);
                    db.transaction(function(tx) {
                        tx.executeSql("SELECT * FROM __sys__", [], function(tx, data) {
                            var tables = data.rows;
                            (function deleteTables(i) {
                                if (i >= tables.length) {
                                    // If all tables are deleted, delete the housekeeping tables
                                    tx.executeSql("DROP TABLE IF EXISTS __sys__", [], function() {
                                        // Finally, delete the record for this DB from sysdb
                                        deleteFromDbVersions();
                                    }, dbError);
                                } else {
                                    // Delete all tables in this database, maintained in the sys table
                                    tx.executeSql("DROP TABLE " + idbModules.util.quote(tables.item(i).name), [], function() {
                                        deleteTables(i + 1);
                                    }, function() {
                                        deleteTables(i + 1);
                                    });
                                }
                            }(0));
                        }, function(e) {
                            // __sysdb table does not exist, but that does not mean delete did not happen
                            deleteFromDbVersions();
                        });
                    });
                }, dbError);
            }, dbError);
        }, dbError);

        return req;
    };

    /**
     * Compares two keys
     * @param key1
     * @param key2
     * @returns {number}
     */
    IDBFactory.prototype.cmp = function(key1, key2) {
        if (arguments.length < 2) {
            throw new TypeError("You must provide two keys to be compared");
        }

        idbModules.Key.validate(key1);
        idbModules.Key.validate(key2);
        var encodedKey1 = idbModules.Key.encode(key1);
        var encodedKey2 = idbModules.Key.encode(key2);
        var result = encodedKey1 > encodedKey2 ? 1 : encodedKey1 === encodedKey2 ? 0 : -1;
        
        if (idbModules.DEBUG) {
            // verify that the keys encoded correctly
            var decodedKey1 = idbModules.Key.decode(encodedKey1);
            var decodedKey2 = idbModules.Key.decode(encodedKey2);
            if (typeof key1 === "object") {
                key1 = JSON.stringify(key1);
                decodedKey1 = JSON.stringify(decodedKey1);
            }
            if (typeof key2 === "object") {
                key2 = JSON.stringify(key2);
                decodedKey2 = JSON.stringify(decodedKey2);
            }

            // encoding/decoding mismatches are usually due to a loss of floating-point precision
            if (decodedKey1 !== key1) {
                console.warn(key1 + ' was incorrectly encoded as ' + decodedKey1);
            }
            if (decodedKey2 !== key2) {
                console.warn(key2 + ' was incorrectly encoded as ' + decodedKey2);
            }
        }
        
        return result;
    };


    idbModules.shimIndexedDB = new IDBFactory();
    idbModules.IDBFactory = IDBFactory;
}(idbModules));

(function(window, idbModules){
    'use strict';

    function shim(name, value) {
        try {
            // Try setting the property. This will fail if the property is read-only.
            window[name] = value;
        }
        catch (e) {}

        if (window[name] !== value && Object.defineProperty) {
            // Setting a read-only property failed, so try re-defining the property
            try {
                Object.defineProperty(window, name, {
                    value: value
                });
            }
            catch (e) {}

            if (window[name] !== value) {
                window.console && console.warn && console.warn('Unable to shim ' + name);
            }
        }
    }

    shim('shimIndexedDB', idbModules.shimIndexedDB);
    if (window.shimIndexedDB) {
        window.shimIndexedDB.__useShim = function(){
            if (typeof window.openDatabase !== "undefined") {
                // Polyfill ALL of IndexedDB, using WebSQL
                shim('indexedDB', idbModules.shimIndexedDB);
                shim('IDBFactory', idbModules.IDBFactory);
                shim('IDBDatabase', idbModules.IDBDatabase);
                shim('IDBObjectStore', idbModules.IDBObjectStore);
                shim('IDBIndex', idbModules.IDBIndex);
                shim('IDBTransaction', idbModules.IDBTransaction);
                shim('IDBCursor', idbModules.IDBCursor);
                shim('IDBKeyRange', idbModules.IDBKeyRange);
                shim('IDBRequest', idbModules.IDBRequest);
                shim('IDBOpenDBRequest', idbModules.IDBOpenDBRequest);
                shim('IDBVersionChangeEvent', idbModules.IDBVersionChangeEvent);
            }
            else if (typeof window.indexedDB === "object") {
                // Polyfill the missing IndexedDB features
                idbModules.polyfill();
            }
        };

        window.shimIndexedDB.__debug = function(val){
            idbModules.DEBUG = val;
        };
    }
    
    // Workaround to prevent an error in Firefox
    if(!('indexedDB' in window)) {
        window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.oIndexedDB || window.msIndexedDB;
    }
    
    // Detect browsers with known IndexedDb issues (e.g. Android pre-4.4)
    var poorIndexedDbSupport = false;
    if (navigator.userAgent.match(/Android 2/) || navigator.userAgent.match(/Android 3/) || navigator.userAgent.match(/Android 4\.[0-3]/)) {
        /* Chrome is an exception. It supports IndexedDb */
        if (!navigator.userAgent.match(/Chrome/)) {
            poorIndexedDbSupport = true;
        }
    }

    if ((typeof window.indexedDB === "undefined" || !window.indexedDB || poorIndexedDbSupport) && typeof window.openDatabase !== "undefined") {
        window.shimIndexedDB.__useShim();
    }
    else {
        window.IDBDatabase = window.IDBDatabase || window.webkitIDBDatabase;
        window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction;
        window.IDBCursor = window.IDBCursor || window.webkitIDBCursor;
        window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange;
        if(!window.IDBTransaction){
            window.IDBTransaction = {};
        }
        /* Some browsers (e.g. Chrome 18 on Android) support IndexedDb but do not allow writing of these properties */
        try {
            window.IDBTransaction.READ_ONLY = window.IDBTransaction.READ_ONLY || "readonly";
            window.IDBTransaction.READ_WRITE = window.IDBTransaction.READ_WRITE || "readwrite";
        } catch (e) {}
    }
    
}(window, idbModules));


/*
 * JavaScript MD5 1.0.1
 * https://github.com/blueimp/JavaScript-MD5
 *
 * Copyright 2011, Sebastian Tschan
 * https://blueimp.net
 *
 * Licensed under the MIT license:
 * http://www.opensource.org/licenses/MIT
 * 
 * Based on
 * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
 * Digest Algorithm, as defined in RFC 1321.
 * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
 * Distributed under the BSD License
 * See http://pajhome.org.uk/crypt/md5 for more info.
 */

/*jslint bitwise: true */
/*global unescape, define */

(function ($) {
    'use strict';

    /*
    * Add integers, wrapping at 2^32. This uses 16-bit operations internally
    * to work around bugs in some JS interpreters.
    */
    function safe_add(x, y) {
        var lsw = (x & 0xFFFF) + (y & 0xFFFF),
            msw = (x >> 16) + (y >> 16) + (lsw >> 16);
        return (msw << 16) | (lsw & 0xFFFF);
    }

    /*
    * Bitwise rotate a 32-bit number to the left.
    */
    function bit_rol(num, cnt) {
        return (num << cnt) | (num >>> (32 - cnt));
    }

    /*
    * These functions implement the four basic operations the algorithm uses.
    */
    function md5_cmn(q, a, b, x, s, t) {
        return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b);
    }
    function md5_ff(a, b, c, d, x, s, t) {
        return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
    }
    function md5_gg(a, b, c, d, x, s, t) {
        return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
    }
    function md5_hh(a, b, c, d, x, s, t) {
        return md5_cmn(b ^ c ^ d, a, b, x, s, t);
    }
    function md5_ii(a, b, c, d, x, s, t) {
        return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
    }

    /*
    * Calculate the MD5 of an array of little-endian words, and a bit length.
    */
    function binl_md5(x, len) {
        /* append padding */
        x[len >> 5] |= 0x80 << (len % 32);
        x[(((len + 64) >>> 9) << 4) + 14] = len;

        var i, olda, oldb, oldc, oldd,
            a =  1732584193,
            b = -271733879,
            c = -1732584194,
            d =  271733878;

        for (i = 0; i < x.length; i += 16) {
            olda = a;
            oldb = b;
            oldc = c;
            oldd = d;

            a = md5_ff(a, b, c, d, x[i],       7, -680876936);
            d = md5_ff(d, a, b, c, x[i +  1], 12, -389564586);
            c = md5_ff(c, d, a, b, x[i +  2], 17,  606105819);
            b = md5_ff(b, c, d, a, x[i +  3], 22, -1044525330);
            a = md5_ff(a, b, c, d, x[i +  4],  7, -176418897);
            d = md5_ff(d, a, b, c, x[i +  5], 12,  1200080426);
            c = md5_ff(c, d, a, b, x[i +  6], 17, -1473231341);
            b = md5_ff(b, c, d, a, x[i +  7], 22, -45705983);
            a = md5_ff(a, b, c, d, x[i +  8],  7,  1770035416);
            d = md5_ff(d, a, b, c, x[i +  9], 12, -1958414417);
            c = md5_ff(c, d, a, b, x[i + 10], 17, -42063);
            b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162);
            a = md5_ff(a, b, c, d, x[i + 12],  7,  1804603682);
            d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101);
            c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290);
            b = md5_ff(b, c, d, a, x[i + 15], 22,  1236535329);

            a = md5_gg(a, b, c, d, x[i +  1],  5, -165796510);
            d = md5_gg(d, a, b, c, x[i +  6],  9, -1069501632);
            c = md5_gg(c, d, a, b, x[i + 11], 14,  643717713);
            b = md5_gg(b, c, d, a, x[i],      20, -373897302);
            a = md5_gg(a, b, c, d, x[i +  5],  5, -701558691);
            d = md5_gg(d, a, b, c, x[i + 10],  9,  38016083);
            c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335);
            b = md5_gg(b, c, d, a, x[i +  4], 20, -405537848);
            a = md5_gg(a, b, c, d, x[i +  9],  5,  568446438);
            d = md5_gg(d, a, b, c, x[i + 14],  9, -1019803690);
            c = md5_gg(c, d, a, b, x[i +  3], 14, -187363961);
            b = md5_gg(b, c, d, a, x[i +  8], 20,  1163531501);
            a = md5_gg(a, b, c, d, x[i + 13],  5, -1444681467);
            d = md5_gg(d, a, b, c, x[i +  2],  9, -51403784);
            c = md5_gg(c, d, a, b, x[i +  7], 14,  1735328473);
            b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734);

            a = md5_hh(a, b, c, d, x[i +  5],  4, -378558);
            d = md5_hh(d, a, b, c, x[i +  8], 11, -2022574463);
            c = md5_hh(c, d, a, b, x[i + 11], 16,  1839030562);
            b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556);
            a = md5_hh(a, b, c, d, x[i +  1],  4, -1530992060);
            d = md5_hh(d, a, b, c, x[i +  4], 11,  1272893353);
            c = md5_hh(c, d, a, b, x[i +  7], 16, -155497632);
            b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640);
            a = md5_hh(a, b, c, d, x[i + 13],  4,  681279174);
            d = md5_hh(d, a, b, c, x[i],      11, -358537222);
            c = md5_hh(c, d, a, b, x[i +  3], 16, -722521979);
            b = md5_hh(b, c, d, a, x[i +  6], 23,  76029189);
            a = md5_hh(a, b, c, d, x[i +  9],  4, -640364487);
            d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835);
            c = md5_hh(c, d, a, b, x[i + 15], 16,  530742520);
            b = md5_hh(b, c, d, a, x[i +  2], 23, -995338651);

            a = md5_ii(a, b, c, d, x[i],       6, -198630844);
            d = md5_ii(d, a, b, c, x[i +  7], 10,  1126891415);
            c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905);
            b = md5_ii(b, c, d, a, x[i +  5], 21, -57434055);
            a = md5_ii(a, b, c, d, x[i + 12],  6,  1700485571);
            d = md5_ii(d, a, b, c, x[i +  3], 10, -1894986606);
            c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523);
            b = md5_ii(b, c, d, a, x[i +  1], 21, -2054922799);
            a = md5_ii(a, b, c, d, x[i +  8],  6,  1873313359);
            d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744);
            c = md5_ii(c, d, a, b, x[i +  6], 15, -1560198380);
            b = md5_ii(b, c, d, a, x[i + 13], 21,  1309151649);
            a = md5_ii(a, b, c, d, x[i +  4],  6, -145523070);
            d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379);
            c = md5_ii(c, d, a, b, x[i +  2], 15,  718787259);
            b = md5_ii(b, c, d, a, x[i +  9], 21, -343485551);

            a = safe_add(a, olda);
            b = safe_add(b, oldb);
            c = safe_add(c, oldc);
            d = safe_add(d, oldd);
        }
        return [a, b, c, d];
    }

    /*
    * Convert an array of little-endian words to a string
    */
    function binl2rstr(input) {
        var i,
            output = '';
        for (i = 0; i < input.length * 32; i += 8) {
            output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xFF);
        }
        return output;
    }

    /*
    * Convert a raw string to an array of little-endian words
    * Characters >255 have their high-byte silently ignored.
    */
    function rstr2binl(input) {
        var i,
            output = [];
        output[(input.length >> 2) - 1] = undefined;
        for (i = 0; i < output.length; i += 1) {
            output[i] = 0;
        }
        for (i = 0; i < input.length * 8; i += 8) {
            output[i >> 5] |= (input.charCodeAt(i / 8) & 0xFF) << (i % 32);
        }
        return output;
    }

    /*
    * Calculate the MD5 of a raw string
    */
    function rstr_md5(s) {
        return binl2rstr(binl_md5(rstr2binl(s), s.length * 8));
    }

    /*
    * Calculate the HMAC-MD5, of a key and some data (raw strings)
    */
    function rstr_hmac_md5(key, data) {
        var i,
            bkey = rstr2binl(key),
            ipad = [],
            opad = [],
            hash;
        ipad[15] = opad[15] = undefined;
        if (bkey.length > 16) {
            bkey = binl_md5(bkey, key.length * 8);
        }
        for (i = 0; i < 16; i += 1) {
            ipad[i] = bkey[i] ^ 0x36363636;
            opad[i] = bkey[i] ^ 0x5C5C5C5C;
        }
        hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
        return binl2rstr(binl_md5(opad.concat(hash), 512 + 128));
    }

    /*
    * Convert a raw string to a hex string
    */
    function rstr2hex(input) {
        var hex_tab = '0123456789abcdef',
            output = '',
            x,
            i;
        for (i = 0; i < input.length; i += 1) {
            x = input.charCodeAt(i);
            output += hex_tab.charAt((x >>> 4) & 0x0F) +
                hex_tab.charAt(x & 0x0F);
        }
        return output;
    }

    /*
    * Encode a string as utf-8
    */
    function str2rstr_utf8(input) {
        return unescape(encodeURIComponent(input));
    }

    /*
    * Take string arguments and return either raw or hex encoded strings
    */
    function raw_md5(s) {
        return rstr_md5(str2rstr_utf8(s));
    }
    function hex_md5(s) {
        return rstr2hex(raw_md5(s));
    }
    function raw_hmac_md5(k, d) {
        return rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d));
    }
    function hex_hmac_md5(k, d) {
        return rstr2hex(raw_hmac_md5(k, d));
    }

    function md5(string, key, raw) {
        if (!key) {
            if (!raw) {
                return hex_md5(string);
            }
            return raw_md5(string);
        }
        if (!raw) {
            return hex_hmac_md5(key, string);
        }
        return raw_hmac_md5(key, string);
    }

    if (typeof define === 'function' && define.amd) {
        define(function () {
            return md5;
        });
    } else {
        $.md5 = md5;
    }
}(this));
//! moment-timezone.js
//! version : 0.4.0
//! author : Tim Wood
//! license : MIT
//! github.com/moment/moment-timezone

(function (root, factory) {
	"use strict";

	/*global define*/
	if (typeof define === 'function' && define.amd) {
		define(['moment'], factory);                 // AMD
	} else if (typeof exports === 'object') {
		module.exports = factory(require('moment')); // Node
	} else {
		factory(root.moment);                        // Browser
	}
}(this, function (moment) {
	"use strict";

	// Do not load moment-timezone a second time.
	if (moment.tz !== undefined) {
		logError('Moment Timezone ' + moment.tz.version + ' was already loaded ' + (moment.tz.dataVersion ? 'with data from ' : 'without any data') + moment.tz.dataVersion);
		return moment;
	}

	var VERSION = "0.4.0",
		zones = {},
		links = {},
		names = {},

		momentVersion = moment.version.split('.'),
		major = +momentVersion[0],
		minor = +momentVersion[1];

	// Moment.js version check
	if (major < 2 || (major === 2 && minor < 6)) {
		logError('Moment Timezone requires Moment.js >= 2.6.0. You are using Moment.js ' + moment.version + '. See momentjs.com');
	}

	/************************************
		Unpacking
	************************************/

	function charCodeToInt(charCode) {
		if (charCode > 96) {
			return charCode - 87;
		} else if (charCode > 64) {
			return charCode - 29;
		}
		return charCode - 48;
	}

	function unpackBase60(string) {
		var i = 0,
			parts = string.split('.'),
			whole = parts[0],
			fractional = parts[1] || '',
			multiplier = 1,
			num,
			out = 0,
			sign = 1;

		// handle negative numbers
		if (string.charCodeAt(0) === 45) {
			i = 1;
			sign = -1;
		}

		// handle digits before the decimal
		for (i; i < whole.length; i++) {
			num = charCodeToInt(whole.charCodeAt(i));
			out = 60 * out + num;
		}

		// handle digits after the decimal
		for (i = 0; i < fractional.length; i++) {
			multiplier = multiplier / 60;
			num = charCodeToInt(fractional.charCodeAt(i));
			out += num * multiplier;
		}

		return out * sign;
	}

	function arrayToInt (array) {
		for (var i = 0; i < array.length; i++) {
			array[i] = unpackBase60(array[i]);
		}
	}

	function intToUntil (array, length) {
		for (var i = 0; i < length; i++) {
			array[i] = Math.round((array[i - 1] || 0) + (array[i] * 60000)); // minutes to milliseconds
		}

		array[length - 1] = Infinity;
	}

	function mapIndices (source, indices) {
		var out = [], i;

		for (i = 0; i < indices.length; i++) {
			out[i] = source[indices[i]];
		}

		return out;
	}

	function unpack (string) {
		var data = string.split('|'),
			offsets = data[2].split(' '),
			indices = data[3].split(''),
			untils  = data[4].split(' ');

		arrayToInt(offsets);
		arrayToInt(indices);
		arrayToInt(untils);

		intToUntil(untils, indices.length);

		return {
			name    : data[0],
			abbrs   : mapIndices(data[1].split(' '), indices),
			offsets : mapIndices(offsets, indices),
			untils  : untils
		};
	}

	/************************************
		Zone object
	************************************/

	function Zone (packedString) {
		if (packedString) {
			this._set(unpack(packedString));
		}
	}

	Zone.prototype = {
		_set : function (unpacked) {
			this.name    = unpacked.name;
			this.abbrs   = unpacked.abbrs;
			this.untils  = unpacked.untils;
			this.offsets = unpacked.offsets;
		},

		_index : function (timestamp) {
			var target = +timestamp,
				untils = this.untils,
				i;

			for (i = 0; i < untils.length; i++) {
				if (target < untils[i]) {
					return i;
				}
			}
		},

		parse : function (timestamp) {
			var target  = +timestamp,
				offsets = this.offsets,
				untils  = this.untils,
				max     = untils.length - 1,
				offset, offsetNext, offsetPrev, i;

			for (i = 0; i < max; i++) {
				offset     = offsets[i];
				offsetNext = offsets[i + 1];
				offsetPrev = offsets[i ? i - 1 : i];

				if (offset < offsetNext && tz.moveAmbiguousForward) {
					offset = offsetNext;
				} else if (offset > offsetPrev && tz.moveInvalidForward) {
					offset = offsetPrev;
				}

				if (target < untils[i] - (offset * 60000)) {
					return offsets[i];
				}
			}

			return offsets[max];
		},

		abbr : function (mom) {
			return this.abbrs[this._index(mom)];
		},

		offset : function (mom) {
			return this.offsets[this._index(mom)];
		}
	};

	/************************************
		Global Methods
	************************************/

	function normalizeName (name) {
		return (name || '').toLowerCase().replace(/\//g, '_');
	}

	function addZone (packed) {
		var i, name, normalized;

		if (typeof packed === "string") {
			packed = [packed];
		}

		for (i = 0; i < packed.length; i++) {
			name = packed[i].split('|')[0];
			normalized = normalizeName(name);
			zones[normalized] = packed[i];
			names[normalized] = name;
		}
	}

	function getZone (name, caller) {
		name = normalizeName(name);

		var zone = zones[name];
		var link;
		
		if (zone instanceof Zone) {
			return zone;
		}

		if (typeof zone === 'string') {
			zone = new Zone(zone);
			zones[name] = zone;
			return zone;
		}

		// Pass getZone to prevent recursion more than 1 level deep
		if (links[name] && caller !== getZone && (link = getZone(links[name], getZone))) {
			zone = zones[name] = new Zone();
			zone._set(link);
			zone.name = names[name];
			return zone;
		}

		return null;
	}

	function getNames () {
		var i, out = [];

		for (i in names) {
			if (names.hasOwnProperty(i) && (zones[i] || zones[links[i]]) && names[i]) {
				out.push(names[i]);
			}
		}

		return out.sort();
	}

	function addLink (aliases) {
		var i, alias, normal0, normal1;

		if (typeof aliases === "string") {
			aliases = [aliases];
		}

		for (i = 0; i < aliases.length; i++) {
			alias = aliases[i].split('|');

			normal0 = normalizeName(alias[0]);
			normal1 = normalizeName(alias[1]);

			links[normal0] = normal1;
			names[normal0] = alias[0];

			links[normal1] = normal0;
			names[normal1] = alias[1];
		}
	}

	function loadData (data) {
		addZone(data.zones);
		addLink(data.links);
		tz.dataVersion = data.version;
	}

	function zoneExists (name) {
		if (!zoneExists.didShowError) {
			zoneExists.didShowError = true;
				logError("moment.tz.zoneExists('" + name + "') has been deprecated in favor of !moment.tz.zone('" + name + "')");
		}
		return !!getZone(name);
	}

	function needsOffset (m) {
		return !!(m._a && (m._tzm === undefined));
	}

	function logError (message) {
		if (typeof console !== 'undefined' && typeof console.error === 'function') {
			console.error(message);
		}
	}

	/************************************
		moment.tz namespace
	************************************/

	function tz (input) {
		var args = Array.prototype.slice.call(arguments, 0, -1),
			name = arguments[arguments.length - 1],
			zone = getZone(name),
			out  = moment.utc.apply(null, args);

		if (zone && !moment.isMoment(input) && needsOffset(out)) {
			out.add(zone.parse(out), 'minutes');
		}

		out.tz(name);

		return out;
	}

	tz.version      = VERSION;
	tz.dataVersion  = '';
	tz._zones       = zones;
	tz._links       = links;
	tz._names       = names;
	tz.add          = addZone;
	tz.link         = addLink;
	tz.load         = loadData;
	tz.zone         = getZone;
	tz.zoneExists   = zoneExists; // deprecated in 0.1.0
	tz.names        = getNames;
	tz.Zone         = Zone;
	tz.unpack       = unpack;
	tz.unpackBase60 = unpackBase60;
	tz.needsOffset  = needsOffset;
	tz.moveInvalidForward   = true;
	tz.moveAmbiguousForward = false;

	/************************************
		Interface with Moment.js
	************************************/

	var fn = moment.fn;

	moment.tz = tz;

	moment.defaultZone = null;

	moment.updateOffset = function (mom, keepTime) {
		var zone = moment.defaultZone,
			offset;

		if (mom._z === undefined) {
			if (zone && needsOffset(mom) && !mom._isUTC) {
				mom._d = moment.utc(mom._a)._d;
				mom.utc().add(zone.parse(mom), 'minutes');
			}
			mom._z = zone;
		}
		if (mom._z) {
			offset = mom._z.offset(mom);
			if (Math.abs(offset) < 16) {
				offset = offset / 60;
			}
			if (mom.utcOffset !== undefined) {
				mom.utcOffset(-offset, keepTime);
			} else {
				mom.zone(offset, keepTime);
			}
		}
	};

	fn.tz = function (name) {
		if (name) {
			this._z = getZone(name);
			if (this._z) {
				moment.updateOffset(this);
			} else {
				logError("Moment Timezone has no data for " + name + ". See http://momentjs.com/timezone/docs/#/data-loading/.");
			}
			return this;
		}
		if (this._z) { return this._z.name; }
	};

	function abbrWrap (old) {
		return function () {
			if (this._z) { return this._z.abbr(this); }
			return old.call(this);
		};
	}

	function resetZoneWrap (old) {
		return function () {
			this._z = null;
			return old.apply(this, arguments);
		};
	}

	fn.zoneName = abbrWrap(fn.zoneName);
	fn.zoneAbbr = abbrWrap(fn.zoneAbbr);
	fn.utc      = resetZoneWrap(fn.utc);

	moment.tz.setDefault = function(name) {
		if (major < 2 || (major === 2 && minor < 9)) {
			logError('Moment Timezone setDefault() requires Moment.js >= 2.9.0. You are using Moment.js ' + moment.version + '.');
		}
		moment.defaultZone = name ? getZone(name) : null;
		return moment;
	};

	// Cloning a moment should include the _z property.
	var momentProperties = moment.momentProperties;
	if (Object.prototype.toString.call(momentProperties) === '[object Array]') {
		// moment 2.8.1+
		momentProperties.push('_z');
		momentProperties.push('_a');
	} else if (momentProperties) {
		// moment 2.7.0
		momentProperties._z = null;
	}

	loadData({
		"version": "2015d",
		"zones": [
			"Africa/Abidjan|LMT GMT|g.8 0|01|-2ldXH.Q",
			"Africa/Accra|LMT GMT GHST|.Q 0 -k|012121212121212121212121212121212121212121212121|-26BbX.8 6tzX.8 MnE 1BAk MnE 1BAk MnE 1BAk MnE 1C0k MnE 1BAk MnE 1BAk MnE 1BAk MnE 1C0k MnE 1BAk MnE 1BAk MnE 1BAk MnE 1C0k MnE 1BAk MnE 1BAk MnE 1BAk MnE 1C0k MnE 1BAk MnE 1BAk MnE 1BAk MnE 1C0k MnE 1BAk MnE 1BAk MnE",
			"Africa/Addis_Ababa|LMT EAT BEAT BEAUT|-2r.g -30 -2u -2J|01231|-1F3Cr.g 3Dzr.g okMu MFXJ",
			"Africa/Algiers|PMT WET WEST CET CEST|-9.l 0 -10 -10 -20|0121212121212121343431312123431213|-2nco9.l cNb9.l HA0 19A0 1iM0 11c0 1oo0 Wo0 1rc0 QM0 1EM0 UM0 DA0 Imo0 rd0 De0 9Xz0 1fb0 1ap0 16K0 2yo0 mEp0 hwL0 jxA0 11A0 dDd0 17b0 11B0 1cN0 2Dy0 1cN0 1fB0 1cL0",
			"Africa/Bangui|LMT WAT|-d.A -10|01|-22y0d.A",
			"Africa/Bissau|LMT WAT GMT|12.k 10 0|012|-2ldWV.E 2xonV.E",
			"Africa/Blantyre|LMT CAT|-2a.k -20|01|-2GJea.k",
			"Africa/Cairo|EET EEST|-20 -30|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-1bIO0 vb0 1ip0 11z0 1iN0 1nz0 12p0 1pz0 10N0 1pz0 16p0 1jz0 s3d0 Vz0 1oN0 11b0 1oO0 10N0 1pz0 10N0 1pb0 10N0 1pb0 10N0 1pb0 10N0 1pz0 10N0 1pb0 10N0 1pb0 11d0 1oL0 11d0 1pb0 11d0 1oL0 11d0 1oL0 11d0 1oL0 11d0 1pb0 11d0 1oL0 11d0 1oL0 11d0 1oL0 11d0 1pb0 11d0 1oL0 11d0 1oL0 11d0 1oL0 11d0 1pb0 11d0 1oL0 11d0 1WL0 rd0 1Rz0 wp0 1pb0 11d0 1oL0 11d0 1oL0 11d0 1oL0 11d0 1pb0 11d0 1qL0 Xd0 1oL0 11d0 1oL0 11d0 1pb0 11d0 1oL0 11d0 1oL0 11d0 1ny0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 WL0 1qN0 Rb0 1wp0 On0 1zd0 Lz0 1EN0 Fb0 c10 8n0 8Nd0 gL0 e10 mn0",
			"Africa/Casablanca|LMT WET WEST CET|u.k 0 -10 -10|012121212121212121312121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2gMnt.E 130Lt.E rb0 Dd0 dVb0 b6p0 TX0 EoB0 LL0 gnd0 rz0 43d0 AL0 1Nd0 XX0 1Cp0 pz0 dEp0 4mn0 SyN0 AL0 1Nd0 wn0 1FB0 Db0 1zd0 Lz0 1Nf0 wM0 co0 go0 1o00 s00 dA0 vc0 11A0 A00 e00 y00 11A0 uo0 e00 DA0 11A0 rA0 e00 Jc0 WM0 m00 gM0 M00 WM0 jc0 e00 RA0 11A0 dA0 e00 Uo0 11A0 800 gM0 Xc0 11A0 5c0 e00 17A0 WM0 2o0 e00 1ao0 19A0 1g00 16M0 1iM0 1400 1lA0 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qo0 1200 1kM0 14M0 1i00",
			"Africa/Ceuta|WET WEST CET CEST|0 -10 -10 -20|010101010101010101010232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-25KN0 11z0 drd0 18o0 3I00 17c0 1fA0 1a00 1io0 1a00 1y7p0 LL0 gnd0 rz0 43d0 AL0 1Nd0 XX0 1Cp0 pz0 dEp0 4VB0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Africa/El_Aaiun|LMT WAT WET WEST|Q.M 10 0 -10|0123232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-1rDz7.c 1GVA7.c 6L0 AL0 1Nd0 XX0 1Cp0 pz0 1cBB0 AL0 1Nd0 wn0 1FB0 Db0 1zd0 Lz0 1Nf0 wM0 co0 go0 1o00 s00 dA0 vc0 11A0 A00 e00 y00 11A0 uo0 e00 DA0 11A0 rA0 e00 Jc0 WM0 m00 gM0 M00 WM0 jc0 e00 RA0 11A0 dA0 e00 Uo0 11A0 800 gM0 Xc0 11A0 5c0 e00 17A0 WM0 2o0 e00 1ao0 19A0 1g00 16M0 1iM0 1400 1lA0 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qo0 1200 1kM0 14M0 1i00",
			"Africa/Johannesburg|SAST SAST SAST|-1u -20 -30|012121|-2GJdu 1Ajdu 1cL0 1cN0 1cL0",
			"Africa/Juba|LMT CAT CAST EAT|-2a.8 -20 -30 -30|01212121212121212121212121212121213|-1yW2a.8 1zK0a.8 16L0 1iN0 17b0 1jd0 17b0 1ip0 17z0 1i10 17X0 1hB0 18n0 1hd0 19b0 1gp0 19z0 1iN0 17b0 1ip0 17z0 1i10 18n0 1hd0 18L0 1gN0 19b0 1gp0 19z0 1iN0 17z0 1i10 17X0 yGd0",
			"Africa/Monrovia|MMT LRT GMT|H.8 I.u 0|012|-23Lzg.Q 29s01.m",
			"Africa/Ndjamena|LMT WAT WAST|-10.c -10 -20|0121|-2le10.c 2J3c0.c Wn0",
			"Africa/Tripoli|LMT CET CEST EET|-Q.I -10 -20 -20|012121213121212121212121213123123|-21JcQ.I 1hnBQ.I vx0 4iP0 xx0 4eN0 Bb0 7ip0 U0n0 A10 1db0 1cN0 1db0 1dd0 1db0 1eN0 1bb0 1e10 1cL0 1c10 1db0 1dd0 1db0 1cN0 1db0 1q10 fAn0 1ep0 1db0 AKq0 TA0 1o00",
			"Africa/Tunis|PMT CET CEST|-9.l -10 -20|0121212121212121212121212121212121|-2nco9.l 18pa9.l 1qM0 DA0 3Tc0 11B0 1ze0 WM0 7z0 3d0 14L0 1cN0 1f90 1ar0 16J0 1gXB0 WM0 1rA0 11c0 nwo0 Ko0 1cM0 1cM0 1rA0 10M0 zuM0 10N0 1aN0 1qM0 WM0 1qM0 11A0 1o00",
			"Africa/Windhoek|SWAT SAST SAST CAT WAT WAST|-1u -20 -30 -20 -10 -20|012134545454545454545454545454545454545454545454545454545454545454545454545454545454545454545|-2GJdu 1Ajdu 1cL0 1SqL0 9NA0 11D0 1nX0 11B0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 11B0 1nX0 11B0",
			"America/Adak|NST NWT NPT BST BDT AHST HST HDT|b0 a0 a0 b0 a0 a0 a0 90|012034343434343434343434343434343456767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676|-17SX0 8wW0 iB0 Qlb0 52O0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 cm0 10q0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Anchorage|CAT CAWT CAPT AHST AHDT YST AKST AKDT|a0 90 90 a0 90 90 90 80|012034343434343434343434343434343456767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676|-17T00 8wX0 iA0 Qlb0 52O0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 cm0 10q0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Anguilla|LMT AST|46.4 40|01|-2kNvR.U",
			"America/Araguaina|LMT BRT BRST|3c.M 30 20|0121212121212121212121212121212121212121212121212121|-2glwL.c HdKL.c 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 dMN0 Lz0 1zd0 Rb0 1wN0 Wn0 1tB0 Rb0 1tB0 WL0 1tB0 Rb0 1zd0 On0 1HB0 FX0 ny10 Lz0",
			"America/Argentina/Buenos_Aires|CMT ART ARST ART ARST|4g.M 40 30 30 20|0121212121212121212121212121212121212121213434343434343234343|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1wp0 Rb0 1wp0 Rb0 1wp0 TX0 g0p0 10M0 j3c0 uL0 1qN0 WL0",
			"America/Argentina/Catamarca|CMT ART ARST ART ARST WART|4g.M 40 30 30 20 40|0121212121212121212121212121212121212121213434343454343235343|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1wp0 Rb0 1wq0 Ra0 1wp0 TX0 g0p0 10M0 ako0 7B0 8zb0 uL0",
			"America/Argentina/Cordoba|CMT ART ARST ART ARST WART|4g.M 40 30 30 20 40|0121212121212121212121212121212121212121213434343454343234343|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1wp0 Rb0 1wq0 Ra0 1wp0 TX0 g0p0 10M0 j3c0 uL0 1qN0 WL0",
			"America/Argentina/Jujuy|CMT ART ARST ART ARST WART WARST|4g.M 40 30 30 20 40 30|01212121212121212121212121212121212121212134343456543432343|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1ze0 TX0 1ld0 WK0 1wp0 TX0 g0p0 10M0 j3c0 uL0",
			"America/Argentina/La_Rioja|CMT ART ARST ART ARST WART|4g.M 40 30 30 20 40|01212121212121212121212121212121212121212134343434534343235343|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1wp0 Qn0 qO0 16n0 Rb0 1wp0 TX0 g0p0 10M0 ako0 7B0 8zb0 uL0",
			"America/Argentina/Mendoza|CMT ART ARST ART ARST WART WARST|4g.M 40 30 30 20 40 30|0121212121212121212121212121212121212121213434345656543235343|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1u20 SL0 1vd0 Tb0 1wp0 TW0 g0p0 10M0 agM0 Op0 7TX0 uL0",
			"America/Argentina/Rio_Gallegos|CMT ART ARST ART ARST WART|4g.M 40 30 30 20 40|0121212121212121212121212121212121212121213434343434343235343|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1wp0 Rb0 1wp0 Rb0 1wp0 TX0 g0p0 10M0 ako0 7B0 8zb0 uL0",
			"America/Argentina/Salta|CMT ART ARST ART ARST WART|4g.M 40 30 30 20 40|01212121212121212121212121212121212121212134343434543432343|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1wp0 Rb0 1wq0 Ra0 1wp0 TX0 g0p0 10M0 j3c0 uL0",
			"America/Argentina/San_Juan|CMT ART ARST ART ARST WART|4g.M 40 30 30 20 40|01212121212121212121212121212121212121212134343434534343235343|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1wp0 Qn0 qO0 16n0 Rb0 1wp0 TX0 g0p0 10M0 ak00 m10 8lb0 uL0",
			"America/Argentina/San_Luis|CMT ART ARST ART ARST WART WARST|4g.M 40 30 30 20 40 30|01212121212121212121212121212121212121212134343456536353465653|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 XX0 1q20 SL0 AN0 kin0 10M0 ak00 m10 8lb0 8L0 jd0 1qN0 WL0 1qN0",
			"America/Argentina/Tucuman|CMT ART ARST ART ARST WART|4g.M 40 30 30 20 40|012121212121212121212121212121212121212121343434345434323534343|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1wp0 Rb0 1wq0 Ra0 1wp0 TX0 g0p0 10M0 ako0 4N0 8BX0 uL0 1qN0 WL0",
			"America/Argentina/Ushuaia|CMT ART ARST ART ARST WART|4g.M 40 30 30 20 40|0121212121212121212121212121212121212121213434343434343235343|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1wp0 Rb0 1wp0 Rb0 1wp0 TX0 g0p0 10M0 ajA0 8p0 8zb0 uL0",
			"America/Aruba|LMT ANT AST|4z.L 4u 40|012|-2kV7o.d 28KLS.d",
			"America/Asuncion|AMT PYT PYT PYST|3O.E 40 30 30|012131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313|-1x589.k 1DKM9.k 3CL0 3Dd0 10L0 1pB0 10n0 1pB0 10n0 1pB0 1cL0 1dd0 1db0 1dd0 1cL0 1dd0 1cL0 1dd0 1cL0 1dd0 1db0 1dd0 1cL0 1dd0 1cL0 1dd0 1cL0 1dd0 1db0 1dd0 1cL0 1lB0 14n0 1dd0 1cL0 1fd0 WL0 1rd0 1aL0 1dB0 Xz0 1qp0 Xb0 1qN0 10L0 1rB0 TX0 1tB0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1qN0 1cL0 WN0 1qL0 11B0 1nX0 1ip0 WL0 1qN0 WL0 1qN0 WL0 1tB0 TX0 1tB0 TX0 1tB0 19X0 1a10 1fz0 1a10 1fz0 1cN0 17b0 1ip0 17b0 1ip0 17b0 1ip0 19X0 1fB0 19X0 1fB0 19X0 1ip0 17b0 1ip0 17b0 1ip0 19X0 1fB0 19X0 1fB0 19X0 1fB0 19X0 1ip0 17b0 1ip0 17b0 1ip0 19X0 1fB0 19X0 1fB0 19X0 1ip0 17b0 1ip0 17b0 1ip0 19X0 1fB0 19X0 1fB0 19X0 1fB0 19X0 1ip0 17b0 1ip0 17b0 1ip0",
			"America/Atikokan|CST CDT CWT CPT EST|60 50 50 50 50|0101234|-25TQ0 1in0 Rnb0 3je0 8x30 iw0",
			"America/Bahia|LMT BRT BRST|2y.4 30 20|01212121212121212121212121212121212121212121212121212121212121|-2glxp.U HdLp.U 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 1EN0 Lz0 1C10 IL0 1HB0 Db0 1HB0 On0 1zd0 On0 1zd0 Lz0 1zd0 Rb0 1wN0 Wn0 1tB0 Rb0 1tB0 WL0 1tB0 Rb0 1zd0 On0 1HB0 FX0 l5B0 Rb0",
			"America/Bahia_Banderas|LMT MST CST PST MDT CDT|71 70 60 80 60 50|0121212131414141414141414141414141414152525252525252525252525252525252525252525252525252525252|-1UQF0 deL0 8lc0 17c0 10M0 1dd0 otX0 gmN0 P2N0 13Vd0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nW0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0",
			"America/Barbados|LMT BMT AST ADT|3W.t 3W.t 40 30|01232323232|-1Q0I1.v jsM0 1ODC1.v IL0 1ip0 17b0 1ip0 17b0 1ld0 13b0",
			"America/Belem|LMT BRT BRST|3d.U 30 20|012121212121212121212121212121|-2glwK.4 HdKK.4 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0",
			"America/Belize|LMT CST CHDT CDT|5Q.M 60 5u 50|01212121212121212121212121212121212121212121212121213131|-2kBu7.c fPA7.c Onu 1zcu Rbu 1wou Rbu 1wou Rbu 1zcu Onu 1zcu Onu 1zcu Rbu 1wou Rbu 1wou Rbu 1wou Rbu 1zcu Onu 1zcu Onu 1zcu Rbu 1wou Rbu 1wou Rbu 1zcu Onu 1zcu Onu 1zcu Onu 1zcu Rbu 1wou Rbu 1wou Rbu 1zcu Onu 1zcu Onu 1zcu Rbu 1wou Rbu 1f0Mu qn0 lxB0 mn0",
			"America/Blanc-Sablon|AST ADT AWT APT|40 30 30 30|010230|-25TS0 1in0 UGp0 8x50 iu0",
			"America/Boa_Vista|LMT AMT AMST|42.E 40 30|0121212121212121212121212121212121|-2glvV.k HdKV.k 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 smp0 WL0 1tB0 2L0",
			"America/Bogota|BMT COT COST|4U.g 50 40|0121|-2eb73.I 38yo3.I 2en0",
			"America/Boise|PST PDT MST MWT MPT MDT|80 70 70 60 60 60|0101023425252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252|-261q0 1nX0 11B0 1nX0 8C10 JCL0 8x20 ix0 QwN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 Dd0 1Kn0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Cambridge_Bay|zzz MST MWT MPT MDDT MDT CST CDT EST|0 70 60 60 50 60 60 50 50|0123141515151515151515151515151515151515151515678651515151515151515151515151515151515151515151515151515151515151515151515151|-21Jc0 RO90 8x20 ix0 LCL0 1fA0 zgO0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11A0 1nX0 2K0 WQ0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Campo_Grande|LMT AMT AMST|3C.s 40 30|012121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212|-2glwl.w HdLl.w 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 1EN0 Lz0 1C10 IL0 1HB0 Db0 1HB0 On0 1zd0 On0 1zd0 Lz0 1zd0 Rb0 1wN0 Wn0 1tB0 Rb0 1tB0 WL0 1tB0 Rb0 1zd0 On0 1HB0 FX0 1C10 Lz0 1Ip0 HX0 1zd0 On0 1HB0 IL0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 Rb0 1zd0 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1C10 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 Rb0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1C10 Lz0 1C10 Lz0 1C10 Lz0 1C10 On0 1zd0 Rb0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0",
			"America/Cancun|LMT CST EST EDT CDT|5L.4 60 50 40 50|0123232341414141414141414141414141414141412|-1UQG0 2q2o0 yLB0 1lb0 14p0 1lb0 14p0 Lz0 xB0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 Dd0",
			"America/Caracas|CMT VET VET|4r.E 4u 40|0121|-2kV7w.k 28KM2.k 1IwOu",
			"America/Cayenne|LMT GFT GFT|3t.k 40 30|012|-2mrwu.E 2gWou.E",
			"America/Cayman|CMT EST|5j.A 50|01|-2uduE.o",
			"America/Chicago|CST CDT EST CWT CPT|60 50 50 50 50|01010101010101010101010101010101010102010101010103401010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261s0 1nX0 11B0 1nX0 1wp0 TX0 WN0 1qL0 1cN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 11B0 1Hz0 14p0 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 RB0 8x30 iw0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Chihuahua|LMT MST CST CDT MDT|74.k 70 60 50 60|0121212323241414141414141414141414141414141414141414141414141414141414141414141414141414141|-1UQF0 deL0 8lc0 17c0 10M0 1dd0 2zQN0 1lb0 14p0 1lb0 14q0 1lb0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0",
			"America/Costa_Rica|SJMT CST CDT|5A.d 60 50|0121212121|-1Xd6n.L 2lu0n.L Db0 1Kp0 Db0 pRB0 15b0 1kp0 mL0",
			"America/Creston|MST PST|70 80|010|-29DR0 43B0",
			"America/Cuiaba|LMT AMT AMST|3I.k 40 30|0121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212|-2glwf.E HdLf.E 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 1EN0 Lz0 1C10 IL0 1HB0 Db0 1HB0 On0 1zd0 On0 1zd0 Lz0 1zd0 Rb0 1wN0 Wn0 1tB0 Rb0 1tB0 WL0 1tB0 Rb0 1zd0 On0 1HB0 FX0 4a10 HX0 1zd0 On0 1HB0 IL0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 Rb0 1zd0 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1C10 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 Rb0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1C10 Lz0 1C10 Lz0 1C10 Lz0 1C10 On0 1zd0 Rb0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0",
			"America/Danmarkshavn|LMT WGT WGST GMT|1e.E 30 20 0|01212121212121212121212121212121213|-2a5WJ.k 2z5fJ.k 19U0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 DC0",
			"America/Dawson|YST YDT YWT YPT YDDT PST PDT|90 80 80 80 70 80 70|0101023040565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565|-25TN0 1in0 1o10 13V0 Ser0 8x00 iz0 LCL0 1fA0 jrA0 fNd0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Dawson_Creek|PST PDT PWT PPT MST|80 70 70 70 70|0102301010101010101010101010101010101010101010101010101014|-25TO0 1in0 UGp0 8x10 iy0 3NB0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 ML0",
			"America/Denver|MST MDT MWT MPT|70 60 60 60|01010101023010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261r0 1nX0 11B0 1nX0 11B0 1qL0 WN0 mn0 Ord0 8x20 ix0 LCN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Detroit|LMT CST EST EWT EPT EDT|5w.b 60 50 40 40 40|01234252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252|-2Cgir.N peqr.N 156L0 8x40 iv0 6fd0 11z0 Jy10 SL0 dnB0 1cL0 s10 1Vz0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Edmonton|LMT MST MDT MWT MPT|7x.Q 70 60 60 60|01212121212121341212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2yd4q.8 shdq.8 1in0 17d0 hz0 2dB0 1fz0 1a10 11z0 1qN0 WL0 1qN0 11z0 IGN0 8x20 ix0 3NB0 11z0 LFB0 1cL0 3Cp0 1cL0 66N0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Eirunepe|LMT ACT ACST AMT|4D.s 50 40 40|0121212121212121212121212121212131|-2glvk.w HdLk.w 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 dPB0 On0 yTd0 d5X0",
			"America/El_Salvador|LMT CST CDT|5U.M 60 50|012121|-1XiG3.c 2Fvc3.c WL0 1qN0 WL0",
			"America/Ensenada|LMT MST PST PDT PWT PPT|7M.4 70 80 70 70 70|012123245232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-1UQE0 4PX0 8mM0 8lc0 SN0 1cL0 pHB0 83r0 zI0 5O10 1Rz0 cOP0 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 BUp0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 U10 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Fort_Wayne|CST CDT CWT CPT EST EDT|60 50 50 50 50 40|010101023010101010101010101040454545454545454545454545454545454545454545454545454545454545454545454|-261s0 1nX0 11B0 1nX0 QI10 Db0 RB0 8x30 iw0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 5Tz0 1o10 qLb0 1cL0 1cN0 1cL0 1qhd0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Fortaleza|LMT BRT BRST|2y 30 20|0121212121212121212121212121212121212121|-2glxq HdLq 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 nsp0 WL0 1tB0 5z0 2mN0 On0",
			"America/Glace_Bay|LMT AST ADT AWT APT|3X.M 40 30 30 30|012134121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2IsI0.c CwO0.c 1in0 UGp0 8x50 iu0 iq10 11z0 Jg10 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Godthab|LMT WGT WGST|3q.U 30 20|0121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2a5Ux.4 2z5dx.4 19U0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"America/Goose_Bay|NST NDT NST NDT NWT NPT AST ADT ADDT|3u.Q 2u.Q 3u 2u 2u 2u 40 30 20|010232323232323245232323232323232323232323232323232323232326767676767676767676767676767676767676767676768676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676|-25TSt.8 1in0 DXb0 2HbX.8 WL0 1qN0 WL0 1qN0 WL0 1tB0 TX0 1tB0 WL0 1qN0 WL0 1qN0 7UHu itu 1tB0 WL0 1qN0 WL0 1qN0 WL0 1qN0 WL0 1tB0 WL0 1ld0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 S10 g0u 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14n1 1lb0 14p0 1nW0 11C0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zcX Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Grand_Turk|KMT EST EDT AST|57.b 50 40 40|0121212121212121212121212121212121212121212121212121212121212121212121212123|-2l1uQ.N 2HHBQ.N 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Guatemala|LMT CST CDT|62.4 60 50|0121212121|-24KhV.U 2efXV.U An0 mtd0 Nz0 ifB0 17b0 zDB0 11z0",
			"America/Guayaquil|QMT ECT|5e 50|01|-1yVSK",
			"America/Guyana|LMT GBGT GYT GYT GYT|3Q.E 3J 3J 30 40|01234|-2dvU7.k 24JzQ.k mlc0 Bxbf",
			"America/Halifax|LMT AST ADT AWT APT|4e.o 40 30 30 30|0121212121212121212121212121212121212121212121212134121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2IsHJ.A xzzJ.A 1db0 3I30 1in0 3HX0 IL0 1E10 ML0 1yN0 Pb0 1Bd0 Mn0 1Bd0 Rz0 1w10 Xb0 1w10 LX0 1w10 Xb0 1w10 Lz0 1C10 Jz0 1E10 OL0 1yN0 Un0 1qp0 Xb0 1qp0 11X0 1w10 Lz0 1HB0 LX0 1C10 FX0 1w10 Xb0 1qp0 Xb0 1BB0 LX0 1td0 Xb0 1qp0 Xb0 Rf0 8x50 iu0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 3Qp0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 3Qp0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 6i10 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Havana|HMT CST CDT|5t.A 50 40|012121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-1Meuu.o 72zu.o ML0 sld0 An0 1Nd0 Db0 1Nd0 An0 6Ep0 An0 1Nd0 An0 JDd0 Mn0 1Ap0 On0 1fd0 11X0 1qN0 WL0 1wp0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 14n0 1ld0 14L0 1kN0 15b0 1kp0 1cL0 1cN0 1fz0 1a10 1fz0 1fB0 11z0 14p0 1nX0 11B0 1nX0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 14n0 1ld0 14n0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 1a10 1in0 1a10 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 17c0 1o00 11A0 1qM0 11A0 1o00 11A0 1o00 14o0 1lc0 14o0 1lc0 11A0 6i00 Rc0 1wo0 U00 1tA0 Rc0 1wo0 U00 1wo0 U00 1zc0 U00 1qM0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0",
			"America/Hermosillo|LMT MST CST PST MDT|7n.Q 70 60 80 60|0121212131414141|-1UQF0 deL0 8lc0 17c0 10M0 1dd0 otX0 gmN0 P2N0 13Vd0 1lb0 14p0 1lb0 14p0 1lb0",
			"America/Indiana/Knox|CST CDT CWT CPT EST|60 50 50 50 50|0101023010101010101010101010101010101040101010101010101010101010101010101010101010101010141010101010101010101010101010101010101010101010101010101010101010|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 3NB0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 1fz0 1cN0 1cL0 1cN0 11z0 1o10 11z0 1o10 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 3Cn0 8wp0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 z8o0 1o00 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Indiana/Marengo|CST CDT CWT CPT EST EDT|60 50 50 50 50 40|0101023010101010101010104545454545414545454545454545454545454545454545454545454545454545454545454545454|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 dyN0 11z0 6fd0 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 jrz0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1VA0 LA0 1BX0 1e6p0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Indiana/Petersburg|CST CDT CWT CPT EST EDT|60 50 50 50 50 40|01010230101010101010101010104010101010101010101010141014545454545454545454545454545454545454545454545454545454545454|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 njX0 WN0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 3Fb0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 19co0 1o00 Rd0 1zb0 Oo0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Indiana/Tell_City|CST CDT CWT CPT EST EDT|60 50 50 50 50 40|01010230101010101010101010101010454541010101010101010101010101010101010101010101010101010101010101010|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 1o10 11z0 g0p0 11z0 1o10 11z0 1qL0 WN0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 1fz0 1cN0 WL0 1qN0 1cL0 1cN0 1cL0 1cN0 caL0 1cL0 1cN0 1cL0 1qhd0 1o00 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Indiana/Vevay|CST CDT CWT CPT EST EDT|60 50 50 50 50 40|010102304545454545454545454545454545454545454545454545454545454545454545454545454|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 kPB0 Awn0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1lnd0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Indiana/Vincennes|CST CDT CWT CPT EST EDT|60 50 50 50 50 40|01010230101010101010101010101010454541014545454545454545454545454545454545454545454545454545454545454|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 1o10 11z0 g0p0 11z0 1o10 11z0 1qL0 WN0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 1fz0 1cN0 WL0 1qN0 1cL0 1cN0 1cL0 1cN0 caL0 1cL0 1cN0 1cL0 1qhd0 1o00 Rd0 1zb0 Oo0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Indiana/Winamac|CST CDT CWT CPT EST EDT|60 50 50 50 50 40|01010230101010101010101010101010101010454541054545454545454545454545454545454545454545454545454545454545454|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 1fz0 1cN0 1cL0 1cN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 jrz0 1cL0 1cN0 1cL0 1qhd0 1o00 Rd0 1za0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Inuvik|zzz PST PDDT MST MDT|0 80 60 70 60|0121343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343|-FnA0 tWU0 1fA0 wPe0 2pz0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Iqaluit|zzz EWT EPT EST EDDT EDT CST CDT|0 40 40 50 30 40 60 50|01234353535353535353535353535353535353535353567353535353535353535353535353535353535353535353535353535353535353535353535353|-16K00 7nX0 iv0 LCL0 1fA0 zgO0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11C0 1nX0 11A0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Jamaica|KMT EST EDT|57.b 50 40|0121212121212121212121|-2l1uQ.N 2uM1Q.N 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0",
			"America/Juneau|PST PWT PPT PDT YDT YST AKST AKDT|80 70 70 70 80 90 90 80|01203030303030303030303030403030356767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676|-17T20 8x10 iy0 Vo10 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cM0 1cM0 1cL0 1cN0 1fz0 1a10 1fz0 co0 10q0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Kentucky/Louisville|CST CDT CWT CPT EST EDT|60 50 50 50 50 40|0101010102301010101010101010101010101454545454545414545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454|-261s0 1nX0 11B0 1nX0 3Fd0 Nb0 LPd0 11z0 RB0 8x30 iw0 Bb0 10N0 2bB0 8in0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 xz0 gso0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1VA0 LA0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Kentucky/Monticello|CST CDT CWT CPT EST EDT|60 50 50 50 50 40|0101023010101010101010101010101010101010101010101010101010101010101010101454545454545454545454545454545454545454545454545454545454545454545454545454|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 SWp0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11A0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/La_Paz|CMT BOST BOT|4w.A 3w.A 40|012|-1x37r.o 13b0",
			"America/Lima|LMT PET PEST|58.A 50 40|0121212121212121|-2tyGP.o 1bDzP.o zX0 1aN0 1cL0 1cN0 1cL0 1PrB0 zX0 1O10 zX0 6Gp0 zX0 98p0 zX0",
			"America/Los_Angeles|PST PDT PWT PPT|80 70 70 70|010102301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261q0 1nX0 11B0 1nX0 SgN0 8x10 iy0 5Wp0 1Vb0 3dB0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Maceio|LMT BRT BRST|2m.Q 30 20|012121212121212121212121212121212121212121|-2glxB.8 HdLB.8 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 dMN0 Lz0 8Q10 WL0 1tB0 5z0 2mN0 On0",
			"America/Managua|MMT CST EST CDT|5J.c 60 50 50|0121313121213131|-1quie.M 1yAMe.M 4mn0 9Up0 Dz0 1K10 Dz0 s3F0 1KH0 DB0 9In0 k8p0 19X0 1o30 11y0",
			"America/Manaus|LMT AMT AMST|40.4 40 30|01212121212121212121212121212121|-2glvX.U HdKX.U 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 dPB0 On0",
			"America/Martinique|FFMT AST ADT|44.k 40 30|0121|-2mPTT.E 2LPbT.E 19X0",
			"America/Matamoros|LMT CST CDT|6E 60 50|0121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-1UQG0 2FjC0 1nX0 i6p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 U10 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Mazatlan|LMT MST CST PST MDT|75.E 70 60 80 60|0121212131414141414141414141414141414141414141414141414141414141414141414141414141414141414141|-1UQF0 deL0 8lc0 17c0 10M0 1dd0 otX0 gmN0 P2N0 13Vd0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0",
			"America/Menominee|CST CDT CWT CPT EST|60 50 50 50 50|01010230101041010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 1o10 11z0 LCN0 1fz0 6410 9Jb0 1cM0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Merida|LMT CST EST CDT|5W.s 60 50 50|0121313131313131313131313131313131313131313131313131313131313131313131313131313131313131|-1UQG0 2q2o0 2hz0 wu30 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0",
			"America/Metlakatla|PST PWT PPT PDT|80 70 70 70|0120303030303030303030303030303030|-17T20 8x10 iy0 Vo10 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0",
			"America/Mexico_City|LMT MST CST CDT CWT|6A.A 70 60 50 50|012121232324232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-1UQF0 deL0 8lc0 17c0 10M0 1dd0 gEn0 TX0 3xd0 Jb0 6zB0 SL0 e5d0 17b0 1Pff0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0",
			"America/Miquelon|LMT AST PMST PMDT|3I.E 40 30 20|012323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-2mKkf.k 2LTAf.k gQ10 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Moncton|EST AST ADT AWT APT|50 40 30 30 30|012121212121212121212134121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2IsH0 CwN0 1in0 zAo0 An0 1Nd0 An0 1Nd0 An0 1Nd0 An0 1Nd0 An0 1Nd0 An0 1K10 Lz0 1zB0 NX0 1u10 Wn0 S20 8x50 iu0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 3Cp0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14n1 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 ReX 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Monterrey|LMT CST CDT|6F.g 60 50|0121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-1UQG0 2FjC0 1nX0 i6p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0",
			"America/Montevideo|MMT UYT UYHST UYST UYT UYHST|3I.I 3u 30 20 30 2u|012121212121212121212121213434343434345454543453434343434343434343434343434343434343434343434343434343434343434343434343434343434343|-20UIf.g 8jzJ.g 1cLu 1dcu 1cLu 1dcu 1cLu ircu 11zu 1o0u 11zu 1o0u 11zu 1qMu WLu 1qMu WLu 1qMu WLu 1qMu 11zu 1o0u 11zu NAu 11bu 2iMu zWu Dq10 19X0 pd0 jz0 cm10 19X0 1fB0 1on0 11d0 1oL0 1nB0 1fzu 1aou 1fzu 1aou 1fzu 3nAu Jb0 3MN0 1SLu 4jzu 2PB0 Lb0 3Dd0 1pb0 ixd0 An0 1MN0 An0 1wp0 On0 1wp0 Rb0 1zd0 On0 1wp0 Rb0 s8p0 1fB0 1ip0 11z0 1ld0 14n0 1o10 11z0 1o10 11z0 1o10 14n0 1ld0 14n0 1ld0 14n0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 14n0 1ld0 14n0 1ld0 14n0 1o10 11z0 1o10 11z0 1o10 14n0 1ld0 14n0 1ld0 14n0 1ld0 14n0 1o10 11z0 1o10 11z0 1o10 14n0 1ld0 14n0 1ld0 14n0 1o10 11z0 1o10 11z0 1o10 14n0 1ld0 14n0 1ld0 14n0 1ld0 14n0 1o10 11z0 1o10 11z0 1o10",
			"America/Montreal|EST EDT EWT EPT|50 40 40 40|01010101010101010101010101010101010101010101012301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-25TR0 1in0 11Wu 1nzu 1fD0 WJ0 1wr0 Nb0 1Ap0 On0 1zd0 On0 1wp0 TX0 1tB0 TX0 1tB0 TX0 1tB0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 4kM0 8x40 iv0 1o10 11z0 1nX0 11z0 1o10 11z0 1o10 1qL0 11D0 1nX0 11B0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Nassau|LMT EST EDT|59.u 50 40|012121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2kNuO.u 26XdO.u 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/New_York|EST EDT EWT EPT|50 40 40 40|01010101010101010101010101010101010101010101010102301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261t0 1nX0 11B0 1nX0 11B0 1qL0 1a10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 RB0 8x40 iv0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Nipigon|EST EDT EWT EPT|50 40 40 40|010123010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-25TR0 1in0 Rnb0 3je0 8x40 iv0 19yN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Nome|NST NWT NPT BST BDT YST AKST AKDT|b0 a0 a0 b0 a0 90 90 80|012034343434343434343434343434343456767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676|-17SX0 8wW0 iB0 Qlb0 52O0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 cl0 10q0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Noronha|LMT FNT FNST|29.E 20 10|0121212121212121212121212121212121212121|-2glxO.k HdKO.k 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 nsp0 WL0 1tB0 2L0 2pB0 On0",
			"America/North_Dakota/Beulah|MST MDT MWT MPT CST CDT|70 60 60 60 60 50|010102301010101010101010101010101010101010101010101010101010101010101010101010101010101010101014545454545454545454545454545454545454545454545454545454|-261r0 1nX0 11B0 1nX0 SgN0 8x20 ix0 QwN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Oo0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/North_Dakota/Center|MST MDT MWT MPT CST CDT|70 60 60 60 60 50|010102301010101010101010101010101010101010101010101010101014545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454|-261r0 1nX0 11B0 1nX0 SgN0 8x20 ix0 QwN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14o0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/North_Dakota/New_Salem|MST MDT MWT MPT CST CDT|70 60 60 60 60 50|010102301010101010101010101010101010101010101010101010101010101010101010101010101454545454545454545454545454545454545454545454545454545454545454545454|-261r0 1nX0 11B0 1nX0 SgN0 8x20 ix0 QwN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14o0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Ojinaga|LMT MST CST CDT MDT|6V.E 70 60 50 60|0121212323241414141414141414141414141414141414141414141414141414141414141414141414141414141|-1UQF0 deL0 8lc0 17c0 10M0 1dd0 2zQN0 1lb0 14p0 1lb0 14q0 1lb0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 U10 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Pangnirtung|zzz AST AWT APT ADDT ADT EDT EST CST CDT|0 40 30 30 20 30 40 50 60 50|012314151515151515151515151515151515167676767689767676767676767676767676767676767676767676767676767676767676767676767676767|-1XiM0 PnG0 8x50 iu0 LCL0 1fA0 zgO0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1o00 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11C0 1nX0 11A0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Paramaribo|LMT PMT PMT NEGT SRT SRT|3E.E 3E.Q 3E.A 3u 3u 30|012345|-2nDUj.k Wqo0.c qanX.I 1dmLN.o lzc0",
			"America/Phoenix|MST MDT MWT|70 60 60|01010202010|-261r0 1nX0 11B0 1nX0 SgN0 4Al1 Ap0 1db0 SWqX 1cL0",
			"America/Port-au-Prince|PPMT EST EDT|4N 50 40|0121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-28RHb 2FnMb 19X0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14q0 1o00 11A0 1o00 11A0 1o00 14o0 1lc0 14o0 1lc0 14o0 1o00 11A0 1o00 11A0 1o00 14o0 1lc0 14o0 1lc0 i6n0 1nX0 11B0 1nX0 d430 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Porto_Acre|LMT ACT ACST AMT|4v.c 50 40 40|01212121212121212121212121212131|-2glvs.M HdLs.M 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 NBd0 d5X0",
			"America/Porto_Velho|LMT AMT AMST|4f.A 40 30|012121212121212121212121212121|-2glvI.o HdKI.o 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0",
			"America/Puerto_Rico|AST AWT APT|40 30 30|0120|-17lU0 7XT0 iu0",
			"America/Rainy_River|CST CDT CWT CPT|60 50 50 50|010123010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-25TQ0 1in0 Rnb0 3je0 8x30 iw0 19yN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Rankin_Inlet|zzz CST CDDT CDT EST|0 60 40 50 50|012131313131313131313131313131313131313131313431313131313131313131313131313131313131313131313131313131313131313131313131|-vDc0 keu0 1fA0 zgO0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Recife|LMT BRT BRST|2j.A 30 20|0121212121212121212121212121212121212121|-2glxE.o HdLE.o 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 nsp0 WL0 1tB0 2L0 2pB0 On0",
			"America/Regina|LMT MST MDT MWT MPT CST|6W.A 70 60 60 60 60|012121212121212121212121341212121212121212121212121215|-2AD51.o uHe1.o 1in0 s2L0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 66N0 1cL0 1cN0 19X0 1fB0 1cL0 1fB0 1cL0 1cN0 1cL0 M30 8x20 ix0 1ip0 1cL0 1ip0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 3NB0 1cL0 1cN0",
			"America/Resolute|zzz CST CDDT CDT EST|0 60 40 50 50|012131313131313131313131313131313131313131313431313131313431313131313131313131313131313131313131313131313131313131313131|-SnA0 GWS0 1fA0 zgO0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Santa_Isabel|LMT MST PST PDT PWT PPT|7D.s 70 80 70 70 70|012123245232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-1UQE0 4PX0 8mM0 8lc0 SN0 1cL0 pHB0 83r0 zI0 5O10 1Rz0 cOP0 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 BUp0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0",
			"America/Santarem|LMT AMT AMST BRT|3C.M 40 30 30|0121212121212121212121212121213|-2glwl.c HdLl.c 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 NBd0",
			"America/Santiago|SMT CLT CLT CLST CLST CLT|4G.K 50 40 40 30 30|01020313131313121242124242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424245|-2q2jh.e fJAh.e 5knG.K 1Vzh.e jRAG.K 1pbh.e 11d0 1oL0 11d0 1oL0 11d0 1oL0 11d0 1pb0 11d0 nHX0 op0 9Bz0 jb0 1oN0 ko0 Qeo0 WL0 1zd0 On0 1ip0 11z0 1o10 11z0 1qN0 WL0 1ld0 14n0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 WL0 1qN0 1cL0 1cN0 11z0 1o10 11z0 1qN0 WL0 1fB0 19X0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 17b0 1ip0 11z0 1ip0 1fz0 1fB0 11z0 1qN0 WL0 1qN0 WL0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 17b0 1ip0 11z0 1o10 19X0 1fB0 1nX0 G10 1EL0 Op0 1zb0 Rd0 1wn0 Rd0 1wn0",
			"America/Santo_Domingo|SDMT EST EDT EHDT AST|4E 50 40 4u 40|01213131313131414|-1ttjk 1lJMk Mn0 6sp0 Lbu 1Cou yLu 1RAu wLu 1QMu xzu 1Q0u xXu 1PAu 13jB0 e00",
			"America/Sao_Paulo|LMT BRT BRST|36.s 30 20|012121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212|-2glwR.w HdKR.w 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 pTd0 PX0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 1EN0 Lz0 1C10 IL0 1HB0 Db0 1HB0 On0 1zd0 On0 1zd0 Lz0 1zd0 Rb0 1wN0 Wn0 1tB0 Rb0 1tB0 WL0 1tB0 Rb0 1zd0 On0 1HB0 FX0 1C10 Lz0 1Ip0 HX0 1zd0 On0 1HB0 IL0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 Rb0 1zd0 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1C10 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 Rb0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1C10 Lz0 1C10 Lz0 1C10 Lz0 1C10 On0 1zd0 Rb0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0",
			"America/Scoresbysund|LMT CGT CGST EGST EGT|1r.Q 20 10 0 10|0121343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434|-2a5Ww.8 2z5ew.8 1a00 1cK0 1cL0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"America/Sitka|PST PWT PPT PDT YST AKST AKDT|80 70 70 70 90 90 80|01203030303030303030303030303030345656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565|-17T20 8x10 iy0 Vo10 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 co0 10q0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/St_Johns|NST NDT NST NDT NWT NPT NDDT|3u.Q 2u.Q 3u 2u 2u 2u 1u|01010101010101010101010101010101010102323232323232324523232323232323232323232323232323232323232323232323232323232323232323232323232323232326232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-28oit.8 14L0 1nB0 1in0 1gm0 Dz0 1JB0 1cL0 1cN0 1cL0 1fB0 19X0 1fB0 19X0 1fB0 19X0 1fB0 19X0 1fB0 1cL0 1cN0 1cL0 1fB0 19X0 1fB0 19X0 1fB0 19X0 1fB0 19X0 1fB0 1cL0 1fB0 19X0 1fB0 19X0 10O0 eKX.8 19X0 1iq0 WL0 1qN0 WL0 1qN0 WL0 1tB0 TX0 1tB0 WL0 1qN0 WL0 1qN0 7UHu itu 1tB0 WL0 1qN0 WL0 1qN0 WL0 1qN0 WL0 1tB0 WL0 1ld0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14n1 1lb0 14p0 1nW0 11C0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zcX Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Swift_Current|LMT MST MDT MWT MPT CST|7b.k 70 60 60 60 60|012134121212121212121215|-2AD4M.E uHdM.E 1in0 UGp0 8x20 ix0 1o10 17b0 1ip0 11z0 1o10 11z0 1o10 11z0 isN0 1cL0 3Cp0 1cL0 1cN0 11z0 1qN0 WL0 pMp0",
			"America/Tegucigalpa|LMT CST CDT|5M.Q 60 50|01212121|-1WGGb.8 2ETcb.8 WL0 1qN0 WL0 GRd0 AL0",
			"America/Thule|LMT AST ADT|4z.8 40 30|012121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2a5To.Q 31NBo.Q 1cL0 1cN0 1cL0 1fB0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Thunder_Bay|CST EST EWT EPT EDT|60 50 40 40 40|0123141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141|-2q5S0 1iaN0 8x40 iv0 XNB0 1cL0 1cN0 1fz0 1cN0 1cL0 3Cp0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Vancouver|PST PDT PWT PPT|80 70 70 70|0102301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-25TO0 1in0 UGp0 8x10 iy0 1o10 17b0 1ip0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Whitehorse|YST YDT YWT YPT YDDT PST PDT|90 80 80 80 70 80 70|0101023040565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565|-25TN0 1in0 1o10 13V0 Ser0 8x00 iz0 LCL0 1fA0 3NA0 vrd0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Winnipeg|CST CDT CWT CPT|60 50 50 50|010101023010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2aIi0 WL0 3ND0 1in0 Jap0 Rb0 aCN0 8x30 iw0 1tB0 11z0 1ip0 11z0 1o10 11z0 1o10 11z0 1rd0 10L0 1op0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 1cL0 1cN0 11z0 6i10 WL0 6i10 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1a00 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1a00 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 14o0 1lc0 14o0 1o00 11A0 1o00 11A0 1o00 14o0 1lc0 14o0 1lc0 14o0 1o00 11A0 1o00 11A0 1o00 14o0 1lc0 14o0 1lc0 14o0 1lc0 14o0 1o00 11A0 1o00 11A0 1o00 14o0 1lc0 14o0 1lc0 14o0 1o00 11A0 1o00 11A0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Yakutat|YST YWT YPT YDT AKST AKDT|90 80 80 80 90 80|01203030303030303030303030303030304545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454|-17T10 8x00 iz0 Vo10 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 cn0 10q0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"America/Yellowknife|zzz MST MWT MPT MDDT MDT|0 70 60 60 50 60|012314151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151|-1pdA0 hix0 8x20 ix0 LCL0 1fA0 zgO0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"Antarctica/Casey|zzz AWST CAST|0 -80 -b0|012121|-2q00 1DjS0 T90 40P0 KL0",
			"Antarctica/Davis|zzz DAVT DAVT|0 -70 -50|01012121|-vyo0 iXt0 alj0 1D7v0 VB0 3Wn0 KN0",
			"Antarctica/DumontDUrville|zzz PMT DDUT|0 -a0 -a0|0102|-U0o0 cfq0 bFm0",
			"Antarctica/Macquarie|AEST AEDT zzz MIST|-a0 -b0 0 -b0|0102010101010101010101010101010101010101010101010101010101010101010101010101010101010101013|-29E80 19X0 4SL0 1ayy0 Lvs0 1cM0 1o00 Rc0 1wo0 Rc0 1wo0 U00 1wo0 LA0 1C00 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 11A0 1qM0 WM0 1qM0 Oo0 1zc0 Oo0 1zc0 Oo0 1wo0 WM0 1tA0 WM0 1tA0 U00 1tA0 U00 1tA0 11A0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 11A0 1o00 1io0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1cM0 1a00 1io0 1cM0 1cM0 1cM0 1cM0 1cM0",
			"Antarctica/Mawson|zzz MAWT MAWT|0 -60 -50|012|-CEo0 2fyk0",
			"Antarctica/McMurdo|NZMT NZST NZST NZDT|-bu -cu -c0 -d0|01020202020202020202020202023232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323|-1GCVu Lz0 1tB0 11zu 1o0u 11zu 1o0u 11zu 1o0u 14nu 1lcu 14nu 1lcu 1lbu 11Au 1nXu 11Au 1nXu 11Au 1nXu 11Au 1nXu 11Au 1qLu WMu 1qLu 11Au 1n1bu IM0 1C00 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1qM0 14o0 1lc0 14o0 1lc0 14o0 1lc0 17c0 1io0 17c0 1io0 17c0 1io0 17c0 1lc0 14o0 1lc0 14o0 1lc0 17c0 1io0 17c0 1io0 17c0 1lc0 14o0 1lc0 14o0 1lc0 17c0 1io0 17c0 1io0 17c0 1io0 17c0 1io0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1io0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00",
			"Antarctica/Palmer|zzz ARST ART ART ARST CLT CLST CLT|0 30 40 30 20 40 30 30|012121212123435656565656565656565656565656565656565656565656565656565656565656567|-cao0 nD0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 jsN0 14N0 11z0 1o10 11z0 1qN0 WL0 1qN0 WL0 1qN0 1cL0 1cN0 11z0 1o10 11z0 1qN0 WL0 1fB0 19X0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 17b0 1ip0 11z0 1ip0 1fz0 1fB0 11z0 1qN0 WL0 1qN0 WL0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 17b0 1ip0 11z0 1o10 19X0 1fB0 1nX0 G10 1EL0 Op0 1zb0 Rd0 1wn0 Rd0 1wn0",
			"Antarctica/Rothera|zzz ROTT|0 30|01|gOo0",
			"Antarctica/Syowa|zzz SYOT|0 -30|01|-vs00",
			"Antarctica/Troll|zzz UTC CEST|0 0 -20|01212121212121212121212121212121212121212121212121212121212121212121|1puo0 hd0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Antarctica/Vostok|zzz VOST|0 -60|01|-tjA0",
			"Arctic/Longyearbyen|CET CEST|-10 -20|010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2awM0 Qm0 W6o0 5pf0 WM0 1fA0 1cM0 1cM0 1cM0 1cM0 wJc0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1qM0 WM0 zpc0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Asia/Aden|LMT AST|-36.Q -30|01|-TvD6.Q",
			"Asia/Almaty|LMT ALMT ALMT ALMST|-57.M -50 -60 -70|0123232323232323232323232323232323232323232323232|-1Pc57.M eUo7.M 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 3Cl0 1cL0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0",
			"Asia/Amman|LMT EET EEST|-2n.I -20 -30|0121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-1yW2n.I 1HiMn.I KL0 1oN0 11b0 1oN0 11b0 1pd0 1dz0 1cp0 11b0 1op0 11b0 fO10 1db0 1e10 1cL0 1cN0 1cL0 1cN0 1fz0 1pd0 10n0 1ld0 14n0 1hB0 15b0 1ip0 19X0 1cN0 1cL0 1cN0 17b0 1ld0 14o0 1lc0 17c0 1io0 17c0 1io0 17c0 1So0 y00 1fc0 1dc0 1co0 1dc0 1cM0 1cM0 1cM0 1o00 11A0 1lc0 17c0 1cM0 1cM0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 4bX0 Dd0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0",
			"Asia/Anadyr|LMT ANAT ANAT ANAST ANAST ANAST ANAT|-bN.U -c0 -d0 -e0 -d0 -c0 -b0|01232414141414141414141561414141414141414141414141414141414141561|-1PcbN.U eUnN.U 23CL0 1db0 1cN0 1dc0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rU0 1cL0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qN0 WM0",
			"Asia/Aqtau|LMT FORT FORT SHET SHET SHEST AQTT AQTST AQTST AQTT|-3l.4 -40 -50 -50 -60 -60 -50 -60 -50 -40|012345353535353535353536767676898989898989898989896|-1Pc3l.4 eUnl.4 1jcL0 JDc0 1cL0 1dc0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2UK0 Fz0 1cL0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cN0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 RW0",
			"Asia/Aqtobe|LMT AKTT AKTT AKTST AKTT AQTT AQTST|-3M.E -40 -50 -60 -60 -50 -60|01234323232323232323232565656565656565656565656565|-1Pc3M.E eUnM.E 23CL0 1db0 1cM0 1dc0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2UK0 Fz0 1cL0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0",
			"Asia/Ashgabat|LMT ASHT ASHT ASHST ASHST TMT TMT|-3R.w -40 -50 -60 -50 -40 -50|012323232323232323232324156|-1Pc3R.w eUnR.w 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 ba0 xC0",
			"Asia/Baghdad|BMT AST ADT|-2V.A -30 -40|012121212121212121212121212121212121212121212121212121|-26BeV.A 2ACnV.A 11b0 1cp0 1dz0 1dd0 1db0 1cN0 1cp0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1de0 1dc0 1dc0 1dc0 1cM0 1dc0 1cM0 1dc0 1cM0 1dc0 1dc0 1dc0 1cM0 1dc0 1cM0 1dc0 1cM0 1dc0 1dc0 1dc0 1cM0 1dc0 1cM0 1dc0 1cM0 1dc0 1dc0 1dc0 1cM0 1dc0 1cM0 1dc0 1cM0 1dc0",
			"Asia/Bahrain|LMT GST AST|-3q.8 -40 -30|012|-21Jfq.8 27BXq.8",
			"Asia/Baku|LMT BAKT BAKT BAKST BAKST AZST AZT AZT AZST|-3j.o -30 -40 -50 -40 -40 -30 -40 -50|0123232323232323232323245657878787878787878787878787878787878787878787878787878787878787878787878787878787878787|-1Pc3j.o 1jUoj.o WCL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 10K0 c30 1cJ0 1cL0 8wu0 1o00 11z0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Asia/Bangkok|BMT ICT|-6G.4 -70|01|-218SG.4",
			"Asia/Beirut|EET EEST|-20 -30|010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-21aq0 1on0 1410 1db0 19B0 1in0 1ip0 WL0 1lQp0 11b0 1oN0 11b0 1oN0 11b0 1pd0 11b0 1oN0 11b0 q6N0 En0 1oN0 11b0 1oN0 11b0 1oN0 11b0 1pd0 11b0 1oN0 11b0 1op0 11b0 dA10 17b0 1iN0 17b0 1iN0 17b0 1iN0 17b0 1vB0 SL0 1mp0 13z0 1iN0 17b0 1iN0 17b0 1jd0 12n0 1a10 1cL0 1cN0 1cL0 1cN0 1cL0 1fB0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0",
			"Asia/Bishkek|LMT FRUT FRUT FRUST FRUST KGT KGST KGT|-4W.o -50 -60 -70 -60 -50 -60 -60|01232323232323232323232456565656565656565656565656567|-1Pc4W.o eUnW.o 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 11c0 1tX0 17b0 1ip0 17b0 1ip0 17b0 1ip0 17b0 1ip0 19X0 1cPu 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 T8u",
			"Asia/Brunei|LMT BNT BNT|-7D.E -7u -80|012|-1KITD.E gDc9.E",
			"Asia/Calcutta|HMT BURT IST IST|-5R.k -6u -5u -6u|01232|-18LFR.k 1unn.k HB0 7zX0",
			"Asia/Chita|LMT YAKT YAKT YAKST YAKST YAKT IRKT|-7x.Q -80 -90 -a0 -90 -a0 -80|012323232323232323232324123232323232323232323232323232323232323256|-21Q7x.Q pAnx.Q 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rU0 1cL0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0",
			"Asia/Choibalsan|LMT ULAT ULAT CHOST CHOT CHOT CHOST|-7C -70 -80 -a0 -90 -80 -90|0123434343434343434343434343434343434343434343456565656565656565656565656565656565656565656565|-2APHC 2UkoC cKn0 1da0 1dd0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1fB0 1cL0 1cN0 1cL0 1cN0 1cL0 6hD0 11z0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 3Db0 h1f0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0",
			"Asia/Chongqing|CST CDT|-80 -90|01010101010101010|-1c1I0 LX0 16p0 1jz0 1Myp0 Rb0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0",
			"Asia/Colombo|MMT IST IHST IST LKT LKT|-5j.w -5u -60 -6u -6u -60|01231451|-2zOtj.w 1rFbN.w 1zzu 7Apu 23dz0 11zu n3cu",
			"Asia/Dacca|HMT BURT IST DACT BDT BDST|-5R.k -6u -5u -60 -60 -70|01213454|-18LFR.k 1unn.k HB0 m6n0 LqMu 1x6n0 1i00",
			"Asia/Damascus|LMT EET EEST|-2p.c -20 -30|01212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-21Jep.c Hep.c 17b0 1ip0 17b0 1ip0 17b0 1ip0 19X0 1xRB0 11X0 1oN0 10L0 1pB0 11b0 1oN0 10L0 1mp0 13X0 1oN0 11b0 1pd0 11b0 1oN0 11b0 1oN0 11b0 1oN0 11b0 1pd0 11b0 1oN0 11b0 1oN0 11b0 1oN0 11b0 1pd0 11b0 1oN0 Nb0 1AN0 Nb0 bcp0 19X0 1gp0 19X0 3ld0 1xX0 Vd0 1Bz0 Sp0 1vX0 10p0 1dz0 1cN0 1cL0 1db0 1db0 1g10 1an0 1ap0 1db0 1fd0 1db0 1cN0 1db0 1dd0 1db0 1cp0 1dz0 1c10 1dX0 1cN0 1db0 1dd0 1db0 1cN0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1db0 1cN0 1db0 1cN0 19z0 1fB0 1qL0 11B0 1on0 Wp0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0",
			"Asia/Dili|LMT TLT JST TLT WITA|-8m.k -80 -90 -90 -80|012343|-2le8m.k 1dnXm.k 8HA0 1ew00 Xld0",
			"Asia/Dubai|LMT GST|-3F.c -40|01|-21JfF.c",
			"Asia/Dushanbe|LMT DUST DUST DUSST DUSST TJT|-4z.c -50 -60 -70 -60 -50|0123232323232323232323245|-1Pc4z.c eUnz.c 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 14N0",
			"Asia/Gaza|EET EET EEST IST IDT|-20 -30 -30 -20 -30|010101010102020202020202020202023434343434343434343434343430202020202020202020202020202020202020202020202020202020202020202020202020202020202020|-1c2q0 5Rb0 10r0 1px0 10N0 1pz0 16p0 1jB0 16p0 1jx0 pBd0 Vz0 1oN0 11b0 1oO0 10N0 1pz0 10N0 1pb0 10N0 1pb0 10N0 1pb0 10N0 1pz0 10N0 1pb0 10N0 1pb0 11d0 1oL0 dW0 hfB0 Db0 1fB0 Rb0 npB0 11z0 1C10 IL0 1s10 10n0 1o10 WL0 1zd0 On0 1ld0 11z0 1o10 14n0 1o10 14n0 1nd0 12n0 1nd0 Xz0 1q10 12n0 M10 C00 17c0 1io0 17c0 1io0 17c0 1o00 1cL0 1fB0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 17c0 1io0 18N0 1bz0 19z0 1gp0 1610 1iL0 11z0 1o10 14o0 1lA1 SKX 1xd1 MKX 1AN0 1a00 1fA0 1cL0 1cN0 1nX0 1210 1nz0 1210 1nz0 14N0 1nz0 1210 1nz0 1210 1nz0 1210 1nz0 1210 1nz0 1210 1nz0 14N0 1nz0 1210 1nz0 1210 1nz0 1210 1nz0 1210 1nz0 14N0 1nz0 1210 1nz0 1210 1nz0 1210 1nz0 1210 1nz0 1210 1nz0 14N0 1nz0 1210 1nz0 1210 1nz0 1210 1nz0",
			"Asia/Hebron|EET EET EEST IST IDT|-20 -30 -30 -20 -30|01010101010202020202020202020202343434343434343434343434343020202020202020202020202020202020202020202020202020202020202020202020202020202020202020|-1c2q0 5Rb0 10r0 1px0 10N0 1pz0 16p0 1jB0 16p0 1jx0 pBd0 Vz0 1oN0 11b0 1oO0 10N0 1pz0 10N0 1pb0 10N0 1pb0 10N0 1pb0 10N0 1pz0 10N0 1pb0 10N0 1pb0 11d0 1oL0 dW0 hfB0 Db0 1fB0 Rb0 npB0 11z0 1C10 IL0 1s10 10n0 1o10 WL0 1zd0 On0 1ld0 11z0 1o10 14n0 1o10 14n0 1nd0 12n0 1nd0 Xz0 1q10 12n0 M10 C00 17c0 1io0 17c0 1io0 17c0 1o00 1cL0 1fB0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 17c0 1io0 18N0 1bz0 19z0 1gp0 1610 1iL0 12L0 1mN0 14o0 1lc0 Tb0 1xd1 MKX bB0 cn0 1cN0 1a00 1fA0 1cL0 1cN0 1nX0 1210 1nz0 1210 1nz0 14N0 1nz0 1210 1nz0 1210 1nz0 1210 1nz0 1210 1nz0 1210 1nz0 14N0 1nz0 1210 1nz0 1210 1nz0 1210 1nz0 1210 1nz0 14N0 1nz0 1210 1nz0 1210 1nz0 1210 1nz0 1210 1nz0 1210 1nz0 14N0 1nz0 1210 1nz0 1210 1nz0 1210 1nz0",
			"Asia/Ho_Chi_Minh|LMT PLMT ICT IDT JST|-76.E -76.u -70 -80 -90|0123423232|-2yC76.E bK00.a 1h7b6.u 5lz0 18o0 3Oq0 k5b0 aW00 BAM0",
			"Asia/Hong_Kong|LMT HKT HKST JST|-7A.G -80 -90 -90|0121312121212121212121212121212121212121212121212121212121212121212121|-2CFHA.G 1sEP6.G 1cL0 ylu 93X0 1qQu 1tX0 Rd0 1In0 NB0 1cL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1kL0 14N0 1nX0 U10 1tz0 U10 1wn0 Rd0 1wn0 U10 1tz0 U10 1tz0 U10 1tz0 U10 1wn0 Rd0 1wn0 Rd0 1wn0 U10 1tz0 U10 1tz0 17d0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 s10 1Vz0 1cN0 1cL0 1cN0 1cL0 6fd0 14n0",
			"Asia/Hovd|LMT HOVT HOVT HOVST|-66.A -60 -70 -80|012323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-2APG6.A 2Uko6.A cKn0 1db0 1dd0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1fB0 1cL0 1cN0 1cL0 1cN0 1cL0 6hD0 11z0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 kEp0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0",
			"Asia/Irkutsk|IMT IRKT IRKT IRKST IRKST IRKT|-6V.5 -70 -80 -90 -80 -90|012323232323232323232324123232323232323232323232323232323232323252|-21zGV.5 pjXV.5 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rU0 1cL0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0",
			"Asia/Istanbul|IMT EET EEST TRST TRT|-1U.U -20 -30 -40 -30|012121212121212121212121212121212121212121212121212121234343434342121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2ogNU.U dzzU.U 11b0 8tB0 1on0 1410 1db0 19B0 1in0 3Rd0 Un0 1oN0 11b0 zSp0 CL0 mN0 1Vz0 1gN0 1pz0 5Rd0 1fz0 1yp0 ML0 1kp0 17b0 1ip0 17b0 1fB0 19X0 1jB0 18L0 1ip0 17z0 qdd0 xX0 3S10 Tz0 dA10 11z0 1o10 11z0 1qN0 11z0 1ze0 11B0 WM0 1qO0 WI0 1nX0 1rB0 10L0 11B0 1in0 17d0 1in0 2pX0 19E0 1fU0 16Q0 1iI0 16Q0 1iI0 1Vd0 pb0 3Kp0 14o0 1df0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cL0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WO0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 Xc0 1qo0 WM0 1qM0 11A0 1o00 1200 1nA0 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Asia/Jakarta|BMT JAVT WIB JST WIB WIB|-77.c -7k -7u -90 -80 -70|01232425|-1Q0Tk luM0 mPzO 8vWu 6kpu 4PXu xhcu",
			"Asia/Jayapura|LMT WIT ACST|-9m.M -90 -9u|0121|-1uu9m.M sMMm.M L4nu",
			"Asia/Jerusalem|JMT IST IDT IDDT|-2k.E -20 -30 -40|01212121212132121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-26Bek.E SyMk.E 5Rb0 10r0 1px0 10N0 1pz0 16p0 1jB0 16p0 1jx0 3LB0 Em0 or0 1cn0 1dB0 16n0 10O0 1ja0 1tC0 14o0 1cM0 1a00 11A0 1Na0 An0 1MP0 AJ0 1Kp0 LC0 1oo0 Wl0 EQN0 Db0 1fB0 Rb0 npB0 11z0 1C10 IL0 1s10 10n0 1o10 WL0 1zd0 On0 1ld0 11z0 1o10 14n0 1o10 14n0 1nd0 12n0 1nd0 Xz0 1q10 12n0 1hB0 1dX0 1ep0 1aL0 1eN0 17X0 1nf0 11z0 1tB0 19W0 1e10 17b0 1ep0 1gL0 18N0 1fz0 1eN0 17b0 1gq0 1gn0 19d0 1dz0 1c10 17X0 1hB0 1gn0 19d0 1dz0 1c10 17X0 1kp0 1dz0 1c10 1aL0 1eN0 1oL0 10N0 1oL0 10N0 1oL0 10N0 1rz0 W10 1rz0 W10 1rz0 10N0 1oL0 10N0 1oL0 10N0 1rz0 W10 1rz0 W10 1rz0 10N0 1oL0 10N0 1oL0 10N0 1oL0 10N0 1rz0 W10 1rz0 W10 1rz0 10N0 1oL0 10N0 1oL0 10N0 1rz0 W10 1rz0 W10 1rz0 W10 1rz0 10N0 1oL0 10N0 1oL0",
			"Asia/Kabul|AFT AFT|-40 -4u|01|-10Qs0",
			"Asia/Kamchatka|LMT PETT PETT PETST PETST|-ay.A -b0 -c0 -d0 -c0|01232323232323232323232412323232323232323232323232323232323232412|-1SLKy.A ivXy.A 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rU0 1cL0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qN0 WM0",
			"Asia/Karachi|LMT IST IST KART PKT PKST|-4s.c -5u -6u -50 -50 -60|012134545454|-2xoss.c 1qOKW.c 7zX0 eup0 LqMu 1fy01 1cL0 dK0X 11b0 1610 1jX0",
			"Asia/Kashgar|LMT XJT|-5O.k -60|01|-1GgtO.k",
			"Asia/Kathmandu|LMT IST NPT|-5F.g -5u -5J|012|-21JhF.g 2EGMb.g",
			"Asia/Khandyga|LMT YAKT YAKT YAKST YAKST VLAT VLAST VLAT YAKT|-92.d -80 -90 -a0 -90 -a0 -b0 -b0 -a0|01232323232323232323232412323232323232323232323232565656565656565782|-21Q92.d pAp2.d 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rU0 1cL0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 qK0 yN0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 17V0 7zD0",
			"Asia/Krasnoyarsk|LMT KRAT KRAT KRAST KRAST KRAT|-6b.q -60 -70 -80 -70 -80|012323232323232323232324123232323232323232323232323232323232323252|-21Hib.q prAb.q 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rU0 1cL0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0",
			"Asia/Kuala_Lumpur|SMT MALT MALST MALT MALT JST MYT|-6T.p -70 -7k -7k -7u -90 -80|01234546|-2Bg6T.p 17anT.p 7hXE dM00 17bO 8Fyu 1so1u",
			"Asia/Kuching|LMT BORT BORT BORTST JST MYT|-7l.k -7u -80 -8k -90 -80|01232323232323232425|-1KITl.k gDbP.k 6ynu AnE 1O0k AnE 1NAk AnE 1NAk AnE 1NAk AnE 1O0k AnE 1NAk AnE pAk 8Fz0 1so10",
			"Asia/Macao|LMT MOT MOST CST|-7y.k -80 -90 -80|0121212121212121212121212121212121212121213|-2le7y.k 1XO34.k 1wn0 Rd0 1wn0 R9u 1wqu U10 1tz0 TVu 1tz0 17gu 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cJu 1cL0 1cN0 1fz0 1cN0 1cOu 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cJu 1cL0 1cN0 1fz0 1cN0 1cL0 KEp0",
			"Asia/Magadan|LMT MAGT MAGT MAGST MAGST MAGT|-a3.c -a0 -b0 -c0 -b0 -c0|012323232323232323232324123232323232323232323232323232323232323251|-1Pca3.c eUo3.c 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rU0 1cL0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0",
			"Asia/Makassar|LMT MMT WITA JST|-7V.A -7V.A -80 -90|01232|-21JjV.A vfc0 myLV.A 8ML0",
			"Asia/Manila|PHT PHST JST|-80 -90 -90|010201010|-1kJI0 AL0 cK10 65X0 mXB0 vX0 VK10 1db0",
			"Asia/Nicosia|LMT EET EEST|-2d.s -20 -30|01212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-1Vc2d.s 2a3cd.s 1cL0 1qp0 Xz0 19B0 19X0 1fB0 1db0 1cp0 1cL0 1fB0 19X0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1fB0 1cL0 1cN0 1cL0 1cN0 1o30 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Asia/Novokuznetsk|LMT KRAT KRAT KRAST KRAST NOVST NOVT NOVT|-5M.M -60 -70 -80 -70 -70 -60 -70|012323232323232323232324123232323232323232323232323232323232325672|-1PctM.M eULM.M 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rU0 1cL0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qN0 WM0 8Hz0",
			"Asia/Novosibirsk|LMT NOVT NOVT NOVST NOVST|-5v.E -60 -70 -80 -70|0123232323232323232323241232341414141414141414141414141414141414121|-21Qnv.E pAFv.E 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rU0 1cL0 1cQ0 ml0 Os0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0",
			"Asia/Omsk|LMT OMST OMST OMSST OMSST OMST|-4R.u -50 -60 -70 -60 -70|012323232323232323232324123232323232323232323232323232323232323252|-224sR.u pMLR.u 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rU0 1cL0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0",
			"Asia/Oral|LMT URAT URAT URAST URAT URAST ORAT ORAST ORAT|-3p.o -40 -50 -60 -60 -50 -40 -50 -50|012343232323232323251516767676767676767676767676768|-1Pc3p.o eUnp.o 23CL0 1db0 1cM0 1dc0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cN0 1cM0 1fA0 2UK0 Fz0 1cL0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 RW0",
			"Asia/Pontianak|LMT PMT WIB JST WIB WITA WIB|-7h.k -7h.k -7u -90 -80 -80 -70|012324256|-2ua7h.k XE00 munL.k 8Rau 6kpu 4PXu xhcu Wqnu",
			"Asia/Pyongyang|LMT KST JCST JST KST|-8n -8u -90 -90 -90|01234|-2um8n 97XR 12FXu jdA0",
			"Asia/Qyzylorda|LMT KIZT KIZT KIZST KIZT QYZT QYZT QYZST|-4l.Q -40 -50 -60 -60 -50 -60 -70|012343232323232323232325676767676767676767676767676|-1Pc4l.Q eUol.Q 23CL0 1db0 1cM0 1dc0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2UK0 dC0 rU0 1cL0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0",
			"Asia/Rangoon|RMT BURT JST MMT|-6o.E -6u -90 -6u|0123|-21Jio.E SmnS.E 7j9u",
			"Asia/Sakhalin|LMT JCST JST SAKT SAKST SAKST SAKT|-9u.M -90 -90 -b0 -c0 -b0 -a0|0123434343434343434343435634343434343565656565656565656565656565636|-2AGVu.M 1iaMu.M je00 1qFa0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rU0 1cL0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o10 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0",
			"Asia/Samarkand|LMT SAMT SAMT SAMST TAST UZST UZT|-4r.R -40 -50 -60 -60 -60 -50|01234323232323232323232356|-1Pc4r.R eUor.R 23CL0 1db0 1cM0 1dc0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 11x0 bf0",
			"Asia/Seoul|LMT KST JCST JST KST KDT KDT|-8r.Q -8u -90 -90 -90 -9u -a0|01234151515151515146464|-2um8r.Q 97XV.Q 12FXu jjA0 kKo0 2I0u OL0 1FB0 Rb0 1qN0 TX0 1tB0 TX0 1tB0 TX0 1tB0 TX0 2ap0 12FBu 11A0 1o00 11A0",
			"Asia/Singapore|SMT MALT MALST MALT MALT JST SGT SGT|-6T.p -70 -7k -7k -7u -90 -7u -80|012345467|-2Bg6T.p 17anT.p 7hXE dM00 17bO 8Fyu Mspu DTA0",
			"Asia/Srednekolymsk|LMT MAGT MAGT MAGST MAGST MAGT SRET|-ae.Q -a0 -b0 -c0 -b0 -c0 -b0|012323232323232323232324123232323232323232323232323232323232323256|-1Pcae.Q eUoe.Q 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rU0 1cL0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0",
			"Asia/Taipei|JWST JST CST CDT|-80 -90 -80 -90|01232323232323232323232323232323232323232|-1iw80 joM0 1yo0 Tz0 1ip0 1jX0 1cN0 11b0 1oN0 11b0 1oN0 11b0 1oN0 11b0 10N0 1BX0 10p0 1pz0 10p0 1pz0 10p0 1db0 1dd0 1db0 1cN0 1db0 1cN0 1db0 1cN0 1db0 1BB0 ML0 1Bd0 ML0 uq10 1db0 1cN0 1db0 97B0 AL0",
			"Asia/Tashkent|LMT TAST TAST TASST TASST UZST UZT|-4B.b -50 -60 -70 -60 -60 -50|01232323232323232323232456|-1Pc4B.b eUnB.b 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 11y0 bf0",
			"Asia/Tbilisi|TBMT TBIT TBIT TBIST TBIST GEST GET GET GEST|-2X.b -30 -40 -50 -40 -40 -30 -40 -50|0123232323232323232323245656565787878787878787878567|-1Pc2X.b 1jUnX.b WCL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 3y0 19f0 1cK0 1cL0 1cN0 1cL0 1cN0 1cL0 1cM0 1cL0 1fB0 3Nz0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 An0 Os0 WM0",
			"Asia/Tehran|LMT TMT IRST IRST IRDT IRDT|-3p.I -3p.I -3u -40 -50 -4u|01234325252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252|-2btDp.I 1d3c0 1huLT.I TXu 1pz0 sN0 vAu 1cL0 1dB0 1en0 pNB0 UL0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 64p0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0",
			"Asia/Thimbu|LMT IST BTT|-5W.A -5u -60|012|-Su5W.A 1BGMs.A",
			"Asia/Tokyo|JCST JST JDT|-90 -90 -a0|0121212121|-1iw90 pKq0 QL0 1lB0 13X0 1zB0 NX0 1zB0 NX0",
			"Asia/Ulaanbaatar|LMT ULAT ULAT ULAST|-77.w -70 -80 -90|012323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-2APH7.w 2Uko7.w cKn0 1db0 1dd0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1fB0 1cL0 1cN0 1cL0 1cN0 1cL0 6hD0 11z0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 kEp0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0",
			"Asia/Ust-Nera|LMT YAKT YAKT MAGST MAGT MAGST MAGT MAGT VLAT VLAT|-9w.S -80 -90 -c0 -b0 -b0 -a0 -c0 -b0 -a0|0123434343434343434343456434343434343434343434343434343434343434789|-21Q9w.S pApw.S 23CL0 1d90 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rU0 1cL0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 17V0 7zD0",
			"Asia/Vladivostok|LMT VLAT VLAT VLAST VLAST VLAT|-8L.v -90 -a0 -b0 -a0 -b0|012323232323232323232324123232323232323232323232323232323232323252|-1SJIL.v itXL.v 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rU0 1cL0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0",
			"Asia/Yakutsk|LMT YAKT YAKT YAKST YAKST YAKT|-8C.W -80 -90 -a0 -90 -a0|012323232323232323232324123232323232323232323232323232323232323252|-21Q8C.W pAoC.W 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rU0 1cL0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0",
			"Asia/Yekaterinburg|LMT PMT SVET SVET SVEST SVEST YEKT YEKST YEKT|-42.x -3J.5 -40 -50 -60 -50 -50 -60 -60|0123434343434343434343435267676767676767676767676767676767676767686|-2ag42.x 7mQh.s qBvJ.5 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rU0 1cL0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0",
			"Asia/Yerevan|LMT YERT YERT YERST YERST AMST AMT AMT AMST|-2W -30 -40 -50 -40 -40 -30 -40 -50|0123232323232323232323245656565657878787878787878787878787878787|-1Pc2W 1jUnW WCL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1am0 2r0 1cJ0 1cL0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cM0 3Fb0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0",
			"Atlantic/Azores|HMT AZOT AZOST AZOMT AZOT AZOST WET|1S.w 20 10 0 10 0 0|01212121212121212121212121212121212121212121232123212321232121212121212121212121212121212121212121454545454545454545454545454545456545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454|-2ldW5.s aPX5.s Sp0 LX0 1vc0 Tc0 1uM0 SM0 1vc0 Tc0 1vc0 SM0 1vc0 6600 1co0 3E00 17c0 1fA0 1a00 1io0 1a00 1io0 17c0 3I00 17c0 1cM0 1cM0 3Fc0 1cM0 1a00 1fA0 1io0 17c0 1cM0 1cM0 1a00 1fA0 1io0 1qM0 Dc0 1tA0 1cM0 1dc0 1400 gL0 IM0 s10 U00 dX0 Rc0 pd0 Rc0 gL0 Oo0 pd0 Rc0 gL0 Oo0 pd0 14o0 1cM0 1cP0 1cM0 1cM0 1cM0 1cM0 1cM0 3Co0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 qIl0 1cM0 1fA0 1cM0 1cM0 1cN0 1cL0 1cN0 1cM0 1cM0 1cM0 1cM0 1cN0 1cL0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cL0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Atlantic/Bermuda|LMT AST ADT|4j.i 40 30|0121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-1BnRE.G 1LTbE.G 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"Atlantic/Canary|LMT CANT WET WEST|11.A 10 0 -10|01232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-1UtaW.o XPAW.o 1lAK0 1a10 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Atlantic/Cape_Verde|LMT CVT CVST CVT|1y.4 20 10 10|01213|-2xomp.U 1qOMp.U 7zX0 1djf0",
			"Atlantic/Faeroe|LMT WET WEST|r.4 0 -10|01212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2uSnw.U 2Wgow.U 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Atlantic/Madeira|FMT MADT MADST MADMT WET WEST|17.A 10 0 -10 0 -10|01212121212121212121212121212121212121212121232123212321232121212121212121212121212121212121212121454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454|-2ldWQ.o aPWQ.o Sp0 LX0 1vc0 Tc0 1uM0 SM0 1vc0 Tc0 1vc0 SM0 1vc0 6600 1co0 3E00 17c0 1fA0 1a00 1io0 1a00 1io0 17c0 3I00 17c0 1cM0 1cM0 3Fc0 1cM0 1a00 1fA0 1io0 17c0 1cM0 1cM0 1a00 1fA0 1io0 1qM0 Dc0 1tA0 1cM0 1dc0 1400 gL0 IM0 s10 U00 dX0 Rc0 pd0 Rc0 gL0 Oo0 pd0 Rc0 gL0 Oo0 pd0 14o0 1cM0 1cP0 1cM0 1cM0 1cM0 1cM0 1cM0 3Co0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 qIl0 1cM0 1fA0 1cM0 1cM0 1cN0 1cL0 1cN0 1cM0 1cM0 1cM0 1cM0 1cN0 1cL0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Atlantic/Reykjavik|LMT IST ISST GMT|1s 10 0 0|012121212121212121212121212121212121212121212121212121212121212121213|-2uWmw mfaw 1Bd0 ML0 1LB0 Cn0 1LB0 3fX0 C10 HrX0 1cO0 LB0 1EL0 LA0 1C00 Oo0 1wo0 Rc0 1wo0 Rc0 1wo0 Rc0 1zc0 Oo0 1zc0 14o0 1lc0 14o0 1lc0 14o0 1o00 11A0 1lc0 14o0 1o00 14o0 1lc0 14o0 1lc0 14o0 1lc0 14o0 1lc0 14o0 1o00 14o0 1lc0 14o0 1lc0 14o0 1lc0 14o0 1lc0 14o0 1lc0 14o0 1o00 14o0 1lc0 14o0 1lc0 14o0 1lc0 14o0 1lc0 14o0 1o00 14o0",
			"Atlantic/South_Georgia|GST|20|0|",
			"Atlantic/Stanley|SMT FKT FKST FKT FKST|3P.o 40 30 30 20|0121212121212134343212121212121212121212121212121212121212121212121212|-2kJw8.A 12bA8.A 19X0 1fB0 19X0 1ip0 19X0 1fB0 19X0 1fB0 19X0 1fB0 Cn0 1Cc10 WL0 1qL0 U10 1tz0 U10 1qM0 WN0 1qL0 WN0 1qL0 WN0 1qL0 WN0 1tz0 U10 1tz0 WN0 1qL0 WN0 1qL0 WN0 1qL0 WN0 1qL0 WN0 1tz0 WN0 1qL0 WN0 1qL0 WN0 1qL0 WN0 1qL0 WN0 1qN0 U10 1wn0 Rd0 1wn0 U10 1tz0 U10 1tz0 U10 1tz0 U10 1tz0 U10 1wn0 U10 1tz0 U10 1tz0 U10",
			"Australia/ACT|AEST AEDT|-a0 -b0|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101|-293lX xcX 10jd0 yL0 1cN0 1cL0 1fB0 19X0 17c10 LA0 1C00 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 14o0 1o00 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 U00 1qM0 WM0 1tA0 WM0 1tA0 U00 1tA0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 11A0 1o00 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 11A0 1o00 WM0 1qM0 14o0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0",
			"Australia/Adelaide|ACST ACDT|-9u -au|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101|-293lt xcX 10jd0 yL0 1cN0 1cL0 1fB0 19X0 17c10 LA0 1C00 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 U00 1qM0 WM0 1tA0 WM0 1tA0 U00 1tA0 U00 1tA0 Oo0 1zc0 WM0 1qM0 Rc0 1zc0 U00 1tA0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 11A0 1o00 WM0 1qM0 14o0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0",
			"Australia/Brisbane|AEST AEDT|-a0 -b0|01010101010101010|-293lX xcX 10jd0 yL0 1cN0 1cL0 1fB0 19X0 17c10 LA0 H1A0 Oo0 1zc0 Oo0 1zc0 Oo0",
			"Australia/Broken_Hill|ACST ACDT|-9u -au|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101|-293lt xcX 10jd0 yL0 1cN0 1cL0 1fB0 19X0 17c10 LA0 1C00 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 14o0 1o00 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 U00 1qM0 WM0 1tA0 WM0 1tA0 U00 1tA0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 11A0 1o00 WM0 1qM0 14o0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0",
			"Australia/Currie|AEST AEDT|-a0 -b0|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101|-29E80 19X0 10jd0 yL0 1cN0 1cL0 1fB0 19X0 17c10 LA0 1C00 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 11A0 1qM0 WM0 1qM0 Oo0 1zc0 Oo0 1zc0 Oo0 1wo0 WM0 1tA0 WM0 1tA0 U00 1tA0 U00 1tA0 11A0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 11A0 1o00 1io0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1cM0 1a00 1io0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0",
			"Australia/Darwin|ACST ACDT|-9u -au|010101010|-293lt xcX 10jd0 yL0 1cN0 1cL0 1fB0 19X0",
			"Australia/Eucla|ACWST ACWDT|-8J -9J|0101010101010101010|-293kI xcX 10jd0 yL0 1cN0 1cL0 1gSp0 Oo0 l5A0 Oo0 iJA0 G00 zU00 IM0 1qM0 11A0 1o00 11A0",
			"Australia/Hobart|AEST AEDT|-a0 -b0|010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101|-29E80 19X0 10jd0 yL0 1cN0 1cL0 1fB0 19X0 VfB0 1cM0 1o00 Rc0 1wo0 Rc0 1wo0 U00 1wo0 LA0 1C00 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 11A0 1qM0 WM0 1qM0 Oo0 1zc0 Oo0 1zc0 Oo0 1wo0 WM0 1tA0 WM0 1tA0 U00 1tA0 U00 1tA0 11A0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 11A0 1o00 1io0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1cM0 1a00 1io0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0",
			"Australia/LHI|AEST LHST LHDT LHDT|-a0 -au -bu -b0|0121212121313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313|raC0 1zdu Rb0 1zd0 On0 1zd0 On0 1zd0 On0 1zd0 TXu 1qMu WLu 1tAu WLu 1tAu TXu 1tAu Onu 1zcu Onu 1zcu Onu 1zcu Rbu 1zcu Onu 1zcu Onu 1zcu 11zu 1o0u 11zu 1o0u 11zu 1o0u 11zu 1qMu WLu 11Au 1nXu 1qMu 11zu 1o0u 11zu 1o0u 11zu 1qMu WLu 1qMu 11zu 1o0u WLu 1qMu 14nu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1fAu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1fAu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1fzu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1fAu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1fAu 1cLu 1cMu 1cLu 1cMu",
			"Australia/Lindeman|AEST AEDT|-a0 -b0|010101010101010101010|-293lX xcX 10jd0 yL0 1cN0 1cL0 1fB0 19X0 17c10 LA0 H1A0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0",
			"Australia/Melbourne|AEST AEDT|-a0 -b0|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101|-293lX xcX 10jd0 yL0 1cN0 1cL0 1fB0 19X0 17c10 LA0 1C00 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 U00 1qM0 WM0 1qM0 11A0 1tA0 U00 1tA0 U00 1tA0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 11A0 1o00 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 11A0 1o00 WM0 1qM0 14o0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0",
			"Australia/Perth|AWST AWDT|-80 -90|0101010101010101010|-293jX xcX 10jd0 yL0 1cN0 1cL0 1gSp0 Oo0 l5A0 Oo0 iJA0 G00 zU00 IM0 1qM0 11A0 1o00 11A0",
			"CET|CET CEST|-10 -20|01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2aFe0 11d0 1iO0 11A0 1o00 11A0 Qrc0 6i00 WM0 1fA0 1cM0 1cM0 1cM0 16M0 1gMM0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"CST6CDT|CST CDT CWT CPT|60 50 50 50|010102301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 QwN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"Chile/EasterIsland|EMT EAST EASST EAST EASST EAST|7h.s 70 60 60 50 50|012121212121212121212121212123434343434343434343434343434343434343434343434343434343434343434345|-1uSgG.w 1s4IG.w WL0 1zd0 On0 1ip0 11z0 1o10 11z0 1qN0 WL0 1ld0 14n0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 WL0 1qN0 1cL0 1cN0 11z0 1o10 11z0 1qN0 WL0 1fB0 19X0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 17b0 1ip0 11z0 1ip0 1fz0 1fB0 11z0 1qN0 WL0 1qN0 WL0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 17b0 1ip0 11z0 1o10 19X0 1fB0 1nX0 G10 1EL0 Op0 1zb0 Rd0 1wn0 Rd0 1wn0",
			"EET|EET EEST|-20 -30|010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|hDB0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"EST|EST|50|0|",
			"EST5EDT|EST EDT EWT EPT|50 40 40 40|010102301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261t0 1nX0 11B0 1nX0 SgN0 8x40 iv0 QwN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"Eire|DMT IST GMT BST IST|p.l -y.D 0 -10 -10|01232323232324242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242|-2ax9y.D Rc0 1fzy.D 14M0 1fc0 1g00 1co0 1dc0 1co0 1oo0 1400 1dc0 19A0 1io0 1io0 WM0 1o00 14o0 1o00 17c0 1io0 17c0 1fA0 1a00 1lc0 17c0 1io0 17c0 1fA0 1a00 1io0 17c0 1io0 17c0 1fA0 1cM0 1io0 17c0 1fA0 1a00 1io0 17c0 1io0 17c0 1fA0 1a00 1io0 1qM0 Dc0 g5X0 14p0 1wn0 17d0 1io0 11A0 1o00 17c0 1fA0 1a00 1fA0 1cM0 1fA0 1a00 17c0 1fA0 1a00 1io0 17c0 1lc0 17c0 1fA0 1a00 1io0 17c0 1io0 17c0 1fA0 1a00 1a00 1qM0 WM0 1qM0 11A0 1o00 WM0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1tA0 IM0 90o0 U00 1tA0 U00 1tA0 U00 1tA0 U00 1tA0 WM0 1qM0 WM0 1qM0 WM0 1tA0 U00 1tA0 U00 1tA0 11z0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 14o0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Etc/GMT+0|GMT|0|0|",
			"Etc/GMT+1|GMT+1|10|0|",
			"Etc/GMT+10|GMT+10|a0|0|",
			"Etc/GMT+11|GMT+11|b0|0|",
			"Etc/GMT+12|GMT+12|c0|0|",
			"Etc/GMT+2|GMT+2|20|0|",
			"Etc/GMT+3|GMT+3|30|0|",
			"Etc/GMT+4|GMT+4|40|0|",
			"Etc/GMT+5|GMT+5|50|0|",
			"Etc/GMT+6|GMT+6|60|0|",
			"Etc/GMT+7|GMT+7|70|0|",
			"Etc/GMT+8|GMT+8|80|0|",
			"Etc/GMT+9|GMT+9|90|0|",
			"Etc/GMT-1|GMT-1|-10|0|",
			"Etc/GMT-10|GMT-10|-a0|0|",
			"Etc/GMT-11|GMT-11|-b0|0|",
			"Etc/GMT-12|GMT-12|-c0|0|",
			"Etc/GMT-13|GMT-13|-d0|0|",
			"Etc/GMT-14|GMT-14|-e0|0|",
			"Etc/GMT-2|GMT-2|-20|0|",
			"Etc/GMT-3|GMT-3|-30|0|",
			"Etc/GMT-4|GMT-4|-40|0|",
			"Etc/GMT-5|GMT-5|-50|0|",
			"Etc/GMT-6|GMT-6|-60|0|",
			"Etc/GMT-7|GMT-7|-70|0|",
			"Etc/GMT-8|GMT-8|-80|0|",
			"Etc/GMT-9|GMT-9|-90|0|",
			"Etc/UCT|UCT|0|0|",
			"Etc/UTC|UTC|0|0|",
			"Europe/Amsterdam|AMT NST NEST NET CEST CET|-j.w -1j.w -1k -k -20 -10|010101010101010101010101010101010101010101012323234545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545|-2aFcj.w 11b0 1iP0 11A0 1io0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1co0 1io0 1yo0 Pc0 1a00 1fA0 1Bc0 Mo0 1tc0 Uo0 1tA0 U00 1uo0 W00 1s00 VA0 1so0 Vc0 1sM0 UM0 1wo0 Rc0 1u00 Wo0 1rA0 W00 1s00 VA0 1sM0 UM0 1w00 fV0 BCX.w 1tA0 U00 1u00 Wo0 1sm0 601k WM0 1fA0 1cM0 1cM0 1cM0 16M0 1gMM0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Andorra|WET CET CEST|0 -10 -20|012121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-UBA0 1xIN0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Athens|AMT EET EEST CEST CET|-1y.Q -20 -30 -20 -10|012123434121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2a61x.Q CNbx.Q mn0 kU10 9b0 3Es0 Xa0 1fb0 1dd0 k3X0 Nz0 SCp0 1vc0 SO0 1cM0 1a00 1ao0 1fc0 1a10 1fG0 1cg0 1dX0 1bX0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Belfast|GMT BST BDST|0 -10 -20|0101010101010101010101010101010101010101010101010121212121210101210101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2axa0 Rc0 1fA0 14M0 1fc0 1g00 1co0 1dc0 1co0 1oo0 1400 1dc0 19A0 1io0 1io0 WM0 1o00 14o0 1o00 17c0 1io0 17c0 1fA0 1a00 1lc0 17c0 1io0 17c0 1fA0 1a00 1io0 17c0 1io0 17c0 1fA0 1cM0 1io0 17c0 1fA0 1a00 1io0 17c0 1io0 17c0 1fA0 1a00 1io0 1qM0 Dc0 2Rz0 Dc0 1zc0 Oo0 1zc0 Rc0 1wo0 17c0 1iM0 FA0 xB0 1fA0 1a00 14o0 bb0 LA0 xB0 Rc0 1wo0 11A0 1o00 17c0 1fA0 1a00 1fA0 1cM0 1fA0 1a00 17c0 1fA0 1a00 1io0 17c0 1lc0 17c0 1fA0 1a00 1io0 17c0 1io0 17c0 1fA0 1a00 1a00 1qM0 WM0 1qM0 11A0 1o00 WM0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1tA0 IM0 90o0 U00 1tA0 U00 1tA0 U00 1tA0 U00 1tA0 WM0 1qM0 WM0 1qM0 WM0 1tA0 U00 1tA0 U00 1tA0 11z0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 14o0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Belgrade|CET CEST|-10 -20|01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-19RC0 3IP0 WM0 1fA0 1cM0 1cM0 1rc0 Qo0 1vmo0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Berlin|CET CEST CEMT|-10 -20 -30|01010101010101210101210101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2aFe0 11d0 1iO0 11A0 1o00 11A0 Qrc0 6i00 WM0 1fA0 1cM0 1cM0 1cM0 kL0 Nc0 m10 WM0 1ao0 1cp0 dX0 jz0 Dd0 1io0 17c0 1fA0 1a00 1ehA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Bratislava|CET CEST|-10 -20|010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2aFe0 11d0 1iO0 11A0 1o00 11A0 Qrc0 6i00 WM0 1fA0 1cM0 16M0 1lc0 1tA0 17A0 11c0 1io0 17c0 1io0 17c0 1fc0 1ao0 1bNc0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Brussels|WET CET CEST WEST|0 -10 -20 -10|0121212103030303030303030303030303030303030303030303212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2ehc0 3zX0 11c0 1iO0 11A0 1o00 11A0 my0 Ic0 1qM0 Rc0 1EM0 UM0 1u00 10o0 1io0 1io0 17c0 1a00 1fA0 1cM0 1cM0 1io0 17c0 1fA0 1a00 1io0 1a30 1io0 17c0 1fA0 1a00 1io0 17c0 1cM0 1cM0 1a00 1io0 1cM0 1cM0 1a00 1fA0 1io0 17c0 1cM0 1cM0 1a00 1fA0 1io0 1qM0 Dc0 y00 5Wn0 WM0 1fA0 1cM0 16M0 1iM0 16M0 1C00 Uo0 1eeo0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Bucharest|BMT EET EEST|-1I.o -20 -30|0121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-1xApI.o 20LI.o RA0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1Axc0 On0 1fA0 1a10 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cK0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cL0 1cN0 1cL0 1fB0 1nX0 11E0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Budapest|CET CEST|-10 -20|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2aFe0 11d0 1iO0 11A0 1ip0 17b0 1op0 1tb0 Q2m0 3Ne0 WM0 1fA0 1cM0 1cM0 1oJ0 1dc0 1030 1fA0 1cM0 1cM0 1cM0 1cM0 1fA0 1a00 1iM0 1fA0 8Ha0 Rb0 1wN0 Rb0 1BB0 Lz0 1C20 LB0 SNX0 1a10 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Busingen|CET CEST|-10 -20|01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-19Lc0 11A0 1o00 11A0 1xG10 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Chisinau|CMT BMT EET EEST CEST CET MSK MSD|-1T -1I.o -20 -30 -20 -10 -30 -40|0123232323232323232345454676767676767676767623232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-26jdT wGMa.A 20LI.o RA0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 27A0 2en0 39g0 WM0 1fA0 1cM0 V90 1t7z0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1ty0 2bD0 1cM0 1cK0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1fB0 1nX0 11E0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Copenhagen|CET CEST|-10 -20|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2azC0 Tz0 VuO0 60q0 WM0 1fA0 1cM0 1cM0 1cM0 S00 1HA0 Nc0 1C00 Dc0 1Nc0 Ao0 1h5A0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Gibraltar|GMT BST BDST CET CEST|0 -10 -20 -10 -20|010101010101010101010101010101010101010101010101012121212121010121010101010101010101034343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343|-2axa0 Rc0 1fA0 14M0 1fc0 1g00 1co0 1dc0 1co0 1oo0 1400 1dc0 19A0 1io0 1io0 WM0 1o00 14o0 1o00 17c0 1io0 17c0 1fA0 1a00 1lc0 17c0 1io0 17c0 1fA0 1a00 1io0 17c0 1io0 17c0 1fA0 1cM0 1io0 17c0 1fA0 1a00 1io0 17c0 1io0 17c0 1fA0 1a00 1io0 1qM0 Dc0 2Rz0 Dc0 1zc0 Oo0 1zc0 Rc0 1wo0 17c0 1iM0 FA0 xB0 1fA0 1a00 14o0 bb0 LA0 xB0 Rc0 1wo0 11A0 1o00 17c0 1fA0 1a00 1fA0 1cM0 1fA0 1a00 17c0 1fA0 1a00 1io0 17c0 1lc0 17c0 1fA0 10Jz0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Helsinki|HMT EET EEST|-1D.N -20 -30|0121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-1WuND.N OULD.N 1dA0 1xGq0 1cM0 1cM0 1cM0 1cN0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Kaliningrad|CET CEST CET CEST MSK MSD EEST EET FET|-10 -20 -20 -30 -30 -40 -30 -20 -30|0101010101010232454545454545454545454676767676767676767676767676767676767676787|-2aFe0 11d0 1iO0 11A0 1o00 11A0 Qrc0 6i00 WM0 1fA0 1cM0 1cM0 Am0 Lb0 1en0 op0 1pNz0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 1cJ0 1cL0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0",
			"Europe/Kiev|KMT EET MSK CEST CET MSD EEST|-22.4 -20 -30 -20 -10 -40 -30|0123434252525252525252525256161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161|-1Pc22.4 eUo2.4 rnz0 2Hg0 WM0 1fA0 da0 1v4m0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 Db0 3220 1cK0 1cL0 1cN0 1cL0 1cN0 1cL0 1cQ0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Lisbon|LMT WET WEST WEMT CET CEST|A.J 0 -10 -20 -10 -20|012121212121212121212121212121212121212121212321232123212321212121212121212121212121212121212121214121212121212121212121212121212124545454212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2ldXn.f aPWn.f Sp0 LX0 1vc0 Tc0 1uM0 SM0 1vc0 Tc0 1vc0 SM0 1vc0 6600 1co0 3E00 17c0 1fA0 1a00 1io0 1a00 1io0 17c0 3I00 17c0 1cM0 1cM0 3Fc0 1cM0 1a00 1fA0 1io0 17c0 1cM0 1cM0 1a00 1fA0 1io0 1qM0 Dc0 1tA0 1cM0 1dc0 1400 gL0 IM0 s10 U00 dX0 Rc0 pd0 Rc0 gL0 Oo0 pd0 Rc0 gL0 Oo0 pd0 14o0 1cM0 1cP0 1cM0 1cM0 1cM0 1cM0 1cM0 3Co0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 pvy0 1cM0 1cM0 1fA0 1cM0 1cM0 1cN0 1cL0 1cN0 1cM0 1cM0 1cM0 1cM0 1cN0 1cL0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Luxembourg|LMT CET CEST WET WEST WEST WET|-o.A -10 -20 0 -10 -20 -10|0121212134343434343434343434343434343434343434343434565651212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2DG0o.A t6mo.A TB0 1nX0 Up0 1o20 11A0 rW0 CM0 1qP0 R90 1EO0 UK0 1u20 10m0 1ip0 1in0 17e0 19W0 1fB0 1db0 1cp0 1in0 17d0 1fz0 1a10 1in0 1a10 1in0 17f0 1fA0 1a00 1io0 17c0 1cM0 1cM0 1a00 1io0 1cM0 1cM0 1a00 1fA0 1io0 17c0 1cM0 1cM0 1a00 1fA0 1io0 1qM0 Dc0 vA0 60L0 WM0 1fA0 1cM0 17c0 1io0 16M0 1C00 Uo0 1eeo0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Madrid|WET WEST WEMT CET CEST|0 -10 -20 -10 -20|01010101010101010101010121212121234343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343|-28dd0 11A0 1go0 19A0 1co0 1dA0 b1A0 18o0 3I00 17c0 1fA0 1a00 1io0 1a00 1io0 17c0 iyo0 Rc0 18o0 1hc0 1io0 1a00 14o0 5aL0 MM0 1vc0 17A0 1i00 1bc0 1eo0 17d0 1in0 17A0 6hA0 10N0 XIL0 1a10 1in0 17d0 19X0 1cN0 1fz0 1a10 1fX0 1cp0 1cO0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Malta|CET CEST|-10 -20|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2as10 M00 1cM0 1cM0 14o0 1o00 WM0 1qM0 17c0 1cM0 M3A0 5M20 WM0 1fA0 1cM0 1cM0 1cM0 16m0 1de0 1lc0 14m0 1lc0 WO0 1qM0 GTW0 On0 1C10 Lz0 1C10 Lz0 1EN0 Lz0 1C10 Lz0 1zd0 Oo0 1C00 On0 1cp0 1cM0 1lA0 Xc0 1qq0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1iN0 19z0 1fB0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Minsk|MMT EET MSK CEST CET MSD EEST FET|-1O -20 -30 -20 -10 -40 -30 -30|012343432525252525252525252616161616161616161616161616161616161616172|-1Pc1O eUnO qNX0 3gQ0 WM0 1fA0 1cM0 Al0 1tsn0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 3Fc0 1cN0 1cK0 1cM0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hy0",
			"Europe/Monaco|PMT WET WEST WEMT CET CEST|-9.l 0 -10 -20 -10 -20|01212121212121212121212121212121212121212121212121232323232345454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454|-2nco9.l cNb9.l HA0 19A0 1iM0 11c0 1oo0 Wo0 1rc0 QM0 1EM0 UM0 1u00 10o0 1io0 1wo0 Rc0 1a00 1fA0 1cM0 1cM0 1io0 17c0 1fA0 1a00 1io0 1a00 1io0 17c0 1fA0 1a00 1io0 17c0 1cM0 1cM0 1a00 1io0 1cM0 1cM0 1a00 1fA0 1io0 17c0 1cM0 1cM0 1a00 1fA0 1io0 1qM0 Df0 2RV0 11z0 11B0 1ze0 WM0 1fA0 1cM0 1fa0 1aq0 16M0 1ekn0 1cL0 1fC0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Moscow|MMT MMT MST MDST MSD MSK MSM EET EEST MSK|-2u.h -2v.j -3v.j -4v.j -40 -30 -50 -20 -30 -40|012132345464575454545454545454545458754545454545454545454545454545454545454595|-2ag2u.h 2pyW.W 1bA0 11X0 GN0 1Hb0 c20 imv.j 3DA0 dz0 15A0 c10 2q10 iM10 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rU0 1cL0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0",
			"Europe/Paris|PMT WET WEST CEST CET WEMT|-9.l 0 -10 -20 -10 -20|0121212121212121212121212121212121212121212121212123434352543434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434|-2nco8.l cNb8.l HA0 19A0 1iM0 11c0 1oo0 Wo0 1rc0 QM0 1EM0 UM0 1u00 10o0 1io0 1wo0 Rc0 1a00 1fA0 1cM0 1cM0 1io0 17c0 1fA0 1a00 1io0 1a00 1io0 17c0 1fA0 1a00 1io0 17c0 1cM0 1cM0 1a00 1io0 1cM0 1cM0 1a00 1fA0 1io0 17c0 1cM0 1cM0 1a00 1fA0 1io0 1qM0 Df0 Ik0 5M30 WM0 1fA0 1cM0 Vx0 hB0 1aq0 16M0 1ekn0 1cL0 1fC0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Riga|RMT LST EET MSK CEST CET MSD EEST|-1A.y -2A.y -20 -30 -20 -10 -40 -30|010102345454536363636363636363727272727272727272727272727272727272727272727272727272727272727272727272727272727272727272727272|-25TzA.y 11A0 1iM0 ko0 gWm0 yDXA.y 2bX0 3fE0 WM0 1fA0 1cM0 1cM0 4m0 1sLy0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cN0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 1o00 11A0 1o00 11A0 1qM0 3oo0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Rome|CET CEST|-10 -20|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2as10 M00 1cM0 1cM0 14o0 1o00 WM0 1qM0 17c0 1cM0 M3A0 5M20 WM0 1fA0 1cM0 16K0 1iO0 16m0 1de0 1lc0 14m0 1lc0 WO0 1qM0 GTW0 On0 1C10 Lz0 1C10 Lz0 1EN0 Lz0 1C10 Lz0 1zd0 Oo0 1C00 On0 1C10 Lz0 1zd0 On0 1C10 LA0 1C00 LA0 1zc0 Oo0 1C00 Oo0 1zc0 Oo0 1fC0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Samara|LMT SAMT SAMT KUYT KUYST MSD MSK EEST KUYT SAMST SAMST|-3k.k -30 -40 -40 -50 -40 -30 -30 -30 -50 -40|012343434343434343435656782929292929292929292929292929292929292a12|-22WNk.k qHak.k bcn0 1Qqo0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cN0 1cM0 1fA0 1cM0 1cN0 8o0 14j0 1cL0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qN0 WM0",
			"Europe/Simferopol|SMT EET MSK CEST CET MSD EEST MSK|-2g -20 -30 -20 -10 -40 -30 -40|012343432525252525252525252161616525252616161616161616161616161616161616172|-1Pc2g eUog rEn0 2qs0 WM0 1fA0 1cM0 3V0 1u0L0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1Q00 4eL0 1cL0 1cN0 1cL0 1cN0 dX0 WL0 1cN0 1cL0 1fB0 1o30 11B0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11z0 1nW0",
			"Europe/Sofia|EET CET CEST EEST|-20 -10 -20 -30|01212103030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030|-168L0 WM0 1fA0 1cM0 1cM0 1cN0 1mKH0 1dd0 1fb0 1ap0 1fb0 1a20 1fy0 1a30 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cK0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1fB0 1nX0 11E0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Stockholm|CET CEST|-10 -20|01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2azC0 TB0 2yDe0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Tallinn|TMT CET CEST EET MSK MSD EEST|-1D -10 -20 -20 -30 -40 -30|012103421212454545454545454546363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363|-26oND teD 11A0 1Ta0 4rXl KSLD 2FX0 2Jg0 WM0 1fA0 1cM0 18J0 1sTX0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cN0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o10 11A0 1qM0 5QM0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Tirane|LMT CET CEST|-1j.k -10 -20|01212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2glBj.k 14pcj.k 5LC0 WM0 4M0 1fCK0 10n0 1op0 11z0 1pd0 11z0 1qN0 WL0 1qp0 Xb0 1qp0 Xb0 1qp0 11z0 1lB0 11z0 1qN0 11z0 1iN0 16n0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Uzhgorod|CET CEST MSK MSD EET EEST|-10 -20 -30 -40 -20 -30|010101023232323232323232320454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454|-1cqL0 6i00 WM0 1fA0 1cM0 1ml0 1Cp0 1r3W0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1Q00 1Nf0 2pw0 1cL0 1cN0 1cL0 1cN0 1cL0 1cQ0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Vienna|CET CEST|-10 -20|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2aFe0 11d0 1iO0 11A0 1o00 11A0 3KM0 14o0 LA00 6i00 WM0 1fA0 1cM0 1cM0 1cM0 400 2qM0 1a00 1cM0 1cM0 1io0 17c0 1gHa0 19X0 1cP0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Vilnius|WMT KMT CET EET MSK CEST MSD EEST|-1o -1z.A -10 -20 -30 -20 -40 -30|012324525254646464646464646464647373737373737352537373737373737373737373737373737373737373737373737373737373737373737373|-293do 6ILM.o 1Ooz.A zz0 Mfd0 29W0 3is0 WM0 1fA0 1cM0 LV0 1tgL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11B0 1o00 11A0 1qM0 8io0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Volgograd|LMT TSAT STAT STAT VOLT VOLST VOLST VOLT MSD MSK MSK|-2V.E -30 -30 -40 -40 -50 -40 -30 -40 -30 -40|0123454545454545454546767489898989898989898989898989898989898989a9|-21IqV.E cLXV.E cEM0 1gqn0 Lco0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cN0 1cM0 1fA0 1cM0 2pz0 1cJ0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0",
			"Europe/Warsaw|WMT CET CEST EET EEST|-1o -10 -20 -20 -30|012121234312121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2ctdo 1LXo 11d0 1iO0 11A0 1o00 11A0 1on0 11A0 6zy0 HWP0 5IM0 WM0 1fA0 1cM0 1dz0 1mL0 1en0 15B0 1aq0 1nA0 11A0 1io0 17c0 1fA0 1a00 iDX0 LA0 1cM0 1cM0 1C00 Oo0 1cM0 1cM0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1C00 LA0 uso0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cN0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"Europe/Zaporozhye|CUT EET MSK CEST CET MSD EEST|-2k -20 -30 -20 -10 -40 -30|01234342525252525252525252526161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161|-1Pc2k eUok rdb0 2RE0 WM0 1fA0 8m0 1v9a0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cK0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cQ0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"HST|HST|a0|0|",
			"Indian/Chagos|LMT IOT IOT|-4N.E -50 -60|012|-2xosN.E 3AGLN.E",
			"Indian/Christmas|CXT|-70|0|",
			"Indian/Cocos|CCT|-6u|0|",
			"Indian/Kerguelen|zzz TFT|0 -50|01|-MG00",
			"Indian/Mahe|LMT SCT|-3F.M -40|01|-2yO3F.M",
			"Indian/Maldives|MMT MVT|-4S -50|01|-olgS",
			"Indian/Mauritius|LMT MUT MUST|-3O -40 -50|012121|-2xorO 34unO 14L0 12kr0 11z0",
			"Indian/Reunion|LMT RET|-3F.Q -40|01|-2mDDF.Q",
			"Kwajalein|MHT KWAT MHT|-b0 c0 -c0|012|-AX0 W9X0",
			"MET|MET MEST|-10 -20|01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2aFe0 11d0 1iO0 11A0 1o00 11A0 Qrc0 6i00 WM0 1fA0 1cM0 1cM0 1cM0 16M0 1gMM0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00",
			"MST|MST|70|0|",
			"MST7MDT|MST MDT MWT MPT|70 60 60 60|010102301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261r0 1nX0 11B0 1nX0 SgN0 8x20 ix0 QwN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"NZ-CHAT|CHAST CHAST CHADT|-cf -cJ -dJ|012121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212|-WqAf 1adef IM0 1C00 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1qM0 14o0 1lc0 14o0 1lc0 14o0 1lc0 17c0 1io0 17c0 1io0 17c0 1io0 17c0 1lc0 14o0 1lc0 14o0 1lc0 17c0 1io0 17c0 1io0 17c0 1lc0 14o0 1lc0 14o0 1lc0 17c0 1io0 17c0 1io0 17c0 1io0 17c0 1io0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1io0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00",
			"PST8PDT|PST PDT PWT PPT|80 70 70 70|010102301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261q0 1nX0 11B0 1nX0 SgN0 8x10 iy0 QwN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0",
			"Pacific/Apia|LMT WSST SST SDT WSDT WSST|bq.U bu b0 a0 -e0 -d0|01232345454545454545454545454545454545454545454545454545454|-2nDMx.4 1yW03.4 2rRbu 1ff0 1a00 CI0 AQ0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1io0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00",
			"Pacific/Bougainville|PGT JST BST|-a0 -90 -b0|0102|-16Wy0 7CN0 2MQp0",
			"Pacific/Chuuk|CHUT|-a0|0|",
			"Pacific/Efate|LMT VUT VUST|-bd.g -b0 -c0|0121212121212121212121|-2l9nd.g 2Szcd.g 1cL0 1oN0 10L0 1fB0 19X0 1fB0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1fB0 Lz0 1Nd0 An0",
			"Pacific/Enderbury|PHOT PHOT PHOT|c0 b0 -d0|012|nIc0 B8n0",
			"Pacific/Fakaofo|TKT TKT|b0 -d0|01|1Gfn0",
			"Pacific/Fiji|LMT FJT FJST|-bT.I -c0 -d0|012121212121212121212121212121212121212121212121212121212121212|-2bUzT.I 3m8NT.I LA0 1EM0 IM0 nJc0 LA0 1o00 Rc0 1wo0 Ao0 1Nc0 Ao0 1Q00 xz0 1SN0 uM0 1SM0 xA0 1SM0 uM0 1SM0 uM0 1SM0 uM0 1SM0 uM0 1SM0 xA0 1SM0 uM0 1SM0 uM0 1SM0 uM0 1SM0 uM0 1SM0 uM0 1SM0 xA0 1SM0 uM0 1SM0 uM0 1SM0 uM0 1SM0 uM0 1SM0 uM0 1VA0 uM0 1SM0 uM0 1SM0 uM0 1SM0 uM0 1SM0 uM0 1SM0",
			"Pacific/Funafuti|TVT|-c0|0|",
			"Pacific/Galapagos|LMT ECT GALT|5W.o 50 60|012|-1yVS1.A 2dTz1.A",
			"Pacific/Gambier|LMT GAMT|8X.M 90|01|-2jof0.c",
			"Pacific/Guadalcanal|LMT SBT|-aD.M -b0|01|-2joyD.M",
			"Pacific/Guam|GST ChST|-a0 -a0|01|1fpq0",
			"Pacific/Honolulu|HST HDT HST|au 9u a0|010102|-1thLu 8x0 lef0 8Pz0 46p0",
			"Pacific/Kiritimati|LINT LINT LINT|aE a0 -e0|012|nIaE B8nk",
			"Pacific/Kosrae|KOST KOST|-b0 -c0|010|-AX0 1bdz0",
			"Pacific/Majuro|MHT MHT|-b0 -c0|01|-AX0",
			"Pacific/Marquesas|LMT MART|9i 9u|01|-2joeG",
			"Pacific/Midway|LMT NST BST SST|bm.M b0 b0 b0|0123|-2nDMB.c 2gVzB.c EyM0",
			"Pacific/Nauru|LMT NRT JST NRT|-b7.E -bu -90 -c0|01213|-1Xdn7.E PvzB.E 5RCu 1ouJu",
			"Pacific/Niue|NUT NUT NUT|bk bu b0|012|-KfME 17y0a",
			"Pacific/Norfolk|NMT NFT|-bc -bu|01|-Kgbc",
			"Pacific/Noumea|LMT NCT NCST|-b5.M -b0 -c0|01212121|-2l9n5.M 2EqM5.M xX0 1PB0 yn0 HeP0 Ao0",
			"Pacific/Palau|PWT|-90|0|",
			"Pacific/Pitcairn|PNT PST|8u 80|01|18Vku",
			"Pacific/Pohnpei|PONT|-b0|0|",
			"Pacific/Port_Moresby|PGT|-a0|0|",
			"Pacific/Rarotonga|CKT CKHST CKT|au 9u a0|012121212121212121212121212|lyWu IL0 1zcu Onu 1zcu Onu 1zcu Rbu 1zcu Onu 1zcu Onu 1zcu Onu 1zcu Onu 1zcu Onu 1zcu Rbu 1zcu Onu 1zcu Onu 1zcu Onu",
			"Pacific/Tahiti|LMT TAHT|9W.g a0|01|-2joe1.I",
			"Pacific/Tarawa|GILT|-c0|0|",
			"Pacific/Tongatapu|TOT TOT TOST|-ck -d0 -e0|01212121|-1aB0k 2n5dk 15A0 1wo0 xz0 1Q10 xz0",
			"Pacific/Wake|WAKT|-c0|0|",
			"Pacific/Wallis|WFT|-c0|0|",
			"WET|WET WEST|0 -10|010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|hDB0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00"
		],
		"links": [
			"Africa/Abidjan|Africa/Bamako",
			"Africa/Abidjan|Africa/Banjul",
			"Africa/Abidjan|Africa/Conakry",
			"Africa/Abidjan|Africa/Dakar",
			"Africa/Abidjan|Africa/Freetown",
			"Africa/Abidjan|Africa/Lome",
			"Africa/Abidjan|Africa/Nouakchott",
			"Africa/Abidjan|Africa/Ouagadougou",
			"Africa/Abidjan|Africa/Sao_Tome",
			"Africa/Abidjan|Africa/Timbuktu",
			"Africa/Abidjan|Atlantic/St_Helena",
			"Africa/Addis_Ababa|Africa/Asmara",
			"Africa/Addis_Ababa|Africa/Asmera",
			"Africa/Addis_Ababa|Africa/Dar_es_Salaam",
			"Africa/Addis_Ababa|Africa/Djibouti",
			"Africa/Addis_Ababa|Africa/Kampala",
			"Africa/Addis_Ababa|Africa/Mogadishu",
			"Africa/Addis_Ababa|Africa/Nairobi",
			"Africa/Addis_Ababa|Indian/Antananarivo",
			"Africa/Addis_Ababa|Indian/Comoro",
			"Africa/Addis_Ababa|Indian/Mayotte",
			"Africa/Bangui|Africa/Brazzaville",
			"Africa/Bangui|Africa/Douala",
			"Africa/Bangui|Africa/Kinshasa",
			"Africa/Bangui|Africa/Lagos",
			"Africa/Bangui|Africa/Libreville",
			"Africa/Bangui|Africa/Luanda",
			"Africa/Bangui|Africa/Malabo",
			"Africa/Bangui|Africa/Niamey",
			"Africa/Bangui|Africa/Porto-Novo",
			"Africa/Blantyre|Africa/Bujumbura",
			"Africa/Blantyre|Africa/Gaborone",
			"Africa/Blantyre|Africa/Harare",
			"Africa/Blantyre|Africa/Kigali",
			"Africa/Blantyre|Africa/Lubumbashi",
			"Africa/Blantyre|Africa/Lusaka",
			"Africa/Blantyre|Africa/Maputo",
			"Africa/Cairo|Egypt",
			"Africa/Johannesburg|Africa/Maseru",
			"Africa/Johannesburg|Africa/Mbabane",
			"Africa/Juba|Africa/Khartoum",
			"Africa/Tripoli|Libya",
			"America/Adak|America/Atka",
			"America/Adak|US/Aleutian",
			"America/Anchorage|US/Alaska",
			"America/Anguilla|America/Antigua",
			"America/Anguilla|America/Dominica",
			"America/Anguilla|America/Grenada",
			"America/Anguilla|America/Guadeloupe",
			"America/Anguilla|America/Marigot",
			"America/Anguilla|America/Montserrat",
			"America/Anguilla|America/Port_of_Spain",
			"America/Anguilla|America/St_Barthelemy",
			"America/Anguilla|America/St_Kitts",
			"America/Anguilla|America/St_Lucia",
			"America/Anguilla|America/St_Thomas",
			"America/Anguilla|America/St_Vincent",
			"America/Anguilla|America/Tortola",
			"America/Anguilla|America/Virgin",
			"America/Argentina/Buenos_Aires|America/Buenos_Aires",
			"America/Argentina/Catamarca|America/Argentina/ComodRivadavia",
			"America/Argentina/Catamarca|America/Catamarca",
			"America/Argentina/Cordoba|America/Cordoba",
			"America/Argentina/Cordoba|America/Rosario",
			"America/Argentina/Jujuy|America/Jujuy",
			"America/Argentina/Mendoza|America/Mendoza",
			"America/Aruba|America/Curacao",
			"America/Aruba|America/Kralendijk",
			"America/Aruba|America/Lower_Princes",
			"America/Atikokan|America/Coral_Harbour",
			"America/Cayman|America/Panama",
			"America/Chicago|US/Central",
			"America/Denver|America/Shiprock",
			"America/Denver|Navajo",
			"America/Denver|US/Mountain",
			"America/Detroit|US/Michigan",
			"America/Edmonton|Canada/Mountain",
			"America/Ensenada|America/Tijuana",
			"America/Ensenada|Mexico/BajaNorte",
			"America/Fort_Wayne|America/Indiana/Indianapolis",
			"America/Fort_Wayne|America/Indianapolis",
			"America/Fort_Wayne|US/East-Indiana",
			"America/Halifax|Canada/Atlantic",
			"America/Havana|Cuba",
			"America/Indiana/Knox|America/Knox_IN",
			"America/Indiana/Knox|US/Indiana-Starke",
			"America/Jamaica|Jamaica",
			"America/Kentucky/Louisville|America/Louisville",
			"America/Los_Angeles|US/Pacific",
			"America/Los_Angeles|US/Pacific-New",
			"America/Manaus|Brazil/West",
			"America/Mazatlan|Mexico/BajaSur",
			"America/Mexico_City|Mexico/General",
			"America/Montreal|America/Toronto",
			"America/Montreal|Canada/Eastern",
			"America/New_York|US/Eastern",
			"America/Noronha|Brazil/DeNoronha",
			"America/Phoenix|US/Arizona",
			"America/Porto_Acre|America/Rio_Branco",
			"America/Porto_Acre|Brazil/Acre",
			"America/Regina|Canada/East-Saskatchewan",
			"America/Regina|Canada/Saskatchewan",
			"America/Santiago|Chile/Continental",
			"America/Sao_Paulo|Brazil/East",
			"America/St_Johns|Canada/Newfoundland",
			"America/Vancouver|Canada/Pacific",
			"America/Whitehorse|Canada/Yukon",
			"America/Winnipeg|Canada/Central",
			"Antarctica/McMurdo|Antarctica/South_Pole",
			"Antarctica/McMurdo|NZ",
			"Antarctica/McMurdo|Pacific/Auckland",
			"Arctic/Longyearbyen|Atlantic/Jan_Mayen",
			"Arctic/Longyearbyen|Europe/Oslo",
			"Asia/Aden|Asia/Kuwait",
			"Asia/Aden|Asia/Riyadh",
			"Asia/Ashgabat|Asia/Ashkhabad",
			"Asia/Bahrain|Asia/Qatar",
			"Asia/Bangkok|Asia/Phnom_Penh",
			"Asia/Bangkok|Asia/Vientiane",
			"Asia/Calcutta|Asia/Kolkata",
			"Asia/Chongqing|Asia/Chungking",
			"Asia/Chongqing|Asia/Harbin",
			"Asia/Chongqing|Asia/Shanghai",
			"Asia/Chongqing|PRC",
			"Asia/Dacca|Asia/Dhaka",
			"Asia/Dubai|Asia/Muscat",
			"Asia/Ho_Chi_Minh|Asia/Saigon",
			"Asia/Hong_Kong|Hongkong",
			"Asia/Istanbul|Europe/Istanbul",
			"Asia/Istanbul|Turkey",
			"Asia/Jerusalem|Asia/Tel_Aviv",
			"Asia/Jerusalem|Israel",
			"Asia/Kashgar|Asia/Urumqi",
			"Asia/Kathmandu|Asia/Katmandu",
			"Asia/Macao|Asia/Macau",
			"Asia/Makassar|Asia/Ujung_Pandang",
			"Asia/Nicosia|Europe/Nicosia",
			"Asia/Seoul|ROK",
			"Asia/Singapore|Singapore",
			"Asia/Taipei|ROC",
			"Asia/Tehran|Iran",
			"Asia/Thimbu|Asia/Thimphu",
			"Asia/Tokyo|Japan",
			"Asia/Ulaanbaatar|Asia/Ulan_Bator",
			"Atlantic/Faeroe|Atlantic/Faroe",
			"Atlantic/Reykjavik|Iceland",
			"Australia/ACT|Australia/Canberra",
			"Australia/ACT|Australia/NSW",
			"Australia/ACT|Australia/Sydney",
			"Australia/Adelaide|Australia/South",
			"Australia/Brisbane|Australia/Queensland",
			"Australia/Broken_Hill|Australia/Yancowinna",
			"Australia/Darwin|Australia/North",
			"Australia/Hobart|Australia/Tasmania",
			"Australia/LHI|Australia/Lord_Howe",
			"Australia/Melbourne|Australia/Victoria",
			"Australia/Perth|Australia/West",
			"Chile/EasterIsland|Pacific/Easter",
			"Eire|Europe/Dublin",
			"Etc/GMT+0|Etc/GMT",
			"Etc/GMT+0|Etc/GMT-0",
			"Etc/GMT+0|Etc/GMT0",
			"Etc/GMT+0|Etc/Greenwich",
			"Etc/GMT+0|GMT",
			"Etc/GMT+0|GMT+0",
			"Etc/GMT+0|GMT-0",
			"Etc/GMT+0|GMT0",
			"Etc/GMT+0|Greenwich",
			"Etc/UCT|UCT",
			"Etc/UTC|Etc/Universal",
			"Etc/UTC|Etc/Zulu",
			"Etc/UTC|UTC",
			"Etc/UTC|Universal",
			"Etc/UTC|Zulu",
			"Europe/Belfast|Europe/Guernsey",
			"Europe/Belfast|Europe/Isle_of_Man",
			"Europe/Belfast|Europe/Jersey",
			"Europe/Belfast|Europe/London",
			"Europe/Belfast|GB",
			"Europe/Belfast|GB-Eire",
			"Europe/Belgrade|Europe/Ljubljana",
			"Europe/Belgrade|Europe/Podgorica",
			"Europe/Belgrade|Europe/Sarajevo",
			"Europe/Belgrade|Europe/Skopje",
			"Europe/Belgrade|Europe/Zagreb",
			"Europe/Bratislava|Europe/Prague",
			"Europe/Busingen|Europe/Vaduz",
			"Europe/Busingen|Europe/Zurich",
			"Europe/Chisinau|Europe/Tiraspol",
			"Europe/Helsinki|Europe/Mariehamn",
			"Europe/Lisbon|Portugal",
			"Europe/Moscow|W-SU",
			"Europe/Rome|Europe/San_Marino",
			"Europe/Rome|Europe/Vatican",
			"Europe/Warsaw|Poland",
			"Kwajalein|Pacific/Kwajalein",
			"NZ-CHAT|Pacific/Chatham",
			"Pacific/Chuuk|Pacific/Truk",
			"Pacific/Chuuk|Pacific/Yap",
			"Pacific/Guam|Pacific/Saipan",
			"Pacific/Honolulu|Pacific/Johnston",
			"Pacific/Honolulu|US/Hawaii",
			"Pacific/Midway|Pacific/Pago_Pago",
			"Pacific/Midway|Pacific/Samoa",
			"Pacific/Midway|US/Samoa",
			"Pacific/Pohnpei|Pacific/Ponape"
		]
	});


	return moment;
}));

!function(a,b,c,d){function e(b,c){this.settings=null,this.options=a.extend({},e.Defaults,c),this.$element=a(b),this._handlers={},this._plugins={},this._supress={},this._current=null,this._speed=null,this._coordinates=[],this._breakpoint=null,this._width=null,this._items=[],this._clones=[],this._mergers=[],this._widths=[],this._invalidated={},this._pipe=[],this._drag={time:null,target:null,pointer:null,stage:{start:null,current:null},direction:null},this._states={current:{},tags:{initializing:["busy"],animating:["busy"],dragging:["interacting"]}},a.each(["onResize","onThrottledResize"],a.proxy(function(b,c){this._handlers[c]=a.proxy(this[c],this)},this)),a.each(e.Plugins,a.proxy(function(a,b){this._plugins[a.charAt(0).toLowerCase()+a.slice(1)]=new b(this)},this)),a.each(e.Workers,a.proxy(function(b,c){this._pipe.push({filter:c.filter,run:a.proxy(c.run,this)})},this)),this.setup(),this.initialize()}e.Defaults={items:3,loop:!1,center:!1,rewind:!1,mouseDrag:!0,touchDrag:!0,pullDrag:!0,freeDrag:!1,margin:0,stagePadding:0,merge:!1,mergeFit:!0,autoWidth:!1,startPosition:0,rtl:!1,smartSpeed:250,fluidSpeed:!1,dragEndSpeed:!1,responsive:{},responsiveRefreshRate:200,responsiveBaseElement:b,fallbackEasing:"swing",info:!1,nestedItemSelector:!1,itemElement:"div",stageElement:"div",refreshClass:"owl-refresh",loadedClass:"owl-loaded",loadingClass:"owl-loading",rtlClass:"owl-rtl",responsiveClass:"owl-responsive",dragClass:"owl-drag",itemClass:"owl-item",stageClass:"owl-stage",stageOuterClass:"owl-stage-outer",grabClass:"owl-grab"},e.Width={Default:"default",Inner:"inner",Outer:"outer"},e.Type={Event:"event",State:"state"},e.Plugins={},e.Workers=[{filter:["width","settings"],run:function(){this._width=this.$element.width()}},{filter:["width","items","settings"],run:function(a){a.current=this._items&&this._items[this.relative(this._current)]}},{filter:["items","settings"],run:function(){this.$stage.children(".cloned").remove()}},{filter:["width","items","settings"],run:function(a){var b=this.settings.margin||"",c=!this.settings.autoWidth,d=this.settings.rtl,e={width:"auto","margin-left":d?b:"","margin-right":d?"":b};!c&&this.$stage.children().css(e),a.css=e}},{filter:["width","items","settings"],run:function(a){var b=(this.width()/this.settings.items).toFixed(3)-this.settings.margin,c=null,d=this._items.length,e=!this.settings.autoWidth,f=[];for(a.items={merge:!1,width:b};d--;)c=this._mergers[d],c=this.settings.mergeFit&&Math.min(c,this.settings.items)||c,a.items.merge=c>1||a.items.merge,f[d]=e?b*c:this._items[d].width();this._widths=f}},{filter:["items","settings"],run:function(){var b=[],c=this._items,d=this.settings,e=Math.max(2*d.items,4),f=2*Math.ceil(c.length/2),g=d.loop&&c.length?d.rewind?e:Math.max(e,f):0,h="",i="";for(g/=2;g--;)b.push(this.normalize(b.length/2,!0)),h+=c[b[b.length-1]][0].outerHTML,b.push(this.normalize(c.length-1-(b.length-1)/2,!0)),i=c[b[b.length-1]][0].outerHTML+i;this._clones=b,a(h).addClass("cloned").appendTo(this.$stage),a(i).addClass("cloned").prependTo(this.$stage)}},{filter:["width","items","settings"],run:function(){for(var a=this.settings.rtl?1:-1,b=this._clones.length+this._items.length,c=-1,d=0,e=0,f=[];++c<b;)d=f[c-1]||0,e=this._widths[this.relative(c)]+this.settings.margin,f.push(d+e*a);this._coordinates=f}},{filter:["width","items","settings"],run:function(){var a=this.settings.stagePadding,b=this._coordinates,c={width:Math.ceil(Math.abs(b[b.length-1]))+2*a,"padding-left":a||"","padding-right":a||""};this.$stage.css(c)}},{filter:["width","items","settings"],run:function(a){var b=this._coordinates.length,c=!this.settings.autoWidth,d=this.$stage.children();if(c&&a.items.merge)for(;b--;)a.css.width=this._widths[this.relative(b)],d.eq(b).css(a.css);else c&&(a.css.width=a.items.width,d.css(a.css))}},{filter:["items"],run:function(){this._coordinates.length<1&&this.$stage.removeAttr("style")}},{filter:["width","items","settings"],run:function(a){a.current=a.current?this.$stage.children().index(a.current):0,a.current=Math.max(this.minimum(),Math.min(this.maximum(),a.current)),this.reset(a.current)}},{filter:["position"],run:function(){this.animate(this.coordinates(this._current))}},{filter:["width","position","items","settings"],run:function(){var a,b,c,d,e=this.settings.rtl?1:-1,f=2*this.settings.stagePadding,g=this.coordinates(this.current())+f,h=g+this.width()*e,i=[];for(c=0,d=this._coordinates.length;d>c;c++)a=this._coordinates[c-1]||0,b=Math.abs(this._coordinates[c])+f*e,(this.op(a,"<=",g)&&this.op(a,">",h)||this.op(b,"<",g)&&this.op(b,">",h))&&i.push(c);this.$stage.children(".active").removeClass("active"),this.$stage.children(":eq("+i.join("), :eq(")+")").addClass("active"),this.settings.center&&(this.$stage.children(".center").removeClass("center"),this.$stage.children().eq(this.current()).addClass("center"))}}],e.prototype.initialize=function(){if(this.enter("initializing"),this.trigger("initialize"),this.$element.toggleClass(this.settings.rtlClass,this.settings.rtl),this.settings.autoWidth&&!this.is("pre-loading")){var b,c,e;b=this.$element.find("img"),c=this.settings.nestedItemSelector?"."+this.settings.nestedItemSelector:d,e=this.$element.children(c).width(),b.length&&0>=e&&this.preloadAutoWidthImages(b)}this.$element.addClass(this.options.loadingClass),this.$stage=a("<"+this.settings.stageElement+' class="'+this.settings.stageClass+'"/>').wrap('<div class="'+this.settings.stageOuterClass+'"/>'),this.$element.append(this.$stage.parent()),this.replace(this.$element.children().not(this.$stage.parent())),this.$element.is(":visible")?this.refresh():this.invalidate("width"),this.$element.removeClass(this.options.loadingClass).addClass(this.options.loadedClass),this.registerEventHandlers(),this.leave("initializing"),this.trigger("initialized")},e.prototype.setup=function(){var b=this.viewport(),c=this.options.responsive,d=-1,e=null;c?(a.each(c,function(a){b>=a&&a>d&&(d=Number(a))}),e=a.extend({},this.options,c[d]),delete e.responsive,e.responsiveClass&&this.$element.attr("class",this.$element.attr("class").replace(new RegExp("("+this.options.responsiveClass+"-)\\S+\\s","g"),"$1"+d))):e=a.extend({},this.options),(null===this.settings||this._breakpoint!==d)&&(this.trigger("change",{property:{name:"settings",value:e}}),this._breakpoint=d,this.settings=e,this.invalidate("settings"),this.trigger("changed",{property:{name:"settings",value:this.settings}}))},e.prototype.optionsLogic=function(){this.settings.autoWidth&&(this.settings.stagePadding=!1,this.settings.merge=!1)},e.prototype.prepare=function(b){var c=this.trigger("prepare",{content:b});return c.data||(c.data=a("<"+this.settings.itemElement+"/>").addClass(this.options.itemClass).append(b)),this.trigger("prepared",{content:c.data}),c.data},e.prototype.update=function(){for(var b=0,c=this._pipe.length,d=a.proxy(function(a){return this[a]},this._invalidated),e={};c>b;)(this._invalidated.all||a.grep(this._pipe[b].filter,d).length>0)&&this._pipe[b].run(e),b++;this._invalidated={},!this.is("valid")&&this.enter("valid")},e.prototype.width=function(a){switch(a=a||e.Width.Default){case e.Width.Inner:case e.Width.Outer:return this._width;default:return this._width-2*this.settings.stagePadding+this.settings.margin}},e.prototype.refresh=function(){this.enter("refreshing"),this.trigger("refresh"),this.setup(),this.optionsLogic(),this.$element.addClass(this.options.refreshClass),this.update(),this.$element.removeClass(this.options.refreshClass),this.leave("refreshing"),this.trigger("refreshed")},e.prototype.onThrottledResize=function(){b.clearTimeout(this.resizeTimer),this.resizeTimer=b.setTimeout(this._handlers.onResize,this.settings.responsiveRefreshRate)},e.prototype.onResize=function(){return this._items.length?this._width===this.$element.width()?!1:this.$element.is(":visible")?(this.enter("resizing"),this.trigger("resize").isDefaultPrevented()?(this.leave("resizing"),!1):(this.invalidate("width"),this.refresh(),this.leave("resizing"),void this.trigger("resized"))):!1:!1},e.prototype.registerEventHandlers=function(){a.support.transition&&this.$stage.on(a.support.transition.end+".owl.core",a.proxy(this.onTransitionEnd,this)),this.settings.responsive!==!1&&this.on(b,"resize",this._handlers.onThrottledResize),this.settings.mouseDrag&&(this.$element.addClass(this.options.dragClass),this.$stage.on("mousedown.owl.core",a.proxy(this.onDragStart,this)),this.$stage.on("dragstart.owl.core selectstart.owl.core",function(){return!1})),this.settings.touchDrag&&(this.$stage.on("touchstart.owl.core",a.proxy(this.onDragStart,this)),this.$stage.on("touchcancel.owl.core",a.proxy(this.onDragEnd,this)))},e.prototype.onDragStart=function(b){var d=null;3!==b.which&&(a.support.transform?(d=this.$stage.css("transform").replace(/.*\(|\)| /g,"").split(","),d={x:d[16===d.length?12:4],y:d[16===d.length?13:5]}):(d=this.$stage.position(),d={x:this.settings.rtl?d.left+this.$stage.width()-this.width()+this.settings.margin:d.left,y:d.top}),this.is("animating")&&(a.support.transform?this.animate(d.x):this.$stage.stop(),this.invalidate("position")),this.$element.toggleClass(this.options.grabClass,"mousedown"===b.type),this.speed(0),this._drag.time=(new Date).getTime(),this._drag.target=a(b.target),this._drag.stage.start=d,this._drag.stage.current=d,this._drag.pointer=this.pointer(b),a(c).on("mouseup.owl.core touchend.owl.core",a.proxy(this.onDragEnd,this)),a(c).one("mousemove.owl.core touchmove.owl.core",a.proxy(function(b){var d=this.difference(this._drag.pointer,this.pointer(b));a(c).on("mousemove.owl.core touchmove.owl.core",a.proxy(this.onDragMove,this)),Math.abs(d.x)<Math.abs(d.y)&&this.is("valid")||(b.preventDefault(),this.enter("dragging"),this.trigger("drag"))},this)))},e.prototype.onDragMove=function(a){var b=null,c=null,d=null,e=this.difference(this._drag.pointer,this.pointer(a)),f=this.difference(this._drag.stage.start,e);this.is("dragging")&&(a.preventDefault(),this.settings.loop?(b=this.coordinates(this.minimum()),c=this.coordinates(this.maximum()+1)-b,f.x=((f.x-b)%c+c)%c+b):(b=this.coordinates(this.settings.rtl?this.maximum():this.minimum()),c=this.coordinates(this.settings.rtl?this.minimum():this.maximum()),d=this.settings.pullDrag?-1*e.x/5:0,f.x=Math.max(Math.min(f.x,b+d),c+d)),this._drag.stage.current=f,this.animate(f.x))},e.prototype.onDragEnd=function(b){var d=this.difference(this._drag.pointer,this.pointer(b)),e=this._drag.stage.current,f=d.x>0^this.settings.rtl?"left":"right";a(c).off(".owl.core"),this.$element.removeClass(this.options.grabClass),(0!==d.x&&this.is("dragging")||!this.is("valid"))&&(this.speed(this.settings.dragEndSpeed||this.settings.smartSpeed),this.current(this.closest(e.x,0!==d.x?f:this._drag.direction)),this.invalidate("position"),this.update(),this._drag.direction=f,(Math.abs(d.x)>3||(new Date).getTime()-this._drag.time>300)&&this._drag.target.one("click.owl.core",function(){return!1})),this.is("dragging")&&(this.leave("dragging"),this.trigger("dragged"))},e.prototype.closest=function(b,c){var d=-1,e=30,f=this.width(),g=this.coordinates();return this.settings.freeDrag||a.each(g,a.proxy(function(a,h){return b>h-e&&h+e>b?d=a:this.op(b,"<",h)&&this.op(b,">",g[a+1]||h-f)&&(d="left"===c?a+1:a),-1===d},this)),this.settings.loop||(this.op(b,">",g[this.minimum()])?d=b=this.minimum():this.op(b,"<",g[this.maximum()])&&(d=b=this.maximum())),d},e.prototype.animate=function(b){var c=this.speed()>0;this.is("animating")&&this.onTransitionEnd(),c&&(this.enter("animating"),this.trigger("translate")),a.support.transform3d&&a.support.transition?this.$stage.css({transform:"translate3d("+b+"px,0px,0px)",transition:this.speed()/1e3+"s"}):c?this.$stage.animate({left:b+"px"},this.speed(),this.settings.fallbackEasing,a.proxy(this.onTransitionEnd,this)):this.$stage.css({left:b+"px"})},e.prototype.is=function(a){return this._states.current[a]&&this._states.current[a]>0},e.prototype.current=function(a){if(a===d)return this._current;if(0===this._items.length)return d;if(a=this.normalize(a),this._current!==a){var b=this.trigger("change",{property:{name:"position",value:a}});b.data!==d&&(a=this.normalize(b.data)),this._current=a,this.invalidate("position"),this.trigger("changed",{property:{name:"position",value:this._current}})}return this._current},e.prototype.invalidate=function(b){return"string"===a.type(b)&&(this._invalidated[b]=!0,this.is("valid")&&this.leave("valid")),a.map(this._invalidated,function(a,b){return b})},e.prototype.reset=function(a){a=this.normalize(a),a!==d&&(this._speed=0,this._current=a,this.suppress(["translate","translated"]),this.animate(this.coordinates(a)),this.release(["translate","translated"]))},e.prototype.normalize=function(b,c){var e=this._items.length,f=c?0:this._clones.length;return!a.isNumeric(b)||1>e?b=d:(0>b||b>=e+f)&&(b=((b-f/2)%e+e)%e+f/2),b},e.prototype.relative=function(a){return a-=this._clones.length/2,this.normalize(a,!0)},e.prototype.maximum=function(a){var b,c=this.settings,d=this._coordinates.length,e=Math.abs(this._coordinates[d-1])-this._width,f=-1;if(c.loop)d=this._clones.length/2+this._items.length-1;else if(c.autoWidth||c.merge)for(;d-f>1;)Math.abs(this._coordinates[b=d+f>>1])<e?f=b:d=b;else d=c.center?this._items.length-1:this._items.length-c.items;return a&&(d-=this._clones.length/2),Math.max(d,0)},e.prototype.minimum=function(a){return a?0:this._clones.length/2},e.prototype.items=function(a){return a===d?this._items.slice():(a=this.normalize(a,!0),this._items[a])},e.prototype.mergers=function(a){return a===d?this._mergers.slice():(a=this.normalize(a,!0),this._mergers[a])},e.prototype.clones=function(b){var c=this._clones.length/2,e=c+this._items.length,f=function(a){return a%2===0?e+a/2:c-(a+1)/2};return b===d?a.map(this._clones,function(a,b){return f(b)}):a.map(this._clones,function(a,c){return a===b?f(c):null})},e.prototype.speed=function(a){return a!==d&&(this._speed=a),this._speed},e.prototype.coordinates=function(b){var c=null;return b===d?a.map(this._coordinates,a.proxy(function(a,b){return this.coordinates(b)},this)):(this.settings.center?(c=this._coordinates[b],c+=(this.width()-c+(this._coordinates[b-1]||0))/2*(this.settings.rtl?-1:1)):c=this._coordinates[b-1]||0,c)},e.prototype.duration=function(a,b,c){return Math.min(Math.max(Math.abs(b-a),1),6)*Math.abs(c||this.settings.smartSpeed)},e.prototype.to=function(a,b){var c=this.current(),d=null,e=a-this.relative(c),f=(e>0)-(0>e),g=this._items.length,h=this.minimum(),i=this.maximum();this.settings.loop?(!this.settings.rewind&&Math.abs(e)>g/2&&(e+=-1*f*g),a=c+e,d=((a-h)%g+g)%g+h,d!==a&&i>=d-e&&d-e>0&&(c=d-e,a=d,this.reset(c))):this.settings.rewind?(i+=1,a=(a%i+i)%i):a=Math.max(h,Math.min(i,a)),this.speed(this.duration(c,a,b)),this.current(a),this.$element.is(":visible")&&this.update()},e.prototype.next=function(a){a=a||!1,this.to(this.relative(this.current())+1,a)},e.prototype.prev=function(a){a=a||!1,this.to(this.relative(this.current())-1,a)},e.prototype.onTransitionEnd=function(a){return a!==d&&(a.stopPropagation(),(a.target||a.srcElement||a.originalTarget)!==this.$stage.get(0))?!1:(this.leave("animating"),void this.trigger("translated"))},e.prototype.viewport=function(){var d;if(this.options.responsiveBaseElement!==b)d=a(this.options.responsiveBaseElement).width();else if(b.innerWidth)d=b.innerWidth;else{if(!c.documentElement||!c.documentElement.clientWidth)throw"Can not detect viewport width.";d=c.documentElement.clientWidth}return d},e.prototype.replace=function(b){this.$stage.empty(),this._items=[],b&&(b=b instanceof jQuery?b:a(b)),this.settings.nestedItemSelector&&(b=b.find("."+this.settings.nestedItemSelector)),b.filter(function(){return 1===this.nodeType}).each(a.proxy(function(a,b){b=this.prepare(b),this.$stage.append(b),this._items.push(b),this._mergers.push(1*b.find("[data-merge]").andSelf("[data-merge]").attr("data-merge")||1)},this)),this.reset(a.isNumeric(this.settings.startPosition)?this.settings.startPosition:0),this.invalidate("items")},e.prototype.add=function(b,c){var e=this.relative(this._current);c=c===d?this._items.length:this.normalize(c,!0),b=b instanceof jQuery?b:a(b),this.trigger("add",{content:b,position:c}),b=this.prepare(b),0===this._items.length||c===this._items.length?(0===this._items.length&&this.$stage.append(b),0!==this._items.length&&this._items[c-1].after(b),this._items.push(b),this._mergers.push(1*b.find("[data-merge]").andSelf("[data-merge]").attr("data-merge")||1)):(this._items[c].before(b),this._items.splice(c,0,b),this._mergers.splice(c,0,1*b.find("[data-merge]").andSelf("[data-merge]").attr("data-merge")||1)),this._items[e]&&this.reset(this._items[e].index()),this.invalidate("items"),this.trigger("added",{content:b,position:c})},e.prototype.remove=function(a){a=this.normalize(a,!0),a!==d&&(this.trigger("remove",{content:this._items[a],position:a}),this._items[a].remove(),this._items.splice(a,1),this._mergers.splice(a,1),this.invalidate("items"),this.trigger("removed",{content:null,position:a}))},e.prototype.preloadAutoWidthImages=function(b){b.each(a.proxy(function(b,c){this.enter("pre-loading"),c=a(c),a(new Image).one("load",a.proxy(function(a){c.attr("src",a.target.src),c.css("opacity",1),this.leave("pre-loading"),!this.is("pre-loading")&&!this.is("initializing")&&this.refresh()},this)).attr("src",c.attr("src")||c.attr("data-src")||c.attr("data-src-retina"))},this))},e.prototype.destroy=function(){this.$element.off(".owl.core"),this.$stage.off(".owl.core"),a(c).off(".owl.core"),this.settings.responsive!==!1&&(b.clearTimeout(this.resizeTimer),this.off(b,"resize",this._handlers.onThrottledResize));for(var d in this._plugins)this._plugins[d].destroy();this.$stage.children(".cloned").remove(),this.$stage.unwrap(),this.$stage.children().contents().unwrap(),this.$stage.children().unwrap(),this.$element.removeClass(this.options.refreshClass).removeClass(this.options.loadingClass).removeClass(this.options.loadedClass).removeClass(this.options.rtlClass).removeClass(this.options.dragClass).removeClass(this.options.grabClass).attr("class",this.$element.attr("class").replace(new RegExp(this.options.responsiveClass+"-\\S+\\s","g"),"")).removeData("owl.carousel")},e.prototype.op=function(a,b,c){var d=this.settings.rtl;switch(b){case"<":return d?a>c:c>a;case">":return d?c>a:a>c;case">=":return d?c>=a:a>=c;case"<=":return d?a>=c:c>=a}},e.prototype.on=function(a,b,c,d){a.addEventListener?a.addEventListener(b,c,d):a.attachEvent&&a.attachEvent("on"+b,c)},e.prototype.off=function(a,b,c,d){a.removeEventListener?a.removeEventListener(b,c,d):a.detachEvent&&a.detachEvent("on"+b,c)},e.prototype.trigger=function(b,c,d,f,g){var h={item:{count:this._items.length,index:this.current()}},i=a.camelCase(a.grep(["on",b,d],function(a){return a}).join("-").toLowerCase()),j=a.Event([b,"owl",d||"carousel"].join(".").toLowerCase(),a.extend({relatedTarget:this},h,c));return this._supress[b]||(a.each(this._plugins,function(a,b){b.onTrigger&&b.onTrigger(j)}),this.register({type:e.Type.Event,name:b}),this.$element.trigger(j),this.settings&&"function"==typeof this.settings[i]&&this.settings[i].call(this,j)),j},e.prototype.enter=function(b){a.each([b].concat(this._states.tags[b]||[]),a.proxy(function(a,b){this._states.current[b]===d&&(this._states.current[b]=0),this._states.current[b]++},this))},e.prototype.leave=function(b){a.each([b].concat(this._states.tags[b]||[]),a.proxy(function(a,b){this._states.current[b]--},this))},e.prototype.register=function(b){if(b.type===e.Type.Event){if(a.event.special[b.name]||(a.event.special[b.name]={}),!a.event.special[b.name].owl){var c=a.event.special[b.name]._default;a.event.special[b.name]._default=function(a){return!c||!c.apply||a.namespace&&-1!==a.namespace.indexOf("owl")?a.namespace&&a.namespace.indexOf("owl")>-1:c.apply(this,arguments)},a.event.special[b.name].owl=!0}}else b.type===e.Type.State&&(this._states.tags[b.name]?this._states.tags[b.name]=this._states.tags[b.name].concat(b.tags):this._states.tags[b.name]=b.tags,this._states.tags[b.name]=a.grep(this._states.tags[b.name],a.proxy(function(c,d){return a.inArray(c,this._states.tags[b.name])===d},this)))},e.prototype.suppress=function(b){a.each(b,a.proxy(function(a,b){this._supress[b]=!0},this))},e.prototype.release=function(b){a.each(b,a.proxy(function(a,b){delete this._supress[b]},this))},e.prototype.pointer=function(a){var c={x:null,y:null};return a=a.originalEvent||a||b.event,a=a.touches&&a.touches.length?a.touches[0]:a.changedTouches&&a.changedTouches.length?a.changedTouches[0]:a,a.pageX?(c.x=a.pageX,c.y=a.pageY):(c.x=a.clientX,c.y=a.clientY),c},e.prototype.difference=function(a,b){return{x:a.x-b.x,y:a.y-b.y}},a.fn.owlCarousel=function(b){var c=Array.prototype.slice.call(arguments,1);return this.each(function(){var d=a(this),f=d.data("owl.carousel");f||(f=new e(this,"object"==typeof b&&b),d.data("owl.carousel",f),a.each(["next","prev","to","destroy","refresh","replace","add","remove"],function(b,c){f.register({type:e.Type.Event,name:c}),f.$element.on(c+".owl.carousel.core",a.proxy(function(a){a.namespace&&a.relatedTarget!==this&&(this.suppress([c]),f[c].apply(this,[].slice.call(arguments,1)),this.release([c]))},f))})),"string"==typeof b&&"_"!==b.charAt(0)&&f[b].apply(f,c)})},a.fn.owlCarousel.Constructor=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this._core=b,this._interval=null,this._visible=null,this._handlers={"initialized.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.autoRefresh&&this.watch()},this)},this._core.options=a.extend({},e.Defaults,this._core.options),this._core.$element.on(this._handlers)};e.Defaults={autoRefresh:!0,autoRefreshInterval:500},e.prototype.watch=function(){this._interval||(this._visible=this._core.$element.is(":visible"),this._interval=b.setInterval(a.proxy(this.refresh,this),this._core.settings.autoRefreshInterval))},e.prototype.refresh=function(){this._core.$element.is(":visible")!==this._visible&&(this._visible=!this._visible,this._core.$element.toggleClass("owl-hidden",!this._visible),this._visible&&this._core.invalidate("width")&&this._core.refresh())},e.prototype.destroy=function(){var a,c;b.clearInterval(this._interval);for(a in this._handlers)this._core.$element.off(a,this._handlers[a]);for(c in Object.getOwnPropertyNames(this))"function"!=typeof this[c]&&(this[c]=null)},a.fn.owlCarousel.Constructor.Plugins.AutoRefresh=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this._core=b,this._loaded=[],this._handlers={"initialized.owl.carousel change.owl.carousel":a.proxy(function(b){if(b.namespace&&this._core.settings&&this._core.settings.lazyLoad&&(b.property&&"position"==b.property.name||"initialized"==b.type))for(var c=this._core.settings,d=c.center&&Math.ceil(c.items/2)||c.items,e=c.center&&-1*d||0,f=(b.property&&b.property.value||this._core.current())+e,g=this._core.clones().length,h=a.proxy(function(a,b){this.load(b)},this);e++<d;)this.load(g/2+this._core.relative(f)),g&&a.each(this._core.clones(this._core.relative(f)),h),f++},this)},this._core.options=a.extend({},e.Defaults,this._core.options),this._core.$element.on(this._handlers)};e.Defaults={lazyLoad:!1},e.prototype.load=function(c){var d=this._core.$stage.children().eq(c),e=d&&d.find(".owl-lazy");!e||a.inArray(d.get(0),this._loaded)>-1||(e.each(a.proxy(function(c,d){var e,f=a(d),g=b.devicePixelRatio>1&&f.attr("data-src-retina")||f.attr("data-src");this._core.trigger("load",{element:f,url:g},"lazy"),f.is("img")?f.one("load.owl.lazy",a.proxy(function(){f.css("opacity",1),this._core.trigger("loaded",{element:f,url:g},"lazy")},this)).attr("src",g):(e=new Image,e.onload=a.proxy(function(){f.css({"background-image":"url("+g+")",opacity:"1"}),this._core.trigger("loaded",{element:f,url:g},"lazy")},this),e.src=g)},this)),this._loaded.push(d.get(0)))},e.prototype.destroy=function(){var a,b;for(a in this.handlers)this._core.$element.off(a,this.handlers[a]);for(b in Object.getOwnPropertyNames(this))"function"!=typeof this[b]&&(this[b]=null)},a.fn.owlCarousel.Constructor.Plugins.Lazy=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this._core=b,this._handlers={"initialized.owl.carousel refreshed.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.autoHeight&&this.update()},this),"changed.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.autoHeight&&"position"==a.property.name&&this.update()},this),"loaded.owl.lazy":a.proxy(function(a){a.namespace&&this._core.settings.autoHeight&&a.element.closest("."+this._core.settings.itemClass).index()===this._core.current()&&this.update()},this)},this._core.options=a.extend({},e.Defaults,this._core.options),this._core.$element.on(this._handlers)};e.Defaults={autoHeight:!1,autoHeightClass:"owl-height"},e.prototype.update=function(){var b=this._core._current,c=b+this._core.settings.items,d=this._core.$stage.children().toArray().slice(b,c);heights=[],maxheight=0,a.each(d,function(b,c){heights.push(a(c).height())}),maxheight=Math.max.apply(null,heights),this._core.$stage.parent().height(maxheight).addClass(this._core.settings.autoHeightClass)},e.prototype.destroy=function(){var a,b;for(a in this._handlers)this._core.$element.off(a,this._handlers[a]);for(b in Object.getOwnPropertyNames(this))"function"!=typeof this[b]&&(this[b]=null)},a.fn.owlCarousel.Constructor.Plugins.AutoHeight=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this._core=b,this._videos={},this._playing=null,this._handlers={"initialized.owl.carousel":a.proxy(function(a){a.namespace&&this._core.register({type:"state",name:"playing",tags:["interacting"]})},this),"resize.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.video&&this.isInFullScreen()&&a.preventDefault()},this),"refreshed.owl.carousel":a.proxy(function(a){a.namespace&&this._core.is("resizing")&&this._core.$stage.find(".cloned .owl-video-frame").remove()},this),"changed.owl.carousel":a.proxy(function(a){a.namespace&&"position"===a.property.name&&this._playing&&this.stop()},this),"prepared.owl.carousel":a.proxy(function(b){if(b.namespace){var c=a(b.content).find(".owl-video");c.length&&(c.css("display","none"),this.fetch(c,a(b.content)))}},this)},this._core.options=a.extend({},e.Defaults,this._core.options),this._core.$element.on(this._handlers),this._core.$element.on("click.owl.video",".owl-video-play-icon",a.proxy(function(a){this.play(a)},this))};e.Defaults={video:!1,videoHeight:!1,videoWidth:!1},e.prototype.fetch=function(a,b){var c=a.attr("data-vimeo-id")?"vimeo":"youtube",d=a.attr("data-vimeo-id")||a.attr("data-youtube-id"),e=a.attr("data-width")||this._core.settings.videoWidth,f=a.attr("data-height")||this._core.settings.videoHeight,g=a.attr("href");if(!g)throw new Error("Missing video URL.");if(d=g.match(/(http:|https:|)\/\/(player.|www.)?(vimeo\.com|youtu(be\.com|\.be|be\.googleapis\.com))\/(video\/|embed\/|watch\?v=|v\/)?([A-Za-z0-9._%-]*)(\&\S+)?/),d[3].indexOf("youtu")>-1)c="youtube";else{if(!(d[3].indexOf("vimeo")>-1))throw new Error("Video URL not supported.");c="vimeo"}d=d[6],this._videos[g]={type:c,id:d,width:e,height:f},b.attr("data-video",g),this.thumbnail(a,this._videos[g])},e.prototype.thumbnail=function(b,c){var d,e,f,g=c.width&&c.height?'style="width:'+c.width+"px;height:"+c.height+'px;"':"",h=b.find("img"),i="src",j="",k=this._core.settings,l=function(a){e='<div class="owl-video-play-icon"></div>',d=k.lazyLoad?'<div class="owl-video-tn '+j+'" '+i+'="'+a+'"></div>':'<div class="owl-video-tn" style="opacity:1;background-image:url('+a+')"></div>',b.after(d),b.after(e)};return b.wrap('<div class="owl-video-wrapper"'+g+"></div>"),this._core.settings.lazyLoad&&(i="data-src",j="owl-lazy"),h.length?(l(h.attr(i)),h.remove(),!1):void("youtube"===c.type?(f="http://img.youtube.com/vi/"+c.id+"/hqdefault.jpg",l(f)):"vimeo"===c.type&&a.ajax({type:"GET",url:"http://vimeo.com/api/v2/video/"+c.id+".json",jsonp:"callback",dataType:"jsonp",success:function(a){f=a[0].thumbnail_large,l(f)}}))},e.prototype.stop=function(){this._core.trigger("stop",null,"video"),this._playing.find(".owl-video-frame").remove(),this._playing.removeClass("owl-video-playing"),this._playing=null,this._core.leave("playing"),this._core.trigger("stopped",null,"video")},e.prototype.play=function(b){var c,d=a(b.target),e=d.closest("."+this._core.settings.itemClass),f=this._videos[e.attr("data-video")],g=f.width||"100%",h=f.height||this._core.$stage.height();this._playing||(this._core.enter("playing"),this._core.trigger("play",null,"video"),e=this._core.items(this._core.relative(e.index())),this._core.reset(e.index()),"youtube"===f.type?c='<iframe width="'+g+'" height="'+h+'" src="http://www.youtube.com/embed/'+f.id+"?autoplay=1&v="+f.id+'" frameborder="0" allowfullscreen></iframe>':"vimeo"===f.type&&(c='<iframe src="http://player.vimeo.com/video/'+f.id+'?autoplay=1" width="'+g+'" height="'+h+'" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>'),a('<div class="owl-video-frame">'+c+"</div>").insertAfter(e.find(".owl-video")),this._playing=e.addClass("owl-video-playing"))},e.prototype.isInFullScreen=function(){var b=c.fullscreenElement||c.mozFullScreenElement||c.webkitFullscreenElement;return b&&a(b).parent().hasClass("owl-video-frame")},e.prototype.destroy=function(){var a,b;this._core.$element.off("click.owl.video");for(a in this._handlers)this._core.$element.off(a,this._handlers[a]);for(b in Object.getOwnPropertyNames(this))"function"!=typeof this[b]&&(this[b]=null)},a.fn.owlCarousel.Constructor.Plugins.Video=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this.core=b,this.core.options=a.extend({},e.Defaults,this.core.options),this.swapping=!0,this.previous=d,this.next=d,this.handlers={"change.owl.carousel":a.proxy(function(a){a.namespace&&"position"==a.property.name&&(this.previous=this.core.current(),this.next=a.property.value)},this),"drag.owl.carousel dragged.owl.carousel translated.owl.carousel":a.proxy(function(a){a.namespace&&(this.swapping="translated"==a.type)},this),"translate.owl.carousel":a.proxy(function(a){a.namespace&&this.swapping&&(this.core.options.animateOut||this.core.options.animateIn)&&this.swap()},this)},this.core.$element.on(this.handlers)};e.Defaults={animateOut:!1,animateIn:!1},e.prototype.swap=function(){if(1===this.core.settings.items&&a.support.animation&&a.support.transition){this.core.speed(0);var b,c=a.proxy(this.clear,this),d=this.core.$stage.children().eq(this.previous),e=this.core.$stage.children().eq(this.next),f=this.core.settings.animateIn,g=this.core.settings.animateOut;this.core.current()!==this.previous&&(g&&(b=this.core.coordinates(this.previous)-this.core.coordinates(this.next),d.one(a.support.animation.end,c).css({left:b+"px"}).addClass("animated owl-animated-out").addClass(g)),f&&e.one(a.support.animation.end,c).addClass("animated owl-animated-in").addClass(f))}},e.prototype.clear=function(b){a(b.target).css({left:""}).removeClass("animated owl-animated-out owl-animated-in").removeClass(this.core.settings.animateIn).removeClass(this.core.settings.animateOut),this.core.onTransitionEnd()},e.prototype.destroy=function(){var a,b;for(a in this.handlers)this.core.$element.off(a,this.handlers[a]);for(b in Object.getOwnPropertyNames(this))"function"!=typeof this[b]&&(this[b]=null)},a.fn.owlCarousel.Constructor.Plugins.Animate=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this._core=b,this._interval=null,this._paused=!1,this._handlers={"changed.owl.carousel":a.proxy(function(a){a.namespace&&"settings"===a.property.name&&(this._core.settings.autoplay?this.play():this.stop())},this),"initialized.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.autoplay&&this.play()},this),"play.owl.autoplay":a.proxy(function(a,b,c){a.namespace&&this.play(b,c)},this),"stop.owl.autoplay":a.proxy(function(a){a.namespace&&this.stop()},this),"mouseover.owl.autoplay":a.proxy(function(){this._core.settings.autoplayHoverPause&&this._core.is("rotating")&&this.pause()},this),"mouseleave.owl.autoplay":a.proxy(function(){
this._core.settings.autoplayHoverPause&&this._core.is("rotating")&&this.play()},this)},this._core.$element.on(this._handlers),this._core.options=a.extend({},e.Defaults,this._core.options)};e.Defaults={autoplay:!1,autoplayTimeout:5e3,autoplayHoverPause:!1,autoplaySpeed:!1},e.prototype.play=function(d,e){this._paused=!1,this._core.is("rotating")||(this._core.enter("rotating"),this._interval=b.setInterval(a.proxy(function(){this._paused||this._core.is("busy")||this._core.is("interacting")||c.hidden||this._core.next(e||this._core.settings.autoplaySpeed)},this),d||this._core.settings.autoplayTimeout))},e.prototype.stop=function(){this._core.is("rotating")&&(b.clearInterval(this._interval),this._core.leave("rotating"))},e.prototype.pause=function(){this._core.is("rotating")&&(this._paused=!0)},e.prototype.destroy=function(){var a,b;this.stop();for(a in this._handlers)this._core.$element.off(a,this._handlers[a]);for(b in Object.getOwnPropertyNames(this))"function"!=typeof this[b]&&(this[b]=null)},a.fn.owlCarousel.Constructor.Plugins.autoplay=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){"use strict";var e=function(b){this._core=b,this._initialized=!1,this._pages=[],this._controls={},this._templates=[],this.$element=this._core.$element,this._overrides={next:this._core.next,prev:this._core.prev,to:this._core.to},this._handlers={"prepared.owl.carousel":a.proxy(function(b){b.namespace&&this._core.settings.dotsData&&this._templates.push('<div class="'+this._core.settings.dotClass+'">'+a(b.content).find("[data-dot]").andSelf("[data-dot]").attr("data-dot")+"</div>")},this),"added.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.dotsData&&this._templates.splice(a.position,0,this._templates.pop())},this),"remove.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.dotsData&&this._templates.splice(a.position,1)},this),"changed.owl.carousel":a.proxy(function(a){a.namespace&&"position"==a.property.name&&this.draw()},this),"initialized.owl.carousel":a.proxy(function(a){a.namespace&&!this._initialized&&(this._core.trigger("initialize",null,"navigation"),this.initialize(),this.update(),this.draw(),this._initialized=!0,this._core.trigger("initialized",null,"navigation"))},this),"refreshed.owl.carousel":a.proxy(function(a){a.namespace&&this._initialized&&(this._core.trigger("refresh",null,"navigation"),this.update(),this.draw(),this._core.trigger("refreshed",null,"navigation"))},this)},this._core.options=a.extend({},e.Defaults,this._core.options),this.$element.on(this._handlers)};e.Defaults={nav:!1,navText:["prev","next"],navSpeed:!1,navElement:"div",navContainer:!1,navContainerClass:"owl-nav",navClass:["owl-prev","owl-next"],slideBy:1,dotClass:"owl-dot",dotsClass:"owl-dots",dots:!0,dotsEach:!1,dotsData:!1,dotsSpeed:!1,dotsContainer:!1},e.prototype.initialize=function(){var b,c=this._core.settings;this._controls.$relative=(c.navContainer?a(c.navContainer):a("<div>").addClass(c.navContainerClass).appendTo(this.$element)).addClass("disabled"),this._controls.$previous=a("<"+c.navElement+">").addClass(c.navClass[0]).html(c.navText[0]).prependTo(this._controls.$relative).on("click",a.proxy(function(a){this.prev(c.navSpeed)},this)),this._controls.$next=a("<"+c.navElement+">").addClass(c.navClass[1]).html(c.navText[1]).appendTo(this._controls.$relative).on("click",a.proxy(function(a){this.next(c.navSpeed)},this)),c.dotsData||(this._templates=[a("<div>").addClass(c.dotClass).append(a("<span>")).prop("outerHTML")]),this._controls.$absolute=(c.dotsContainer?a(c.dotsContainer):a("<div>").addClass(c.dotsClass).appendTo(this.$element)).addClass("disabled"),this._controls.$absolute.on("click","div",a.proxy(function(b){var d=a(b.target).parent().is(this._controls.$absolute)?a(b.target).index():a(b.target).parent().index();b.preventDefault(),this.to(d,c.dotsSpeed)},this));for(b in this._overrides)this._core[b]=a.proxy(this[b],this)},e.prototype.destroy=function(){var a,b,c,d;for(a in this._handlers)this.$element.off(a,this._handlers[a]);for(b in this._controls)this._controls[b].remove();for(d in this.overides)this._core[d]=this._overrides[d];for(c in Object.getOwnPropertyNames(this))"function"!=typeof this[c]&&(this[c]=null)},e.prototype.update=function(){var a,b,c,d=this._core.clones().length/2,e=d+this._core.items().length,f=this._core.maximum(!0),g=this._core.settings,h=g.center||g.autoWidth||g.dotsData?1:g.dotsEach||g.items;if("page"!==g.slideBy&&(g.slideBy=Math.min(g.slideBy,g.items)),g.dots||"page"==g.slideBy)for(this._pages=[],a=d,b=0,c=0;e>a;a++){if(b>=h||0===b){if(this._pages.push({start:Math.min(f,a-d),end:a-d+h-1}),Math.min(f,a-d)===f)break;b=0,++c}b+=this._core.mergers(this._core.relative(a))}},e.prototype.draw=function(){var b,c=this._core.settings,d=this._core.items().length<=c.items,e=this._core.relative(this._core.current()),f=c.loop||c.rewind;this._controls.$relative.toggleClass("disabled",!c.nav||d),c.nav&&(this._controls.$previous.toggleClass("disabled",!f&&e<=this._core.minimum(!0)),this._controls.$next.toggleClass("disabled",!f&&e>=this._core.maximum(!0))),this._controls.$absolute.toggleClass("disabled",!c.dots||d),c.dots&&(b=this._pages.length-this._controls.$absolute.children().length,c.dotsData&&0!==b?this._controls.$absolute.html(this._templates.join("")):b>0?this._controls.$absolute.append(new Array(b+1).join(this._templates[0])):0>b&&this._controls.$absolute.children().slice(b).remove(),this._controls.$absolute.find(".active").removeClass("active"),this._controls.$absolute.children().eq(a.inArray(this.current(),this._pages)).addClass("active"))},e.prototype.onTrigger=function(b){var c=this._core.settings;b.page={index:a.inArray(this.current(),this._pages),count:this._pages.length,size:c&&(c.center||c.autoWidth||c.dotsData?1:c.dotsEach||c.items)}},e.prototype.current=function(){var b=this._core.relative(this._core.current());return a.grep(this._pages,a.proxy(function(a,c){return a.start<=b&&a.end>=b},this)).pop()},e.prototype.getPosition=function(b){var c,d,e=this._core.settings;return"page"==e.slideBy?(c=a.inArray(this.current(),this._pages),d=this._pages.length,b?++c:--c,c=this._pages[(c%d+d)%d].start):(c=this._core.relative(this._core.current()),d=this._core.items().length,b?c+=e.slideBy:c-=e.slideBy),c},e.prototype.next=function(b){a.proxy(this._overrides.to,this._core)(this.getPosition(!0),b)},e.prototype.prev=function(b){a.proxy(this._overrides.to,this._core)(this.getPosition(!1),b)},e.prototype.to=function(b,c,d){var e;d?a.proxy(this._overrides.to,this._core)(b,c):(e=this._pages.length,a.proxy(this._overrides.to,this._core)(this._pages[(b%e+e)%e].start,c))},a.fn.owlCarousel.Constructor.Plugins.Navigation=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){"use strict";var e=function(c){this._core=c,this._hashes={},this.$element=this._core.$element,this._handlers={"initialized.owl.carousel":a.proxy(function(c){c.namespace&&"URLHash"===this._core.settings.startPosition&&a(b).trigger("hashchange.owl.navigation")},this),"prepared.owl.carousel":a.proxy(function(b){if(b.namespace){var c=a(b.content).find("[data-hash]").andSelf("[data-hash]").attr("data-hash");if(!c)return;this._hashes[c]=b.content}},this),"changed.owl.carousel":a.proxy(function(c){if(c.namespace&&"position"===c.property.name){var d=this._core.items(this._core.relative(this._core.current())),e=a.map(this._hashes,function(a,b){return a===d?b:null}).join();if(!e||b.location.hash.slice(1)===e)return;b.location.hash=e}},this)},this._core.options=a.extend({},e.Defaults,this._core.options),this.$element.on(this._handlers),a(b).on("hashchange.owl.navigation",a.proxy(function(a){var c=b.location.hash.substring(1),e=this._core.$stage.children(),f=this._hashes[c]&&e.index(this._hashes[c]);f!==d&&f!==this._core.current()&&this._core.to(this._core.relative(f),!1,!0)},this))};e.Defaults={URLhashListener:!1},e.prototype.destroy=function(){var c,d;a(b).off("hashchange.owl.navigation");for(c in this._handlers)this._core.$element.off(c,this._handlers[c]);for(d in Object.getOwnPropertyNames(this))"function"!=typeof this[d]&&(this[d]=null)},a.fn.owlCarousel.Constructor.Plugins.Hash=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){function e(b,c){var e=!1,f=b.charAt(0).toUpperCase()+b.slice(1);return a.each((b+" "+h.join(f+" ")+f).split(" "),function(a,b){return g[b]!==d?(e=c?b:!0,!1):void 0}),e}function f(a){return e(a,!0)}var g=a("<support>").get(0).style,h="Webkit Moz O ms".split(" "),i={transition:{end:{WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"}},animation:{end:{WebkitAnimation:"webkitAnimationEnd",MozAnimation:"animationend",OAnimation:"oAnimationEnd",animation:"animationend"}}},j={csstransforms:function(){return!!e("transform")},csstransforms3d:function(){return!!e("perspective")},csstransitions:function(){return!!e("transition")},cssanimations:function(){return!!e("animation")}};j.csstransitions()&&(a.support.transition=new String(f("transition")),a.support.transition.end=i.transition.end[a.support.transition]),j.cssanimations()&&(a.support.animation=new String(f("animation")),a.support.animation.end=i.animation.end[a.support.animation]),j.csstransforms()&&(a.support.transform=new String(f("transform")),a.support.transform3d=j.csstransforms3d())}(window.Zepto||window.jQuery,window,document);
// Basic polyfill for window animation frame.
(function(window) {
    // polyfill for window.requestAnimationFrame
    if (!window.requestAnimationFrame) {
        window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || setTimeout;
    }
    if (!window.cancelAnimationFrame) {
        window.cancelAnimationFrame = window.cancelAnimationFrame || window.mozRequestCancelFrame || window.webkitCancelAnimationFrame || window.msCancelAnimationFrame || clearTimeout;
    }
}(window));

/*!     uuid.js
*
*     Copyright (c) 2010-2012 Robert Kieffer
*     MIT License - http://opensource.org/licenses/mit-license.php
*/
(function() {
  var _global = this;

  // Unique ID creation requires a high quality random # generator.  We feature
  // detect to determine the best RNG source, normalizing to a function that
  // returns 128-bits of randomness, since that's what's usually required
  var _rng;

  // Allow for MSIE11 msCrypto
  var _crypto = _global.crypto || _global.msCrypto;

  // Node.js crypto-based RNG - http://nodejs.org/docs/v0.6.2/api/crypto.html
  //
  // Moderately fast, high quality
  if (typeof(_global.require) == 'function') {
    try {
      var _rb = _global.require('crypto').randomBytes;
      _rng = _rb && function() {return _rb(16);};
    } catch(e) {}
  }

  if (!_rng && _crypto && _crypto.getRandomValues) {
    // WHATWG crypto-based RNG - http://wiki.whatwg.org/wiki/Crypto
    //
    // Moderately fast, high quality
    var _rnds8 = new Uint8Array(16);
    _rng = function whatwgRNG() {
      _crypto.getRandomValues(_rnds8);
      return _rnds8;
    };
  }

  if (!_rng) {
    // Math.random()-based (RNG)
    //
    // If all else fails, use Math.random().  It's fast, but is of unspecified
    // quality.
    var  _rnds = new Array(16);
    _rng = function() {
      for (var i = 0, r; i < 16; i++) {
        if ((i & 0x03) === 0) r = Math.random() * 0x100000000;
        _rnds[i] = r >>> ((i & 0x03) << 3) & 0xff;
      }

      return _rnds;
    };
  }

  // Buffer class to use
  var BufferClass = typeof(_global.Buffer) == 'function' ? _global.Buffer : Array;

  // Maps for number <-> hex string conversion
  var _byteToHex = [];
  var _hexToByte = {};
  for (var i = 0; i < 256; i++) {
    _byteToHex[i] = (i + 0x100).toString(16).substr(1);
    _hexToByte[_byteToHex[i]] = i;
  }

  // **`parse()` - Parse a UUID into it's component bytes**
  function parse(s, buf, offset) {
    var i = (buf && offset) || 0, ii = 0;

    buf = buf || [];
    s.toLowerCase().replace(/[0-9a-f]{2}/g, function(oct) {
      if (ii < 16) { // Don't overflow!
        buf[i + ii++] = _hexToByte[oct];
      }
    });

    // Zero out remaining bytes if string was short
    while (ii < 16) {
      buf[i + ii++] = 0;
    }

    return buf;
  }

  // **`unparse()` - Convert UUID byte array (ala parse()) into a string**
  function unparse(buf, offset) {
    var i = offset || 0, bth = _byteToHex;
    return  bth[buf[i++]] + bth[buf[i++]] +
            bth[buf[i++]] + bth[buf[i++]] + '-' +
            bth[buf[i++]] + bth[buf[i++]] + '-' +
            bth[buf[i++]] + bth[buf[i++]] + '-' +
            bth[buf[i++]] + bth[buf[i++]] + '-' +
            bth[buf[i++]] + bth[buf[i++]] +
            bth[buf[i++]] + bth[buf[i++]] +
            bth[buf[i++]] + bth[buf[i++]];
  }

  // **`v1()` - Generate time-based UUID**
  //
  // Inspired by https://github.com/LiosK/UUID.js
  // and http://docs.python.org/library/uuid.html

  // random #'s we need to init node and clockseq
  var _seedBytes = _rng();

  // Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1)
  var _nodeId = [
    _seedBytes[0] | 0x01,
    _seedBytes[1], _seedBytes[2], _seedBytes[3], _seedBytes[4], _seedBytes[5]
  ];

  // Per 4.2.2, randomize (14 bit) clockseq
  var _clockseq = (_seedBytes[6] << 8 | _seedBytes[7]) & 0x3fff;

  // Previous uuid creation time
  var _lastMSecs = 0, _lastNSecs = 0;

  // See https://github.com/broofa/node-uuid for API details
  function v1(options, buf, offset) {
    var i = buf && offset || 0;
    var b = buf || [];

    options = options || {};

    var clockseq = options.clockseq != null ? options.clockseq : _clockseq;

    // UUID timestamps are 100 nano-second units since the Gregorian epoch,
    // (1582-10-15 00:00).  JSNumbers aren't precise enough for this, so
    // time is handled internally as 'msecs' (integer milliseconds) and 'nsecs'
    // (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01 00:00.
    var msecs = options.msecs != null ? options.msecs : new Date().getTime();

    // Per 4.2.1.2, use count of uuid's generated during the current clock
    // cycle to simulate higher resolution clock
    var nsecs = options.nsecs != null ? options.nsecs : _lastNSecs + 1;

    // Time since last uuid creation (in msecs)
    var dt = (msecs - _lastMSecs) + (nsecs - _lastNSecs)/10000;

    // Per 4.2.1.2, Bump clockseq on clock regression
    if (dt < 0 && options.clockseq == null) {
      clockseq = clockseq + 1 & 0x3fff;
    }

    // Reset nsecs if clock regresses (new clockseq) or we've moved onto a new
    // time interval
    if ((dt < 0 || msecs > _lastMSecs) && options.nsecs == null) {
      nsecs = 0;
    }

    // Per 4.2.1.2 Throw error if too many uuids are requested
    if (nsecs >= 10000) {
      throw new Error('uuid.v1(): Can\'t create more than 10M uuids/sec');
    }

    _lastMSecs = msecs;
    _lastNSecs = nsecs;
    _clockseq = clockseq;

    // Per 4.1.4 - Convert from unix epoch to Gregorian epoch
    msecs += 12219292800000;

    // `time_low`
    var tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000;
    b[i++] = tl >>> 24 & 0xff;
    b[i++] = tl >>> 16 & 0xff;
    b[i++] = tl >>> 8 & 0xff;
    b[i++] = tl & 0xff;

    // `time_mid`
    var tmh = (msecs / 0x100000000 * 10000) & 0xfffffff;
    b[i++] = tmh >>> 8 & 0xff;
    b[i++] = tmh & 0xff;

    // `time_high_and_version`
    b[i++] = tmh >>> 24 & 0xf | 0x10; // include version
    b[i++] = tmh >>> 16 & 0xff;

    // `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant)
    b[i++] = clockseq >>> 8 | 0x80;

    // `clock_seq_low`
    b[i++] = clockseq & 0xff;

    // `node`
    var node = options.node || _nodeId;
    for (var n = 0; n < 6; n++) {
      b[i + n] = node[n];
    }

    return buf ? buf : unparse(b);
  }

  // **`v4()` - Generate random UUID**

  // See https://github.com/broofa/node-uuid for API details
  function v4(options, buf, offset) {
    // Deprecated - 'format' argument, as supported in v1.2
    var i = buf && offset || 0;

    if (typeof(options) == 'string') {
      buf = options == 'binary' ? new BufferClass(16) : null;
      options = null;
    }
    options = options || {};

    var rnds = options.random || (options.rng || _rng)();

    // Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
    rnds[6] = (rnds[6] & 0x0f) | 0x40;
    rnds[8] = (rnds[8] & 0x3f) | 0x80;

    // Copy bytes to buffer, if provided
    if (buf) {
      for (var ii = 0; ii < 16; ii++) {
        buf[i + ii] = rnds[ii];
      }
    }

    return buf || unparse(rnds);
  }

  // Export public API
  var uuid = v4;
  uuid.v1 = v1;
  uuid.v4 = v4;
  uuid.parse = parse;
  uuid.unparse = unparse;
  uuid.BufferClass = BufferClass;

  if (typeof(module) != 'undefined' && module.exports) {
    // Publish as node.js module
    module.exports = uuid;
  } else  if (typeof define === 'function' && define.amd) {
    // Publish as AMD module
    define(function() {return uuid;});
 

  } else {
    // Publish as global (in browsers)
    var _previousRoot = _global.uuid;

    // **`noConflict()` - (browser only) to reset global 'uuid' var**
    uuid.noConflict = function() {
      _global.uuid = _previousRoot;
      return uuid;
    };

    _global.uuid = uuid;
  }
}).call(this);

(function($angular) {

    "use strict";

	//declare core module and its dependencies 
    var app = $angular.module('core', ['storage']);

})(window.angular);
(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.constant('ADM_EVENT', {
        QUEUE_MARKER_UPDATED: 'queue-marker-updated',
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.constant('ASERRORCODE', {
        LICENSE_EXPIRED: 900,
        ERROR_RETRIEVE_TIMESTAMP: 920,
        DEVICE_NOT_FOUND: 921,
        EMPTY_TIMESTAMP: 922,
        INCOMPLETE_INJECTION_PARAMETERS: 923,
        NO_INJECTION_PARAMETERS: 924,
        INVALID_APPSPACE_URL: 925,
        INVALID_DEVICE_GUID: 926,
        PWA_CACHE_ERROR: 927,
        INSUFFICIENT_AVAILABLE_STORAGE: 928,
        RETRYABLE_DOWNLOAD_ERROR: 929,
        DOWNLOAD_TIMEOUT: 930,
        NO_CHANNEL_ASSIGNED: 931,
        CHANNEL_INITIALIZATION_TIMEOUT: 932,
        DEVICE_NOT_REGISTERED: 933,
        NO_CONTENT_AVAILABLE: 934,
        NO_VALID_CONTENT: 935,
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.constant('BROADCAST_ALERT_EVENT', {
        BROADCAST_UPDATE: 'broadcast-update',
        CURRENT_BROADCAST_UPDATE: 'current-broadcast-update',
        CONNECTION_CLOSED: 'connection-closed'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.constant('CACHE_COMMAND', {
        PURGE: 'PURGE',
        REMOVE_ALL: 'REMOVE_ALL',
        ASSIGN_MESSAGE_PORT: 'ASSIGN_MESSAGE_PORT',
        KEEP_ALIVE: 'KEEP_ALIVE',
        ACK_ALIVE: 'ACK_ALIVE',
        LOG: 'LOG',
        SET_MANIFEST: 'SET_MANIFEST'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');
    app.constant('CDN_EVENT', {
        SYSTEM_CDN_FAILURE: 'systemcdnfailure',
        SYSTEM_CDN_UNCONFIGURED: 'systemcdnunconfigured'
    });
})(window.angular);

(function($angular) {
    'use strict';
    $angular.module('core').constant('COMPARE_VALUE', {
        SAME: 'same',
        BIGGER: 'bigger',
        SMALLER: 'smaller',
        INVALID: 'invalid'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.constant('CONSTELLATION_TOKENS_EVENT', {
        CLAIMED: 'constellation-tokens-claimed',
        REFRESHED: 'constellation-tokens-refreshed'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.constant('CONTENT_STATE_EVENT', {
        ITEM_LOAD_ERROR: 'ITEM_LOAD_ERROR',
        NO_CONTENT_AVAILABLE: 'NO_CONTENT_AVAILABLE',
        CONTENT_AVAILABLE: 'CONTENT_AVAILABLE',
        AUTOPLAY_NO_CONTENT: 'AUTOPLAY_NO_CONTENT',
        NO_CHANNEL_ASSIGNED: 'NO_CHANNEL_ASSIGNED',
        END_OF_PLAYLIST: 'END_OF_PLAYLIST',
        NO_VALID_CONTENT: 'NO_VALID_CONTENT',
        CHANNEL_LOADED: 'CHANNEL_LOADED',
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');
    app.constant('CONTENT_STRETCH', {
        CONTAIN: 'contain',
        UNIFORMFILL: 'uniformfill',
        FILL: 'fill',
        COVER: 'cover',
        UNIFORM: 'uniform'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');
    app.constant('CRESTRON_CHANNELS', {
        OCCUPIED: 'crestron-occupied',
        UNOCCUPIED: 'crestron-unoccupied'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');
    app.constant('CRESTRON_MODE', {
        OCCUPIED: 'occupied',
        UNOCCUPIED: 'unoccupied',
        STANDBY: 'standby'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');
    app.constant('GLOBAL_EVENT', {
        VIDEO_BACKMOST_LAYER: 'video-backmost-layer'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.constant('HEARTBEAT_STATUS', {
        IN_PROGRESS: 'in-progress',
        SUCCESS: 'success',
        FAILED: 'failed'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');
    app.constant('INVALID_PAGE_ERROR', {
        WEB_PUBLISHING: 'webpublishing',
        CDN_FAILURE: 'systemcdnfailure',
        CDN_UNCONFIGURED: 'systemcdnunconfigured'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');
    app.constant('MOUSE_CLICK', {
        LEFT: 1
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.constant('NETWORK_CONNECTION_STATE', {
        CONNECTED: 'connected',
        DISCONNECTED: 'disconnected'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');
    app.constant('ORIENTATION', {
        'landscape': 'landscape',
        'portrait': 'portrait',
        'landscape-primary': 'landscape-primary',
        'landscape-secondary': 'landscape-secondary',
        'portrait-primary': 'portrait-primary',
        'portrait-secondary': 'portrait-secondary',
        'unlock': 'unlock'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.constant('PNS_TOPIC_ATTRIBUTE', {
        POST: 'post',
        CHANNEL: 'channel',
        SEPARATOR: '.',
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.constant('PNS_TOPIC_KEY', {
        POST_SOCIAL_CONFIG_UPDATE: 'post.social.config.update',
    });
})(window.angular);

(function($angular) {
    'use strict';

    // name with '-' is for outgoing messages
    // name without '-' is for incoming messages
    var app = $angular.module('core');
    app.constant('POST_MESSAGES', {
        NAME: 'appspaceapp-message',
        REGISTRATION_UPDATE: 'registrationupdate',
        READY: 'ready',
        UNLOADED: 'unloaded',
        STANDBY_PROCESSED: 'standby-processed',
        STANDBY_LOADED: 'standby-loaded',
        OCCUPIED_PROCESSED: 'occupied-processed',
        OCCUPIED_LOADED: 'occupied-loaded',
        UNOCCUPIED_PROCESSED: 'unoccupied-processed',
        UNOCCUPIED_LOADED: 'unoccupied-loaded',
        UNKNOWN: 'unknown-command-received',
        LAYOUT_LOADED: 'layout-loaded',
        WIDGET_POSITION_CHANGED: 'widget-position-changed',
        WIDGET_SELECTED: 'widget-selected',
        NO_WIDGETS_SELECTED: 'no-widgets-selected',
        WIDGET_DOUBLE_CLICKED: 'widget-double-clicked',
        PROFILE_READY: 'profile-ready',
        WIDGET_EDITOR_DISABLED: 'widget-editor-disabled',
        SELECT_WIDGET: 'selectwidget',
        POSITION_WIDGET: 'positionwidget',
        MOVE_WIDGET: 'movewidget',
        SCALE_WIDGET: 'scalewidget',
        SET_GRID_CONFIGURATION: 'setgridconfiguration',
        ENABLE_WIDGET_EDITOR: 'enablewidgeteditor',
        SET_WIDGET_OPACITY: 'setwidgetopacity',
        SET_EDITOR_SCALE: 'seteditorscale',
        PERSIST_DATA: 'backup-app-cache',
        CLEAR_WEB_CACHE: 'clear-web-cache',
        POST_MESSAGE_READY: 'appspaceapp.postmessage.ready',
        WEBVIEW_POST_MESSAGE_INIT: 'appspaceapp.webview.postmessage.init',
        MSWEBVIEW_POST_MESSAGE_INIT: 'appspaceapp.mswebview.postmessage.init',
        APPSPACE_APP_READY: 'appspaceappready',
        WIDGET_END_OF_PLAYLIST: 'widget-end-of-playlist',
        PERSIST_THIRD_PARTY_STATE: 'persist-3rd-party-state',
        PERSISTED_THIRD_PARTY_STATE: 'persisted-3rd-party-state',
        CLEAR_THIRD_PARTY_STATE: 'clear-3rd-party-state',
        CLEARED_THIRD_PARTY_STATE: 'cleared-3rd-party-state',
        INIT: 'init',
        LOAD: 'load',
        ERROR: 'error',
        SOURCE: {
            MICROSOFT: 'microsoft',
            APPSPACE: 'appspace'
        },
        HEALTH: 'health',
        ON_TOPIC_RECEIVED: 'ontopicreceived',
        BRIGHTNESS: 'display.brightness',
        NOTIFY: 'notify',
        UPDATE_RESOURCE_MANIFEST: 'updateresourcemanifest'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');
    app.constant('POST_MESSAGES_ERROR', {
        NOT_REGISTERED: 'device-not-registered',
        CHANNEL_NOT_SET: 'channel-not-set',
        LOADED_SINGLE_CHANNEL: 'loaded-single-channel',
        LOADED_NON_CRESTRON_CHANNEL: 'loaded-non-crestron-channel',
        TIMEOUT: 'timeout'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.constant('PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT', {
        SERVICE_STARTED: 'service.started', // Service is started
        SERVICE_STOPPED: 'service.stopped', // Service is stopped
        CLIENT_CONNECTED: 'client.connected', // Fire when successfully connected to the PNS server and authentication completed successfully
        CLIENT_DISCONNECTED: 'client.disconnected', // Fire when the websocket disconnected, before any reconnection attempts or check
        CLIENT_CLOSED: 'client.closed', // PNS client closed permanently without anymore reconnection.
        CLIENT_RECONNECTING: 'client.reconnecting', // Fire when the client is in progress of reconnecting to the PNS server
        CLIENT_RECONNECTED: 'client.reconnected', // Fire when reconnected successfully to the PNS server
        CLIENT_HEARTBEAT_ACKNOWLEDGED: 'client.heartbeat-acknowledged', // Heartbeat action ended with success or failed error.
        WATCHDOG_STARTED: 'watchdog.started', // Watchdog is started
        WATCHDOG_STOPPED: 'watchdog.stopped', // Watchdog is stopped
        WATCGDOG_ALERT: 'watchdog.alert', // Abnormality detected, watchdog will restart the PNS client.
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.constant('PUSH_UPDATE_EVENT', {
        INITIALIZED: 'initialized',
        STARTED: 'started',
        STOPPED: 'stopped',
        TIMESTAMP_UPDATE: 'timestamp-update',
        CHANNEL_ASSIGNED: 'channel-assigned',
        DEVICE_UNREGISTERED: 'device-unregistered',
        DEVICE_TAGS_UPDATED: 'device-tags-updated',
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.constant('REACTION_ICON', {
        CELEBRATE: {
            name: 'celebrate',
            icon: './assets/images/celebrate.svg',
            style: {}
        },
        LOVE: {
            name: 'love',
            icon: './assets/images/love.svg',
            style: {}
        },
        LIKE: {
            name: 'like',
            icon: './assets/images/like.svg',
            style: {}
        },
        LAUGH: {
            name: 'laugh',
            icon: './assets/images/laugh.svg',
            style: {}
        },
        SAD: {
            name: 'sad',
            icon: './assets/images/sad.svg',
            style: {}
        },
        SURPRISE: {
            name: 'surprise',
            icon: './assets/images/surprise.svg',
            style: {}
        }
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');
    app.constant('ROUTE', {
        PREVIEW_SIGN_CHANNEL: '/tv/previewappdisplay',
        INVALID_INJECTION: '/tv/invalid',
        EXCLUSIVE_CHANNEL: '/exclusive/true',
        APP_INIT: '/tv/appinit',
        BROADCASTS: '/tv/broadcasts',
        REGISTRATION_RESTORE_SUCCESS: '/tv/registrationrestoresuccess',
        SPLASH: '/tv/splash',
        STANDBY: '/tv/standby',
        REGISTRATION_FAIL: '/tv/registrationfail',
        REGISTRATION: '/tv/registrationlanguage',
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.constant('SERVICES', {
        SYNC: 'sync-service',
        ADM: 'adm-service',
        JOB_SCHEDULER: 'job-scheduler-service',
        ALERTS: 'alerts-service',
        HEALTH: 'health-service',
        PLAYOUT_METRIC: 'playout-metric-service',
        PUSH_NOTIFICATION_CLIENT: 'push-notification-client-service',
        PUSH_UPDATE: 'push-update-service',
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.constant('SOCIAL_BAR_EVENT', {
        POST_UPDATE: 'post-update',
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.constant('SOFTWARE_UPDATE_EVENT', {
        TARGET_VERSION_UPDATED: 'target-version-updated',
        CONFIGURATION_UPDATED: 'configuration-updated',
        AUTO_UPDATE_CONFIGURATION_UPDATED: 'auto-update-configuration-updated',
        APP_UPDATE_CONFIGURATION_UPDATED: 'app-update-configuration-updated',
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.constant('SYSTEM_DATA', {
        MIN_TIMEOUT_DURATION: 10000,
        ERROR_MESSAGES: {
            NO_APP: 'Almost finished, sign in to your Appspace account and assign a channel to this device.',
            APP_UNASSIGNED: 'No channels are currently available for display. Please assign a channel.',
            INVALID_ID: 'No channel preview available.'
        },
        RETAINED_URL_PARAMS: ['registrationmode', 'crestronmode', 'channel', 'channelname', 'embedprofile', 'embeddedplatform'],
        LAYOUT_BACKGROUND_TRANSPARENT_COLOR: '#010203',
        FORCE_IFRAME_KEY: 'forceiframe',
        IFRAME_ELEMENT: 'iframe',
        PASSIVE_REGISTRATION_MODE: 'passive',
        FINGERPRINT_TYPE: {
            GENERATED: 'generated',
            HARDWARE: 'hardware'
        },
        DEFAULT_IP_ADDRESS: '127.0.0.1',
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.constant('VIDEO_STRETCH_MODE', {
        SCALE: 'scale'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');
    app.constant('WEB_BROADCAST_MESSAGES', {
        MTR_PAIRING_REQUEST: 'mtr.pairing.request',
        MTR_PAIRING_RESPONSE: 'mtr.pairing.response',
        MTR_HEARTBEAT_PING: 'mtr.heartbeat.ping',
        MTR_HEARTBEAT_PONG: 'mtr.heartbeat.pong',
        MTR_CLAIM_REQUESTSTATUS: 'mtr.claim.requeststatus',
        MTR_CLAIM_RESPONSESTATUS: 'mtr.claim.responsestatus',
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.factory('ConsoleAppender', ["$window", function($window) {
        // Define the constructor function.
        function ConsoleAppender() {

        }


        function write(logEntry) {
            var consoleType = logEntry.logLevel;
            var messages = logEntry.messages;

            // rewrite logtype
            switch (logEntry.logLevel) {
                case 'trace':
                    consoleType = 'debug';
                    break;
                case 'debug':
                    consoleType = 'debug';
                    break;
                case 'fatal':
                    consoleType = 'error';
                    break;
                case 'warn':
                    consoleType = 'warn';
                    break;
                case 'error':
                    consoleType = 'error';
                    break;
            }

            // todo: this might break in ie, as window.console object does not exists
            var console = $window.console || {};
            var logFunc = console[consoleType];
            var prepend = '[' + moment().format() + ' ' + logEntry.logger.name + ' ' + logEntry.logLevel + '] ';

            // convert to array if it don't
            if (!(messages instanceof Array)) {
                var tmp = messages;
                messages = [];
                messages.push(tmp);
            }

            // prepend log header
            var newMessages = [prepend + messages[0]||''].concat(messages.slice(1));
            return logFunc.apply($window.console, newMessages);
        }


        // Define the instance methods and properties
        ConsoleAppender.prototype = {
            // Public interface
            type: 'ConsoleAppender',
            config: {
                flushTimer: 100
            },
            write: write
        };

        // Return the constructor for the class
        return (ConsoleAppender);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.factory('DBAppender', ["$q", "$timeout", function($q, $timeout) {
        // Define the constructor function.
        function DBAppender() {
            var queue = [];
            var flushTimeout;
            // Use 1ms for the flush interval to avoid log key duplications.
            // Currently the indexed DB key for log history is the timestamp itself
            // Having the same timestamp causes fail to push log into indexed db
            // This is a workaround, actual solution is to rebuild & migrate the log's indexedDb as it requires index change
            var timeoutDuration = 1;

            this.type = 'DBAppender';
            this.config = {
                flushTimer: 1000
            };

            var startTimer = function() {
                clearTimer();
                flushTimeout = $timeout(flushQueue, timeoutDuration);
            };

            var clearTimer = function() {
                if (!flushTimeout) {
                    return;
                }

                $timeout.cancel(flushTimeout);
                flushTimeout = undefined;
            };

            var flushQueue = function() {
                var queueItem = queue.shift();
                if (!queueItem) {
                    clearTimer();
                    return;
                }

                var finalize = function() {
                    if (queue.length === 0) {
                        clearTimer();
                        return;
                    }

                    startTimer();
                };

                var onError = function(error) {
                    // eslint-disable-next-line no-console
                    console.log('Error when pushing log', error);
                    finalize();
                };

                addToDb(queueItem).then(finalize, onError);
            };

            var addToDb = function(queueItem) {
                var defer = $q.defer();
                try {
                    var key = new Date().getTime();
                    var transaction = queueItem.option.db.transaction('history', 'readwrite');
                    var logStore = transaction.objectStore('history');
                    logStore.add({
                        'createdAt': key,
                        'logger': queueItem.entry.logger.name,
                        'logLevel': queueItem.entry.logLevel,
                        'messages': queueItem.entry.messages.join(' ')
                    });
                    transaction.oncomplete = defer.resolve;
                    transaction.onerror = defer.reject;
                } catch (e) {
                    defer.reject('Exception during addToDb', e);
                }

                return defer.promise;
            };

            this.write = function(logEntry, opt) {
                if (!logEntry || !opt || !opt.db) {
                    return;
                }

                queue.push({entry: logEntry, option: opt});

                // if we have ongoing flush, wait
                if (flushTimeout) {
                    return;
                }

                startTimer();
            };
        }

        // Return the constructor for the class
        return (DBAppender);
    }]);
})(window.angular);

(function ($angular) {

    "use strict";

    var app = $angular.module('core');

    app.factory("HTTPAppender", ["$window", "$http", "coreConfigurationService", function ($window, $http, coreConfigurationService) {

        // Define the constructor function.
        function HTTPAppender() {

        };

        //Define the instance methods and properties
        HTTPAppender.prototype = {
            //Public interface
            type: "HTTPAppender",
            config: {
                flushTimer: 1000,
                destination: '',
                username: undefined,
                password: undefined,
                HTTPMethod: 'PUT'
            }
        };

        HTTPAppender.prototype.write = function (logEntry) {
            var message = logEntry.messages.join(" ");
            var rawMessage = logEntry.messages;
            var loggerName = logEntry.logger.name;
            var logLevel = logEntry.logLevel;
            var date = moment().format();
            var ticks = new Date().getTime();
            var playerGUID = coreConfigurationService.get('device', 'playerguid');
            var playerid = coreConfigurationService.get('device.sync', 'playerintid');
            var appspaceURL = coreConfigurationService.get('appspace', 'appspaceURL');
            var cfg = {
                method: this.config.HTTPMethod,
                headers: {
                    'Content-Type' : 'application/json'
                },
                data: {
                    PlayerID: playerGUID,
                    PlayerIntId: playerid,
                    AppspaceURL: appspaceURL,
                    LocalTimestamp: date,
                    Ticks: ticks,
                    LogLevel: logLevel,
                    Logger: loggerName,
                    RawMessage: rawMessage,
                    Message: message
                },
                url : this.config.destination
            };
            if (this.config.username && this.config.password) {
                cfg.headers['Authorization'] = 'Basic ' + $window.btoa(this.config.username + ':' + this.config.password);
            }
            $http(cfg).then(function(res){
                // console.log("success ", res);
            },function(err){
                // console.log("fail ", err);
            });
        };



        //Return the constructor for the class
        return (HTTPAppender);
    }]);

})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('core');
    app.factory('BaseRemoteService', function() {
        function BaseRemoteService() {
            var subscribers = [];
            this.subscribe = function(callback) {
                if (!callback || subscribers.indexOf(callback) > -1) {
                    return;
                }
                subscribers.push(callback);
            };

            this.unsubscribe = function(callback) {
                var index = subscribers.indexOf(callback);
                if (index === -1) {
                    return;
                }
                subscribers.splice(index, 1);
            };

            this.notifySubscribers = function(event) {
                for (var i = 0; i < subscribers.length; i++) {
                    subscribers[i](event);
                }
            };
        }

        return (BaseRemoteService);
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.factory('ReleaseChannelWorkflow', ["$q", "$http", "UrlUtil", "dataUtil", "versionUtil", "logService", "configurationService", "PLAYER_PROPERTY_KEYS", "SOFTWARE_UPDATE_EVENT", function(
        $q,
        $http,
        UrlUtil,
        dataUtil,
        versionUtil,
        logService,
        configurationService,
        PLAYER_PROPERTY_KEYS,
        SOFTWARE_UPDATE_EVENT
        ) {
        var currentConfiguration = {};
        var currentUpdateUrl = '';
        var LATEST_SOFTWARE_UPDATE_VERSION = 'latest';
        var SOFTWARE_UPDATE_VERSION_PROPERTY = '';
        var APP_PACKAGE_FILE_NAME = 'app-package.json';
        var LOG_KEY = '';
        var SUPPORTED_SERVICE_VERSIONS = {
            CLOUD: {
                MIN: '8.7.0-ac.7',
                MAX: '8.7.0'
            },
            GLOBAL: '8.7.0'
        };

        function ReleaseChannelWorkflow(configuration, logKey) {
            currentConfiguration = configuration;
            LOG_KEY = logKey;
        }

        function isSupported() {
            if (dataUtil.isObjectEmpty(currentConfiguration)) {
                logService.debug(LOG_KEY, 'Software update release channel workflow is not supported');
                return $q.resolve(false);
            }

            if (!currentConfiguration.deviceType) {
                logService.debug(LOG_KEY, 'Device type is required for software update release channel workflow');
                return $q.resolve(false);
            }

            var deferred = $q.defer();
            versionUtil.isVersionSupported(SUPPORTED_SERVICE_VERSIONS, LOG_KEY).then(function(isPlatformVersionSupported) {
                if (!isPlatformVersionSupported) {
                    logService.debug(LOG_KEY, 'Platform version does not meet the requirement for software update release channel workflow');
                }

                deferred.resolve(isPlatformVersionSupported);
            });
            return deferred.promise;
        }

        function onEvent(event, data) {
            switch (event.toLowerCase()) {
                case SOFTWARE_UPDATE_EVENT.TARGET_VERSION_UPDATED:
                    var updatedConfiguration = configurationService.getCurrentProperties() || {};
                    updatedConfiguration[SOFTWARE_UPDATE_VERSION_PROPERTY] = data;
                    onConfigurationUpdated(updatedConfiguration);
                    break;
            }
        }

        function start() {
            logService.debug(LOG_KEY, 'Starting software update release channel workflow');
            SOFTWARE_UPDATE_VERSION_PROPERTY = 'device.' + currentConfiguration.deviceType.toLowerCase() + '.softwareupdateversion';
            currentConfiguration.getUpdateUrl().then(function(result) {
                currentUpdateUrl = result;
                if (currentUpdateUrl) {
                    currentUpdateUrl = UrlUtil.removeTrailingSlash(currentUpdateUrl.toLowerCase());
                }

                logService.info(LOG_KEY, 'Current software update URL: ' + currentUpdateUrl);
                onConfigurationUpdated(configurationService.getCurrentProperties());
            }, function(error) {
                logService.error(LOG_KEY, 'Failed to retrieve software update URL: ' + JSON.stringify(error));
            });
        }

        function setUpdateUrlToLatestVersion(properties) {
            var deferred = $q.defer();
            logService.info(LOG_KEY, 'Initiating app update process...');
            properties[SOFTWARE_UPDATE_VERSION_PROPERTY] = LATEST_SOFTWARE_UPDATE_VERSION;
            var executeUpdateCallback = currentConfiguration.executeUpdate || $angular.noop;
            doUpdateApp(properties).then(function() {
                deferred.resolve();
                logService.info(LOG_KEY, 'Executing app update');
                executeUpdateCallback();
            }, function(error) {
                var errorMessage = 'App update execution failed: ' + error;
                logService.error(LOG_KEY, errorMessage);
                deferred.reject(errorMessage);
            });
            return deferred.promise;
        }

        function doUpdateApp(properties, validateUrl) {
            var onValidationFailed = function() {
                return $q.reject('Software update conditions not met');
            };

            var update = getUpdateInformation(properties);
            if (!update) {
                return onValidationFailed();
            }

            var isUrlValid = validateUrl ? validateUrl(update.url) : true;
            return isUrlValid ? setUpdateUrl(update.url, update.version) : onValidationFailed();
        }

        function getUpdateInformation(properties) {
            var softwareUpdateUrl = UrlUtil.removeTrailingSlash(properties[PLAYER_PROPERTY_KEYS.SOFTWARE_UPDATE_URL] || '');
            if (!softwareUpdateUrl) {
                logService.debug(LOG_KEY, 'Invalid software update URL');
                return;
            }

            if (currentUpdateUrl && !dataUtil.includesString(softwareUpdateUrl, currentUpdateUrl)) {
                logService.debug(LOG_KEY, 'Software update release channel workflow is only supported for Appspace-hosted URL');
                return;
            }

            softwareUpdateUrl = softwareUpdateUrl.toLowerCase();
            var softwareUpdateVersion = properties[SOFTWARE_UPDATE_VERSION_PROPERTY] || '';
            if (!softwareUpdateVersion) {
                logService.debug(LOG_KEY, 'Invalid software update version');
                return;
            }

            var processedVersion = softwareUpdateVersion.toLowerCase() === LATEST_SOFTWARE_UPDATE_VERSION ?
                undefined : softwareUpdateVersion;
            var urlPaths = [softwareUpdateUrl];
            if (processedVersion) {
                urlPaths.push(processedVersion);
            }

            var processedUrl = urlPaths.join('/');
            return {
                url: processedUrl,
                version: processedVersion,
            };
        }

        function setUpdateUrl(processedUrl, processedVersion) {
            if (!isUrlUpdated(processedUrl)) {
                return $q.resolve();
            }

            var deferred = $q.defer();
            var onFailedToSetUpdateUrl = function(error) {
                var errorMessage = 'Failed to set software update URL (' + processedUrl + ') due to: ' + error;
                logService.error(LOG_KEY, errorMessage);
                deferred.reject(errorMessage);
            };

            logService.debug(LOG_KEY, 'Attempting to set software update URL to ' + processedUrl);
            validateUpdateUrl(processedUrl, processedVersion, currentConfiguration.packageType).then(function() {
                doSetReleaseChannelUpdateUrl(processedUrl).then(function() {
                    currentUpdateUrl = processedUrl;
                    logService.info(LOG_KEY, 'Software update URL set to ' + processedUrl);
                    deferred.resolve();
                }, onFailedToSetUpdateUrl);
            }, function(error) {
                onFailedToSetUpdateUrl('Update URL validation failed - ' + error);
            });
            return deferred.promise;
        }

        function isUrlUpdated(url) {
            var isUpdated = url !== currentUpdateUrl;
            if (!isUpdated) {
                logService.debug(LOG_KEY, 'Skipping software update URL as it is the same as the current one');
            }

            return isUpdated;
        }

        function onConfigurationUpdated(properties) {
            doUpdateApp(properties || {}, isUrlUpdated).then($angular.noop, $angular.noop);
        }

        function validateUpdateUrl(url, version, packageType) {
            var deferred = $q.defer();
            var appPackageFileUrl = url + '/' + APP_PACKAGE_FILE_NAME;
            $http.get(appPackageFileUrl).then(function(res) {
                var packageInfo = (res || {}).data;
                if (dataUtil.isObjectEmptyOrUndefined(packageInfo)) {
                    deferred.reject('Package information not found');
                    return;
                }

                // Validate package type
                if (packageInfo.type !== packageType) {
                    deferred.reject('Invalid package type (expected: ' + packageType + ', actual: ' + packageInfo.type + ')');
                    return;
                }

                // Validate version-specific package
                if (version && packageInfo.version !== version) {
                    deferred.reject('Invalid package version (expected: ' + version + ', actual: ' + packageInfo.version + ')');
                    return;
                }

                deferred.resolve();
            }, function(error) {
                deferred.reject('Failed to retrieve package information: ' + JSON.stringify(error));
            });
            return deferred.promise;
        }

        function doSetReleaseChannelUpdateUrl(url) {
            var deferred = $q.defer();
            var getUpdateUrl = function() {
                currentConfiguration.getUpdateUrl().then(function(result) {
                    if (result !== url) {
                        deferred.reject('Software update URL retrieved is not the same as the one set');
                        return;
                    }

                    deferred.resolve();
                }, function(error) {
                    deferred.reject('Failed to retrieve software update URL: ' + JSON.stringify(error));
                });
            };

            currentConfiguration.setUpdateUrl(url).then(getUpdateUrl, deferred.reject);
            return deferred.promise;
        }

        function stop() {
            return true;
        }

        ReleaseChannelWorkflow.prototype = {
            start: start,
            stop: stop,
            isSupported: isSupported,
            onConfigurationUpdated: onConfigurationUpdated,
            onEvent: onEvent,
            updateApp: setUpdateUrlToLatestVersion,
        };

        return (ReleaseChannelWorkflow);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.factory('SelfUpdateWorkflow', ["$q", "logService", "driverUtil", function(
        $q,
        logService,
        driverUtil
        ) {
        var baseUpdateUrl;
        var LOG_KEY = '';
        var selfUpdateConfiguration;
        function SelfUpdateWorkflow(configuration, logKey) {
            LOG_KEY = logKey;
        }

        function isSelfUpdateSupported() {
            selfUpdateConfiguration = driverUtil.getSelfUpdateConfiguration();
            baseUpdateUrl = selfUpdateConfiguration.baseUpdateUrl;
            var supportsSelfUpdate = selfUpdateConfiguration.isSupportedByDevice;
            if (!supportsSelfUpdate) {
                logService.debug(LOG_KEY, 'Self update is not supported for this device');
            } else if (!baseUpdateUrl) {
                logService.error(LOG_KEY, 'SELF_UPDATE_BASE_URL is required');
                supportsSelfUpdate = false;
            }

            return supportsSelfUpdate;
        }

        function isSupported() {
            return $q.resolve(isSelfUpdateSupported());
        }

        function updateApp() {
            return selfUpdateConfiguration.updateApp();
        }

        function start() {
            logService.debug(LOG_KEY, 'Starting software update service (base URL:' + baseUpdateUrl + ')');
        }

        function stop() {
            if (!isSelfUpdateSupported()) {
                return false;
            }

            return true;
        }

        SelfUpdateWorkflow.prototype = {
            start: start,
            stop: stop,
            isSupported: isSupported,
            updateApp: updateApp,
        };

        return (SelfUpdateWorkflow);
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var app = $angular.module('core');

    app.service('authenticationLibraryService', ["$q", "$window", "halNetworkService", "Observable", "postMessagingService", "embedProfileService", function(
        $q,
        $window,
        halNetworkService,
        Observable,
        postMessagingService,
        embedProfileService
    ) {
        var authenticationHelper = $window.appspace.AuthenticationHelper;
        var AuthenticationClient = $window.appspace.AuthenticationClient;
        var constellationTokensObservable = new Observable();
        var constellationTokensRefreshFailedObservable = new Observable();

        function parseConstellationTokens(tokensObject) {
            return authenticationHelper.parseConstellationTokens(tokensObject);
        }

        function requestClient(config) {
            var promise = $q.defer();
            halNetworkService.request(config).then(function(response) {
                if (!response) {
                    promise.reject('Empty response');
                    return;
                }

                promise.resolve(response.response);
            }, promise.reject);
            return promise.promise;
        }

        function onConstellationTokensRefreshed(tokens) {
            constellationTokensObservable.notifyObservers(tokens);
        }

        function onConstellationTokensRefreshFailed(error) {
            constellationTokensRefreshFailedObservable.notifyObservers(error);
        }

        function requestConstellationTokens() {
            return postMessagingService.requestPostMessage({}, 'api.requestconstellationtokens', 'api.requestconstellationtokens.response');
        }

        function executeAuthenticationClientFunction(tokens, functionName) {
            var options = {
                requestClient: requestClient,
                onConstellationTokensRefreshed: onConstellationTokensRefreshed,
                onConstellationTokensRefreshFailed: onConstellationTokensRefreshFailed,
            };
            var appspaceClient = new AuthenticationClient(options);
            if (!appspaceClient || !appspaceClient[functionName]) {
                return $q.reject('AuthenticationClient ' + functionName + ' not found');
            }

            if (!tokens) {
                return $q.reject('Tokens not supplied');
            }

            try {
                appspaceClient.setConstellationTokens(tokens);
            } catch (error) {
                return $q.reject('Failed to set constellation tokens for authentication client: ' + error.message);
            }

            return appspaceClient[functionName]();
        }

        function getConstellationBaseUrl(tokens) {
            return executeAuthenticationClientFunction(tokens, 'getConstellationBaseUrl');
        }

        function getConstellationAccountId(tokens) {
            return executeAuthenticationClientFunction(tokens, 'getAccountId');
        }

        function getUpdatedConstellationTokens(tokens) {
            return executeAuthenticationClientFunction(tokens, 'getTokens');
        }

        function requestConstellationService(relativeUrl, tokens, configuration) {
            if (!relativeUrl) {
                return $q.reject('Missing relative URL');
            }

            if (!embedProfileService.isChannelPreviewMode()) {
                if (!tokens) {
                    return $q.reject('Missing constellation tokens');
                }

                if (!tokens.accessToken || !tokens.refreshToken || !tokens.idToken) {
                    return $q.reject('Incomplete tokens');
                }
            }

            configuration = configuration || {};
            configuration.headers = configuration.headers || {};
            var options = {
                requestClient: requestClient,
                onConstellationTokensRefreshed: onConstellationTokensRefreshed,
                onConstellationTokensRefreshFailed: onConstellationTokensRefreshFailed,
            };
            var appspaceClient = new AuthenticationClient(options);

            if (tokens) {
                try {
                    appspaceClient.setConstellationTokens(tokens);
                } catch (error) {
                    return $q.reject('Failed to set constellation tokens for authentication client: ' + error.message);
                }
            }

            if (embedProfileService.isChannelPreviewMode()) {
                appspaceClient.options.requestConstellationTokens = requestConstellationTokens;
                appspaceClient.setOptions(appspaceClient.options);
            }

            return appspaceClient.requestConstellationService(relativeUrl, configuration);
        }

        function subscribeToConstellationTokensUpdate(callback) {
            constellationTokensObservable.registerObserver(callback);
        }

        function unsubscribeToConstellationTokensUpdate(callback) {
            constellationTokensObservable.removeObserver(callback);
        }

        function subscribeToConstellationTokensRefreshFailed(callback) {
            constellationTokensRefreshFailedObservable.registerObserver(callback);
        }

        function unsubscribeToConstellationTokensRefreshFailed(callback) {
            constellationTokensRefreshFailedObservable.removeObserver(callback);
        }

        function createTokenByRefreshToken(configuration) {
            if (!configuration || !configuration.refreshToken || !configuration.subjectId || !configuration.subjectType || !configuration.baseUrl) {
                return $q.reject('Invalid configuration.');
            }

            var appspaceClient = new AuthenticationClient({
                requestClient: requestClient,
                onConstellationTokensRefreshed: onConstellationTokensRefreshed
            });
            return appspaceClient.createTokenByRefreshToken(configuration.refreshToken, configuration.subjectId, configuration.subjectType, configuration.baseUrl);
        }

        return {
            parseConstellationTokens: parseConstellationTokens,
            requestConstellationService: requestConstellationService,
            subscribeToConstellationTokensUpdate: subscribeToConstellationTokensUpdate,
            unsubscribeToConstellationTokensUpdate: unsubscribeToConstellationTokensUpdate,
            subscribeToConstellationTokensRefreshFailed: subscribeToConstellationTokensRefreshFailed,
            unsubscribeToConstellationTokensRefreshFailed: unsubscribeToConstellationTokensRefreshFailed,
            getConstellationBaseUrl: getConstellationBaseUrl,
            getConstellationAccountId: getConstellationAccountId,
            getUpdatedConstellationTokens: getUpdatedConstellationTokens,
            createTokenByRefreshToken: createTokenByRefreshToken,
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('core');

    app.service('broadcastAlertService', ["$q", "$window", "$timeout", "$interval", "logService", "networkConnectionService", "authenticationLibraryService", "dataUtil", "deviceTokenService", "broadcastAlertUtil", "BROADCAST_ALERT_EVENT", "NETWORK_CONNECTION_STATE", function(
        $q,
        $window,
        $timeout,
        $interval,
        logService,
        networkConnectionService,
        authenticationLibraryService,
        dataUtil,
        deviceTokenService,
        broadcastAlertUtil,
        BROADCAST_ALERT_EVENT,
        NETWORK_CONNECTION_STATE
    ) {
        var NOT_FOUND_INDEX = -1;
        var EVENT = {
            BROADCASTS: 'broadcasts',
            CONNECTED: 'connected',
            ERROR: 'error',
            CLOSED: 'closed',
            STATE: 'state',
            DEBUG: 'debug'
        };
        var UPDATE_SOURCE = {
            MANUAL_CHECKING: 'manual checking',
            BROADCAST_CLIENT: 'broadcast client'
        };
        var LOG_KEY = 'appspace.core.broadcastalert';
        var BROADCAST_PROPERTIES = ['isNotification', 'isCritical', 'isChannel', 'isMedia', 'isText', 'isLiveChannel', 'isFullscreenContent', 'requiresManualDeactivation'];
        var broadcastClient;
        var subscribers = [];
        var isStarted = false;
        var isStarting = false;
        var isStartedPromise;
        var activeBroadcasts = [];
        var currentBroadcast;
        var contentBaseUrl = '';
        var deviceTokens;
        var retryStartTimeout;
        var RETRY_START_DELAY = 30000;
        var retryAttempt = 0;
        var startupConfiguration;
        var INITIAL_BROADCAST_INDEX = 0;
        var currentBroadcastIndex = INITIAL_BROADCAST_INDEX;
        var CHECK_BROADCASTS_END_TIME_INTERVAL = 20000;
        var checkBroadcastsEndTimeInterval;
        var UPDATE_BROADCAST_DELAY = 500;
        var updateBroadcastDelay = undefined;

        function notifySubscribers(event, data) {
            for (var i = 0; i < subscribers.length; i++) {
                subscribers[i](event, data);
            }
        }

        function processBroadcastProperty(broadcast, key) {
            broadcast[key] = broadcastAlertUtil[key](broadcast);
        }

        function processBroadcast(broadcast) {
            if (!broadcast) {
                return;
            }

            var processedBroadcast = broadcastAlertUtil.generateBroadcastItem(broadcast);
            for (var i = 0; i < BROADCAST_PROPERTIES.length; i++) {
                processBroadcastProperty(processedBroadcast, BROADCAST_PROPERTIES[i]);
            }

            broadcastAlertUtil.processChannelPreviewUrl(processedBroadcast);
            broadcastAlertUtil.processMediaItems(processedBroadcast, contentBaseUrl);
            broadcastAlertUtil.processBroadcastEnd(processedBroadcast);
            return processedBroadcast;
        }

        function updateActiveBroadcasts(update, source) {
            update = broadcastAlertUtil.sortBroadcasts(update);
            logService.debug(LOG_KEY, 'Received broadcast alerts update via ' + source + ': ' + JSON.stringify(update));
            var currentActiveBroadcasts = $angular.copy(activeBroadcasts);
            activeBroadcasts = [];
            currentBroadcastIndex = INITIAL_BROADCAST_INDEX;
            for (var i = 0; i < update.length; i++) {
                var processedBroadcast = processBroadcast(update[i]);
                if (!processedBroadcast) {
                    continue;
                }

                activeBroadcasts.push(processedBroadcast);
            }

            var changesDetected = activeBroadcasts.length !== currentActiveBroadcasts.length;
            if (!changesDetected) {
                for (var j = 0; j < activeBroadcasts.length; j++) {
                    if (!broadcastAlertUtil.isBroadcastDifferent(currentActiveBroadcasts[j], activeBroadcasts[j])) {
                        continue;
                    }

                    changesDetected = true;
                    break;
                }
            }

            if (!changesDetected) {
                logService.debug(LOG_KEY, 'Skipping broadcast alerts update due to no changes detected');
                return;
            }

            notifySubscribers(BROADCAST_ALERT_EVENT.BROADCAST_UPDATE, update);
            updateCurrentBroadcast();
        }

        function cancelUpdateBroadcastDelay() {
            if (!updateBroadcastDelay) {
                return;
            }

            $timeout.cancel(updateBroadcastDelay);
            updateBroadcastDelay = undefined;
        }

        function delayUpdateBroadcast(update) {
            cancelUpdateBroadcastDelay();
            updateBroadcastDelay = $timeout(function() {
                updateActiveBroadcasts(update, UPDATE_SOURCE.BROADCAST_CLIENT);
            }, UPDATE_BROADCAST_DELAY);
        }

        function onUpdate(update) {
            if (!update) {
                return;
            }

            update = !dataUtil.isArray(update) ? [] : update;
            // Upon updating an active broadcast, the broadcast will be deactivated first
            // (removed from active broadcasts list)
            // before reactivated and readded back to active broadcasts list
            // Add a delay to ensure that only notify the intended broadcasts update
            delayUpdateBroadcast(update);

            // Receiving broadcast client update is an indication that the network connection
            // is established or recovered, and manual checking interval is no longer required
            cancelCheckBroadcastsEndTimeInterval();
        }

        function updateCurrentBroadcast() {
            currentBroadcast = activeBroadcasts[currentBroadcastIndex];
            notifySubscribers(BROADCAST_ALERT_EVENT.CURRENT_BROADCAST_UPDATE, currentBroadcast);
        }

        function onError(error) {
            logService.error(LOG_KEY, 'Broadcast client error: ' + JSON.stringify(error));
        }

        function onState(state) {
            if (!isStarted || !state || state.isConnected) {
                return;
            }

            notifySubscribers(BROADCAST_ALERT_EVENT.CONNECTION_CLOSED);
            logService.debug(LOG_KEY, 'Broadcast alert service connection closed');
        }

        function onDebug(event) {
            logService.debug(LOG_KEY, 'Broadcast alert service debug message: ' + event.message);
        }

        function subscribeToEvents(subscribe) {
            var process = subscribe ? 'on' : 'off';
            broadcastClient[process](EVENT.BROADCASTS, onUpdate);
            broadcastClient[process](EVENT.ERROR, onError);
            broadcastClient[process](EVENT.STATE, onState);
            broadcastClient[process](EVENT.DEBUG, onDebug);
        }

        function cancelCheckBroadcastsEndTimeInterval() {
            if (!checkBroadcastsEndTimeInterval) {
                return;
            }

            $interval.cancel(checkBroadcastsEndTimeInterval);
            checkBroadcastsEndTimeInterval = undefined;
        }

        function checkBroadcastsEndTime() {
            var checkedBroadcasts = [];
            var broadcastUpdated = false;
            for (var i = 0; i < activeBroadcasts.length; i++) {
                var broadcast = activeBroadcasts[i];
                var now = new Date().getTime();
                if (broadcast.endTime && now >= broadcast.endTime) {
                    broadcastUpdated = true;
                    continue;
                }

                checkedBroadcasts.push(broadcast);
            }

            if (!broadcastUpdated) {
                return;
            }

            // Only update active broadcasts when necessary
            updateActiveBroadcasts(checkedBroadcasts, UPDATE_SOURCE.MANUAL_CHECKING);
        }

        function onNetworkConnectionEvent(event) {
            switch ((event || '').toLowerCase()) {
                case NETWORK_CONNECTION_STATE.CONNECTED:
                    cancelCheckBroadcastsEndTimeInterval();
                    break;
                case NETWORK_CONNECTION_STATE.DISCONNECTED:
                    if (!checkBroadcastsEndTimeInterval && activeBroadcasts.length > 0) {
                        cancelCheckBroadcastsEndTimeInterval();
                        // Network disrupted after connected to broadcast client,
                        // start checking if we need to manually end any active broadcasts
                        checkBroadcastsEndTimeInterval = $interval(checkBroadcastsEndTime, CHECK_BROADCASTS_END_TIME_INTERVAL);
                    }

                    break;
            }
        }

        function resetRetryStartTimeout() {
            retryAttempt = 0;
            cancelRetryStartTimeout();
        }

        function cancelRetryStartTimeout() {
            if (!retryStartTimeout) {
                return;
            }

            $timeout.cancel(retryStartTimeout);
            retryStartTimeout = undefined;
        }

        function beginRetryStartTimeout() {
            cancelRetryStartTimeout();

            var retryStart = function() {
                retryAttempt++;
                logService.info(LOG_KEY, 'Retrying to start broadcast alert service. Attempt: ' + retryAttempt);
                start(startupConfiguration);
            };

            retryStartTimeout = $timeout(retryStart, RETRY_START_DELAY);
        }

        function rejectIsStartedPromise(reason, shouldRetry) {
            logService.error(LOG_KEY, 'Broadcast alert service start failed: ' + JSON.stringify(reason));
            isStarted = false;
            isStarting = false;
            subscribeToEvents(false);

            if (shouldRetry) {
                beginRetryStartTimeout();
                return;
            }

            isStartedPromise.reject(JSON.stringify(reason));
            resetRetryStartTimeout();
        }

        function resolveIsStartedPromise() {
            isStartedPromise.resolve();
            isStarted = true;
            isStarting = false;
            networkConnectionService.subscribe(onNetworkConnectionEvent);
            logService.info(LOG_KEY, 'Broadcast alert service started');
            resetRetryStartTimeout();
        }

        function getAccountId() {
            return authenticationLibraryService.getConstellationAccountId(deviceTokens);
        }

        function getBaseUrl() {
            return authenticationLibraryService.getConstellationBaseUrl(deviceTokens);
        }

        function getContentBaseUrl() {
            var deferred = $q.defer();
            getBaseUrl().then(function(url) {
                getAccountId().then(function(accountId) {
                    contentBaseUrl = url + '/contents/' + accountId + '/';
                    deferred.resolve();
                }, deferred.reject);
            }, deferred.reject);
            return deferred.promise;
        }

        function start(configuration) {
            if (isStarting) {
                logService.debug(LOG_KEY, 'Broadcast alert service initiation in progress');
                return isStartedPromise.promise;
            }

            if (isStarted) {
                // Stop current broadcast client before starting a new one
                stop(true);
            }

            if (!configuration) {
                return $q.reject('Empty configuration');
            }

            startupConfiguration = $angular.copy(configuration);
            var pnsClient = configuration.client;
            if (!pnsClient) {
                return $q.reject('Missing PNS client');
            }

            var domain = configuration.domain;
            if (!domain) {
                return $q.reject('Missing domain');
            }

            deviceTokens = deviceTokenService.getConstellationTokens();
            if (!deviceTokens || !deviceTokens.accessToken || !deviceTokens.idToken) {
                return $q.reject('Incomplete device tokens');
            }

            var deviceId = configuration.deviceId;
            if (!deviceId) {
                return $q.reject('Missing device ID');
            }

            var topicSubscription = configuration.topicSubscription;
            if (!topicSubscription || !topicSubscription.on || !topicSubscription.off) {
                return $q.reject('Missing topic subscription');
            }

            if (!$window.BroadcastClient || !$window.BroadcastClient.BroadcastClient) {
                return $q.reject('BroadcastClient not found');
            }

            isStarting = true;
            logService.info(LOG_KEY, 'Starting broadcast alert service');
            if (!isStartedPromise) {
                isStartedPromise = $q.defer();
            }

            getContentBaseUrl().then(function() {
                try {
                    broadcastClient = new $window.BroadcastClient.BroadcastClient({
                        domain: domain,
                        accessToken: deviceTokens.accessToken,
                        pnsConnection: pnsClient,
                        deviceId: deviceId,
                        autoStart: false,
                        topicSubscription: topicSubscription
                    });

                    subscribeToEvents(true);
                    broadcastClient.start().then(resolveIsStartedPromise, function(error) {
                        rejectIsStartedPromise(error, true);
                    });
                } catch (error) {
                    rejectIsStartedPromise(error.message);
                }
            }, rejectIsStartedPromise);
            return isStartedPromise.promise;
        }

        function stop(restartClientOnly) {
            if (!isStarted) {
                return;
            }

            logService.debug(LOG_KEY, 'Stopping broadcast alert service');
            isStarted = false;
            isStarting = false;
            isStartedPromise = undefined;
            if (!restartClientOnly) {
                subscribers = [];
                subscribeToEvents(false);
                cancelCheckBroadcastsEndTimeInterval();
                networkConnectionService.unsubscribe(onNetworkConnectionEvent);
                currentBroadcast = undefined;
                activeBroadcasts = [];
                deviceTokens = undefined;
                contentBaseUrl = '';
            }

            broadcastClient.stop();
            broadcastClient = undefined;
            resetRetryStartTimeout();
        }

        function subscribe(callback) {
            if (!callback || subscribers.indexOf(callback) !== NOT_FOUND_INDEX) {
                return;
            }

            subscribers.push(callback);
        }

        function unsubscribe(callback) {
            var subscriberIndex = subscribers.indexOf(callback);
            if (subscriberIndex === NOT_FOUND_INDEX) {
                return;
            }

            subscribers.splice(subscriberIndex, 1);
        }

        function notifyBroadcastComplete() {
            currentBroadcastIndex = currentBroadcastIndex >= activeBroadcasts.length - 1 ?
                INITIAL_BROADCAST_INDEX : currentBroadcastIndex + 1;
            updateCurrentBroadcast();
        }

        function isFullscreenBroadcastActive() {
            return Boolean(currentBroadcast && !currentBroadcast.isNotification);
        }

        return {
            start: start,
            stop: stop,
            subscribe: subscribe,
            unsubscribe: unsubscribe,
            notifyBroadcastComplete: notifyBroadcastComplete,
            isFullscreenBroadcastActive: isFullscreenBroadcastActive
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('core');
    app.service('broadcastService', ["$rootScope", "GLOBAL_EVENT", function($rootScope, GLOBAL_EVENT) {
        function enableBackmostVideoLayer() {
            fireBackmostVideoLayer(true);
        }

        function disableBackmostVideoLayer() {
            fireBackmostVideoLayer(false);
        }

        function fireBackmostVideoLayer(enable) {
            $rootScope.$emit(GLOBAL_EVENT.VIDEO_BACKMOST_LAYER, {enable: enable});
        }

        function onBackmostVideoLayerEvent(callback) {
            return $rootScope.$on(GLOBAL_EVENT.VIDEO_BACKMOST_LAYER, callback);
        }

        return {
            enableBackmostVideoLayer: enableBackmostVideoLayer,
            disableBackmostVideoLayer: disableBackmostVideoLayer,
            onBackmostVideoLayerEvent: onBackmostVideoLayerEvent
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('core');

    app.service('commandService', ["$q", "$http", "$window", "logService", "versionUtil", "halPlatformService", "deviceTokenService", "embedProfileService", "deviceRegistrationService", "authenticationLibraryService", "pushNotificationClientService", function(
        $q,
        $http,
        $window,
        logService,
        versionUtil,
        halPlatformService,
        deviceTokenService,
        embedProfileService,
        deviceRegistrationService,
        authenticationLibraryService,
        pushNotificationClientService
    ) {
        var LOG_KEY = 'appspace.core.command';
        var DEVICE_TOPIC_KEY = 'device';
        var TOPIC_EVENT = {
            COMMAND: 'command'
        };
        var isStarted = false;
        var commandServiceInstance = {};

        function retrieveFile(url, configuration) {
            var deferred = $q.defer();
            $http.get(url, configuration || {}).then(function(result) {
                if (!result || !result.data) {
                    deferred.reject('No data found in the response');
                    return;
                }

                var mimeType = result.headers('content-type');
                if (!mimeType && result.data && result.data.type) {
                    mimeType = result.data.type;
                }

                deferred.resolve({
                    data: result.data,
                    mimeType: mimeType,
                });
            }, deferred.reject);
            return deferred.promise;
        }

        function isDeviceCommandMessage(update) {
            return Boolean(
                update &&
                update.payload &&
                update.topic &&
                update.topic.key === DEVICE_TOPIC_KEY &&
                update.topic.event &&
                update.topic.event.toLowerCase() === TOPIC_EVENT.COMMAND
            );
        }

        function onDeviceCommand(message) {
            if (!isDeviceCommandMessage(message) || !commandServiceInstance.onCommandReceived) {
                return;
            }

            commandServiceInstance.onCommandReceived(message.payload);
        }

        function subscribeToPnsUpdates(subscribe) {
            var topicSubscription = subscribe ? 'subscribeToTopic' : 'unsubscribeFromTopic';
            pushNotificationClientService[topicSubscription](DEVICE_TOPIC_KEY, onDeviceCommand);
        }

        function requestConstellationService(request) {
            return authenticationLibraryService.requestConstellationService(request.url, deviceTokenService.getConstellationTokens(), request);
        }

        function start() {
            if (isStarted) {
                logService.debug(LOG_KEY, 'Command service is already running');
                return;
            }

            if (embedProfileService.isPreviewMode()) {
                logService.debug(LOG_KEY, 'Command service is not supported in preview mode');
                return;
            }

            var deviceId = deviceRegistrationService.getPlayerId();
            if (!deviceId) {
                logService.debug(LOG_KEY, 'Command service is not supported in unregistered state');
                return;
            }

            if (!$window.AppspaceCloudDeviceCommand || !$window.AppspaceCloudDeviceCommand.CommandService) {
                logService.error(LOG_KEY, 'Command service is not supported in this mode');
                return;
            }

            commandServiceInstance = new $window.AppspaceCloudDeviceCommand.CommandService(
                logService,
                LOG_KEY,
                halPlatformService.getFeatureSupport,
                versionUtil.isVersionSupported,
                halPlatformService.executeCommand,
                halPlatformService.postCommandExecution,
                requestConstellationService,
                retrieveFile
            );
            commandServiceInstance.start();
            subscribeToPnsUpdates(true);
            isStarted = true;
        }

        function stop() {
            if (!isStarted) {
                return;
            }

            isStarted = false;
            subscribeToPnsUpdates(false);

            if (commandServiceInstance.stop) {
                commandServiceInstance.stop();
            }

            commandServiceInstance = {};
        }

        return {
            start: start,
            stop: stop,
        };
    }]);
})(window.angular);


(function($angular) {
    'use strict';
    var app = $angular.module('core');
    app.factory('coreConfigurationService', ["$q", "APPSPACE_CONFIGURATION_PROPERTY", "STORAGE_COLLECTION_KEY", "STORAGE_KEY", "dataUtil", "UrlUtil", "storageLocalStorageService", function(
        $q,
        APPSPACE_CONFIGURATION_PROPERTY,
        STORAGE_COLLECTION_KEY,
        STORAGE_KEY,
        dataUtil,
        UrlUtil,
        storageLocalStorageService) {
        var collectionListeners = [];
        var configurationListeners = [];
        var appspaceReadyPromise = $q.defer();
        var PROMISE_RESOLVED_STATE = 1;

        // Notifiy listeners of an udpate
        function onConfigurationUpdate(collection, key, newValue, oldValue) {
            // Find registered listeners
            for (var i = 0; i < configurationListeners.length; i++) {
                var l = configurationListeners[i];
                if (l.key == key && l.collection == collection) {
                    l.callback(collection, key, newValue, oldValue);
                }
            }
        }

        // Notifiy listeners of an udpate
        function onCollectionUpdate(collection, key, newValue, oldValue) {
            // Find registered listeners
            for (var i = 0; i < collectionListeners.length; i++) {
                var l = collectionListeners[i];
                if (l.collection == collection) {
                    l.callback(collection, key, newValue, oldValue);
                }
            }
        }

        function isReadyPromiseResolved() {
            return appspaceReadyPromise.$$state && appspaceReadyPromise.$$state.status === PROMISE_RESOLVED_STATE;
        }

        function recreateReadyPromise() {
            appspaceReadyPromise = $q.defer();
        }

        function isAppspaceReady() {
            if (get(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.APPSPACE_URL)) {
                appspaceReadyPromise.resolve();
            }

            return appspaceReadyPromise.promise;
        }

        function isReady() {
            return storageLocalStorageService.isReady();
        }

        // Stores a configuration based on a key and collection
        function set(collection, key, value) {
            var isAppspaceUrl = key === STORAGE_KEY.APPSPACE_URL;
            var prevValue = storageLocalStorageService.get(collection, key);
            var res = storageLocalStorageService.set(collection, key, value);
            if (prevValue != value) {
                onConfigurationUpdate(collection, key, value, prevValue);
                onCollectionUpdate(collection, key, value, prevValue);
            }

            if (!isAppspaceUrl || isReadyPromiseResolved()) {
                return res;
            }

            if (!value) {
                recreateReadyPromise();
                return res;
            }

            appspaceReadyPromise.resolve();
            return res;
        }

        // Retrieves a configuration based on a key and collection name
        function get(collection, key) {
            return storageLocalStorageService.get(collection, key);
        }

        // Retrieves all configurations in a collection
        function getConfigurations(collection) {
            return storageLocalStorageService.getKeys(collection, true);
        }

        // Retrieves all collections
        function getCollections() {
            return storageLocalStorageService.getCollections();
        }

        // Removes a configuration from a collection
        function removeConfiguration(collection, key) {
            if (collection === STORAGE_COLLECTION_KEY.APPSPACE && key === STORAGE_KEY.APPSPACE_URL) {
                recreateReadyPromise();
            }

            var prevValue = storageLocalStorageService.get(collection, key);
            onConfigurationUpdate(collection, key, undefined, prevValue);
            onCollectionUpdate(collection, key, undefined, prevValue);
            return storageLocalStorageService.removeKey(collection, key);
        }

        // Removes all configurations in a collection
        function removeCollection(collection) {
            if (collection === STORAGE_COLLECTION_KEY.APPSPACE) {
                recreateReadyPromise();
            }

            var configs = storageLocalStorageService.getKeys(collection, false);
            for (var i = 0; i < configs.length; i++) {
                var c = configs[i];
                removeConfiguration(collection, c.key);
            }

            return storageLocalStorageService.removeCollection(collection);
        }

        // Register a listener to configuration changes
        function registerConfigurationListener(collection, key, callback) {
            // Check if already registered
            for (var i = 0; i < configurationListeners.length; i++) {
                var l = configurationListeners[i];
                if (l.callback == callback && l.key == key && l.collection == collection) {
                    console.log('already registered to listen to configuration');
                    return true;
                }
            }

            // Create a listener entry
            var listener = {
                collection: collection,
                key: key,
                callback: callback
            };
            // Add to collection
            configurationListeners.push(listener);
            return true;
        }

        // Remove a listener to configuration changes
        function removeConfigurationListener(collection, key, callback) {
            // Check if already registered
            for (var i = 0; i < configurationListeners.length; i++) {
                var l = configurationListeners[i];
                if (l.callback == callback && l.key == key && l.collection == collection) {
                    configurationListeners.splice(i, 1);
                    return true;
                }
            }

            return false;
        }

        // Register a listener to configuration changes
        function registerCollectionListener(collection, callback) {
            // Check if already registered
            for (var i = 0; i < collectionListeners.length; i++) {
                var l = collectionListeners[i];
                if (l.callback == callback && l.collection == collection) {
                    console.log('already registered to listen to collection');
                    return true;
                }
            }

            // Create a listener entry
            var listener = {
                collection: collection,
                callback: callback
            };
            // Add to collection
            collectionListeners.push(listener);
            return true;
        }

        // Remove a listener to configuration changes
        function removeCollectionListener(collection, configuration, callback) {
            // Check if already registered
            for (var i = 0; i < collectionListeners.length; i++) {
                var l = collectionListeners[i];
                if (l.callback == callback && l.collection == collection) {
                    collectionListeners.splice(i, 1);
                    return true;
                }
            }

            return false;
        }

        function getReferenceDeviceId() {
            return get(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.REFERENCE_DEVICE_ID);
        }

        function setReferenceDeviceId(referenceDeviceId) {
            set(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.REFERENCE_DEVICE_ID, referenceDeviceId);
        }

        function removeReferenceDeviceId() {
            removeConfiguration(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.REFERENCE_DEVICE_ID);
        }

        function getSerialNumber() {
            return get(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.SERIAL_NUMBER);
        }

        function setSerialNumber(serialNumber) {
            set(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.SERIAL_NUMBER, serialNumber);
        }

        function getReferenceDeviceName() {
            return get(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.REFERENCE_DEVICE_NAME);
        }

        function setReferenceDeviceName(referenceDeviceName) {
            set(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.REFERENCE_DEVICE_NAME, referenceDeviceName);
        }

        function removeReferenceDeviceName() {
            removeConfiguration(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.REFERENCE_DEVICE_NAME);
        }

        function setRegistrationPolicyId(id) {
            set(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.REGISTRATION_POLICY_ID, id);
        }

        function getRegistrationPolicyId() {
            return get(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.REGISTRATION_POLICY_ID);
        }

        function removeRegistrationPolicyId() {
            removeConfiguration(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.REGISTRATION_POLICY_ID);
        }

        function getIntegrationType() {
            return get(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.INTEGRATION_TYPE);
        }

        function setIntegrationType(integrationType) {
            set(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.INTEGRATION_TYPE, integrationType);
        }

        function removeIntegrationType() {
            removeConfiguration(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.INTEGRATION_TYPE);
        }

        function getAppspaceConfiguration() {
            var configuration = get(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.APPSPACE_CONFIGURATION);
            return configuration ? JSON.parse(configuration) : configuration;
        }

        function setAppspaceConfiguration(configuration) {
            configuration = configuration || {};
            if (!dataUtil.isObject(configuration)) {
                throw new Error('Configuration must be an object');
            }

            var processedConfiguration = {};
            for (var key in configuration) {
                var mappedKey = APPSPACE_CONFIGURATION_PROPERTY[key.toUpperCase()];
                if (!mappedKey) {
                    throw new Error('Configuration object contains an invalid key');
                }

                processedConfiguration[mappedKey] = configuration[key];
            }

            set(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.APPSPACE_CONFIGURATION, JSON.stringify(processedConfiguration));
        }

        function clearAppspaceConfiguration() {
            removeConfiguration(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.APPSPACE_CONFIGURATION);
        }

        function getServiceApiUrl() {
            return UrlUtil.removeTrailingSlash((get(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.SERVICE_API_URL) || '').toLowerCase());
        }

        return {
            // Public interface
            set: set,
            get: get,
            getConfigurations: getConfigurations,
            getCollections: getCollections,
            removeConfiguration: removeConfiguration,
            removeCollection: removeCollection,
            registerConfigurationListener: registerConfigurationListener,
            removeConfigurationListener: removeConfigurationListener,
            registerCollectionListener: registerCollectionListener,
            removeCollectionListener: removeCollectionListener,
            isAppspaceReady: isAppspaceReady,
            isReady: isReady,
            getReferenceDeviceId: getReferenceDeviceId,
            setReferenceDeviceId: setReferenceDeviceId,
            removeReferenceDeviceId: removeReferenceDeviceId,
            getSerialNumber: getSerialNumber,
            setSerialNumber: setSerialNumber,
            getReferenceDeviceName: getReferenceDeviceName,
            setReferenceDeviceName: setReferenceDeviceName,
            removeReferenceDeviceName: removeReferenceDeviceName,
            getIntegrationType: getIntegrationType,
            setIntegrationType: setIntegrationType,
            removeIntegrationType: removeIntegrationType,
            setRegistrationPolicyId: setRegistrationPolicyId,
            getRegistrationPolicyId: getRegistrationPolicyId,
            removeRegistrationPolicyId: removeRegistrationPolicyId,
            getAppspaceConfiguration: getAppspaceConfiguration,
            setAppspaceConfiguration: setAppspaceConfiguration,
            clearAppspaceConfiguration: clearAppspaceConfiguration,
            getServiceApiUrl: getServiceApiUrl
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.service('contentFallbackService', ["$q", "dataUtil", "mediaUtil", "logService", "halNetworkService", "halMediaService", "deviceTokenService", "deviceStateService", "embedProfileService", "configurationService", "coreConfigurationService", "authenticationLibraryService", "networkContentCacheService", "storageFileService", "FileUtil", "STORAGE_KEY", "CONTENT_TYPE", "PLAYER_PROPERTY_KEYS", "CONTENT_ACCESS_CONTEXT", "STORAGE_COLLECTION_KEY", function(
        $q,
        dataUtil,
        mediaUtil,
        logService,
        halNetworkService,
        halMediaService,
        deviceTokenService,
        deviceStateService,
        embedProfileService,
        configurationService,
        coreConfigurationService,
        authenticationLibraryService,
        networkContentCacheService,
        storageFileService,
        FileUtil,
        STORAGE_KEY,
        CONTENT_TYPE,
        PLAYER_PROPERTY_KEYS,
        CONTENT_ACCESS_CONTEXT,
        STORAGE_COLLECTION_KEY
    ) {
        var configuration;
        var deviceTokens;
        var contentBaseUrl = '';
        var currentDeviceState;
        var subscribers = [];
        var LOG_KEY = 'appspace.core.contentfallback';
        var CACHE_FALLBACK_CONTENT_REPO = 'signs.fallbackcontent';
        var DEFAULT_CONFIGURATION = {};
        var DEVICE_PROPERTY_KEY = PLAYER_PROPERTY_KEYS.FALLBACK_CONTENT_CONFIGURATION;
        var FALLBACK_CONTENT_TYPE = {
            MEDIA: 'media',
        };
        var FALLBACK_CONTENT_JSON_FILE_NAME = 'fallbackcontent.json';
        var SUPPORTED_MEDIA_TYPES = [CONTENT_TYPE.IMAGE, CONTENT_TYPE.VIDEO];
        var NOT_FOUND_INDEX = -1;
        var lastNotifiedData;
        var FALLBACK_CONTENT_JSON_PATH = FileUtil.join(CACHE_FALLBACK_CONTENT_REPO, FALLBACK_CONTENT_JSON_FILE_NAME);

        function retrieveCachedConfiguration() {
            var cachedConfiguration = dataUtil.tryParseJSON(coreConfigurationService.get(STORAGE_COLLECTION_KEY.PLAY, STORAGE_KEY.FALLBACK_CONTENT_CONFIGURATION));
            return cachedConfiguration && cachedConfiguration.mediaContentItems && cachedConfiguration.mediaContentItems.length ?
                cachedConfiguration : undefined;
        }

        function saveFallbackContentConfiguration() {
            coreConfigurationService.set(
                STORAGE_COLLECTION_KEY.PLAY,
                STORAGE_KEY.FALLBACK_CONTENT_CONFIGURATION,
                JSON.stringify(configuration));

            if (!configuration || dataUtil.isObjectEmpty(configuration)) {
                // Skip writing file as the entire signs.fallbackcontent folder will be purged momentarily
                logService.debug(LOG_KEY, 'Empty fallback content configuration.');
                return;
            }

            storageFileService.writeFile(getCacheFilePath(FALLBACK_CONTENT_JSON_PATH), JSON.stringify(configuration)).then(function() {
                logService.debug(LOG_KEY, 'Fallback content cached.');
            }, function(err) {
                logService.warn(LOG_KEY, 'Failed to cache fallback content JSON. Reason: ' + JSON.stringify(err));
            });
        }

        function notify(data) {
            for (var i = 0; i < subscribers.length; i++) {
                subscribers[i](data);
            }
        }

        function getCacheFilePath(filePath) {
            var defaultCachePath = '/cache';
            var cachePath = coreConfigurationService.get(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.CACHE_PATH) || storageFileService.resolveUrl(defaultCachePath);

            return FileUtil.join(cachePath, filePath);
        }

        function subscribe(callback) {
            if (!callback || subscribers.indexOf(callback) !== NOT_FOUND_INDEX) {
                return;
            }

            subscribers.push(callback);
        }

        function unsubscribe(callback) {
            var subscriberIndex = subscribers.indexOf(callback);
            if (subscriberIndex === NOT_FOUND_INDEX) {
                return;
            }

            subscribers.splice(subscriberIndex, 1);
        }

        function getAccountId() {
            return authenticationLibraryService.getConstellationAccountId(deviceTokens);
        }

        function getBaseUrl() {
            return authenticationLibraryService.getConstellationBaseUrl(deviceTokens);
        }

        function getContentBaseUrl() {
            if (contentBaseUrl) {
                return $q.resolve();
            }

            deviceTokens = deviceTokenService.getConstellationTokens();
            var deferred = $q.defer();
            var onFailed = function(reason) {
                if (!dataUtil.isString(reason)) {
                    reason = JSON.stringify(reason);
                }

                logService.warn(LOG_KEY, 'Unable to retrieve content base URL due to: ' + reason);
                deferred.reject(reason);
            };

            getBaseUrl().then(function(url) {
                getAccountId().then(function(accountId) {
                    contentBaseUrl = url + '/contents/' + accountId + '/';
                    deferred.resolve();
                }, onFailed);
            }, onFailed);
            return deferred.promise;
        }

        function onContentUpdated(items) {
            if (configuration) {
                configuration.mediaContentItems = $angular.copy(items);
            }

            saveFallbackContentConfiguration();
            updateFallbackContent();
            processCaching();
        }

        function processContentItems(contentItems) {
            if (!contentItems.length) {
                onContentUpdated(contentItems);
                return;
            }

            var processedContentItems = [];
            getContentBaseUrl().then(function() {
                for (var i = 0; i < contentItems.length; i++) {
                    var item = contentItems[i];
                    if (!item ||
                        !item.contentId ||
                        !item.contentType ||
                        !dataUtil.includesValue(item.contentType.toLowerCase(), SUPPORTED_MEDIA_TYPES)
                        ) {
                        continue;
                    }

                    item.mediaUrl = mediaUtil.generateContentUrl(item.contentId, contentBaseUrl, CONTENT_ACCESS_CONTEXT.CONTENT_FALLBACK);
                    processedContentItems.push(item);
                }

                onContentUpdated(processedContentItems);
            }, function(error) {
                logService.warn(LOG_KEY, 'Failed to process fallback content items due to missing content base URL: ' + error);
            });
        }

        function processFallbackContent() {
            if (!configuration) {
                processContentItems([]);
                return;
            }

            switch ((configuration.type || '').toLowerCase()) {
                case FALLBACK_CONTENT_TYPE.MEDIA:
                    var contentItems = $angular.copy(configuration.mediaContentItems);
                    processContentItems(dataUtil.isArray(contentItems) ? contentItems : []);
                    break;
                default:
                    // Unsupported content type, reset fallback content
                    processContentItems([]);
                    break;
            }
        }

        function onDevicePropertiesUpdated(properties) {
            properties = properties || {};
            processConfiguration(dataUtil.tryParseJSON(properties[DEVICE_PROPERTY_KEY]));
            processFallbackContent();
        }

        function updateFallbackContent() {
            var isFallbackMode = shouldShowFallbackContent();
            var dataToNotify = isFallbackMode ? configuration : undefined;
            if (dataUtil.isObjectEqual(lastNotifiedData, dataToNotify)) {
                return;
            }

            notify(dataToNotify);
            lastNotifiedData = $angular.copy(dataToNotify);
            if (!isFallbackMode) {
                return;
            }

            logService.warn(LOG_KEY, 'Content fallback mode enforced due to erroneous device state - ' + JSON.stringify(currentDeviceState));
        }

        function shouldShowFallbackContent() {
            return Boolean(currentDeviceState &&
                currentDeviceState.code &&
                configuration &&
                configuration.isEnabled &&
                configuration.mediaContentItems &&
                configuration.mediaContentItems.length > 0);
        }

        function onDeviceStateUpdated(state) {
            currentDeviceState = state;
            updateFallbackContent();
        }

        function processConfiguration(updatedConfiguration) {
            if (updatedConfiguration) {
                configuration = $angular.copy(updatedConfiguration);
                return;
            }

            var devicePropertyConfiguration = dataUtil.tryParseJSON(configurationService.getCurrentProperty(DEVICE_PROPERTY_KEY));
            configuration = devicePropertyConfiguration ?
                $angular.copy(devicePropertyConfiguration) : DEFAULT_CONFIGURATION;
        }

        function initialize() {
            if (embedProfileService.isPreviewMode()) {
                logService.debug(LOG_KEY, 'Content fallback is not supported in preview mode');
                return;
            }

            halMediaService.isReady().then(function() {
                logService.info(LOG_KEY, 'Content fallback service initialized');
                configurationService.subscribe(onDevicePropertiesUpdated);
    
                initConfiguration().then(processFallbackContent);
                deviceStateService.subscribe(onDeviceStateUpdated);
                onDeviceStateUpdated(deviceStateService.getCurrentState());
            });
        }

        function initConfiguration() {
            var configuration = retrieveCachedConfiguration();

            if (configuration) {
                processConfiguration(configuration);
                return $q.resolve();
            }

            var defer = $q.defer();

            $q.allSettled([configurationService.isReady(), loadConfiguration()]).then(function() {
                processConfiguration(configuration);
                defer.resolve();
            });

            return defer.promise;
        }

        function loadConfiguration() {
            var defer = $q.defer();

            if (configuration && configuration.mediaContentItems && configuration.mediaContentItems.length) {
                defer.resolve();
                return defer.promise;
            }

            storageFileService.readFile(getCacheFilePath(FALLBACK_CONTENT_JSON_PATH)).then(function(data) {
                configuration = data && dataUtil.isString(data) ? dataUtil.tryParseJSON(data) : undefined;
                defer.resolve();
            }, function(err) {
                logService.warn(LOG_KEY, 'Failed to load fallback content JSON. Reason: ' + JSON.stringify(err));
                defer.resolve();
            });

            return defer.promise;
        }

        function clearRepository() {
            networkContentCacheService.clearRepository(CACHE_FALLBACK_CONTENT_REPO);
        }

        function reset() {
            configuration = undefined;
            deviceTokens = undefined;
            contentBaseUrl = '';
            currentDeviceState = undefined;
            clearRepository();
            processFallbackContent();
            processCaching();
        }

        function setCacheManifest(files) {
            var cacheManifest = {
                files: files,
            };
            coreConfigurationService.set(
                STORAGE_COLLECTION_KEY.PLAY,
                STORAGE_KEY.FALLBACK_CACHE_MANIFEST,
                JSON.stringify(cacheManifest));
            halNetworkService.updateResourceManifest(cacheManifest, CACHE_FALLBACK_CONTENT_REPO);
        }

        function processCaching() {
            var deferred = $q.defer();
            var cacheManifest = [];
            if (!configuration || !configuration.mediaContentItems || !configuration.mediaContentItems.length) {
                clearRepository();
                setCacheManifest(cacheManifest);
                storageFileService.removeFile(getCacheFilePath(FALLBACK_CONTENT_JSON_PATH)).then(function() {
                    logService.debug(LOG_KEY, 'Fallback content JSON removed.');
                }, function(err) {
                    logService.warn(LOG_KEY, 'Failed to remove fallback content JSON. Reason: ' + JSON.stringify(err));
                });

                logService.warn(LOG_KEY, 'No fallback content to cache.');
                return deferred.resolve();
            }

            var cachePromises = [];

            logService.debug(LOG_KEY, 'Caching fallback content.');
            for (var i = 0; i < configuration.mediaContentItems.length; i++) {
                var mediaContent = configuration.mediaContentItems[i];
                if (!mediaContent ||
                    !mediaContent.contentId || !mediaContent.mediaUrl) {
                    continue;
                }

                var cachePromise = networkContentCacheService.cacheFile(CACHE_FALLBACK_CONTENT_REPO, mediaContent.mediaUrl, false).then($angular.noop(), function(err) {
                    logService.warn(LOG_KEY, 'Failed to cache fallback content with content id ' + mediaContent.contentId + '. Reason: ' + JSON.stringify(err));
                });

                cacheManifest.push({
                    url: mediaContent.mediaUrl,
                    contentId: mediaContent.contentId,
                });
                cachePromises.push(cachePromise);
            }

            var finalize = function() {
                logService.debug(LOG_KEY, 'Fallback content cached.');
                setCacheManifest(cacheManifest);
                deferred.resolve();
            };

            $q.allSettled(cachePromises).then(finalize, finalize);
        }

        function getCacheManifest() {
            var cachedManifest = dataUtil.tryParseJSON(
                coreConfigurationService.get(
                    STORAGE_COLLECTION_KEY.PLAY,
                    STORAGE_KEY.FALLBACK_CACHE_MANIFEST)
            );

            return cachedManifest || {};
        }

        return {
            initialize: initialize,
            reset: reset,
            subscribe: subscribe,
            unsubscribe: unsubscribe,
            getCacheManifest: getCacheManifest,
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.service('contentStateService', ["$filter", "halMediaService", "deviceStateService", "dataUtil", "ASERRORCODE", "CONTENT_STATE_EVENT", function(
        $filter,
        halMediaService,
        deviceStateService,
        dataUtil,
        ASERRORCODE,
        CONTENT_STATE_EVENT) {
        var currentChannel;
        var channelLayouts = [];
        var ERROR_STATE_MAPPING = {};
        ERROR_STATE_MAPPING[CONTENT_STATE_EVENT.NO_CONTENT_AVAILABLE] = {
            code: ASERRORCODE.NO_CONTENT_AVAILABLE,
            message: 'No content available',
        };
        ERROR_STATE_MAPPING[CONTENT_STATE_EVENT.NO_VALID_CONTENT] = {
            code: ASERRORCODE.NO_VALID_CONTENT,
            message: 'No valid content',
        };
        ERROR_STATE_MAPPING[CONTENT_STATE_EVENT.CONTENT_AVAILABLE] = {};

        function setCurrentChannel(channel) {
            var oldApplicationId = currentChannel ? currentChannel.applicationId : null;
            currentChannel = channel;
            if (oldApplicationId === channel.applicationId) {
                return;
            }

            channelLayouts = [];
        }

        function populateZone(widgetInstance) {
            if (!widgetInstance || !widgetInstance.datasource || !widgetInstance.datasource.result || !widgetInstance.datasource.result.playlists) {
                return {};
            }

            var items = [];
            for (var j = 0; widgetInstance.datasource.result.playlists.length > j; j++) {
                var playlist = widgetInstance.datasource.result.playlists[j];
                for (var k = 0; playlist.items.length > k; k++) {
                    var content = playlist.items[k];
                    if (!content) {
                        continue;
                    }

                    items.push({
                        id: content.id,
                        contentId: content.contentId,
                        state: '',
                    });
                }
            }

            return {
                id: widgetInstance.id,
                items: items,
            };
        }

        function populateCurrentContentLayout(layout) {
            // only populate layout when single layout and single widget/zone
            if (!layout ||
                !layout.widgetInstances ||
                !isSingleLayoutChannel() ||
                Object.keys(layout.widgetInstances).length !== 1
            ) {
                return;
            }

            // remove layout from channellayouts if same layoutId exist
            channelLayouts = $filter('filter')(channelLayouts, function(layout) {
                return layout.id === layout.layoutId;
            });
            var zones = [];
            // populate layout zones
            for (var widgetId in layout.widgetInstances) {
                zones.push(populateZone(layout.widgetInstances[widgetId]));
            }

            channelLayouts.push({
                id: layout.layoutId,
                zones: zones,
            });
        }

        function areAllContentHasErrors() {
            for (var i = 0; channelLayouts.length > i; i++) {
                if (!channelLayouts[i] || !channelLayouts[i].zones) {
                    continue;
                }

                var layout = channelLayouts[i];
                for (var j = 0; layout.zones.length > j; j++) {
                    var zone = layout.zones[j];
                    if (!zone) {
                        continue;
                    }

                    for (var k = 0; zone.items.length > k; k++) {
                        if (!zone.items[k] || zone.items[k].state === CONTENT_STATE_EVENT.ITEM_LOAD_ERROR) {
                            continue;
                        }

                        return false;
                    }
                }
            }

            return true;
        }

        function updateContentItemState(zoneId, content, state) {
            if (!zoneId || !content || !state) {
                return false;
            }

            var currentItem = findContentItem(zoneId, content);
            if (!currentItem) {
                return false;
            }

            currentItem.state = state;
            return true;
        }

        function isSingleLayoutChannel() {
            return currentChannel && currentChannel.layouts && currentChannel.layouts.length === 1;
        }

        function isValidZone(zoneId) {
            for (var i = 0; channelLayouts.length > i; i++) {
                if (!channelLayouts[i] || !channelLayouts[i].zones) {
                    continue;
                }

                var layout = channelLayouts[i];
                for (var j = 0; layout.zones.length > j; j++) {
                    if (!layout.zones[j] || dataUtil.isNullOrUndefined(layout.zones[j].id) || layout.zones[j].id !== zoneId) {
                        continue;
                    }

                    return true;
                }
            }

            return false;
        }

        function findContentItem(zoneId, content) {
            if (!zoneId || !content) {
                return null;
            }

            for (var i = 0; channelLayouts.length > i; i++) {
                if (!channelLayouts[i] || !channelLayouts[i].zones) {
                    continue;
                }

                var layout = channelLayouts[i];
                for (var j = 0; layout.zones.length > j; j++) {
                    if (!layout.zones[j] || dataUtil.isNullOrUndefined(layout.zones[j].id) || layout.zones[j].id !== zoneId) {
                        continue;
                    }

                    var zone = layout.zones[j];
                    for (var k = 0; zone.items.length > k; k++) {
                        if (!zone.items[k] || zone.items[k].id !== content.id || zone.items[k].contentId !== content.contentId) {
                            continue;
                        }

                        return zone.items[k];
                    }
                }
            }

            return null;
        }

        function areAllZonesEmpty() {
            for (var i = 0; channelLayouts.length > i; i++) {
                if (!channelLayouts[i] || !channelLayouts[i].zones) {
                    continue;
                }

                var layout = channelLayouts[i];
                for (var j = 0; layout.zones.length > j; j++) {
                    if (!layout.zones[j] || !layout.zones[j].items || layout.zones[j].items.length === 0) {
                        continue;
                    }

                    return false;
                }
            }

            return true;
        }

        function updateContentState(data) {
            if (!data.state) {
                return;
            }

            switch (data.state) {
                case CONTENT_STATE_EVENT.ITEM_LOAD_ERROR:
                    if (!data.content || dataUtil.isNullOrUndefined(data.zoneId) || !isValidZone(data.zoneId)) {
                        return;
                    }

                    var status = updateContentItemState(data.zoneId, data.content, data.state);
                    if (!status) {
                        return;
                    }

                    break;
                case CONTENT_STATE_EVENT.END_OF_PLAYLIST:
                    if (dataUtil.isNullOrUndefined(data.zoneId) || !isValidZone(data.zoneId) || !areAllContentHasErrors()) {
                        return;
                    }

                    data.state = areAllZonesEmpty() ? CONTENT_STATE_EVENT.NO_CONTENT_AVAILABLE : CONTENT_STATE_EVENT.NO_VALID_CONTENT;
                    break;
                case CONTENT_STATE_EVENT.NO_CONTENT_AVAILABLE:
                    if (!channelLayouts || channelLayouts.length === 0) {
                        return;
                    }

                    break;
            }

            halMediaService.processContentState(data);
            var mappedErrorState = ERROR_STATE_MAPPING[data.state];
            if (!mappedErrorState) {
                return;
            }

            deviceStateService.setCurrentState(mappedErrorState);
        }

        function updateZone(widgetInstance) {
            if (!channelLayouts || channelLayouts.length === 0) {
                return;
            }

            for (var i = 0; channelLayouts.length > i; i++) {
                if (!channelLayouts[i] || !channelLayouts[i].zones) {
                    continue;
                }

                for (var j = 0; channelLayouts[i].zones.length > j; j++) {
                    if (!channelLayouts[i].zones[j] || channelLayouts[i].zones[j].id !== widgetInstance.id) {
                        continue;
                    }

                    channelLayouts[i].zones[j] = populateZone(widgetInstance);
                    break;
                }
            }
        }

        return {
            updateContentState: updateContentState,
            populateCurrentContentLayout: populateCurrentContentLayout,
            setCurrentChannel: setCurrentChannel,
            updateZone: updateZone,
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('core');

    app.service('cronService', ["$timeout", "dataUtil", "DateTimeUtil", "logService", "halPlatformService", "embedProfileService", "deviceStateService", function(
        $timeout,
        dataUtil,
        DateTimeUtil,
        logService,
        halPlatformService,
        embedProfileService,
        deviceStateService
    ) {
        var LOG_KEY = 'appspace.core.cron';
        var INTERVAL_FORMAT = 'HH:mm:ss';
        var CHECK_CRON_INTERVAL = '00:01:00'; // 1 minute
        var dateTimeUtil = new DateTimeUtil();
        var cronJobs = {};
        var isStarted = false;
        var checkCronTimer;

        function start() {
            if (isStarted) {
                logService.debug(LOG_KEY, 'Cron service has already started');
                return;
            }

            if (embedProfileService.isPreviewMode() || !deviceStateService.isRegistered()) {
                isStarted = false;
                logService.debug(LOG_KEY, 'Cron service is not supported in preview or unregistered mode');
                return;
            }

            isStarted = true;
            logService.info(LOG_KEY, 'Cron service started');
            var jobs = halPlatformService.getCronJobs();
            if (!jobs || !dataUtil.isArray(jobs) || jobs.length === 0) {
                return;
            }

            for (var i = 0; i < jobs.length; i++) {
                var job = jobs[i];
                if (!job || !dataUtil.isObject(job)) {
                    continue;
                }

                addCronJob(job);
            }

            startCheckCronTimer();
        }

        function cancelCheckCronTimer() {
            if (checkCronTimer) {
                $timeout.cancel(checkCronTimer);
            }

            checkCronTimer = undefined;
        }

        function startCheckCronTimer() {
            cancelCheckCronTimer();
            var nextCheckAt = getNextExecutionTime(CHECK_CRON_INTERVAL);
            var nextCheckInMs = nextCheckAt - new Date().getTime();
            checkCronTimer = $timeout(checkCron, nextCheckInMs);
            logService.trace(LOG_KEY, 'Cron jobs check scheduled in ' + dateTimeUtil.toTimeText(dateTimeUtil.toSeconds(nextCheckInMs)));
        }

        function checkCron() {
            cancelCheckCronTimer();
            logService.trace(LOG_KEY, 'Performing cron jobs check');
            for (var key in cronJobs) {
                var job = cronJobs[key];
                if (!dateTimeUtil.isNowOrPast(job.nextExecutionAt)) {
                    continue;
                }

                job.callback();
                logService.info(LOG_KEY, 'Cron job [' + job.id + '] executed');
                addCronJob(job);
            }

            startCheckCronTimer();
        }

        function stop() {
            if (!isStarted) {
                logService.debug(LOG_KEY, 'Cron service has already stopped');
                return;
            }

            cancelCheckCronTimer();
            isStarted = false;
            cronJobs = {};
            logService.info(LOG_KEY, 'Cron service stopped');
        }

        function getNextExecutionTime(interval) {
            return dateTimeUtil.addDurationToTicks(new Date().getTime(), interval);
        }

        function removeCronJob(jobId, isUpdated) {
            if (!jobId || !cronJobs[jobId]) {
                return;
            }

            delete cronJobs[jobId];
            if (isUpdated) {
                return;
            }

            logService.info(LOG_KEY, 'Cron job [' + jobId + '] removed');
        }

        function addCronJob(job) {
            if (!job ||
                !dataUtil.isObject(job) ||
                !job.id ||
                !dateTimeUtil.matchesFormat(job.interval, INTERVAL_FORMAT) ||
                !job.callback) {
                return;
            }

            if (cronJobs[job.id]) {
                removeCronJob(job.id, true);
            }

            cronJobs[job.id] = $angular.copy(job);
            cronJobs[job.id].nextExecutionAt = getNextExecutionTime(job.interval);
            logService.debug(LOG_KEY, 'Cron job [' + job.id + '] scheduled for ' + dateTimeUtil.toTimeText(job.interval) +
                ' later at ' + new Date(cronJobs[job.id].nextExecutionAt).toString());
        }

        function getCronJobs() {
            return cronJobs;
        }

        return {
            start: start,
            stop: stop,
            addCronJob: addCronJob,
            removeCronJob: removeCronJob,
            getCronJobs: getCronJobs,
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('core');

    app.service('deviceIdentityService', ["$q", "$timeout", "$injector", "logService", "coreConfigurationService", "halPlatformService", "versionUtil", "dataUtil", "deviceAPIService", "embedProfileService", function(
        $q,
        $timeout,
        $injector,
        logService,
        coreConfigurationService,
        halPlatformService,
        versionUtil,
        dataUtil,
        deviceAPIService,
        embedProfileService) {
        var FAIL_RETRY_TIMEOUT = 60000;
        var CHECK_IDENTITY_TIMEOUT = 86400000; // 24 hours
        var NO_DELAY_TIMEOUT = 0;
        var LOG_KEY = 'appspace.core.deviceidentity';
        var KEY_APPSPACE_NAMESPACE = 'appspace';
        var KEY_DEVICE_NAMESPACE = 'device';
        var KEY_DEVICE_GUID = 'playerguid';
        var KEY_DEVICE_IDENTITY = 'identity';
        var KEY_DEVICE_IDENTITY_HASH = 'hash';
        var KEY_DEVICE_TYPE = 'type';
        var KEY_SERVER_VERSION = 'serverVersion';
        var KEY_SERVICE_API_URL = 'serviceApiUrl';
        var MAX_RETRY_COUNT = 5;
        var RETRY_TIMEOUT = 10000;
        var SUPPORTED_DEVICE_IDENTITY_VERSIONS = {
            CLOUD: {
                MIN: '7.1.0-ac.20',
                MAX: '7.1.0'
            },
            GLOBAL: '7.1.3'
        };
        var SUPPORTED_DEVICE_CAPABILITIES_VERSIONS = {
            CLOUD: {
                MIN: '7.1.0-ac.27',
                MAX: '7.1.0'
            },
            GLOBAL: '7.1.5'
        };
        var SUPPORTED_DEVICE_SERVICE_VERSIONS = {
            CLOUD: {
                MIN: '7.2.0-ac.7',
                MAX: '7.2.0'
            },
            GLOBAL: '7.2.0'
        };
        var ERROR_TYPE = {
            IDENTITY: 'identity',
            CAPABILITIES: 'capabilities'
        };
        var APPSPACE_IDENTITY_FEATURE_KEY = {
            PUSH_HEALTH_STATUS: 'pnsdevicestatus',
        };
        var COMPULSORY_CAPABILITY_KEYS = ['supportFileCaching', 'appUpdate', 'conference', 'screenshot'];
        var DEVICE_NAME_URL_PARAMETER_KEY = 'devicename';
        var USE_SERVER_DEVICE_TYPES = ['PWA'];
        var driverModel = halPlatformService.getAppspaceDeviceType();
        var USE_SERVER_DEVICE_TYPE = dataUtil.includesValue(driverModel, USE_SERVER_DEVICE_TYPES);
        var isAppspaceVersionValid = versionUtil.isAppspaceVersionValid;
        var getAppspaceVersionFromCache = versionUtil.getAppspaceVersionFromCache;
        var getAppspaceVersionFromServer = versionUtil.getAppspaceVersionFromServer;
        var checkIdentityTimeoutPromise;
        var updateIdentityDeferred;
        var pendingIdentityToUpdate;
        var isStarted = false;
        var isStarting = false;
        var subscribers = [];
        var registrationService;
        var appspaceFeatures = {};
        var requirementsValidation = {};
        var STORAGE_DIFFERENCE_THRESHOLD = 0.01; // 1%
        var dataMassageDefinitions = {
            'hardware.storage': function(oldValue, newValue) {
                var oldSize = parseFloat(oldValue);
                var newSize = parseFloat(newValue);

                if (Math.abs(oldSize - newSize) / oldSize > STORAGE_DIFFERENCE_THRESHOLD) {
                    logService.debug(LOG_KEY, 'Hardware storage difference exceeded ' + (STORAGE_DIFFERENCE_THRESHOLD * 100) + '%. Old: ' + oldValue + ', New: ' + newValue);
                    // return new value if exceed threshold
                    return newValue;
                }

                logService.debug(LOG_KEY, 'Hardware storage difference within ' + (STORAGE_DIFFERENCE_THRESHOLD * 100) + '%. Old: ' + oldValue + ', New: ' + newValue);
                return oldValue;
            }
        };

        function getRegistrationService() {
            if (!registrationService) {
                // To avoid circular dependency
                registrationService = $injector.get('deviceRegistrationService');
            }

            return registrationService;
        }

        function validateRequirements() {
            // Requirements have been validated before, return validation result
            if (!dataUtil.isObjectEmpty(requirementsValidation)) {
                return $q.resolve(requirementsValidation);
            }

            var deferred = $q.defer();
            var finalizeResult = function(requirementsMet, isIdentityReady) {
                requirementsValidation = {
                    requirementsMet: requirementsMet,
                    isIdentityReady: isIdentityReady,
                };
                deferred.resolve(requirementsValidation);
                return deferred.promise;
            };

            coreConfigurationService.isReady().then(function() {
                if (embedProfileService.isPreviewMode()) {
                    logService.warn(LOG_KEY, 'Skipping device identity checking in preview mode');
                    return finalizeResult(false, true);
                }

                if (!getRegistrationService().getPlayerId()) {
                    logService.debug(LOG_KEY, 'Skipping device identity checking in unregistered state');
                    return finalizeResult(false, false);
                }

                getDeviceModel().then(function(registeredModel) {
                    if (!coreConfigurationService.getIntegrationType() &&
                        driverModel.toLowerCase() !== registeredModel.toLowerCase()) {
                        logService.warn(LOG_KEY, 'Mismatch driver model type and Appspace model type (driver: ' + driverModel + ', Appspace: ' + registeredModel + '). Preventing device identity service to start.');
                        return finalizeResult(false, false);
                    }

                    return finalizeResult(true, true);
                }, function() {
                    logService.warn(LOG_KEY, 'Unable to start identity service due to missing device model from Appspace');
                    return finalizeResult(false, false);
                });
            });

            return deferred.promise;
        }

        function start() {
            if (isStarted || isStarting) {
                logService.debug(LOG_KEY, 'Device identity service is already running');
                return;
            }

            isStarting = true;
            validateRequirements().then(function(result) {
                if (!result.requirementsMet) {
                    if (result.isIdentityReady) {
                        notifySubscribers();
                    }

                    return;
                }

                startCheckIdentity(NO_DELAY_TIMEOUT);
                isStarted = true;
                isStarting = false;
            });
        }

        function stop() {
            if (!isStarted) {
                logService.debug(LOG_KEY, 'Device identity service is already stopped');
            } else {
                cancelCheckIdentity();
            }

            isStarted = false;
            isStarting = false;
            requirementsValidation = {};
        }

        function restart() {
            logService.debug(LOG_KEY, 'Restarting device identity service...');
            stop();
            start();
        }

        function startCheckIdentity(timeout) {
            cancelCheckIdentity();
            logService.debug(LOG_KEY, 'Checking device identity in ' + dataUtil.toMinutes(timeout) + ' minutes');
            checkIdentityTimeoutPromise = $timeout(checkIdentity, timeout);
        }

        function cancelCheckIdentity() {
            if (!checkIdentityTimeoutPromise) {
                return;
            }

            $timeout.cancel(checkIdentityTimeoutPromise);
            checkIdentityTimeoutPromise = undefined;
        }

        function notifySubscribers(data) {
            for (var i = 0; i < subscribers.length; i++) {
                subscribers[i](data);
            }
        }

        function subscribeToIdentityCheck(callback) {
            if (!callback || subscribers.indexOf(callback) > -1) {
                return;
            }

            subscribers.push(callback);
        }

        function unsubscribeToIdentityCheck(callback) {
            var subscriberIndex = subscribers.indexOf(callback);
            if (subscriberIndex === -1) {
                return;
            }

            subscribers.splice(subscriberIndex, 1);
        }

        function checkIdentity() {
            logService.debug(LOG_KEY, 'Checking device identity');
            var deviceGuid = coreConfigurationService.get(KEY_DEVICE_NAMESPACE, KEY_DEVICE_GUID);
            if (!deviceGuid) {
                logService.debug(LOG_KEY, 'No device GUID found during identity checking. Stopping process.');
                stop();
                return;
            }

            var generateFailMessage = function(type) {
                return 'Unable to retrieve device ' + type + ' due to unsupported Appspace version (' + getAppspaceVersionFromCache() + '), retrying.';
            };

            var onGenerateSuccessfully = function(deviceIdentity) {
                // send the identity and stop directly after that
                updateDeviceIdentity(deviceIdentity).finally(function() {
                    notifySubscribers(deviceIdentity);
                    stop();
                });
            };

            var onGenerateFailed = function(error) {
                logService.warn(LOG_KEY, generateFailMessage(error.type));
                // if rejected because of capabilities, send what we have first and retry
                if (error.type === ERROR_TYPE.CAPABILITIES) {
                    updateDeviceIdentity(error.currentIdentity).finally(function() {
                        startCheckIdentity(CHECK_IDENTITY_TIMEOUT);
                    });
                    return;
                }

                // if not, retry directly
                startCheckIdentity(CHECK_IDENTITY_TIMEOUT);
            };

            var onVersionRetrieved = function() {
                generateDeviceIdentityObject().then(onGenerateSuccessfully, onGenerateFailed);
            };

            var onVersionFailedToRetrieve = function(error) {
                logService.warn(LOG_KEY, 'Failed to retrieve Appspace version, retrying:' + JSON.stringify(error));
                startCheckIdentity(FAIL_RETRY_TIMEOUT);
            };

            // get Appspace version
            getAppspaceVersionFromServer(LOG_KEY).then(onVersionRetrieved, onVersionFailedToRetrieve);
        }

        function getCurrentHash() {
            return coreConfigurationService.get(KEY_DEVICE_NAMESPACE, KEY_DEVICE_IDENTITY_HASH);
        }

        function updateDeviceIdentity(identity) {
            var deferred = $q.defer();
            validateRequirements().then(function(result) {
                if (!result.requirementsMet) {
                    var reason = 'Skipping attempt to update device identity due to failure to meet device identity service requirements.';
                    logService.warn(LOG_KEY, reason);
                    deferred.resolve(reason);
                    return;
                }

                var hash = dataUtil.toHash(identity);
                if (hash === getCurrentHash()) {
                    logService.debug(LOG_KEY, 'Device identity has been sent before. Skipping.');
                    deferred.resolve();
                    return;
                }

                logService.info(LOG_KEY, 'Sending device identity to Appspace:' + JSON.stringify(identity));
                getServiceApiUrl().then(function() {
                    sendDeviceIdentityToAppspace(identity).finally(deferred.resolve);
                }, function(errorMessage) {
                    var error = 'Failed to get Service API URL:' + errorMessage;
                    logService.warn(LOG_KEY, error);
                    deferred.reject(error);
                });
            });
            return deferred.promise;
        }

        function updateDeviceName(urlParameters) {
            var urlParameterReferenceDeviceName = urlParameters[DEVICE_NAME_URL_PARAMETER_KEY];
            // referenceDeviceName in url parameters is not set
            if (!urlParameterReferenceDeviceName) {
                return;
            }

            var storedReferenceDeviceName = halPlatformService.getReferenceDeviceName();
            // referenceDeviceName in url parameter and local storage are the same
            if (urlParameterReferenceDeviceName === storedReferenceDeviceName) {
                return;
            }

            halPlatformService.updateReferenceDeviceName();
            // integration type doesn't exist
            if (!halPlatformService.getIntegrationType()) {
                return;
            }

            logService.debug(LOG_KEY, ('Updating device name to ' + urlParameterReferenceDeviceName));
            deviceAPIService.updateDeviceName(urlParameterReferenceDeviceName).then(function() {
                logService.debug(LOG_KEY, 'Updated device name successfully.');
            }, function(error) {
                logService.error(LOG_KEY, 'Failed to update device name: ' + JSON.stringify(error));
            });
        }

        function updateAppspaceFeature(key, value) {
            return generateDeviceIdentityObject().then(function(identity) {
                if (identity.appspace.features[key] === value) {
                    return;
                }

                appspaceFeatures[key] = value;
                identity.appspace.features[key] = value;
                return updateDeviceIdentity(identity);
            });
        }

        function updateDevicePushHealthStatusFeature(enabled) {
            return updateAppspaceFeature(APPSPACE_IDENTITY_FEATURE_KEY.PUSH_HEALTH_STATUS, enabled);
        }

        function getServiceApiUrl() {
            var serviceApiUrl = coreConfigurationService.get(KEY_APPSPACE_NAMESPACE, KEY_SERVICE_API_URL);
            if (serviceApiUrl) {
                return $q.resolve(serviceApiUrl);
            }

            var promise = $q.defer();
            deviceAPIService.getPlayerServiceApi().then(function(apiUrl) {
                if (!apiUrl) {
                    promise.reject('Retrieved Service API URL is empty');
                    return;
                }

                coreConfigurationService.set(KEY_APPSPACE_NAMESPACE, KEY_SERVICE_API_URL, apiUrl);
                promise.resolve(apiUrl);
            }, function() {
                promise.reject('Unable to get Service API URL');
            });
            return promise.promise;
        }

        function isPendingIdentityUpdate() {
            if (!pendingIdentityToUpdate) {
                return false;
            }

            logService.debug(LOG_KEY, 'Updating pending identity');
            doSendIdentity(pendingIdentityToUpdate);
            pendingIdentityToUpdate = undefined;
            return true;
        }

        function finalizeIdentityUpdate(resolutionCallback) {
            if (isPendingIdentityUpdate()) {
                return;
            }

            updateIdentityDeferred[resolutionCallback]();
            updateIdentityDeferred = undefined;
        }

        function sendDeviceIdentityToAppspace(identity) {
            if (updateIdentityDeferred) {
                // Device identity is being updated, store the identity to update later
                logService.debug(LOG_KEY, 'Device identity is being updated, storing the identity to update later');
                pendingIdentityToUpdate = $angular.copy(identity);
                return updateIdentityDeferred.promise;
            }

            return doSendIdentity(identity);
        }

        function doSendIdentity(identity) {
            if (!updateIdentityDeferred) {
                updateIdentityDeferred = $q.defer();
            }

            var identityPayload = $angular.copy(identity);
            var sendTryCount = 0;
            var retryPromise;
            var retrySendDeviceIdentity = function() {
                clearRetry();
                retryPromise = $timeout(sendDeviceIdentityViaAPI, RETRY_TIMEOUT);
            };

            var clearRetry = function() {
                if (!retryPromise) {
                    return;
                }

                $timeout.cancel(retryPromise);
                retryPromise = undefined;
            };

            var sendDeviceIdentityViaAPI = function() {
                sendTryCount++;

                isDeviceServiceSupported().then(function(isSupported) {
                    var sendIdentityMethod = isSupported ? deviceAPIService.sendIdentityDeviceService : deviceAPIService.sendIdentityNetworkService;

                    sendIdentityMethod(identityPayload).then(function() {
                        logService.debug(LOG_KEY, 'Successfully sent device identity');
                        saveIdentity($angular.copy(identityPayload));
                        finalizeIdentityUpdate('resolve');
                    }, function() {
                        if (sendTryCount >= MAX_RETRY_COUNT) {
                            logService.warn(LOG_KEY, 'Failed to send device identity after ' + MAX_RETRY_COUNT + ' attempts.');
                            finalizeIdentityUpdate('reject');
                            return;
                        }

                        logService.warn(LOG_KEY, 'Failed to send device identity. Retrying. (' + sendTryCount + ' out of ' + MAX_RETRY_COUNT + ' attempts)');
                        retrySendDeviceIdentity();
                    });
                });
            };

            sendDeviceIdentityViaAPI();
            return updateIdentityDeferred.promise;
        }

        function isDeviceServiceSupported() {
            return versionUtil.isVersionSupported(SUPPORTED_DEVICE_SERVICE_VERSIONS, LOG_KEY);
        }

        function saveIdentity(identity) {
            logService.debug(LOG_KEY, 'Saving device identity to local:' + JSON.stringify(identity));
            coreConfigurationService.set(KEY_DEVICE_NAMESPACE, KEY_DEVICE_IDENTITY, JSON.stringify(identity));

            var hash = dataUtil.toHash(identity);
            logService.debug(LOG_KEY, 'Saving device identity hash to local:' + hash);
            coreConfigurationService.set(KEY_DEVICE_NAMESPACE, KEY_DEVICE_IDENTITY_HASH, hash);
        }

        function getCompulsoryCapabilities(capabilities) {
            var compulsoryCapabilities = {};
            for (var key in capabilities) {
                if (!dataUtil.includesValue(key, COMPULSORY_CAPABILITY_KEYS)) {
                    continue;
                }

                compulsoryCapabilities[key] = capabilities[key];
            }

            return compulsoryCapabilities;
        }

        function generateDeviceIdentityObject() {
            var promise = $q.defer();
            getDeviceInformation(isDeviceIdentitySupported, halPlatformService.getDeviceInfo).then(function(deviceIdentity) {
                if (!deviceIdentity) {
                    promise.reject({
                        type: ERROR_TYPE.IDENTITY
                    });
                    return;
                }

                getDeviceInformation(isDeviceCapabilitiesSupported, getDeviceCapabilitiesPromise).then(function(capabilities) {
                    if (!capabilities) {
                        promise.reject({
                            type: ERROR_TYPE.CAPABILITIES,
                            currentIdentity: deviceIdentity
                        });
                        return;
                    }

                    deviceIdentity.capabilities = getCompulsoryCapabilities(capabilities);
                    deviceIdentity.appspace = deviceIdentity.appspace || {};
                    deviceIdentity.appspace.features = appspaceFeatures;
                    deviceIdentity = processDeviceIdentity(deviceIdentity);
                    promise.resolve(deviceIdentity);
                });
            });
            return promise.promise;
        }

        function processDeviceIdentity(newIdentity) {
            var oldIdentity = dataUtil.tryParseJSON(coreConfigurationService.get(KEY_DEVICE_NAMESPACE, KEY_DEVICE_IDENTITY)) || {};

            for (var key in dataMassageDefinitions) {
                var oldValue = dataUtil.getObjectValueAtPath(oldIdentity, key);
                var newValue = dataUtil.getObjectValueAtPath(newIdentity, key);

                if (!oldValue) {
                    logService.debug(LOG_KEY, 'Old value for ' + key + ' is not found. Skipping.');
                    continue;
                }

                var massagedValue = dataMassageDefinitions[key](oldValue, newValue);
                try {
                    dataUtil.setObjectValueAtPath(newIdentity, key, massagedValue);
                } catch (error) {
                    logService.error(LOG_KEY, 'Failed to set massaged value for ' + key + ': ' + error);
                }
            }

            return newIdentity;
        }

        function generateDeviceIdentity() {
            var promise = $q.defer();
            generateDeviceIdentityObject().then(promise.resolve, function(error) {
                // if rejected with an identity, resolve with that identity. if not, empty object.
                promise.resolve(error.currentIdentity || {});
            });
            return promise.promise;
        }

        function getDeviceCapabilitiesPromise() {
            return $q.resolve(halPlatformService.getDeviceCapabilities());
        }

        function getDeviceInformation(versionCheckingMethod, getInfoMethod) {
            var promise = $q.defer();
            // custom resolve method to ensure we will always resolve undefined
            var onFailure = function() {
                promise.resolve();
            };

            versionUtil.getAppspaceVersion(LOG_KEY).then(function(version) {
                if (!versionCheckingMethod(version)) {
                    onFailure();
                    return;
                }

                getInfoMethod().then(promise.resolve, onFailure);
            }, onFailure);
            return promise.promise;
        }

        function isDeviceIdentitySupported(appspaceVersion) {
            return isAppspaceVersionValid(appspaceVersion, SUPPORTED_DEVICE_IDENTITY_VERSIONS);
        }

        function isDeviceCapabilitiesSupported(appspaceVersion) {
            return isAppspaceVersionValid(appspaceVersion, SUPPORTED_DEVICE_CAPABILITIES_VERSIONS);
        }

        function clean() {
            logService.debug(LOG_KEY, 'Cleaning up device identity configurations (hash, server version and service API URL)');
            coreConfigurationService.removeConfiguration(KEY_DEVICE_NAMESPACE, KEY_DEVICE_IDENTITY_HASH);
            coreConfigurationService.removeConfiguration(KEY_APPSPACE_NAMESPACE, KEY_SERVER_VERSION);
            coreConfigurationService.removeConfiguration(KEY_APPSPACE_NAMESPACE, KEY_SERVICE_API_URL);
        }

        function getDeviceModel() {
            var deviceType = coreConfigurationService.get(KEY_DEVICE_NAMESPACE, KEY_DEVICE_TYPE);
            if (deviceType && !USE_SERVER_DEVICE_TYPE) {
                return $q.resolve(deviceType);
            }

            // get from API
            var promise = $q.defer();
            deviceAPIService.getPlayerModelName().then(function(appspaceDeviceType) {
                // only save the type if we're not supposed to use server's device type
                if (!USE_SERVER_DEVICE_TYPE) {
                    coreConfigurationService.set(KEY_DEVICE_NAMESPACE, KEY_DEVICE_TYPE, appspaceDeviceType);
                }

                promise.resolve(appspaceDeviceType);
            }, promise.reject);
            return promise.promise;
        }

        function isDeviceIdentityValid() {
            var promise = $q.defer();
            generateDeviceIdentity().then(function(identity) {
                promise.resolve(dataUtil.toHash(identity) === getCurrentHash());
            });
            return promise.promise;
        }

        return {
            start: start,
            stop: stop,
            restart: restart,
            generateDeviceIdentity: generateDeviceIdentity,
            updateDeviceName: updateDeviceName,
            subscribeToIdentityCheck: subscribeToIdentityCheck,
            unsubscribeToIdentityCheck: unsubscribeToIdentityCheck,
            saveIdentity: saveIdentity,
            clean: clean,
            isDeviceIdentityValid: isDeviceIdentityValid,
            updateDevicePushHealthStatusFeature: updateDevicePushHealthStatusFeature
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('core');

    app.service('deviceMetricsService', ["$q", "$timeout", "STORAGE_KEY", "STORAGE_COLLECTION_KEY", "NETWORK_CONNECTION_STATE", "CONSTELLATION_TOKENS_EVENT", "dataUtil", "versionUtil", "DateTimeUtil", "logService", "deviceTokenService", "halPlatformService", "embedProfileService", "networkConnectionService", "coreConfigurationService", "deviceRegistrationService", "authenticationLibraryService", function(
        $q,
        $timeout,
        STORAGE_KEY,
        STORAGE_COLLECTION_KEY,
        NETWORK_CONNECTION_STATE,
        CONSTELLATION_TOKENS_EVENT,
        dataUtil,
        versionUtil,
        DateTimeUtil,
        logService,
        deviceTokenService,
        halPlatformService,
        embedProfileService,
        networkConnectionService,
        coreConfigurationService,
        deviceRegistrationService,
        authenticationLibraryService
    ) {
        var LOG_KEY = 'appspace.core.devicemetrics';
        var SUPPORTED_SERVICE_VERSIONS = {
            CLOUD: {
                MIN: '8.11.0-ac.1',
                MAX: '8.11.0'
            },
            GLOBAL: '8.11.0'
        };
        var RETRY_CONFIG = {
            exponentialBase: 2,
            backoffInterval: 60, // 1 minute
            failureBackoffMax: 1800, // 30 minutes
        };
        var STORAGE_DIFFERENCE_THRESHOLD = 0.01; // 1%
        var REPORT_METRICS_INTERVAL = '12:00:00'; // 12 hours
        var DEVICE_ID_PLACEHOLDER = '{{deviceId}}';
        var API_URL = '/api/v3/devicegateway/devices/' + DEVICE_ID_PLACEHOLDER + '/metrics';
        var dateTimeUtil = new DateTimeUtil();
        var isServiceSupported;
        var isStarting = false;
        var isStarted = false;
        var deviceId;
        var checkMetricsTimer;
        var retryTimeout;
        var attemptCount = 0;
        var isNetworkDisconnected = false;

        function processStorageValue(oldValue, newValue, key) {
            var oldSize = parseInt(oldValue, 10);
            var newSize = parseInt(newValue, 10);

            var logDifference = function(result) {
                logService.debug(LOG_KEY, 'Storage ' + key + ' difference ' + result + ' ' +
                    (STORAGE_DIFFERENCE_THRESHOLD * 100) + '% threshold. Old: ' + oldValue + ', new: ' + newValue);
            };

            if (Math.abs(oldSize - newSize) / oldSize > STORAGE_DIFFERENCE_THRESHOLD) {
                logDifference('exceeded');
                return newSize;
            }

            logDifference('within');
            return oldSize;
        }

        var processDataDefinitions = {
            'storage.totalBytes': processStorageValue,
            'storage.usedBytes': processStorageValue,
        };

        function getApiUrl() {
            return API_URL.replace(DEVICE_ID_PLACEHOLDER, deviceId);
        }

        function processData(payload) {
            var lastSentData = dataUtil.tryParseJSON(coreConfigurationService.get(
                STORAGE_COLLECTION_KEY.DEVICE,
                STORAGE_KEY.METRICS)) ||
                 {};

            for (var key in processDataDefinitions) {
                var oldValue = dataUtil.getObjectValueAtPath(lastSentData, key);
                var newValue = dataUtil.getObjectValueAtPath(payload, key);
                if (!oldValue || !newValue) {
                    continue;
                }

                var processDataCallback = processDataDefinitions[key];
                if (!processDataCallback) {
                    continue;
                }

                var processedValue = processDataCallback(oldValue, newValue, key);
                try {
                    dataUtil.setObjectValueAtPath(payload, key, processedValue);
                } catch (error) {
                    logService.error(LOG_KEY, 'Failed to set processed value for ' + key + ': ' + error);
                }
            }

            return {
                old: lastSentData,
                new: payload,
            };
        }

        function getMetricsData() {
            var deferred = $q.defer();
            var payload = {
                storage: {
                    totalBytes: 0,
                    usedBytes: 0,
                },
            };
            halPlatformService.getAvailableStorage().then(function(storage) {
                if (storage && dataUtil.isObject(storage)) {
                    payload.storage.totalBytes = storage.total;
                    payload.storage.usedBytes = storage.used;
                }

                deferred.resolve(processData(payload));
            });
            return deferred.promise;
        }

        function cancelRetryTimeout() {
            if (retryTimeout) {
                $timeout.cancel(retryTimeout);
            }

            retryTimeout = undefined;
        }

        function onMetricsUpdated(newMetricsData, timestamp) {
            coreConfigurationService.set(
                STORAGE_COLLECTION_KEY.DEVICE,
                STORAGE_KEY.METRICS,
                JSON.stringify(newMetricsData)
            );
            coreConfigurationService.set(
                STORAGE_COLLECTION_KEY.DEVICE,
                STORAGE_KEY.METRICS_UPDATED_AT,
                timestamp
            );
            resetRetryAttempt();
            // Schedule timer for next check
            checkMetrics();
        }

        function sendMetrics() {
            cancelRetryTimeout();
            attemptCount++;
            var tokens = getTokens();
            getMetricsData().then(function(metricsData) {
                var newMetricsData = $angular.copy(metricsData.new);
                var oldMetricsData = $angular.copy(metricsData.old);
                var payload = $angular.copy(newMetricsData);
                var timestamp = new Date().toISOString();
                payload.timestamp = timestamp;
                if (dataUtil.toHash(oldMetricsData) === dataUtil.toHash(newMetricsData)) {
                    logService.info(LOG_KEY, 'Device metrics have been sent before. Skipping...');
                    onMetricsUpdated(newMetricsData, timestamp);
                    return;
                }

                var stringifiedPayload = JSON.stringify(payload);
                logService.info(LOG_KEY, 'Sending device metrics: ' + stringifiedPayload);
                authenticationLibraryService.requestConstellationService(
                    getApiUrl(),
                    tokens,
                    {
                        method: 'POST',
                        contentType: 'application/json',
                        data: stringifiedPayload,
                    }
                ).then(function() {
                    logService.info(LOG_KEY, 'Device metrics sent successfully');
                    onMetricsUpdated(newMetricsData, timestamp);
                }, function(error) {
                    logService.error(LOG_KEY, 'Failed attempt ' + attemptCount + ' to send device metrics due to: ' + JSON.stringify(error));
                    if (isNetworkDisconnected) {
                        logService.warn(LOG_KEY, 'Skipping attempt to retry device metrics update due to network disruption');
                        return;
                    }

                    var backOffTime = dateTimeUtil.getBackoffTimeInSeconds(attemptCount, RETRY_CONFIG);
                    retryTimeout = $timeout(sendMetrics, dateTimeUtil.toMilliseconds(backOffTime));
                    logService.info(LOG_KEY, 'Retrying device metrics update in ' + dateTimeUtil.toTimeText(backOffTime) + '...');
                });
            });
        }

        function getTokens() {
            var tokens = deviceTokenService.getConstellationTokens();
            return (!tokens || !tokens.accessToken) ?
                undefined : tokens;
        }

        function onDeviceTokenEvent(event) {
            event = event || {};
            var eventType = (event.type || '').toLowerCase();
            switch (eventType) {
                case CONSTELLATION_TOKENS_EVENT.REFRESHED:
                case CONSTELLATION_TOKENS_EVENT.CLAIMED:
                    start();
                    break;
            }
        }

        function onNetworkConnectionEvent(event) {
            switch ((event || '').toLowerCase()) {
                case NETWORK_CONNECTION_STATE.CONNECTED:
                    if (isStarted && isNetworkDisconnected) {
                        // To ensure that metrics are sent (if needed) upon network recovery
                        logService.info(LOG_KEY, 'Checking metrics after recovering from network disruption');
                        resetRetryAttempt();
                        checkMetrics();
                        isNetworkDisconnected = false;
                        break;
                    }

                    // Attempt to start the service on network connected
                    start();
                    break;
                case NETWORK_CONNECTION_STATE.DISCONNECTED:
                    isNetworkDisconnected = true;
                    if (!retryTimeout) {
                        break;
                    }

                    resetRetryAttempt();
                    logService.warn(LOG_KEY, 'Cancelling device metrics update retry attempt due to network disruption');
                    break;
            }
        }

        function onInitializationFailed(reason) {
            var message = 'Device metrics service initialization failed due to: ';
            logService.warn(LOG_KEY, message + reason);
            isStarting = false;
        }

        function checkServiceSupport() {
            if (!dataUtil.isNullOrUndefined(isServiceSupported)) {
                // Device metrics service support has been determined before
                return $q.resolve();
            }

            if (embedProfileService.isPreviewMode()) {
                isServiceSupported = false;
                onInitializationFailed('Device metrics are not supported in preview mode');
                return $q.resolve();
            }

            deviceId = deviceRegistrationService.getPlayerId();
            if (!deviceId) {
                onInitializationFailed('Device metrics are not supported in unregistered state');
                stop();
                return $q.resolve();
            }

            var tokens = getTokens();
            if (!tokens) {
                onInitializationFailed('Missing device constellation tokens');
                return $q.resolve();
            }

            var deferred = $q.defer();
            versionUtil.isVersionSupported(SUPPORTED_SERVICE_VERSIONS, LOG_KEY).then(function(isVersionSupported) {
                isServiceSupported = isVersionSupported;
                if (!isVersionSupported) {
                    onInitializationFailed('Platform version does not meet the minimum device metrics service requirement');
                }

                deferred.resolve();
            });
            return deferred.promise;
        }

        function getLastUpdatedAt() {
            return coreConfigurationService.get(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.METRICS_UPDATED_AT);
        }

        function cancelCheckMetricTimer() {
            if (checkMetricsTimer) {
                $timeout.cancel(checkMetricsTimer);
            }

            checkMetricsTimer = undefined;
        }

        function startCheckMetricsTimer(nextCheckAt) {
            cancelCheckMetricTimer();
            var nextCheckInMs = nextCheckAt - new Date().getTime();
            checkMetricsTimer = $timeout(checkMetrics, nextCheckInMs);
            logService.debug(LOG_KEY, 'Metrics check scheduled in ' + dateTimeUtil.toTimeText(dateTimeUtil.toSeconds(nextCheckInMs)));
        }

        function getNextCheckTime(lastUpdatedAtTicks) {
            return dateTimeUtil.addDurationToTicks(lastUpdatedAtTicks, REPORT_METRICS_INTERVAL);
        }

        function checkMetrics() {
            cancelCheckMetricTimer();
            logService.debug(LOG_KEY, 'Performing metrics check');
            var lastUpdatedAt = getLastUpdatedAt();
            if (!lastUpdatedAt || !dateTimeUtil.isValidDate(lastUpdatedAt)) {
                // This is the first time that we are sending metrics
                sendMetrics();
                return;
            }

            var lastUpdatedAtDate = new Date(lastUpdatedAt);
            var nextCheckAt = getNextCheckTime(lastUpdatedAtDate.getTime());
            logService.info(LOG_KEY, 'Metrics last updated at ' + lastUpdatedAtDate.toString() + '.');
            if (dateTimeUtil.isNowOrPast(nextCheckAt)) {
                logService.info(LOG_KEY, 'Metrics update required: ' + REPORT_METRICS_INTERVAL + ' hours or more have passed since the last update');
                sendMetrics();
                return;
            }

            logService.info(LOG_KEY, 'Next metrics check at ' + new Date(nextCheckAt).toString());
            startCheckMetricsTimer(nextCheckAt);
        }

        function resetRetryAttempt() {
            attemptCount = 0;
            cancelRetryTimeout();
        }

        function start() {
            if (isStarting || isStarted) {
                return;
            }

            isStarting = true;
            deviceTokenService.subscribe(onDeviceTokenEvent);
            networkConnectionService.subscribe(onNetworkConnectionEvent);
            checkServiceSupport().then(function() {
                if (!isServiceSupported) {
                    logService.info(LOG_KEY, 'Device metrics service not started due to failure to meet the service requirements.');
                    return;
                }

                isStarted = true;
                isStarting = false;
                logService.info(LOG_KEY, 'Device metrics service started.');
                resetRetryAttempt();
                checkMetrics();
            });
        }

        function stop() {
            if (isStarted) {
                logService.info(LOG_KEY, 'Device metrics service stopped.');
            }

            cancelCheckMetricTimer();
            resetRetryAttempt();
            deviceTokenService.unsubscribe(onDeviceTokenEvent);
            networkConnectionService.unsubscribe(onNetworkConnectionEvent);
            isServiceSupported = null;
            deviceId = null;
            isStarted = false;
            isStarting = false;
            isNetworkDisconnected = false;
            coreConfigurationService.removeConfiguration(
                STORAGE_COLLECTION_KEY.DEVICE,
                STORAGE_KEY.METRICS
            );
            coreConfigurationService.removeConfiguration(
                STORAGE_COLLECTION_KEY.DEVICE,
                STORAGE_KEY.METRICS_UPDATED_AT
            );
        }

        return {
            start: start,
            stop: stop,
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var app = $angular.module('core');

    app.service('deviceTokenService', ["$q", "$injector", "CONSTELLATION_TOKENS_EVENT", "authenticationLibraryService", "coreConfigurationService", "embedProfileService", "deviceAPIService", "sessionService", "logService", "versionUtil", "jwtUtil", "dataUtil", "mediaUtil", function(
        $q,
        $injector,
        CONSTELLATION_TOKENS_EVENT,
        authenticationLibraryService,
        coreConfigurationService,
        embedProfileService,
        deviceAPIService,
        sessionService,
        logService,
        versionUtil,
        jwtUtil,
        dataUtil,
        mediaUtil
        ) {
        var COLLECTION_KEY = 'device';
        var JWT_STORAGE_KEY = 'jwt';
        var CONSTELLATION_TOKENS_STORAGE_KEY = 'constellationtokens';
        var CONTENT_COLLECTION_KEY = 'contenttokens';
        var CONTENT_CONSTELLATION_TOKENS_PREPEND_KEY = 'tokens.';
        var LOG_KEY = 'appspace.core.devicetoken';
        var CREATE_TOKEN_ENDPOINT = '/api/v3/authorization/token';
        var CREATE_CONTENT_TOKEN_SUBJECT_TYPE = 'Content';
        var CREATE_CONTENT_TOKEN_CONTEXT = 'device';
        var CREATE_CONTENT_TOKEN_GRANT_TYPE = 'createToken';
        var CONTENT_TOKENS_DELIMITER = '.';
        var CONTENT_TOKENS_DELIMITER_LENGTH = 3;
        var SUPPORTED_TOKENS_CLAIM_VERSIONS = {
            CLOUD: {
                MIN: '8.0.0-ac.1',
                MAX: '8.0.0'
            },
            GLOBAL: '8.0.0'
        };
        var NOT_FOUND_INDEX = -1;
        var REFRESH_TOKEN_KEY = 'refreshToken';
        var DEVICE_SUBJECT_TYPE = 'Device';
        var telemetryService;
        var processConstellationTokensPromise = $q.defer();
        var isProcessingConstellationTokens;
        var subscribers = [];
        var httpWrapper;
        var STATUS_CODE = {
            UNAUTHORIZED: 401
        };

        var TOKEN_RENEWAL = {
            BUFFER_MS: 3600000, // 1 hour before expiry
            CRON_JOB_ID: 'token-renewal',
            CRON_INTERVAL: '01:00:00' // 1 hour in HH:mm:ss format
        };
        var isTokenRenewalStarted = false;

        function isConstellationTokensProcessed() {
            return processConstellationTokensPromise.promise;
        }

        function isConstellationTokensClaimSupported() {
            return versionUtil.isVersionSupported(SUPPORTED_TOKENS_CLAIM_VERSIONS, LOG_KEY);
        }

        function save(token) {
            if (!token) {
                return;
            }

            var storageKey = JWT_STORAGE_KEY;
            var tokenToSave = $angular.copy(token);
            var currentToken;
            var shouldNotifySubscribers = false;
            if (dataUtil.isObject(token)) {
                shouldNotifySubscribers = true;
                currentToken = $angular.copy(getConstellationTokens());
                storageKey = CONSTELLATION_TOKENS_STORAGE_KEY;
                tokenToSave = JSON.stringify(token);
            }

            coreConfigurationService.set(COLLECTION_KEY, storageKey, tokenToSave);
            getRegistrationUtil().persistRegistrationInfo();
            if (shouldNotifySubscribers) {
                notifySubscribers(currentToken ?
                    CONSTELLATION_TOKENS_EVENT.REFRESHED :
                    CONSTELLATION_TOKENS_EVENT.CLAIMED, token);
            }

            processConstellationTokensPromise.resolve();
        }

        function clear() {
            coreConfigurationService.removeConfiguration(COLLECTION_KEY, JWT_STORAGE_KEY);
            coreConfigurationService.removeConfiguration(COLLECTION_KEY, CONSTELLATION_TOKENS_STORAGE_KEY);
            clearContentTokens();
            processConstellationTokensPromise = $q.defer();
        }

        function decodeRegistrationInfo(response) {
            var jwt = jwtUtil.decode(response);
            return (jwt || {}).metadata;
        }

        function getRegistrationInfo() {
            // Retrieve device JWT from local storage, decode it, and then return its metadata
            return decodeRegistrationInfo(coreConfigurationService.get(COLLECTION_KEY, JWT_STORAGE_KEY));
        }

        function areConstellationTokensValid(tokens) {
            return tokens && tokens.accessToken && tokens.refreshToken && tokens.idToken;
        }

        function validateConstellationTokens(tokens) {
            tokens = authenticationLibraryService.parseConstellationTokens(tokens);
            return areConstellationTokensValid(tokens) ? tokens : undefined;
        }

        function processQueryStringConstellationTokens(isTokensInjectedSupported) {
            if (!isTokensInjectedSupported) {
                logService.info(LOG_KEY, 'Skipping device constellation tokens injection: Tokens injection is not supported in this mode');
                processConstellationTokensPromise.resolve();
                return;
            }

            var queryStringTokens = validateConstellationTokens(sessionService.getUrlParameters());
            if (queryStringTokens) {
                // Message is set to "info" log level so that it's accessible in preview mode
                logService.info(LOG_KEY, 'Injected device constellation tokens have been processed successfully');
                save(queryStringTokens);
                processConstellationTokensPromise.resolve();
                return;
            }

            if (embedProfileService.isChannelPreviewMode()) {
                logService.info(LOG_KEY, 'Tokens are not injected in channel preview mode');
                processConstellationTokensPromise.resolve();
                return;
            }

            var errorMessage = 'Failed to process injected device constellation tokens: Incomplete tokens';
            logService.error(LOG_KEY, errorMessage);
            processConstellationTokensPromise.reject(errorMessage);
        }

        function getTelemetryService() {
            if (!telemetryService) {
                telemetryService = $injector.get('telemetryService');
            }

            return telemetryService;
        }

        function processConstellationTokens(identity) {
            logService.debug(LOG_KEY, 'Processing device constellation tokens. ' + JSON.stringify(identity));
            if (isProcessingConstellationTokens) {
                logService.debug(LOG_KEY, 'Device constellation tokens are being processed. Skipping current attempt');
                return;
            }

            isProcessingConstellationTokens = true;
            var logError = function(message, isSkipped) {
                isProcessingConstellationTokens = false;
                var errorMessage = 'Failed to claim device constellation tokens: ';
                var isAttemptSkipped = (message || {}).isAttemptSkipped;
                var logLevel = isSkipped || isAttemptSkipped ? 'debug' : 'error';
                if (isSkipped) {
                    errorMessage = 'Skipping device constellation tokens claim: ';
                } else if (isAttemptSkipped) {
                    message = message.error;
                    errorMessage = 'Skipped attempt to claim device constellation tokens: ';
                }

                errorMessage += message;
                logService[logLevel](LOG_KEY, errorMessage);
                if (isAttemptSkipped) {
                    // Don't resolve or reject promise if claim was not attempted; wait for the next valid attempt
                    return;
                }

                if (isSkipped) {
                    processConstellationTokensPromise.resolve();
                    return;
                }

                processConstellationTokensPromise.reject(errorMessage);
            };

            coreConfigurationService.isReady().then(function() {
                logService.debug(LOG_KEY, 'Core configuration service is ready to process device constellation tokens');
                var deviceTokens = getConstellationTokens();
                var deviceIdentity = identity || {};
                var deviceId = (deviceIdentity.appspace || {}).deviceGuid;
                if (deviceTokens) {
                    var tokenValidationResult = isTokenIdentityValid(deviceTokens, deviceId, DEVICE_SUBJECT_TYPE);
                    if (tokenValidationResult.isValid) {
                        logError('Tokens have already been claimed before', true);
                        return;
                    }

                    var stringifiedTokenIdentity = JSON.stringify(tokenValidationResult.tokenIdentity);
                    getTelemetryService().addToQueue({
                        module: 'device-authorization',
                        type: 'jwt-identity-mismatch',
                        description: 'JWT identity mismatch',
                        metadata: {
                            'token_identity': stringifiedTokenIdentity,
                        },
                    });
                    logService.error(LOG_KEY, 'Token identity mismatch: ' + stringifiedTokenIdentity + '. Clearing tokens to re-claim shortly...');
                    clear();
                }

                isConstellationTokensClaimSupported().then(function(isSupported) {
                    if (!isSupported) {
                        logError('Incompatible with required Appspace server version', true);
                        isProcessingConstellationTokens = false;
                        return;
                    }

                    var profile = embedProfileService.getEmbeddedProfileConfig();
                    if (profile) {
                        processQueryStringConstellationTokens(profile.supportsTokensInjection);
                        isProcessingConstellationTokens = false;
                        return;
                    }

                    logService.debug(LOG_KEY, 'Claiming device constellation tokens');
                    deviceAPIService.claimConstellationTokens(identity).then(function(response) {
                        var tokens = validateConstellationTokens(response);
                        if (!tokens) {
                            logError('Incomplete tokens');
                            isProcessingConstellationTokens = false;
                            return;
                        }

                        logService.debug(LOG_KEY, 'Device constellation tokens have been claimed successfully');
                        save(tokens);
                        isProcessingConstellationTokens = false;
                        processConstellationTokensPromise.resolve();
                    }, function(error, isAttemptSkipped) {
                        logError(error, false, isAttemptSkipped);
                    });
                });
            });
        }

        function getStoredToken(tokenKey) {
            return coreConfigurationService.get(COLLECTION_KEY, tokenKey);
        }

        function getStoredContentToken(contentId, contentNumericId) {
            var tokens = coreConfigurationService.get(CONTENT_COLLECTION_KEY, getContentTokenKey(contentId, contentNumericId));
            return parseStoredTokens(tokens);
        }

        function getContentTokenKey(contentId, contentNumericId) {
            return dataUtil.isNullOrUndefined(contentId) || dataUtil.isNullOrUndefined(contentNumericId) ?
                undefined : CONTENT_CONSTELLATION_TOKENS_PREPEND_KEY + contentId + '.' + contentNumericId;
        }

        function getToken() {
            return getStoredToken(JWT_STORAGE_KEY);
        }

        function refreshConstellationTokens() {
            return authenticationLibraryService.getUpdatedConstellationTokens(getConstellationTokens());
        }

        function getConstellationTokens() {
            var constellationTokens = getStoredToken(CONSTELLATION_TOKENS_STORAGE_KEY);
            return parseStoredTokens(constellationTokens);
        }

        function parseStoredTokens(tokens) {
            return tokens ? JSON.parse(tokens) : tokens;
        }

        function isTokenIdentityValid(tokens, subjectId, subjectType) {
            var result = {
                isValid: false,
            };
            var idToken = tokens.idToken;
            if (!subjectId || !subjectType) {
                // Token identity is deemed valid since there is no subjectId or subjectType to match against
                result.isValid = true;
            } else if (idToken) {
                var decodedIdToken = jwtUtil.decode(idToken);

                if (decodedIdToken) {
                    result.tokenIdentity = decodedIdToken;
                    result.isValid = Boolean(decodedIdToken.subjectId === subjectId &&
                        decodedIdToken.subjectType &&
                        decodedIdToken.subjectType.toLowerCase() === subjectType.toLowerCase());
                }
            }

            return result;
        }

        function requestConstellationTokens(subjectId, subjectType, context) {
            var promise = $q.defer();
            var deviceTokens = getConstellationTokens();
            if (!deviceTokens && !embedProfileService.isChannelPreviewMode()) {
                return $q.reject('No device constellation tokens.');
            }

            var data = {
                subjectType: subjectType,
                subjectId: subjectId,
                grantType: CREATE_CONTENT_TOKEN_GRANT_TYPE,
                context: context
            };
            subjectType = subjectType.toLowerCase();

            authenticationLibraryService.requestConstellationService(CREATE_TOKEN_ENDPOINT, deviceTokens, {
                method: 'POST',
                data: data,
            }).then(function(result) {
                if (!result) {
                    promise.reject('Invalid result when retrieving ' + subjectType + ' constellation tokens:' + JSON.stringify(result));
                    return;
                }

                var tokens = authenticationLibraryService.parseConstellationTokens(result);
                if (areConstellationTokensValid(tokens)) {
                    promise.resolve(tokens);
                    return;
                }

                promise.reject('Invalid response when retrieving ' + subjectType + ' constellation tokens:' + JSON.stringify(result));
            }, function(error) {
                promise.reject('Failed to retrieve ' + subjectType + ' constellation tokens:' + JSON.stringify(error));
            });
            return promise.promise;
        }

        function getContentConstellationTokens(contentId, contentNumericId) {
            var storedTokens = getStoredContentToken(contentId, contentNumericId);
            if (storedTokens) {
                return $q.resolve(storedTokens);
            }

            var promise = $q.defer();
            isConstellationTokensProcessed().then(function() {
                requestConstellationTokens(contentId, CREATE_CONTENT_TOKEN_SUBJECT_TYPE, CREATE_CONTENT_TOKEN_CONTEXT).then(function(tokens) {
                    saveContentConstellationTokens(contentId, contentNumericId, tokens);
                    promise.resolve(tokens);
                }, promise.reject);
            }, promise.reject);
            return promise.promise;
        }

        function saveContentConstellationTokens(contentId, contentNumericId, tokens) {
            var contentKey = getContentTokenKey(contentId, contentNumericId);
            coreConfigurationService.set(CONTENT_COLLECTION_KEY, contentKey, JSON.stringify(tokens));
        }

        function clearContentTokensFromManifest(files) {
            var collections = coreConfigurationService.getConfigurations(CONTENT_COLLECTION_KEY);
            if (!collections || !collections.length) {
                return;
            }

            files = files || [];
            // loop through all of our content tokens and find which 1 that we don't use anymore
            for (var i = 0; i < collections.length; i++) {
                var collection = collections[i];
                var key = collection.key;
                var segments = key.split(CONTENT_TOKENS_DELIMITER);
                if (segments.length !== CONTENT_TOKENS_DELIMITER_LENGTH) {
                    continue;
                }

                var collectionNumericId = segments[2];
                if (isContentNumericIdExistsInManifest(collectionNumericId, files)) {
                    continue;
                }

                coreConfigurationService.removeConfiguration(CONTENT_COLLECTION_KEY, key);
            }
        }

        function isContentNumericIdExistsInManifest(contentId, files) {
            for (var i = 0; i < files.length; i++) {
                var file = files[i];
                if (!file || !file.url) {
                    continue;
                }

                var manifestContentId = mediaUtil.getMediaIdFromUrl(file.url);
                if (!manifestContentId) {
                    continue;
                }

                if (contentId === manifestContentId) {
                    return true;
                }
            }

            return false;
        }

        function clearContentTokens() {
            coreConfigurationService.removeCollection(CONTENT_COLLECTION_KEY);
        }

        function notifySubscribers(event, tokens) {
            var eventData = {
                type: event,
                tokens: tokens
            };
            for (var i = 0; i < subscribers.length; i++) {
                subscribers[i](eventData);
            }
        }

        function subscribe(callback) {
            if (!callback || subscribers.indexOf(callback) > NOT_FOUND_INDEX) {
                return;
            }

            subscribers.push(callback);
        }

        function unsubscribe(callback) {
            var index = subscribers.indexOf(callback);
            if (index === NOT_FOUND_INDEX) {
                return;
            }

            subscribers.splice(index, 1);
        }

        function getRegistrationUtil() {
            return $injector.get('RegistrationUtil');
        }

        function getHttpWrapper() {
            if (!httpWrapper) {
                var HttpWrapper = $injector.get('HttpWrapper');
                httpWrapper = new HttpWrapper();
            }

            return httpWrapper;
        }

        function refreshConstellationTokensByRefreshToken(config) {
            var subjectType = 'Device';
            var failToRefreshMessage = 'Device constellation tokens failed to refresh: ';
            var invalidConfigurationMessage = failToRefreshMessage + 'Invalid configuration: ';
            config = config || {};
            var baseUrl = config.apiUrl || coreConfigurationService.getServiceApiUrl();
            if (!config.registrationToken || !config.deviceGuid || !baseUrl) {
                logService.error(LOG_KEY, invalidConfigurationMessage + JSON.stringify(config));
                return $q.reject(invalidConfigurationMessage + JSON.stringify(config));
            }

            var defer = $q.defer();
            var handleRefreshFailure = function(message, error) {
                var defaultError = error;

                if (!dataUtil.isObject(defaultError)) {
                    defaultError = {};
                    defaultError.response = error;
                }

                if (!defaultError.status && !dataUtil.isNumber(defaultError.status)) {
                    defaultError.status = STATUS_CODE.UNAUTHORIZED;
                }

                defaultError.isServerError = getHttpWrapper().isServerError(defaultError.status);
                defaultError.isUnauthorized = getHttpWrapper().isUnauthorized(defaultError.status);
                defaultError.isForbidden = getHttpWrapper().isForbidden(defaultError.status);

                logService.error(LOG_KEY, failToRefreshMessage + message, JSON.stringify(error || defaultError));

                defer.reject({
                    originalError: defaultError,
                    errorMessage: failToRefreshMessage + message,
                });
            };

            var configuration = {
                subjectType: subjectType,
                subjectId: config.deviceGuid,
                grantType: REFRESH_TOKEN_KEY,
                refreshToken: config.registrationToken,
                baseUrl: baseUrl,
            };

            authenticationLibraryService.createTokenByRefreshToken(configuration).then(function(result) {
                if (!result) {
                    handleRefreshFailure('Invalid result when create constellation tokens: ' + JSON.stringify(result));
                    return;
                }

                var tokens = authenticationLibraryService.parseConstellationTokens(result);
                if (!areConstellationTokensValid(tokens)) {
                    handleRefreshFailure('Invalid tokens when create constellation tokens: ' + JSON.stringify(result));
                    return;
                }

                save(tokens);
                logService.debug(LOG_KEY, 'Device constellation tokens have been created successfully.');
                defer.resolve(tokens);
            }, function(error) {
                if (error.xhr) {
                    error.status = error.xhr.status;
                }

                handleRefreshFailure('Failed to retrieve ' + subjectType + ' constellation tokens. ', error);
            });

            return defer.promise;
        }

        /**
         * Check if token needs renewal (expires within 1 hour)
         * @return {boolean} - True if token should be renewed
         */
        function shouldRenewToken() {
            var tokens = getConstellationTokens();
            if (!areConstellationTokensValid(tokens)) {
                logService.debug(LOG_KEY, 'No constellation tokens found, skipping renewal check');
                return false;
            }

            var expiryTime = jwtUtil.getExpiry(tokens.accessToken);
            if (!expiryTime) {
                logService.debug(LOG_KEY, 'Could not determine token expiry, skipping renewal check');
                return false;
            }

            var now = Date.now();
            var timeUntilExpiry = expiryTime - now;
            var shouldRenew = timeUntilExpiry <= TOKEN_RENEWAL.BUFFER_MS;

            var minutesUntilExpiry = Math.round(timeUntilExpiry / 60000);
            logService.debug(LOG_KEY, 'Token expires in ' + minutesUntilExpiry + ' minutes, ' + (shouldRenew ? 'renewal needed' : 'no renewal needed'));

            return shouldRenew;
        }

        /**
         * Attempt to renew the token
         * @return {Promise} - Promise that resolves with renewed tokens
         */
        function renewToken() {
            var deferred = $q.defer();
            logService.info(LOG_KEY, 'Attempting to renew constellation tokens');
            refreshConstellationTokens().then(function(tokens) {
                if (!tokens || !tokens.accessToken) {
                    logService.warn(LOG_KEY, 'Invalid tokens returned.');
                    deferred.resolve(tokens);
                    return;
                }

                // Save the renewed tokens to ensure they're persisted and subscribers are notified
                save(tokens);
                logService.info(LOG_KEY, 'Constellation tokens renewed and saved successfully');

                deferred.resolve(tokens);
            }, function(error) {
                logService.error(LOG_KEY, 'Failed to renew constellation tokens: ' + JSON.stringify(error));
                deferred.reject(error);
            });
            return deferred.promise;
        }

        /**
         * Hourly token check: if tokens exist, validate and refresh if needed; otherwise do nothing.
         * Claiming tokens is handled by the existing registration/claim workflow.
         */
        function checkAndRenewToken() {
            if (!isTokenRenewalStarted || !shouldRenewToken()) {
                return;
            }

            renewToken();
        }

        /**
         * Start the token renewal service
         * Performs an immediate check, then sets up hourly checks via cronService
         */
        function startTokenRenewal() {
            if (isTokenRenewalStarted) {
                logService.debug(LOG_KEY, 'Token renewal service already started');
                return;
            }

            if (embedProfileService.isPreviewMode()) {
                logService.debug(LOG_KEY, 'Token renewal service is not supported in preview mode');
                return;
            }

            isTokenRenewalStarted = true;
            logService.info(LOG_KEY, 'Token renewal service started');

            // Perform immediate check on first run
            checkAndRenewToken();

            // Add cron job for hourly checks (resolve lazily to avoid circular dependency with cronService)
            // Note: cronService.addCronJob works even if cronService hasn't started yet
            // The job will be picked up when cronService starts or on the next check cycle
            try {
                var cronService = $injector.get('cronService');
                cronService.addCronJob({
                    id: TOKEN_RENEWAL.CRON_JOB_ID,
                    interval: TOKEN_RENEWAL.CRON_INTERVAL,
                    callback: checkAndRenewToken
                });
                logService.info(LOG_KEY, 'Token renewal check scheduled to run every ' + TOKEN_RENEWAL.CRON_INTERVAL + ' via cron service');
            } catch (error) {
                logService.error(LOG_KEY, 'Failed to add token renewal cron job: ' + JSON.stringify(error));
                isTokenRenewalStarted = false;
            }
        }

        /**
         * Stop the token renewal service
         */
        function stopTokenRenewal() {
            if (!isTokenRenewalStarted) {
                logService.debug(LOG_KEY, 'Token renewal service already stopped');
                return;
            }

            // Remove cron job (resolve lazily to avoid circular dependency with cronService)
            try {
                var cronService = $injector.get('cronService');
                cronService.removeCronJob(TOKEN_RENEWAL.CRON_JOB_ID);
            } catch (error) {
                logService.error(LOG_KEY, 'Failed to remove token renewal cron job: ' + JSON.stringify(error));
            }

            isTokenRenewalStarted = false;
            logService.info(LOG_KEY, 'Token renewal service stopped');
        }

        /**
         * Start the device token service
         */
        function start() {
            isConstellationTokensProcessed().then(function() {
                logService.debug(LOG_KEY, 'Core configuration service is ready, starting token renewal');
                startTokenRenewal();
            });
        }

        /**
         * Stop the device token service
         */
        function stop() {
            stopTokenRenewal();
        }

        return {
            save: save,
            clear: clear,
            subscribe: subscribe,
            unsubscribe: unsubscribe,
            getRegistrationInfo: getRegistrationInfo,
            decodeRegistrationInfo: decodeRegistrationInfo,
            processConstellationTokens: processConstellationTokens,
            getToken: getToken,
            getConstellationTokens: getConstellationTokens,
            getContentConstellationTokens: getContentConstellationTokens,
            saveContentConstellationTokens: saveContentConstellationTokens,
            clearContentTokensFromManifest: clearContentTokensFromManifest,
            clearContentTokens: clearContentTokens,
            requestConstellationTokens: requestConstellationTokens,
            refreshConstellationTokens: refreshConstellationTokens,
            refreshConstellationTokensByRefreshToken: refreshConstellationTokensByRefreshToken,
            isConstellationTokensProcessed: isConstellationTokensProcessed,
            start: start,
            stop: stop,
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('core');

    app.service('embedProfileService', ["$injector", "dataUtil", "ROUTE", "UrlUtil", function(
        $injector,
        dataUtil,
        ROUTE,
        UrlUtil) {
        var PREVIEW_MODE = 'preview';
        var WEB_PUBLISHING_MODE = 'public';
        var QUERY_STRING_KEYS = {
            CAPABILITIES: 'capabilities',
            NO_CONTENT_MESSAGE: 'nocontentmessage',
            CONTEXT: 'context'
        };
        var CAPABILITIES = {
            CONTENT_NAVIGATION: 'contentnavigation',
            NO_CONTENT_MESSAGE: 'nocontentmessage',
            RESPONSIVE_DISPLAY: 'responsivedisplay'
        };
        var CAPABILITIES_SEPARATOR = ',';
        var profileMapping = {
            editor: {
                redirectUrl: ROUTE.PREVIEW_SIGN_CHANNEL,
                skipSplash: true,
                preview: true,
                editor: true,
                supportsTokensInjection: true
            },
            public: {
                redirectUrl: ROUTE.PREVIEW_SIGN_CHANNEL,
                requiredParams: ['timestampurl', 'scripturl', 'appid', 'appspaceurl'],
                preview: true,
                supportsTokensInjection: true
            },
            preview: {
                redirectUrl: ROUTE.PREVIEW_SIGN_CHANNEL,
                requiredParams: ['appspaceurl'],
                optionalParams: ['appid', 'deviceguid'],
                skipSplash: true,
                preview: true,
                supportsTokensInjection: true
            },
            ondemand: {
                disableNonCriticalServices: true
            },
        };
        var currentUrlParameters = {};

        function getSessionUrlParameters() {
            return $injector.get('sessionService').getUrlParameters();
        }

        function getUrlParameter(key) {
            return currentUrlParameters[key] || getSessionUrlParameters()[key];
        }

        function isParamsValid(embedProfile, urlParameters) {
            var requiredParams = embedProfile.requiredParams;
            var optionalParams = embedProfile.optionalParams;
            return (isParamsEmpty(requiredParams) || isRequiredParamsValid(requiredParams, urlParameters))
                && (isParamsEmpty(optionalParams) || isOptionalParamsValid(optionalParams, urlParameters));
        }

        function isParamsEmpty(params) {
            return dataUtil.isNullOrUndefined(params) || !params.length;
        }

        function isRequiredParamsValid(requiredParams, urlParameters) {
            for (var i = 0; i < requiredParams.length; i++) {
                if (dataUtil.isNullOrUndefined(urlParameters[requiredParams[i]])) {
                    return false;
                }
            }

            return true;
        }

        function isOptionalParamsValid(optionalParams, urlParameters) {
            for (var i = 0; i < optionalParams.length; i++) {
                if (!dataUtil.isNullOrUndefined(urlParameters[optionalParams[i]])) {
                    return true;
                }
            }

            return false;
        }

        function getEmbeddedProfileConfig() {
            return profileMapping[isPreviewMode() ? PREVIEW_MODE : getProfile()];
        }

        function getEmbedProfile(urlParameters) {
            return profileMapping[urlParameters['embedprofile']] || {};
        }

        function getRedirectUrl() {
            var embedProfile = getEmbedProfile(currentUrlParameters);
            if (!embedProfile.redirectUrl) {
                return;
            }

            if (!isParamsValid(embedProfile, currentUrlParameters)) {
                return ROUTE.INVALID_INJECTION;
            }

            return embedProfile.redirectUrl;
        }

        function shouldSkipSplash() {
            return getEmbedProfile(currentUrlParameters).skipSplash || dataUtil.isValueTrue(getUrlParameter('skipsplash'));
        }

        function shouldDisableNonCriticalServices() {
            var urlParameters = getSessionUrlParameters();
            return getEmbedProfile(urlParameters).disableNonCriticalServices || false;
        }

        function isPreviewMode() {
            return getEmbedProfile(currentUrlParameters).preview || currentUrlParameters[PREVIEW_MODE] == 'true';
        }

        function isChannelPreviewMode() {
            return isPreviewMode() && currentUrlParameters.appid;
        }

        function isEditorMode() {
            return getEmbedProfile(currentUrlParameters).editor;
        }

        function isWebPublishingMode() {
            return getProfile() === WEB_PUBLISHING_MODE;
        }

        function getProfile() {
            return currentUrlParameters['embedprofile'];
        }

        function isValid() {
            return isParamsValid(getEmbedProfile(currentUrlParameters), currentUrlParameters);
        }

        function init() {
            currentUrlParameters = UrlUtil.retrieveParams() || {};
        }

        function getBackgroundColor() {
            // value has been decodeURI in UrlUtil
            return getUrlParameter('embed-bgcolor');
        }

        function getCapabilities() {
            var capabilitiesString = getUrlParameter(QUERY_STRING_KEYS.CAPABILITIES) || '';
            return capabilitiesString.toLowerCase().trim().split(CAPABILITIES_SEPARATOR);
        }

        function isContentNavigationEnabled() {
            return isCapabilityEnabled(CAPABILITIES.CONTENT_NAVIGATION);
        }

        function isNoContentMessageEnabled() {
            return isCapabilityEnabled(CAPABILITIES.NO_CONTENT_MESSAGE);
        }

        function isResponsiveDisplay() {
            return isCapabilityEnabled(CAPABILITIES.RESPONSIVE_DISPLAY);
        }

        function getNoContentMessage() {
            return getUrlParameter(QUERY_STRING_KEYS.NO_CONTENT_MESSAGE) || '';
        }

        function isCapabilityEnabled(capability) {
            var capabilities = getCapabilities();
            return capabilities.indexOf(capability) >= 0;
        }

        function getContext() {
            return getUrlParameter(QUERY_STRING_KEYS.CONTEXT);
        }

        init();

        this.getRedirectUrl = getRedirectUrl;
        this.shouldSkipSplash = shouldSkipSplash;
        this.shouldDisableNonCriticalServices = shouldDisableNonCriticalServices;
        this.isPreviewMode = isPreviewMode;
        this.isChannelPreviewMode = isChannelPreviewMode;
        this.isEditorMode = isEditorMode;
        this.isWebPublishingMode = isWebPublishingMode;
        this.getProfile = getProfile;
        this.getEmbeddedProfileConfig = getEmbeddedProfileConfig;
        this.isValid = isValid;
        this.getBackgroundColor = getBackgroundColor;
        this.isContentNavigationEnabled = isContentNavigationEnabled;
        this.isNoContentMessageEnabled = isNoContentMessageEnabled;
        this.isResponsiveDisplay = isResponsiveDisplay;
        this.getNoContentMessage = getNoContentMessage;
        this.getSessionUrlParameters = getSessionUrlParameters;
        this.getContext = getContext;
        this.getUrlParameter = getUrlParameter;
    }]);
})(window.angular);

(function ($angular) {

    "use strict";

    var app = $angular.module('core');

    app.service("coreFontManagerService", ["$q", "$interval", "$timeout", "logService", "coreConfigurationService", "networkContentRouterService", "storageLocalStorageService", function ($q, $interval, $timeout, logService, coreConfigurationService,
        networkContentRouterService, storageLocalStorageService) {


        var storage = storageLocalStorageService;
        var fontLookupCache = {};

        //Public interface
        return {
            isFontAvailable: isFontAvailable,
            addFont: addFont,
            removeFont: removeFont,
            getFonts: getFonts,
            init: init
        };

        function init() {
            //removeFont("BillionStars_PersonalUse");
            logService.debug("appspace.core", "FontManager Service initialized");
            loadFonts();
            //addFont({family:"BillionStars_PersonalUse", url: "assets/fonts/BillionStars_PersonalUse.ttf"});
        }

        //Add font to manager
        function addFont(font) {
            if (findInstalledFont(font.family)) {
                logService.debug("appspace.core", "Font already added" + font.family + " available? " + isFontAvailable(font.family));
                return true;
            }
            //Add font to collection
            storage.set('core.fontmanager.fonts', font.family.toLowerCase(), JSON.stringify(font));
            //Attempt to load font
            loadFont(font);
        }

        //remove font
        function removeFont(family) {
            family = family.toLowerCase();
            var fontKeys = storage.getKeys('core.fontmanager.fonts', true);
            for (var i = 0; i < fontKeys.length; i++) {
                if (fontKeys[i].key == family) {
                    //Remove the entry
                    storage.removeKey('core.fontmanager.fonts', fontKeys[i].key);
                    return true;
                }
            }
            return false;
        }

        //Check if this is an installed font
        function findInstalledFont(family) {
            family = family.toLowerCase();
            var fonts = getFonts();
            for (var i = 0; i < fonts.length; i++) {
                var font = fonts[i];
                if (font.family.toLowerCase() == family) {
                    return font;
                }
            }
            return null;
        }

        //get all fonts installed in the manager
        function getFonts() {
            var fonts = [];
            var fontKeys = storage.getKeys('core.fontmanager.fonts', true);
            for (var i = 0; i < fontKeys.length; i++) {
                var font = JSON.parse(fontKeys[i].value);
                fonts.push(font);
            }
            return fonts;
        }

        //Goes through installed fonts and loads if not available
        function loadFonts() {
            var fontCollection = getFonts();
            for (var i = 0; i < fontCollection.length; i++) {
                var font = fontCollection[i];
                if (!isFontAvailable(font.family)) {
                    loadFont(font);

                    //Set timer to confirm font loaded
                    $timeout(function () {
                        //console.log("Font " + font.family + " available?:" + isFontAvailable(font.family));
                        logService.debug("appspace.core", "Font " + font.family + " available? " + isFontAvailable(font.family));
                    }, 2000);
                }
            }
            return;
        }

        function loadFont(font) {
            var defered = $q.defer();
            if (isFontAvailable(font.family)) {
                logService.debug("appspace.core", "Font family already installed: " + font.family);
                defered.resolve();
                return defered.promise;
            }
            //Look for where font is located
            var p = networkContentRouterService.resolve(font.url, "font-routing-service");
            p.then(function (url) {
                //Create a sheet dynamicaly to load font
                var sheet = createSheet();
                var rule = "@font-face {font-family:'" + font.family + "'; src:url('" + url + "')}";
                sheet.insertRule(rule, 0);
                logService.info("appspace.core", "Font family loaded: " + font.family);
                fontLookupCache[font.family] = true;
                defered.resolve();
            }, defered.reject);
            return defered.promise;
        }

        //Create a style sheet
        function createSheet() {
            var style = document.createElement("style");
            // style.setAttribute("", "")
            style.appendChild(document.createTextNode(""));
            document.head.appendChild(style);
            return style.sheet;
        }

        //Check font exist by looking for size differences
        function isFontAvailable(fontName) {
            if (fontLookupCache[fontName]) {
                return true;
            }
            var sampleText = "abcdefghijklmnopqrstuvwxyz0123456789";
            var canvas = document.createElement("canvas");
            var context = canvas.getContext("2d");
            //Set default font to monospace so we can determine base size
            context.font = "72px monospace";
            var baseSize = context.measureText(sampleText).width;

            //Now check for our font, and fallback to monospace if it doesnt exist
            context.font = "72px '" + fontName + "', monospace";
            var fontSize = context.measureText(sampleText).width;
            //If they are the same then the font doesnt exist
            if (fontSize != baseSize) {
                fontLookupCache[fontName] = true;
                return true;
            } else {
                return false;
            }
        }
    }])
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.service('coreFrameworkService', ["$q", "$http", "$injector", "$interval", "halDriverService", "halPlatformService", "APPSPACE_CONFIGURATION_PROPERTY", function($q, $http, $injector, $interval,
        halDriverService, halPlatformService, APPSPACE_CONFIGURATION_PROPERTY
    ) {
        var alertsService;
        var admAPIService;
        var authenticationAPIService;
        var deviceAPIService;
        var signsAPIService;
        var coreConfigurationService;
        var coreFrameworkService;
        var logService;
        var logManager;
        var deviceHealthService;
        var deviceRegistrationService;
        var pathService;
        var halMediaService;
        var halNetworkService;
        var halRemoteService;
        var admService;
        var jobSchedulerService;
        var cdnService;
        var networkContentCacheService;
        var storageFileService;
        var storageLocalStorageService;
        var coreFontManagerService;
        var deviceStateService;
        var processEvalInterval;
        var policyUrl;
        var discoveryUrl;
        var qrClaimCodeUrl;
        var selfUpdateUrl;
        var initPromise;

        function init() {
            if (initPromise) {
                return initPromise.promise;
            }

            initPromise = $q.defer();
            alertsService = $injector.get('alertsService');
            admAPIService = $injector.get('admAPIService');
            authenticationAPIService = $injector.get('authenticationAPIService');
            deviceAPIService = $injector.get('deviceAPIService');
            signsAPIService = $injector.get('signsAPIService');
            coreConfigurationService = $injector.get('coreConfigurationService');
            coreFrameworkService = $injector.get('coreFrameworkService');
            logService = $injector.get('logService');
            logManager = $injector.get('logManager');
            deviceHealthService = $injector.get('deviceHealthService');
            deviceRegistrationService = $injector.get('deviceRegistrationService');
            pathService = $injector.get('pathService');
            halMediaService = $injector.get('halMediaService');
            halNetworkService = $injector.get('halNetworkService');
            halRemoteService = $injector.get('halRemoteService');
            admService = $injector.get('admService');
            jobSchedulerService = $injector.get('jobSchedulerService');
            cdnService = $injector.get('cdnService');
            storageFileService = $injector.get('storageFileService');
            storageLocalStorageService = $injector.get('storageLocalStorageService');
            coreFontManagerService = $injector.get('coreFontManagerService');
            networkContentCacheService = $injector.get('networkContentCacheService');
            deviceStateService = $injector.get('deviceStateService');
            var setVersions = function(appVersion, buildVersion) {
                coreConfigurationService.set('appspace', 'appVersion', appVersion);
                coreConfigurationService.set('appspace', 'buildVersion', buildVersion);
                return $q.resolve();
            };

            storageLocalStorageService.isReady().then(function() {
                var sessionId = deviceStateService.getSessionId();
                // retrieve config.json
                $http({
                    method: 'GET',
                    url: '_version.json?sessionId=' + sessionId
                }).then(function(config) {
                    var data = config.data || {};
                    setNetworkPolicyUrl(data['policyUrl']);
                    setDiscoveryUrl(data['discoveryUrl']);
                    setSelfUpdateUrl(data['selfUpdateUrl']);
                    qrClaimCodeUrl = data['qrCodeClaimUrl'];
                    setVersions(data['appVersion'], data['buildVersion']).then(initPromise.resolve);
                }, function() {
                    // if unable to retrive version.json, set the version to default value
                    setVersions('-', '-').then(initPromise.resolve);
                });
            });

            $interval.cancel(processEvalInterval);
            // run process eval script
            processEvalInterval = $interval(processEval, 1000, null, false);

            return initPromise.promise;
        }

        function setNetworkPolicyUrl(versionJsonValue) {
            policyUrl = getConfigurationValue(APPSPACE_CONFIGURATION_PROPERTY.NETWORKPOLICYURL, versionJsonValue);
        }

        function setDiscoveryUrl(versionJsonValue) {
            discoveryUrl = getConfigurationValue(APPSPACE_CONFIGURATION_PROPERTY.DISCOVERYURL, versionJsonValue);
        }

        function setSelfUpdateUrl(versionJsonValue) {
            selfUpdateUrl = getConfigurationValue(APPSPACE_CONFIGURATION_PROPERTY.SELFUPDATEURL, versionJsonValue);
        }

        function isReady() {
            var defer = $q.defer();

            halDriverService.isReady().then(function() {
                init().then(function() {
                    storageLocalStorageService.isReady().then(defer.resolve);
                });
            }, defer.reject);

            return defer.promise;
        }

        function getAppVersion() {
            return coreConfigurationService.get('appspace', 'appVersion');
        }

        function getBuildVersion() {
            return coreConfigurationService.get('appspace', 'buildVersion');
        }

        function getConfigurationValue(configurationProperty, versionJsonValue) {
            return getAppspaceConfiguration()[configurationProperty] || versionJsonValue;
        }

        function getAppspaceConfiguration() {
            return coreConfigurationService.getAppspaceConfiguration() || {};
        }

        function getPolicyUrl() {
            return policyUrl;
        }

        function getDiscoveryUrl() {
            return discoveryUrl;
        }

        function getQrClaimCodeUrl() {
            return qrClaimCodeUrl;
        }

        function getServices() {
            return {
                alerts: {
                    alertsService: alertsService
                },
                appspaceAPI: {
                    admAPIService: admAPIService,
                    authenticationAPIService: authenticationAPIService,
                    deviceAPIService: deviceAPIService,
                    signsAPIService: signsAPIService
                },
                core: {
                    coreConfigurationService: coreConfigurationService,
                    coreFrameworkService: coreFrameworkService,
                    logService: logService,
                    logManager: logManager,
                    fontManager: coreFontManagerService
                },
                deviceManager: {
                    deviceHealthService: deviceHealthService,
                    deviceRegistrationService: deviceRegistrationService
                },
                hal: {
                    halDriverService: halDriverService,
                    pathService: pathService,
                    halMediaService: halMediaService,
                    halNetworkService: halNetworkService,
                    halPlatformService: halPlatformService,
                    halRemoteService: halRemoteService
                },
                job: {
                    admService: admService,
                    jobSchedulerService: jobSchedulerService
                },
                network: {
                    cdnService: cdnService,
                    networkContentCacheService: networkContentCacheService
                },
                storage: {
                    storageFileService: storageFileService,
                    storageLocalStorageService: storageLocalStorageService,

                }
            };
        }

        // chrome eval hack
        var evalQueue = [];
        var currentEval = null;
        var isEvalRunning = false;

        // global callback for eval
        window.$eval = {
            success: function(res, id) {
                // if (currentEval !== null) {
                //     console.log('id>>', id, 'currentEval.id>>', currentEval.opts.id);
                // }

                // skip if awaiting eval different
                if (currentEval === null || (id !== undefined && currentEval.opts.id !== id)) {
                    // console.log('$eval.success return');
                    return;
                }

                // console.log('$eval.success', res);
                currentEval.defer.resolve(res);
                currentEval = null;
                isEvalRunning = false;
            },
            error: function(err, id) {
                // if (currentEval !== null) {
                //     console.log('id>>', id, 'currentEval.id>>', currentEval.opts.id);
                // }

                // skip if awaiting eval different
                if (currentEval === null || (id !== undefined && currentEval.opts.id !== id)) {
                    // console.log('$eval.error return');
                    return;
                }

                // console.log('$eval.error', err);
                currentEval.defer.reject(err);
                currentEval = null;
                isEvalRunning = false;
            }
        };

        // process eval
        function processEval() {
            if (isEvalRunning === false && evalQueue.length > 0) {
                isEvalRunning = true;
                currentEval = evalQueue.splice(0, 1)[0];

                if (currentEval != null && currentEval.opts !== undefined && currentEval.opts.timeout !== undefined) {
                    (function(timeout, id) {
                        setTimeout(function() {
                            window.$eval.error('eval timeout', id);
                        }, timeout);
                    }(currentEval.opts.timeout, currentEval.opts.id));
                }

                halPlatformService.execScript(currentEval.script, currentEval.opts, currentEval.defer);
            }
        }

        function execEval(script, opts) {
            var defer = $q.defer();
            var async = false;

            // eval
            if (script.indexOf('$eval.success') > -1 || script.indexOf('$eval.error') > -1) {
                async = true;
            }

            opts = opts === undefined ? {} : opts;

            // append to opts
            opts.id = new Date().getTime();
            opts.async = async;

            evalQueue.push({
                script: script,
                opts: opts,
                defer: defer
            });
            return defer.promise;
        }

        function getSelfUpdateUrl() {
            return selfUpdateUrl;
        }

        var coreFrameworkServiceApi = {
            isReady: isReady,
            getAppVersion: getAppVersion,
            getBuildVersion: getBuildVersion,
            getServices: getServices,
            eval: execEval,
            getPolicyUrl: getPolicyUrl,
            getDiscoveryUrl: getDiscoveryUrl,
            getQrClaimCodeUrl: getQrClaimCodeUrl,
            getSelfUpdateUrl: getSelfUpdateUrl
        };
        window.$asCoreFrameworkService = coreFrameworkServiceApi;

        return coreFrameworkServiceApi;
    }]);
})(window.angular);

/* eslint-disable prefer-rest-params */
(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.constant('LOGLEVEL', {
        trace: 0,
        debug: 1,
        info: 2,
        warn: 3,
        error: 4,
        fatal: 5,
        none: 6
    });

    app.factory('LogEntry', function() {
        var LogEntry = function(logger, logLevel, appenderName, messages, createdAt) {
            // default parameter
            this.logger = logger;
            this.logLevel = logLevel;
            this.appenderName = appenderName;
            this.messages = messages;
            this.createdAt = (createdAt === undefined) ? moment() : createdAt;
        };

        return LogEntry;
    });

    app.service('logService', ["$q", "$timeout", "$injector", "LOGLEVEL", "LogEntry", "storageObjectDbService", function($q, $timeout, $injector, LOGLEVEL, LogEntry, storageObjectDbService) {
        var loggers;
        var queue;
        var appenders;
        var config;
        var objStoreName = 'history';

        // log to keep in days (-1 flush all)
        var DEFAULT_RETAIN_HISTORY_WINDOW = 7;
        var subscribers = [];
        var _db;
        var hasInit = false;
        var readyQueue = [];
        var retainHistoryWindowConfig;
        var lastAutoCleanup;
        var CLEANUP_INTERVAL = 3600000; // 1 hour
        var FAIL_CLEANUP_RETRY = 5000;
        var backgroundCleanupTimeoutPromise;

        function init() {
            if (hasInit) {
                return;
            }
            reset();
            // init index db
            var conn = storageObjectDbService.indexDB.open('appspace.core.logService', 1);
            conn.onupgradeneeded = function(e) {
                var db = e.target.result;
                for (var i = (e.oldVersion + 1); i <= e.newVersion; i++) {
                    switch (i) {
                        case 1:
                            if (!db.objectStoreNames.contains(objStoreName)) {
                                db.createObjectStore(objStoreName, {keyPath: 'createdAt'});
                            }
                            break;
                    }
                }
            };
            conn.onsuccess = function(e) {
                _db = conn.result;

                // start processing
                setInterval(processQueue, 1000);

                // flush previous queue
                hasInit = true;
                for (var i in readyQueue) {
                    readyQueue[i].resolve();
                }
                readyQueue = [];
            };
            conn.onerror = function(e) {
                for (var i in readyQueue) {
                    readyQueue[i].reject();
                }
                readyQueue = [];
            };
        }

        function isReady() {
            if (hasInit) {
                return $q.resolve();
            }
            var defer = $q.defer();
            // push to wait queue if not yet ready
            readyQueue.push(defer);
            return defer.promise;
        }


        function reset() {
            loggers = {};
            queue = {};
            appenders = {};
            config = {
                logLevel: 'debug',
                appenders: [],
                loggers: []
            };
        }

        // Initialise the service
        init();

        // Public interface
        return {
            isReady: isReady,
            reset: reset,
            setConfiguration: setConfiguration,
            getConfiguration: getConfiguration,
            addLogger: addLogger,
            getLogger: getLogger,
            getAppender: getAppender,
            addAppender: addAppender,
            removeAppender: removeAppender,
            subscribe: subscribe,
            unsubscribe: unsubscribe,
            getHistory: getHistory,
            clearHistory: clearHistory,
            trace: trace,
            debug: debug,
            info: info,
            warn: warn,
            error: error,
            fatal: fatal
        };

        // Returns the history
        // @@params
        // startDate: filter from (epochtime, date)
        // maxResults: result per query (integer)
        // endDate: filter to (epochtime, date) (optional)
        // logger: logger name (string) (optional)
        // order: asc (oldest to latest log), desc (latest to oldest log)
        function getHistory(options) {
            var defer = $q.defer();
            var range;
            var results = [];

            // default date
            var defaults = {
                startDate: 0,
                maxResults: 400,
                order: 'asc'
            };

            // override with default, if compulsory params not exist
            options = $angular.extend(defaults, options);

            // parameters
            var startDate = options['startDate'];
            var endDate = options['endDate'];
            var maxResults = options['maxResults'];
            var logger = options['logger'];

            // convert date to epoch
            if (startDate instanceof Date) {
                startDate = startDate.getTime();
            }
            if (endDate instanceof Date) {
                endDate = endDate.getTime();
            }

            // convert moment to epoch
            if (typeof startDate !== 'undefined' && typeof startDate['unix'] === 'function') {
                startDate = startDate.unix() * 1000;
            }
            if (typeof endDate !== 'undefined' && typeof endDate['unix'] === 'function') {
                endDate = endDate.unix() * 1000;
            }

            // create date range
            if (options['endDate'] === undefined) {
                range = IDBKeyRange.lowerBound(startDate);
            } else {
                // convert epoch time back to date object
                // var endDate = new Date(endDate);
                // set to end of the day
                // endDate.setHours(23,59,59,999);
                // endDate = endDate.getTime();
                // console.log('From>>', startDate, 'To>>', endDate);

                // create the range object
                range = IDBKeyRange.bound(startDate, endDate, false, true);
            }

            // open cursor
            var historyStore = _db.transaction(objStoreName)
                .objectStore(objStoreName);

            // configure log ordering
            var orderMode = 'next';
            if (options.order === 'desc') {
                orderMode = 'prev';
            }

            var historyCursor = historyStore.openCursor(range, orderMode);

            var createLogEntry = function(log) {
                return new LogEntry({name: log.logger}, log.logLevel, '', log.messages, log.createdAt);
            };

            // start traversing
            historyCursor.onsuccess = function(e) {
                var cursor = e.target.result;
                if (cursor) {
                    // filter by logger name if exists
                    if (logger !== undefined) {
                        if (logger === cursor.value['logger']) {
                            results.push(createLogEntry(cursor.value));
                        }
                    } else {
                        results.push(createLogEntry(cursor.value));
                    }

                    cursor.continue();
                } else {
                    if (results.length > maxResults) {
                        results.splice(0, results.length - maxResults);
                    }
                    defer.resolve(results);
                }
            };
            historyCursor.onerror = function(e) {
                defer.reject(e);
            };

            return defer.promise;
        }


        function clearHistory(days, ignoreDateFloor) {
            var defer = $q.defer();
            var historyStore = _db.transaction(objStoreName, 'readwrite')
                .objectStore(objStoreName);


            // set start date, beginning of the day
            var startDate = new Date();

            var maxDays = days ? days : DEFAULT_RETAIN_HISTORY_WINDOW;
            startDate.setDate(startDate.getDate() - maxDays);

            if (!ignoreDateFloor) {
                startDate = startDate.setHours(0, 0, 0, 0);
            }

            var range = IDBKeyRange.upperBound(startDate);
            var historyCursor = historyStore.openCursor(range);
            historyCursor.onsuccess = function(e) {
                var cursor = e.target.result;
                if (cursor) {
                    historyStore.delete(cursor.primaryKey);
                    cursor.continue();
                } else {
                    defer.resolve();
                }
            };
            historyCursor.onerror = function(e) {
                defer.reject(e);
            };

            return defer.promise;
        }


        // Subscribes a callback to log events
        function subscribe(callback) {
            // Check is this callback already subscribed
            for (var i = 0; i < subscribers.length; i++) {
                var s = subscribers[i];
                if (s == callback) {
                    return;
                }
            }
            // Add a subscriber
            subscribers.push(callback);
        }

        // Unsubscribes a callback to log events
        function unsubscribe(callback) {
            // Check is this callback is subscribed
            for (var i = 0; i < subscribers.length; i++) {
                var s = subscribers[i];
                if (s == callback) {
                    subscribers.splice(i, 1);
                    return;
                }
            }
        }

        // start processing each appender
        function processAppenderQueue(q) {
            var jobCount = q.jobs.length;
            for (var i = 0; i < jobCount; i++) {
                var logEntry = q.jobs[i];
                try {
                    q.appender.write(logEntry, {db: _db});
                } catch (exception) {
                    // eslint-disable-next-line no-console
                    console.error(exception);
                }
            }
            // Clear the array
            q.jobs = [];
        }

        // process and check different appender queue time
        function processQueue() {
            for (var type in queue) {
                var q = queue[type];
                var appender = q.appender;
                var flushTimer = appender.config.flushTimer;
                var timeNow = new Date().getTime();
                var timeDiff = timeNow - q.lastExecute;

                // Check has the flush timer elapsed
                if (timeDiff >= flushTimer) {
                    processAppenderQueue(q);
                    q.lastExecute = timeNow;
                }
            }
        }

        // message type
        function pushQueue(logger, appenderCollection, logLevel, messages) {
            var time = moment();
            var logEntry = new LogEntry(logger, logLevel, '', messages, time);
            var i;
            // stringify json object
            if (typeof messages === 'object') {
                for (i in messages) {
                    if (typeof messages[i] === 'object') {
                        // fail safe - attempting to convert window object, will throw error
                        try {
                            messages[i] = JSON.stringify(messages[i]);
                        } catch (e) {
                            // eslint-disable-next-line no-console
                            console.error(e);
                        }
                    }
                }
            }

            // Notify subscribers
            for (i = 0; i < subscribers.length; i++) {
                subscribers[i](logEntry);
            }

            var count = appenderCollection.length;
            for (i = 0; i < count; i++) {
                var type = appenderCollection[i];
                if (appenders[type] === undefined) {
                    continue;
                }

                logEntry = new LogEntry(logger, logLevel, type, messages, time);

                // Add log to the queue
                queue[type].jobs.push(logEntry);
            }
        }

        // supports log format of function arguments, or 2nd arguments as array
        // logService.debug('appspace.network.contentcache', 'message1', 'message2');
        // logService.debug('appspace.network.contentcache', ['message1', 'message2']);
        function log(logLevel, argsParam) {
            // convert argument object to array
            var args = Array.prototype.slice.call(argsParam);

            // console.log(args);

            var loggerName = args[0];
            var messages = args.slice(1, args.length);
            var logger = loggers[loggerName];

            // check if second parameter is array, convert as main messages, if does
            if ($angular.isArray(args[1])) {
                messages = args[1];
            }

            // skip if log name or messages wasn't given
            if (logger === undefined || messages.length < 1) {
                return;
            }

            var confLogLevel = config.logLevel;
            if (logger.config && logger.logLevel) {
                confLogLevel = logger.logLevel;
            }

            var appens = [];
            var pushAppenders = function(list) {
                if (!list) {
                    return;
                }

                for (var i = 0; i < list.length; i++) {
                    var ap = list[i];
                    if (appenders[ap]) {
                        appens.push(ap);
                    }
                }
            };

            // Get custom appender for the logger or the default appenders from the config
            var confAppenders = logger.config.appenders || config.defaultAppenders;
            pushAppenders(confAppenders);

            if (LOGLEVEL[logLevel] >= LOGLEVEL[confLogLevel]) {
                pushQueue(logger, appens, logLevel, messages);
            }

            appens = [];
            appens = null;
            args = [];
            args = null;
        }


        function instantiateAppenders(configs) {
            for (var i = 0; i < configs.length; i++) {
                var appender = configs[i];
                var ai = getAppender(appender.type);
                if (ai === undefined) {
                    // The appender does exist in our collection, go create it
                    var AppenderInstance = $injector.get(appender.type);
                    if (AppenderInstance !== undefined) {
                        var newAppenderInstance = new AppenderInstance();
                        if (appender.config) {
                            newAppenderInstance.config = appender.config;
                        }
                        addAppender(newAppenderInstance);
                    }
                } else if (appender.config) { // Update existing config if any
                    ai.config = appender.config;
                }
            }
        }


        function setConfiguration(newConfig) {
            if (newConfig === undefined || newConfig.logLevel === undefined) {
                throw new Error('Fail to set logservice configuration');
            }
            var i;
            // remove old external appenders
            for (i = 0; i < config.externalAppenders; i++) {
                removeAppender(config.externalAppenders.name);
            }

            // Instatiate defuault appenders config
            instantiateAppenders(newConfig.internalAppenders);
            instantiateAppenders(newConfig.externalAppenders);

            // Instatiate loggers
            if (newConfig.loggers !== undefined) {
                for (i = 0; i < newConfig.loggers.length; i++) {
                    var logger = newConfig.loggers[i];
                    var li = getLogger(logger.name);
                    if (li === undefined) {
                        // Logger does not exist, so create it
                        addLogger(logger);
                    } else {
                        // Update existing config
                        li.config = logger.config;
                    }
                }
            }
            retainHistoryWindowConfig = newConfig.retainHistoryWindow || DEFAULT_RETAIN_HISTORY_WINDOW;
            config = newConfig;
            executeBackgroundCleanup();
        }

        function onHistoryCleared() {
            lastAutoCleanup = new Date().getTime();
            startBackgroundCleanupTimer(CLEANUP_INTERVAL);
        }

        function onHistoryClearFailed() {
            // retry in 5 seconds
            startBackgroundCleanupTimer(FAIL_CLEANUP_RETRY);
        }

        function executeBackgroundCleanup() {
            var nowTime = new Date().getTime();
            // if last auto cleanup is less than clean up interval, sleep
            if (lastAutoCleanup && nowTime - lastAutoCleanup < CLEANUP_INTERVAL) {
                startBackgroundCleanupTimer(CLEANUP_INTERVAL);
                return;
            }
            clearHistory(retainHistoryWindowConfig).then(onHistoryCleared, onHistoryClearFailed);
        }

        function startBackgroundCleanupTimer(timeout) {
            clearBackgroundCleanupTimer();
            backgroundCleanupTimeoutPromise =
                $timeout(executeBackgroundCleanup, timeout);
        }

        function clearBackgroundCleanupTimer() {
            if (!backgroundCleanupTimeoutPromise) {
                return;
            }

            $timeout.cancel(backgroundCleanupTimeoutPromise);
            backgroundCleanupTimeoutPromise = undefined;
        }

        function getConfiguration() {
            return config;
        }

        function addLogger(logger) {
            if (logger.name === undefined) {
                return false;
            }
            loggers[logger.name] = logger;
            return true;
        }

        function getLogger(loggerName) {
            return loggers[loggerName];
        }

        function getAppender(appenderName) {
            return appenders[appenderName];
        }

        function addAppender(appender) {
            if (appender.type === undefined || appender.write === undefined) {
                return false;
            }
            if (appender.config === undefined || appender.config.flushTimer === undefined) {
                return false;
            }

            appenders[appender.type] = appender;

            if (queue[appender.type] === undefined) {
                // init appender for each queue
                queue[appender.type] = {
                    appender: appender,
                    lastExecute: (new Date).getTime(),
                    jobs: []
                };
            } else {
                // Update the existing appender
                queue[appender.type].appender = appender;
            }
            return true;
        }

        function removeAppender(appenderName) {
            var index = appenders.indexOf(appenderName);
            if (index > 0) {
                appenders.splice(index, 1);
            }
        }

        function trace() {
            log('trace', arguments);
        }

        function debug() {
            log('debug', arguments);
        }

        function info() {
            log('info', arguments);
        }

        function warn() {
            log('warn', arguments);
        }

        function error() {
            log('error', arguments);
        }

        function fatal() {
            log('fatal', arguments);
        }
    }]);
})(window.angular);

(function($angular) {

    "use strict";

    var app = $angular.module('core');

    app.service('logManager', ["$q", "logService", "storageFileService", "FileUtil", function($q, logService, storageFileService, FileUtil){

        // public interface
        return {
            exportHistory: exportHistory
        };

        function init(){

        }
        init();


        // export 
        function exportHistory(path, options) {
            var defer = $q.defer();
            var dirPath = FileUtil.getDirPath(path);

            logService.getHistory(options).then(function(res){
                var count = 0;
                var arr = [];
                var line;
                var content = '';
                var delimiter = '\t';

                for (var i in res) {
                    var param = [];

                    // push to array
                    param.push(new Date(res[i].createdAt));
                    param.push(res[i].logLevel);
                    param.push(res[i].logger);
                    param.push(res[i].messages);

                    line = param.join(delimiter) + '\r\n';

                    // export join field with separator
                    arr.push(line);                    
                    count++;
                }

                // merge to string
                content = arr.join('');

                // write the logs
                storageFileService.createDirectory(dirPath).then(function(res){
                    storageFileService.writeFile(path, content)
                        .then(function(res){
                            defer.resolve(res);
                        }, function(err){
                            defer.reject(err);
                        });  
                }, function(err){
                    defer.reject(err);
                })              
            }); 

            return defer.promise;
        } 
    }]);
})(window.angular);
(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('core');

    app.service('navigationService', ["$location", "$timeout", "$injector", function(
        $location,
        $timeout,
        $injector
    ) {
        var RELOAD_DELAY = 10;
        var halPlatformService;

        function getHalPlatformService() {
            if (!halPlatformService) {
                halPlatformService = $injector.get('halPlatformService');
            }

            return halPlatformService;
        }

        function reloadView(view) {
            if (!view) {
                return;
            }

            $location.path(view);
            $timeout(function() {
                getHalPlatformService().reload();
            }, RELOAD_DELAY);
        }

        return {
            reloadView: reloadView
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('core');

    app.service('networkConnectionService', ["$injector", "syncService", "SYNC_EVENT", "NETWORK_CONNECTION_STATE", "PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT", function(
        $injector,
        syncService,
        SYNC_EVENT,
        NETWORK_CONNECTION_STATE,
        PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT
    ) {
        var NOT_FOUND_INDEX = -1;
        var subscribers = [];
        var currentState;
        var pushNotificationClientService;
        var halNetworkService;

        function updateConnectionState(newState) {
            if (newState === currentState) {
                return;
            }

            currentState = newState;
            notifySubscribers(currentState);
        }

        function onSyncEvent(event) {
            if (!event) {
                return;
            }

            switch ((event.event || '').toLowerCase()) {
                case SYNC_EVENT.TIMESTAMP_RETRIEVED:
                    updateConnectionState(NETWORK_CONNECTION_STATE.CONNECTED);
                    break;
                case SYNC_EVENT.ERROR_RETRIEVING_TIMESTAMP:
                    updateConnectionState(NETWORK_CONNECTION_STATE.DISCONNECTED);
                    break;
                case SYNC_EVENT.SCRIPT_READY:
                    // To avoid premature PNS event subscription that leads to circular dependency
                    getPushNotificationClientService().subscribe(onPushNotificationClientEvent);
                    var currentPnsState = getCurrentPnsState();
                    if (!currentPnsState.isStarted) {
                        return;
                    }

                    onPnsStarted(currentPnsState);
                    break;
            }
        }

        function notifySubscribers(event) {
            for (var i = 0; i < subscribers.length; i++) {
                subscribers[i](event);
            }

            getHalNetworkService().processNetworkConnectionState(event);
        }

        function subscribe(callback) {
            if (subscribers.indexOf(callback) !== NOT_FOUND_INDEX) {
                return;
            }

            subscribers.push(callback);
        }

        function unsubscribe(callback) {
            var subscriberIndex = subscribers.indexOf(callback);
            if (subscriberIndex === NOT_FOUND_INDEX) {
                return;
            }

            subscribers.splice(subscriberIndex, 1);
        }

        function getCurrentState() {
            return currentState;
        }

        function getPushNotificationClientService() {
            if (!pushNotificationClientService) {
                pushNotificationClientService = $injector.get('pushNotificationClientService');
            }

            return pushNotificationClientService;
        }

        function getHalNetworkService() {
            if (!halNetworkService) {
                halNetworkService = $injector.get('halNetworkService');
            }

            return halNetworkService;
        }

        function getCurrentPnsState() {
            return getPushNotificationClientService().getCurrentState() || {};
        }

        function onPnsStarted(pnsState) {
            pnsState = pnsState || getCurrentPnsState();
            if (!pnsState.isKeepAliveEnabled) {
                // Heartbeat check is not enabled
                return;
            }

            updateConnectionState(NETWORK_CONNECTION_STATE.CONNECTED);
        }

        function onPushNotificationClientEvent(event, data) {
            if (!event) {
                return;
            }

            switch (event) {
                case PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.CLIENT_CONNECTED:
                case PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.CLIENT_RECONNECTED:
                    onPnsStarted();
                    break;
                case PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.CLIENT_HEARTBEAT_ACKNOWLEDGED:
                    updateConnectionState(data ?
                        NETWORK_CONNECTION_STATE.CONNECTED :
                        NETWORK_CONNECTION_STATE.DISCONNECTED);
                    break;
                case PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.CLIENT_DISCONNECTED:
                    updateConnectionState(NETWORK_CONNECTION_STATE.DISCONNECTED);
                    break;
            }
        }

        syncService.subscribe(onSyncEvent);

        return {
            subscribe: subscribe,
            unsubscribe: unsubscribe,
            getCurrentState: getCurrentState
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('core');

    app.service('occupancyService', ["halPlatformService", "logService", "dataUtil", function(halPlatformService, logService, dataUtil) {
        var currentStatus;
        var LOG_KEY = 'appspace.core.occupancy';
        var subscribers = [];
        var SPLICE_AMOUNT = 1;

        function onStatusReceived(status) {
            if (status === currentStatus) {
                return;
            }

            currentStatus = status;
            logService.debug(LOG_KEY, 'Occupancy sensor status changed: ' + currentStatus);
            notifySubscribers(currentStatus);
        }

        function runOccupancyServiceMethod(methodName, argument) {
            var service = halPlatformService.getOccupancyService();
            if (!service) {
                return;
            }

            service[methodName](argument);
        }

        function start() {
            stop();
            runOccupancyServiceMethod('subscribe', onStatusReceived);
        }

        function stop() {
            runOccupancyServiceMethod('unsubscribe');
        }

        function notifySubscribers(status) {
            for (var i = 0; i < subscribers.length; i++) {
                var subscriber = subscribers[i];
                if (!subscriber) {
                    continue;
                }

                subscriber(status);
            }
        }

        function subscribe(callback) {
            if (!callback || dataUtil.includesValue(callback, subscribers)) {
                return;
            }

            subscribers.push(callback);
            callback(currentStatus);
        }

        function unsubscribe(callback) {
            if (!callback) {
                return;
            }

            var subscriberIndex = subscribers.indexOf(callback);
            if (subscriberIndex === -1) {
                return;
            }

            subscribers.splice(subscriberIndex, SPLICE_AMOUNT);
        }

        return {
            start: start,
            stop: stop,
            subscribe: subscribe,
            unsubscribe: unsubscribe
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('core');
    app.service('postMessagingService', ["$q", "$window", "$timeout", "BaseRemoteService", "embedProfileService", "logService", "halPlatformService", "coreConfigurationService", "postMessagingUtil", "sessionService", "POST_MESSAGES", "SYSTEM_DATA", "FEATURE_CODE", "POST_MESSAGES_ERROR", function(
        $q,
        $window,
        $timeout,
        BaseRemoteService,
        embedProfileService,
        logService,
        halPlatformService,
        coreConfigurationService,
        postMessagingUtil,
        sessionService,
        POST_MESSAGES,
        SYSTEM_DATA,
        FEATURE_CODE,
        POST_MESSAGES_ERROR
    ) {
        function PostMessagingService() {
            var base = new BaseRemoteService();
            var contentBase = new BaseRemoteService();
            var started = false;
            var logKey = 'appspace.network.comm';
            var expectingReplyTimeout = {};
            var contentCallbacks = {};
            var requestPostMessageInfo = {};
            var requestPostMessageId = 0;
            var appOrigin;
            var appWindow;
            var msMessaging;
            var REQUEST_POST_MESSAGE_TIMEOUT = 10000;
            var EVENT_LISTENER_TYPE = {
                MESSAGE: 'message',
                ON_MESSAGE: 'onmessage'
            };

            this.subscribe = base.subscribe;
            this.unsubscribe = base.unsubscribe;
            this.requestPostMessage = requestPostMessage;
            this.subscribeToContentMessages = contentBase.subscribe;
            this.unsubscribeFromContentMessages = contentBase.unsubscribe;

            // Global function for ms-webview post messaging
            function globalMsPostMessaging(message) {
                var messageObject = JSON.parse(message);
                var event = {
                    data: messageObject
                };
                onReceivedMessageFromParent(event);
            }

            // Global function for ms-webview retrieve messaging
            function globalMsRetrieveMessaging() {
                var message = '[]';
                if (msMessaging) {
                    message = JSON.stringify(msMessaging);
                    msMessaging = [];
                }

                return message;
            }

            function startListenEventsFromParent() {
                // UWP specific workflow
                $window.msPostMessaging = globalMsPostMessaging;
                $window.msRetrieveMessaging = globalMsRetrieveMessaging;
                // Set up parent window event listener for commands
                // Create IE + others compatible event handler
                if ($window.addEventListener) {
                    $window.addEventListener(EVENT_LISTENER_TYPE.MESSAGE, onReceivedMessageFromParent);
                } else {
                    $window.attachEvent(EVENT_LISTENER_TYPE.ON_MESSAGE, onReceivedMessageFromParent);
                }

                raiseParentMessage({message: POST_MESSAGES.APPSPACE_APP_READY});
            }

            this.start = function() {
                if (started) {
                    logDebug('PostMessagingService has already been started');
                    return;
                }

                started = true;
                halPlatformService.subscribeToPostMessaging(onReceivedMessageFromParent);
                halPlatformService.startPostMessaging();
                startListenEventsFromParent();
            };

            function stopListenEventsFromParent() {
                if ($window.removeEventListener) {
                    $window.removeEventListener(EVENT_LISTENER_TYPE.MESSAGE, processWebviewMessage);
                    return;
                }

                $window.detachEvent(EVENT_LISTENER_TYPE.ON_MESSAGE, processWebviewMessage);
            }

            this.stop = function() {
                if (!started) {
                    logDebug('PostMessagingService has already been stopped');
                    return;
                }

                started = false;
                halPlatformService.stopPostMessaging();
                stopListenEventsFromParent();
            };

            this.raiseMessage = raiseMessage;

            this.raiseCrestronMessage = raiseCrestronMessage;

            this.raiseCrestronLoadedMessage = function(error) {
                raiseCrestronMessage(error, postMessagingUtil.getLoadedMessageFromCrestronMode);
            };

            this.raiseCrestronProcessedMessage = function(error) {
                raiseCrestronMessage(error, postMessagingUtil.getProcessedMessageFromCrestronMode);
            };

            this.raiseSuccessfulRegistrationMessage = function() {
                raiseRegistrationMessage(getRegistrationInformation());
            };

            this.raiseSuccessfullUnregistrationMessage = function() {
                raiseRegistrationMessage({
                    isRegistered: false
                });
            };

            this.registerContent = function(id, callback) {
                if (contentCallbacks[id]) {
                    return;
                }

                contentCallbacks[id] = callback;
            };

            this.unregisterContent = function(id) {
                if (!contentCallbacks[id]) {
                    return;
                }

                delete contentCallbacks[id];
            };

            this.raiseContentMessage = function(event) {
                event = event || {};
                var data = event.data || {};
                logDebug('Received content post message:' + data.message);
                contentBase.notifySubscribers(data);
            };

            function raiseParentMessage(message) {
                if (!message) {
                    return;
                }

                var logMessage = function() {
                    logDebug('Sending parent post message: ' + JSON.stringify(message));
                };

                if (appOrigin && appWindow) {
                    appWindow.postMessage(message, appOrigin);
                    logMessage();
                    return;
                }

                if (msMessaging) {
                    msMessaging.push(message);
                    logMessage();
                    return;
                }

                if (!$window || $window == $window.parent || !$window.parent.postMessage) {
                    return;
                }

                $window.parent.postMessage(message, '*');
                logMessage();
            }

            this.raiseParentMessage = raiseParentMessage;

            this.sendPostMessageToContent = function(message) {
                for (var key in contentCallbacks) {
                    contentCallbacks[key](message);
                }
            };

            function getRegistrationInformation() {
                var playerguid = coreConfigurationService.get('device', 'playerguid');
                if (!playerguid) {
                    return {isRegistered: false};
                }

                return {
                    isRegistered: true,
                    registeredTo: coreConfigurationService.get('appspace', 'appspaceURL'),
                    deviceId: playerguid
                };
            }

            function raiseRegistrationMessage(registrationInfo) {
                if (halPlatformService.getFeatureSupport(FEATURE_CODE.REGISTRATION_UPDATE_SUPPORTED) !== true) {
                    return;
                }

                raiseMessage({
                    message: POST_MESSAGES.NAME,
                    type: POST_MESSAGES.REGISTRATION_UPDATE,
                    info: registrationInfo
                });
            }

            function raiseCrestronMessage(error, messageMethod) {
                var crestronMode = sessionService.get('crestronMode');
                if (!crestronMode || !messageMethod) {
                    return;
                }

                var event = {
                    message: POST_MESSAGES.NAME,
                    type: messageMethod(crestronMode)
                };
                if (error) {
                    event.error = error;
                }

                raiseMessage(event);
            }

            function raiseMessage(data) {
                if (!data) {
                    return;
                }

                logDebug('Posting event:' + JSON.stringify(data));
                var raiseMessageCallback = embedProfileService.isChannelPreviewMode() ?
                    raiseParentMessage : halPlatformService.raisePostMessage;
                raiseMessageCallback(data);
                cancelExpectingReplyTimeout(data.type);
            }

            function logDebug(message) {
                logService.debug(logKey, message);
            }

            function raiseReadyEvent() {
                raiseMessage({
                    message: POST_MESSAGES.NAME,
                    type: POST_MESSAGES.READY,
                    info: getRegistrationInformation()
                });
            }

            function requestPostMessage(parameters, requestMessage, responseMessage) {
                var defer = $q.defer();
                // Generate Id based on expected response message and incremental Id
                requestPostMessageId++;
                var id = responseMessage + '_' + requestPostMessageId;
                requestPostMessageInfo[id] = {
                    defer: defer,
                    timeout: $timeout(function() {
                        delete requestPostMessageInfo[id];
                        defer.reject('Timeout on requesting post message: "' + requestMessage +
                            '" with parameter: ' + JSON.stringify(parameters));
                    }, REQUEST_POST_MESSAGE_TIMEOUT)
                };

                parameters = parameters || {};
                parameters.message = requestMessage;
                parameters.callback = {$requestId: requestPostMessageId};

                raiseMessage(parameters);
                return defer.promise;
            }

            function processPostMessageResponse(id, response) {
                var request = requestPostMessageInfo[id];
                if (!request) {
                    return;
                }

                if (request.timeout) {
                    $timeout.cancel(request.timeout);
                }

                var defer = request.defer;
                delete requestPostMessageInfo[id];
                if (response.errorMessage) {
                    defer.reject(response.errorMessage);
                    return;
                }

                defer.resolve(response.result);
            }

            function processWebviewMessage(event) {
                switch (event.data.message.toLowerCase()) {
                    case POST_MESSAGES.WEBVIEW_POST_MESSAGE_INIT:
                        // Ignore this message if we have already init
                        if (!appWindow && !appOrigin) {
                            appWindow = event.source;
                            appOrigin = event.origin;
                            raiseParentMessage({message: POST_MESSAGES.APPSPACE_APP_READY});
                        }

                        return true;
                    case POST_MESSAGES.MSWEBVIEW_POST_MESSAGE_INIT:
                        msMessaging = [];
                        raiseParentMessage({message: POST_MESSAGES.APPSPACE_APP_READY});
                        return true;
                }

                return false;
            }

            function onReceivedMessageFromParent(event) {
                var postMessageDataEvent = event && event.data ? event.data : {};
                var eventOrigin = event && event.origin ? event.origin : '';
                var message = postMessageDataEvent.message;

                if (halPlatformService.processPostMessagingMessage(postMessageDataEvent, eventOrigin)) {
                    return;
                }

                if (!message) {
                    logDebug('Received invalid post message event');
                    return;
                }

                if (processWebviewMessage(event)) {
                    return;
                }

                logDebug('Received post message:' + message);
                // handle appspaceapp.postmessage.ready internally
                if (message == 'appspaceapp.postmessage.ready') {
                    raiseReadyEvent();
                    return;
                }

                base.notifySubscribers(event.data);
                if (event.data.callback && event.data.callback.$requestId) {
                    processPostMessageResponse(message + '_' + event.data.callback.$requestId, event.data.callback);
                }

                var mappedResponse = postMessagingUtil.getLoadedMessageFromCrestronMode(postMessageDataEvent.mode);
                if (mappedResponse == POST_MESSAGES.UNKNOWN) {
                    return;
                }

                if (!expectingReplyTimeout[mappedResponse]) {
                    expectingReplyTimeout[mappedResponse] = [];
                }

                logDebug('Waiting for response for ' + postMessageDataEvent.mode + ' post message event. Expecting ' + mappedResponse + '.');
                expectingReplyTimeout[mappedResponse].push($timeout(function() {
                    onExpectingReplyTimeout(mappedResponse);
                }, SYSTEM_DATA.MIN_TIMEOUT_DURATION));
            }

            function onExpectingReplyTimeout(event) {
                logDebug('No ' + event + ' event fired within ' + SYSTEM_DATA.MIN_TIMEOUT_DURATION + 'ms. Sending timeout message.');
                cancelExpectingReplyTimeout(event);
                raiseMessage({
                    message: POST_MESSAGES.NAME,
                    type: event,
                    error: POST_MESSAGES_ERROR.TIMEOUT
                });
            }

            function cancelExpectingReplyTimeout(event) {
                if (!expectingReplyTimeout[event]) {
                    return;
                }

                $timeout.cancel(expectingReplyTimeout[event].shift());
                if (expectingReplyTimeout[event].length > 0) {
                    return;
                }

                delete expectingReplyTimeout[event];
            }
        }

        return new PostMessagingService();
    }]);
})(window.angular);

(function($angular) {
    var app = $angular.module('core');
    /**
     * Push Notification Client Service
     * @enum events @see PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT:
     *  - service.started: service is started
     *  - service.stopped: service is stopped
     *  - client.connected: connected to the PNS server
     *  - client.disconnected: disconnected to the PNS server (aka disrupted). May or may not retry
     *  - client.closed: PNS client closed, no reconnection. Confirmed no reconnection.
     *  - client.reconnecting: reconnecting to the PNS server
     *  - client.reconnected: reconnected to the PNS server
     *  - client.heartbeat-acknowledged
     *  - watchdog.started: watchdog is started
     *  - watchdog.stopped: watchdog is stopped
     *  - watchdog.alert: abnormality detected, watchdog will restart the PNS client
     */
    app.service('pushNotificationClientService', ["$filter", "$q", "$timeout", "$window", "CONSTELLATION_TOKENS_EVENT", "HEARTBEAT_STATUS", "PLAYER_PROPERTY_KEYS", "PNS_TOPIC_ATTRIBUTE", "PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT", "authenticationLibraryService", "configurationService", "deviceRegistrationService", "deviceTokenService", "embedProfileService", "halPlatformService", "telemetryService", "logService", "syncService", "dataUtil", "versionUtil", "DateTimeUtil", function(
        $filter,
        $q,
        $timeout,
        $window,
        CONSTELLATION_TOKENS_EVENT,
        HEARTBEAT_STATUS,
        PLAYER_PROPERTY_KEYS,
        PNS_TOPIC_ATTRIBUTE,
        PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT,
        authenticationLibraryService,
        configurationService,
        deviceRegistrationService,
        deviceTokenService,
        embedProfileService,
        halPlatformService,
        telemetryService,
        logService,
        syncService,
        dataUtil,
        versionUtil,
        DateTimeUtil
    ) {
        // MARK: CONSTANTS
        var LOG_KEY = 'appspace.core.pushnotificationclient';
        var SUPPORTED_VERSIONS = {
            PUSH_NOTIFICATION_SERVICE: {
                CLOUD: {
                    MIN: '8.1.0-ac.43',
                    MAX: '8.1.0'
                },
                GLOBAL: '8.1.0',
                UNSUPPORTED_MESSAGE: 'Platform version does not meet the minimum platform version requirement to run.'
            },
            HEARTBEAT_FEATURE: {
                CLOUD: {
                    MIN: '8.5.0-ac.2',
                    MAX: '8.5.0'
                },
                GLOBAL: '8.5.0',
                UNSUPPORTED_MESSAGE: 'Platform version does not meet the minimum requirement to support heartbeat keepalive.'
            }
        };
        var SERVICE_STATE_ENUM = {
            INITIAL: 'initial',
            INITIALIZING: 'initializing',
            INITIALIZED: 'initialized',
            STARTING: 'starting',
            STARTED: 'started',
            RESTARTING: 'restarting',
            STOPPING: 'stopping',
            STOPPED: 'stopped',
        };
        var PNS_CLIENT_STATE_ENUM = {
            INITIAL: 'initial',
            INITIALIZING: 'initializing',
            INITIALIZED: 'initialized',
            CONNECTING: 'connecting',
            CONNECTED: 'connected',
            RECONNECTING: 'reconnecting',
            DISCONNECTED: 'disconnected', // aka disrupted
            CLOSING: 'closing',
            CLOSED: 'closed', // only used when the client is stop manually
        };
        var WATCHDOG_STATE_ENUM = {
            INITIAL: 'initial',
            INITIALIZING: 'initializing',
            INITIALIZED: 'initialized',
            DISABLED: 'disabled',
            STARTED: 'started',
            STOPPED: 'stopped',
        };
        var HEARTBEAT_DEFAULT_VALUE = {
            INTERVAL: 60000,
            TIMEOUT: 5000,
            RANDOM_BACKOFF_MAX: 5000, // this is the max value for random backoff
        };
        var PNS_CLIENT_EVENTS = ($window.PNSClient && $window.PNSClient.PNSEvents) ? $window.PNSClient.PNSEvents : {};
        var CLOSURE_CODE = ($window.PNSClient && $window.PNSClient.WEBSOCKET_CONNECTION_CLOSE_CODES) ? $window.PNSClient.WEBSOCKET_CONNECTION_CLOSE_CODES : {};
        var PNS_CLIENT_EVENT_HANDLERS = {};
        PNS_CLIENT_EVENT_HANDLERS[PNS_CLIENT_EVENTS.closed] = onClosed;
        PNS_CLIENT_EVENT_HANDLERS[PNS_CLIENT_EVENTS.connected] = onConnected;
        PNS_CLIENT_EVENT_HANDLERS[PNS_CLIENT_EVENTS.connecting] = onConnecting;
        PNS_CLIENT_EVENT_HANDLERS[PNS_CLIENT_EVENTS.debug] = onDebug;
        PNS_CLIENT_EVENT_HANDLERS[PNS_CLIENT_EVENTS.disconnected] = onDisconnected;
        PNS_CLIENT_EVENT_HANDLERS[PNS_CLIENT_EVENTS.error] = onError;
        PNS_CLIENT_EVENT_HANDLERS[PNS_CLIENT_EVENTS.heartbeat] = onHeartbeat;
        PNS_CLIENT_EVENT_HANDLERS[PNS_CLIENT_EVENTS.message] = onMessage;
        PNS_CLIENT_EVENT_HANDLERS[PNS_CLIENT_EVENTS.reconnecting] = onReconnecting;
        PNS_CLIENT_EVENT_HANDLERS[PNS_CLIENT_EVENTS.state] = onState;
        PNS_CLIENT_EVENT_HANDLERS[PNS_CLIENT_EVENTS.updatedIdentity] = onIdentityUpdated;
        var IDENTITY_ATTRIBUTES = {
            DEVICE_GROUP_ID: 'SubjectGroupID',
            DEVICE_TAGS: 'Tag',
            NETWORK_ID: 'NetworkID',
            DEVICE_TYPE: 'DeviceType',
            SUBJECT_ID: 'SubjectID',
            SUBJECT_TYPE: 'SubjectType',
            CURRENT_NETWORK_ID: 'CurrentNetworkID',
            CHANNEL_PUBLISHING_MODE: 'ChannelPublishingMode',
            TOPIC: 'Topic',
        };
        var DEVICE_SUBJECT_TYPE = 'device';
        var WATCHDOG_DEFAULT_VALUES = {
            ENABLED: true,
            BACKOFF: 10000,
        };
        var PNS_CLIENT_INITIAL_CONFIG = {
            domain: undefined,
            accessToken: undefined,
            idToken: undefined,
            identityAttrs: [],
            autoStart: false,
            customMessageProcessor: undefined,
            keepAliveOptions: {
                isEnabled: undefined,
                interval: HEARTBEAT_DEFAULT_VALUE.INTERVAL,
                timeout: HEARTBEAT_DEFAULT_VALUE.TIMEOUT,
                triggerOnFocus: false,
            }
        };
        var PNS_MESSAGES_TO_LOG_AS_DEBUG = [
            'pong'
        ];
        // MARK: LOCAL VARIABLES
        var deviceId;
        var isPushNotificationServiceSupported;
        var isHeartbeatFeatureSupported;
        var initPromise;
        var pnsClient;
        var pnsClientConfig;
        var state = {
            service: SERVICE_STATE_ENUM.INITIAL,
            pnsClient: PNS_CLIENT_STATE_ENUM.INITIAL,
            watchdog: WATCHDOG_STATE_ENUM.INITIAL,
        };
        var subscribers = [];
        var topicAttribute = {};
        var topicSubscribers = {};
        var watchdogConfig = {};
        var watchdogTimeout;
        var stopClientPromise;
        var connectionAudit = {};
        var INITIAL_CONNECTION_AUDIT = {
            history: [],
            connection: {
                connectedAt: '',
                disconnectedAt: '',
                duration: 0,
            }
        };
        var deviceTokens;
        var dateTimeUtil = new DateTimeUtil();

        unsetConnectionAudit();
        resetPnsClientConfig();

        // MARK: PUBLIC METHODS
        function start() {
            if (state.service === SERVICE_STATE_ENUM.STARTED ||
                state.service === SERVICE_STATE_ENUM.STARTING) {
                logService.info(LOG_KEY, 'Push notification client service already ' + state.service + '. Skipping..');
                return;
            }

            // is preview mode (device preview)
            if (embedProfileService.isPreviewMode()) {
                logService.info(LOG_KEY, 'Starting push notification client service skipped. Device is in preview mode');
                return;
            }

            deviceId = deviceRegistrationService.getPlayerId();
            if (!deviceId) {
                logService.info(LOG_KEY, 'Starting push notification client service skipped. Device is in unregistered state.');
                return;
            }

            // init service, client and watchdog if not already initialized
            var onInitResolved = function() {
                // make sure we are initializing, initialized or stopped
                if (state.service !== SERVICE_STATE_ENUM.INITIALIZING &&
                    state.service !== SERVICE_STATE_ENUM.INITIALIZED &&
                    state.service !== SERVICE_STATE_ENUM.STOPPED &&
                    state.service !== SERVICE_STATE_ENUM.RESTARTING) {
                    logService.warn(LOG_KEY, 'Push notification client service is not in a valid state to start. Current state: ', state.service);
                    return;
                }

                if (!isPushNotificationServiceSupported) {
                    logService.warn(LOG_KEY, SUPPORTED_VERSIONS.PUSH_NOTIFICATION_SERVICE.UNSUPPORTED_MESSAGE);
                    return;
                }

                logService.info(LOG_KEY, 'Starting push notification client service. Current state: ', state.service);
                state.service = SERVICE_STATE_ENUM.STARTING;

                startPnsClient();
                startWatchdog();

                var message = 'Push notification client service started';
                logService.info(LOG_KEY, message);
                state.service = SERVICE_STATE_ENUM.STARTED;
                notifySubscribers(PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.SERVICE_STARTED);
            };

            var onInitRejected = function(error) {
                logService.error(LOG_KEY, 'Push notification client service initialization failed: ', error);
                initPromise = null;
                // possibly token issue
                logService.debug(LOG_KEY, 'Subscribed to device token event');
                deviceTokenService.subscribe(onDeviceTokenEvent);
            };

            initAsync().then(onInitResolved, onInitRejected);
        }

        function stop() {
            if (state.service === SERVICE_STATE_ENUM.STOPPED ||
                state.service === SERVICE_STATE_ENUM.STOPPING ||
                state.service === SERVICE_STATE_ENUM.INITIAL) {
                logService.info(LOG_KEY, 'Push notification client service is "' + state.service + '". Skipped stopping..');
                return;
            }

            if (state.service !== SERVICE_STATE_ENUM.RESTARTING) {
                logService.info(LOG_KEY, 'Stopping push notification client service. State: ', state.service);
                state.service = SERVICE_STATE_ENUM.STOPPING;
            }

            stopWatchdog();
            stopPnsClient().then(function() {
                if (state.service === SERVICE_STATE_ENUM.RESTARTING) {
                    logService.debug(LOG_KEY, 'Push notification client service is restarting');
                    start();
                    return;
                }

                // Only reset the subscribers after the client is completely closed and not restart workflow
                // to allow subscribe to receive the `client.closed` event
                // unsubscribe from pns client and service event once it completely stopped (by promise resolved)
                unsubscribeFromPnsClientEvents();
                unsubscribeFromServiceEvents();
                // to reset all configs and to be initialize after service stopped by unregistration
                resetService();
            });

            if (state.service === SERVICE_STATE_ENUM.RESTARTING) {
                logService.debug(LOG_KEY, 'Push notification client service is stopped by restart');
                return;
            }

            var message = 'Push notification client service stopped';
            logService.info(LOG_KEY, message);
            state.service = SERVICE_STATE_ENUM.STOPPED;
            notifySubscribers(PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.SERVICE_STOPPED);
        }

        function resetService() {
            initPromise = undefined;
            pnsClient = undefined;
            deviceId = undefined;
            isPushNotificationServiceSupported = undefined;
            isHeartbeatFeatureSupported = undefined;
            watchdogTimeout = undefined;
            stopClientPromise = undefined;
            watchdogConfig = {};
            resetPnsClientConfig();
        }

        /**
         * Restart the push notitification client service
         * @param {string} reason, optional reason for restart
         */
        function restart(reason) {
            var message = 'Restarting push notification client service';
            if (reason) {
                message += ' due to: ' + reason;
            }

            logService.info(LOG_KEY, message);
            state.service = SERVICE_STATE_ENUM.RESTARTING;
            addEventToConnectionHistory(message);
            stop();
        }

        function subscribe(callback) {
            if (!callback || subscribers.indexOf(callback) !== -1) {
                return;
            }

            subscribers.push(callback);
        }

        function unsubscribe(callback) {
            var subscriberIndex = subscribers.indexOf(callback);
            if (subscriberIndex === -1) {
                return;
            }

            subscribers.splice(subscriberIndex, 1);
        }

        function subscribeToTopic(topic, callback) {
            logService.debug(LOG_KEY, 'Subscribing to topic: ', topic);
            topicSubscribers[topic] = topicSubscribers[topic] || [];
            if (!callback || topicSubscribers[topic].indexOf(callback) !== -1) {
                return;
            }

            topicSubscribers[topic].push(callback);
        }

        function unsubscribeFromTopic(topic, callback) {
            logService.debug(LOG_KEY, 'Unsubscribing from topic: ', topic);
            topicSubscribers[topic] = topicSubscribers[topic] || [];
            var subscriberIndex = topicSubscribers[topic].indexOf(callback);
            if (subscriberIndex === -1) {
                return;
            }

            topicSubscribers[topic].splice(subscriberIndex, 1);
        }

        function getCurrentState() {
            return {
                isStarted: state.client === PNS_CLIENT_STATE_ENUM.CONNECTED,
                isStarting: state.client === PNS_CLIENT_STATE_ENUM.CONNECTING,
                isKeepAliveEnabled: pnsClientConfig.keepAliveOptions.isEnabled,
                isDisconnected: state.client === PNS_CLIENT_STATE_ENUM.DISCONNECTED || PNS_CLIENT_STATE_ENUM.CLOSED,
            };
        }

        /**
         * Update interested topic attributes
         * @param {string} attribute, example: 'post'
         * @param {string[]} value, example: ['guid1', 'guid2']
         */
        function updateTopicAttribute(attribute, value) {
            logService.info(LOG_KEY, 'Updating topic attribute: ', attribute, value);

            if (!dataUtil.isArray(value) && !dataUtil.isString(value)) {
                logService.error(LOG_KEY, 'Failed to update date topic attribute. Topic value is not array. Value: ' + value);
                return;
            }

            value = dataUtil.isArray(value) ? value : [value];
            topicAttribute[attribute] = value;
            if (processIdentityAttributes(syncService.getCurrentAppProperties())) {
                updatePnsClientIdentity();
            }
        }

        // MARK: PRIVATE METHODS
        function initAsync() {
            if (initPromise) {
                return initPromise.promise;
            }

            initPromise = $q.defer();

            var onInitClientResolved = function() {
                logService.info(LOG_KEY, 'Push notification client initialized');
                state.pnsClient = PNS_CLIENT_STATE_ENUM.INITIALIZED;
                initWatchdog();
                initPromise.resolve();
            };

            var onInitClientRejected = function(error) {
                logService.error(LOG_KEY, 'Push notification client initialization failed: ', JSON.stringify(error));
                state.pnsClient = PNS_CLIENT_STATE_ENUM.INITIAL;
                initPromise.reject(error);
            };

            var onInitServiceResolved = function() {
                logService.info(LOG_KEY, 'Push notification client service initialized');
                state.service = SERVICE_STATE_ENUM.INITIALIZED;
                initClientAsync().then(onInitClientResolved, onInitClientRejected);
            };

            var onInitServiceRejected = function(error) {
                logService.error(LOG_KEY, 'Push notification client service initialization failed: ', JSON.stringify(error));
                state.service = SERVICE_STATE_ENUM.INITIAL;
                initPromise.reject(error);
            };

            initServiceAsync().then(onInitServiceResolved, onInitServiceRejected);
            return initPromise.promise;
        }

        function initServiceAsync() {
            if (state.service === SERVICE_STATE_ENUM.INITIALIZED) {
                logService.debug(LOG_KEY, 'Push notification client service already initialized. Skipping..');
                return $q.resolve();
            }

            logService.debug(LOG_KEY, 'Initializing push notification client service');
            state.service = SERVICE_STATE_ENUM.INITIALIZING;
            subscribeToServiceEvents();

            var deferred = $q.defer();
            var promiseArray = [];

            promiseArray.push(checkIsPushNotificationServiceSupportedAsync());
            promiseArray.push(checkIsHeartbeatFeatureSupportedAsync());
            promiseArray.push(deviceTokenService.isConstellationTokensProcessed());

            $q.all(promiseArray).then(deferred.resolve, deferred.reject);

            return deferred.promise;
        }

        function checkIsPushNotificationServiceSupportedAsync() {
            var deferred = $q.defer();
            logService.debug(LOG_KEY, 'Checking if push notification service is supported');
            isPushNotificationServiceSupportedAsync().then(function(isSupported) {
                isPushNotificationServiceSupported = isSupported;
                deferred.resolve(isSupported);
                logService.debug(LOG_KEY, 'Push notification service supported: ', isSupported);
            }, function(error) {
                logService.error('Error checking push notification service support: ', JSON.stringify(error));
                deferred.reject(error);
            });
            return deferred.promise;
        }

        function isPushNotificationServiceSupportedAsync() {
            // versionUtil.isVersionSupported returns promise
            return versionUtil.isVersionSupported(SUPPORTED_VERSIONS.PUSH_NOTIFICATION_SERVICE, LOG_KEY);
        }

        function checkIsHeartbeatFeatureSupportedAsync() {
            var deferred = $q.defer();
            logService.debug(LOG_KEY, 'Checking if heartbeat feature is supported');
            isHeartbeatSupportedAsync().then(function(isSupported) {
                isHeartbeatFeatureSupported = isSupported;
                deferred.resolve(isSupported);
                logService.debug(LOG_KEY, 'Heartbeat supported: ', isSupported);
            }, function(error) {
                logService.error('Error checking heartbeat feature support: ', JSON.stringify(error));
                deferred.reject(error);
            });
            return deferred.promise;
        }

        function isHeartbeatSupportedAsync() {
            // versionUtil.isVersionSupported returns promise
            return versionUtil.isVersionSupported(SUPPORTED_VERSIONS.HEARTBEAT_FEATURE, LOG_KEY);
        }

        function getDomainAsync() {
            var deviceTokens = getTokens();
            if (!deviceTokens) {
                var message = 'Missing device constellation tokens. Cannot get domain';
                logService.error(LOG_KEY, message);
                return $q.reject(message);
            }

            var deferred = $q.defer();
            authenticationLibraryService.getConstellationBaseUrl(deviceTokens).then(function(baseUrl) {
                if (!baseUrl) {
                    deferred.reject('Service API URL is undefined');
                    return;
                }

                var url = new URL(baseUrl);

                deferred.resolve(url.hostname);
            }, deferred.reject);

            return deferred.promise;
        }

        function getTokens() {
            return deviceTokenService.getConstellationTokens();
        }

        function subscribeToServiceEvents() {
            deviceTokenService.subscribe(onDeviceTokenEvent);
            configurationService.subscribe(onPlayerPropertiesUpdated);
        }

        function unsubscribeFromServiceEvents() {
            deviceTokenService.unsubscribe(onDeviceTokenEvent);
            configurationService.unsubscribe(onPlayerPropertiesUpdated);
        }

        // MARK: SERVICE EVENT HANDLERS
        function onDeviceTokenEvent(event) {
            logService.debug(LOG_KEY, 'Device token event: ', JSON.stringify(event));

            if (!event || !event.tokens || !event.type) {
                logService.error(LOG_KEY, 'Invalid device token event: ', JSON.stringify(event));
                return;
            }

            var eventType = event.type.toLowerCase();
            switch (eventType) {
                case CONSTELLATION_TOKENS_EVENT.REFRESHED:
                case CONSTELLATION_TOKENS_EVENT.CLAIMED:
                    logService.debug(LOG_KEY, 'Token ' + eventType + '. Client state: ' + state.pnsClient);
                    onTokenRefreshed(event.tokens);
                    break;
                default:
                    logService.warn(LOG_KEY, 'Unhandled device token event type: ', event.type);
                    break;
            }
        }

        function onPlayerPropertiesUpdated(properties) {
            logService.debug(LOG_KEY, 'Player properties updated: ', JSON.stringify(properties));

            if (processIdentityAttributes(properties)) {
                updatePnsClientIdentity();
            }
        }

        function notifySubscribers(event, data) {
            for (var i = 0; i < subscribers.length; i++) {
                subscribers[i](event, data);
            }
        }

        /**
         * When token refreshed
         * @param { {accessToken: string, idToken: string} } tokens
         */
        function onTokenRefreshed(tokens) {
            if (!tokens || !tokens.accessToken || !tokens.idToken) {
                logService.error(LOG_KEY, 'Invalid token: ', JSON.stringify(tokens));
                return;
            }

            if (pnsClientConfig.accessToken === tokens.accessToken && pnsClientConfig.idToken === tokens.idToken) {
                logService.debug(LOG_KEY, 'Token is not changed. Skipping update');
                return;
            }

            deviceTokens = tokens;
            pnsClientConfig.accessToken = deviceTokens.accessToken;
            pnsClientConfig.idToken = deviceTokens.idToken;

            if (state.service === SERVICE_STATE_ENUM.INITIAL || state.pnsClient === PNS_CLIENT_STATE_ENUM.INITIAL) {
                start();
                return;
            }

            // Only update client identity if client is initialized
            if (!pnsClient) {
                logService.info(LOG_KEY, 'Token received but push notification client not initialized. Only update local PNS config');
                return;
            }

            updatePnsClientIdentity();
        }

        /**
         * Massage identity attributes
         * @param {*} properties
         * @return {boolean} true if identity attributes are changed
         */
        function processIdentityAttributes(properties) {
            var previousIdentityAttributes = $angular.copy(pnsClientConfig.identityAttrs);
            properties = properties || {};
            pnsClientConfig.identityAttrs = [];
            addIdentityAttribute(IDENTITY_ATTRIBUTES.NETWORK_ID, properties[PLAYER_PROPERTY_KEYS.NETWORK_ID]);
            addIdentityAttribute(IDENTITY_ATTRIBUTES.NETWORK_ID, properties[PLAYER_PROPERTY_KEYS.NETWORK_ANCESTORS_ID]);
            addIdentityAttribute(IDENTITY_ATTRIBUTES.DEVICE_TAGS, properties[PLAYER_PROPERTY_KEYS.DEVICE_TAGS]);
            addIdentityAttribute(IDENTITY_ATTRIBUTES.DEVICE_GROUP_ID, properties[PLAYER_PROPERTY_KEYS.DEVICE_GROUP_ID]);
            addIdentityAttribute(IDENTITY_ATTRIBUTES.DEVICE_GROUP_ID, properties[PLAYER_PROPERTY_KEYS.DEVICE_GROUP_ANCESTORS_ID]);
            addIdentityAttribute(IDENTITY_ATTRIBUTES.DEVICE_TYPE, properties[PLAYER_PROPERTY_KEYS.DEVICE_TYPE]);
            addIdentityAttribute(IDENTITY_ATTRIBUTES.SUBJECT_ID, properties[PLAYER_PROPERTY_KEYS.DEVICE_ID] || deviceId);
            addIdentityAttribute(IDENTITY_ATTRIBUTES.SUBJECT_TYPE, DEVICE_SUBJECT_TYPE);
            addIdentityAttribute(IDENTITY_ATTRIBUTES.CURRENT_NETWORK_ID, properties[PLAYER_PROPERTY_KEYS.NETWORK_ID]);
            addIdentityAttribute(IDENTITY_ATTRIBUTES.CHANNEL_PUBLISHING_MODE, properties[PLAYER_PROPERTY_KEYS.CHANNEL_PUBLISHING_MODE]);

            // Add topic: channel.{channelId}
            var channelId = properties[PLAYER_PROPERTY_KEYS.CHANNEL_ID];
            if (channelId) {
                addValueToTopicAttribute(composeTopicValue(PNS_TOPIC_ATTRIBUTE.CHANNEL, channelId));
            }

            for (var topic in topicAttribute) {
                for (var i = 0; i < topicAttribute[topic].length; i++) {
                    // Add topic: post.{postId}
                    addValueToTopicAttribute(composeTopicValue(topic, topicAttribute[topic][i]));
                }
            }

            if ($angular.equals(previousIdentityAttributes, pnsClientConfig.identityAttrs)) {
                logService.debug(LOG_KEY, 'Identity attributes are not changed: ', JSON.stringify(previousIdentityAttributes));
                return false;
            }

            logService.debug(LOG_KEY, 'Identity attributes are changed: ', JSON.stringify(previousIdentityAttributes), ' -> ', JSON.stringify(pnsClientConfig.identityAttrs));

            return true;
        }

        function addIdentityAttribute(key, value) {
            if (!value) {
                return;
            }

            var deviceIdentity = $filter('filter')(pnsClientConfig.identityAttrs, {'propertyName': key})[0];
            if (!deviceIdentity) {
                pnsClientConfig.identityAttrs.push({
                    propertyName: key,
                    value: value.split(',')
                });
                return;
            }

            deviceIdentity.value = deviceIdentity.value.concat(value.split(','));
        }

        /**
         * Upsert topic to local identity attribute
         * @param {*} value "channel.{channelId}" or "post.{postId}"
         */
        function addValueToTopicAttribute(value) {
            var topicAttribute = $filter('filter')(pnsClientConfig.identityAttrs, {'propertyName': IDENTITY_ATTRIBUTES.TOPIC})[0];
            if (!topicAttribute) {
                pnsClientConfig.identityAttrs.push({
                    propertyName: IDENTITY_ATTRIBUTES.TOPIC,
                    value: [value]
                });
                return;
            }

            topicAttribute.value.push(value);
        }

        function composeTopicValue(topic, value) {
            return topic + PNS_TOPIC_ATTRIBUTE.SEPARATOR + value;
        }

        // MARK: PNS CLIENT
        function initClientAsync() {
            if (pnsClient) {
                logService.debug(LOG_KEY, 'Push notification client already initialized');
                return $q.resolve();
            }

            logService.debug(LOG_KEY, 'Initializing push notification client');
            state.pnsClient = PNS_CLIENT_STATE_ENUM.INITIALIZING;

            var deferred = $q.defer();

            var onGetDomainResolved = function(domain) {
                if (!domain) {
                    deferred.reject('Push notification client service domain is undefined');
                    return;
                }

                pnsClientConfig.domain = domain;
                // Need to get again because the token might refreshed while waiting for domain to resolve
                deviceTokens = getTokens();
                if (!deviceTokens) {
                    logService.error(LOG_KEY, 'Missing device constellation tokens');
                    return $q.reject('Missing device constellation tokens');
                }

                processIdentityAttributes(syncService.getCurrentAppProperties());
                pnsClientConfig.accessToken = deviceTokens.accessToken;
                pnsClientConfig.idToken = deviceTokens.idToken;
                pnsClientConfig.customMessageProcessor = halPlatformService.getPnsMessageProcessor();
                pnsClientConfig.keepAliveOptions.isEnabled = isHeartbeatFeatureSupported;

                if (!$window || !$window.PNSClient || !$window.PNSClient.PnsClient) {
                    deferred.reject('PNSClient.PnsClient not found');
                    return;
                }

                try {
                    logService.debug(LOG_KEY, 'Creating PnsClient with config: ', JSON.stringify(pnsClientConfig));
                    pnsClient = new $window.PNSClient.PnsClient(pnsClientConfig);
                } catch (error) {
                    logService.error(LOG_KEY, 'Error creating PnsClient: ', error);
                    deferred.reject(error);
                    return;
                }

                subscribeToPnsClientEvents();
                deferred.resolve();
                return;
            };

            var onGetDomainRejected = function(error) {
                logService.error(LOG_KEY, 'Error getting domain: ', error);
                deferred.reject(error);
            };

            getDomainAsync().then(onGetDomainResolved, onGetDomainRejected);

            return deferred.promise;
        }

        function startPnsClient() {
            if (!pnsClient) {
                logService.error(LOG_KEY, 'Push notification client is not initialized');
                return;
            }

            if (!isPnsClientConfigValidToStart()) {
                logService.info(LOG_KEY, 'Push notification client configuration is not valid to start: ', JSON.stringify(pnsClientConfig));
                return;
            }

            logService.debug(LOG_KEY, 'Connecting push notification client');
            state.pnsClient = PNS_CLIENT_STATE_ENUM.CONNECTING;
            try {
                if (pnsClient.start()) {
                    logService.info(LOG_KEY, 'Push notification client started');
                } else {
                    logService.error(LOG_KEY, 'Push notification client failed to start');
                }
            } catch (error) {
                logService.error(LOG_KEY, 'Error connecting push notification client: ', error.toString());
            }
        }

        function stopPnsClient() {
            if (!pnsClient || state.pnsClient === PNS_CLIENT_STATE_ENUM.CLOSED) {
                logService.debug(LOG_KEY, 'Push notification client is already closed');
                stopClientPromise = undefined;
                return $q.resolve();
            }

            stopClientPromise = $q.defer();
            pnsClient.stop();
            logService.info(LOG_KEY, 'Closing push notification client');
            state.pnsClient = PNS_CLIENT_STATE_ENUM.CLOSING;

            return stopClientPromise.promise;
        }

        function isPnsClientConfigValidToStart() {
            if (!pnsClientConfig.domain) {
                logService.warn(LOG_KEY, 'Domain is not set. ' + JSON.stringify(pnsClientConfig));
                return false;
            }

            if (!pnsClientConfig.accessToken) {
                logService.warn(LOG_KEY, 'Access token is not set' + JSON.stringify(pnsClientConfig));
                return false;
            }

            if (!pnsClientConfig.idToken) {
                logService.warn(LOG_KEY, 'Id token is not set' + JSON.stringify(pnsClientConfig));
                return false;
            }

            return true;
        }

        function subscribeToPnsClientEvents() {
            if (!pnsClient) {
                logService.error(LOG_KEY, 'Push notification client is not initialized');
                return;
            }

            logService.debug(LOG_KEY, 'Subscribing to push notification client events');

            for (var event in PNS_CLIENT_EVENT_HANDLERS) {
                if (PNS_CLIENT_EVENT_HANDLERS.hasOwnProperty(event)) {
                    pnsClient.on(event, PNS_CLIENT_EVENT_HANDLERS[event]);
                }
            }
        }

        function unsubscribeFromPnsClientEvents() {
            if (!pnsClient) {
                logService.error(LOG_KEY, 'Push notification client is not initialized');
                return;
            }

            logService.debug(LOG_KEY, 'Unsubscribing from push notification client events');

            for (var event in PNS_CLIENT_EVENT_HANDLERS) {
                if (PNS_CLIENT_EVENT_HANDLERS.hasOwnProperty(event)) {
                    pnsClient.off(event, PNS_CLIENT_EVENT_HANDLERS[event]);
                }
            }
        }

        /**
         * Expect tokens, and attributes has updated to config
         * @return {void}
         */
        function updatePnsClientIdentity() {
            logService.info(LOG_KEY, 'Updating push notification client configuration: ', JSON.stringify(pnsClientConfig));
            if (!pnsClient || !pnsClient.cmdSetIdentity) {
                logService.error(LOG_KEY, 'Push notification client cmdSetIdentity not found');
                return;
            }

            var data = {
                accessToken: pnsClientConfig.accessToken,
                idToken: pnsClientConfig.idToken,
                identityAttrs: pnsClientConfig.identityAttrs,
            };

            var onCmdSetIdentityResolved = function() {
                logService.debug(LOG_KEY, 'Push notification client cmdSetIdentity resolved: ', JSON.stringify(data));
                if (state.pnsClient === PNS_CLIENT_STATE_ENUM.CONNECTED) {
                    return;
                }

                logService.info(LOG_KEY, 'Push notification client cmdSetIdentity success. Client state: ' + state.pnsClient + '. Starting the client');
                startPnsClient();
            };

            var onCmdSetIdentityRejected = function(error) {
                logService.error(LOG_KEY, 'Push notification client cmdSetIdentity failed: ', JSON.stringify(error));
            };

            pnsClient.cmdSetIdentity(data).then(onCmdSetIdentityResolved, onCmdSetIdentityRejected);
        }

        function calculateConnectionDuration() {
            if (!connectionAudit.connection.connectedAt || !connectionAudit.connection.disconnectedAt) {
                return;
            }

            connectionAudit.connection.duration = Math.abs(dateTimeUtil.getSecondsDifference(
                connectionAudit.connection.connectedAt, connectionAudit.connection.disconnectedAt));
        }

        /**
         * Update PNS client connected timestamp
        */
        function updateConnectionAuditConnectedAt() {
            connectionAudit.connection.connectedAt = moment().toISOString();
        }

        /**
         * Reset connection audit to PNS client initial state
        */
        function unsetConnectionAudit() {
            connectionAudit = $angular.copy(INITIAL_CONNECTION_AUDIT);
        }

        function reportConnectionTelemetry(telemetry) {
            telemetryService.addToQueue({
                module: 'device-push-notification',
                type: telemetry.type,
                description: telemetry.description,
                metadata: $angular.copy(connectionAudit),
            });
        }

        function addEventToConnectionHistory(message) {
            connectionAudit.history.push({
                timestamp: moment().toISOString(),
                state: $angular.copy(state),
                message: message,
            });
        }

        function restartConnectionAudit(event) {
            unsetConnectionAudit();
            updateConnectionAuditConnectedAt();
            addEventToConnectionHistory(event);
        }

        // MARK: PNS CLIENT EVENT HANDLERS
        function onConnected(isReconnect) {
            logService.info(LOG_KEY, 'Push notification client connected. isReconnect:', isReconnect);
            state.pnsClient = PNS_CLIENT_STATE_ENUM.CONNECTED;
            if (isReconnect) {
                notifySubscribers(PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.CLIENT_RECONNECTED);
                reportConnectionTelemetry({
                    type: 'pns-reconnect',
                    description: 'Reconnection of web socket connection to Push Notification Service',
                });
                restartConnectionAudit('Push notification client reconnected');
                return;
            }

            resetWatchdogTimeout('client connected');
            restartConnectionAudit('Push notification client connected');
            notifySubscribers(PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.CLIENT_CONNECTED, {
                client: pnsClient,
                domain: pnsClientConfig.domain,
                tokens: deviceTokens,
                deviceId: deviceId || deviceRegistrationService.getPlayerId(),
                topicSubscription: {
                    on: subscribeToTopic,
                    off: unsubscribeFromTopic
                }
            });

            if (processIdentityAttributes(syncService.getCurrentAppProperties())) {
                updatePnsClientIdentity();
            }
        }

        function onClosed(closureCode) {
            state.pnsClient = PNS_CLIENT_STATE_ENUM.CLOSED;
            notifySubscribers(PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.CLIENT_CLOSED, {code: closureCode});
            addEventToConnectionHistory('Push notification client closed due to code: ' + closureCode);
            if (stopClientPromise) {
                stopClientPromise.resolve();
                stopClientPromise = undefined;
            }

            switch (closureCode) {
                case CLOSURE_CODE.MANUAL_CLOSURE:
                    logService.info(LOG_KEY, 'Push notification client manually closed');
                    break;
                case CLOSURE_CODE.AUTHENTICATION_FAILED:
                case CLOSURE_CODE.IDENTITY_FAILED:
                    logService.error(LOG_KEY, 'Push notification client closed due to ' + closureCode + '. Retry after refresh the tokens.');

                    var onRefreshCompleted = function(tokens) {
                        logService.debug(LOG_KEY, 'Token refreshed. Client state: ' + state.pnsClient);
                        onTokenRefreshed(tokens);
                    };

                    var onRefreshFailed = function(error) {
                        logService.error(LOG_KEY, 'Push notification client failed to refresh tokens: ', error);
                    };

                    deviceTokenService.refreshConstellationTokens().then(onRefreshCompleted, onRefreshFailed);
                    resetWatchdogTimeout('client closed');
                    break;
                default:
                    logService.info(LOG_KEY, 'Push notification client closed with code ', closureCode);
                    break;
            }
        }

        function onError(error) {
            var message = 'Push notification client error: ' + JSON.stringify(error);
            logService.error(LOG_KEY, message);
            if (connectionAudit.connection.disconnectedAt) {
                return;
            }

            // Record potential error event that might lead to PNS disconnection
            addEventToConnectionHistory(message);
        }

        function onReconnecting(eventCode) {
            logService.debug(LOG_KEY, 'Push notification client reconnecting due to event code ' + eventCode);
            state.pnsClient = PNS_CLIENT_STATE_ENUM.RECONNECTING;
            notifySubscribers(PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.CLIENT_RECONNECTING);
            resetWatchdogTimeout('reconnecting event code: ' + eventCode);
        }

        function onHeartbeat(event) {
            if (!event || !event.status) {
                logService.error(LOG_KEY, 'Invalid push notification client heartbeat event: ', JSON.stringify(event));
                return;
            }

            resetWatchdogTimeout('heartbeat ' + event.status);
            switch (event.status) {
                case HEARTBEAT_STATUS.IN_PROGRESS:
                    logService.debug(LOG_KEY, 'Push notification client heartbeat in-progress');
                    break;
                case HEARTBEAT_STATUS.SUCCESS:
                    logService.info(LOG_KEY, 'Push notification client heartbeat success');
                    notifySubscribers(PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.CLIENT_HEARTBEAT_ACKNOWLEDGED, true);
                    break;
                case HEARTBEAT_STATUS.FAILED:
                    var message = 'Push notification client heartbeat failed' + (event.reason ? ' - Reason: ' + event.reason : '');
                    logService.error(LOG_KEY, message);
                    notifySubscribers(PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.CLIENT_HEARTBEAT_ACKNOWLEDGED, false);
                    addEventToConnectionHistory(message);
                    break;
            }
        }

        function onMessage(event) {
            if (!event || !event.payload) {
                logService.error(LOG_KEY, 'Invalid push notification client message event: ', JSON.stringify(event));
                return;
            }

            var logLevel = PNS_MESSAGES_TO_LOG_AS_DEBUG.indexOf(event.payload.message) > -1 ? 'debug' : 'info';
            logService[logLevel](LOG_KEY, 'Push notification client message: ', JSON.stringify(event));

            if (!event.payload.Topic) {
                return;
            }

            var topicKey = event.payload.Topic;
            if (!dataUtil.isString(topicKey)) {
                return;
            }

            if (notifyTopicSubscribers(topicKey, event)) {
                return;
            }

            var topicBreakdown = topicKey.split('.');
            if (topicBreakdown.length < 5) {
                return;
            }

            var topic = {
                targetId: topicBreakdown[0],
                targetType: topicBreakdown[1],
                hash: topicBreakdown[2],
                key: topicBreakdown[3],
                event: topicBreakdown[4]
            };
            event.topic = topic;

            notifyTopicSubscribers(topic.key, event);
            resetWatchdogTimeout('message event');
        }

        function onDebug(data) {
            logService.debug(LOG_KEY, 'Push notification client debug: ', JSON.stringify(data));
        }

        function onConnecting(isReconnect) {
            logService.debug(LOG_KEY, 'Push notification client connecting: ', JSON.stringify(isReconnect));
            resetWatchdogTimeout('connecting event: (isReconnect-' + isReconnect + ')');
        }

        function onDisconnected(data) {
            var message = 'Push notification client disconnected/disrupted: ' + JSON.stringify(data);
            logService.debug(LOG_KEY, message);
            state.pnsClient = PNS_CLIENT_STATE_ENUM.DISCONNECTED;
            notifySubscribers(PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.CLIENT_DISCONNECTED, data);
            if (connectionAudit.connection.disconnectedAt) {
                return;
            }

            // Only record the first disconnected event since last connected
            // to prevent inaccuracy disconnected timestamp
            connectionAudit.connection.disconnectedAt = moment().toISOString();
            calculateConnectionDuration();
            addEventToConnectionHistory(message);
        }

        function onState(state) {
            logService.debug(LOG_KEY, 'Push notification client state: ', JSON.stringify(state));
        }

        function onIdentityUpdated(identity) {
            logService.debug(LOG_KEY, 'Push notification client updated-identity: ', JSON.stringify(identity));
            if (!pnsClient || pnsClient.isConnected) {
                return;
            }

            logService.info(LOG_KEY, 'Push notification client identity updated but not connected.');
            startPnsClient();
        }

        function notifyTopicSubscribers(topic, message) {
            var subscribersToNotify = topicSubscribers[topic];
            if (!subscribersToNotify || subscribersToNotify.length === 0) {
                return false;
            }

            var isNotified = false;
            for (var i = 0; i < subscribersToNotify.length; i++) {
                subscribersToNotify[i](message);
                isNotified = true;
            }

            return isNotified;
        }

        // MARK: WATCHDOG
        function initWatchdog() {
            logService.debug(LOG_KEY, 'Initializing push notification client watchdog');
            populateWatchdogConfig();
            logService.info(LOG_KEY, 'Push notification client watchdog initialized');
            state.watchdog = WATCHDOG_STATE_ENUM.INITIALIZED;
        }

        function populateWatchdogConfig() {
            if (!pnsClientConfig.keepAliveOptions || !pnsClientConfig.keepAliveOptions.isEnabled) {
                logService.debug(LOG_KEY, 'Watchdog is disabled due to keep alive disabled.');
                state.watchdog = WATCHDOG_STATE_ENUM.DISABLED;
                return;
            }

            if (!isWatchdogEnabled()) {
                logService.info(LOG_KEY, 'Watchdog is disabled in player property. Skipping initialization');
                state.watchdog = WATCHDOG_STATE_ENUM.DISABLED;
                watchdogConfig.isEnabled = false;
                return;
            }

            watchdogConfig.isEnabled = true;
            watchdogConfig.timeout = getWatchdogTimeout();
        }

        function isWatchdogEnabled() {
            var value = configurationService.getCurrentProperty(PLAYER_PROPERTY_KEYS.PNS_WATCHDOG_ENABLED);

            // default it to WATCHDOG_DEFAULT_VALUES.ENABLED if not set or invalid value
            if (['true', 'false'].indexOf(dataUtil.toLowerCase(value)) < 0) {
                return WATCHDOG_DEFAULT_VALUES.ENABLED;
            }

            return dataUtil.parseBoolean(value);
        }

        function getWatchdogTimeout() {
            if (watchdogConfig.timeout) {
                return watchdogConfig.timeout;
            }

            return pnsClientConfig.keepAliveOptions.interval +
                pnsClientConfig.keepAliveOptions.timeout +
                HEARTBEAT_DEFAULT_VALUE.RANDOM_BACKOFF_MAX +
                WATCHDOG_DEFAULT_VALUES.BACKOFF;
        }

        function startWatchdog() {
            if (!watchdogConfig.isEnabled) {
                logService.debug(LOG_KEY, 'Push notification client watchdog is disabled. Skipping start');
                return;
            }

            state.watchdog = WATCHDOG_STATE_ENUM.STARTED;
            startWatchdogTimeout();
            logService.info(LOG_KEY, 'Push notification client watchdog started');
            notifySubscribers(PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.WATCHDOG_STARTED);
        }

        function stopWatchdog() {
            state.watchdog = WATCHDOG_STATE_ENUM.STOPPED;
            cancelWatchdogTimeout();
            logService.info(LOG_KEY, 'Push notification client watchdog stopped');
            notifySubscribers(PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.WATCHDOG_STOPPED);
        }

        function startWatchdogTimeout() {
            cancelWatchdogTimeout();
            watchdogTimeout = $timeout(onWatchdogTimeoutElapsed, getWatchdogTimeout());
        }

        function cancelWatchdogTimeout() {
            if (!watchdogTimeout) {
                return;
            }

            watchdogTimeout = $timeout.cancel(watchdogTimeout);
            watchdogTimeout = undefined;
        }

        /**
         * Reset the timeout for the watchdog.
         * @param {string} reason - Reason for resetting the timeout. For example, "heartbeat success", "message event", etc. This is used for logging purposes.
         */
        function resetWatchdogTimeout(reason) {
            if (state.watchdog !== WATCHDOG_STATE_ENUM.STARTED) {
                return;
            }

            logService.debug(LOG_KEY, 'Resetting watchdog timeout', (reason ? 'due to "' + reason : '') + '"');
            cancelWatchdogTimeout();
            startWatchdogTimeout();
        }

        /**
         * Watchdog timeout elapsed indicating abnormality with the client.
         */
        function onWatchdogTimeoutElapsed() {
            var message = 'Watchdog timeout elapsed';
            logService.warn(LOG_KEY, message);
            deviceTokenService.refreshConstellationTokens().then(onTokenRefreshed, function(error) {
                logService.error(LOG_KEY, 'Watchdog elapsed but failed to get updated tokens: ' + JSON.stringify(error));
            });
            notifySubscribers(PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.WATCGDOG_ALERT);
        }

        function resetPnsClientConfig() {
            pnsClientConfig = $angular.copy(PNS_CLIENT_INITIAL_CONFIG);
        }

        return {
            start: start,
            stop: stop,
            restart: restart,
            subscribe: subscribe,
            unsubscribe: unsubscribe,
            subscribeToTopic: subscribeToTopic,
            unsubscribeFromTopic: unsubscribeFromTopic,
            getCurrentState: getCurrentState,
            updateTopicAttribute: updateTopicAttribute,
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('core');

    app.service('pushUpdateService', ["pushNotificationClientService", "dataUtil", "versionUtil", "logService", "PUSH_UPDATE_EVENT", "PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT", function(
        pushNotificationClientService,
        dataUtil,
        versionUtil,
        logService,
        PUSH_UPDATE_EVENT,
        PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT
    ) {
        var NOT_FOUND_INDEX = -1;
        var SUPPORTED_SERVICE_VERSIONS = {
            CLOUD: {
                MIN: '8.7.0-ac.1',
                MAX: '8.7.0'
            },
            GLOBAL: '8.7.0'
        };
        var CHANNEL_EVENT = {
            TIMESTAMP_UPDATED: 'timestampupdated',
            UNASSIGNED: 'unassigned',
            ASSIGNED: 'assigned',
        };
        var DEVICE_EVENT = {
            UNREGISTERED: 'unregistered',
            TAGS_UPDATED: 'devicetagsupdated',
        };
        var LOG_KEY = 'appspace.core.pushupdate';
        var TOPIC = {
            CHANNEL: 'channel',
            DEVICE: 'device',
        };
        var EMPTY_CHANNEL_TIMESTAMP = '';
        var isStarted = false;
        var subscribers = [];

        function onChannelUpdate(update) {
            if (!isPnsTopicUpdateValid(update)) {
                return;
            }

            var shouldNotify = true;
            var subscriberEvent = PUSH_UPDATE_EVENT.TIMESTAMP_UPDATE;
            switch ((update.topic.event || '').toLowerCase()) {
                case CHANNEL_EVENT.ASSIGNED:
                    subscriberEvent = PUSH_UPDATE_EVENT.CHANNEL_ASSIGNED;
                    break;
                case CHANNEL_EVENT.UNASSIGNED:
                    update.payload.Timestamp = EMPTY_CHANNEL_TIMESTAMP;
                    break;
                case CHANNEL_EVENT.TIMESTAMP_UPDATED:
                    if (dataUtil.isNullOrUndefined(update.payload.Timestamp)) {
                        shouldNotify = false;
                        return;
                    }

                    break;
                default:
                    shouldNotify = false;
                    break;
            }

            if (!shouldNotify) {
                return;
            }

            notifySubscribers(subscriberEvent, update.payload.Timestamp, update.payload);
        }

        function isPnsTopicUpdateValid(update) {
            return Boolean(update && update.payload && update.topic);
        }

        function onDeviceUpdate(update) {
            if (!isPnsTopicUpdateValid(update)) {
                return;
            }

            switch ((update.topic.event || '').toLowerCase()) {
                case DEVICE_EVENT.UNREGISTERED:
                    notifySubscribers(PUSH_UPDATE_EVENT.DEVICE_UNREGISTERED, undefined, update.payload);
                    break;
                case DEVICE_EVENT.TAGS_UPDATED:
                    notifySubscribers(PUSH_UPDATE_EVENT.DEVICE_TAGS_UPDATED, update.payload.DeviceTags || [], update.payload);
                    break;
            }
        }

        function onPushNotificationClientEvent(event) {
            var eventMessage = 'push notification client ' + event + ' event';
            switch (event) {
                case PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.CLIENT_RECONNECTED:
                    restart(eventMessage);
                    break;
                case PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.CLIENT_CONNECTED:
                    start();
                    break;
                case PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.CLIENT_DISCONNECTED:
                case PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.CLIENT_CLOSED:
                case PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.SERVICE_STOPPED:
                    stop(eventMessage, false, true);
                    break;
            }
        }

        function isPushUpdateSupported() {
            return versionUtil.isVersionSupported(SUPPORTED_SERVICE_VERSIONS, LOG_KEY);
        }

        function restart(reason) {
            logService.info(LOG_KEY, getActionMessage('Restarting push update service', reason));
            stop(reason, true);
            start();
        }

        function subscribeToPnsUpdates(subscribe) {
            var eventSubscription = subscribe ? 'subscribe' : 'unsubscribe';
            var topicSubscription = subscribe ? 'subscribeToTopic' : 'unsubscribeFromTopic';
            pushNotificationClientService[eventSubscription](onPushNotificationClientEvent);
            pushNotificationClientService[topicSubscription](TOPIC.CHANNEL, onChannelUpdate);
            pushNotificationClientService[topicSubscription](TOPIC.DEVICE, onDeviceUpdate);
        }

        function start() {
            if (isStarted) {
                logService.debug(LOG_KEY, 'Push update service is already running');
                notifySubscribers(PUSH_UPDATE_EVENT.STARTED);
                return;
            }

            isPushUpdateSupported().then(function(isSupported) {
                if (!isSupported) {
                    logService.debug(LOG_KEY, 'Platform version does not meet the minimum push update platform version requirement');
                    return;
                }

                var pnsClientState = pushNotificationClientService.getCurrentState() || {};
                // Check PNS current state to avoid premature "started" notification
                isStarted = pnsClientState.isStarted;
                var event = isStarted ? PUSH_UPDATE_EVENT.STARTED : PUSH_UPDATE_EVENT.INITIALIZED;
                var logMessage = isStarted ? 'Push update service started' : 'Push update service initialized and awaiting push notification client to start';
                notifySubscribers(event);
                logService.info(LOG_KEY, logMessage);
                subscribeToPnsUpdates(true);
            });
        }

        function stop(reason, skipNotify, isRecoverable) {
            if (!isStarted) {
                return;
            }

            isStarted = false;
            logService.info(LOG_KEY, getActionMessage('Push update service stopped', reason));
            if (!isRecoverable) {
                subscribeToPnsUpdates(false);
            }

            if (skipNotify) {
                return;
            }

            notifySubscribers(PUSH_UPDATE_EVENT.STOPPED);
        }

        function getActionMessage(message, reason) {
            return message + (reason ? ' due to ' + reason : '');
        }

        function subscribe(callback) {
            if (!callback || subscribers.indexOf(callback) !== NOT_FOUND_INDEX) {
                return;
            }

            subscribers.push(callback);
        }

        function unsubscribe(callback) {
            var subscriberIndex = subscribers.indexOf(callback);
            if (subscriberIndex === NOT_FOUND_INDEX) {
                return;
            }

            subscribers.splice(subscriberIndex, 1);
        }

        function notifySubscribers(event, data, payload) {
            for (var i = 0; i < subscribers.length; i++) {
                subscribers[i](event, data, payload);
            }
        }

        return {
            start: start,
            stop: stop,
            subscribe: subscribe,
            unsubscribe: unsubscribe
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('core');

    app.service('pwaCacheService', ["$q", "$injector", "UrlUtil", "dataUtil", "logService", "STORAGE_KEY", "STORAGE_COLLECTION_KEY", "FEATURE_CODE", "SYNC_EVENT", "CACHE_COMMAND", function(
        $q,
        $injector,
        UrlUtil,
        dataUtil,
        logService,
        STORAGE_KEY,
        STORAGE_COLLECTION_KEY,
        FEATURE_CODE,
        SYNC_EVENT,
        CACHE_COMMAND
    ) {
        var VIRTUAL_PATH = '/pwa-cache-service';
        var ACCOUNT_ID = '{{ACCOUNT_ID}}';
        var DEVICE_ID = '{{DEVICE_ID}}';
        var LOG_KEY = 'appspace.core.pwacacheservice';
        var INDEX_HTML = 'index.html';
        var CONTENT_PATH = '/content';
        var APPSPACE_USER_CONTENT_DOMAIN = 'appspaceusercontent.com';
        var webApi;
        var coreConfigurationService;
        var deviceStateService;
        var httpWrapper;
        var GET_SERVICE_WORKER_LOCATION_ENDPOINT = '/contents/service-worker?accountId={{ACCOUNT_ID}}&deviceId={{DEVICE_ID}}';
        var isServiceWorkerLoaded = false;
        var halPlatformService;
        var syncService;
        var currentServiceWorkerUrls = [];
        var isReadyDeferred = $q.defer();
        var waitServiceWorkerReadyDeferred = $q.defer();

        function setApi(api) {
            if (!api) {
                return;
            }

            webApi = api;
            isReady().then(start);
            getDeviceStateService().subscribe(onDeviceStateChanged);
            getSyncService().subscribe(onSyncServiceUpdated);
        }

        function onDeviceStateChanged() {
            if (!deviceStateService.isRegistered()) {
                reset();
            }
        }

        function onSyncServiceUpdated(event) {
            var eventName = event.event;

            switch (eventName) {
                case SYNC_EVENT.SCRIPT_READY:
                    return isReadyDeferred.resolve();
                case SYNC_EVENT.SCRIPT_SAVED:
                    start();
                    break;
                default:
                    break;
            }
        }

        function reset() {
            if (!webApi || !webApi.clearWebFrames || !isServiceWorkerLoaded) {
                return;
            }

            webApi.clearWebFrames();
            isServiceWorkerLoaded = false;
            coreConfigurationService.removeConfiguration(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.SERVICE_WORKER_URLS);
        }

        function postMessage(message) {
            if (!webApi || !webApi.postMessage || !message) {
                return;
            }

            webApi.postMessage(message);
        }

        function start() {
            if (!getDeviceStateService().isRegistered() || !webApi || !webApi.createWebFrames) {
                return;
            }

            getServiceWorkerUrls().then(function(urls) {
                if (dataUtil.isArrayIdentical(currentServiceWorkerUrls, urls)) {
                    return;
                }

                reset();
                webApi.createWebFrames(urls);
                currentServiceWorkerUrls = urls;
                isServiceWorkerLoaded = true;
                waitServiceWorkerReadyDeferred.resolve();
            }).catch(function(err) {
                logService.warn(LOG_KEY, 'Failed to load service worker URLs. Reason: ' + JSON.stringify(err));
            });
        }

        function getServiceWorkerUrls() {
            var defer = $q.defer();
            var deviceId = getCoreConfigurationService().get(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.DEVICE_ID);
            var accountId = getAccountId();
            var appSpaceUrl = getAppspaceUrl();
            var savedServiceWorkerUrls = dataUtil.tryParseJSON(coreConfigurationService.get(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.SERVICE_WORKER_URLS));
            var serviceWorkerUrls = [appSpaceUrl + VIRTUAL_PATH];

            if (!accountId || !deviceId) {
                defer.resolve(savedServiceWorkerUrls || serviceWorkerUrls);
                return defer.promise;
            }

            var endpoint = (appSpaceUrl + GET_SERVICE_WORKER_LOCATION_ENDPOINT)
                .replace(ACCOUNT_ID, accountId)
                .replace(DEVICE_ID, deviceId);

            getHttpWrapper().handlerGet(endpoint).then(function(res) {
                if (res && res.data && res.data.serviceWorkerUrls && res.data.serviceWorkerUrls.length) {
                    res.data.serviceWorkerUrls.forEach(function(url) {
                        serviceWorkerUrls.push(url + INDEX_HTML);
                    });
                    coreConfigurationService.set(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.SERVICE_WORKER_URLS, JSON.stringify(serviceWorkerUrls));
                }

                defer.resolve(serviceWorkerUrls);
            }, function(err) {
                logService.warn(LOG_KEY, 'Failed to get service worker URLs. Reason: ' + JSON.stringify(err));
                serviceWorkerUrls = savedServiceWorkerUrls || serviceWorkerUrls;
                defer.resolve(serviceWorkerUrls);
            });

            return defer.promise;
        }

        function getCoreConfigurationService() {
            if (!coreConfigurationService) {
                coreConfigurationService = $injector.get('coreConfigurationService');
            }

            return coreConfigurationService;
        }

        function getDeviceStateService() {
            if (!deviceStateService) {
                deviceStateService = $injector.get('deviceStateService');
            }

            return deviceStateService;
        }

        function getHttpWrapper() {
            if (!httpWrapper) {
                var HttpWrapper = $injector.get('HttpWrapper');
                httpWrapper = new HttpWrapper();
            }

            return httpWrapper;
        }

        function getAppspaceUrl() {
            return UrlUtil.removeTrailingSlash((getCoreConfigurationService().get(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.APPSPACE_URL) || '').toLowerCase());
        }

        function getSyncService() {
            if (!syncService) {
                syncService = $injector.get('syncService');
            }

            return syncService;
        }

        function isTransparentCacheSupported() {
            return getHalPlatformService().getFeatureSupport(FEATURE_CODE.NETWORK_TRANSPARENT_CACHE);
        }

        function clearContentCache(manifest) {
            if (!manifest) {
                return;
            }

            if (!isServiceWorkerLoaded) {
                waitServiceWorkerReady().then(function() {
                    clearContentCache(manifest);
                });
                return;
            }

            var accountId = getAccountId();
            var paths = [];
            currentServiceWorkerUrls.forEach(function(url) {
                var appspaceUserContentUrl = getAppspaceUserContentUrl(url);

                if (!appspaceUserContentUrl) {
                    return;
                }

                for (var mediaId in manifest.contentIdMappings ) {
                    var contentId = manifest.contentIdMappings[mediaId];
                    paths.push({
                        url: appspaceUserContentUrl + CONTENT_PATH + '/' + accountId + '/' + contentId
                    });
                }
            });

            var message = {
                command: CACHE_COMMAND.PURGE,
                usedPaths: manifest.files.concat(paths),
            };

            postMessage(message);
        }

        function clearAllResources() {
            var message = {
                command: CACHE_COMMAND.REMOVE_ALL
            };

            if (!isServiceWorkerLoaded) {
                waitServiceWorkerReady().then(function() {
                    clearAllResources();
                });
                return;
            }

            postMessage(message);
        }

        function getAppspaceUserContentUrl(url) {
            if (!dataUtil.includesString(APPSPACE_USER_CONTENT_DOMAIN, url)) {
                return '';
            }

            return url.substr(0, url.indexOf(CONTENT_PATH));
        }

        function getHalPlatformService() {
            if (!halPlatformService) {
                halPlatformService = $injector.get('halPlatformService');
            }

            return halPlatformService;
        }

        function getAccountId() {
            return getCoreConfigurationService().get(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.ACCOUNT_ID);
        }

        function isReady() {
            return isReadyDeferred.promise;
        }

        function waitServiceWorkerReady() {
            return waitServiceWorkerReadyDeferred.promise;
        }

        return {
            setApi: setApi,
            postMessage: postMessage,
            start: start,
            isTransparentCacheSupported: isTransparentCacheSupported,
            clearContentCache: clearContentCache,
            clearAllResources: clearAllResources
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('core');

    app.service('registrationRestorationService', ["$q", "SYSTEM_DATA", "dataUtil", "versionUtil", "logService", "halPlatformService", "rendezvousService", "HttpWrapper", function(
        $q,
        SYSTEM_DATA,
        dataUtil,
        versionUtil,
        logService,
        halPlatformService,
        rendezvousService,
        HttpWrapper) {
        var httpWrapper = new HttpWrapper();
        var LOG_KEY = 'appspace.core.registrationrestoration';
        var SUPPORTED_RESTORATION_VERSIONS = {
            CLOUD: {
                MIN: '8.6.0-ac.1',
                MAX: '8.6.0'
            },
            GLOBAL: '8.6.0'
        };
        var DEVICE_ID_PLACEHOLDER = '{deviceId}';
        var MAX_RETRY_COUNT = 5;
        var STATUS_CODE = {
            OK: 200,
            BAD_REQUEST: 400,
            UNAUTHORIZED: 401,
            NOT_FOUND: 404
        };
        var FATAL_STATUS_CODES = [STATUS_CODE.BAD_REQUEST, STATUS_CODE.UNAUTHORIZED, STATUS_CODE.NOT_FOUND];
        var restorationRequestApiUrl = '/api/v3/devices/' + DEVICE_ID_PLACEHOLDER + '/restore';

        function isRestorationSupported(appspaceUrl) {
            return versionUtil.isVersionSupported(SUPPORTED_RESTORATION_VERSIONS, LOG_KEY, appspaceUrl);
        }

        function getRegistrationRestoreRequestUrl(restorationInfo) {
            return restorationInfo.apiUrl + restorationRequestApiUrl.replace(DEVICE_ID_PLACEHOLDER, restorationInfo.deviceId);
        }

        function getFingerprint() {
            var deferred = $q.defer();
            var onFailedToRetrieveFingerprint = function(error) {
                logService.warn(LOG_KEY, 'Unable to retrieve fingerprint: ' + error);
                deferred.reject(error);
            };

            halPlatformService.getFingerprint().then(function(response) {
                if (!response || !response.fingerprint) {
                    onFailedToRetrieveFingerprint('No fingerprint');
                    return;
                }

                deferred.resolve(response);
            }, function(error) {
                onFailedToRetrieveFingerprint(JSON.stringify(error));
            });
            return deferred.promise;
        }

        function getRestorationConfiguration() {
            var deferred = $q.defer();
            var config = {
                fingerprint: undefined,
                isSupported: false
            };
            getFingerprint().then(function(result) {
                config.fingerprint = result.fingerprint;
                config.isSupported = result.type === SYSTEM_DATA.FINGERPRINT_TYPE.HARDWARE;
                config.type = result.type;
                deferred.resolve(config);
            }, deferred.reject);
            return deferred.promise;
        }

        function getRegistrationInfo() {
            var deferred = $q.defer();
            var onFailedToRetrieveRegistrationInfo = function(error) {
                logService.info(LOG_KEY, 'Unable to retrieve registration restoration information: ' + error);
                deferred.reject(error);
            };

            getRestorationConfiguration().then(function(config) {
                if (!config.isSupported) {
                    onFailedToRetrieveRegistrationInfo('Device does not support registration restoration');
                    return;
                }

                rendezvousService.getRegistrationInfo(config.fingerprint).then(deferred.resolve, deferred.reject);
            }, deferred.reject);
            return deferred.promise;
        }

        function createRestoreRequest(restorationInfo) {
            var attemptCount = 0;
            var deferred = $q.defer();
            var onFailedToCreateRestoreRequest = function(error, skipRetry) {
                logService.error(LOG_KEY, 'Failed to create registration restore request: ' + error);
                if (skipRetry) {
                    deferred.reject(error);
                    return;
                }

                if (attemptCount >= MAX_RETRY_COUNT) {
                    logService.error(LOG_KEY, 'Attempt to create registration restore request aborted after ' + MAX_RETRY_COUNT + ' attempts');
                    deferred.reject(error);
                    return;
                }

                logService.warn(LOG_KEY, 'Reattempting to create registration restore request (' + attemptCount + ' out of ' + MAX_RETRY_COUNT + ' attempts)');
                doCreateRestoreRequest();
            };

            if (!restorationInfo ||
                !restorationInfo.deviceId ||
                !restorationInfo.apiUrl ||
                !restorationInfo.fingerprint) {
                onFailedToCreateRestoreRequest('Incomplete registration restoration information', true);
                return deferred.promise;
            }

            var doCreateRestoreRequest = function() {
                attemptCount++;
                httpWrapper.handlerPost(
                    getRegistrationRestoreRequestUrl(restorationInfo),
                    undefined,
                    undefined,
                    {
                        headers: {
                            'x-appspace-fingerprint': restorationInfo.fingerprint
                        }
                    }
                ).then(function(response) {
                    response = response || {};
                    if (response.status !== STATUS_CODE.OK) {
                        onFailedToCreateRestoreRequest(JSON.stringify(response));
                        return;
                    }

                    deferred.resolve(response);
                }, function(error) {
                    error = error || {};
                    onFailedToCreateRestoreRequest(
                        JSON.stringify(error),
                        dataUtil.includesValue(error.status, FATAL_STATUS_CODES));
                });
            };

            isRestorationSupported(restorationInfo.apiUrl).then(function(isSupported) {
                if (!isSupported) {
                    onFailedToCreateRestoreRequest('Platform version does not meet the minimum device registration restoration requirement', true);
                    return;
                }

                doCreateRestoreRequest();
            });

            return deferred.promise;
        }

        function deleteRegistrationInfo(deviceId) {
            var deferred = $q.defer();
            var finalize = function(reason, failed) {
                var logMessage = failed ?
                    'Unable to delete registration restoration information: ' + reason :
                    'Registration restoration information deleted successfully.';
                logService.info(LOG_KEY, logMessage);
                deferred.resolve(logMessage);
            };

            if (!deviceId) {
                finalize('Missing device ID', true);
                return deferred.promise;
            }

            rendezvousService.deleteRegistrationInfo(deviceId).then(finalize, function(error) {
                finalize(error, true);
            });
            return deferred.promise;
        }

        function updateRegistrationInfo(registrationInformation) {
            var deferred = $q.defer();
            var onFailure = function(error) {
                logService.warn(LOG_KEY, 'Failed to update registration information due to: ' + error);
                deferred.reject(error);
            };

            getRestorationConfiguration().then(function(config) {
                if (!config.isSupported) {
                    onFailure('Device does not support registration restoration');
                    return;
                }

                rendezvousService.updateRegistrationInfo(config, registrationInformation).then(deferred.resolve, function(error) {
                    onFailure(JSON.stringify(error));
                });
            }, function() {
                onFailure('Fingerprint information retrieval failure');
            });
            return deferred.promise;
        }

        return {
            getRegistrationInfo: getRegistrationInfo,
            createRestoreRequest: createRestoreRequest,
            updateRegistrationInfo: updateRegistrationInfo,
            deleteRegistrationInfo: deleteRegistrationInfo
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('core');
    app.factory('serviceWorkerMessageChannel', ["$timeout", "$window", "CACHE_COMMAND", "pwaCacheService", function($timeout, $window, CACHE_COMMAND, pwaCacheService) {
            var channel;
            // 5 minutes to check the message port is alive
            var CHECK_ALIVE_TIMEOUT_DURATION = 300000;
            var ACK_ALIVE_TIMEOUT_DURATION = 5000;
            var subscribers = [];
            var NOT_FOUND_INDEX = -1;

            function initialize() {
                var ackLiveFlag = false;
                var sendAliveTimeout;
                var controller = getServiceWorkerController();

                var assignPortMessage = {
                    command: CACHE_COMMAND.ASSIGN_MESSAGE_PORT
                };
                var aliveMessage = {
                    command: CACHE_COMMAND.KEEP_ALIVE
                };

                if (!controller.postMessage) {
                    return;
                }

                function checkMessageChannelAlive() {
                    if (sendAliveTimeout) {
                        $timeout.cancel(sendAliveTimeout);
                        sendAliveTimeout = undefined;
                    }

                    sendAliveTimeout = $timeout(function() {
                        ackLiveFlag = false;
                        channel.port1.postMessage(aliveMessage);

                        $timeout(function() {
                            if (ackLiveFlag) {
                                return;
                            }

                            initialize();
                        }, ACK_ALIVE_TIMEOUT_DURATION);
                    }, CHECK_ALIVE_TIMEOUT_DURATION);
                }

                channel = new $window.MessageChannel();
                channel.port1.onmessage = function(event) {
                    var data = (event || {}).data || {};

                    if (!data.command) {
                        return;
                    }

                    switch (data.command) {
                        case CACHE_COMMAND.ACK_ALIVE:
                            ackLiveFlag = true;
                            break;
                    }

                    notifySubscribers(data);
                    checkMessageChannelAlive();
                };

                controller.postMessage(assignPortMessage, [channel.port2]);
                checkMessageChannelAlive();
            }

            function getServiceWorkerController() {
                var serviceWorker = $window.navigator.serviceWorker || {};
                return serviceWorker.controller || {};
            }

            function notifySubscribers(data) {
                for (var i = 0; i < subscribers.length; i++) {
                    subscribers[i](data);
                }
            }

            function subscribe(callback) {
                if (!callback || subscribers.indexOf(callback) !== NOT_FOUND_INDEX) {
                    return;
                }

                subscribers.push(callback);
            }

            function sendServiceWorkerMessage(message) {
                if (!channel || !channel.port1 || !message) {
                    return;
                }

                channel.port1.postMessage(message);
                pwaCacheService.postMessage(message);
            }

        return {
            initialize: initialize,
            subscribe: subscribe,
            sendServiceWorkerMessage: sendServiceWorkerMessage
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('core');

    app.service('socialService', ["$q", "versionUtil", "RegistrationUtil", "authenticationLibraryService", "pushNotificationClientService", "logService", "deviceTokenService", "embedProfileService", "UrlUtil", "SOCIAL_BAR_EVENT", "configurationService", "PLAYER_PROPERTY_KEYS", "dataUtil", "PNS_TOPIC_ATTRIBUTE", "PNS_TOPIC_KEY", "PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT", function(
        $q,
        versionUtil,
        RegistrationUtil,
        authenticationLibraryService,
        pushNotificationClientService,
        logService,
        deviceTokenService,
        embedProfileService,
        UrlUtil,
        SOCIAL_BAR_EVENT,
        configurationService,
        PLAYER_PROPERTY_KEYS,
        dataUtil,
        PNS_TOPIC_ATTRIBUTE,
        PNS_TOPIC_KEY,
        PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT
    ) {
        var LOG_KEY = 'appspace.core.socialservice';
        var SUPPORTED_SERVICE_VERSIONS = {
            CLOUD: {
                MIN: '8.7.0-ac.1',
                MAX: '8.7.0'
            },
            GLOBAL: '8.7.0'
        };
        var LIMIT_REACTION_SUMMARY_REQUEST = 500;
        var DEFAULT_REACTION_SUMMARY_API_PATH_PREFIX = '/api/v3/contentfeed';
        var DEFAULT_REACTION_SUMMARY_API_PATH_POSTFIX = '/posts/engagement/search';
        var NOT_FOUND_INDEX = -1;
        var ACTION_LINK_METHOD_QR = 'qr';
        var reactionSummaryApiUrl = DEFAULT_REACTION_SUMMARY_API_PATH_PREFIX + DEFAULT_REACTION_SUMMARY_API_PATH_POSTFIX;
        var retrievePostSummaryDefer = $q.defer();
        var postList = {};
        var subscribers = [];
        var currentPost;
        var SOCIAL_TOPIC = 'social';
        var currentPostId = '';
        var memoryScheduledScript;
        var isStarted = false;

        function isReactionSupported() {
            return versionUtil.isVersionSupported(SUPPORTED_SERVICE_VERSIONS, LOG_KEY);
        }

        function subscribe(callback) {
            if (!callback || subscribers.indexOf(callback) !== NOT_FOUND_INDEX) {
                return;
            }

            subscribers.push(callback);
        }

        function unsubscribe(callback) {
            var subscriberIndex = subscribers.indexOf(callback);
            if (subscriberIndex === NOT_FOUND_INDEX) {
                return;
            }

            subscribers.splice(subscriberIndex, 1);
        }

        function notifySubscribers(event, data) {
            if (dataUtil.isObjectEqual(currentPost, data)) {
                return;
            }

            currentPost = data;
            for (var i = 0; i < subscribers.length; i++) {
                subscribers[i](event, data);
            }
        }

        function calculateTotalAmount(counters) {
            var totalCounter = 0;
            for (var i = 0; i < counters.length; i++) {
                totalCounter += counters[i].count;
            }

            return totalCounter;
        }

        function disableReactionContent() {
            notifySubscribers(SOCIAL_BAR_EVENT.POST_UPDATE, {
                isReactionEnabled: false
            });
        }

        function getReactionIconOrder(reactionCounters) {
            var reactionIconOrder = [];
            for (var i = 0; i < reactionCounters.length; i++) {
                reactionIconOrder.push(reactionCounters[i].reactionType);
            }

            return reactionIconOrder;
        }

        function setCurrentPost(postId) {
            isPostSummaryRetrieved().then(function() {
                if (!postList[postId]) {
                    return;
                }

                currentPostId = postId;
                notifySubscribers(SOCIAL_BAR_EVENT.POST_UPDATE, postList[currentPostId]);
            }, $angular.noop);
        }

        function getCurrentPost() {
            return {
                id: currentPostId,
                post: currentPost,
            };
        }

        function processPostsSummary(postsSummary) {
            for (var i = 0; i < postsSummary.length; i++) {
                var post = postsSummary[i];
                upsertPostContent(post);
            }
        }

        function updateUserReactionCounters(counters, userReactions) {
            if (!userReactions) {
                return counters;
            }

            for (var k = 0; k < counters.length; k++) {
                counters[k].users = [];
            }

            for (var i = 0; i < userReactions.length; i++) {
                var reaction = userReactions[i];
                var reactionType = reaction.reactionType;
                for (var j = 0; j < counters.length; j++) {
                    var counter = counters[j];
                    if (counter.reactionType !== reactionType) {
                        continue;
                    }

                    counter.users.push(reaction);
                }
            }

            return counters;
        }

        function upsertPostContent(post) {
            if (!post) {
                return;
            }

            var reactionCounters = updateUserReactionCounters(post.reactionSummary.counters, post.reactions);
            var totalReactionCount = calculateTotalAmount(reactionCounters);
            var originalQrCodeLink = postList[post.postId] && postList[post.postId].qrcodeLink ? postList[post.postId].qrcodeLink : '';
            var qrcodeLink = dataUtil.isNullOrUndefined(post.actionLinkUrl) ? originalQrCodeLink : processActionLink(post);
            var isReactionEnabled = dataUtil.isNullOrUndefined(post.isEnabled) ? postList[post.postId].isReactionEnabled : post.isEnabled;

            // update reactionArragement
            var postContent = {
                id: post.postId,
                isReactionBarEnabled: isReactionEnabled || Boolean(qrcodeLink),
                isReactionEnabled: isReactionEnabled,
                qrcodeLink: qrcodeLink,
                reactionCount: totalReactionCount,
                commentCount: post.commentSummary.totalCount,
                reactionIconOrder: getReactionIconOrder(reactionCounters),
                reactionCounters: reactionCounters,
            };
            postList[post.postId] = postContent;
        }

        function isPostSummaryRetrieved() {
            return retrievePostSummaryDefer.promise;
        }

        function retrievePostSummary(postIds) {
            var promises = [];
            var postSummaries = [];
            retrievePostSummaryDefer = $q.defer();
            postList = {};
            deviceTokenService.isConstellationTokensProcessed().then(function() {
                var tokens = RegistrationUtil.getRegistrationInfo().tokens || {};

                // limit payload postIds array size to 500
                for (var i = 0; i < postIds.length; i += LIMIT_REACTION_SUMMARY_REQUEST) {
                    var nextPostIds = postIds.slice(i, i + LIMIT_REACTION_SUMMARY_REQUEST);
                    var payload = {
                        postIds: nextPostIds
                    };
                    var promise = authenticationLibraryService.requestConstellationService(
                        reactionSummaryApiUrl, tokens, {
                        method: 'POST',
                        data: JSON.stringify(payload),
                        contentType: 'application/json',
                    }
                    );
                    promises.push(promise);
                }

                $q.allSettled(promises).then(function(results) {
                    for (var i = 0; i < results.length; i++) {
                        if (results[i].state !== 'fulfilled') {
                            retrievePostSummaryDefer.reject(results[i].reason);
                            return;
                        }

                        if (!results[i].value || !results[i].value.posts) {
                            retrievePostSummaryDefer.reject('Invalid response');
                            return;
                        }

                        postSummaries = postSummaries.concat(results[i].value.posts);
                    }

                    logService.info(LOG_KEY, 'Successfully retrieved post summary');
                    pushNotificationClientService.updateTopicAttribute(PNS_TOPIC_ATTRIBUTE.POST, postIds);
                    retrievePostSummaryDefer.resolve(postSummaries);
                }, retrievePostSummaryDefer.reject);
            }, retrievePostSummaryDefer.reject);

            return retrievePostSummaryDefer.promise;
        }

        function processPushNotificationPostContent(post) {
            var topic = post.topic;
            if (!dataUtil.isString(topic)) {
                return post;
            }

            var topicBreakdown = topic.split('.');
            if (topicBreakdown.length < 5) {
                return post;
            }

            topic = {
                accountId: topicBreakdown[0],
                targetType: topicBreakdown[1],
                postId: topicBreakdown[2],
                key: topicBreakdown[3],
                event: topicBreakdown[4]
            };
            post.postId = topic.postId;
            return post;
        }

        function onSocialPostUpdate(data) {
            if (!data || !data.payload) {
                return;
            }

            data.payload = dataUtil.serializeObjectCamelCase(data.payload);
            processPushNotificationPostContent(data.payload);
            upsertPostContent(data.payload);
            notifySubscribers(SOCIAL_BAR_EVENT.POST_UPDATE, postList[currentPostId]);
        }

        function unsubscribeToPushNotificationUpdates(reason, isRecoverable) {
            isStarted = false;
            logService.info(LOG_KEY, 'Social pns update service stopped due to: ' + reason);
            if (!isRecoverable) {
                pushNotificationClientService.unsubscribeFromTopic(SOCIAL_TOPIC, onSocialPostUpdate);
                pushNotificationClientService.unsubscribeFromTopic(PNS_TOPIC_KEY.POST_SOCIAL_CONFIG_UPDATE, onSocialConfigUpdate);
                pushNotificationClientService.unsubscribe(onPushNotificationClientEvent);
            }
        }

        function resubscribeToPushNotificationUpdates(reason) {
            logService.info(LOG_KEY, 'Social pns update service restarted due to: ' + reason);
            unsubscribeToPushNotificationUpdates(reason, false);
            updatePostSummaryBySchedulingScript(memoryScheduledScript);
        }

        function onSocialConfigUpdate() {
            logService.info(LOG_KEY, 'Social config updated. Retrieving latest post summary.');
            isStarted = false;
            updatePostSummaryBySchedulingScript(memoryScheduledScript);
        }

        function subscribeToPushNotificationUpdates() {
            isStarted = true;
            logService.info(LOG_KEY, 'Social pns update service started');
            pushNotificationClientService.subscribeToTopic(PNS_TOPIC_KEY.POST_SOCIAL_CONFIG_UPDATE, onSocialConfigUpdate);
            pushNotificationClientService.subscribeToTopic(SOCIAL_TOPIC, onSocialPostUpdate);
            pushNotificationClientService.subscribe(onPushNotificationClientEvent);
        }

        function onPushNotificationClientEvent(event) {
            var eventMessage = 'push notification client ' + event + ' event';
            switch (event) {
                case PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.CLIENT_RECONNECTED:
                    resubscribeToPushNotificationUpdates(eventMessage);
                    break;
                case PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.CLIENT_CONNECTED:
                    updatePostSummaryBySchedulingScript(memoryScheduledScript);
                    break;
                case PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.CLIENT_DISCONNECTED:
                case PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.CLIENT_CLOSED:
                case PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.SERVICE_STOPPED:
                    unsubscribeToPushNotificationUpdates(eventMessage, true);
                    break;
            }
        }

        function updatePostSummaryBySchedulingScript(schedulingScript) {
            if (!dataUtil.isObjectEqual(memoryScheduledScript, schedulingScript)) {
                isStarted = false;
            }

            if (!schedulingScript || !schedulingScript.mediaZones || isStarted || embedProfileService.isPreviewMode()) {
                return;
            }

            isReactionSupported().then(function(isSupported) {
                if (!isSupported) {
                    logService.debug(LOG_KEY, 'Platform version does not meet the minimum post platform version requirement.');
                    return;
                }

                memoryScheduledScript = schedulingScript;
                subscribeToPushNotificationUpdates();
                processApiUrl();
                processSchedulingScript(schedulingScript);
            }, function() {
                logService.error(LOG_KEY, 'Fails to check if post is supported');
            });
        }

        function processApiUrl() {
            var apiPrefix = configurationService.getCurrentProperty(PLAYER_PROPERTY_KEYS.REACTION_SUMMARY_API_PATH_PREFIX);
            apiPrefix = apiPrefix ? UrlUtil.removeTrailingSlash(apiPrefix) : DEFAULT_REACTION_SUMMARY_API_PATH_PREFIX;
            reactionSummaryApiUrl = apiPrefix + DEFAULT_REACTION_SUMMARY_API_PATH_POSTFIX;
            logService.debug(LOG_KEY, 'Using reaction summary API URL: ' + reactionSummaryApiUrl);
        }

        function processSchedulingScript(schedulingScript) {
            var postIds = [];
            // for loop schedulingResult.mediaZones
            for (var i = 0; i < schedulingScript.mediaZones.length; i++) {
                var mediaZone = schedulingScript.mediaZones[i];
                // for loop playlists
                for (var j = 0; j < mediaZone.playlists.length; j++) {
                    var playlist = mediaZone.playlists[j];
                    // for loop items
                    for (var k = 0; k < playlist.items.length; k++) {
                        if (!playlist.items[k] || !playlist.items[k].postId) {
                            return;
                        }

                        postIds.push(playlist.items[k].postId);
                    }
                }
            }

            if (postIds.length === 0) {
                return;
            }

            retrievePostSummary(postIds).then(function(postSummary) {
                processPostsSummary(postSummary);
                if (!currentPostId) {
                    return;
                }

                notifySubscribers(SOCIAL_BAR_EVENT.POST_UPDATE, postList[currentPostId]);
            }, function(error) {
                // disable post content on failure
                disableReactionContent();
                logService.error(LOG_KEY, 'Fails to retrieve post summary: ' + JSON.stringify(error));
            });
        }

        function processActionLink(post) {
            return !post.actionLinkUrl ? undefined : UrlUtil.appendQueryString(post.actionLinkUrl, {
                deviceId: RegistrationUtil.getRegistrationInfo().deviceId,
                method: ACTION_LINK_METHOD_QR,
                channelId: findChannelIdFromSchedulingScript(post.postId) ||
                    configurationService.getCurrentProperty(PLAYER_PROPERTY_KEYS.CHANNEL_ID)
            });
        }

        function findChannelIdFromSchedulingScript(postId) {
            if (!memoryScheduledScript || !memoryScheduledScript.mediaZones || memoryScheduledScript.mediaZones.length === 0) {
                return undefined;
            }

            // Find the channelId that matches the postId in the scheduling script
            for (var i = 0; i < memoryScheduledScript.mediaZones.length; i++) {
                var mediaZone = memoryScheduledScript.mediaZones[i];
                for (var j = 0; j < mediaZone.playlists.length; j++) {
                    var playlist = mediaZone.playlists[j];
                    for (var k = 0; k < playlist.items.length; k++) {
                        if (!playlist.items[k] || !playlist.items[k].postId || playlist.items[k].postId !== postId) {
                            continue;
                        }

                        return playlist.items[k].channelGuid;
                    }
                }
            }

            return undefined;
        }

        return {
            setCurrentPost: setCurrentPost,
            getCurrentPost: getCurrentPost,
            updatePostSummaryBySchedulingScript: updatePostSummaryBySchedulingScript,
            subscribe: subscribe,
            unsubscribe: unsubscribe,
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('core');
    app.service('softwareUpdateService', ["SelfUpdateWorkflow", "ReleaseChannelWorkflow", "$q", "$http", "$window", "dataUtil", "versionUtil", "$timeout", "DateTimeUtil", "STORAGE_KEY", "PLAYER_PROPERTY_KEYS", "SOFTWARE_UPDATE_EVENT", "STORAGE_COLLECTION_KEY", "PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT", "logService", "telemetryService", "deviceTokenService", "halPlatformService", "embedProfileService", "coreFrameworkService", "deviceRegistrationService", "authenticationLibraryService", "configurationService", "coreConfigurationService", "pushNotificationClientService", function(
        SelfUpdateWorkflow,
        ReleaseChannelWorkflow,
        $q,
        $http,
        $window,
        dataUtil,
        versionUtil,
        $timeout,
        DateTimeUtil,
        STORAGE_KEY,
        PLAYER_PROPERTY_KEYS,
        SOFTWARE_UPDATE_EVENT,
        STORAGE_COLLECTION_KEY,
        PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT,
        logService,
        telemetryService,
        deviceTokenService,
        halPlatformService,
        embedProfileService,
        coreFrameworkService,
        deviceRegistrationService,
        authenticationLibraryService,
        configurationService,
        coreConfigurationService,
        pushNotificationClientService) {
        var workflow = {};
        var LOG_KEY = 'appspace.core.softwareupdate';
        var SOFTWARE_UPDATE_TOPIC = 'softwareupdate';
        var PNS_EVENT = {
            CONFIGURATION_UPDATED: 'configurationupdated'
        };
        var UPDATE_RANGE = 300000; // 5 minutes
        var dateTimeUtil = new DateTimeUtil();
        var capabilities = {};
        var CHECK_TIMEOUT = 20000; // 20 seconds
        var currentUpdateSchedule = {};
        var currentUpdateRecurrence;
        var deviceProperties = {};
        var deviceId = '';
        var appVersion = '';
        var ISO_STRING_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSS[Z]';
        var SUPPORTED_GENERAL_UPDATER_SERVICE_VERSIONS = {
            CLOUD: {
                MIN: '8.12.0-ac.1',
                MAX: '8.12.0'
            },
            GLOBAL: '8.12.0'
        };
        var isAutoUpdateEnabled;
        var isAppUpdateEnabled;
        var autoUpdateTime;
        var DEFAULT_UPDATE_TIME = '03:00:00'; // 3AM
        var AUTO_UPDATE_PROPERTY_KEY = PLAYER_PROPERTY_KEYS.SOFTWARE_UPDATES;
        var PACKAGE_TYPE_PLACEHOLDER = '{{packageType}}';
        var APP_UPDATE_FEATURE_FLAG = 'feature.devices.softwareupdates.' + PACKAGE_TYPE_PLACEHOLDER + '.enabled';
        var recurrenceTime;
        var appPackageType;
        var updateStrategies = {
            scheduled: {
                checkUpdateCallback: checkScheduledUpdate,
                executeUpdateCallback: executeManualUpdate,
            },
            recurring: {
                checkUpdateCallback: checkRecurringUpdate,
                executeUpdateCallback: executeAutoUpdate,
            },
        };
        var checkUpdateTimer;
        var updateDeferred;

        function isGeneralAppUpdateSupported() {
            var isAppUpdateServiceAvailable = Boolean($window.AppspaceCloudDeviceAppUpdate &&
                $window.AppspaceCloudDeviceAppUpdate.AppUpdateService);
            return isAppUpdateServiceAvailable ?
                versionUtil.isVersionSupported(SUPPORTED_GENERAL_UPDATER_SERVICE_VERSIONS, LOG_KEY) :
                $q.resolve(false);
        }

        function requestConstellationService(configuration) {
            return authenticationLibraryService.requestConstellationService(configuration.url, deviceTokenService.getConstellationTokens(), configuration);
        }

        function retrieveFile(url) {
            var deferred = $q.defer();
            $http.get(url).then(function(result) {
                if (!result || !result.data) {
                    deferred.reject('No data found in the response');
                    return;
                }

                deferred.resolve(result.data);
            }, deferred.reject);
            return deferred.promise;
        }

        function start() {
            if (embedProfileService.isPreviewMode()) {
                logService.debug(LOG_KEY, 'Software update service is not supported in preview mode');
                return;
            }

            deviceId = deviceRegistrationService.getPlayerId();
            if (!deviceId) {
                logService.debug(LOG_KEY, 'Software update service is not supported in unregistered state');
                return;
            }

            logService.info(LOG_KEY, 'Starting software update service');
            var appUpdateHandler = halPlatformService.getAppUpdateHandler() || {};
            var configuration = halPlatformService.getSoftwareUpdateConfiguration();
            capabilities = halPlatformService.getAppUpdateCapabilities() || {};
            deviceProperties = configurationService.getCurrentProperties() || {};
            appVersion = coreFrameworkService.getAppVersion();
            appPackageType = halPlatformService.getAppPackageType();
            isGeneralAppUpdateSupported().then(function(isSupported) {
                if (isSupported) {
                    workflow = new $window.AppspaceCloudDeviceAppUpdate.AppUpdateService(
                        capabilities,
                        logService,
                        LOG_KEY,
                        requestConstellationService,
                        retrieveFile,
                        {
                            deviceId: deviceId,
                            appVersion: appVersion,
                            appPackageType: appPackageType,
                        },
                        appUpdateHandler
                    );
                } else {
                    workflow = dataUtil.isNullOrUndefined(configuration) ?
                        new SelfUpdateWorkflow(configuration, LOG_KEY) :
                        new ReleaseChannelWorkflow(configuration, LOG_KEY);
                }

                workflow.isSupported().then(function(isWorkflowSupported) {
                    if (!isWorkflowSupported) {
                        logService.debug(LOG_KEY, 'App update is not supported');
                        return;
                    }

                    configurationService.isReady().then(function() {
                        configurationService.subscribe(onDevicePropertiesUpdated);
                        workflow.start();
                        pushNotificationClientService.subscribe(onPnsEvent);
                        pushNotificationClientService.subscribeToTopic(SOFTWARE_UPDATE_TOPIC, onSoftwareUpdateTopicEvent);
                        onDevicePropertiesUpdated(deviceProperties);
                        reportRollback();
                    });
                });
            });
        }

        function checkUpdate() {
            cancelCheckUpdateTimer();
            if (updateDeferred) {
                // Update is in progress, skip checking for now
                return;
            }

            for (var key in updateStrategies) {
                var strategy = updateStrategies[key];
                var shouldUpdate = strategy.checkUpdateCallback();
                if (!shouldUpdate) {
                    continue;
                }

                updateDeferred = strategy.executeUpdateCallback();
                break;
            }

            if (!updateDeferred) {
                // No update to execute, restart the check update timer
                startCheckUpdateTimer();
                return;
            }

            // Recheck update upon completing execution
            updateDeferred.then(function() {
                updateDeferred = undefined;
                startCheckUpdateTimer();
            });
        }

        function startCheckUpdateTimer() {
            cancelCheckUpdateTimer();
            checkUpdateTimer = $timeout(checkUpdate, CHECK_TIMEOUT);
        }

        function cancelCheckUpdateTimer() {
            if (!checkUpdateTimer) {
                return;
            }

            $timeout.cancel(checkUpdateTimer);
            checkUpdateTimer = undefined;
        }

        function shouldUpdateNow() {
            var startOfDay = moment().startOf('day').valueOf();
            var dateRangeStart = dateTimeUtil.addDurationToTicks(startOfDay, recurrenceTime);
            var dateRangeEnd = dateRangeStart + UPDATE_RANGE;
            return dateTimeUtil.isInRange(new Date(dateRangeStart), new Date(dateRangeEnd));
        }

        function getLastSelfUpdateCheck() {
            return coreConfigurationService.get(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.LAST_SELF_UPDATE_CHECK);
        }

        function setLastSelfUpdateCheck() {
            coreConfigurationService.set(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.LAST_SELF_UPDATE_CHECK, dateTimeUtil.getCurrentTime());
        }

        function executeAutoUpdate() {
            return updateApp('Recurring', setLastSelfUpdateCheck);
        }

        function checkRecurringUpdate() {
            var lastUpdateCheck = getLastSelfUpdateCheck();
            var shouldUpdate = shouldUpdateNow();
            // if this is the first update, check whether we're in range to update
            if (!lastUpdateCheck) {
                if (shouldUpdate) {
                    logService.debug(LOG_KEY, 'First time checking update.');
                    return true;
                }

                return false;
            }

            // for subsequent updates, we only update if the last update is yesterday AND we are in range
            var lastUpdateCheckDate = new Date(parseInt(lastUpdateCheck, 10));
            if (dateTimeUtil.isToday(lastUpdateCheckDate) || !shouldUpdate) {
                return false;
            }

            logService.debug(LOG_KEY, 'Checking for update.');
            return true;
        }

        function processAutoUpdateConfiguration(properties) {
            var updatedConfiguration = dataUtil.isValueTrue(properties[AUTO_UPDATE_PROPERTY_KEY]);
            if (updatedConfiguration === isAutoUpdateEnabled) {
                return;
            }

            isAutoUpdateEnabled = updatedConfiguration;
            logService.info(LOG_KEY, 'Software update configuration is updated to ' +
                (isAutoUpdateEnabled ? 'auto': 'manual'));
            notifyWorkflow(SOFTWARE_UPDATE_EVENT.AUTO_UPDATE_CONFIGURATION_UPDATED, isAutoUpdateEnabled);
            setUpdateRecurrence();
        }

        function setUpdateRecurrence(recurrence) {
            currentUpdateRecurrence = recurrence;
            if (!isAutoUpdateEnabled) {
                logService.debug(LOG_KEY, 'Skipping recurrence time processing as auto update is disabled');
                return;
            }

            var updatedRecurrenceTime = autoUpdateTime;
            if (!dateTimeUtil.matchesFormat(updatedRecurrenceTime, 'HH:mm:ss')) {
                if (updatedRecurrenceTime) {
                    logService.debug(LOG_KEY, 'Invalid update recurrence (' + updatedRecurrenceTime + '). Defaulting update check schedule to ' + DEFAULT_UPDATE_TIME);
                }

                // Default to 3AM in case of invalid update time
                updatedRecurrenceTime = DEFAULT_UPDATE_TIME;
            }

            if (updatedRecurrenceTime === recurrenceTime) {
                // Recurrence time is the same, keep the update check timer running
                return;
            }

            recurrenceTime = updatedRecurrenceTime;
            logService.info(LOG_KEY, 'Update check scheduled at ' + recurrenceTime);
        }

        function resetManualUpdateSchedule() {
            currentUpdateSchedule = {};
            processManualUpdateSchedule({});
        }

        function processAutoUpdateRecurrence(schedule) {
            if (!capabilities.frequency || !schedule || !schedule.updateTime) {
                setUpdateRecurrence();
                return;
            }

            var recurrence = schedule.updateTime;
            if (currentUpdateRecurrence === recurrence) {
                // No changes to the update recurrence
                return;
            }

            autoUpdateTime = recurrence;
            setUpdateRecurrence(recurrence);
        }

        function processManualUpdateSchedule(configuration) {
            if (!capabilities.scheduling) {
                return;
            }

            var scheduledDate = configuration.scheduledDate;
            if (!scheduledDate ||
                !dateTimeUtil.isValidDate(scheduledDate)) {
                // Update not scheduled
                return;
            }

            if (configuration.processValidatedDate) {
                scheduledDate = configuration.processValidatedDate(scheduledDate);
            }

            scheduledDate = dateTimeUtil.removeTimezone(scheduledDate);
            if (scheduledDate === currentUpdateSchedule.date) {
                // No changes to the current update schedule,
                // let the checkUpdate timer continue running
                return;
            }

            var lastUpdate = getUpdateExecutionTime();
            if (scheduledDate === lastUpdate) {
                // The same update schedule has been executed before
                return;
            }

            // Skip if the last executed update time is past the new schedule
            if (lastUpdate && dateTimeUtil.isOlderThan(scheduledDate, lastUpdate)) {
                logService.debug(LOG_KEY, 'Skipping outdated update schedule (' + scheduledDate + '). Last update executed at ' + lastUpdate);
                return;
            }

            currentUpdateSchedule.date = scheduledDate;
            currentUpdateSchedule.type = configuration.type;
            logService.info(LOG_KEY, 'Update scheduled at ' + currentUpdateSchedule.date);
        }

        function processUpdateSchedule() {
            var updateSchedule = deviceProperties[PLAYER_PROPERTY_KEYS.SOFTWARE_UPDATE_SCHEDULE];
            updateSchedule = dataUtil.tryParseJSON(updateSchedule) || {};
            processAutoUpdateConfiguration(deviceProperties);
            switch ((updateSchedule.updateType || '').toLowerCase()) {
                case 'scheduled':
                    processManualUpdateSchedule({
                        type: updateSchedule.updateType,
                        scheduledDate: updateSchedule.scheduledDate,
                    });
                    break;
                case 'recurring':
                    // Reset manual update schedule on automatic update mode
                    resetManualUpdateSchedule();
                    processAutoUpdateRecurrence(updateSchedule);
                    break;
                case 'immediate':
                    processManualUpdateSchedule({
                        type: updateSchedule.updateType,
                        scheduledDate: updateSchedule.triggeredAt,
                        processValidatedDate: function(validatedDate) {
                            // Convert trigger time to local in ISO format
                            return dateTimeUtil.formatDate(validatedDate, ISO_STRING_FORMAT);
                        }
                    });
                    break;
                default:
                    // Reset manual update schedule when schedule is removed
                    resetManualUpdateSchedule();
            }

            checkUpdate();
        }

        function processAppUpdateFeatureFlag() {
            var featureFlagKey = dataUtil.replaceStringPlaceholder(APP_UPDATE_FEATURE_FLAG, PACKAGE_TYPE_PLACEHOLDER, appPackageType).toLowerCase();
            var isFeatureFlagEnabled = configurationService.isFeatureFlagEnabled(featureFlagKey);
            if (isFeatureFlagEnabled === isAppUpdateEnabled) {
                return;
            }

            isAppUpdateEnabled = isFeatureFlagEnabled;
            logService.info(LOG_KEY, 'App update feature flag (' + featureFlagKey + ') is ' + (isAppUpdateEnabled ? 'enabled': 'disabled'));
            notifyWorkflow(SOFTWARE_UPDATE_EVENT.APP_UPDATE_CONFIGURATION_UPDATED, {
                isEnabled: isAppUpdateEnabled,
            });
        }

        function getUpdateExecutionTime() {
            return coreConfigurationService.get(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.SOFTWARE_UPDATE_EXECUTED_AT);
        }

        function setUpdateExecutionTime() {
            return coreConfigurationService.set(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.SOFTWARE_UPDATE_EXECUTED_AT, currentUpdateSchedule.date);
        }

        function updateApp(type, finalizeCallback) {
            var deferred = $q.defer();
            workflow.updateApp(deviceProperties).then(function() {
                logService.info(LOG_KEY, type + ' app update has been executed successfully');
            }, function(error) {
                logService.error(LOG_KEY, type + ' app update failed due to: ' + error);
            }).finally(function() {
                finalizeCallback();
                deferred.resolve();
            });
            return deferred.promise;
        }

        function executeManualUpdate() {
            return updateApp(currentUpdateSchedule.type, function() {
                setUpdateExecutionTime();
                currentUpdateSchedule = {};
                processUpdateSchedule();
            });
        }

        function checkScheduledUpdate() {
            if (!currentUpdateSchedule || !currentUpdateSchedule.date || !dateTimeUtil.isNowOrPast(currentUpdateSchedule.date)) {
                return false;
            }

            logService.info(LOG_KEY, currentUpdateSchedule.type + ' update check in progress...');
            return true;
        }

        function stop() {
            logService.info(LOG_KEY, 'Stopping software update service');
            currentUpdateSchedule = {};
            currentUpdateRecurrence = undefined;
            updateDeferred = undefined;
            deviceProperties = {};
            cancelCheckUpdateTimer();
            configurationService.unsubscribe(onDevicePropertiesUpdated);
            pushNotificationClientService.unsubscribe(onPnsEvent);
            pushNotificationClientService.unsubscribeFromTopic(SOFTWARE_UPDATE_TOPIC, onSoftwareUpdateTopicEvent);
            if (!workflow.stop || !workflow.stop()) {
                workflow = undefined;
                return false;
            }

            workflow = undefined;
            logService.debug(LOG_KEY, 'Software update service has been stopped');
            return true;
        }

        function notifyWorkflow(event, data) {
            if (!workflow.onEvent) {
                return;
            }

            workflow.onEvent(event, data);
        }

        function onSoftwareUpdateTopicEvent(data) {
            if (!data || !data.topic) {
                return;
            }

            var payload = data.payload || {};
            switch ((data.topic.event || '').toLowerCase()) {
                case PNS_EVENT.CONFIGURATION_UPDATED:
                    logService.debug(LOG_KEY, 'Received software update configuration update: ' + JSON.stringify(payload));
                    var updateConfiguration = payload.SoftwareUpdateConfiguration;
                    if (!dataUtil.isObject(updateConfiguration)) {
                        return;
                    }

                    if (updateConfiguration.version) {
                        notifyWorkflow(SOFTWARE_UPDATE_EVENT.TARGET_VERSION_UPDATED, updateConfiguration.version);
                    }

                    notifyWorkflow(SOFTWARE_UPDATE_EVENT.CONFIGURATION_UPDATED, updateConfiguration);
                    deviceProperties[PLAYER_PROPERTY_KEYS.SOFTWARE_UPDATE_SCHEDULE] = JSON.stringify(updateConfiguration.schedule);
                    processUpdateSchedule();
                    break;
            }
        }

        function onPnsEvent(event) {
            switch (event) {
                case PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.CLIENT_CONNECTED:
                case PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.CLIENT_RECONNECTED:
                    onDevicePropertiesUpdated(configurationService.getCurrentProperties());
                    break;
            }
        }

        function onDevicePropertiesUpdated(properties) {
            deviceProperties = $angular.copy(properties || {});
            processUpdateSchedule();
            processAppUpdateFeatureFlag();
            if (!workflow.onConfigurationUpdated) {
                return;
            }

            workflow.onConfigurationUpdated(deviceProperties);
        }

        function getRollbackTimestamp() {
            var storedTimestamp = coreConfigurationService.get(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.SOFTWARE_ROLLBACK_EXECUTED_AT);
            return dataUtil.isNumber(storedTimestamp) ? parseInt(storedTimestamp, 10) : undefined;
        }

        function setRollbackTimestamp(timestamp) {
            coreConfigurationService.set(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.SOFTWARE_ROLLBACK_EXECUTED_AT, timestamp);
        }

        function reportRollback() {
            halPlatformService.getAppRollbackInfo().then(function(rollbackInfo) {
                rollbackInfo = rollbackInfo || {};
                var timestamp = rollbackInfo.timestamp;
                if (dataUtil.isNumber(timestamp)) {
                    timestamp = parseInt(timestamp, 10);
                }

                // Skip reporting if the rollback timestamp is not available or same as the last one
                if (!timestamp || getRollbackTimestamp() === timestamp) {
                    return;
                }

                logService.warn(LOG_KEY, 'Rollback executed at ' + timestamp + ' with info: ' + JSON.stringify(rollbackInfo));
                telemetryService.addToQueue({
                    module: 'device-app-update',
                    type: 'rollback',
                    description: 'App update rollback',
                    metadata: {
                        'reason': rollbackInfo.reason,
                        'status': rollbackInfo.status,
                        'appVersion': rollbackInfo.appVersion,
                        'buildVersion': rollbackInfo.buildVersion,
                        'rollbackAppVersion': rollbackInfo.rollbackAppVersion,
                        'rollbackBuildVersion': rollbackInfo.rollbackBuildVersion,
                        'rollbackMethod': rollbackInfo.rollbackMethod,
                        'timestamp': timestamp,
                    },
                });
                setRollbackTimestamp(rollbackInfo.timestamp);
            }, function(error) {
                logService.debug(LOG_KEY, 'Unable to retrieve rollback info due to: ' + error);
            });
        }

        return {
            start: start,
            stop: stop
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('core');

    app.service('telemetryService', ["$q", "$timeout", "$interval", "$injector", "HttpWrapper", "STORAGE_KEY", "STORAGE_COLLECTION_KEY", "dataUtil", "versionUtil", "platformUtil", "logService", "deviceTokenService", "embedProfileService", "deviceIdentityService", "coreConfigurationService", function(
        $q,
        $timeout,
        $interval,
        $injector,
        HttpWrapper,
        STORAGE_KEY,
        STORAGE_COLLECTION_KEY,
        dataUtil,
        versionUtil,
        platformUtil,
        logService,
        deviceTokenService,
        embedProfileService,
        deviceIdentityService,
        coreConfigurationService
    ) {
        var LOG_KEY = 'appspace.core.telemetry';
        var API_PATH = '{{appspace-url}}/services/telemetry/api/v1/events';
        var SUPPORTED_SERVICE_VERSIONS = {
            CLOUD: {
                MIN: '8.1.0-ac.1',
                MAX: '8.1.0'
            },
            GLOBAL: '8.1.0'
        };
        var httpWrapper = new HttpWrapper();
        var deviceStateService;
        var isStarted = false;
        var isStarting = false;
        var isSupported;
        var queue = [];
        var eventCount = {};
        var flushTimeout;
        var FLUSH_DELAY_DURATION = 30000;
        var MAX_QUEUE_SIZE = 10;
        var reportHealthInterval;
        var REPORT_HEALTH_INTERVAL_DURATION = 1800000; // 30 minutes
        var DEVICE_HEALTH_MODULE = 'device-health';

        function getDeviceStateService() {
            if (!deviceStateService) {
                deviceStateService = $injector.get('deviceStateService');
            }

            return deviceStateService;
        }

        function checkTelemetrySupport() {
            if (!dataUtil.isNullOrUndefined(isSupported)) {
                // Telemetry service support has been determined before
                return $q.resolve();
            }

            if (embedProfileService.isPreviewMode()) {
                isSupported = false;
                logService.warn(LOG_KEY, 'Telemetry service is not supported in preview mode.');
                return $q.resolve();
            }

            if (!getDeviceStateService().isRegistered()) {
                logService.warn(LOG_KEY, 'Telemetry service is not supported in unregistered state.');
                stop();
                return $q.resolve();
            }

            var deferred = $q.defer();
            versionUtil.isVersionSupported(SUPPORTED_SERVICE_VERSIONS, LOG_KEY).then(function(isVersionSupported) {
                isSupported = isVersionSupported;
                if (!isVersionSupported) {
                    logService.warn(LOG_KEY, 'Platform version does not meet the minimum telemetry service requirement.');
                }

                deferred.resolve();
            });
            return deferred.promise;
        }

        function composePayload() {
            var deferred = $q.defer();
            var payload = {
                events: $angular.copy(queue),
                device: {},
                eventCount: eventCount,
            };
            deviceIdentityService.generateDeviceIdentity().then(function(identity) {
                payload.device = {
                    hardware: {
                        model: identity.hardware.model,
                        serialNumber: identity.hardware.serialNumber,
                        macAddress: identity.hardware.macAddress,
                        ipAddress: identity.hardware.ipAddress,
                        deviceType: identity.appspace.deviceType,
                    },
                    software: {
                        firmware: identity.software.firmware,
                        OSVersion: identity.software.OSVersion,
                        appVersion: identity.software.appVersion,
                        userAgent: platformUtil.getUserAgent(),
                        appPackageType: identity.software.appPackageType,
                    },
                    appspace: {
                        appspaceUrl: identity.appspace.instanceUrl,
                        deviceGuid: identity.appspace.deviceGuid,
                        accountId: identity.appspace.accountId,
                        apiUrl: coreConfigurationService.getServiceApiUrl(),
                    },
                };
                deferred.resolve(payload);
            });
            return deferred.promise;
        }

        function flushQueue() {
            cancelFlushTimeout();
            composePayload().then(sendTelemetry);
            // For the first phase, we are clearing the queue here regardless of the sendTelemetry result
            // This workflow is open to changes once we implemenent some retry strategy in the future
            clearQueue();
        }

        function cancelFlushTimeout() {
            if (!flushTimeout) {
                return;
            }

            $timeout.cancel(flushTimeout);
            flushTimeout = undefined;
        }

        function delaySendTelemetry() {
            cancelFlushTimeout();
            if (queue.length >= MAX_QUEUE_SIZE) {
                flushQueue();
                return;
            }

            flushTimeout = $timeout(flushQueue, FLUSH_DELAY_DURATION);
        }

        function startQueue() {
            saveQueue();
            delaySendTelemetry();
        }

        function addEventCount(event) {
            var moduleEvent = event.module + '.' + event.type;
            eventCount[moduleEvent] = (eventCount[moduleEvent] || 0) + 1;
        }

        function addToQueue(event) {
            var onFailed = function(reason) {
                logService.debug(LOG_KEY, 'Failed to add telemetry entry due to ' + reason);
            };

            if (!event || !event.module || !event.type) {
                onFailed('incomplete information: ' + JSON.stringify(event));
                return;
            }

            checkTelemetrySupport().then(function() {
                if (!isSupported) {
                    onFailed('failure to meet the service requirements.');
                    return;
                }

                queue.push({
                    timestamp: new Date().toISOString(),
                    module: event.module,
                    type: event.type,
                    description: event.description,
                    metadata: event.metadata || {},
                    sessionId: getDeviceStateService().getSessionId(),
                    uptime: getDeviceStateService().getUpTime(),
                });
                addEventCount(event);
                logService.debug(LOG_KEY, 'Added telemetry to queue: ' + JSON.stringify(event));
                startQueue();
            });
        }

        function saveQueue() {
            coreConfigurationService.set(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.TELEMETRY_QUEUE, JSON.stringify(queue));
        }

        function clearQueue() {
            queue = [];
            eventCount = {};
            saveQueue();
        }

        function retrieveQueue() {
            var savedQueue = coreConfigurationService.get(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.TELEMETRY_QUEUE);
            queue = savedQueue ? JSON.parse(savedQueue) : [];
            eventCount = {};
            for (var i = 0; i < queue.length; i++) {
                addEventCount(queue[i]);
            }
        }

        function sendTelemetry(payload) {
            var onFailed = function(reason) {
                logService.error(LOG_KEY, 'Failed to send telemetry event(s) due to: ' + reason);
            };

            checkTelemetrySupport().then(function() {
                if (!isSupported) {
                    onFailed('Failure to meet the service requirements.');
                    return;
                }

                var configuration = {};
                var constellationTokens = deviceTokenService.getConstellationTokens() || {};
                if (constellationTokens.accessToken) {
                    // Authorization header is optional for telemetry service APIs
                    configuration.headers = {
                        'Authorization': 'Bearer ' + constellationTokens.accessToken
                    };
                }

                var requestPayload = {
                    events: payload.events,
                    device: payload.device,
                };
                logService.info(LOG_KEY, 'Sending telemetry event(s): ' + JSON.stringify(payload.eventCount));
                httpWrapper.handlerPost(API_PATH, requestPayload, configuration).then(function() {
                    logService.info(LOG_KEY, 'Telemetry event(s) have been sent successfully.');
                }, function(error) {
                    onFailed(JSON.stringify(error));
                });
            });
        }

        function reportHealth() {
            addToQueue({
                module: DEVICE_HEALTH_MODULE,
                type: 'heartbeat',
                description: 'Periodic device health check',
            });
        }

        function cancelReportHealthInterval() {
            if (!reportHealthInterval) {
                return;
            }

            $interval.cancel(reportHealthInterval);
            reportHealthInterval = undefined;
        }

        function startReportHealthInterval() {
            cancelReportHealthInterval();
            reportHealthInterval = $interval(reportHealth, REPORT_HEALTH_INTERVAL_DURATION);
        }

        function start() {
            if (isStarting || isStarted) {
                return;
            }

            isStarting = true;
            checkTelemetrySupport().then(function() {
                if (!isSupported) {
                    logService.info(LOG_KEY, 'Telemetry service not started due to failure to meet the service requirements.');
                    return;
                }

                isStarted = true;
                isStarting = false;
                logService.info(LOG_KEY, 'Telemetry service started.');
                // Flush unsent telemetry messages on start
                retrieveQueue();
                addToQueue({
                    module: DEVICE_HEALTH_MODULE,
                    type: 'initialization',
                    description: 'Device initialized',
                });
                startReportHealthInterval();
            });
        }

        function stop(retainQueue) {
            if (isStarted) {
                logService.info(LOG_KEY, 'Telemetry service stopped.');
            }

            if (!retainQueue) {
                clearQueue();
            }

            cancelFlushTimeout();
            cancelReportHealthInterval();
            isStarted = false;
            isStarting = false;
            isSupported = null;
        }

        return {
            start: start,
            stop: stop,
            addToQueue: addToQueue,
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('core');

    app.factory('TssOccupancyService', ["$timeout", "logService", "DateTimeUtil", function($timeout, logService, DateTimeUtil) {
        function TssOccupancyService(getCurrentOccupancySensorState) {
            var LOG_KEY = 'appspace.core.occupancy';
            var onOccupancyStatusUpdated;
            var statusPollTimeoutHandler;
            var OCCUPANCY_STATUS_POLL_TIMEOUT = 15000;
            var dateTimeUtil = new DateTimeUtil();

            function stopOccupancyStatusPolling() {
                if (!statusPollTimeoutHandler) {
                    return;
                }

                $timeout.cancel(statusPollTimeoutHandler);
                statusPollTimeoutHandler = undefined;
            }

            function startOccupancyStatusPolling() {
                stopOccupancyStatusPolling();
                logService.trace(LOG_KEY, 'Next occupancy sensor status poll in ' + dateTimeUtil.toSeconds(OCCUPANCY_STATUS_POLL_TIMEOUT) + ' seconds');
                statusPollTimeoutHandler = $timeout(function() {
                    getCurrentOccupancySensorState().then(function(status) {
                        logService.trace(LOG_KEY, 'Occupancy sensor status: ' + status);
                        onOccupancyStatusUpdated(status);
                    }).finally(startOccupancyStatusPolling);
                }, OCCUPANCY_STATUS_POLL_TIMEOUT);
            }

            this.subscribe = function subscribeToOccupancyStatus(callback) {
                onOccupancyStatusUpdated = callback;
                startOccupancyStatusPolling();
            };

            this.unsubscribe = function unsubscribeFromOccupancyStatus() {
                onOccupancyStatusUpdated = undefined;
                stopOccupancyStatusPolling();
            };
        }

        return (TssOccupancyService);
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('core');
    app.service('webBroadcastService', ["$window", "logService", function(
        $window,
        logService
    ) {
        var LOG_KEY = 'appspace.core.webbroadcastservice';
        var BROADCAST_CHANNEL_NAME = 'appspace.broadcast.channel';
        var NOT_FOUND_INDEX = -1;
        var broadcastChannel;
        var subscribers = [];

        function notifySubscribers(event) {
            for (var i = 0; i < subscribers.length; i++) {
                subscribers[i](event);
            }
        }

        function subscribe(callback) {
            if (!callback || subscribers.indexOf(callback) !== NOT_FOUND_INDEX) {
                return;
            }

            subscribers.push(callback);
        }

        function unsubscribe(callback) {
            var subscriberIndex = subscribers.indexOf(callback);
            if (subscriberIndex === NOT_FOUND_INDEX) {
                return;
            }

            subscribers.splice(subscriberIndex, 1);
        }

        function onBroadcastMessage(message) {
            if (!message) {
                return;
            }

            notifySubscribers(message);
        }

        function start() {
            if (!$window.BroadcastChannel) {
                return;
            }

            broadcastChannel = new $window.BroadcastChannel(BROADCAST_CHANNEL_NAME);
            broadcastChannel.onmessage = onBroadcastMessage;
        }

        function stop() {
            if (!broadcastChannel) {
                return;
            }

            broadcastChannel.close();
            broadcastChannel = null;
        }

        function postMessage(data) {
            if (!broadcastChannel || !data) {
                logService.warn(LOG_KEY, 'Broadcast channel or data is undefined.');
                return;
            }

            broadcastChannel.postMessage(data);
        }

        return {
            start: start,
            stop: stop,
            subscribe: subscribe,
            unsubscribe: unsubscribe,
            postMessage: postMessage,
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('core');

    app.service('webPublishingService', ["platformUtil", "dataUtil", "embedProfileService", "PLATFORM_TYPE", function(platformUtil, dataUtil, embedProfileService, PLATFORM_TYPE) {
        var RESTRICTED_PLATFORMS = [
            PLATFORM_TYPE.AIRMEDIA200,
            PLATFORM_TYPE.AIRMEDIA300,
            PLATFORM_TYPE.AIRMEDIA,
            PLATFORM_TYPE.BRIGHTSIGN,
            PLATFORM_TYPE.IEC,
            PLATFORM_TYPE.MERCURY,
            PLATFORM_TYPE.MERSIVE,
            PLATFORM_TYPE.SAMSUNG_SSSP3,
            PLATFORM_TYPE.SAMSUNG_TIZEN,
            PLATFORM_TYPE.WEBEX_BOARD,
            PLATFORM_TYPE.WEBEX_ROOM_KIT,
            PLATFORM_TYPE.WEBOS,
            PLATFORM_TYPE.MICROSOFT_TEAMS_ROOMS,
        ];

        this.isRestricted = function() {
            return embedProfileService.isWebPublishingMode() &&
                dataUtil.includesValue(platformUtil.detectPlatform(), RESTRICTED_PLATFORMS);
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.factory('Observable', function() {
        function Observable() {
            var observers = [];

            this.registerObserver = function(observer) {
                if (observers.indexOf(observer) != -1) {
                    return;
                }
                observers.push(observer);
            };

            this.removeObserver = function(observer) {
                var observerIndex = observers.indexOf(observer);
                if (observerIndex == -1) {
                    return;
                }
                observers.splice(observerIndex, 1);
            };

            this.notifyObservers = function(event) {
                for (var i = 0; i < observers.length; i++) {
                    observers[i](event);
                }
            };
        }

        return (Observable);
    });
})(window.angular);


(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.factory('StringUtil', function() {
        var NEW_LINES_REGEX = /\r\n|\r|\n/g;

        function splitByNewLine(value, trim) {
            if (!value) {
                return value;
            }

            var result = value.split(NEW_LINES_REGEX);
            if (!trim) {
                return result;
            }

            // remove empty item for first & last
            if (!result[0]) {
                result.splice(0, 1);
            }

            if (!result[result.length - 1]) {
                result.splice(result.length - 1, 1);
            }

            return result;
        }

        return {
            splitByNewLine: splitByNewLine
        };
    });
})(window.angular);


(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.factory('TimeoutUtil', ["$timeout", function($timeout) {
        var debounceTimeout;

        function cancel(timeout) {
            if (!timeout) {
                return;
            }

            $timeout.cancel(timeout);
        }

        function start(callback, timeoutDuration) {
            if (!callback) {
                return;
            }

            return $timeout(callback, timeoutDuration);
        }

        function clearDebounceTimeout() {
            if (!debounceTimeout) {
                return;
            }

            $timeout.cancel(debounceTimeout);
            debounceTimeout = undefined;
        }

        function debounce(callback, wait, immediate) {
            if (!callback || !callback.apply) {
                return;
            }

            wait = wait || 0;
            return function() {
                var context = self;
                var later = function() {
                    clearDebounceTimeout();
                    if (immediate) {
                        return;
                    }

                    callback.apply(context);
                };

                var callNow = immediate && !debounceTimeout;
                clearDebounceTimeout();
                debounceTimeout = $timeout(later, wait);
                if (!callNow) {
                    return;
                }

                callback.apply(context);
            };
        }

        return {
            cancel: cancel,
            start: start,
            debounce: debounce
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.factory('UrlUtil', ["$location", "$window", "$injector", "dataUtil", function($location, $window, $injector, dataUtil) {
        var DEVICE_ID_KEY = 'deviceid';
        var FILE_PROTOCOL = 'file';
        var PROTOCOL_REGEX = /^[^:]+(?=:\/\/)/i;
        var BASE_URL_REGEX = /(.*)(?:\?)/;
        var re = /([a-zA-Z0-9]+:\/\/)?([a-zA-Z0-9.\-%]*){1}:?(\d{2,})?(\/[a-zA-Z0-9.\-%/]*)?/;
        function retrieveParams(result) {
            result = result || {};
            result = dataUtil.mergeObject(
                result,
                $angular.copy($location.search()));
            if (!$window.location.search) {
                return result;
            }

            return dataUtil.mergeObject(
                result,
                toQueryObject($window.location.search.substr(1)));
        }

        function toQueryObject(queryString) {
            var result = {};
            var splits = queryString.split('&');
            for (var i = 0; i < splits.length; i++) {
                var keyValuePair = dataUtil.getKeyValuePairFromString(splits[i]);
                if (!keyValuePair) {
                    continue;
                }

                result[keyValuePair.key.toLowerCase()] = decodeURI(keyValuePair.value);
            }

            return result;
        }

        function retrieveUrlAnatomy(url) {
            if (!url) {
                return {};
            }

            var matcher = re.exec(url);
            return {
                protocol: matcher[1],
                ip: matcher[2],
                port: matcher[3],
                path: matcher[4]
            };
        }

        function clearParams() {
            $location.search({});
        }

        function processNativeQueryString() {
            if (!$window.location.search) {
                return false;
            }

            var newUrl = $window.location.origin + $window.location.pathname + '#';
            $window.location.href = newUrl + '?' + httpParamSerializer(retrieveParams());
            return true;
        }

        function httpParamSerializer(obj) {
            var res = [];
            for (var key in obj) {
                if (dataUtil.isNullOrUndefined(obj[key])) {
                    continue;
                }

                res.push(key + '=' + obj[key]);
            }

            return res.join('&');
        }

        function fixUrlProtocol(url) {
            if (!url) {
                return;
            }

            return retrieveUrlAnatomy(url).protocol ? url : ('https://' + url);
        }

        function isUrlHttpOrHttps(url) {
            return url && (url.toLowerCase().indexOf('http:') >= 0 || url.toLowerCase().indexOf('https:') >= 0);
        }

        function removeTrailingSlash(url) {
            if (!url || !dataUtil.isString(url)) {
                return url;
            }

            return url.replace(/\/$/, '');
        }

        function getQueryStringSeparator(url) {
            return url.indexOf('?') !== -1 ? '&' : '?';
        }

        function appendQueryString(url, queryObject) {
            var existingQueryParam = retrieveParamsFromUrl(url);
            queryObject = dataUtil.mergeObject(existingQueryParam, queryObject || {});
            var baseUrl = getBaseUrl(url);
            // if query object is empty, don't do anything, just return back
            return dataUtil.isObjectEmpty(queryObject) ? baseUrl :
                baseUrl +
                getQueryStringSeparator(baseUrl) +
                httpParamSerializer(queryObject);
        }

        function removeQueryString(url, keysToRemove) {
            if (!url) {
                return url;
            }

            var urlParameters = retrieveParamsFromUrl(url);
            for (var i = 0; i < keysToRemove.length; i++) {
                delete urlParameters[keysToRemove[i]];
            }

            return appendQueryString(getBaseUrl(url), urlParameters);
        }

        function appendDeviceInformation(url) {
            if (!url) {
                return url;
            }

            var embedProfileService = $injector.get('embedProfileService');
            var deviceid = embedProfileService.isPreviewMode() ? embedProfileService.getSessionUrlParameters()['deviceid'] || '' : $injector.get('deviceRegistrationService').getPlayerId();
            var urlParameters = retrieveParamsFromUrl(url);
            if (!deviceid) {
                return url;
            }

            urlParameters[DEVICE_ID_KEY] = deviceid;
            return appendQueryString(getBaseUrl(url), urlParameters);
        }

        function getProtocol(path) {
            if (!path) {
                return '';
            }

            var matches = path.match(PROTOCOL_REGEX) || [''];
            return matches[0].toLowerCase();
        }

        function isFileProtocol(path) {
            return Boolean(path && getProtocol(path) === FILE_PROTOCOL);
        }

        function retrieveParamsFromUrl(url) {
            var DEFAULT_RESULT = {};
            var seperatorIndex = (url || '').indexOf('?');
            if (seperatorIndex === -1) {
                return DEFAULT_RESULT;
            }

            var queryString = url.substring(seperatorIndex + 1);
            if (!queryString) {
                return DEFAULT_RESULT;
            }

            return toQueryObject(queryString);
        }

        function getBaseUrl(url) {
            if (!url) {
                return '';
            }

            var urlMatch = url.match(BASE_URL_REGEX);
            return urlMatch && urlMatch.length > 1 ? urlMatch[1] : url;
        }

        function hasQueryString(url, key) {
            var queryString = retrieveParamsFromUrl(url);
            return queryString[key] !== undefined;
        }

        function getPathName(url) {
            return url ? new URL(url).pathname : '';
        }

        return {
            retrieveParams: retrieveParams,
            retrieveUrlAnatomy: retrieveUrlAnatomy,
            clearParams: clearParams,
            processNativeQueryString: processNativeQueryString,
            httpParamSerializer: httpParamSerializer,
            fixUrlProtocol: fixUrlProtocol,
            isUrlHttpOrHttps: isUrlHttpOrHttps,
            removeTrailingSlash: removeTrailingSlash,
            appendDeviceInformation: appendDeviceInformation,
            isFileProtocol: isFileProtocol,
            retrieveParamsFromUrl: retrieveParamsFromUrl,
            getProtocolFromUrl: getProtocol,
            getBaseUrl: getBaseUrl,
            getPathName: getPathName,
            appendQueryString: appendQueryString,
            removeQueryString: removeQueryString,
            hasQueryString: hasQueryString
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    // Reads file into string
    app.factory('AppspaceUrlUtil', ["coreConfigurationService", "UrlUtil", function(coreConfigurationService, UrlUtil) {
        var APPSPACE_URL_REGEX = /{{appspace-url}}/g;
        var APPSPACE_API_URL_REGEX = /{{appspace-api-url}}/g;
        var APPSPACE_CONTENT_HOST_REGEX = /{{appspaceContentHost}}/g;
        var HTTP_HTTPS_REGEX = /^(http|https):\/\//i;
        var COLLLECTION_KEY = 'appspace';
        var APPSPACE_URL_KEY = 'appspaceURL';
        var APPSPACE_API_URL_KEY_NAME = 'serviceApiUrl';
        var SLASH = '/';

        function getAppspaceUrl(){            
            var appspaceUrl = coreConfigurationService.get(COLLLECTION_KEY, APPSPACE_URL_KEY);
            return UrlUtil.removeTrailingSlash(appspaceUrl);
        }

        function replaceUrl(url, regexToReplace, value) {
            if (!isReplacementRequired(regexToReplace, url)) {
                return url;
            }

            return value || value == '' ? url.replace(regexToReplace, value) : url;
        }

        function replaceUrlWithKey(url, regex, key) {
            return replaceUrl(url, regex, coreConfigurationService.get(COLLLECTION_KEY, key));
        }

        function replaceAppspaceUrl(url) {
            var result = replaceUrlWithKey(url, APPSPACE_API_URL_REGEX, APPSPACE_API_URL_KEY_NAME);
            return replaceUrlWithKey(result, APPSPACE_URL_REGEX, APPSPACE_URL_KEY);
        }

        function replaceAppspaceContentHost(url) {
            var appspaceContentHost = getAppspaceUrl();
            return appspaceContentHost ?
                replaceUrl(url, APPSPACE_CONTENT_HOST_REGEX, appspaceContentHost.replace(HTTP_HTTPS_REGEX, '')) :
                undefined;
        }

        function isReplacementRequired(regex, url) {
            return !url || url.match(regex);
        }

        function isAppspaceUrlReplacementRequired(url) {
            return isReplacementRequired(APPSPACE_URL_REGEX, url) ||
                isReplacementRequired(APPSPACE_API_URL_REGEX, url);
        }

        return {
            replaceAppspaceUrl: replaceAppspaceUrl,
            replaceAppspaceContentHost: replaceAppspaceContentHost,
            isAppspaceUrlReplacementRequired: isAppspaceUrlReplacementRequired,
            getAppspaceUrl: getAppspaceUrl,
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.factory('broadcastAlertUtil', ["$filter", "dataUtil", "mediaUtil", "channelUtil", "DateTimeUtil", "UrlUtil", "CONTENT_ACCESS_CONTEXT", function(
        $filter,
        dataUtil,
        mediaUtil,
        channelUtil,
        DateTimeUtil,
        UrlUtil,
        CONTENT_ACCESS_CONTEXT) {
        var DISPLAY_MODE = {
            SCREEN_TAKEOVER: 'screentakeover',
            NOTIFICATION: 'notification'
        };
        var PRIORITY = {
            CRITICAL: 'critical',
            STANDARD: 'standard'
        };
        var CONTENT_TYPE = {
            CHANNEL: 'channel',
            TEXT: 'text',
            MEDIA: 'media'
        };
        var CHANNEL_TYPE = {
            LIVE: 'live',
            PLAYLIST: 'playlist'
        };
        var END_TYPE = {
            MANUAL_DEACTIVATION: 'manualdeactivation',
            DURATION: 'duration',
            END_TIME: 'endtime'
        };
        var CONTENT_TYPE_PROPERTY = 'contentType';
        var PROPERTIES = ['id', 'displayMode', 'priority', 'endType', 'duration', 'priorityOrder', 'thumbnailUrl', 'updatedAt', 'activatedAt', 'content'];
        var BROADCAST_CONTENT_PROPERTIES = ['message', 'title', 'contentType', 'channelId', 'channelType', 'mediaContentItems'];
        var dateTimeUtil = new DateTimeUtil();

        function sortBroadcasts(broadcasts) {
            // Higher order value indicates higher priority
            return dataUtil.isArray(broadcasts) ?
                $filter('orderBy')(broadcasts, '-priorityOrder') : broadcasts;
        }

        function matchesProperty(broadcast, key, value) {
            broadcast = broadcast || {};
            var property = (broadcast[key] || '').toLowerCase();
            return property === value;
        }

        function isNotification(broadcast) {
            return matchesProperty(broadcast, 'displayMode', DISPLAY_MODE.NOTIFICATION);
        }

        function isCritical(broadcast) {
            return matchesProperty(broadcast, 'priority', PRIORITY.CRITICAL);
        }

        function matchesContentProperty(broadcast, key, value) {
            broadcast = broadcast || {};
            return matchesProperty(broadcast.content, key, value);
        }

        function isChannel(broadcast) {
            return matchesContentProperty(broadcast, CONTENT_TYPE_PROPERTY, CONTENT_TYPE.CHANNEL);
        }

        function isMedia(broadcast) {
            return matchesContentProperty(broadcast, CONTENT_TYPE_PROPERTY, CONTENT_TYPE.MEDIA);
        }

        function isText(broadcast) {
            return matchesContentProperty(broadcast, CONTENT_TYPE_PROPERTY, CONTENT_TYPE.TEXT);
        }

        function isLiveChannel(broadcast) {
            return matchesContentProperty(broadcast, 'channelType', CHANNEL_TYPE.LIVE);
        }

        function requiresManualDeactivation(broadcast) {
            return matchesProperty(broadcast, 'endType', END_TYPE.MANUAL_DEACTIVATION);
        }

        function isFullscreenContent(broadcast) {
            broadcast = broadcast || {};
            return Boolean(!broadcast.isNotification && (broadcast.isChannel || broadcast.isMedia));
        }

        function isBroadcastDifferent(currentBroadcast, updatedBroadcast) {
            currentBroadcast = currentBroadcast || {};
            updatedBroadcast = updatedBroadcast || {};
            return dataUtil.isObjectPropertiesDifferent(currentBroadcast, updatedBroadcast, ['updatedAt']);
        }

        function processChannelPreviewUrl(broadcast) {
            if (!broadcast ||
                !broadcast.isChannel ||
                !broadcast.content ||
                !broadcast.content.channelId) {
                return;
            }

            var mediaUrl = channelUtil.generateSignsPreviewURL(broadcast.content.channelId, CONTENT_ACCESS_CONTEXT.BROADCAST);
            broadcast.mediaUrl = UrlUtil.appendDeviceInformation(mediaUrl);
        }

        function processMediaItems(broadcast, contentBaseUrl) {
            if (!contentBaseUrl ||
                !broadcast ||
                !broadcast.isMedia ||
                !broadcast.content ||
                !broadcast.content.mediaContentItems ||
                !dataUtil.isArray(broadcast.content.mediaContentItems)) {
                return;
            }

            var items = broadcast.content.mediaContentItems;
            for (var i = 0; i < items.length; i++) {
                var mediaContent = items[i];
                if (!mediaContent ||
                    !mediaContent.contentId) {
                    continue;
                }

                mediaContent.mediaUrl = mediaUtil.generateContentUrl(
                    mediaContent.contentId,
                    contentBaseUrl,
                    CONTENT_ACCESS_CONTEXT.BROADCAST);
            }
        }

        function processBroadcastEnd(broadcast) {
            if (!broadcast ||
                broadcast.requiresManualDeactivation ||
                !broadcast.duration ||
                !dataUtil.isString(broadcast.duration) ||
                !dateTimeUtil.isValidDate(broadcast.activatedAt)) {
                return;
            }

            var startTicks = new Date(broadcast.activatedAt).getTime();
            broadcast.endTime = dateTimeUtil.addDurationToTicks(startTicks, broadcast.duration);
        }

        function generateBroadcastItem(broadcast) {
            if (!dataUtil.isObject(broadcast)) {
                return;
            }

            var item = {};
            for (var i = 0; i < PROPERTIES.length; i++) {
                var key = PROPERTIES[i];
                var value = broadcast[key];
                if (!value) {
                    continue;
                }

                item[key] = value;
            }

            item.content = {};
            broadcast.content = broadcast.content || {};
            for (var j = 0; j < BROADCAST_CONTENT_PROPERTIES.length; j++) {
                var contentKey = BROADCAST_CONTENT_PROPERTIES[j];
                var contentValue = broadcast.content[contentKey];
                if (!contentValue) {
                    continue;
                }

                item.content[contentKey] = contentValue;
            }

            if (item.activatedAt && dateTimeUtil.isValidDate(item.activatedAt)) {
                item.activatedAt = new Date(item.activatedAt).toISOString();
            }

            return item;
        }

        return {
            sortBroadcasts: sortBroadcasts,
            isNotification: isNotification,
            isCritical: isCritical,
            isChannel: isChannel,
            isMedia: isMedia,
            isText: isText,
            isLiveChannel: isLiveChannel,
            isFullscreenContent: isFullscreenContent,
            isBroadcastDifferent: isBroadcastDifferent,
            generateBroadcastItem: generateBroadcastItem,
            requiresManualDeactivation: requiresManualDeactivation,
            processChannelPreviewUrl: processChannelPreviewUrl,
            processMediaItems: processMediaItems,
            processBroadcastEnd: processBroadcastEnd
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.factory('channelUtil', ["$window", "$injector", "STORAGE_KEY", "STORAGE_COLLECTION_KEY", "UrlUtil", "coreConfigurationService", function(
        $window,
        $injector,
        STORAGE_KEY,
        STORAGE_COLLECTION_KEY,
        UrlUtil,
        coreConfigurationService) {
        var halMediaService;
        function getHalMediaService() {
            if (!halMediaService) {
                halMediaService = $injector.get('halMediaService');
            }

            return halMediaService;
        }

        function generateSignsPreviewURL(appId, context) {
            var appspaceUrl = coreConfigurationService.get(
                STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.APPSPACE_URL);
            if (!appId || !appspaceUrl) {
                return '';
            }

            var previewUrl = $window.location.origin + $window.location.pathname + '#/tv/previewappdisplay';

            previewUrl = UrlUtil.appendQueryString(previewUrl, {
                appid: appId,
                appspaceurl: encodeURIComponent(appspaceUrl),
                embedprofile: 'preview',
                mute: getHalMediaService().getDeviceMute(),
                context: context,
            });

            return previewUrl;
        }

        return {
            generateSignsPreviewURL: generateSignsPreviewURL
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    // Reads file into string
    app.factory('CSVParser', ["$window", "$timeout", "$q", "StringUtil", function($window, $timeout, $q, StringUtil) {
        // Define the constructor function.
        function CSVParser() {

        }

        // Define the instance methods and properties
        CSVParser.prototype = {
            // Public interface
            parse: parse
        };

        // Schedule a deferred parsing of the file
        function parse(csv) {
            // promise object for async response
            var deferred = $q.defer();
            $timeout(
                function() {
                    doParse(csv, deferred);
                }, 1);
            return deferred.promise;
        }

        // Actual reading of file
        // promise.resolve(); //finished parsing
        // promise.reject(); //error during parse
        function doParse(csvString, promise) {
            if (csvString == undefined || csvString.length === 0) {
                promise.reject();
                return;
            }

            // handle empty csv (only have new line)
            if (csvString.length == 1 && csvString.charCodeAt(0) == 10) {
                promise.reject();
                return;
            }

            // split csvString by new line to array
            var rawLines = StringUtil.splitByNewLine(csvString, true);
            if (!rawLines || !rawLines.length) {
                promise.reject();
                return;
            }

            // the first line is the header definition
            var headerDefinitionLine = rawLines[0];
            if (!headerDefinitionLine) {
                promise.reject();
                return;
            }

            // split headerDefinitionLine by ,
            var headersDefinition = headerDefinitionLine.split(',');
            var result = [];
            var previousLine;
            // iterate the headersDefinition from last to first
            // easier to find the rows for each table when moving backwards
            for (var j = headersDefinition.length - 1; j >= 0; j--) {
                var header = headersDefinition[j];
                // if empty, continue
                if (!header) {
                    continue;
                }
                // 0 = table name
                // 1 = starting position of its row
                var headerInfo = header.split('=');
                // invalid table definition
                if (headerInfo.length !== 2) {
                    continue;
                }

                var startLine = parseInt(headerInfo[1], 10);
                var data = rawLines.slice(startLine, previousLine).join('\n');
                // slice the raw lines based on the start position of the table and the previous table start line
                var array = data ? csvToArray(data) : [];
                // maintain the position of the original tables. set to j directly
                result[j] = {
                    type: headerInfo[0],
                    items: rowsToObjects(array)
                };
                previousLine = startLine;
            }

            promise.resolve(result);
        }

        // convert each row to an object with the column values as properties
        function rowsToObjects(rows) {
            var objArray = [];

            if (rows.length > 0) {
                // put header in an array
                var headers = [];
                for (var k = 0; k < rows[0].length; k++) {
                    var key = rows[0][k];
                    if (key && key.length > 0) {
                        headers[k] = key.trim();
                    }
                }

                // convert each row to an object with the column values as properties
                for (var i = 1; i < rows.length; i++) {
                    // ignore empty row
                    if (rows[i].length == 1 && !rows[i][0]) {
                        continue;
                    }
                    objArray[i - 1] = {};
                    for (var k = 0; k < headers.length; k++) {
                        objArray[i - 1][headers[k]] = rows[i][k].trim();
                    }
                }
            }
            return objArray;
        }

        // Source: http://www.bennadel.com/blog/1504-Ask-Ben-Parsing-CSV-Strings-With-Javascript-Exec-Regular-Expression-Command.htm
        function csvToArray(strData, strDelimiter) {
            // Check to see if the delimiter is defined. If not, then default to comma.
            strDelimiter = (strDelimiter || ',');
            // Create a regular expression to parse the CSV values.
            var objPattern = new RegExp((
                // Delimiters.
                '(\\' + strDelimiter + '|\\r?\\n|\\r|^)' +
                // Quoted fields.
                '(?:"([^"]*(?:""[^"]*)*)"|' +
                // Standard fields.
                '([^"\\' + strDelimiter + '\\r\\n]*))'), 'gi');
            // Create an array to hold our data. Give the array a default empty first row.
            var arrData = [[]];
            // Create an array to hold our individual pattern matching groups.
            var arrMatches = null;
            // Keep looping over the regular expression matches until we can no longer find a match.
            while (arrMatches = objPattern.exec(strData)) {
                // Get the delimiter that was found.
                var strMatchedDelimiter = arrMatches[1];
                // Check to see if the given delimiter has a length (is not the start of string) and if it matches
                // field delimiter. If id does not, then we know that this delimiter is a row delimiter.
                if (strMatchedDelimiter.length && (strMatchedDelimiter != strDelimiter)) {
                    // Since we have reached a new row of data, add an empty row to our data array.
                    arrData.push([]);
                }
                // Now that we have our delimiter out of the way, let's check to see which kind of value we captured (quoted or unquoted).
                if (arrMatches[2]) {
                    // We found a quoted value. When we capture this value, unescape any double quotes.
                    var strMatchedValue = arrMatches[2].replace(
                        new RegExp('""', 'g'), '"');
                } else {
                    // We found a non-quoted value.
                    var strMatchedValue = arrMatches[3];
                }
                // Now that we have our value string, let's add it to the data array.
                arrData[arrData.length - 1].push(strMatchedValue);
            }
            // Return the parsed data.
            return (arrData);
        }

        // Return the constructor for the class
        return (CSVParser);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');
    app.factory('DateTimeUtil', ["dataUtil", "$window", function(dataUtil, $window) {
        var WITHOUT_TIMEZONE_REGEX = /^(\d{1,4}(?:\/|-)\d{1,2}(?:\/|-)\d{1,4}(?:\s|T)\d{1,2}:\d{1,2}:\d{1,2})(\s*am|pm)?$/i;
        var CLEAN_DATE_TIME_FORMAT_REGEX = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?/;
        var SECOND_IN_MILLISECONDS = 1000;
        var EXPONENTIAL_BASE = 2;
        var BACKOFF_INTERVAL = 15;
        var FAILURE_BACKOFF_MAX = 1800; // 30 minutes
        var TIME_UNIT = {
            HOUR: {
                SINGULAR: 'hour',
                PLURAL: 'hours'
            },
            MINUTE: {
                SINGULAR: 'minute',
                PLURAL: 'minutes'
            },
            SECOND: {
                SINGULAR: 'second',
                PLURAL: 'seconds'
            }
        };
        // Define the constructor function.
        function DateTimeUtil() {

        }

        // Define the instance methods and properties
        DateTimeUtil.prototype = {
            // Public interface
            convertWindowsFormatDateToMoment: convertWindowsFormatDateToMoment,
            hhmmss: hhmmss,
            addMillisecondsToDayStart: addMillisecondsToDayStart,
            addDurationToTicks: addDurationToTicks,
            isOlderThan: isOlderThan,
            isInRange: isInRange,
            isNowOrPast: isNowOrPast,
            getCurrentTime: getCurrentTime,
            removeTimezone: removeTimezone,
            isToday: isToday,
            isValidDate: isValidDate,
            matchesFormat: matchesFormat,
            formatDate: formatDate,
            fixTimeZone: fixTimeZone,
            getCurrentMinute: getCurrentMinute,
            getSecondsDifference: getSecondsDifference,
            toSeconds: toSeconds,
            toMilliseconds: toMilliseconds,
            toTimeText: toTimeText,
            getTimeOrigin: getTimeOrigin,
            getBackoffTimeInSeconds: getBackoffTimeInSeconds,
            parseUtcString: parseUtcString,
        };

        function parseUtcString(utcString) {
            return !utcString ? moment().toDate() : moment.utc(utcString).toDate();
        }

        function addMillisecondsToDayStart(ticks) {
            return moment().startOf('day').add(ticks, 'ms').toDate();
        }

        function addDurationToTicks(ticks, duration) {
            if (!ticks || !dataUtil.isNumber(ticks) || !duration) {
                return;
            }

            var splitDurations = duration.split(':');
            if (splitDurations.length !== 3) {
                return;
            }

            var hours = splitDurations[0];
            var minutes = splitDurations[1];
            var seconds = splitDurations[2];
            if (!dataUtil.isNumber(hours) || !dataUtil.isNumber(minutes) || !dataUtil.isNumber(seconds)) {
                return;
            }

            return moment(ticks)
                .add(hours, 'hours')
                .add(minutes, 'minutes')
                .add(seconds, 'seconds').valueOf();
        }

        function isInRange(startDate, endDate) {
            var currentTime = moment().valueOf();
            return currentTime >= startDate.getTime() && currentTime < endDate.getTime();
        }

        function isOlderThan(dateToCheck, targetDate) {
            return (!dateToCheck || !targetDate) ?
                false : moment(dateToCheck).isBefore(targetDate);
        }

        function isNowOrPast(targetDate) {
            return moment(moment()).isSameOrAfter(targetDate);
        }

        function removeTimezone(targetDate) {
            if (!targetDate || !isValidDate(targetDate)) {
                return targetDate;
            }

            var matches = targetDate.match(CLEAN_DATE_TIME_FORMAT_REGEX);
            return matches.length ? matches[0] : targetDate;
        }

        function getCurrentTime() {
            return moment().valueOf();
        }

        function isToday(dateToCheck) {
            return moment().isSame(moment(dateToCheck), 'day');
        }

        function isValidDate(dateToCheck) {
            return moment(dateToCheck).isValid();
        }

        function convertWindowsFormatDateToMoment(pattern) {
            if (!pattern) {
                return;
            }

            pattern = pattern.split('tt').join('a');
            pattern = pattern.split('TT').join('A');
            pattern = pattern.split('yyyy').join('YYYY');
            pattern = pattern.split('yy').join('YY');
            // pattern = "dMMM ddd,yyyydd dd";
            // add space for our regex searching
            pattern = ' ' + pattern;
            // replace dd pattern
            pattern = replace(pattern, 'dd', 'DD', 'd');
            // replace d pattern
            pattern = replace(pattern, 'd', 'D', 'd');
            // replace mmm pattern
            pattern = replace(pattern, 'mmm', 'MMM', 'm');

            // remove the space infront
            pattern = pattern.substring(1);
            // console.log(pattern);
            return pattern;
        }

        function replace(text, source, target, holder) {
            var pattern = text;
            var regDD = new RegExp('([^' + holder + '])(' + source + ')(?!' + holder + ')', 'g');
            var res;
            while ((res = regDD.exec(pattern)) !== null) {
                var temp = pattern.substring(res.index, res.index + source.length + 1);
                temp = temp.replace(source, target);
                pattern = pattern.substring(0, res.index) + temp + pattern.substring(res.index + source.length + 1);
            }

            return pattern;
        }

        function hhmmss(time, format) {
            var secs = Math.floor(time % 60);
            var minutes = Math.floor(time / 60);
            var hours = Math.floor(minutes / 60);
            minutes = minutes % 60;

            // give default format if none is given
            if (format == null || format.length == 0) {
                if (hours <= 0) {
                    format = 'mm:ss';
                } else {
                    format = 'HH:mm';
                }
            }

            var timeStr = pad(hours) + ':' + pad(minutes) + ':' + pad(secs);
            var res = moment(timeStr, 'HH:mm:ss').format(format);
            return res;
        }

        function pad(num) {
            var res = ('0' + num).slice(-2);
            return res;
        }

        function fixTimeZone(date) {
            // if date is empty OR contains extra information after the time that is not am/pm, return directly
            // e.g. 4/20/2020 15:00:00+07:00 or 4/20/2020 15:00:00 GMT-5
            return !date || !dataUtil.isString(date) || !WITHOUT_TIMEZONE_REGEX.test(date) ? date :
                date + ' +0'; // assume it's UTC
        }

        function getCurrentMinute() {
            return moment().minutes();
        }

        function getSecondsDifference(startDate, endDate) {
            return moment(endDate).diff(moment(startDate), TIME_UNIT.SECOND.SINGULAR);
        }

        function toSeconds(milliseconds) {
            return isNaN(milliseconds) ? milliseconds : (milliseconds / SECOND_IN_MILLISECONDS);
        }

        function getTimeTextByUnit(time, singularUnit, pluralUnit) {
            var timeText = time + ' ';
            timeText += time > 1 ? pluralUnit : singularUnit;
            return timeText;
        }

        function toTimeText(seconds) {
            var time = moment.duration(seconds, TIME_UNIT.SECOND.PLURAL)._data;
            var timeTexts = [];
            if (time.hours) {
                timeTexts.push(getTimeTextByUnit(time.hours, TIME_UNIT.HOUR.SINGULAR, TIME_UNIT.HOUR.PLURAL));
            }

            if (time.minutes) {
                timeTexts.push(getTimeTextByUnit(time.minutes, TIME_UNIT.MINUTE.SINGULAR, TIME_UNIT.MINUTE.PLURAL));
            }

            if (time.seconds) {
                timeTexts.push(getTimeTextByUnit(time.seconds, TIME_UNIT.SECOND.SINGULAR, TIME_UNIT.SECOND.PLURAL));
            }

            return timeTexts.filter(Boolean).join(' ');
        }

        function toMilliseconds(seconds) {
            return !seconds || isNaN(seconds) ?
                seconds : seconds * SECOND_IN_MILLISECONDS;
        }

        function getTimeOrigin() {
           var timeOrigin = $window.performance.timeOrigin;
           return dataUtil.isNullOrUndefined(timeOrigin) ? $window.performance.timing.navigationStart : timeOrigin;
        }

        function getBackoffTimeInSeconds(retryCount, config) {
            config = config || {};
            config.exponentialBase = config.exponentialBase || EXPONENTIAL_BASE;
            config.backoffInterval = config.backoffInterval || BACKOFF_INTERVAL;
            config.failureBackoffMax = config.failureBackoffMax || FAILURE_BACKOFF_MAX;

            return !retryCount || isNaN(retryCount) ?
                retryCount :
                Math.min(Math.pow(config.exponentialBase, retryCount - 1) * config.backoffInterval, config.failureBackoffMax);
        }

        function matchesFormat(targetDate, format) {
            return moment(targetDate, format, true).isValid();
        }

        function formatDate(targetDate, format) {
            return (!isValidDate(targetDate) || !format) ?
                targetDate :
                moment(targetDate).format(format);
        }

        // Return the constructor for the class
        return (DateTimeUtil);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');
    app.factory('displayUtil', ["$q", "$window", "$injector", "ORIENTATION", "dataUtil", function($q, $window, $injector, ORIENTATION, dataUtil) {
        function isScreenPortrait() {
            // use match media to check screen orientation
            if (window.matchMedia) {
                return window.matchMedia('(orientation: portrait)').matches;
            }

            // if browser doesn't support matchMedia, use the old-school way
            return $window.innerHeight > $window.innerWidth;
        }

        function applyRotation(applicationProperties, defaultRotation, ignoreSameRotation) {
            var promise = $q.defer();
            var rotation = defaultRotation;

            var propertyRotation = (applicationProperties || {})['display.rotation'];
            if (!isNaN(propertyRotation)) {
                rotation = parseInt(propertyRotation, 10);
            }

            var onFail = function(message) {
                promise.reject(message);
            };

            var onSuccess = function(message) {
                promise.resolve(message);
            };

            var onRotationSet = function() {
                $injector.get('halMediaService').setCursorVisible(false, true);
                onSuccess(rotation);
            };

            var onCheckRotationState = function(same) {
                // if rotation is not the same, rotate the display
                if (!same) {
                    doSetRotation(rotation).then(onRotationSet, onFail);
                } else {
                    onFail('screen already at the same rotation');
                }
            };

            if (rotation != undefined) {
                if (ignoreSameRotation) {
                    doSetRotation(rotation).then(onRotationSet, onFail);
                } else {
                    checkRotationState(rotation).then(onCheckRotationState, onFail);
                }
            } else {
                doSetRotation('unlock').then(onRotationSet, onFail);
            }

            return promise.promise;
        }

        function doSetRotation(targetRotation) {
            var promise = $q.defer();

            var onFail = function(message) {
                promise.reject(message);
            };

            var onRotationSet = function() {
                promise.resolve();
            };

            var setRotationPromise = $injector.get('halMediaService').setDisplayRotation(targetRotation);
            if (setRotationPromise) {
                setRotationPromise.then(onRotationSet, onFail);
            } else {
                onFail('no setDisplayRotation implementation for the current device');
            }

            return promise.promise;
        }

        function checkRotationState(targetRotation) {
            var promise = $q.defer();

            var onFail = function(message) {
                promise.reject(message);
            };

            var onDisplayRotationRetrieved = function(currentConfig) {
                promise.resolve((currentConfig || {}).rotation == targetRotation);
            };

            // check current rotation
            var getRotationPromise = $injector.get('halMediaService').getDisplayRotation();
            if (getRotationPromise) {
                getRotationPromise.then(onDisplayRotationRetrieved, onFail);
            } else {
                onFail('no getDisplayRotation implementation for the current device');
            }

            return promise.promise;
        }

        function getOrientationValue(rotation, mappings) {
            var defaultRotation = ORIENTATION.unlock;
            if (dataUtil.isNullOrUndefined(rotation)) {
                return defaultRotation;
            }

            rotation = rotation.toString().toLowerCase();
            var orientationMatch = ORIENTATION[rotation];
            if (orientationMatch) {
                return orientationMatch;
            }

            return (mappings || {})[rotation] ||
                defaultRotation;
        }

        function lockOrientation(rotation, orientationMappings) {
            var orientation = getOrientationValue(rotation, orientationMappings);

            if (!$window.screen || (!$window.screen.orientation && !$window.screen.lockOrientation && !$window.screen.unlockOrientation)) {
                return $q.resolve(orientation);
            }

            if (orientation === ORIENTATION.unlock) {
               // unlockOrientation has been deprecated in latest version of cordova-screen-orientation plugin
               // can be remove after the plugin has been updated in ios
               if ($window.screen.unlockOrientation) {
                   $window.screen.unlockOrientation();
               }

               if ($window.screen.orientation.unlock){
                   $window.screen.orientation.unlock();
               }

               return $q.resolve(orientation);
            }

            //lockOrientation has been deprecated in latest version of cordova-screen-orientation plugin
            // can be remove after the plugin has been updated in ios
            if ($window.screen.lockOrientation) {
                $window.screen.lockOrientation(orientation);
                return $q.resolve(orientation);
            }

            return $window.screen.orientation.lock(orientation).then(function() {
                return orientation;
            });
        }

        function getScreenOrientation() {
            var screenOrientation = {};
            if ($window.screen && $window.screen.orientation) {
                screenOrientation.angle = $window.screen.orientation.angle;
                screenOrientation.type = $window.screen.orientation.type;
            }

            return screenOrientation;
        }

        // Return the constructor for the class
        return {
            applyRotation: applyRotation,
            isScreenPortrait: isScreenPortrait,
            lockOrientation: lockOrientation,
            getScreenOrientation: getScreenOrientation,
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');
    app.factory('domUtil', function() {
        function getElement(identifier) {
            return $angular.element(identifier);
        }

        function createElement(tag) {
            return $angular.element(tag)[0];
        }

        return {
            getElement: getElement,
            createElement: createElement
        };
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.factory('jwtUtil', ["DateTimeUtil", function(DateTimeUtil) {
        function decode(jwt) {
            try {
                var decodedJwt = jwt_decode(jwt);
                return decodedJwt;
            } catch (error) {
                return;
            }
        }

        /**
         * Get expiration time from a JWT (exp claim in milliseconds).
         * @param {string} jwt - JWT token string
         * @return {number|undefined} - Expiration timestamp in milliseconds, or undefined if missing/invalid
         */
        function getExpiry(jwt) {
            if (!jwt) {
                return undefined;
            }

            try {
                var decoded = decode(jwt);
                if (decoded && decoded.exp != null) {
                    // DateTimeUtil exposes toMilliseconds on its prototype, not as a static method on the constructor.
                    // Call on an instance so production code works without a test-only decorator.
                    return (new DateTimeUtil()).toMilliseconds(decoded.exp);
                }
            } catch (error) {
                // ignore
            }

            return undefined;
        }

        return {
            decode: decode,
            getExpiry: getExpiry
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');
    app.service('postMessagingUtil', ["CRESTRON_MODE", "POST_MESSAGES", function(CRESTRON_MODE, POST_MESSAGES) {
        var processedMappings = {};
        processedMappings[CRESTRON_MODE.STANDBY] = POST_MESSAGES.STANDBY_PROCESSED;
        processedMappings[CRESTRON_MODE.OCCUPIED] = POST_MESSAGES.OCCUPIED_PROCESSED;
        processedMappings[CRESTRON_MODE.UNOCCUPIED] = POST_MESSAGES.UNOCCUPIED_PROCESSED;
        var loadedMappings = {};
        loadedMappings[CRESTRON_MODE.STANDBY] = POST_MESSAGES.STANDBY_LOADED;
        loadedMappings[CRESTRON_MODE.OCCUPIED] = POST_MESSAGES.OCCUPIED_LOADED;
        loadedMappings[CRESTRON_MODE.UNOCCUPIED] = POST_MESSAGES.UNOCCUPIED_LOADED;
        var applicableCrestronModes = [CRESTRON_MODE.OCCUPIED, CRESTRON_MODE.UNOCCUPIED];
        function getProcessedMessageFromCrestronMode(crestronMode) {
            return processedMappings[crestronMode] || POST_MESSAGES.UNKNOWN;
        }

        function getLoadedMessageFromCrestronMode(crestronMode) {
            return loadedMappings[crestronMode] || POST_MESSAGES.UNKNOWN;
        }

        function isCrestronModeApplicable(mode) {
            return applicableCrestronModes.indexOf(mode) != -1;
        }

        function getCrestronPersistData() {
            return POST_MESSAGES.PERSIST_DATA;
        }

        return {
            getProcessedMessageFromCrestronMode: getProcessedMessageFromCrestronMode,
            getLoadedMessageFromCrestronMode: getLoadedMessageFromCrestronMode,
            isCrestronModeApplicable: isCrestronModeApplicable,
            getCrestronPersistData: getCrestronPersistData
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.factory('sessionStorageUtil', ["$window", function($window) {
        var URL_PARAMETERS_SESSION_KEY = 'urlparameters';
        function getUrlParameters() {
            var storageParameters = $window.sessionStorage.getItem(URL_PARAMETERS_SESSION_KEY);
            return storageParameters ? JSON.parse(storageParameters) : {};
        }

        return {
            getUrlParameters: getUrlParameters
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var app = $angular.module('core');

    // Reads file into string
    app.factory('VersionCompare', ["COMPARE_VALUE", function(COMPARE_VALUE) {
        // Return the constructor for the class
        return (VersionCompare);

        // Define the instance methods and properties
        // lexicographical: true -  compares each part of the version strings lexicographically instead of
        // naturally; this allows suffixes such as "b" or "dev" but will cause "1.10" to be considered smaller than
        // "1.2".

        // zeroExtend: true - changes the result if one version string has less parts than the other. In
        // this case the shorter string will be padded with "zero" parts instead of being considered smaller.
        // more info from https://gist.github.com/TheDistantSea/8021359
        function VersionCompare() {
            this.compare = function(v1, v2, options) {
                var lexicographical = options && options.lexicographical;
                var zeroExtend = options && options.zeroExtend;
                var v1parts = v1.split('.');
                var v2parts = v2.split('.');

                function isValidPart(x) {
                    return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x);
                }

                if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) {
                    // NaN
                    return false;
                }

                if (zeroExtend) {
                    while (v1parts.length < v2parts.length) v1parts.push('0');
                    while (v2parts.length < v1parts.length) v2parts.push('0');
                }

                if (!lexicographical) {
                    v1parts = v1parts.map(Number);
                    v2parts = v2parts.map(Number);
                }

                for (var i = 0; i < v1parts.length; ++i) {
                    if (v2parts.length == i) {
                        return true;
                    }

                    if (v1parts[i] == v2parts[i]) {
                        continue;
                    } else if (v1parts[i] > v2parts[i]) {
                        return true;
                    } else {
                        return false;
                    }
                }

                if (v1parts.length != v2parts.length) {
                    return false;
                }

                return true;
            };

            this.compareVersion = function(compareFrom, compareTo) {
                if (typeof compareFrom !== 'string' || typeof compareTo !== 'string') {
                    return COMPARE_VALUE.INVALID;
                }
                var fromSplits = compareFrom.split('.');
                var toSplits = compareTo.split('.');
                var count = Math.max(fromSplits.length, toSplits.length);
                for (var i = 0; i < count; ++i) {
                    var fromValue = parseInt(fromSplits[i], 10);
                    fromValue = isNaN(fromValue) ? 0 : fromValue;
                    var toValue = parseInt(toSplits[i], 10);
                    toValue = isNaN(toValue) ? 0 : toValue;
                    if (fromValue > toValue) {
                        return COMPARE_VALUE.BIGGER;
                    }
                    if (fromValue < toValue) {
                        return COMPARE_VALUE.SMALLER;
                    }
                }
                return COMPARE_VALUE.SAME;
            };
        }
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');
    app.factory('versionUtil', ["$q", "$window", "coreConfigurationService", "appspaceApiService", "logService", function(
        $q,
        $window,
        coreConfigurationService,
        appspaceApiService,
        logService) {
        var acRegex = /(\d+\.\d+\.\d+)(?:-g[a-z0-9]+)?(-ac\.\d+(?:\.\d+)*)?/i;
        var KEY_APPSPACE_NAMESPACE = 'appspace';
        var KEY_SERVER_VERSION = 'serverVersion';

        function cleanAppspaceVersion(version) {
            if (!version) {
                return version;
            }

            var resultRegex = version.match(acRegex);
            if (!resultRegex) {
                return version;
            }

            if (resultRegex.length < 3) {
                return version;
            }

            return resultRegex[1] + // actual version
                (resultRegex[2] || ''); // error handling non-ac version
        }

        function isGreaterThan(versionFrom, versionTo) {
            return compare(versionFrom, versionTo, '>');
        }

        function isLessThan(versionFrom, versionTo) {
            return compare(versionFrom, versionTo, '<');
        }

        function isGreaterThanOrEqual(versionFrom, versionTo) {
            return compare(versionFrom, versionTo, '>=');
        }

        function isLessThanOrEqual(versionFrom, versionTo) {
            return compare(versionFrom, versionTo, '<=');
        }

        function compare(versionFrom, versionTo, operator) {
            if (!$window.compareVersions) {
                throw new Error('compareVersions is undefined');
            }

            return $window.compareVersions.compare(versionFrom, versionTo, operator);
        }

        function getAppspaceVersionFromServer(logKey, customUrl) {
            var promise = $q.defer();
            appspaceApiService.getAppspaceVersion(customUrl).then(function(version) {
                if (!version) {
                    logService.error(logKey, 'Failed to get Appspace version from server.');
                    promise.reject();
                    return;
                }

                var cleanedVersion = cleanAppspaceVersion(version);
                if (!customUrl) {
                    coreConfigurationService.set(KEY_APPSPACE_NAMESPACE, KEY_SERVER_VERSION, cleanedVersion);
                }

                promise.resolve(cleanedVersion);
            }, promise.reject);
            return promise.promise;
        }

        function getAppspaceVersionFromCache() {
            return coreConfigurationService.get(KEY_APPSPACE_NAMESPACE, KEY_SERVER_VERSION);
        }

        function getAppspaceVersion(logKey, appspaceUrl) {
            var versionFromCache = getAppspaceVersionFromCache();
            return (appspaceUrl || !versionFromCache) ?
                getAppspaceVersionFromServer(logKey, appspaceUrl) :
                $q.resolve(versionFromCache);
        }

        function isAppspaceVersionValid(appspaceVersion, versionConfiguration) {
            if (!appspaceVersion || !versionConfiguration || !versionConfiguration.CLOUD || !versionConfiguration.GLOBAL) {
                return false;
            }

            var isCloudVersionValid = function(version) {
                return isGreaterThanOrEqual(version, versionConfiguration.CLOUD.MIN) &&
                    isLessThan(version, versionConfiguration.CLOUD.MAX);
            };

            var isGlobalVersionValid = function(version) {
                return isGreaterThanOrEqual(version, versionConfiguration.GLOBAL);
            };

            return isCloudVersionValid(appspaceVersion) || isGlobalVersionValid(appspaceVersion);
        }

        function isVersionSupported(supportedVersions, logKey, appspaceUrl) {
            if (!supportedVersions) {
                return $q.resolve(false);
            }

            var promise = $q.defer();
            getAppspaceVersion(logKey, appspaceUrl).then(function(version) {
                promise.resolve(isAppspaceVersionValid(version, supportedVersions));
            }, function() {
                promise.resolve(false);
            });
            return promise.promise;
        }

        return {
            cleanAppspaceVersion: cleanAppspaceVersion,
            isGreaterThan: isGreaterThan,
            isGreaterThanOrEqual: isGreaterThanOrEqual,
            isLessThan: isLessThan,
            isLessThanOrEqual: isLessThanOrEqual,
            isVersionSupported: isVersionSupported,
            isAppspaceVersionValid: isAppspaceVersionValid,
            getAppspaceVersion: getAppspaceVersion,
            getAppspaceVersionFromCache: getAppspaceVersionFromCache,
            getAppspaceVersionFromServer: getAppspaceVersionFromServer
        };
    }]);
})(window.angular);

(function($angular) {

    "use strict";

	//declare media module and its dependencies 
    var app = $angular.module('deviceManager', ['core']);
})(window.angular);
(function($angular) {
    'use strict';

    var app = $angular.module('deviceManager');

    app.service('deviceHealthService', ["$q", "$injector", "$timeout", "telemetryService", "deviceAPIService", "deviceRegistrationService", "pushNotificationClientService", "halNetworkService", "logService", "storageLocalStorageService", "STORAGE_COLLECTION_KEY", "SYSTEM_DATA", "STORAGE_KEY", "SERVICES", "dataUtil", "versionUtil", "deviceIdentityService", "configurationService", "syncService", "embedProfileService", "SYNC_EVENT", function(
        $q,
        $injector,
        $timeout,
        telemetryService,
        deviceAPIService,
        deviceRegistrationService,
        pushNotificationClientService,
        halNetworkService,
        logService,
        storageLocalStorageService,
        STORAGE_COLLECTION_KEY,
        SYSTEM_DATA,
        STORAGE_KEY,
        SERVICES,
        dataUtil,
        versionUtil,
        deviceIdentityService,
        configurationService,
        syncService,
        embedProfileService,
        SYNC_EVENT) {
        var deviceAPI = deviceAPIService;
        var reports = [];
        var sendHealthInterval = 60 * 1000;
        var sendHealthHandler;
        var registrationService = deviceRegistrationService;
        var networkService = halNetworkService;
        var isStop = false;
        var LOG_KEY = 'appspace.core.devicemanager';
        var SUPPORTED_SERVICE_VERSIONS = {
            CLOUD: {
                MIN: '8.10.0-ac.3.2',
                MAX: '8.10.0'
            },
            GLOBAL: '8.10.0'
        };
        var FEATURE_FLAG = {
            PUSH_HEALTH_STATUS: 'feature.devices.pnsdevicestatus',
        };
        var updateHealthStatusByPNSSupported;
        var requiresPnsRestart = false;
        var hasInitialized = false;
        var isReadyDeferred = $q.defer();
        var deviceStateService;
        var debouncePromise;
        var DEBOUNCE_TIME_MILLISECONDS = 500;

        function start() {
            if (!hasInitialized) {
                init();
                return;
            }

            if (updateHealthStatusByPNSSupported) {
                logService.info(LOG_KEY, 'Health service started using PNS.');
                telemetryService.addToQueue({
                    module: 'device-health-status',
                    type: 'pns-enabled',
                    description: 'Device health status is being reported using Push Notification Service (PNS).',
                });
                return;
            }

            logService.info(LOG_KEY, 'Health service started using polling.');
            isStop = false;
            startTimer(1);
        }

        function init() {
            hasInitialized = true;
            debounceProcessPushHealthUpdateMode();
            syncService.subscribe(onSyncServiceUpdate);
        }

        function onSyncServiceUpdate(event) {
            if (event.event !== SYNC_EVENT.SCRIPT_SAVED) {
                return;
            }

            debounceProcessPushHealthUpdateMode();
        }

        function updatePushHealthStatusFeature(supported) {
            if (supported === updateHealthStatusByPNSSupported) {
                return $q.resolve();
            }

            if (embedProfileService.isPreviewMode()) {
                logService.warn(LOG_KEY, 'Update push health status feature is not supported in preview mode.');
                return $q.resolve();
            }

            var deferred = $q.defer();
            // PNS restart is only required when push health status is explicitly enabled during runtime
            requiresPnsRestart = updateHealthStatusByPNSSupported === false && supported === true;
            updateHealthStatusByPNSSupported = supported;
            deviceIdentityService.updateDevicePushHealthStatusFeature(supported).then($angular.noop, $angular.noop).finally(function() {
                deferred.resolve(true);
            });
            return deferred.promise;
        }

        function cancelDebounceProcessPushHealthUpdateMode() {
            if (!debouncePromise) {
                return;
            }

            $timeout.cancel(debouncePromise);
            debouncePromise = undefined;
        }

        function debounceProcessPushHealthUpdateMode() {
            cancelDebounceProcessPushHealthUpdateMode();
            debouncePromise = $timeout(function doProcess() {
                processHealthUpdateMode();
            }, DEBOUNCE_TIME_MILLISECONDS);
        }

        function processHealthUpdateMode() {
            return isUpdateHealthStatusByPNSSupported().then(function(supported) {
                updatePushHealthStatusFeature(supported).then(function(isUpdated) {
                    isReadyDeferred.resolve();
                    if (!isUpdated) {
                        return;
                    }

                    // restarting the service
                    reset();
                    start();
                    if (!requiresPnsRestart) {
                        return;
                    }

                    var pnsCurrentState = pushNotificationClientService.getCurrentState();
                    if (!pnsCurrentState || !pnsCurrentState.isStarted) {
                        return;
                    }

                    // ISSUE: Device Service will reject existing connected PNS client's status update if it came before the device identity update
                    // RESOLUTION: Restart the PNS client if the push health status feature was enabled after the PNS client had already connected to
                    // ensure that the current connected client re-establishes its connection to properly reflect the updated feature setting
                    var message = 'Restarting connected PNS client to update push health status feature setting';
                    logService.info(LOG_KEY, message);
                    pushNotificationClientService.restart(message);
                    telemetryService.addToQueue({
                        module: 'device-health-status',
                        type: 'pns-client-restart',
                        description: 'Restarting connected PNS client to update push health status feature setting',
                    });
                });
            });
        }

        function isUpdateHealthStatusByPNSSupported() {
            return versionUtil.isVersionSupported(SUPPORTED_SERVICE_VERSIONS, LOG_KEY).then(function(supported) {
                if (supported && isFeatureFlagValid(FEATURE_FLAG.PUSH_HEALTH_STATUS)) {
                    return true;
                }

                logService.debug(LOG_KEY, 'Health Service using PNS is not supported.');
                return false;
            });
        }

        function isFeatureFlagValid(featureFlag) {
            return dataUtil.isValueTrue(configurationService.getCurrentProperty(featureFlag));
        }

        function stop() {
            logService.debug(LOG_KEY, 'Health service stopped');
            isReadyDeferred = $q.defer();
            reset();
        }

        function reset() {
            isStop = true;
            clearTimer();
        }

        function reportStatus(state, desc, notifyImmediately) {
            if (!notifyImmediately) {
                notifyImmediately = false;
            }

            if (!desc) {
                desc = '';
            }

            state = (state != true) ? false : true;
            reports.push({state: state, description: desc});

            if (notifyImmediately) {
                sendStatus();
            }
        }

        function startTimer(interval) {
            if (isStop || updateHealthStatusByPNSSupported) {
                return;
            }

            var nextInterval = sendHealthInterval;
            if (interval) {
                nextInterval = interval;
            }

            clearTimer();
            sendHealthHandler = $timeout(sendStatus, nextInterval);
        }

        function clearTimer() {
            if (sendHealthHandler) {
                $timeout.cancel(sendHealthHandler);
                sendHealthHandler = null;
            }
        }

        function sendStatus() {
            clearTimer();

            var status = true;
            for (var i = 0; i < reports.length; i++) {
                var report = reports[i];
                if (!report.state) {
                    status = false; break;
                }
            }

            // reset reports
            reports = [];

            var deviceState = (status == true) ? 'Online' : 'Failed';

            var playerId = registrationService.getPlayerId();

            if (!playerId) {
                startTimer();
                return;
            }

            var onSendHealthCompleted = function() {
                startTimer();
            };

            var sendHealth = function(address) {
                logService.debug(LOG_KEY, 'sending ' + deviceState + ' back to Appspace');
                deviceAPI.sendHealthStatus(registrationService.getPlayerId(), deviceState, address).then(onSendHealthCompleted, onSendHealthCompleted);
            };

            var ipAddress = SYSTEM_DATA.DEFAULT_IP_ADDRESS;
            networkService.getIpAddress().then(function(response) {
                if (!response || !response.address) {
                    return;
                }

                ipAddress = response.address;
            }, $angular.noop).finally(function() {
                sendHealth(ipAddress);
            });
        }

        function setHealthStatus(status) {
            if (dataUtil.isObjectEmptyOrUndefined(status)) {
                clearHealthStatus();
                return;
            }

            storageLocalStorageService.set(STORAGE_COLLECTION_KEY.DEVICE_INFO, STORAGE_KEY.DEVICE_HEALTH_STATUS, JSON.stringify(status));
        }

        function getHealthStatus() {
            // tryParseJSON will return false if unable to parse JSON
            var status = dataUtil.tryParseJSON(storageLocalStorageService.get(STORAGE_COLLECTION_KEY.DEVICE_INFO, STORAGE_KEY.DEVICE_HEALTH_STATUS));
            return status || undefined;
        }

        function clearHealthStatus() {
            storageLocalStorageService.removeKey(STORAGE_COLLECTION_KEY.DEVICE_INFO, STORAGE_KEY.DEVICE_HEALTH_STATUS);
        }

        function getDeviceStateService() {
            if (!deviceStateService) {
                deviceStateService = $injector.get('deviceStateService');
            }

            return deviceStateService;
        }

        function isEnabled() {
            return getDeviceStateService().isServiceAvailable(SERVICES.HEALTH);
        }

        function isReady() {
            if (!getDeviceStateService().isRegistered()) {
                // Unregistered state or embedded preview mode
                isReadyDeferred.resolve();
            } else if (!isEnabled()) {
                // Ensure the devices that have health service disabled
                // will always send the push health status feature flag as false
                updatePushHealthStatusFeature(false, true).then(isReadyDeferred.resolve);
            }

            return isReadyDeferred.promise;
        }

        return {
            start: start,
            stop: stop,
            reportStatus: reportStatus,
            getHealthStatus: getHealthStatus,
            setHealthStatus: setHealthStatus,
            clearHealthStatus: clearHealthStatus,
            isReady: isReady,
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('deviceManager');

    app.service('deviceRegistrationService', ["$q", "dataUtil", "deviceAPIService", "logService", "halPlatformService", "halNetworkService", "coreConfigurationService", "authenticationAPIService", "deviceIdentityService", "deviceTokenService", "$injector", "jwtUtil", "registrationRestorationService", function($q, dataUtil, deviceAPIService, logService, halPlatformService,
        halNetworkService, coreConfigurationService, authenticationAPIService, deviceIdentityService, deviceTokenService,
        $injector, jwtUtil, registrationRestorationService) {
        var LOG_KEY = 'appspace.core.devicemanager';
        var deviceAPI = deviceAPIService;
        var halPlatform = halPlatformService;
        var halNetwork = halNetworkService;
        var configService = coreConfigurationService;
        var authenticationService = authenticationAPIService;

        var _isRegistered = false;
        var stepRetrieveMachineInfo = {
            step: retrieveMachineInfo,
            progressMessage: 'Retrieving machine info',
            errorMessage: 'Fail to retrieve machine info'
        };

        var stepProcessQueryStringInformation = {
            step: processQueryStringInformation,
            progressMessage: 'Processing query string information',
            errorMessage: 'Fail to process query string information'
        };

        var stepRetrieveAPIKey = {
            step: retrieveAPIKey,
            progressMessage: 'Retrieving account\'s API key',
            errorMessage: 'Fail to retrieve account\'s API key'
        };

        var stepRetrieveDeviceId = {
            step: retrieveDeviceId,
            progressMessage: 'Retrieving machine ID from Appspace',
            errorMessage: 'Fail to retrieve machine ID from Appspace'
        };

        var stepCheckIfDeviceIsBounded = {
            step: checkIfDeviceIsBounded,
            progressMessage: 'Checking device bind status from Appspace',
            errorMessage: 'Fail to check device bind status'
        };

        var stepUnbindDevice = {
            step: unbindDevice,
            progressMessage: 'Unbinding device from Appspace',
            errorMessage: 'Fail to unbind device'
        };

        var stepBindDevice = {
            step: bindDevice,
            progressMessage: 'Binding device to Appspace',
            errorMessage: 'Fail to bind device to Appspace'
        };

        var stepRetrievePlayerGuid = {
            step: retrievePlayerGuid,
            progressMessage: 'Retrieving player GUID from Appspace',
            errorMessage: 'Fail to retrieve player GUID'
        };

        var stepSendDeviceModelName = {
            step: sendDeviceModelName,
            progressMessage: 'Send device model to Appspace',
            errorMessage: 'Fail to send device model to Appspace'
        };

        var stepSendDeviceGuid = {
            step: sendDeviceGuid,
            progressMessage: 'Send device GUID to Appspace',
            errorMesssage: 'Fail to send device GUID to Appspace'
        };

        var stepCreateDevice = {
            step: createDevice,
            progressMessage: 'Creating device on Appspace',
            errorMessage: 'Fail to create device on Appspace'
        };

        var stepClaimTokens = {
            step: claimTokens,
            progressMessage: 'Claiming device constellation tokens',
            errorMessage: 'Failed to claim device constellation tokens'
        };

        var stepRecreateDevice = {
            step: recreateDevice,
            progressMessage: 'Recreating device on Appspace',
            errorMessage: 'Fail to recreate device on Appspace'
        };

        var stepDeleteDevice = {
            step: deleteDevice,
            progressMessage: 'Unregistering device from Appspace',
            errorMessage: 'Fail to unregister device from Appspace'
        };

        var stepDeleteRegistrationInfo = {
            step: deleteRegistrationInfo,
            progressMessage: 'Deleting registration information',
            errorMessage: 'Failed to delete registration information'
        };

        function registerByPolicy(policy) {
            return executeRegistration(policy);
        }

        function registerV2(config) {
            return register(config.devicename, config.username, config.password, config.apikey, config.token);
        }

        function register(deviceName, username, password, apikey, token) {
            return executeRegistration({
                device_name: deviceName,
                api_key: apikey,
                security_token: token
            }, username, password);
        }

        function deleteRegistrationInfo(config) {
            registrationRestorationService.deleteRegistrationInfo(getPlayerId()).then(function() {
                doNextStep(config);
            }, function(error) {
                onStepFail(config, error);
            });
        }

        function deleteDevice(config) {
            var finalize = function() {
                clearConfigurations();
                doNextStep(config);
            };

            // If we need to clean the configurations only, skip deleting device
            // (use case: sync service receives 410 when retrieving timestamp)
            if (config.cleanOnly) {
                finalize();
                return;
            }

            logService.debug(LOG_KEY, 'Unregistering device using ' + (config.tokens ? 'constellation tokens' : 'JWT'));
            deviceAPIService.deleteDevice(config).then(finalize, function(error) {
                logService.error(LOG_KEY, 'Failed to unregister device due to: ' + JSON.stringify(error));
                finalize();
            });
        }

        function doCreateDevice(policy) {
            var promise = $q.defer();
            deviceAPIService.createDevice(policy).then(function(response) {
                coreConfigurationService.setRegistrationPolicyId(policy.policy_id);
                policy.registrationToken = response;
                promise.resolve(response);
            }, promise.reject);
            return promise.promise;
        }

        function createDevice(config) {
            var onFailedToCreateDevice = function(error) {
                onStepFail(config, 'Failed to create device: ' + JSON.stringify(error));
            };

            if (!config || !config.policy) {
                onFailedToCreateDevice('incomplete policy');
                return;
            }

            config.policy.fingerprint = config.fingerprint;
            doCreateDevice(config.policy).then(function(response) {
                if (!response) {
                    onFailedToCreateDevice('invalid response');
                    return;
                }

                deviceTokenService.save(response);
                doNextStep(config);
            }, onFailedToCreateDevice);
        }

        function claimTokens(config) {
            var policy = config.policy || {};
            var registrationToken = jwtUtil.decode(policy.registrationToken) || {};
            var metadata = registrationToken.metadata || {};
            var refreshToken = metadata.refreshToken;
            var deviceGuid = metadata.deviceId;
            var apiUrl = metadata.instanceUrl;
            var finalize = function(message, isError) {
                // Tokens claiming is not critical, so we can continue even if it fails
                if (isError) {
                    logService.warn(LOG_KEY, 'Failed to claim tokens due to: ' + message);
                }

                doNextStep(config);
            };

            if (!refreshToken || !deviceGuid || !apiUrl) {
                doNextStep(config);
                return;
            }

            deviceTokenService.refreshConstellationTokensByRefreshToken({
                registrationToken: refreshToken,
                deviceGuid: deviceGuid,
                apiUrl: apiUrl,
            }).then(function(tokens) {
                if (!tokens || !tokens.accessToken) {
                    finalize('Invalid token response: ' + JSON.stringify(tokens), true);
                    return;
                }

                doNextStep(config);
            }, function(error) {
                var errorMessage = dataUtil.isString(error) ? error : JSON.stringify(error);
                finalize(errorMessage, true);
            });
        }

        function getRegistrationUtil() {
            return $injector.get('RegistrationUtil');
        }

        function recreateDevice(config) {
            var registrationInfo;
            var onFailedToRecreateDevice = function(error) {
                onStepFail(config, 'Failed to recreate device: ' + JSON.stringify(error));
            };

            if (!config || !config.policy) {
                onFailedToRecreateDevice('incomplete policy');
                return;
            }

            config.policy.fingerprint = config.fingerprint;
            var RegistrationUtil = getRegistrationUtil();
            var cleanUpDevice = function() {
                RegistrationUtil.cleanUpRegistrationInfo();
                RegistrationUtil.saveRegistrationInfo(registrationInfo);
                RegistrationUtil.postRegistrationCallback();
                doNextStep(config);
            };

            var onUnregistrationFailed = function(error) {
                logService.warn(LOG_KEY, 'Failed to unregister device: ' + JSON.stringify(error));
            };

            var onDeviceCreated = function(response) {
                if (!response) {
                    onFailedToRecreateDevice('invalid response');
                    return;
                }

                registrationInfo = deviceTokenService.decodeRegistrationInfo(response);
                var difference = RegistrationUtil.isRegistrationInfoDifferent(registrationInfo) || {};
                if (!difference.isDifferent) {
                    onFailedToRecreateDevice('registration info is unchanged');
                    return;
                }

                // Unregister device if account ID is different, followed by
                // cleaning up device, updating registration info, and then restarting device
                unregister(!difference.accountId).then(undefined, onUnregistrationFailed).finally(cleanUpDevice);
            };

            var onDeviceNameProcessed = function(deviceName) {
                if (!deviceName) {
                    onFailedToRecreateDevice('no device name');
                    return;
                }

                config.policy.device_name = deviceName;
                doCreateDevice(config.policy).then(onDeviceCreated, onFailedToRecreateDevice);
            };

            RegistrationUtil.processDeviceName(undefined, true).then(onDeviceNameProcessed, onFailedToRecreateDevice);
        }

        function recreate(policy) {
            var deferred = $q.defer();
            var config = initConfig(deferred);
            config.policy = policy;
            config.steps.push(stepRetrieveMachineInfo);
            config.steps.push(stepRecreateDevice);
            config.steps.push(stepClaimTokens);

            doNextStep(config);
            return deferred.promise;
        }

        function registerByPolicyId(policy) {
            var deferred = $q.defer();
            var config = initConfig(deferred);
            config.policy = policy;
            config.steps.push(stepProcessQueryStringInformation);
            config.steps.push(stepRetrieveMachineInfo);
            config.steps.push(stepCreateDevice);
            config.steps.push(stepClaimTokens);

            doNextStep(config);
            return deferred.promise;
        }

        function executeRegistration(policy, username, password) {
            if (policy.isPolicyIdBased) {
                return registerByPolicyId(policy);
            }

            var deferred = $q.defer();
            var config = initConfig(deferred);
            config.policy = policy;
            config.username = username;
            config.password = password;
            config.isPolicyBased = config.policy.capabilities && config.policy.capabilities.indexOf('PolicyBasedRegistration') > -1;
            authenticationService.setUserInfo({
                apikey: config.policy.api_key,
                token: config.policy.security_token
            });

            config.steps.push(stepRetrieveMachineInfo);
            config.steps.push(stepRetrieveAPIKey);
            config.steps.push(stepRetrieveDeviceId);
            config.steps.push(stepCheckIfDeviceIsBounded);
            config.steps.push(stepUnbindDevice);
            config.steps.push(stepBindDevice);
            config.steps.push(stepRetrievePlayerGuid);
            config.steps.push(stepSendDeviceModelName);
            config.steps.push(stepSendDeviceGuid);

            doNextStep(config);

            return deferred.promise;
        }

        function unregister(cleanOnly) {
            var deferred = $q.defer();
            var config = initConfig(deferred);
            config.jwt = deviceTokenService.getToken();
            config.tokens = deviceTokenService.getConstellationTokens();
            config.steps.push(stepDeleteRegistrationInfo);
            if (config.jwt || config.tokens) {
                config.cleanOnly = cleanOnly;
                config.steps.push(stepDeleteDevice);
            } else {
                config.steps.push(stepRetrieveMachineInfo);
                config.steps.push(stepRetrieveDeviceId);
                config.steps.push(stepCheckIfDeviceIsBounded);
                config.steps.push(stepUnbindDevice);
            }

            doNextStep(config);
            return deferred.promise;
        }

        function isRegistered() {
            return _isRegistered;
        }

        function getPlayerId() {
            return configService.get('device', 'playerguid');
        }

        // ///// Private ///////
        function initConfig(promise) {
            return {
                currentStep: -1,
                steps: [],
                promise: promise,
                policy: {}
            };
        }

        function doNextStep(config) {
            var step = config.steps[config.currentStep];
            if (step) {
                if (config.error) {
                    config.promise.reject(config.errorMessage || step.errorMessage);
                    return;
                }

                config.promise.notify({
                    step: config.currentStep + 1,
                    total: config.steps.length,
                    message: step.progressMessage,
                    skip: config.skip ? true : false
                });
            }

            // get the next step
            step = config.steps[++config.currentStep];
            if (step) {
                // reset the skip flag to false
                config.skip = false;
                step.step(config);
            } else {
                config.promise.resolve(config);
            }
        }

        function onStepFail(config, errorMessage, err) {
            if (errorMessage) {
                logService.warn(LOG_KEY, errorMessage, err);
            }

            config.error = true;
            doNextStep(config);
        }

        function retrieveMachineInfo(config) {
            halPlatform.getFingerprint().then(function(res) {
                config.fingerprint = res.fingerprint;
                config.deviceModel = halPlatform.getAppspaceDeviceType();
                halNetwork.getIpAddress().then(function(res) {
                    config.policy.device_ip = res.address;
                    doNextStep(config);
                }, function(err) {
                    config.policy.device_ip = '127.0.0.1';
                    doNextStep(config);
                });
            }, function(err) {
                onStepFail(config, 'Failed to get device fingerprint: ', err);
            });
        }

        function processQueryStringInformation(config) {
            halPlatformService.updateReferenceDeviceId(halPlatformService.getReferenceDeviceIdKey());
            halPlatformService.updateIntegrationType();
            doNextStep(config);
        }

        function retrieveAPIKey(config) {
            // if we have existing api key, directly proceed to next step
            if (config.policy.api_key) {
                skipStep(config);
                return;
            }

            authenticationService.retrieveApiKey(config.username, config.password).then(function(apikey) {
                config.policy.api_key = apikey;
                doNextStep(config);
            }, function(err) {
                onStepFail(config, 'Failed to get api key: ', err);
            });
        }

        function retrieveDeviceId(config) {
            if (!config.fingerprint) {
                onStepFail(config);
                return;
            }

            deviceAPI.retrieveDeviceGuid(config.fingerprint).then(function(res) {
                config.policy.physical_device_id = res;
                doNextStep(config);
            }, function(err) {
                onStepFail(config, 'Failed to get device guid: ', err);
            });
        }

        function checkIfDeviceIsBounded(config) {
            deviceAPI.isDeviceBounded(config.policy.physical_device_id).then(function(res) {
                // skip the next step if device not bounded
                config.deviceBounded = res;
                doNextStep(config);
            }, function(err) {
                onStepFail(config, 'Failed to check if device is bound: ', err);
            });
        }

        function clearConfigurations() {
            configService.removeConfiguration('device', 'playerguid');
            configService.removeConfiguration('appspace', 'accountId');
            configService.removeConfiguration('appspace', 'instanceGuid');
            configService.removeConfiguration('device', 'type');
            configService.removeReferenceDeviceId();
            configService.removeReferenceDeviceName();
            configService.removeIntegrationType();
            if (!halPlatformService.isUrlPolicyModeEnforced()) {
                 // Remove registration policy ID only if URL policy mode is not enforced
                configService.removeRegistrationPolicyId();
            }

            deviceTokenService.clear();
            deviceIdentityService.clean();
            deviceIdentityService.stop();
        }

        function unbindDevice(config) {
            // skip this step if device is not bounded
            if (!config.deviceBounded) {
                clearConfigurations();
                skipStep(config);
                return;
            }

            deviceAPI.unbindDevice(config.policy.physical_device_id).then(function(res) {
                config.playerGuid = undefined;
                _isRegistered = false;
                clearConfigurations();
                doNextStep(config);
            }, function(err) {
                onStepFail(config, 'Failed to unbind device: ', err);
            });
        }

        function bindDevice(config) {
            config.policy.device_name = config.policy.device_name || config.fingerprint;

            var onDeviceBinded = function(res) {
                config.dei = res;
                _isRegistered = true;
                configService.set('device', 'type', config.deviceModel);
                doNextStep(config);
            };

            var onDeviceFailToBind = function(err) {
                if (err == 'max exceeded') {
                    config.errorMessage = 'License limit has been reached';
                } else if (typeof err == 'string' && err.toLowerCase().indexOf('license limit has been reached') > -1) {
                    config.errorMessage = err;
                }

                onStepFail(config, 'Failed to bind device: ', err);
            };

            deviceIdentityService.generateDeviceIdentity().then(function(deviceIdentity) {
                // use the old bind device by policy if we don't have device identity AND we are not policy based registration
                var isDeviceIdentityValid = !dataUtil.isObjectEmpty(deviceIdentity);
                if (!isDeviceIdentityValid && !config.isPolicyBased) {
                    logService.debug(LOG_KEY, 'Binding device via legacy');
                    deviceAPI.bindDevice(config.policy.physical_device_id, config.policy.device_name, config.policy.device_ip)
                        .then(onDeviceBinded, onDeviceFailToBind);
                    return;
                }

                config.policy.device_identity = deviceIdentity;
                logService.debug(LOG_KEY, 'Device identity:' + JSON.stringify(deviceIdentity));
                logService.debug(LOG_KEY, 'Binding device via policy (POST)');
                deviceAPI.bindDeviceByPolicy(config.policy).then(function(res) {
                    if (isDeviceIdentityValid) {
                        deviceIdentityService.saveIdentity(deviceIdentity);
                    }

                    onDeviceBinded(res);
                }, onDeviceFailToBind);
            });
        }

        function retrievePlayerGuid(config) {
            deviceAPI.retrievePlayerGuid(config.fingerprint, config.dei).then(function(res) {
                config.playerGuid = res;
                configService.set('device', 'playerguid', res);
                doNextStep(config);
            }, function(err) {
                onStepFail(config, 'Failed to retrieve player guid: ', err);
            });
        }

        function sendDeviceModelName(config) {
            // if this is a policy based reg, skip sending device model name
            if (config.isPolicyBased) {
                skipStep(config);
                return;
            }

            deviceAPI.setProperty(config.playerGuid, 'Player.Model.Name', config.deviceModel).then(function(res) {
                doNextStep(config);
            }, function(err) {
                onStepFail(config, 'Failed to send device model: ', err);
            });
        }

        function sendDeviceGuid(config) {
            deviceAPI.setProperty(config.playerGuid, 'Player.GUID', config.playerGuid).then(function(res) {
                doNextStep(config);
            }, function(err) {
                onStepFail(config, 'Failed to send device guid: ', err);
            });
        }

        function skipStep(config) {
            config.skip = true;
            doNextStep(config);
        }

        return {
            register: register,
            registerV2: registerV2,
            registerByPolicy: registerByPolicy,
            unregister: unregister,
            recreate: recreate,
            isRegistered: isRegistered,
            getPlayerId: getPlayerId
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    // declare hal module and its dependencies
    $angular.module('hal', ['network', 'core', 'media', 'AppCommon']);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    var printerName = 'cordovaBrotherPrinter';

    app.factory(printerName, ["$window", "$q", "logService", "dataUtil", function($window, $q, logService, dataUtil) {
        'ngInject';

        var LOG_KEY = 'appspace.core.hal';
        var ALLOWED_OPTIONS_PROPERTIES = ['port', 'ipAddress', 'model', 'modelName', 'paperLabelName'];

        function print(options) {
            logService.info(LOG_KEY, 'Brother printer: Starting print operation');
            var promise = $q.defer();

            try {
                // Validate required input
                if (!options || !options.input) {
                    throw new Error('Invalid print options: input is required');
                }

                logService.debug(LOG_KEY, 'Brother printer: Input validated - type: ' + typeof options.input +
                    ', length: ' + (options.input ? options.input.length : 0));

                // Configure print options
                options.printOptions = options.printOptions || {};
                options.printOptions.port = options.printOptions.port || 'NET';

                // print options must be assigned a string value to prevent app from crashing during print attempt
                options.printOptions.ipAddress = options.printOptions.ipAddress || '';
                options.printOptions.model = (options.printOptions.model || '').replace('-', '_');
                options.printOptions.modelName = options.printOptions.modelName || '';

                // to prevent plugin to obtain value from previous value through app storage
                // default this to 'W62' because plugin would set it to 'W62' if it is not defined
                options.printOptions.paperLabelName = options.printOptions.paperLabelName || 'W62';

                logService.debug(LOG_KEY, 'Brother printer: Configuration set - IP: ' +
                    (options.printOptions.ipAddress || 'empty') + ', Model: ' +
                    (options.printOptions.model || 'empty') + ', Port: ' + options.printOptions.port);

                try {
                    options.printOptions = dataUtil.sanitizeObjectProperties(options.printOptions, ALLOWED_OPTIONS_PROPERTIES);
                    logService.debug(LOG_KEY, 'Brother printer: Options sanitized and ready');
                } catch (sanitizeError) {
                    throw new Error('Failed to sanitize print options: ' + sanitizeError.message);
                }

                var sdkPrintSuccessCallback = function(status) {
                    try {
                        logService.debug(LOG_KEY, 'Brother printer: Print SDK callback received');

                        if (status && status.result && status.result !== 'ERROR_NONE'
                            && dataUtil.isString(status.result) && status.result.indexOf('ERROR') != -1
                        ) {
                            var statusStr = status ? (JSON.stringify(status) || 'status object') : 'undefined status';
                            var errorMessage = 'Brother printer: Print failed with error - ' + statusStr;
                            logService.error(LOG_KEY, errorMessage);
                            promise.reject(errorMessage);
                            return;
                        }

                        logService.info(LOG_KEY, 'Brother printer: Print completed successfully');
                        promise.resolve();
                    } catch (callbackError) {
                        var callbackErrorMessage = 'Brother printer: Error in success callback - ' + callbackError.message;
                        logService.error(LOG_KEY, callbackErrorMessage);
                        promise.reject(callbackErrorMessage);
                    }
                };

                var sdkPrintErrorCallback = function(error) {
                    try {
                        var errorStr = error ? (JSON.stringify(error) || ('error: ' + error)) : 'undefined error';
                        var errorMessage = 'Brother printer: SDK print error - ' + errorStr;
                        logService.error(LOG_KEY, errorMessage);
                        promise.reject(errorMessage);
                    } catch (callbackError) {
                        var fallbackMessage = 'Brother printer: Error in SDK error callback - ' + callbackError.message;
                        logService.error(LOG_KEY, fallbackMessage);
                        promise.reject(fallbackMessage);
                    }
                };

                var setPrinterSuccessCallback = function() {
                    try {
                        logService.info(LOG_KEY, 'Brother printer: Printer configured successfully, starting print');

                        var inputData = options.input.replace('base64://', '');
                        logService.debug(LOG_KEY, 'Brother printer: Processed input data, length: ' + inputData.length);

                        $window.cordova.plugins.brotherPrinter.printViaSDK(inputData, sdkPrintSuccessCallback, sdkPrintErrorCallback);
                    } catch (printError) {
                        var errorMessage = 'Brother printer: Error during printViaSDK - ' + printError.message;
                        logService.error(LOG_KEY, errorMessage);
                        promise.reject(errorMessage);
                    }
                };

                var setPrinterErrorCallback = function(error) {
                    try {
                        var errorStr = error ? (JSON.stringify(error) || ('error: ' + error)) : 'undefined error';
                        var errorMessage = 'Brother printer: Printer setup failed - ' + errorStr;
                        logService.error(LOG_KEY, errorMessage);
                        promise.reject(errorMessage);
                    } catch (callbackError) {
                        var fallbackMessage = 'Brother printer: Error in setup callback - ' + callbackError.message;
                        logService.error(LOG_KEY, fallbackMessage);
                        promise.reject(fallbackMessage);
                    }
                };

                // Validate plugin availability before calling
                if (!$window.cordova || !$window.cordova.plugins || !$window.cordova.plugins.brotherPrinter) {
                    throw new Error('Brother printer plugin is not available');
                }

                logService.debug(LOG_KEY, 'Brother printer: Starting printer setup with validated plugin');
                $window.cordova.plugins.brotherPrinter.setPrinter(options.printOptions, setPrinterSuccessCallback, setPrinterErrorCallback);
            } catch (error) {
                var errorMessage = 'Brother printer: Setup error - ' + error.message;
                logService.error(LOG_KEY, errorMessage);
                promise.reject(errorMessage);
            }

            return promise.promise;
        }

        function getName() {
            return printerName;
        }

        return {
            getName: getName,
            print: print
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    var printerName = 'cordovaGenericPrinter';

	app.factory(printerName, ["$window", "$q", function($window, $q) {
        'ngInject';

        function print(options) {
            options.printOptions = options.printOptions || {};

            // print option must be assigned a value to prevent app from crashing during print attempt
            options.printOptions.printer = options.printOptions.printer || 'ipp://';

            $window.cordova.plugins.printer.print(options.input, options.printOptions);
            return $q.resolve();
        }

        function getName() {
            return printerName;
        }

        return {
            getName: getName,
            print: print
        };
    }]);
})(window.angular);
(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('cordovaPrinterFactory', ["PRINTER_TYPE", "cordovaBrotherPrinter", "cordovaGenericPrinter", "logService", function(
        PRINTER_TYPE,
        cordovaBrotherPrinter,
        cordovaGenericPrinter,
        logService
    ) {
        var LOG_KEY = 'appspace.core.hal';
        var PRINTER_TYPE_MAPPING = {};
        PRINTER_TYPE_MAPPING[PRINTER_TYPE.GENERIC] = cordovaGenericPrinter;
        PRINTER_TYPE_MAPPING[PRINTER_TYPE.BROTHER] = cordovaBrotherPrinter;

        function get(printerType) {
            printerType = printerType || PRINTER_TYPE.GENERIC;
            var plugin = PRINTER_TYPE_MAPPING[printerType.toLowerCase()] || PRINTER_TYPE_MAPPING[PRINTER_TYPE.GENERIC];
            if (plugin && plugin.getName) {
                logService.info(LOG_KEY, 'Selected printer plugin: ' + plugin.getName());
            }

            return plugin;
        }

        return {
            get: get
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');
    app.factory('AirMediaDriver', ["$q", "$injector", "$timeout", "platformUtil", "PLATFORM_TYPE", "POST_MESSAGES", "MEDIA_DIRECTIVE", "FEATURE_CODE", "dataUtil", "postMessagingUtil", function($q, $injector, $timeout, platformUtil,
        PLATFORM_TYPE, POST_MESSAGES, MEDIA_DIRECTIVE, FEATURE_CODE, dataUtil, postMessagingUtil) {
        var model;
        var AM_3X00_REGEX = /AM-\d{3,4}/;
        var MODEL_MATCH_INDEX = 0;
        var MODEL_TYPE = {
            AM300: 'AM-300',
            AM200: 'AM-200'
        };
        var persistPlatformRegistrationInformationPromise;
        var clearRegistrationInformationPromise;
        var persistRegistrationInfoTimeout;
        var clearRegistrationInfoTimeout;
        var persistDataTimeout;
        var supports3rdPartyDataPersistence = false;
        var PLATFORM_MESSAGE_KEY = 'appspaceapp-message';
        var PLATFORM_INFO_KEY = 'com.appspace.app';
        var CRESTRON_MESSAGE_KEY = 'appspaceapp.crestron';
        var SEND_PLATFORM_MESSAGE_TIMEOUT_DURATION = 30000;
        var PERSIST_DATA_DELAY = 5000; // Delay introduced to allow enough time to flush local storage data
        var TRANSPARENT_1PX_PNG = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="';
        var VIDEO_ATTRIBUTES = 'poster="data:image/png;base64,' + TRANSPARENT_1PX_PNG;

        function AirMediaDriver() {
            switch (platformUtil.detectPlatform()) {
                case PLATFORM_TYPE.AIRMEDIA300:
                    model = MODEL_TYPE.AM300;
                    break;
                case PLATFORM_TYPE.AIRMEDIA200:
                    model = MODEL_TYPE.AM200;
                    break;
                case PLATFORM_TYPE.AIRMEDIA:
                case PLATFORM_TYPE.AIRMEDIA3K:
                    supports3rdPartyDataPersistence = true;
                    var userAgent = platformUtil.getUserAgent();
                    var matches = userAgent.match(AM_3X00_REGEX);
                    if (!matches || matches.length === 0) {
                        break;
                    }

                    model = matches[MODEL_MATCH_INDEX];
                    break;
            }
        }

        function getModel() {
            return model;
        }

        function getPostMessagingService() {
            return $injector.get('postMessagingService');
        }

        function raisePostMessage(data) {
            getPostMessagingService().raiseMessage(data);
        }

        function processPostMessagingMessage(data) {
            if (!data || !data.message || data.message.toLowerCase() !== CRESTRON_MESSAGE_KEY) {
                return false;
            }

            switch (data.type.toLowerCase()) {
                case POST_MESSAGES.PERSISTED_THIRD_PARTY_STATE:
                    persistRegistrationInfoCallback(data);
                    return true;
                case POST_MESSAGES.CLEARED_THIRD_PARTY_STATE:
                    cancelClearRegistrationInfoTimeout();
                    if (clearRegistrationInformationPromise) {
                        clearRegistrationInformationPromise.resolve();
                    }

                    return true;
            }

            return false;
        }

        function raiseCrestronPersistData() {
            if (persistDataTimeout) {
                $timeout.cancel(persistDataTimeout);
                persistDataTimeout = undefined;
            }

            persistDataTimeout = $timeout(function() {
                getPostMessagingService().raiseCrestronMessage(undefined, postMessagingUtil.getCrestronPersistData);
            }, PERSIST_DATA_DELAY);
        }

        function persistRegistrationInfo(registrationInfo) {
            raiseCrestronPersistData();
            if (!supports3rdPartyDataPersistence) {
                return $q.reject('Device does not support persisting registration info');
            }

            if (!registrationInfo || !dataUtil.isObject(registrationInfo) ||
                !registrationInfo.tokens || !registrationInfo.deviceId || !registrationInfo.appspaceUrl) {
                return $q.reject('Incomplete registration information');
            }

            var strRegistrationInfo = JSON.stringify(registrationInfo);
            cancelPersistRegistrationInfoTimeout();
            persistRegistrationInfoTimeout = $timeout(function() {
                persistPlatformRegistrationInformationPromise.reject('Persist registration info timeout');
                persistRegistrationInfoTimeout = undefined;
            }, SEND_PLATFORM_MESSAGE_TIMEOUT_DURATION);
            persistPlatformRegistrationInformationPromise = $q.defer();
            raisePostMessage({
                message: PLATFORM_MESSAGE_KEY,
                type: POST_MESSAGES.PERSIST_THIRD_PARTY_STATE,
                info: {
                    key: PLATFORM_INFO_KEY,
                    value: strRegistrationInfo
                }
            });

            return persistPlatformRegistrationInformationPromise.promise;
        }

        function persistRegistrationInfoCallback(data) {
            if (!persistPlatformRegistrationInformationPromise) {
                return;
            }

            if (!data.info || !data.info.value) {
                persistPlatformRegistrationInformationPromise.reject('Invalid persisted registration information');
                return;
            }

            cancelPersistRegistrationInfoTimeout();
            persistPlatformRegistrationInformationPromise.resolve();
        }

        function clearRegistrationInformation() {
            raiseCrestronPersistData();
            if (!supports3rdPartyDataPersistence) {
                return $q.reject('Device does not support clearing persisted registration info');
            }

            clearRegistrationInformationPromise = $q.defer();
            cancelClearRegistrationInfoTimeout();
            clearRegistrationInfoTimeout = $timeout(function() {
                clearRegistrationInformationPromise.reject('Clear registration info timeout');
                clearRegistrationInfoTimeout = undefined;
            }, SEND_PLATFORM_MESSAGE_TIMEOUT_DURATION);
            raisePostMessage({
                message: PLATFORM_MESSAGE_KEY,
                type: POST_MESSAGES.CLEAR_THIRD_PARTY_STATE,
                info: {
                    key: PLATFORM_INFO_KEY
                }
            });

            return clearRegistrationInformationPromise.promise;
        }

        // Persist on bootup is only implemented on AirMedia driver during device identity callback
        // Preventing TSS driver from running persist registration info
        function persistRegistrationInfoOnBoot(registrationInfo) {
            return persistRegistrationInfo(registrationInfo);
        }

        function cancelPersistRegistrationInfoTimeout() {
            if (!persistRegistrationInfoTimeout) {
                return;
            }

            $timeout.cancel(persistRegistrationInfoTimeout);
            persistRegistrationInfoTimeout = undefined;
        }

        function cancelClearRegistrationInfoTimeout() {
            if (!clearRegistrationInfoTimeout) {
                return;
            }

            $timeout.cancel(clearRegistrationInfoTimeout);
            clearRegistrationInfoTimeout = undefined;
        }

        function getTransitionSupport(direction) {
            switch (direction) {
                case 'fade':
                    return false;
            }

            return true;
        }

        function getFeatureSupport(feature) {
            if (!feature) {
                return null;
            }

            switch (feature.toLowerCase()) {
                case FEATURE_CODE.REGISTRATION_UPDATE_SUPPORTED:
                    return !supports3rdPartyDataPersistence;
                case FEATURE_CODE.REGISTRATION_INFO_PERSISTENCE_ON_BOOT_SUPPORTED:
                    return supports3rdPartyDataPersistence;
                default:
                    return null;
            }
        }

        function getMediaDirective(elementType, options) {
            var result = {
                directive: elementType,
                attributes: '',
            };
            options = options || {};
            switch (elementType) {
                case MEDIA_DIRECTIVE.AS_MEDIA_VIDEO:
                    result.attributes = VIDEO_ATTRIBUTES;
                    result.attributes += ' allow-play-through="true"';
                    break;
                case MEDIA_DIRECTIVE.VIDEO:
                    result.attributes = VIDEO_ATTRIBUTES;
                    result.attributes += ' crossorigin="anonymous"';
                    break;
            }

            return result;
        }

        AirMediaDriver.prototype = {
            id: 'browser.airmedia',
            platform: {
                getModel: getModel,
                persistRegistrationInfo: persistRegistrationInfo,
                clearRegistrationInformation: clearRegistrationInformation,
                processPostMessagingMessage: processPostMessagingMessage,
                persistRegistrationInfoOnBoot: persistRegistrationInfoOnBoot,
                getTransitionSupport: getTransitionSupport,
                getFeatureSupport: getFeatureSupport
            },
            media: {
                getMediaDirective: getMediaDirective
            },
        };

        return (AirMediaDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');
    app.factory('AmazonSignageDriver', ["FEATURE_CODE", function(FEATURE_CODE) {
        var AMAZONSIGNAGE_DRIVER_ID = 'browser.amazonsignage';

        function AmazonSignageDriver() {
            // Constructor
        }

        function getAppspaceDeviceType() {
            return 'AMAZONSIGNAGE';
        }

        function getFeatureSupport(feature) {
            if (!feature) {
                return null;
            }
            
            feature = feature.toLowerCase();
            switch (feature) {
                case FEATURE_CODE.ENFORCED_REGISTRATION_MODE:
                    return 'passive';
                case FEATURE_CODE.DEVICE_MENU_SUPPORT:
                    return 'no';
                case FEATURE_CODE.CHANNEL_VIEWER_INTERACTIVE_DISABLE:
                    return true;
                default:
                    return null;
            }
        }

        AmazonSignageDriver.prototype = {
            id: AMAZONSIGNAGE_DRIVER_ID,
            platform: {
                getAppspaceDeviceType: getAppspaceDeviceType,
                getAppPackageType: getAppspaceDeviceType,
                getFeatureSupport: getFeatureSupport
            }
        };

        return (AmazonSignageDriver);
    }]);
})(window.angular);


(function($angular) {
    'use strict';

    var app = $angular.module('hal');
    app.factory('CiscoWebexBoardDriver', function() {
        function CiscoWebexBoardDriver() {
        }

        function getAppspaceDeviceType() {
            return 'WEBEXBOARD';
        }

        CiscoWebexBoardDriver.prototype = {
            id: 'browser.cisco.webexboard',
            platform: {
                getAppspaceDeviceType: getAppspaceDeviceType
            },
        };

        return (CiscoWebexBoardDriver);
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');
    app.factory('CiscoWebexDriver', ["MEDIA_DIRECTIVE", "$interval", "$document", "FEATURE_CODE", "INTEGRATION_TYPE", "driverUtil", function(MEDIA_DIRECTIVE,
        $interval,
        $document,
        FEATURE_CODE,
        INTEGRATION_TYPE,
        driverUtil) {
        var standbySubscribers = [];
        var isStandby = false;
        var isStandbyCheckingInitialized = false;
        var BROWSER_VISIBILITY_HIDDEN = 'hidden';
        var standbyCheckingInterval;
        var STANDBY_CHECKING_INTERVAL = 1000;

        function CiscoWebexDriver() {
        }

        function isRegistrationTokenRequired() {
            return driverUtil.getIntegrationType().toLowerCase() === INTEGRATION_TYPE.WEBEX_CONTROL_HUB;
        }

        function getFeatureSupport(feature) {
            if (!feature) {
                return feature;
            }

            switch (feature.toLowerCase()) {
                case 'signs.behaviour.reloadonresize':
                    return false;
                case 'support.lowbandwidth.mode':
                    return true;
                case FEATURE_CODE.MAX_WEB_VIEW_COLLECTION:
                    return 0;
                case FEATURE_CODE.REGISTRATION_TOKEN_REQUIRED:
                    return isRegistrationTokenRequired();
                default:
                    return null;
            }
        }

        function clearSessionStorage() {
            driverUtil.resetSessionStorage();
        }

        function getMediaDirective(elementType, options) {
            var result = {
                directive: elementType,
                attributes: ''
            };

            if (elementType === MEDIA_DIRECTIVE.CARD) {
                result.options = {
                    supportSwapWithVideo: false
                };
            }

            if (elementType === MEDIA_DIRECTIVE.VIDEO) {
                result.attributes += 'crossorigin="anonymous" ';
            }

            return result;
        }

        function cancelStandbyCheckingInterval() {
            if (!standbyCheckingInterval) {
                return;
            }

            $interval.cancel(standbyCheckingInterval);
            standbyCheckingInterval = undefined;
        }

        function notifyStandbySubscribers() {
            for (var i = 0; i < standbySubscribers.length; i++) {
                standbySubscribers[i](isStandby);
            }
        }

        function isBrowserHidden() {
            return $document.visibilityState === BROWSER_VISIBILITY_HIDDEN;
        }

        function checkStandby() {
            var currentIsStandby = isBrowserHidden();
            if (currentIsStandby === isStandby) {
                return;
            }

            isStandby = currentIsStandby;
            notifyStandbySubscribers();
        }

        function initializeStandbyChecking() {
            cancelStandbyCheckingInterval();
            isStandbyCheckingInitialized = true;
            standbyCheckingInterval = $interval(checkStandby, STANDBY_CHECKING_INTERVAL);
        }

        function subscribeToStandby(callback) {
            if (!callback || standbySubscribers.indexOf(callback) > -1) {
                return;
            }

            standbySubscribers.push(callback);
            callback(isStandby);
            if (isStandbyCheckingInitialized) {
                return;
            }

            initializeStandbyChecking();
        }

        CiscoWebexDriver.prototype = {
            id: 'browser.cisco.webex',
            platform: {
                getFeatureSupport: getFeatureSupport,
                clearSessionStorage: clearSessionStorage,
                subscribeToStandby: subscribeToStandby
            },
            media: {
                getMediaDirective: getMediaDirective
            }
        };

        return (CiscoWebexDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');
    app.factory('CiscoWebexNavigatorDriver', ["$q", "$timeout", "$window", "logService", "$injector", "dataUtil", "driverUtil", "SYSTEM_DATA", "DRIVER_ID", function(
        $q,
        $timeout,
        $window,
        logService,
        $injector,
        dataUtil,
        driverUtil,
        SYSTEM_DATA,
        DRIVER_ID
    ) {
        var LOG_KEY = 'appspace.core.hal';
        var LED_MODE = {
            MANUAL: 'Manual'
        };
        var ROOM_STATUS = {
            AVAILABLE: 'available',
            OCCUPIED: 'occupied',
            CHECK_IN: 'check-in'
        };
        var LED_COLOR = {
            RED: 'Red',
            GREEN: 'Green',
            YELLOW: 'Yellow',
            OFF: 'Off',
            BLUE: 'Blue',
            ORANGE: 'Orange',
        };
        var COLOR_PALETTE = {
            DEFAULT: 'DEFAULT',
            ADA: 'ADA'
        };
        var DEFAULT_REFERENCE_DEVICE_ID_KEY = 'referencedeviceid';
        var DEFAULT_REFERENCE_DEVICE_MODEL_KEY = 'referencedevicemodel';
        var DEFAULT_REFERENCE_SERIAL_NUMBER_KEY = 'referenceserialnumber';
        var PERIPHERAL_DEVICE_ID_PREFIX = 'appspace-sn';
        var PERIPHERAL_DEVICE_MODEL = 'Room Navigator';
        var xapiProcessTimeoutDuration = 5000;
        var xapiSetModeTimeoutDuration = 1000;
        var STATUS_COLOR_MAPPING = {};
        STATUS_COLOR_MAPPING[COLOR_PALETTE.DEFAULT] = {};
        STATUS_COLOR_MAPPING[COLOR_PALETTE.ADA] = {};
        STATUS_COLOR_MAPPING[COLOR_PALETTE.DEFAULT][ROOM_STATUS.AVAILABLE] = LED_COLOR.GREEN;
        STATUS_COLOR_MAPPING[COLOR_PALETTE.DEFAULT][ROOM_STATUS.OCCUPIED] = LED_COLOR.RED;
        STATUS_COLOR_MAPPING[COLOR_PALETTE.DEFAULT][ROOM_STATUS.CHECK_IN] = LED_COLOR.YELLOW;
        STATUS_COLOR_MAPPING[COLOR_PALETTE.ADA][ROOM_STATUS.AVAILABLE] = LED_COLOR.BLUE;
        STATUS_COLOR_MAPPING[COLOR_PALETTE.ADA][ROOM_STATUS.OCCUPIED] = LED_COLOR.ORANGE;
        STATUS_COLOR_MAPPING[COLOR_PALETTE.ADA][ROOM_STATUS.CHECK_IN] = LED_COLOR.YELLOW;
        var isReadyPromise = $q.defer();
        var xapi;
        var pwaDriver;

        function CiscoWebexNavigatorDriver(config) {
            var readyPromises = [];

            if (config && config.drivers) {
                pwaDriver = driverUtil.findDriver(DRIVER_ID.PWA, config.drivers);
                if (pwaDriver) {
                    readyPromises.push(pwaDriver.isReady());
                }
            }

            // Driver will be considered as loaded regardless of whether XAPI is present during startup.
            // XAPI will be retrieved again on demand basis
            readyPromises.push(getXapi());
            $q.allSettled(readyPromises).then($angular.noop, $angular.noop).finally(isReadyPromise.resolve);
        }

        function getXapi() {
            if (xapi) {
                return $q.resolve(xapi);
            }

            var defer = $q.defer();
            var errorMessage = 'get XAPI is not defined.';
            var onGetXapi = function(result) {
                xapi = result;
                defer.resolve(xapi);
            };

            if (!$window.getXAPI) {
                logService.warn(LOG_KEY, errorMessage);
                defer.reject(errorMessage);
                return defer.promise;
            }

            getWindowXapi().then(onGetXapi, function(error) {
                logService.warn(LOG_KEY, error);
                defer.reject(error);
            });

            return defer.promise;
        }

        function getWindowXapi() {
            var defer = $q.defer();
            // workaround for get Xapi not responding on first Xapi load
            var timeout = $timeout(function() {
                defer.reject('get XAPI is not responding.');
            }, xapiProcessTimeoutDuration);

            $window.getXAPI().then(function(result) {
                $timeout.cancel(timeout);
                defer.resolve(result);
            }, function(error) {
                defer.reject('Unable to get XAPI. Error source: ' + JSON.stringify(error));
            });

            return defer.promise;
        }

        function enableStatusLight(config) {
            if (!config || !config.status) {
                return $q.reject('Invalid color status');
            }

            var colorPalette = config.colorPalette;
            if (colorPalette) {
                colorPalette = colorPalette.toUpperCase();
            }

            var statusColorConfiguration = STATUS_COLOR_MAPPING[colorPalette] || STATUS_COLOR_MAPPING[COLOR_PALETTE.DEFAULT];
            var ledColor = statusColorConfiguration[config.status];
            return !ledColor ? $q.reject('Invalid color status') : setLedColor(ledColor);
        }

        function disableStatusLight() {
            return setLedColor(LED_COLOR.OFF);
        }

        function setLedColor(color) {
            var defer = $q.defer();
            var onSetLedColor = function(xapi) {
                xapi.Command.UserInterface.LedControl.Color.Set({Color: color}).then(defer.resolve, function(error) {
                    logService.warn(LOG_KEY, 'Unable to set LED color. Error source: ' + JSON.stringify(error));
                    defer.reject(error);
                });
            };

            var onSetLedMode = function(xapi) {
                try {
                    setMode().then(function() {
                        onSetLedColor(xapi);
                    }, function(error) {
                        logService.warn(LOG_KEY, error);
                        defer.reject(error);
                    });
                } catch (error) {
                    logService.warn(LOG_KEY, 'Error on XAPI config: ' + JSON.stringify(error));
                    defer.reject(error);
                }
            };

            getXapi().then(onSetLedMode, function() {
                defer.reject('XAPI is undefined');
            });
            return defer.promise;
        }

        function setMode() {
            var defer = $q.defer();
            // workaround for get LED Mode not responding on first Xapi load
            var timeout = $timeout(function() {
                logService.warn(LOG_KEY, 'set LED mode is not responding.');
                defer.resolve();
            }, xapiSetModeTimeoutDuration);

            xapi.Config.UserInterface.LedControl.Mode.set(LED_MODE.MANUAL).then(function() {
                $timeout.cancel(timeout);
                defer.resolve();
            }, function(error) {
                defer.reject('Unable to set LED mode to manual. Error source: ' + JSON.stringify(error));
            });

            return defer.promise;
        }

        function getAppspaceDeviceType() {
            return 'WEBEXNAVIGATOR';
        }

        function updateReferenceDeviceId(referenceDeviceIdKey) {
            var urlParameters = getSessionService().getUrlParameters();
            var value = urlParameters[referenceDeviceIdKey || DEFAULT_REFERENCE_DEVICE_ID_KEY];
            var deviceModel = urlParameters[DEFAULT_REFERENCE_DEVICE_MODEL_KEY];
            var serialNumber = urlParameters[DEFAULT_REFERENCE_SERIAL_NUMBER_KEY];

            if (isPairedNavigator(deviceModel, serialNumber)) {
                value = PERIPHERAL_DEVICE_ID_PREFIX + ':' + serialNumber;
            }

            if (dataUtil.isNullOrUndefined(value)) {
                return;
            }

            getConfigurationService()['setReferenceDeviceId'](value);
        }

        function isPairedNavigator(referenceDeviceModel, referenceSerialNumber) {
            // If the peripheral is attached to a parent device
            // the device model will be its parent model
            return referenceDeviceModel != PERIPHERAL_DEVICE_MODEL ?
                !dataUtil.isNullOrUndefined(referenceSerialNumber) && referenceSerialNumber != '$(peripheralSerial)' :
                false;
        }

        function getConfigurationService() {
            return $injector.get('coreConfigurationService');
        }

        function getSessionService() {
            return $injector.get('sessionService');
        }

        function isReady() {
            return isReadyPromise.promise;
        }

        function getFingerprint() {
            var urlParameters = getSessionService().getUrlParameters();
            var serialNumber = urlParameters[DEFAULT_REFERENCE_SERIAL_NUMBER_KEY];
            var deviceModel = urlParameters[DEFAULT_REFERENCE_DEVICE_MODEL_KEY];
            if (!deviceModel || !serialNumber) {
                return driverUtil.getFingerprint();
            }

            return isPairedNavigator(deviceModel, serialNumber) ? driverUtil.getFingerprint() : $q.resolve({
                fingerprint: serialNumber,
                type: SYSTEM_DATA.FINGERPRINT_TYPE.HARDWARE
            });
        }

        CiscoWebexNavigatorDriver.prototype = {
            id: DRIVER_ID.WEBEXNAVIGATOR,
            isReady: isReady,
            platform: {
                getAppspaceDeviceType: getAppspaceDeviceType,
                updateReferenceDeviceId: updateReferenceDeviceId,
                enableStatusLight: enableStatusLight,
                disableStatusLight: disableStatusLight,
                getFingerprint: getFingerprint,
            }
        };

        return (CiscoWebexNavigatorDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');
    app.factory('CiscoWebexRoomKitDriver', function() {
        function CiscoWebexRoomKitDriver() {
        }

        function getAppspaceDeviceType() {
            return 'WEBEXROOMKIT';
        }

        CiscoWebexRoomKitDriver.prototype = {
            id: 'browser.cisco.webexroomkit',
            platform: {
                getAppspaceDeviceType: getAppspaceDeviceType
            }
        };

        return (CiscoWebexRoomKitDriver);
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('CrestronConnectDriver', ["$q", "$window", "$timeout", "crestronAndroidUtil", "logService", "dataUtil", "MEDIA_DIRECTIVE", "CRESTRON_INTENT_MESSAGE", "FEATURE_CODE", "SYSTEM_DATA", function($q, $window, $timeout, crestronAndroidUtil, logService, dataUtil,
        MEDIA_DIRECTIVE, CRESTRON_INTENT_MESSAGE, FEATURE_CODE, SYSTEM_DATA) {
        var LOG_KEY = 'appspace.core.hal';
        var SEND_PLATFORM_MESSAGE_DURATION = 30000;
        var PLATFORM_MESSAGE_KEY = 'com.appspace.app';
        var receiverRegisteredDefer = $q.defer();
        var cacheStateDefer = $q.defer();
        var crestronReadyMessageDefer = $q.defer();
        var isReadyDefer = $q.defer();
        var platformRegistrationPromise;
        var platformUnregistrationPromise;
        var getRegistrationInformationTimeout;
        var isCacheEnabled = false;
        var isCrestronReady = false;
        var subscribers = [];
        var UDP_URL_REGEX = /^udp:\/\//i;
        var TRANSPARENT_1PX_PNG = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="';
        var VIDEO_ATTRIBUTES = 'muted poster="data:image/png;base64,' + TRANSPARENT_1PX_PNG;

        function CrestronConnectDriver() {
            var onReceiverRegistrationError = function(error) {
                logService.warn(LOG_KEY, 'Error while registering broadcast receiver. Error source: ' + JSON.stringify(error));
                receiverRegisteredDefer.resolve();
            };

            var onCacheStateReceived = function(result) {
                result = result || {};
                result.state = result.state || '';
                isCacheEnabled = result.state.toLowerCase() === 'true';
                cacheStateDefer.resolve();
            };

            var onCacheStateError = function(error) {
                logService.warn(LOG_KEY, 'Error while getting cache state. Error source: ' + JSON.stringify(error));
                cacheStateDefer.resolve();
            };

            var onReceiverRegistered = function(result) {
                result = result || {};
                // Means we have registered the broadcast receiver before
                if (result.hasStarted) {
                    crestronReadyMessageDefer.resolve();
                }

                receiverRegisteredDefer.resolve();
            };

            var onDeviceReady = function() {
                $window.onBroadcastReceived = onBroadcastReceived;
                crestronAndroidUtil.invokePluginMethod('registerBroadcastReceiver', onReceiverRegistered, onReceiverRegistrationError);
                crestronAndroidUtil.invokePluginMethod('getCacheEnabledState', onCacheStateReceived, onCacheStateError);
                sendPlatformMessage({
                    type: CRESTRON_INTENT_MESSAGE.THIRD_PARTY_READY
                });
            };

            $q.allSettled([receiverRegisteredDefer.promise, cacheStateDefer.promise, crestronReadyMessageDefer.promise]).then(isReadyDefer.resolve);
            $q.allSettled([crestronAndroidUtil.isReady(), logService.isReady()]).then(onDeviceReady);
        }

        function onCrestronReady() {
            if (isCrestronReady) {
                return;
            }

            isCrestronReady = true;
            crestronReadyMessageDefer.resolve();
        }

        function onBroadcastReceived(type, data, option, error, key) {
            logService.debug(LOG_KEY, 'Received broadcast intent. type: ' + type + ', data: ' + data + ', option: ' + option + ', error: ' + error + ', key: ' + key);
            if (option) {
                try {
                    option = JSON.parse(option);
                } catch (ignored) {
                    logService.warn(LOG_KEY, 'Unable to parse JSON of broadcast received option property: ' + JSON.stringify(option));
                }
            }

            switch (type) {
                case CRESTRON_INTENT_MESSAGE.CRESTRON_READY:
                    sendPlatformMessage({
                        type: CRESTRON_INTENT_MESSAGE.THIRD_PARTY_READY_ACK
                    });
                    onCrestronReady();
                    break;
                case CRESTRON_INTENT_MESSAGE.CRESTRON_READY_ACK:
                    onCrestronReady();
                    break;
                case CRESTRON_INTENT_MESSAGE.RETRIEVED_THIRD_PARTY_STATE:
                    cancelRegistrationInformationTimeout();
                    if (!data || !dataUtil.isString(data)) {
                        platformRegistrationPromise.reject('Retrieved registration information is undefined');
                        return;
                    }

                    try {
                        // Data massaging required to handle the escaped-quote \" format that the Crestron plugin passes
                        // to avoid "Unexpected token \ in JSON" error during parsing attempt
                        data = data.replaceAll('\\', '');
                        var information = JSON.parse(data);
                        platformRegistrationPromise.resolve(information);
                    } catch (err) {
                        platformRegistrationPromise.reject('Retrieved registration information is not an object');
                    }

                    break;
                case CRESTRON_INTENT_MESSAGE.CLEARED_THIRD_PARTY_STATE:
                    logService.info(LOG_KEY, 'Registration information cleared');
                    if (!platformUnregistrationPromise) {
                        break;
                    }

                    platformUnregistrationPromise.resolve();
                    platformUnregistrationPromise = undefined;
                    break;
                default:
                    notifySubscribers(type, data, option, error);
                    break;
            }
        }

        function cancelRegistrationInformationTimeout() {
            if (!getRegistrationInformationTimeout) {
                return;
            }

            $timeout.cancel(getRegistrationInformationTimeout);
            getRegistrationInformationTimeout = undefined;
        }

        function sendPlatformMessage(payload) {
            logService.debug(LOG_KEY, 'Sending broadcast intent: ' + JSON.stringify(payload));
            payload = payload || {};
            var option = payload.option;
            if (!dataUtil.isNullOrUndefined(option) && !dataUtil.isString(option)) {
                option = JSON.stringify(option);
            }

            var defer = $q.defer();
            payload.data = payload.data || {};
            crestronAndroidUtil.invokePluginMethod('sendBroadcast', payload.type, payload.data, option, payload.error, payload.key, defer.resolve, defer.reject);
            return defer.promise;
        }

        function isReady() {
            return isReadyDefer.promise;
        }

        function getFeatureSupport(feature) {
            feature = (feature || '').toLowerCase();
            switch (feature) {
                case 'file.localstorage':
                    return isCacheEnabled ? 'yes' : 'no';
                case 'media.hls.supported':
                    return 'yes';
                case FEATURE_CODE.ENFORCED_REGISTRATION_MODE:
                    return SYSTEM_DATA.PASSIVE_REGISTRATION_MODE;
                case FEATURE_CODE.REGISTRATION_INFO_PERSISTENCE_ON_BOOT_SUPPORTED:
                    return false;
                case FEATURE_CODE.REGISTRATION_UPDATE_SUPPORTED:
                    return true;
                case FEATURE_CODE.STANDBY_CONFIGURATION:
                    return {
                        fullOverlay: true,
                        overlayColor: 'transparent'
                    };
                case FEATURE_CODE.MAX_VIDEO_LAYERS:
                    return '99';
                case FEATURE_CODE.REUSE_VIDEO_LAYER:
                    return 'no';
                case FEATURE_CODE.VIDEO_REQUIRE_PAUSE_ON_LOAD:
                    return false;
                default:
                    return null;
            }
        }

        function getModel() {
            var defer = $q.defer();
            crestronAndroidUtil.invokePluginMethod('getDeviceModel', function(result) {
                result = result || {};
                result.model = result.model || '';
                defer.resolve(result.model);
            }, defer.reject);
            return defer.promise;
        }

        function getMediaDirective(elementType, options) {
            options = options || {};
            var result = {
                directive: elementType,
                options: {}
            };
            if ((elementType === MEDIA_DIRECTIVE.AS_MEDIA_VIDEO && UDP_URL_REGEX.test(options.mediaUrl || '')) ||
            (options.mediaUrl || '').indexOf('.m3u8') > -1) {
                result.directive = MEDIA_DIRECTIVE.AS_CRESTRON_VIDEO;
                result.options.isReuseSupported = true;
            } else if (elementType === MEDIA_DIRECTIVE.VIDEO) {
                result.attributes = VIDEO_ATTRIBUTES;
            }

            return result;
        }

        function subscribeToPlatformMessages(callback) {
            if (!callback || subscribers.indexOf(callback) > -1) {
                return false;
            }

            subscribers.push(callback);
            return true;
        }

        function unsubscribeFromPlatformMessages(callback) {
            var index = subscribers.indexOf(callback);
            if (index === -1) {
                return false;
            }

            subscribers.splice(index, 1);
            return true;
        }

        function notifySubscribers(type, data, option, error) {
            for (var i = 0; i < subscribers.length; i++) {
                subscribers[i]({
                    type: type,
                    data: data,
                    option: option,
                    error: error
                });
            }
        }

        function getRegistrationInformation() {
            platformRegistrationPromise = $q.defer();
            cancelRegistrationInformationTimeout();
            isReady().then(function() {
                getRegistrationInformationTimeout = $timeout(function() {
                    platformRegistrationPromise.reject('Timed out');
                }, SEND_PLATFORM_MESSAGE_DURATION);

                sendPlatformMessage({
                    type: CRESTRON_INTENT_MESSAGE.RETRIEVE_THIRD_PARTY_STATE,
                    key: PLATFORM_MESSAGE_KEY
                });
            });

            return platformRegistrationPromise.promise;
        }

        function persistRegistrationInfo() {
            return crestronAndroidUtil.persistRegistrationInfo();
        }

        function clearRegistrationInformation() {
            platformUnregistrationPromise = $q.defer();
            sendPlatformMessage({
                type: CRESTRON_INTENT_MESSAGE.CLEAR_THIRD_PARTY_STATE,
                key: PLATFORM_MESSAGE_KEY
            });
            return platformUnregistrationPromise.promise;
        }

        // Define the instance methods and properties
        CrestronConnectDriver.prototype = {
            // Public interface
            id: 'browser.crestron.connect',
            isReady: isReady,
            platform: {
                getModel: getModel,
                getFeatureSupport: getFeatureSupport,
                getRegistrationInformation: getRegistrationInformation,
                subscribeToPlatformMessages: subscribeToPlatformMessages,
                unsubscribeFromPlatformMessages: unsubscribeFromPlatformMessages,
                sendPlatformMessage: sendPlatformMessage,
                persistRegistrationInfo: persistRegistrationInfo,
                clearRegistrationInformation: clearRegistrationInformation
            },
            media: {
                getMediaDirective: getMediaDirective
            }
        };

        // Return the constructor for the class
        return (CrestronConnectDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('CrestronSignsDriver', ["$window", "$q", "logService", function($window, $q, logService) {
        // Define the constructor function.
        function CrestronSignsDriver() {
        }

        function getFeatureSupport(feature) {
            if (!feature) {
                return null;
            }

            switch (feature.toLowerCase()) {
                case 'media.video.element.backgroundcolor':
                    return 'black';
                default:
                    return null;
            }
        }

         // Define the instance methods and properties
         CrestronSignsDriver.prototype = {
            // Public interface
            id: 'browser.crestron.signs',
            platform: {
                getFeatureSupport: getFeatureSupport
            },
        };

        // Return the constructor for the class
        return (CrestronSignsDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var app = $angular.module('hal');
    app.factory('EditorDriver', function() {
        function EditorDriver() {
            this.id = 'editor';
            this.platform = {
                getFeatureSupport: getFeatureSupport,
                isTransparentRequired: isTransparentRequired
            };

            function getFeatureSupport(feature) {
                if (!feature) {
                    return null;
                }
                feature = feature.toLowerCase();
                switch (feature) {
                    case 'playback.mode.editor':
                        return true;
                    case 'services.disable.sync':
                        return true;
                    case 'support.script.transparent.update':
                        return true;
                    case 'rendering.css3-transitions':
                        return 'no';
                    default:
                        return null;
                }
            }

            function isTransparentRequired(color) {
                return !color;
            }
        }

        return (EditorDriver);
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('ElectronDriver', ["$q", "ipcRendererUtil", "IPC_COMMAND", "SYSTEM_DATA", "FileEntry", "FileError", "driverUtil", "FileUtil", "UrlUtil", "dataUtil", "logService", "CONTENT_TYPE", "ExternalNodeWebSocketServer", function(
        $q,
        ipcRendererUtil,
        IPC_COMMAND,
        SYSTEM_DATA,
        FileEntry,
        FileError,
        driverUtil,
        FileUtil,
        UrlUtil,
        dataUtil,
        logService,
        CONTENT_TYPE,
        ExternalNodeWebSocketServer) {
        function ElectronDriver(platform) {
            var basePath;
            var currentPlatform = platform;
            var REGISTRATION_FILE_PATH = '/registration.json';
            var SYSTEM_PATHS = ['/cache', '/tmp', REGISTRATION_FILE_PATH];
            var WEBVIEW_ELEMENT = 'webview';
            var LOG_KEY = 'appspace.core.hal';
            var ELECTRON_CACHE_BASE_PATH = '/app';
            var DEFAULT_IP = '127.0.0.1';
            var DEVICE_INFORMATION_KEY = {
                DEVICE_MODEL: 'deviceModel',
                DEVICE_TYPE: 'deviceType',
            };
            var ENVIRONMENT_VARIABLE_MAPPING = {};
            ENVIRONMENT_VARIABLE_MAPPING[DEVICE_INFORMATION_KEY.DEVICE_MODEL] = 'APPSPACE_DEVICE_MODEL';
            ENVIRONMENT_VARIABLE_MAPPING[DEVICE_INFORMATION_KEY.DEVICE_TYPE] = 'APPSPACE_DEVICE_TYPE';
            var nodeWebSocketServer;
            var updatePromise;
            var deviceInformation = {};
            if (!ipcRendererUtil.isExists()) {
                throw new Error('IPC renderer is not defined.');
            }

            this.id = 'browser.electron';
            this.isReady = isReady;

            this.platform = {
                reload: restart,
                reboot: restart,
                getFeatureSupport: getFeatureSupport,
                getSerialNumber: getSerialNumber,
                getModel: getModel,
                getFingerprint: getFingerprint,
                closeApplication: closeApplication,
                updateApp: updateApp,
                getAppUpdateCapabilities: getAppUpdateCapabilities,
                getAppUpdateHandler: getAppUpdateHandler,
                getRegistrationFilePath: getRegistrationFilePath,
                clearRegistrationInformation: clearRegistrationInformation,
                getCpuUsage: getCpuUsage,
                getMemoryUsage: getMemoryUsage,
                getAvailableStorage: getAvailableStorage,
                getFirmware: getFirmware,
            };

            this.network = {
                getIpAddress: getIpAddress,
                getMacAddress: getMacAddress,
                downloadHttp: downloadHttp,
                startServer: startServer,
                stopServer: stopServer,
                subscribeServerMessages: subscribeServerMessages,
                unSubscribeServerMessages: unSubscribeServerMessages
            };

            this.file = {
                resolveUrl: resolveUrl,
                resolveRelative: resolveUrl,
                createDirectory: createDirectory,
                checkDirectory: checkDirectory,
                removeDirectory: removeDirectory,
                enumerateDirectory: enumerateDirectory,
                checkFile: checkFile,
                moveFile: moveFile,
                removeFile: removeFile
            };

            this.media = {
                getMediaDirective: getMediaDirective,
                processContentDirectiveOptions: processContentDirectiveOptions
            };

            function getBasePath() {
                var promise = $q.defer();
                getDefaultPath().then(function(result) {
                    basePath = result;
                }).finally(promise.resolve);
                return promise.promise;
            }

            function getDeviceInformation() {
                var promise = $q.defer();
                ipcRendererUtil.invoke(IPC_COMMAND.GET_ENVIRONMENT_VARIABLES, ENVIRONMENT_VARIABLE_MAPPING).then(function(variables) {
                    deviceInformation = variables || {};
                    promise.resolve();
                }, promise.reject);
                return promise.promise;
            }

            function isReady() {
                var promise = $q.defer();
                var initializationPromises = [];
                initializationPromises.push(getDeviceInformation());
                initializationPromises.push(getBasePath());
                $q.allSettled(initializationPromises).then(promise.resolve);
                return promise.promise;
            }

            function getRegistrationFilePath() {
                return resolveUrl(REGISTRATION_FILE_PATH);
            }

            function clearRegistrationInformation() {
                return removeFile(getRegistrationFilePath());
            }

            function resolveUpdatePromise() {
                if (!updatePromise) {
                    return;
                }

                updatePromise.resolve();
                updatePromise = undefined;
            }

            function rejectUpdatePromise(error) {
                if (!updatePromise) {
                    return;
                }

                updatePromise.reject(error);
                updatePromise = undefined;
            }

            function isSelfUpdateSupported(selfUpdateConfiguration) {
                var supportsSelfUpdate = selfUpdateConfiguration.isSupportedByDevice;
                if (!supportsSelfUpdate) {
                    logService.debug(LOG_KEY, 'Self update is not supported for this device');
                } else if (!selfUpdateConfiguration.baseUpdateUrl) {
                    logService.error(LOG_KEY, 'SELF_UPDATE_BASE_URL is required');
                    supportsSelfUpdate = false;
                }

                return supportsSelfUpdate;
            }

            function getAppUpdateHandler() {
                var selfUpdateConfiguration = driverUtil.getSelfUpdateConfiguration();
                return {
                    executeUpdate: selfUpdateConfiguration.updateApp,
                    isSupported: function() {
                        return $q.resolve(isSelfUpdateSupported(selfUpdateConfiguration));
                    },
                };
            }

            function getAppUpdateCapabilities() {
                return {
                    scheduling: true,
                    frequency: true,
                    downgrade: false,
                    pinning: false,
                };
            }

            function updateApp(baseUpdateUrl) {
                if (updatePromise) {
                    return updatePromise.promise;
                }

                updatePromise = $q.defer();
                var updateUrl = UrlUtil.removeTrailingSlash(baseUpdateUrl) + '/electron/' + currentPlatform + '/';
                logService.debug(LOG_KEY, 'Checking for app update at ' + updateUrl + '.');
                ipcRendererUtil.invoke(IPC_COMMAND.CHECK_APP_UPDATE, updateUrl).then(onAppUpdateChecked, function(error) {
                    var errorMessage = 'Failed to check app update: ' + (error || {}).message;
                    logService.warn(LOG_KEY, errorMessage);
                    rejectUpdatePromise(errorMessage);
                });
                return updatePromise.promise;
            }

            function onAppUpdateChecked(info) {
                info = info || {};
                if (!info.available) {
                    logService.debug(LOG_KEY, 'No update found.');
                    resolveUpdatePromise();
                    return;
                }

                logService.debug(LOG_KEY, 'Update found. Downloading.');
                ipcRendererUtil.invoke(IPC_COMMAND.DOWNLOAD_APP_UPDATE).then(onAppUpdateDownloaded, function(error) {
                    var errorMessage = 'Failed to download app update: ' + (error || {}).message;
                    logService.warn(LOG_KEY, errorMessage);
                    rejectUpdatePromise(errorMessage);
                });
            }

            function onAppUpdateDownloaded() {
                logService.debug(LOG_KEY, 'App update downloaded successful. Applying update and restart.');
                ipcRendererUtil.invoke(IPC_COMMAND.UPDATE_AND_RESTART_APP);
                resolveUpdatePromise();
            }

            function restart() {
                ipcRendererUtil.invoke(IPC_COMMAND.RESTART);
            }

            function getMacAddress() {
                var onSuccess = function(result) {
                    return (result || {}).mac;
                };

                return getActiveNetworkConnection(onSuccess);
            }

            function getIpAddress() {
                var promise = $q.defer();
                var onSuccess = function(result) {
                    var connection = result || {};
                    return generateResponse(connection.ip4 || DEFAULT_IP);
                };

                var generateResponse = function(ip) {
                    return {address: ip};
                };

                getActiveNetworkConnection(onSuccess).then(promise.resolve, function() {
                    promise.resolve(generateResponse(DEFAULT_IP));
                });
                return promise.promise;
            }

            function getActiveNetworkConnection(callback) {
                var promise = $q.defer();
                ipcRendererUtil.invoke(IPC_COMMAND.GET_ACTIVE_NETWORK_CONNECTION).then(function(result) {
                    promise.resolve(callback(result));
                }, function(error) {
                    logService.warn(LOG_KEY, 'Unable to find active interface');
                    promise.resolve(callback());
                });
                return promise.promise;
            }

            function getFeatureSupport(feature) {
                if (!feature) {
                    return null;
                }

                switch (feature.toLowerCase()) {
                    case 'file.localstorage':
                        return 'yes';
                    case 'network.maxconcurrentdownloads':
                        return 6;
                    case 'devicemenu.allowclose':
                        return true;
                    case 'device.supportselfupdate':
                        return true;
                }

                return null;
            }

            function downloadHttp(remotePath, localPath) {
                var promise = $q.defer();
                ipcRendererUtil.invoke(IPC_COMMAND.DOWNLOAD, remotePath, localPath).then(promise.resolve,
                    function(error) {
                        // make it compatible with sync service which check against code
                        if (error.statusCode) {
                            error.code = error.statusCode;
                        }

                        promise.reject(error);
                    }
                );
                return promise.promise;
            }

            function resolveUrl(path) {
                // if we are resolving system path (/cache), change it to /app/cache
                // /cache folder is electron default cache. better not to touch it
                if (SYSTEM_PATHS.indexOf(path) !== -1) {
                    path = ELECTRON_CACHE_BASE_PATH + path;
                }

                return basePath + '/' + FileUtil.cleanPath(basePath, path);
            }

            function getDefaultPath() {
                return ipcRendererUtil.invoke(IPC_COMMAND.GET_DEFAULT_PATH);
            }

            function createDirectory(path) {
                var promise = $q.defer();
                ipcRendererUtil.invoke(IPC_COMMAND.CREATE_DIRECTORY, path).then(function() {
                    promise.resolve({
                        created: true,
                        entry: new FileEntry({
                            name: FileUtil.getBaseName(path),
                            path: resolveUrl(path),
                            isDirectory: true
                        })
                    });
                }, function(err) {
                    promise.reject(generateFileError('createDirectory', path, err));
                });
                return promise.promise;
            }

            function checkDirectory(path) {
                return checkFileDirectory(path);
            }

            function checkFileDirectory(path) {
                var promise = $q.defer();
                ipcRendererUtil.invoke(IPC_COMMAND.CHECK_FILE_DIRECTORY, path).then(function(isExists) {
                    promise.resolve({
                        path: isExists ? resolveUrl(path) : undefined,
                        exists: isExists
                    });
                }, function(err) {
                    promise.reject(generateFileError('checkFileDirectory', path, err));
                });
                return promise.promise;
            }

            function generateFileError(operation, path, err) {
                return new FileError({
                    operation: operation,
                    message: JSON.stringify(err),
                    path: path
                });
            }

            function removeDirectory(path) {
                return ipcRendererUtil.invoke(IPC_COMMAND.REMOVE_DIRECTORY, path);
            }

            function enumerateDirectory(path) {
                var promise = $q.defer();
                ipcRendererUtil.invoke(IPC_COMMAND.ENUMERATE_DIRECTORY, path).then(function(result) {
                    if (!result) {
                        promise.resolve([]);
                        return;
                    }

                    var fileEntries = [];
                    for (var i = 0; i < result.length; i++) {
                        var fileEntry = result[i];
                        if (!fileEntry) {
                            continue;
                        }

                        fileEntries.push(new FileEntry({
                            name: FileUtil.getBaseName(fileEntry.name),
                            path: fileEntry.path,
                            isDirectory: fileEntry.isDirectory
                        }));
                    }

                    promise.resolve(fileEntries);
                }, function(err) {
                    promise.reject(generateFileError('enumerateDirectory', path, err));
                });

                return promise.promise;
            }

            function checkFile(path) {
                return checkFileDirectory(path);
            }

            function moveFile(source, destination) {
                var promise = $q.defer();
                ipcRendererUtil.invoke(IPC_COMMAND.MOVE_FILE, source, destination).then(function(result) {
                    promise.resolve(new FileEntry({
                        name: FileUtil.getBaseName(destination),
                        path: resolveUrl(destination),
                        isDirectory: result.isDirectory
                    }));
                }, function(err) {
                    promise.reject(generateFileError('moveFile', source, err));
                });
                return promise.promise;
            }

            function removeFile(path) {
                return ipcRendererUtil.invoke(IPC_COMMAND.REMOVE_FILE, path);
            }

            function getSerialNumber() {
                var promise = $q.defer();
                ipcRendererUtil.invoke(IPC_COMMAND.GET_SERIAL_NUMBER).then(function(serial) {
                    if (!serial || (typeof serial === 'string' && (serial.toLowerCase().indexOf('default string') !== -1 || serial.trim() === ''))) {
                        var errorMessage = 'Serial number is invalid or not available: "' + (serial || 'undefined') + '"';
                        logService.warn(LOG_KEY, errorMessage);
                        promise.reject(errorMessage);
                        return;
                    }
                    promise.resolve(serial);
                }, promise.reject);
                return promise.promise;
            }

            function getModel() {
                return driverUtil.getModel(deviceInformation[DEVICE_INFORMATION_KEY.DEVICE_MODEL]);
            }

            function getFingerprint() {
                // Fingerprint retrieval starting with MAC address, and then generated GUID as the fallback
                var promise = $q.defer();
                var getGeneratedFingerprint = function() {
                    driverUtil.getFingerprint().then(promise.resolve);
                };

                getMacAddress().then(function(macAddress) {
                    if (!macAddress) {
                        getGeneratedFingerprint();
                        return;
                    }

                    promise.resolve({
                        fingerprint: macAddress,
                        type: SYSTEM_DATA.FINGERPRINT_TYPE.HARDWARE
                    });
                }, getGeneratedFingerprint);
                return promise.promise;
            }

            function getCpuUsage() {
                var promise = $q.defer();
                ipcRendererUtil.invoke(IPC_COMMAND.GET_CPU_USAGE).then(function(cpuUsage) {
                    promise.resolve({cpuUsage: cpuUsage});
                }, promise.reject);
                return promise.promise;
            }

            function getMemoryUsage() {
                var promise = $q.defer();
                ipcRendererUtil.invoke(IPC_COMMAND.GET_MEMORY_USAGE).then(function(memoryInfo) {
                    promise.resolve({
                        capacity: memoryInfo.total,
                        availableCapacity: memoryInfo.used,
                        useIEC: true
                    });
                }, promise.reject);
                return promise.promise;
            }

            function getAvailableStorage() {
                var promise = $q.defer();
                ipcRendererUtil.invoke(IPC_COMMAND.GET_AVAILABLE_STORAGE, basePath).then(function(storageInfo) {
                    promise.resolve({
                        used: storageInfo.used,
                        total: storageInfo.total,
                        useIEC: true
                    });
                }, promise.reject);
                return promise.promise;
            }

            function getFirmware() {
                return ipcRendererUtil.invoke(IPC_COMMAND.GET_FIRMWARE, basePath);
            }

            function startServer(config) {
                stopServer();
                nodeWebSocketServer = new ExternalNodeWebSocketServer();
                nodeWebSocketServer.start(config);
            }

            function stopServer() {
                if (!nodeWebSocketServer) {
                    return;
                }

                nodeWebSocketServer.stop();
                nodeWebSocketServer = undefined;
            }

            function subscribeServerMessages(callback) {
                if (!nodeWebSocketServer) {
                    return;
                }

                nodeWebSocketServer.subscribeToUpdates(callback);
            }

            function unSubscribeServerMessages(callback) {
                if (!nodeWebSocketServer) {
                    return;
                }

                nodeWebSocketServer.unsubscribeFromUpdates(callback);
            }

            function closeApplication() {
                ipcRendererUtil.invoke(IPC_COMMAND.CLOSE_APPLICATION);
            }

            function getMediaDirective(elementType, options) {
                var result = {
                    directive: elementType,
                    attributes: ''
                };
                options = options || {};
                switch (elementType) {
                    case SYSTEM_DATA.IFRAME_ELEMENT:
                        result.directive = dataUtil.parseBoolean(options[SYSTEM_DATA.FORCE_IFRAME_KEY]) ?
                            result.directive : WEBVIEW_ELEMENT;
                        break;
                }

                return result;
            }

            function processContentDirectiveOptions(content) {
                if (!content || !content.mediaType || !content.options ||
                    (content.mediaType !== CONTENT_TYPE.CARD &&
                    content.mediaType !== CONTENT_TYPE.SIGNS)) {
                    return content;
                }

                content.options[SYSTEM_DATA.FORCE_IFRAME_KEY] = true;
                return content;
            }
        }

        // Return the constructor for the class
        return (ElectronDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');
    app.factory('GoogleMeetDriver', ["$q", "driverUtil", "SYSTEM_DATA", "FEATURE_CODE", function(
        $q,
        driverUtil,
        SYSTEM_DATA,
        FEATURE_CODE) {
        function GoogleMeetDriver() {
        }

        function getAppspaceDeviceType() {
            return 'GOOGLEMEET';
        }

        function getReferenceDeviceIdKey() {
            return 'google_meet_integration_id';
        }

        function getFingerprint() {
            return $q.resolve({
                fingerprint: driverUtil.getReferenceDeviceId(),
                type: SYSTEM_DATA.FINGERPRINT_TYPE.HARDWARE
            });
        }

        function clearSessionStorage() {
            driverUtil.resetSessionStorage([getReferenceDeviceIdKey()]);
        }

        function getFeatureSupport(feature) {
            var featureToCheck = feature.toLowerCase();
            switch (featureToCheck) {
                case FEATURE_CODE.MAX_WEB_VIEW_COLLECTION:
                    return 0;
                default:
                    return null;
            }
        }

        GoogleMeetDriver.prototype = {
            id: 'browser.googlemeet',
            platform: {
                getAppspaceDeviceType: getAppspaceDeviceType,
                getReferenceDeviceIdKey: getReferenceDeviceIdKey,
                getFingerprint: getFingerprint,
                clearSessionStorage: clearSessionStorage,
                getFeatureSupport: getFeatureSupport
            }
        };

        return (GoogleMeetDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');
    app.factory('IAdeaDriver', ["$q", "$window", "$injector", "logService", "SYSTEM_DATA", function(
        $q,
        $window,
        $injector,
        logService,
        SYSTEM_DATA
    ) {
        var httpWrapper;
        var LOG_KEY = 'appspace.core.hal';
        var accessToken;
        var apiBaseUrl;
        var API_BASE_URL = 'http://{IP_ADDRESS}:8080/v2';
        var API_BASE_URL_REPLACE_VALUE = '{IP_ADDRESS}';
        var PLAYER_INFO_API_PATH = '/system/playerInfo';
        var MODEL_INFO_API_PATH = '/system/modelInfo';
        var FIRMWARE_INFO_API_PATH = '/system/firmwareInfo';
        var ACCESS_TOKEN_API_PATH = '/oauth2/token';
        var WIFI_INFO_API_PATH = '/android.net.wifi.WifiManager/getConnectionInfo';
        var ACCESS_TOKEN_URL_PARAMERTER = '?access_token=';
        var deviceInformation = {};
        var DEVICE_INFORMATION_KEY = {
            SERIAL_NUMBER: 'serialNumber',
            FIRMWARE_VERSION: 'firmwareVersion',
            MODEL: 'model',
            MAC_ADDRESS: 'macAddress'
        };
        var DEVICE_INFORMATION_PROPERTY_NAME = {
            SERIAL_NUMBER: 'serialNumber',
            MODEL: 'modelName',
            FIRMWARE_VERSION: 'firmwareVersion',
            MAC_ADDRESS: 'macAddress'
        };
        var NetworkUtil;
        var initializeDefer = $q.defer();
        var isInitialized = false;
        var IADEA_DRIVER_ID = 'browser.iadea';
        var ROOM_STATUS = {
            AVAILABLE: 'available',
            OCCUPIED: 'occupied',
            CHECK_IN: 'check-in',
            OFFLINE: 'offline'
        };
        var STATUS_LIGHT_MAPPING = {};
        STATUS_LIGHT_MAPPING[ROOM_STATUS.AVAILABLE] = {
            brightness: '1',
            color: '#00ff00'
        };
        STATUS_LIGHT_MAPPING[ROOM_STATUS.OCCUPIED] = {
            brightness: '1',
            color: '#ff0000',
        };
        STATUS_LIGHT_MAPPING[ROOM_STATUS.CHECK_IN] = {
            brightness: '1',
            color: '#ffff00',
        };
        STATUS_LIGHT_MAPPING[ROOM_STATUS.OFFLINE] = {
            brightness: '0',
            color: '#000000'
        };

        function IAdeaDriver() {}

        function getHttpWrapper() {
            if (!httpWrapper) {
                var HttpWrapper = $injector.get('HttpWrapper');
                httpWrapper = new HttpWrapper();
            }

            return httpWrapper;
        }

        function getAppspaceDeviceType() {
            return 'IADEA';
        }

        function getDeviceInformation(key) {
            return deviceInformation[key];
        }

        function getFirmware() {
            return $q.resolve(getDeviceInformation(DEVICE_INFORMATION_KEY.FIRMWARE_VERSION));
        }

        function getModel() {
            return deviceInformation[DEVICE_INFORMATION_KEY.MODEL];
        }

        function getSerialNumber() {
            return $q.resolve(getDeviceInformation(DEVICE_INFORMATION_KEY.SERIAL_NUMBER));
        }

        function getMacAddress() {
            return $q.resolve(getDeviceInformation(DEVICE_INFORMATION_KEY.MAC_ADDRESS));
        }

        function getApiBaseUrl() {
            if (apiBaseUrl) {
                return $q.resolve(apiBaseUrl);
            }

            var deferred = $q.defer();

            getIpAddress().then(function(res) {
                apiBaseUrl = API_BASE_URL.replace(API_BASE_URL_REPLACE_VALUE, res.address);

                deferred.resolve(apiBaseUrl);
            });

            return deferred.promise;
        }

        function getFingerprint() {
            var deferred = $q.defer();
            getSerialNumber().then(function(serialNumber) {
                deferred.resolve({
                    fingerprint: serialNumber,
                    type: SYSTEM_DATA.FINGERPRINT_TYPE.HARDWARE,
                });
            });
            return deferred.promise;
        }

        function getNetworkUtil() {
            if (!NetworkUtil) {
                NetworkUtil = $injector.get('NetworkUtil');
            }

            return NetworkUtil;
        }

        function getIpAddress() {
            return getNetworkUtil().getCordovaIpAddress(LOG_KEY);
        }

        function initialize() {
            if (!isInitialized) {
                getApiBaseUrl().then(function(res) {
                    return $q.resolve(getAccessToken());
                })
                .then(retrieveDeviceInformation)
                .catch(function(error) {
                    initializeDefer.reject(error);
                });
            }

            return initializeDefer.promise;
        }

        function getAccessToken() {
            if (accessToken) {
                return $q.resolve(accessToken);
            }

            var deferred = $q.defer();

            getHttpWrapper().handlerPost(apiBaseUrl + ACCESS_TOKEN_API_PATH, {
                grant_type: 'password',
                username: 'admin',
                password: ''
            }).then(function(response) {
                var data = (response || {}).data;
                if (!data || !data.access_token) {
                    return deferred.reject('No access token returned');
                }

                accessToken = data.access_token;
                deferred.resolve(accessToken);
            }, function(error) {
                deferred.reject('Failed to retrieve access token: ' + JSON.stringify(error));
            });

            return deferred.promise;
        }

        function retrieveDeviceInformation() {
            isInitialized = true;

            var onFailure = function(error) {
              logService.error(LOG_KEY, 'Failed to retrieve device information: ' + error);
              initializeDefer.resolve();
            };

            function fetchAndValidate(path, key, property) {
              return getHttpWrapper().handlerGet(apiBaseUrl + path + ACCESS_TOKEN_URL_PARAMERTER + accessToken).then(function(response) {
                var data = (response || {}).data;
                if (!data) {
                  onFailure('No data returned from ' + key + ' API');
                }

                deviceInformation[key] = data[property];
              });
            }

            var promises = [
              fetchAndValidate(PLAYER_INFO_API_PATH, DEVICE_INFORMATION_KEY.SERIAL_NUMBER, DEVICE_INFORMATION_PROPERTY_NAME.SERIAL_NUMBER),
              fetchAndValidate(MODEL_INFO_API_PATH, DEVICE_INFORMATION_KEY.MODEL, DEVICE_INFORMATION_PROPERTY_NAME.MODEL),
              fetchAndValidate(FIRMWARE_INFO_API_PATH, DEVICE_INFORMATION_KEY.FIRMWARE_VERSION, DEVICE_INFORMATION_PROPERTY_NAME.FIRMWARE_VERSION),
              fetchAndValidate(WIFI_INFO_API_PATH, DEVICE_INFORMATION_KEY.MAC_ADDRESS, DEVICE_INFORMATION_PROPERTY_NAME.MAC_ADDRESS)
            ];

            $q.all(promises).then(function() {
                logService.info(LOG_KEY, 'Device information retrieved: ' + JSON.stringify(deviceInformation));
                initializeDefer.resolve();
            }, function(error) {
                onFailure(JSON.stringify(error));
            });
        }

        function setLedLight(mapping) {
            return getHttpWrapper().handlerPost(apiBaseUrl + '/hardware/light?access_token=' + accessToken, {
                name: 'frame',
                brightness: mapping.brightness,
                color: mapping.color
            });
        }

        function enableStatusLight(config) {
            if (!config || !config.status) {
                return $q.reject('Invalid color status');
            }

            var mapping = STATUS_LIGHT_MAPPING[config.status];
            return !mapping ?
                $q.reject('Unable to find status light mapping for: ' + config.status) :
                setLedLight(mapping);
        }

        function disableStatusLight() {
            return setLedLight(STATUS_LIGHT_MAPPING[ROOM_STATUS.OFFLINE]);
        }

        IAdeaDriver.prototype = {
            id: IADEA_DRIVER_ID,
            platform: {
                initialize: initialize,
                getAppspaceDeviceType: getAppspaceDeviceType,
                getAppPackageType: getAppspaceDeviceType,
                getFirmware: getFirmware,
                getModel: getModel,
                getSerialNumber: getSerialNumber,
                getFingerprint: getFingerprint,
                enableStatusLight: enableStatusLight,
                disableStatusLight: disableStatusLight,
            },
            network: {
                getMacAddress: getMacAddress,
                getIpAddress: getIpAddress,
            }
        };

        return (IAdeaDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var app = $angular.module('hal');
    app.factory('Ie11Driver', ["FEATURE_CODE", function(FEATURE_CODE) {
        function Ie11Driver() {
            this.id = 'Ie11Driver';
            this.platform = {
                getFeatureSupport: getFeatureSupport
            };

            function getFeatureSupport(feature) {
                if (!feature) {
                    return null;
                }
                feature = feature.toLowerCase();
                switch (feature) {
                    case FEATURE_CODE.MEDIA_SUPPORT_DATA_PROTOCOL:
                        return 'no';
                    default:
                        return null;
                }
            }
        }

        return (Ie11Driver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('LinuxDriver', ["$q", "ipcRendererUtil", "IPC_COMMAND", "logService", function($q, ipcRendererUtil, IPC_COMMAND, logService) {
        function LinuxDriver() {
            var LOG_KEY = 'appspace.core.hal';
            this.id = 'browser.linux';
            this.platform = {
                reboot: reboot,
                getFeatureSupport: getFeatureSupport,
                getAppspaceDeviceType: getAppspaceDeviceType,
                getAppPackageType: getAppspaceDeviceType,
                getAppUpdateCapabilities: getAppUpdateCapabilities,
                updateApp: updateApp
            };

            function getAppspaceDeviceType() {
                return 'LINUX';
            }

            function reboot() {
                return ipcRendererUtil.invoke(IPC_COMMAND.REBOOT);
            }

            function getAppUpdateCapabilities() {
                return {
                    scheduling: false,
                    frequency: false,
                    downgrade: false,
                    pinning: false,
                };
            }

            function updateApp() {
                var message = 'Update service is only available on app store.';
                logService.error(LOG_KEY, message);
                return $q.reject(message);
            }

            function getFeatureSupport(feature) {
                if (!feature) {
                    return null;
                }

                switch (feature.toLowerCase()) {
                    case 'device.supportselfupdate':
                        return false;
                }

                return null;
            }
        }

        // Return the constructor for the class
        return (LinuxDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('LogitechDriver', ["$injector", "$window", "$q", "SYSTEM_DATA", "logService", "DEVICE_TYPE", "dataUtil", function(
        $injector,
        $window,
        $q,
        SYSTEM_DATA,
        logService,
        DEVICE_TYPE,
        dataUtil
    ) {
        var DEVICE_READY_EVENT = 'deviceready';
        var LOG_KEY = 'appspace.core.hal';
        var LOGITECH_DRIVER_ID = 'browser.logitech';
        var MENU_INTERNAL_DEVICE_SETTINGS = LOGITECH_DRIVER_ID + '.internaldevicesettings';
        var ROOM_STATUS = {
            AVAILABLE: 'available',
            OCCUPIED: 'occupied',
            CHECK_IN: 'check-in'
        };
        var LED_COLOR = {
            AMBER: 4,
            BLUE: 2,
            GREEN: 1,
            PURPLE: 5,
            RED: 0,
            WHITE: 6,
            YELLOW: 3
        };
        var COLOR_PALETTE = {
            DEFAULT: 'DEFAULT',
            ADA: 'ADA'
        };
        var STATUS_COLOR_CONFIGURATION = {};
        STATUS_COLOR_CONFIGURATION[COLOR_PALETTE.DEFAULT] = {};
        STATUS_COLOR_CONFIGURATION[COLOR_PALETTE.ADA] = {};
        STATUS_COLOR_CONFIGURATION[COLOR_PALETTE.DEFAULT][ROOM_STATUS.AVAILABLE] = LED_COLOR.GREEN;
        STATUS_COLOR_CONFIGURATION[COLOR_PALETTE.DEFAULT][ROOM_STATUS.OCCUPIED] = LED_COLOR.RED;
        STATUS_COLOR_CONFIGURATION[COLOR_PALETTE.DEFAULT][ROOM_STATUS.CHECK_IN] = LED_COLOR.YELLOW;
        STATUS_COLOR_CONFIGURATION[COLOR_PALETTE.ADA][ROOM_STATUS.AVAILABLE] = LED_COLOR.BLUE;
        STATUS_COLOR_CONFIGURATION[COLOR_PALETTE.ADA][ROOM_STATUS.OCCUPIED] = LED_COLOR.AMBER;
        STATUS_COLOR_CONFIGURATION[COLOR_PALETTE.ADA][ROOM_STATUS.CHECK_IN] = LED_COLOR.YELLOW;
        var STATIC_MENU = {};
        var isReadyCallback;
        var isReadyPromise = $q.defer();
        var networkUtil;
        var deviceModel;

        function LogitechDriver(onDeviceReadyCallback) {
            isReadyCallback = onDeviceReadyCallback;
            document.removeEventListener(DEVICE_READY_EVENT, onDeviceReady);
            document.addEventListener(DEVICE_READY_EVENT, onDeviceReady, false);
            STATIC_MENU[MENU_INTERNAL_DEVICE_SETTINGS] = {
                text: 'Internal Device Settings',
                callback: openInternalDeviceSettings
            };
        }

        function onDeviceReady() {
            var callback = function(error) {
                if (error) {
                    logService.error(LOG_KEY, 'Error while creating Logitech platform. Error source: ' + JSON.stringify(error));
                }

                if (isReadyCallback) {
                    isReadyCallback($window.device);
                }

                isReadyPromise.resolve();
            };

            if (!$window.cordova || !$window.cordova.plugins || !$window.cordova.plugins.LogitechCordovaPlugin || !$window.cordova.plugins.LogitechCordovaPlugin.createPlatform) {
                callback('Logitech plugin is not available');
                return;
            }

            $window.cordova.plugins.LogitechCordovaPlugin.createPlatform(callback, callback);
        }

        function isReady() {
            return isReadyPromise.promise;
        }

        function getDisplayInfo() {
            if (!$window.plugins || !$window.plugins.screensize || !$window.plugins.screensize.get) {
                return $q.resolve('');
            }

            var promise = $q.defer();
            var finalize = function(result) {
                promise.resolve(result);
            };

            $window.plugins.screensize.get(function(result) {
                var displayInfo = [];
                displayInfo.push({
                    x: result.width,
                    y: result.height
                });
                finalize(displayInfo);
            }, function() {
                finalize('');
            });

            return promise.promise;
        }

        function getMediaDirective(elementType) {
            return {
                directive: elementType,
                attributes: ''
            };
        }

        function getProductInfo() {
            var defer = $q.defer();
            $window.cordova.plugins.LogitechCordovaPlugin.getProductInfo(defer.resolve, function(error) {
                logService.error(LOG_KEY, 'Error while retrieving product info. Error source: ' + JSON.stringify(error));
                defer.reject(error);
            });
            return defer.promise;
        }

        function getManufacturer() {
            var defer = $q.defer();
            getProductInfo().then(function(result) {
                defer.resolve(result.manufacturer);
            }, defer.reject);
            return defer.promise;
        }

        function getFirmware() {
            var defer = $q.defer();
            $window.cordova.plugins.LogitechCordovaPlugin.getFirmwareInfo(function(result) {
                if (result && result.systemFirmwareVersion) {
                    defer.resolve(result.systemFirmwareVersion);
                    return;
                }

                var error = 'Error while retrieving firmware info';
                logService.error(LOG_KEY, error);
                defer.reject(error);
            }, function(error) {
                logService.error(LOG_KEY, 'Error while retrieving firmware info. Error source: ' + JSON.stringify(error));
                defer.reject(error);
            });
            return defer.promise;
        }

        function getSerialNumber() {
            var defer = $q.defer();
            $window.cordova.plugins.LogitechCordovaPlugin.getSerialNumber(function(result) {
                if (result && result.serialNumber) {
                    defer.resolve(result.serialNumber);
                    return;
                }

                var error = 'Error while retrieving serial number';
                logService.error(LOG_KEY, error);
                defer.reject(error);
            }, function(error) {
                logService.error(LOG_KEY, 'Error while retrieving serial number. Error source: ' + JSON.stringify(error));
                defer.reject(error);
            });

            return defer.promise;
        }

        function getAppspaceDeviceType() {
            return $window.localStorage.getItem('*device>>type') || DEVICE_TYPE.LOGITECH;
        }

        function getAppPackageType() {
            return DEVICE_TYPE.LOGITECH;
        }

        function getRuntimeEnvironments() {
            var storedDeviceType = getAppspaceDeviceType();
            return [storedDeviceType.toLowerCase()];
        }

        function getDeviceMenu() {
            return STATIC_MENU;
        }

        function openInternalDeviceSettings() {
            var defer = $q.defer();
            $window.cordova.plugins.LogitechCordovaPlugin.launchSystemSettingsApp(defer.resolve, defer.reject);
            return defer.promise;
        }

        function enableStatusLight(config) {
            if (!config || !config.status) {
                return $q.reject('Invalid color status');
            }

            var colorPalette = config.colorPalette;
            if (colorPalette) {
                colorPalette = colorPalette.toUpperCase();
            }

            var statusColorConfiguration = STATUS_COLOR_CONFIGURATION[colorPalette] || STATUS_COLOR_CONFIGURATION[COLOR_PALETTE.DEFAULT];
            var ledColor = statusColorConfiguration[config.status];
            if (dataUtil.isNullOrUndefined(ledColor)) {
                return $q.reject('Invalid color status');
            }

            var defer = $q.defer();
            $window.cordova.plugins.LogitechCordovaPlugin.enableStatusLight(ledColor, defer.resolve, defer.reject);
            return defer.promise;
        }

        function disableStatusLight() {
            var defer = $q.defer();
            $window.cordova.plugins.LogitechCordovaPlugin.disableStatusLight(defer.resolve, defer.reject);
            return defer.promise;
        }

        function getFeatureSupport(feature) {
            feature = feature.toLowerCase();
            switch (feature) {
                case 'network.maxconcurrentdownloads':
                    return 1;
                default:
                    return null;
            }
        }

        function getFingerprint() {
            var promise = $q.defer();
            getSerialNumber().then(function(serialNumber) {
                promise.resolve({
                    fingerprint: serialNumber,
                    type: SYSTEM_DATA.FINGERPRINT_TYPE.HARDWARE
                });
            }, function(error) {
                logService.error(LOG_KEY, 'Error while retrieving serial number for the fingerprint. Error source: ' + JSON.stringify(error));
                promise.reject(error);
            });

            return promise.promise;
        }

        function getMacAddress() {
            var defer = $q.defer();
            $window.cordova.plugins.LogitechCordovaPlugin.getMacAddress(function(result) {
                if (result && result.macAddress) {
                    defer.resolve(result.macAddress);
                    return;
                }

                var error = 'Error while retrieving MAC Address';
                logService.error(LOG_KEY, error);
                defer.reject(error);
            }, function(error) {
                logService.error(LOG_KEY, 'Error while retrieving MAC Address. Error source: ' + JSON.stringify(error));
                defer.reject(error);
            });

            return defer.promise;
        }

        function getModel() {
            if (deviceModel) {
                return $q.resolve(deviceModel);
            }

            var defer = $q.defer();
            function failToGetDeviceModel(error) {
                error = error || '';
                var errorMessage = 'Error while retrieving device model: ';
                logService.error(LOG_KEY, errorMessage + JSON.stringify(error));
                defer.reject(error);
            }

            $window.cordova.plugins.LogitechCordovaPlugin.getDeviceModel(function(result) {
                if (!result || !result.deviceModel) {
                    failToGetDeviceModel('Invalid result.');
                    return;
                }

                deviceModel = result.deviceModel;
                defer.resolve(deviceModel);
            }, failToGetDeviceModel);

            return defer.promise;
        }

        function getNetworkUtil() {
            if (!networkUtil) {
                networkUtil = $injector.get('NetworkUtil');
            }

            return networkUtil;
        }

        function getIpAddress() {
            return getNetworkUtil().getCordovaIpAddress(LOG_KEY);
        }

        LogitechDriver.prototype = {
            id: LOGITECH_DRIVER_ID,
            isReady: isReady,
            platform: {
                getAppspaceDeviceType: getAppspaceDeviceType,
                getAppPackageType: getAppPackageType,
                getRuntimeEnvironments: getRuntimeEnvironments,
                getDisplayInfo: getDisplayInfo,
                getManufacturer: getManufacturer,
                getModel: getModel,
                getFirmware: getFirmware,
                getSerialNumber: getSerialNumber,
                getDeviceMenu: getDeviceMenu,
                enableStatusLight: enableStatusLight,
                disableStatusLight: disableStatusLight,
                getFeatureSupport: getFeatureSupport,
                getFingerprint: getFingerprint
            },
            media: {
                getMediaDirective: getMediaDirective
            },
            network: {
                getMacAddress: getMacAddress,
                getIpAddress: getIpAddress,
            }
        };

        return (LogitechDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('MacosDriver', ["platformUtil", "driverUtil", "$injector", "$q", "logService", "SYSTEM_DATA", function(
        platformUtil,
        driverUtil,
        $injector,
        $q,
        logService,
        SYSTEM_DATA) {
        var MINIMUM_STREAMING_ELECTRON_VERSION = '27.0.0';
        var ELECTRON_VERSION_REGEX = /(?:Electron\/)((\d*\.?)+)/i;
        function MacosDriver() {
            var LOG_KEY = 'appspace.core.hal';
            this.id = 'browser.macos';

            this.platform = {
                getAppspaceDeviceType: getAppspaceDeviceType,
                getAppPackageType: getAppspaceDeviceType,
                getFingerprint: getFingerprint,
            };

            this.media = {
                supportsCardStreaming: supportsCardStreaming
            };

            function getAppspaceDeviceType() {
                return 'MACOS';
            }

            function getVersionUtil() {
                return $injector.get('versionUtil');
            }

            function getElectronVersion() {
                var matches = platformUtil.getUserAgent().match(ELECTRON_VERSION_REGEX);
                return matches && matches.length >= 2 ? matches[1] : undefined;
            }

            function supportsCardStreaming() {
                var currentElectronVersion = getElectronVersion();
                return currentElectronVersion && getVersionUtil().isGreaterThanOrEqual(currentElectronVersion, MINIMUM_STREAMING_ELECTRON_VERSION);
            }

            function getFingerprint() {
                var promise = $q.defer();

                function fallbackToGeneratedFingerprint(reason) {
                    logService.warn(LOG_KEY, reason);
                    driverUtil.getFingerprint().then(function(fingerprint) {
                        promise.resolve(fingerprint);
                    }, function(error) {
                        var errorMessage = 'Failed to generate fingerprint: ' + JSON.stringify(error);
                        logService.error(LOG_KEY, errorMessage);
                        promise.reject(errorMessage);
                    });
                }

                driverUtil.getSerialNumber().then(function(serialNumber) {
                    if (driverUtil.isFingerprintFormatValid(serialNumber)) {
                        promise.resolve({
                            fingerprint: serialNumber,
                            type: SYSTEM_DATA.FINGERPRINT_TYPE.HARDWARE,
                        });
                        return;
                    }

                    fallbackToGeneratedFingerprint('Serial number retrieved is of unsupported fingerprint format. Defaulting to generated fingerprint...');
                }, function(error) {
                    fallbackToGeneratedFingerprint('Serial number retrieval failed due to: ' + JSON.stringify(error) + '. Falling back to generated fingerprint...');
                });

                return promise.promise;
            }
        }

        // Return the constructor for the class
        return (MacosDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');
    app.factory('MersiveDriver', ["FEATURE_CODE", "MEDIA_DIRECTIVE", function(FEATURE_CODE, MEDIA_DIRECTIVE) {
        var CARD_TIMEOUT_DURATION = 60000;
        var TRANSPARENT_1PX_PNG = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="';
        var VIDEO_ATTRIBUTES = 'poster="data:image/png;base64,' + TRANSPARENT_1PX_PNG;

        function MersiveDriver() { }

        function getAppspaceDeviceType() {
            return 'MERSIVE';
        }

        function getFeatureSupport(feature) {
            switch ((feature || '').toLowerCase()) {
                case 'support.lowbandwidth.mode':
                    return true;
                case FEATURE_CODE.CARD_TIMEOUT_DURATION:
                    return CARD_TIMEOUT_DURATION;
                default:
                    return null;
            }
        }

        function getMediaDirective(elementType, options) {
            var result = {
                directive: elementType,
                attributes: ''
            };
            if (elementType === MEDIA_DIRECTIVE.CARD) {
                result.options = {
                    supportSwapWithVideo: false
                };
            }

            if (elementType === MEDIA_DIRECTIVE.AS_MEDIA_VIDEO) {
                result.attributes = VIDEO_ATTRIBUTES;
                result.attributes += ' allow-play-through="true"';
            }

            if (elementType === MEDIA_DIRECTIVE.VIDEO) {
                result.attributes = VIDEO_ATTRIBUTES;
                result.attributes += ' crossorigin="anonymous"';
            }

            return result;
        }

        MersiveDriver.prototype = {
            id: 'browser.mersive',
            platform: {
                getAppspaceDeviceType: getAppspaceDeviceType,
                getFeatureSupport: getFeatureSupport
            },
            media: {
                getMediaDirective: getMediaDirective
            }
        };

        return (MersiveDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('MtrDriver', ["$q", "$timeout", "$injector", "$location", "logService", "webBroadcastService", "DRIVER_ID", "dataUtil", "ROUTE", "STORAGE_KEY", "STORAGE_COLLECTION_KEY", "APPSPACE_CONFIGURATION_PROPERTY", "REGISTRATION_MODE", "CONTENT_STATE_EVENT", "NETWORK_CONNECTION_STATE", "FEATURE_CODE", "WEB_BROADCAST_MESSAGES", "POST_MESSAGES", function(
        $q,
        $timeout,
        $injector,
        $location,
        logService,
        webBroadcastService,
        DRIVER_ID,
        dataUtil,
        ROUTE,
        STORAGE_KEY,
        STORAGE_COLLECTION_KEY,
        APPSPACE_CONFIGURATION_PROPERTY,
        REGISTRATION_MODE,
        CONTENT_STATE_EVENT,
        NETWORK_CONNECTION_STATE,
        FEATURE_CODE,
        WEB_BROADCAST_MESSAGES,
        POST_MESSAGES
    ) {
        var isDeviceMuted = false;
        var isLoaded = false;
        var LOG_KEY = 'appspace.core.hal';
        var claimDeviceUtil;
        var coreConfigurationService;
        var postMessagingService;
        var sessionService;
        var coreFrameworkService;
        var syncService;
        var HEALTH_STATUS = {
            FATAL: 'FatalError',
            TOKEN_EXPIRED: 'TokenExpired',
            INVALID_TOKEN: 'InvalidToken',
            SERVER_UNREACHABLE: 'ServerUnreachable',
            DEVICE_UNREGISTERED: 'DeviceUnregistered',
            HEALTHY: 'Healthy',
            UNHEALTHY: 'Unhealthy',
            UNEXPECTED_ERROR: 'UnexpectedError',
            NO_CONTENT_AVAILABLE: 'NoAvailableContent',
            ITEM_LOAD_ERROR: 'ErrorOnContent',
            NO_VALID_CONTENT: 'NoValidContent',
        };
        var VALID_MTR_DOMAIN = {
            GLOBAL_TEAMS: 'teams.microsoft.com',
            US_TEAMS: 'gov.teams.microsoft.us',
            TEAMS_OFFICE: 'teams.office.com',
            MICROSOFT_CLOUD: 'cloud.microsoft',
        };
        var claimDeviceAttempt = {};
        var dateTimeUtil;
        var deviceHealthService;
        var DEFAULT_ERROR_TITLE = 'There was an issue registering your device. Please try again later.';
        var registrationErrorConfiguration = {
            title: DEFAULT_ERROR_TITLE,
            message: '',
            showButton: false,
            showLoader: false,
        };
        var isReadyDefer = $q.defer();
        var HEARTBEAT_INTERVAL = 60000;
        var BROADCAST_MESSAGE_TIMEOUT = 1000;
        var isPrimary = undefined;
        var heartbeatTimeout;
        var frameGuid;
        var pingDefer;
        var pairDefer;
        var pairTimeout;
        var DEVICE_CLAIM_STATUS = {
            CLAIMED: 'claimed',
            IN_PROGRESS: 'in_progress',
        };
        var checkClaimDefer;

        function MtrDriver() {
            frameGuid = dataUtil.generateRandomGuid();
            webBroadcastService.start();
            webBroadcastService.subscribe(onBroadcastMessage);
            checkPrimaryFrame().then(function(flag) {
                isPrimary = flag;
                isReadyDefer.resolve();
            });
        }

        function isReady() {
            return isReadyDefer.promise;
        }

        function getDateTimeUtil() {
            if (!dateTimeUtil) {
                var DateTimeUtil = $injector.get('DateTimeUtil');
                dateTimeUtil = new DateTimeUtil();
            }

            return dateTimeUtil;
        }

        function initialize() {
            var urlParameters = getSessionService().getUrlParameters();

            if (!urlParameters || !urlParameters.apiurl || !urlParameters.deviceguid || !urlParameters.registrationmode || !urlParameters.integrationtype) {
                raiseErrorMessage('Insufficient query parameters.');
                return $q.resolve();
            }

            if (!isClaimDeviceMode()) {
                raiseErrorMessage('Invalid registration mode.');
                return $q.resolve();
            }

            saveServiceApiUrl(urlParameters.apiurl);
            raiseInitMessage();
            return $q.resolve();
        }

        function triggerDeviceMuted(muted) {
            isDeviceMuted = muted === true;
        }

        function isClaimDeviceMode() {
            var urlParameters = getSessionService().getUrlParameters();
            return Boolean(urlParameters && urlParameters.registrationmode && urlParameters.registrationmode === REGISTRATION_MODE.CLAIM_DEVICE);
        }

        function getClaimDeviceUtil() {
            if (!claimDeviceUtil) {
                claimDeviceUtil = $injector.get('claimDeviceUtil');
            }

            return claimDeviceUtil;
        }

        function getPostMessagingService() {
            if (!postMessagingService) {
                postMessagingService = $injector.get('postMessagingService');
            }

            return postMessagingService;
        }

        function getCoreConfigurationService() {
            if (!coreConfigurationService) {
                coreConfigurationService = $injector.get('coreConfigurationService');
            }

            return coreConfigurationService;
        }

        function getSessionService() {
            if (!sessionService) {
                sessionService = $injector.get('sessionService');
            }

            return sessionService;
        }

        function getCoreFrameworkService() {
            if (!coreFrameworkService) {
                coreFrameworkService = $injector.get('coreFrameworkService');
            }

            return coreFrameworkService;
        }

        function saveServiceApiUrl(value) {
            getCoreConfigurationService().set(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.SERVICE_API_URL, value);
        }

        function raiseMtrMessage(messageData) {
            var message = dataUtil.mergeObject({
                eventId: dataUtil.generateRandomGuid(),
                eventSource: POST_MESSAGES.SOURCE.APPSPACE,
                eventTarget: POST_MESSAGES.SOURCE.MICROSOFT
            }, messageData);

            logService.debug(LOG_KEY, 'Posting MTR message: ' + JSON.stringify(message));
            getPostMessagingService().raiseParentMessage(message);
        }

        function raiseInitMessage() {
            var message = {
                eventType: POST_MESSAGES.INIT,
                payload: {
                    appVersion: getCoreFrameworkService().getAppVersion(),
                    vendorId: POST_MESSAGES.SOURCE.APPSPACE
                },
            };
            raiseMtrMessage(message);
        }

        // TO DO: Remove after the full implementation of new error handling
        function raiseErrorMessage(errorMessage) {
            var message = {
                eventType: POST_MESSAGES.ERROR,
                payload: {
                    errorCode: HEALTH_STATUS.FATAL,
                    reason: errorMessage
                },
            };

            raiseMtrMessage(message);
            $location.path(ROUTE.REGISTRATION_FAIL);
            getDeviceHealthService().setHealthStatus(message.payload);
        }

        function processDeviceError(error) {
            var message = {
                eventType: POST_MESSAGES.ERROR,
                payload: {
                    errorCode: error.errorCode,
                    reason: error.reason,
                },
            };

            if (!error.isRecoverable) {
                raiseMtrMessage(message);
            }

            if (!error.skipErrorPageRedirection) {
                // Add timestamp query parameter for registration controller to re-initialize
                // so that the latest registration error can be displayed accordingly
                $location.path(ROUTE.REGISTRATION_FAIL + '?timestamp=' + Date.now());
            }

            getDeviceHealthService().setHealthStatus(message.payload);
        }

        function processHealthMessage(payload) {
            var healthMessage = {
                eventType: POST_MESSAGES.HEALTH,
                payload: payload
            };
            raiseMtrMessage(healthMessage);
            if (payload.errorCode) {
                getDeviceHealthService().setHealthStatus(payload);
                return;
            }

            getDeviceHealthService().clearHealthStatus();
        }

        function raiseLoadMessage() {
            var message = {
                eventType: POST_MESSAGES.LOAD,
            };
            raiseMtrMessage(message);
            isLoaded = true;
        }

        function cancelClaimDeviceRetryTimeout() {
            if (!claimDeviceAttempt.retryTimeout) {
                return;
            }

            $timeout.cancel(claimDeviceAttempt.retryTimeout);
            claimDeviceAttempt.retryTimeout = undefined;
        }

        function resetClaimDeviceAttempt(payload) {
            registrationErrorConfiguration.message = '';
            registrationErrorConfiguration.title = DEFAULT_ERROR_TITLE;
            registrationErrorConfiguration.showButton = false;
            registrationErrorConfiguration.showLoader = false;
            cancelClaimDeviceRetryTimeout();
            claimDeviceAttempt = {
                retryTimeout: undefined,
                failureCount: 0,
                payload: payload,
                maxRetryCount: 5,
            };
        }

        function retryClaimDevice(failureErrorMessage) {
            if (claimDeviceAttempt.retryTimeout) {
                logService.debug(LOG_KEY, 'Device claim will be re-attempted soon. Skipping repeated attempt...');
                return;
            }

            claimDeviceAttempt.failureCount++;
            if (claimDeviceAttempt.failureCount >= claimDeviceAttempt.maxRetryCount) {
                var errorMessage = 'Device claim failed after ' + claimDeviceAttempt.maxRetryCount + ' attempts.';
                logService.error(LOG_KEY, errorMessage);
                registrationErrorConfiguration.title = 'There was an issue registering your device.';
                registrationErrorConfiguration.message = errorMessage;
                registrationErrorConfiguration.showLoader = false;
                registrationErrorConfiguration.showButton = false;
                processDeviceError({
                    errorCode: HEALTH_STATUS.SERVER_UNREACHABLE,
                    reason: errorMessage,
                });

                resetClaimDeviceAttempt();
                return;
            }

            var backOffTime = getDateTimeUtil().getBackoffTimeInSeconds(claimDeviceAttempt.failureCount);
            cancelClaimDeviceRetryTimeout();
            var timeText = getDateTimeUtil().toTimeText(backOffTime);
            logService.debug(LOG_KEY, 'Retrying device claim in ' + timeText + '. Current failure count: ' + claimDeviceAttempt.failureCount);
            registrationErrorConfiguration.title = 'There was an issue registering your device.';
            registrationErrorConfiguration.message = 'Retrying in ' + timeText + ' (' + claimDeviceAttempt.failureCount + '/' + claimDeviceAttempt.maxRetryCount + ')';
            registrationErrorConfiguration.showLoader = true;
            registrationErrorConfiguration.showButton = false;
            processDeviceError({
                errorCode: HEALTH_STATUS.SERVER_UNREACHABLE,
                reason: failureErrorMessage,
                isRecoverable: true,
            });
            claimDeviceAttempt.retryTimeout = $timeout(function() {
                cancelClaimDeviceRetryTimeout();
                doClaimDevice(claimDeviceAttempt.payload);
            }, getDateTimeUtil().toMilliseconds(backOffTime));
        }

        function getHealthStatus() {
            var healthStatus = getDeviceHealthService().getHealthStatus() || {};
            healthStatus.state = dataUtil.isNullOrUndefined(healthStatus.errorCode) ?
                HEALTH_STATUS.HEALTHY : HEALTH_STATUS.UNHEALTHY;
            return healthStatus;
        }

        function respondToHealthCheck(request) {
            raiseMtrMessage({
                respondTo: request.eventId,
                eventType: request.eventType,
                payload: getHealthStatus(),
            });
        }

        function claimSuccess() {
            getCoreConfigurationService().set(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.DEVICE_CLAIM_STATUS, DEVICE_CLAIM_STATUS.CLAIMED);
            var isResyncRequired = getHealthStatus().state === HEALTH_STATUS.UNHEALTHY;
            resetClaimDeviceAttempt();
            raiseLoadMessage();
            processHealthMessage({
                state: HEALTH_STATUS.HEALTHY
            });
            // To prevent the device from being stuck in an unhealthy state perpetually
            // until it manages to sync new content
            if (isResyncRequired) {
                getSyncService().resync();
            }

            $location.path(ROUTE.APP_INIT);
        }

        function getSyncService() {
            if (!syncService) {
                syncService = $injector.get('syncService');
            }

            return syncService;
        }

        function doClaimDevice(payload) {
            var urlParameters = getSessionService().getUrlParameters();
            var deviceGuid = urlParameters.deviceguid;
            var config = {
                deviceGuid: deviceGuid,
                registrationToken: payload.databag.registrationToken,
            };

            getClaimDeviceUtil().claimDevice(config).then(claimSuccess, function(error) {
                var errorMessage = error;
                var isFatal = true;
                var errorCode = HEALTH_STATUS.UNEXPECTED_ERROR;
                if (dataUtil.isObject(error)) {
                    error.originalError = error.originalError || {};
                    if (error.originalError.isServerError) {
                        isFatal = false;
                        errorCode = HEALTH_STATUS.SERVER_UNREACHABLE;
                    }

                    if (error.originalError.isUnauthorized) {
                        errorCode = HEALTH_STATUS.INVALID_TOKEN;
                    }

                    if (error.originalError.isForbidden) {
                        errorCode = HEALTH_STATUS.TOKEN_EXPIRED;
                    }

                    errorMessage = error.errorMessage;
                }

                if (isFatal) {
                    processDeviceError({
                        errorCode: errorCode,
                        reason: errorMessage,
                    });
                    return;
                }

                retryClaimDevice(errorMessage);
            });
        }

        function claimDevice(payload) {
            if (!dataUtil.isNullOrUndefined(isPrimary) && !isPrimary) {
                logService.debug(LOG_KEY, 'Claim device is not supported on secondary screen. Checking primary status...');
                checkPrimaryClaimStatus();
                return;
            }

            getCoreConfigurationService().set(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.DEVICE_CLAIM_STATUS, DEVICE_CLAIM_STATUS.IN_PROGRESS);
            resetClaimDeviceAttempt(payload);
            doClaimDevice(payload);
        }

        function checkPrimaryClaimStatus() {
            sendCheckClaimStatus().then(function(status) {
                if (status === DEVICE_CLAIM_STATUS.CLAIMED) {
                    claimSuccess();
                    return;
                }

                $timeout(function() {
                    claimDevice();
                }, BROADCAST_MESSAGE_TIMEOUT);
            }, function() {
                onBroadcastMessageTimeout().then(function() {
                    claimDevice();
                });
            });
        }

        function sendCheckClaimStatus() {
            checkClaimDefer = $q.defer();
            postWebBroadcastMessage({
                topic: WEB_BROADCAST_MESSAGES.MTR_CLAIM_REQUESTSTATUS,
            });

            $timeout(function() {
                checkClaimDefer.reject();
            }, BROADCAST_MESSAGE_TIMEOUT);

            return checkClaimDefer.promise;
        }

        function getAppspaceConfigurationIntegrationUrl() {
            var appspaceConfiguration = getCoreConfigurationService().getAppspaceConfiguration() || {};
            var integrationUrl = appspaceConfiguration[APPSPACE_CONFIGURATION_PROPERTY.INTEGRATIONURL] || {};
            return integrationUrl;
        }

        function checkIsTrustedDomain(messageData, messageOrigin) {
            if (!messageData || !messageData.eventSource || !messageData.eventTarget || !messageOrigin) {
                return false;
            }

            function isValidResource() {
                return messageData.eventSource === POST_MESSAGES.SOURCE.MICROSOFT
                    && messageData.eventTarget === POST_MESSAGES.SOURCE.APPSPACE;
            }

            function isDomainWhitelisted() {
                for (var key in VALID_MTR_DOMAIN) {
                    if (dataUtil.includesString(VALID_MTR_DOMAIN[key], messageOrigin)) {
                        return true;
                    }
                }

                return false;
            }

            function isTrustedDomain() {
                return isValidResource() && isDomainWhitelisted();
            }

            function isIntegrationUrl() {
                return isValidResource() && dataUtil.includesString(getAppspaceConfigurationIntegrationUrl(), messageOrigin);
            }

            return isTrustedDomain() || isIntegrationUrl();
        }

        function storeDeviceName(deviceName) {
            getCoreConfigurationService().setReferenceDeviceName(deviceName);
        }

        function processMtrMessage(eventData) {
            if (!eventData || !eventData.eventType) {
                return false;
            }

            switch (eventData.eventType) {
                case POST_MESSAGES.INIT:
                    triggerDeviceMuted(eventData.payload.muteAudio);
                    storeDeviceName(eventData.payload.deviceName);
                    claimDevice(eventData.payload);
                    return true;
                case POST_MESSAGES.HEALTH:
                    respondToHealthCheck(eventData);
                    return true;
            }
        }

        function processPostMessagingMessage(messageData, messageOrigin) {
            if (!checkIsTrustedDomain(messageData, messageOrigin)) {
                return false;
            }

            return processMtrMessage(messageData);
        }

        function getAppspaceDeviceType() {
            return 'MICROSOFTTEAMSROOMDEVICE';
        }

        function getRegistrationError() {
            return registrationErrorConfiguration;
        }

        function getDeviceHealthService() {
            if (!deviceHealthService) {
                deviceHealthService = $injector.get('deviceHealthService');
            }

            return deviceHealthService;
        }

        function postUnregistrationCallback() {
            processDeviceError({
                errorCode: HEALTH_STATUS.DEVICE_UNREGISTERED,
                reason: 'Device is unregistered.',
                isFatal: true,
            });
        }

        function getDeviceMute() {
            return isDeviceMuted;
        }

        function shouldMuteVideoOnLoad() {
            return isDeviceMuted;
        }

        function isUrlPolicyModeEnforced() {
            return true;
        }

        function processContentState(payload) {
            switch (payload.state) {
                case CONTENT_STATE_EVENT.ITEM_LOAD_ERROR:
                    processHealthMessage({
                        state: HEALTH_STATUS.UNHEALTHY,
                        errorCode: HEALTH_STATUS.ITEM_LOAD_ERROR,
                        reason: 'Error loading content. Content GUID: ' + payload.content.contentId,
                    });
                    break;
                case CONTENT_STATE_EVENT.NO_VALID_CONTENT:
                    processDeviceError({
                        errorCode: HEALTH_STATUS.NO_VALID_CONTENT,
                        reason: 'No valid content.',
                        skipErrorPageRedirection: true,
                    });
                    break;
                case CONTENT_STATE_EVENT.NO_CHANNEL_ASSIGNED:
                case CONTENT_STATE_EVENT.AUTOPLAY_NO_CONTENT:
                case CONTENT_STATE_EVENT.NO_CONTENT_AVAILABLE:
                    processDeviceError({
                        errorCode: HEALTH_STATUS.NO_CONTENT_AVAILABLE,
                        reason: 'No content available.',
                        skipErrorPageRedirection: true,
                    });
                    break;
                case CONTENT_STATE_EVENT.CHANNEL_LOADED:
                    processHealthMessage({
                        state: HEALTH_STATUS.HEALTHY
                    });
                    break;
            }
        }

        function processNetworkConnectionState(state) {
            // Process network disconnected state only after the MTR is loaded
            if (!isLoaded || state !== NETWORK_CONNECTION_STATE.DISCONNECTED) {
                return;
            }

            processDeviceError({
                errorCode: HEALTH_STATUS.SERVER_UNREACHABLE,
                reason: 'Network disconnected.',
                skipErrorPageRedirection: true,
                isRecoverable: true
            });
        }

        function onBroadcastMessage(message) {
            // Ignore messages for invalid or from the same frame
            if (!message || !message.data || !message.data.topic || message.data.origin === frameGuid) {
                return;
            }

            // Handle internal message
            var data = message.data;
            logService.debug(LOG_KEY, 'Received broadcast message: ' + JSON.stringify(data));
            switch (data.topic.toLowerCase()) {
                case WEB_BROADCAST_MESSAGES.MTR_PAIRING_REQUEST:
                    // 2: When secondary screen request
                    pairDefer.resolve(true);
                    cancelPairTimeout();
                    logService.info(LOG_KEY, 'Secondary screen pairing. Frame Guid:' + data.origin);
                    postWebBroadcastMessage({
                        topic: WEB_BROADCAST_MESSAGES.MTR_PAIRING_RESPONSE,
                        target: data.origin,
                    });
                    break;
                case WEB_BROADCAST_MESSAGES.MTR_PAIRING_RESPONSE:
                    // Receive response not for this frame
                    if (data.target !== frameGuid) {
                        return;
                    }

                    pairDefer.resolve(false);
                    cancelPairTimeout();
                    resetHeartbeat();
                    logService.info(LOG_KEY, 'Primary screen responded. Frame Guid:' + data.origin);
                    break;
                case WEB_BROADCAST_MESSAGES.MTR_HEARTBEAT_PING:
                    if (!isPrimary) {
                        return;
                    }

                    // Ping received, pong back
                    logService.info(LOG_KEY, 'Ping received from Guid:' + data.origin);
                    postWebBroadcastMessage({
                        topic: WEB_BROADCAST_MESSAGES.MTR_HEARTBEAT_PONG,
                        target: data.origin,
                    });
                    break;
                case WEB_BROADCAST_MESSAGES.MTR_HEARTBEAT_PONG:
                    // Receive pong not for this frame
                    if (data.target !== frameGuid) {
                        return;
                    }

                    logService.info(LOG_KEY, 'Pong received from Guid:' + data.origin);
                    pingDefer.resolve();
                    break;
                case WEB_BROADCAST_MESSAGES.MTR_CLAIM_REQUESTSTATUS:
                    if (!isPrimary) {
                        return;
                    }

                    var claimStatus = getCoreConfigurationService().get(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.DEVICE_CLAIM_STATUS);
                    logService.info(LOG_KEY, 'Retrieve claim status ' + claimStatus + ' for secondary screen: ' + data.origin);
                    postWebBroadcastMessage({
                        topic: WEB_BROADCAST_MESSAGES.MTR_CLAIM_RESPONSESTATUS,
                        target: data.origin,
                        payload: {
                            status: claimStatus,
                        },
                    });
                    break;
                case WEB_BROADCAST_MESSAGES.MTR_CLAIM_RESPONSESTATUS:
                    if (data.target !== frameGuid || !data.payload || !data.payload.status || !checkClaimDefer) {
                        return;
                    }

                    var status = data.payload.status;
                    logService.info(LOG_KEY, 'Receive claim status ' + status + ' for primary screen: ' + data.origin);
                    checkClaimDefer.resolve(data.payload.status);
                    break;
            }
        }

        function checkPrimaryFrame() {
            pairDefer = $q.defer();

            postWebBroadcastMessage({
                topic: WEB_BROADCAST_MESSAGES.MTR_PAIRING_REQUEST,
            });

            pairTimeout = $timeout(function() {
                // 1: When no primary responded and timeout
                pairDefer.resolve(true);
            }, BROADCAST_MESSAGE_TIMEOUT);

            return pairDefer.promise;
        }

        function onBroadcastMessageTimeout() {
            var defer = $q.defer();
            // Stop heartbeat
            stopHeartbeat();
            // Check Primary request
            checkPrimaryFrame().then(function(flag) {
                isPrimary = flag;
                defer.resolve();
            });

            return defer.promise;
        }

        function startHeartbeat() {
            heartbeatTimeout = $timeout(function() {
                sendPing().then(function() {
                    // Restart heartbeat
                    resetHeartbeat();
                }, onBroadcastMessageTimeout);
            }, HEARTBEAT_INTERVAL);
        }

        function sendPing() {
            pingDefer = $q.defer();
            postWebBroadcastMessage({
                topic: WEB_BROADCAST_MESSAGES.MTR_HEARTBEAT_PING,
            });

            $timeout(function() {
                pingDefer.reject();
            }, BROADCAST_MESSAGE_TIMEOUT);

            return pingDefer.promise;
        }

        function stopHeartbeat() {
            if (!heartbeatTimeout) {
                return;
            }

            $timeout.cancel(heartbeatTimeout);
            heartbeatTimeout = undefined;
        }

        function resetHeartbeat() {
            stopHeartbeat();
            startHeartbeat();
        }

        function cancelPairTimeout() {
            if (!pairTimeout) {
                return;
            }

            $timeout.cancel(pairTimeout);
            pairTimeout = undefined;
        }

        function postWebBroadcastMessage(message) {
            message = message || {};
            message.origin = frameGuid;
            message.timestamp = Date.now();
            logService.debug(LOG_KEY, 'Posting broadcast message: ' + JSON.stringify(message));
            webBroadcastService.postMessage(message);
        }

        // Returns the support level for a specific feature
        function getFeatureSupport(feature) {
            if (!feature) {
                return;
            }

            feature = feature.toLowerCase();
            switch (feature) {
                case FEATURE_CODE.STORAGE_MEMORY_CACHE_SUPPORT:
                    return !dataUtil.isValueBooleanFalse(isPrimary);
                case FEATURE_CODE.CONFERENCE_SUPPORTED:
                    return true;
                case FEATURE_CODE.LOW_BANDWIDTH_MODE_SUPPORTED:
                    return true;
                default:
                    return null;
            }
        }

        MtrDriver.prototype = {
            id: DRIVER_ID.MTR,
            isReady: isReady,
            media: {
                getDeviceMute: getDeviceMute,
                processContentState: processContentState,
            },
            platform: {
                initialize: initialize,
                getRegistrationError: getRegistrationError,
                getAppspaceDeviceType: getAppspaceDeviceType,
                processPostMessagingMessage: processPostMessagingMessage,
                postUnregistrationCallback: postUnregistrationCallback,
                shouldMuteVideoOnLoad: shouldMuteVideoOnLoad,
                isUrlPolicyModeEnforced: isUrlPolicyModeEnforced,
                isClaimDeviceMode: isClaimDeviceMode,
                getFeatureSupport: getFeatureSupport,
            },
            network: {
                processNetworkConnectionState: processNetworkConnectionState,
            },
        };

        return MtrDriver;
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');
    app.factory('NeatDriver', ["$q", "$window", "$injector", "SYSTEM_DATA", "UrlUtil", "dataUtil", "logService", function(
        $q,
        $window,
        $injector,
        SYSTEM_DATA,
        UrlUtil,
        dataUtil,
        logService
    ) {
        var httpWrapper;
        var NetworkUtil;
        var LOG_KEY = 'appspace.core.hal';
        var API_BASE_URL = 'http://127.0.0.1:8081/api/v1';
        var API_PATH = {
            DEVICE_INFO: '/deviceinfo',
            LED: '/leds',
            CAMERA: '/camera',
        };
        var CAMERA_FRAMING_MODE = {
            DEFAULT: 'mid', // Default to centred intermediate zoom
            ZOOM: 'zoom', // Maximum zoom, centred
            MID: 'mid', // Intermediate zoom, centred
            WIDE: 'wide', // No zoom
            OFF: 'off', // Disables automatic framing
            GROUP: 'group', // Focuses on a group of participants
            INDIVIDUAL: 'individual', // Focuses on individual participants
        };
        var DEFAULT_CAMERA_OPTIONS = {
            framing: CAMERA_FRAMING_MODE.DEFAULT,
        };
        var deviceInformation = {};
        var cameraOptions = $angular.copy(DEFAULT_CAMERA_OPTIONS);
        var DEVICE_INFORMATION_KEY = {
            SERIAL_NUMBER: 'serialNumber',
            FIRMWARE_VERSION: 'firmwareVersion',
            MODEL: 'model',
            MAC_ADDRESS: 'macAddress'
        };
        var initializeDefer = $q.defer();
        var isInitialized = false;
        var STATIC_MENU = {};
        var NEAT_DRIVER_ID = 'browser.neat';
        var MENU_INTERNAL_DEVICE_SETTINGS = NEAT_DRIVER_ID + '.internaldevicesettings';
        var ROOM_STATUS = {
            AVAILABLE: 'available',
            OCCUPIED: 'occupied',
            CHECK_IN: 'check-in',
            OFFLINE: 'offline'
        };
        var STATUS_LIGHT_MAPPING = {};
        STATUS_LIGHT_MAPPING[ROOM_STATUS.AVAILABLE] = {
            status: 'available',
            brightness: 100
        };
        STATUS_LIGHT_MAPPING[ROOM_STATUS.OCCUPIED] = {
            status: 'busy',
            brightness: 100
        };
        STATUS_LIGHT_MAPPING[ROOM_STATUS.CHECK_IN] = {
            status: 'custom',
            brightness: 100
        };
        STATUS_LIGHT_MAPPING[ROOM_STATUS.OFFLINE] = {
            status: 'offline',
            brightness: 0
        };

        function NeatDriver() {
            STATIC_MENU[MENU_INTERNAL_DEVICE_SETTINGS] = {
                text: 'Internal Device Settings',
                callback: openInternalDeviceSettings,
                closeMenu: true
            };
        }

        function getHttpWrapper() {
            if (!httpWrapper) {
                var HttpWrapper = $injector.get('HttpWrapper');
                httpWrapper = new HttpWrapper();
            }

            return httpWrapper;
        }

        function getAppspaceDeviceType() {
            return 'NEAT';
        }

        function getDeviceInformation(key) {
            return deviceInformation[key];
        }

        function getFirmware() {
            return $q.resolve(getDeviceInformation(DEVICE_INFORMATION_KEY.FIRMWARE_VERSION));
        }

        function getModel() {
            return deviceInformation[DEVICE_INFORMATION_KEY.MODEL];
        }

        function getSerialNumber() {
            return $q.resolve(getDeviceInformation(DEVICE_INFORMATION_KEY.SERIAL_NUMBER));
        }

        function getMacAddress() {
            var macAddresses = getDeviceInformation(DEVICE_INFORMATION_KEY.MAC_ADDRESS) || {};
            return $q.resolve(macAddresses.eth0 || macAddresses.wlan0);
        }

        function getFingerprint() {
            var deferred = $q.defer();
            getSerialNumber().then(function(serialNumber) {
                deferred.resolve({
                    fingerprint: serialNumber,
                    type: SYSTEM_DATA.FINGERPRINT_TYPE.HARDWARE
                });
            });
            return deferred.promise;
        }

        function initialize() {
            if (!isInitialized) {
                retrieveDeviceInformation();
            }

            return initializeDefer.promise;
        }

        function retrieveDeviceInformation() {
            isInitialized = true;
            var onFailure = function(error) {
                logService.error(LOG_KEY, 'Failed to retrieve device information: ' + error);
                initializeDefer.resolve();
            };

            getHttpWrapper().handlerGet(API_BASE_URL + API_PATH.DEVICE_INFO).then(function(response) {
                var data = (response || {}).data;
                if (!data) {
                    onFailure('No data returned from deviceinfo API');
                    return;
                }

                deviceInformation[DEVICE_INFORMATION_KEY.SERIAL_NUMBER] = data.serialNumber;
                deviceInformation[DEVICE_INFORMATION_KEY.FIRMWARE_VERSION] = data.firmwareVersion;
                deviceInformation[DEVICE_INFORMATION_KEY.MODEL] = data.model;
                deviceInformation[DEVICE_INFORMATION_KEY.MAC_ADDRESS] = data.macAddresses;
                initializeDefer.resolve();
            }, function(error) {
                onFailure(JSON.stringify(error));
            });
        }

        function executeNeatCordovaPluginMethod(method, args) {
            if (!$window.cordova || !$window.cordova.plugins || !$window.cordova.plugins.NeatCordovaPlugin) {
                return $q.reject('NeatCordovaPlugin not found');
            }

            var pluginMethod = $window.cordova.plugins.NeatCordovaPlugin[method];
            if (!pluginMethod) {
                return $q.reject('NeatCordovaPlugin method not found: ' + method);
            }

            var deferred = $q.defer();
            pluginMethod(deferred.resolve, deferred.reject, args);
            return deferred.promise;
        }

        function openInternalDeviceSettings() {
            return executeNeatCordovaPluginMethod('launchSystemSettingsApp');
        }

        function getDeviceMenu() {
            return STATIC_MENU;
        }

        function setLedLight(mapping) {
            var apiUrl = API_BASE_URL + API_PATH.LED;
            apiUrl += '?state=' + mapping.status;
            apiUrl += '&brightness=' + mapping.brightness;
            return getHttpWrapper().handlerPut(apiUrl);
        }

        function enableStatusLight(config) {
            if (!config || !config.status) {
                return $q.reject('Invalid color status');
            }

            var mapping = STATUS_LIGHT_MAPPING[config.status];
            return !mapping ?
                $q.reject('Unable to find status light mapping for: ' + config.status) :
                setLedLight(mapping);
        }

        function disableStatusLight() {
            return setLedLight(STATUS_LIGHT_MAPPING[ROOM_STATUS.OFFLINE]);
        }

        function getCameraFramingMode(mode) {
            var mappedMode = CAMERA_FRAMING_MODE[(mode || '').toUpperCase()];
            return mappedMode || CAMERA_FRAMING_MODE.DEFAULT;
        }

        function setCameraOptions(options) {
            if (dataUtil.isObject(options)) {
                cameraOptions.framing = getCameraFramingMode(options.framing);
            }

            var apiUrl = API_BASE_URL + API_PATH.CAMERA;
            logService.debug(LOG_KEY, 'Setting camera options with: ' + JSON.stringify(cameraOptions));
            return getHttpWrapper().handlerPut(UrlUtil.appendQueryString(apiUrl, cameraOptions));
        }

        function getNetworkUtil() {
            if (!NetworkUtil) {
                NetworkUtil = $injector.get('NetworkUtil');
            }

            return NetworkUtil;
        }

        function getIpAddress() {
            return getNetworkUtil().getCordovaIpAddress(LOG_KEY);
        }

        NeatDriver.prototype = {
            id: NEAT_DRIVER_ID,
            platform: {
                initialize: initialize,
                getAppspaceDeviceType: getAppspaceDeviceType,
                getAppPackageType: getAppspaceDeviceType,
                getFirmware: getFirmware,
                getModel: getModel,
                getSerialNumber: getSerialNumber,
                getFingerprint: getFingerprint,
                getDeviceMenu: getDeviceMenu,
                enableStatusLight: enableStatusLight,
                disableStatusLight: disableStatusLight,
            },
            media: {
                setCameraOptions: setCameraOptions,
            },
            network: {
                getMacAddress: getMacAddress,
                getIpAddress: getIpAddress,
            }
        };

        return (NeatDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('NmeDriver', ["$q", "$document", "RemoteEvent", "REMOTEVCODE", "REMOTEEVENTTYPE", "RemoteUtil", "WebRTC", "UrlUtil", function($q, $document, RemoteEvent, REMOTEVCODE, REMOTEEVENTTYPE, RemoteUtil, WebRTC, UrlUtil) {
        var remoteCallbackEvents = [];
        var isReadyPromise = $q.defer();
        var VIDEO_ELEMENT = 'video';
        var VIDEO_ATTRIBUTES = 'muted';

        function NmeDriver() {
            document.removeEventListener('deviceready', onDeviceReady);
            document.addEventListener('deviceready', onDeviceReady, false);
        }

        function isReady() {
            return isReadyPromise.promise;
        }

        function onDeviceReady() {
            $document.keydown(keyDownHandler);
            $document.keyup(keyUpHandler);
            isReadyPromise.resolve();
        }

        function createRemoteEvent(e) {
            var remoteEvent = new RemoteEvent(e);
            switch (e.which) {
                case 112: // Home button
                    remoteEvent.vcode = REMOTEVCODE.home;
                    break;
                case 113: // Burger button
                    remoteEvent.vcode = REMOTEVCODE.system;
                    break;
                case 114: // Back button
                    remoteEvent.vcode = REMOTEVCODE.back;
                    break;
                default:
                    remoteEvent = RemoteUtil.processDefaultKeyboardMapping(e);
                    break;
            }
            return remoteEvent;
        }

        function keyDownHandler(event) {
            processKeyboardEvent(event, REMOTEEVENTTYPE.buttonDown);
        }

        function keyUpHandler(event) {
            processKeyboardEvent(event, REMOTEEVENTTYPE.buttonUp);
        }

        function processKeyboardEvent(event, type) {
            var remoteEvent = createRemoteEvent(event);
            if (!remoteEvent) {
                return;
            }

            remoteEvent.eventType = type;
            // Notify remote driver service
            broadcastRemoteEvent(remoteEvent);

            // Prevent default if needed
            if (remoteEvent.isDefaultPrevented()) {
                event.preventDefault(); // prevent the default action (scroll / move caret)
            }
        }

        function registerRemoteEvents(callback) {
            if (getRemoteCallbackIndex(callback) !== -1) {
                return;
            }

            remoteCallbackEvents.push(callback);
        }

        function unRegisterRemoteEvents(callback) {
            var index = getRemoteCallbackIndex(callback);
            if (index == -1) {
                return;
            }

            remoteCallbackEvents.splice(index, 1);
        }

        function broadcastRemoteEvent(event) {
            for (var i = 0; i < remoteCallbackEvents.length; i++) {
                remoteCallbackEvents[i](event);
            }
        }

        function getRemoteCallbackIndex(targetCallback) {
            if (!targetCallback) {
                return -1;
            }

            for (var i = 0; i < remoteCallbackEvents.length; i++) {
                if (remoteCallbackEvents[i] == targetCallback) {
                    return i;
                }
            }

            return -1;
        }

        function getSerialNumber() {
            var cordovaDevice = device; // eslint-disable-line no-undef
            if (!cordovaDevice) {
                throw new Error('cordova device is not defined.');
            }

            return $q.resolve(cordovaDevice.serial);
        }

        function getFingerprint() {
            var defer = $q.defer();
            getSerialNumber().then(function(serialNo) {
                defer.resolve({fingerprint: serialNo});
            });
            return defer.promise;
        }

        function getFeatureSupport(feature) {
            if (!feature) {
                return null;
            }

            switch (feature.toLowerCase()) {
                case 'media.maxvideolayers':
                    return '99';
                case 'media.reusevideolayer':
                    return 'no';
                case 'file.localstorage':
                    return 'yes';
                case 'media.video.requirepauseonload': // override android's configuration
                    return false;
                default:
                    return null;
            }
        }

        // Get the IP Address for the device
        function getIpAddress() {
            var defer = $q.defer();
            var onGetIpAddressSuccess = function(ipAddress) {
                defer.resolve({address: ipAddress});
            };

            var onGetIpAddressFromWebRTCSuccess = function(result) {
                onGetIpAddressSuccess(result.address);
            };

            var onCarrierGetIpAddressFails = function() {
                getIpAddressFromWebRTC().then(onGetIpAddressFromWebRTCSuccess, defer.reject);
            };

            var onCordovaGetIpAddressFails = function() {
                getIpAddressCordova('getCarrierIPAddress').then(onGetIpAddressSuccess, onCarrierGetIpAddressFails);
            };

            getIpAddressCordova('getIPAddress').then(onGetIpAddressSuccess, onCordovaGetIpAddressFails);
            return defer.promise;
        }

        function getIpAddressFromWebRTC() {
            var defer = $q.defer();
            var webRTC = new WebRTC();
            webRTC.getIpAddress().then(defer.resolve, defer.reject);
            return defer.promise;
        }

        function getIpAddressCordova(method) {
            var networkInterfacePlugin = networkinterface;// eslint-disable-line no-undef
            if (!networkInterfacePlugin || !networkInterfacePlugin[method]) {
                return $q.reject();
            }

            var defer = $q.defer();
            networkInterfacePlugin[method](defer.resolve, defer.reject);
            return defer.promise;
        }

        function getMediaDirective(elementType, options) {
            var isVideo = elementType === VIDEO_ELEMENT;
            var res = {
                directive: elementType,
                attributes: '',
                options: {}
            };

            if (isVideo) {
                res.attributes = VIDEO_ATTRIBUTES;
                res.options.useVideoLoad = Boolean(options && UrlUtil.isUrlHttpOrHttps(options.source));
            }

            return res;
        }

        NmeDriver.prototype = {
            // Public interface
            id: 'browser.nme',
            isReady: isReady,
            platform: {
                getSerialNumber: getSerialNumber,
                getFingerprint: getFingerprint,
                getFeatureSupport: getFeatureSupport
            },
            remote: {
                registerEvents: registerRemoteEvents,
                unRegisterEvents: unRegisterRemoteEvents
            },
            network: {
                getIpAddress: getIpAddress
            },
            media: {
                getMediaDirective: getMediaDirective
            }
        };

        return (NmeDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var app = $angular.module('hal');
    app.factory('PreviewDriver', ["MemoryStorage", "UrlUtil", "dataUtil", "FEATURE_CODE", function(
        MemoryStorage,
        UrlUtil,
        dataUtil,
        FEATURE_CODE
    ) {
        function PreviewDriver() {
            var previewLocalStorage = new MemoryStorage();
            var previewSessionStorage = new MemoryStorage();
            this.id = 'preview';
            this.platform = {
                getLocalStorage: getLocalStorage,
                getSessionStorage: getSessionStorage,
                getFeatureSupport: getFeatureSupport
            };
            this.media = {
                getDeviceMute: getDeviceMute,
            };

            function getLocalStorage() {
                return previewLocalStorage;
            }

            function getSessionStorage() {
                return previewSessionStorage;
            }

            function getFeatureSupport(feature) {
                if (!feature) {
                    return null;
                }

                feature = feature.toLowerCase();
                switch (feature) {
                    case FEATURE_CODE.NETWORK_TRANSPARENT_CACHE:
                        return false;
                    case 'support.device.menu':
                        return 'no';
                    case FEATURE_CODE.UNREGISTER_SUPPORTED:
                        return 'no';
                    case 'services.disable.noncritical':
                        return 'yes';
                    case 'bypass.check.appspace.online':
                        return true;
                    case 'bypass.send.insync':
                        return true;
                    case 'bypass.check.app.version':
                        return true;
                    case 'services.disable.sync':
                        return true;
                    case 'ignore.sync.schedule':
                        return true;
                    case 'file.localstorage':
                        return 'no';
                    case 'support.lowbandwidth.mode':
                        return false;
                    default:
                        return null;
                }
            }

            function getDeviceMute() {
                return dataUtil.parseBoolean(UrlUtil.retrieveParams().mute);
            }
        }

        return (PreviewDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('SureVueElectronDriver', ["$q", "$timeout", "$injector", "DEVICE_TYPE", "SYSTEM_DATA", "UrlUtil", "dataUtil", "logService", function(
        $q,
        $timeout,
        $injector,
        DEVICE_TYPE,
        SYSTEM_DATA,
        UrlUtil,
        dataUtil,
        logService
    ) {
        function SureVueElectronDriver() {
            this.id = 'browser.mediavue.electron';
            this.platform = {
                getAppspaceDeviceType: getAppspaceDeviceType,
                reboot: reboot,
                getCpuUsage: getCpuUsage,
                getMemoryUsage: getMemoryUsage,
                getAvailableStorage: getAvailableStorage,
                getDisplayInfo: getDisplayInfo,
                getFirmware: getFirmware,
                getSerialNumber: getSerialNumber,
                getFingerprint: getFingerprint,
            };

            this.media = {
                processMediaURL: processMediaURL,
                getSupportedVideoProtocols: getSupportedVideoProtocols
            };

            var SUPPORTED_VIDEO_PROTOCOLS = ['http', 'https', 'hls', 'multicast'];
            var DEVICE_API_URL = {
                UDP_HELPER: 'http://localhost:7070/udphelper/',
                DEVICE: 'http://localhost/api/v1/',
            };
            var UDP_PROTOCOL_REGEX = /udp:\/\//i;
            var HLS_OUTPUT_POSTFIX = '/out.m3u8';
            var DEVICE_API = {
                REBOOT: 'systemreset',
                GET_CPU_USAGE: 'getcpuusage',
                GET_TOTAL_RAM: 'gettotalram',
                GET_FREE_RAM: 'getfreeram',
                GET_TOTAL_DISK_SPACE: 'gettotaldiskspace',
                GET_FREE_DISK_SPACE: 'getfreediskspace',
                GET_DISPLAY_SETTINGS: 'getdisplaysettings',
                GET_GENERAL_SETTINGS: 'getgeneralsettings',
                GET_SERIAL_NUMBER: 'getserialnumber',
            };
            var MAX_REBOOT_RETRY = 5;
            var LOG_KEY = 'appspace.core.hal';
            var retryRebootPromise;
            var rebootFailCount = 0;
            var httpWrapper;

            function processMediaURL(url) {
                var processedUrl = {url: url};
                if (!url.match(UDP_PROTOCOL_REGEX)) {
                    return processedUrl;
                }

                // Remove UDP protocol
                var udpUrl = url.replace(UDP_PROTOCOL_REGEX, '');
                // Remove query parameters
                udpUrl = UrlUtil.getBaseUrl(udpUrl);
                // Remove trailing slash
                udpUrl = UrlUtil.removeTrailingSlash(udpUrl);
                processedUrl.url = DEVICE_API_URL.UDP_HELPER + udpUrl + HLS_OUTPUT_POSTFIX;
                return processedUrl;
            }

            function getSupportedVideoProtocols() {
                return $q.resolve(SUPPORTED_VIDEO_PROTOCOLS);
            }

            function getAppspaceDeviceType() {
                return DEVICE_TYPE.SUREVUE;
            }

            function log(level, message) {
                logService.isReady().then(function() {
                    logService[level](LOG_KEY, 'SureVue: ' + message);
                });
            }

            function getHttpWrapper() {
                if (!httpWrapper) {
                    var HttpWrapper = $injector.get('HttpWrapper');
                    httpWrapper = new HttpWrapper();
                }

                return httpWrapper;
            }

            function executeDeviceApi(apiName) {
                var deferred = $q.defer();
                var httpWrapper = getHttpWrapper();
                var payload = {
                    id: 0,
                    name: apiName,
                    data: ''
                };
                var onError = function(error) {
                    if (!dataUtil.isString(error)) {
                        error = JSON.stringify(error);
                    }

                    deferred.reject(error);
                };

                httpWrapper.apiPost(DEVICE_API_URL.DEVICE, payload).then(function(response) {
                    log('debug', '[' + apiName + ']' + ' API call success with returned response: ' + JSON.stringify(response));
                    if (!response || !response.data || response.data.message !== 'success') {
                        onError(response);
                        return;
                    }

                    deferred.resolve(response);
                }, onError);
                return deferred.promise;
            }

            function reboot() {
                cancelRetryRebootTimeout();
                log('debug', 'System reboot');
                var onError = function(error) {
                    log('warn', 'Reboot failed [' + error + ']');
                    rebootFailCount++;
                    if (rebootFailCount >= MAX_REBOOT_RETRY) {
                        log('warn', 'Exceeded maximum number of reboot retries (' + rebootFailCount + ' times)');
                        return;
                    }

                    retryReboot();
                };

                executeDeviceApi(DEVICE_API.REBOOT).then(function() {
                    rebootFailCount = 0;
                    log('debug', 'Reboot success');
                }, onError);
            }

            function retryReboot() {
                cancelRetryRebootTimeout();
                log('debug', 'Retrying reboot in 1 second...');
                retryRebootPromise = $timeout(reboot, 1000);
            }

            function cancelRetryRebootTimeout() {
                if (!retryRebootPromise) {
                    return;
                }

                $timeout.cancel(retryRebootPromise);
                retryRebootPromise = undefined;
            }

            function getCpuUsage() {
                var deferred = $q.defer();
                var onError = function(error) {
                    log('debug', 'getCpuUsage failed [' + error + ']');
                    deferred.resolve(0);
                };

                executeDeviceApi(DEVICE_API.GET_CPU_USAGE).then(function(result) {
                    var cpuUsage = parseFloat(result.data.data);
                    if (!dataUtil.isNumber(cpuUsage)) {
                        onError('Unable to retrieve CPU usage');
                        return;
                    }

                    log('debug', 'getCpuUsage succeeded [' + cpuUsage + ']');
                    deferred.resolve({
                        cpuUsage: cpuUsage,
                    });
                }, onError);
                return deferred.promise;
            }

            function toBytes(megabyte) {
                return megabyte * 1000000;
            }

            function getMemoryUsage() {
                var deferred = $q.defer();
                var onError = function(error) {
                    log('debug', 'getMemoryUsage failed [' + error + ']');
                    deferred.resolve(0);
                };

                // Memory returned by SureVue is in MB
                // Use decimal conversion to bytes as device menu calculates by bytes
                getTotalRAM().then(function(total) {
                    total = toBytes(total);
                    getFreeRAM().then(function(free) {
                        free = toBytes(free);
                        deferred.resolve({
                            capacity: total,
                            availableCapacity: total - free
                        });
                    }, onError);
                }, onError);
                return deferred.promise;
            }

            function getTotalRAM() {
                var deferred = $q.defer();
                var onError = function(error) {
                    var errorMessage = 'getTotalRAM failed [' + error + ']';
                    log('debug', errorMessage);
                    deferred.reject(errorMessage);
                };

                executeDeviceApi(DEVICE_API.GET_TOTAL_RAM).then(function(result) {
                    var totalRAM = parseInt(result.data.data, 10);
                    if (!dataUtil.isNumber(totalRAM)) {
                        onError('Unable to retrieve total RAM');
                        return;
                    }

                    deferred.resolve(totalRAM);
                }, onError);
                return deferred.promise;
            }

            function getFreeRAM() {
                var deferred = $q.defer();
                var onError = function(error) {
                    var errorMessage = 'getFreeRAM failed [' + error + ']';
                    log('debug', errorMessage);
                    deferred.reject(errorMessage);
                };

                executeDeviceApi(DEVICE_API.GET_FREE_RAM).then(function(result) {
                    var freeRAM = parseInt(result.data.data, 10);
                    if (!dataUtil.isNumber(freeRAM)) {
                        onError('Unable to retrieve free RAM');
                        return;
                    }

                    deferred.resolve(freeRAM);
                }, onError);
                return deferred.promise;
            }

            function getAvailableStorage() {
                var deferred = $q.defer();
                var onError = function(error) {
                    log('debug', 'getAvailableStorage failed [' + error + ']');
                    deferred.resolve(0);
                };

                getTotalDiskSpace().then(function(total) {
                    total = toBytes(total);
                    getFreeDiskSpace().then(function(free) {
                        free = toBytes(free);
                        deferred.resolve({
                            total: total,
                            used: total - free
                        });
                    }, onError);
                }, onError);
                return deferred.promise;
            }

            function getTotalDiskSpace() {
                var deferred = $q.defer();
                var onError = function(error) {
                    var errorMessage = 'getTotalDiskSpace failed [' + error + ']';
                    log('debug', errorMessage);
                    deferred.reject(errorMessage);
                };

                executeDeviceApi(DEVICE_API.GET_TOTAL_DISK_SPACE).then(function(result) {
                    var totalDiskSpace = parseInt(result.data.data, 10);
                    if (!dataUtil.isNumber(totalDiskSpace)) {
                        onError('Unable to retrieve total disk space');
                        return;
                    }

                    deferred.resolve(totalDiskSpace);
                }, onError);
                return deferred.promise;
            }

            function getFreeDiskSpace() {
                var deferred = $q.defer();
                var onError = function(error) {
                    var errorMessage = 'getFreeDiskSpace failed [' + error + ']';
                    log('debug', errorMessage);
                    deferred.reject(errorMessage);
                };

                executeDeviceApi(DEVICE_API.GET_FREE_DISK_SPACE).then(function(result) {
                    var freeDiskSpace = parseInt(result.data.data, 10);
                    if (!dataUtil.isNumber(freeDiskSpace)) {
                        onError('Unable to retrieve free disk space');
                        return;
                    }

                    deferred.resolve(freeDiskSpace);
                }, onError);
                return deferred.promise;
            }

            function getDisplayInfo() {
                var deferred = $q.defer();
                var displayInfo = [];
                var onError = function(error) {
                    log('debug', 'getDisplayInfo failed [' + error + ']');
                    deferred.resolve(displayInfo);
                };

                executeDeviceApi(DEVICE_API.GET_DISPLAY_SETTINGS).then(function(result) {
                    var data = dataUtil.tryParseJSON(result.data.data);
                    if (!data || !data.DisplayPorts) {
                        onError('Invalid response');
                        return;
                    }

                    var displays = data.DisplayPorts;
                    for (var i = 0; i < displays.length; i++) {
                        var display = displays[i];
                        if (!display.Connected || !display.CurrentSettings || !display.CurrentSettings.Resolution) {
                            continue;
                        }

                        var resolutions = display.CurrentSettings.Resolution.toLowerCase().split('x');
                        if (resolutions.length < 2) {
                            continue;
                        }

                        displayInfo.push({
                            x: resolutions[0],
                            y: resolutions[1]
                        });
                    }

                    deferred.resolve(displayInfo);
                }, onError);
                return deferred.promise;
            }

            function getFirmware() {
                var deferred = $q.defer();
                executeDeviceApi(DEVICE_API.GET_GENERAL_SETTINGS).then(function(result) {
                    var data = dataUtil.tryParseJSON(result.data.data);
                    if (!data || !data.MvosVersion) {
                        deferred.resolve('-');
                        log('debug', 'MvosVersion not found');
                        return;
                    }

                    deferred.resolve(data.MvosVersion);
                });
                return deferred.promise;
            }

            function getSerialNumber() {
                var deferred = $q.defer();
                var onFailed = function(error) {
                    log('debug', error);
                    deferred.reject(error);
                };

                executeDeviceApi(DEVICE_API.GET_SERIAL_NUMBER).then(function(result) {
                    var data = result.data.data;
                    if (!data) {
                        onFailed('Serial number not found');
                        return;
                    }

                    deferred.resolve(data);
                }, onFailed);
                return deferred.promise;
            }

            function getFingerprint() {
                var deferred = $q.defer();
                getSerialNumber().then(function(serialNumber) {
                    deferred.resolve({
                        fingerprint: serialNumber,
                        type: SYSTEM_DATA.FINGERPRINT_TYPE.HARDWARE
                    });
                }, function(error) {
                    var errorMessage = 'Error retrieving serial number for fingerprint due to: ' + error;
                    log('error', errorMessage);
                    deferred.reject(errorMessage);
                });
                return deferred.promise;
            }
        }

        // Return the constructor for the class
        return (SureVueElectronDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('WindowsDriver', ["$q", "SYSTEM_DATA", "IPC_COMMAND", "FEATURE_CODE", "logService", "driverUtil", "ipcRendererUtil", "UrlUtil", function(
        $q,
        SYSTEM_DATA,
        IPC_COMMAND,
        FEATURE_CODE,
        logService,
        driverUtil,
        ipcRendererUtil,
        UrlUtil) {
        function WindowsDriver() {
            var LOG_KEY = 'appspace.core.hal';

            this.id = 'browser.windows';

            this.platform = {
                getAppspaceDeviceType: getAppspaceDeviceType,
                getAppPackageType: getAppspaceDeviceType,
                getFingerprint: getFingerprint,
                getFeatureSupport: getFeatureSupport
            };

            this.media = {
                resolveContentPath: function(path) {
                    if (!path || UrlUtil.isUrlHttpOrHttps(path)) {
                        return $q.resolve(path);
                    }

                    var promise = $q.defer();
                    ipcRendererUtil.invoke(IPC_COMMAND.RESOLVE_CONTENT_PATH, path).then(promise.resolve, function() {
                        promise.resolve(path);
                    });
                    return promise.promise;
                }
            };

            function getAppspaceDeviceType() {
                return 'WINDOWS';
            }

            function getFingerprint() {
                var promise = $q.defer();

                function fallbackToGeneratedFingerprint(reason) {
                    logService.warn(LOG_KEY, reason);
                    driverUtil.getFingerprint().then(function(fingerprint) {
                        promise.resolve(fingerprint);
                    }, function(error) {
                        var errorMessage = 'Failed to generate fingerprint: ' + JSON.stringify(error);
                        logService.error(LOG_KEY, errorMessage);
                        promise.reject(errorMessage);
                    });
                }

                driverUtil.getSerialNumber().then(function(serialNumber) {
                    if (driverUtil.isFingerprintFormatValid(serialNumber)) {
                        promise.resolve({
                            fingerprint: serialNumber,
                            type: SYSTEM_DATA.FINGERPRINT_TYPE.HARDWARE,
                        });
                        return;
                    }

                    fallbackToGeneratedFingerprint('Serial number retrieved is of unsupported fingerprint format. Defaulting to generated fingerprint...');
                }, function(error) {
                    fallbackToGeneratedFingerprint('Serial number retrieval failed due to: ' + JSON.stringify(error) + '. Falling back to generated fingerprint...');
                });

                return promise.promise;
            }

            function getFeatureSupport(feature) {
                if (!feature) {
                    return null;
                }

                switch (feature.toLowerCase()) {
                    case FEATURE_CODE.CARD_TIMEOUT_DURATION:
                        return 60000;
                    case FEATURE_CODE.MAX_WEB_VIEW_COLLECTION:
                        return 0;
                }

                return null;
            }
        }

        // Return the constructor for the class
        return (WindowsDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('ZoomRoomsDriver', ["$q", "$injector", "$http", "$window", "logService", "ENVIRONMENT_MAPPING", "DMIS_URIS", "DRIVER_ID", "REGISTRATION_MODE", "STORAGE_COLLECTION_KEY", "FEATURE_CODE", "STORAGE_KEY", "platformUtil", function(
        $q,
        $injector,
        $http,
        $window,
        logService,
        ENVIRONMENT_MAPPING,
        DMIS_URIS,
        DRIVER_ID,
        REGISTRATION_MODE,
        STORAGE_COLLECTION_KEY,
        FEATURE_CODE,
        STORAGE_KEY,
        platformUtil) {
        var baseDriver;
        var isReadyPromise;
        var LOG_KEY = 'appspace.core.hal';
        var ZOOM_SDK_VERSION = '0.16';
        var ZOOM_SDK_CAPABILITIES = ['getZoomRoomContext'];
        var ZOOM_SDK_SCRIPT_URL = 'https://appssdk.zoom.us/sdk.js';
        var HTTP_HEADERS = {
            CONTENT_TYPE: 'Content-Type',
            ACCEPT: 'Accept',
            APPLICATION_JSON: 'application/json'
        };
        var API_ENDPOINTS = {
            DEVICES: '/api/v1/providers/zoom/integrations/devices'
        };
        var DEVICE_TYPE = 'ZOOMROOMS';
        var sessionService;
        var deviceStateService;
        var coreConfigurationService;
        var claimDeviceUtil;
        function ZoomRoomsDriver(options) {
            baseDriver = options.baseDriver;
        }

        function isReady() {
            if (!baseDriver) {
                return $q.resolve();
            }

            if (isReadyPromise) {
                return isReadyPromise.promise;
            }

            isReadyPromise = $q.defer();
            baseDriver.isReady().then(function() {
                platformUtil.loadScriptFile(ZOOM_SDK_SCRIPT_URL).then(onZoomSdkScriptLoaded);
            });
            return isReadyPromise.promise;
        }

        function onZoomSdkScriptLoaded() {
            zoomSdk.config({version: ZOOM_SDK_VERSION, capabilities: ZOOM_SDK_CAPABILITIES}).then(isReadyPromise.resolve, function() {
                logService.error(LOG_KEY, 'Failed to load Zoom SDK');
                // for GA, need to implement retry
                throw new Error('Failed to load Zoom SDK');
            });
        }

        function claimDevice() {
            var isDeviceRegistered = getDeviceStateService().isRegistered();
            if (isDeviceRegistered) {
                return $q.resolve();
            }

            var urlParameters = getSessionService().getUrlParameters();
            var referenceAccountId = urlParameters.referenceaccountid;
            var defer = $q.defer();
            getDeviceReferenceInformation().then(function(referenceInformation) {
                retrieveDeviceInformationFromDMIS(referenceAccountId, referenceInformation.deviceId, referenceInformation.roomId).then(function(response) {
                // response: deviceId, deviceName, type, appspaceApiUrl, deviceRegistrationToken
                    saveServiceApiUrl(response.appspaceApiUrl);
                    claimDeviceFromInstance(response.deviceId, response.deviceRegistrationToken).then(defer.resolve, defer.reject);
                }, function() {
                    defer.reject({message: 'Failed to retrieve device information from DMIS'});
                });
            }, function(error) {
                defer.reject({error: error, message: 'Failed to get device reference information'});
            });
            return defer.promise;
        }

        function claimDeviceFromInstance(deviceGuid, registrationToken) {
            var configuration = {
                deviceGuid: deviceGuid,
                registrationToken: registrationToken
            };

            return getClaimDeviceUtil().claimDevice(configuration);
        }

        function getDmisBaseUrl() {
            var host = $window.location.hostname;
            var environment = ENVIRONMENT_MAPPING[host];
            return DMIS_URIS[environment] || DMIS_URIS.PRODUCTION;
        }

        function retrieveDeviceInformationFromDMIS(referenceAccountId, deviceReferenceId, roomId) {
            var defer = $q.defer();
            var url = getDmisBaseUrl() + API_ENDPOINTS.DEVICES;
            var body = {
                referenceAccountId: referenceAccountId,
                referenceDeviceId: deviceReferenceId,
                roomId: roomId
            };

            $http.post(url, body, {
                headers: {
                    [HTTP_HEADERS.CONTENT_TYPE]: HTTP_HEADERS.APPLICATION_JSON,
                    [HTTP_HEADERS.ACCEPT]: HTTP_HEADERS.APPLICATION_JSON
                }
            }).then(function(response) {
                if (!response || !response.data) {
                    defer.reject('Invalid response from DMIS');
                    return;
                }

                defer.resolve(response.data);
            }, defer.reject);
            return defer.promise;
        }

        function saveServiceApiUrl(value) {
            getCoreConfigurationService().set(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.SERVICE_API_URL, value);
        }

        function getDeviceReferenceInformation() {
            var defer = $q.defer();
            try {
                zoomSdk.getZoomRoomContext().then(function(context) {
                    defer.resolve({deviceId: context.deviceId, roomId: context.roomId});
                }, function(error) {
                    defer.reject({error: error, message: 'Failed to get Zoom Room Context'});
                });
            } catch (error) {
                defer.reject({error: error, message: 'Caught error when getting Zoom Room Context'});
            }

            return defer.promise;
        }

        function getAppspaceDeviceType() {
            return DEVICE_TYPE;
        }

        function getAvailableStorage() {
            return $q.resolve({
                used: '',
                total: ''
            });
        }

        function getSessionService() {
            if (!sessionService) {
                sessionService = $injector.get('sessionService');
            }

            return sessionService;
        }

        function getDeviceStateService() {
            if (!deviceStateService) {
                deviceStateService = $injector.get('deviceStateService');
            }

            return deviceStateService;
        }

        function getCoreConfigurationService() {
            if (!coreConfigurationService) {
                coreConfigurationService = $injector.get('coreConfigurationService');
            }

            return coreConfigurationService;
        }

        function getClaimDeviceUtil() {
            if (!claimDeviceUtil) {
                claimDeviceUtil = $injector.get('claimDeviceUtil');
            }

            return claimDeviceUtil;
        }

        function isClaimDeviceMode() {
            var urlParameters = getSessionService().getUrlParameters();
            return Boolean(urlParameters && urlParameters.registrationmode && urlParameters.registrationmode.toLowerCase() === REGISTRATION_MODE.CLAIM_DEVICE.toLowerCase());
        }

        function getFeatureSupport(feature) {
            if (!feature) {
                return null;
            }

            feature = feature.toLowerCase();
            switch (feature) {
                case FEATURE_CODE.CONFERENCE_SUPPORTED:
                    return true;
                case FEATURE_CODE.LOW_BANDWIDTH_MODE_SUPPORTED:
                    return true;
                default:
                    return null;
            }
        }

        ZoomRoomsDriver.prototype = {
            id: DRIVER_ID.ZOOM_ROOMS,
            isReady: isReady,
            platform: {
                isClaimDeviceMode: isClaimDeviceMode,
                claimDevice: claimDevice,
                getFeatureSupport: getFeatureSupport,
                getAppspaceDeviceType: getAppspaceDeviceType,
                getAvailableStorage: getAvailableStorage
            }
        };

        return ZoomRoomsDriver;
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('AndroidDriver', ["$window", "$q", "$document", "MEDIA", "MEDIA_DIRECTIVE", "FEATURE_CODE", "RemoteEvent", "REMOTEVCODE", "REMOTEEVENTTYPE", "RemoteUtil", "VersionCompare", "dataUtil", "WebRTC", "logService", function(
        $window,
        $q,
        $document,
        MEDIA,
        MEDIA_DIRECTIVE,
        FEATURE_CODE,
        RemoteEvent,
        REMOTEVCODE,
        REMOTEEVENTTYPE,
        RemoteUtil,
        VersionCompare,
        dataUtil,
        WebRTC,
        logService
    ) {
        var isReadyCallback;
        var isReadyPromise = $q.defer();
        var _remoteCallbackEvents = [];
        var LOG_KEY = 'appspace.core.hal';
        var VIDEO_ELEMENT = 'video';
        var VIDEO_ATTRIBUTES = 'muted poster="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="'; // 1px transparent png
        var IN_APP_BROWSER_ID_PREFIX = 'android_inappbrowser_';
        var EMPTY_STRING = '';
        var inAppBrowsers = [];
        var DEFAULT_IP_ADDRESS = '127.0.0.1';

        // Define the constructor function.
        function AndroidDriver(onDeviceReadyCallback) {
            this.remoteCallbackEvents = _remoteCallbackEvents;
            isReadyCallback = onDeviceReadyCallback;
            document.removeEventListener('deviceready', onDeviceReady);
            document.addEventListener('deviceready', onDeviceReady, false);
        }

        function onDeviceReady() {
            document.removeEventListener('backbutton', onBackKeyDown);
            document.addEventListener('backbutton', onBackKeyDown, false);
            $document.keydown(keydownHandler);
            $document.keyup(keyupHandler);
            if (isReadyCallback) {
                isReadyCallback($window.device);
            }

            isReadyPromise.resolve();
        }

        // Resolves once cordova is ready
        function isReady() {
            return isReadyPromise.promise;
        }

        function getAppspaceDeviceType() {
            return 'ANDROID';
        }

        function createRemoteEvent(e) {
            var re = RemoteUtil.processDefaultKeyboardMapping(e);
            return re;
        }

        function keydownHandler(e) {
            var re = createRemoteEvent(e);
            if (re == null) {
                return;
            }

            re.eventType = REMOTEEVENTTYPE.buttonDown;
            // Notifice remote driver service
            broadcastRemoteEvent(re);

            // Prevent default if needed
            if (re.isDefaultPrevented()) {
                e.preventDefault(); // prevent the default action (scroll / move caret)
            }
        }

        function keyupHandler(e) {
            var re = createRemoteEvent(e);
            if (re == null) {
                return;
            }

            re.eventType = REMOTEEVENTTYPE.buttonUp;
            // Notifice remote driver service
            broadcastRemoteEvent(re);

            // Prevent default if needed
            if (re.isDefaultPrevented()) {
                e.preventDefault(); // prevent the default action (scroll / move caret)
            }
        }

        function registerRemoteEvents(callback) {
            if (callback) {
                _remoteCallbackEvents.push(callback);
            }
        }

        function unRegisterRemoteEvents(callback) {
            if (callback) {
                for (var i = 0; i < _remoteCallbackEvents.length; i++) {
                    var cb = _remoteCallbackEvents[i];
                    if (cb == callback) {
                        _remoteCallbackEvents.splice(i, 1);
                    }
                }
            }
        }

        function onBackKeyDown() {
            // Handle the back button
            var re = new RemoteEvent();
            re.vcode = REMOTEVCODE.back;
            re.eventType = REMOTEEVENTTYPE.buttonDown;
            broadcastRemoteEvent(re, true);
        }

        function broadcastRemoteEvent(event, ignoreLastEventTime) {
            for (var i = 0; i < _remoteCallbackEvents.length; i++) {
                var cb = _remoteCallbackEvents[i];
                cb(event, ignoreLastEventTime);
            }
        }

        function getFeatureSupport(feature) {
            var featureToCheck = feature.toLowerCase();
            switch (featureToCheck) {
                case 'media.maxvideolayers':
                    return isMultiElementSupported() ? null : '1';
                case 'browser.pixel.fraction.round.type':
                    return 'up';
                case 'sleep.full.black.overlay':
                    return 'yes';
                case 'support.video.onload.event':
                    return 'no';
                case 'statusbar.showonresize':
                    return true;
                case 'media.video.requirepauseonload':
                    return true;
                case FEATURE_CODE.WEB_WORKER_SUPPORTED:
                    return false;
                case FEATURE_CODE.VIDEO_SYNCHRONIZED_SEEKING_SUPPORTED:
                    return false;
                case FEATURE_CODE.CARD_TIMEOUT_DURATION:
                    return 60000;
                default:
                    return null;
            }
        }

        function isMultiElementSupported() {
            var firmware = device.version; //eslint-disable-line
            // minumum version that supports multiple video elements pointing to the same source
            var minFirmwareForMultiElement = '5.0.0';
            var vc = new VersionCompare();
            return vc.compare(firmware, minFirmwareForMultiElement);
        }

        var isStatusBarExists = function() {
            return $window && $window.StatusBar;
        };

        function hideStatusBar() {
            var res = true;
            if ($window.AndroidFullScreen && $window.AndroidFullScreen.immersiveMode) {
                $window.AndroidFullScreen.immersiveMode();
            } else if (isStatusBarExists() && $window.StatusBar.hide) {
                $window.StatusBar.hide();
            } else {
                res = false;
            }

            return res;
        }

        function showStatusBar() {
            if (!isStatusBarExists()) {
                return;
            }

            $window.StatusBar.show();
            $window.StatusBar.overlaysWebView(false);
        }

        function getMediaDirective(elementType, options) {
            var result = {
                directive: elementType,
                attributes: EMPTY_STRING
            };
            options = options || {};
            if (elementType === MEDIA_DIRECTIVE.WEB && options.webEngine === MEDIA.WEB_ENGINE_IN_APP_BROWSER) {
                result.directive = MEDIA_DIRECTIVE.IN_APP_BROWSER;
            } else if (elementType === VIDEO_ELEMENT) {
                result.attributes = VIDEO_ATTRIBUTES;
            }

            return result;
        }

        function createBrowser(url, onLoadStop, onLoadError) {
            var inAppBrowser = window.cordova.InAppBrowser.open(url, '_blank', 'location=no,toolbarposition=top');
            inAppBrowser.id = IN_APP_BROWSER_ID_PREFIX + Date.now();
            inAppBrowser.addEventListener('loadstop', onLoadStop);
            inAppBrowser.addEventListener('loadError', onLoadError);
            inAppBrowser.dispose = disposeInAppBrowser.bind(null, onLoadStop, onLoadError, inAppBrowsers, inAppBrowser);
            inAppBrowsers.push(inAppBrowser);
            return inAppBrowser;
        }

        function disposeInAppBrowser(loadStopCallback, loadErrorCallback, browserCollection, inAppBrowser) {
            var unusedInAppBrowser;
            for (var index = 0; index < browserCollection.length; index++) {
                if (inAppBrowser.id !== browserCollection[index].id) {
                    continue;
                }

                unusedInAppBrowser = browserCollection.splice(index, 1)[0];
                break;
            }

            if (inAppBrowsers.length > 0) {
                return;
            }

            unusedInAppBrowser.removeEventListener('loadstop', loadStopCallback);
            unusedInAppBrowser.removeEventListener('loadError', loadErrorCallback);
            unusedInAppBrowser.close();
        }

        function getAvailableStorage() {
            if (!$window.DiskSpacePlugin) {
                logService.debug(LOG_KEY, 'Storage plugin is not available. Using cordova system storage.');
                // move to the next driver implementation
                return undefined;
            }

            var defer = $q.defer();
            $window.DiskSpacePlugin.info({location: 2}, function(info) {
                if (dataUtil.isNullOrUndefined(info) || dataUtil.isNullOrUndefined(info.app) || dataUtil.isNullOrUndefined(info.total) || dataUtil.isNullOrUndefined(info.free)) {
                    logService.error(LOG_KEY, 'Error while retrieving storage space');
                    defer.resolve(EMPTY_STRING);
                    return;
                }

                defer.resolve({
                    used: info.total - info.free,
                    total: info.total
                });
            }, function(error) {
                logService.error(LOG_KEY, 'Error while retrieving storage space: ' + JSON.stringify(error));
                defer.resolve(EMPTY_STRING);
            });

            return defer.promise;
        }

        function getRuntimeEnvironments() {
            return ['android'];
        }

        function getIpAddress() {
            var defer = $q.defer();
            networkinterface.getIPAddress(function(address) {
                if (!address) {
                    logService.error(LOG_KEY, 'Invalid IP Address: ' + JSON.stringify(address));
                    defer.resolve({
                        originalAddress: address,
                        address: DEFAULT_IP_ADDRESS
                    });
                    return;
                }

                logService.debug(LOG_KEY, 'Cordova IP Address: ' + address.ip);
                defer.resolve({
                    address: address.ip
                });
            }, function() {
                getIpAddressFromWebRTC().then(defer.resolve, defer.reject);
            });

            return defer.promise;
        }

        function getIpAddressFromWebRTC() {
            var defer = $q.defer();

            function onFailed(error) {
                logService.error(LOG_KEY, 'Failed to get IP address from WebRTC: ' + JSON.stringify(error));
                defer.reject(error);
            }

            if (!WebRTC) {
                onFailed('WebRTC not supported');
                return defer.promise;
            }

            var webRTC = new WebRTC();
            webRTC.getIpAddress().then(
                function(ipAddress) {
                    logService.debug(LOG_KEY, 'WebRTC IP Address: ' + JSON.stringify(ipAddress));
                    defer.resolve(ipAddress);
                },
                function(error) {
                    onFailed(error);
                }
            );

            return defer.promise;
        }

        // Define the instance methods and properties
        AndroidDriver.prototype = {
            // Public interface
            id: 'browser.android',
            isReady: isReady,
            platform: {
                getAppspaceDeviceType: getAppspaceDeviceType,
                getAppPackageType: getAppspaceDeviceType,
                getFeatureSupport: getFeatureSupport,
                getRuntimeEnvironments: getRuntimeEnvironments,
                hideStatusBar: hideStatusBar,
                showStatusBar: showStatusBar,
                getAvailableStorage: getAvailableStorage
            },
            remote: {
                registerEvents: registerRemoteEvents,
                unRegisterEvents: unRegisterRemoteEvents,
            },
            media: {
                getMediaDirective: getMediaDirective,
                createBrowser: createBrowser
            },
            network: {
                getIpAddress: getIpAddress
            }
        };

        // Return the constructor for the class
        return (AndroidDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('AndroidTvDriver', ["$window", "$q", "ORIENTATION", "FEATURE_CODE", "displayUtil", function(
        $window,
        $q,
        ORIENTATION,
        FEATURE_CODE,
        displayUtil
    ) {
        var DEVICE_READY_EVENT = 'deviceready';
        var isReadyCallback;
        var isReadyPromise = $q.defer();

        // Define the constructor function.
        function AndroidTvDriver(onDeviceReadyCallback) {
            isReadyCallback = onDeviceReadyCallback;
            document.removeEventListener(DEVICE_READY_EVENT, onDeviceReady);
            document.addEventListener(DEVICE_READY_EVENT, onDeviceReady, false);
        }

        function onDeviceReady() {
            if (isReadyCallback) {
                isReadyCallback($window.device);
            }

            isReadyPromise.resolve();
        }

        // Resolves once cordova is ready
        function isReady() {
            return isReadyPromise.promise;
        }

        function getDisplayInfo() {
            if (!$window.plugins || !$window.plugins.screensize || !$window.plugins.screensize.get) {
                return $q.resolve('');
            }

            var promise = $q.defer();
            var finalize = function(result) {
                promise.resolve(result);
            };

            $window.plugins.screensize.get(function(result) {
                var displayInfo = [];
                displayInfo.push({
                    x: result.width,
                    y: result.height
                });
                finalize(displayInfo);
            }, function() {
                finalize('');
            });

            return promise.promise;
        }

        function setDisplayRotation(rotation) {
            // 0 - landscape
            // 90 - portrait
            // 180 - inverse landscape is not supported, default to landscape
            // 270 - inverse portrait
            return displayUtil.lockOrientation(rotation, {
                '0': ORIENTATION['landscape-primary'],
                '90': ORIENTATION['portrait-primary'],
                '180': ORIENTATION['landscape-primary'],
                '270': ORIENTATION['portrait-secondary']
            });
        }

        function getFeatureSupport(feature) {
            var featureToCheck = feature.toLowerCase();
            switch (featureToCheck) {
                case FEATURE_CODE.APP_EXIT_ON_BACK:
                    return true;
                case FEATURE_CODE.MAX_WEB_VIEW_COLLECTION:
                    return 0;
                default:
                    return null;
            }
        }

        function getMediaDirective(elementType) {
            return {
                directive: elementType,
                attributes: ''
            };
        }

        function closeApplication() {
            $window.navigator.app.exitApp();
        }

        // Define the instance methods and properties
        AndroidTvDriver.prototype = {
            // Public interface
            id: 'browser.androidtv',
            isReady: isReady,
            platform: {
                closeApplication: closeApplication,
                getDisplayInfo: getDisplayInfo,
                getFeatureSupport: getFeatureSupport
            },
            media: {
                setDisplayRotation: setDisplayRotation,
                getMediaDirective: getMediaDirective
            }
        };

        // Return the constructor for the class
        return (AndroidTvDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('BrightsignDriver', ["$window", "$timeout", "$document", "$http", "$q", "FileEntry", "FileError", "FileUtil", "dataUtil", "MEDIA", "MEDIA_DIRECTIVE", "pathService", "logService", "RemoteEvent", "REMOTEVCODE", "REMOTEEVENTTYPE", "BrightsignWebSocketServer", "VersionCompare", "RemoteUtil", "SYSTEM_DATA", "FEATURE_CODE", "ASERRORCODE", "OPERATION_COMMAND", function(
        $window,
        $timeout,
        $document,
        $http,
        $q,
        FileEntry,
        FileError,
        FileUtil,
        dataUtil,
        MEDIA,
        MEDIA_DIRECTIVE,
        pathService,
        logService,
        RemoteEvent,
        REMOTEVCODE,
        REMOTEEVENTTYPE,
        BrightsignWebSocketServer,
        VersionCompare,
        RemoteUtil,
        SYSTEM_DATA,
        FEATURE_CODE,
        ASERRORCODE,
        OPERATION_COMMAND
    ) {
        if (!/brightsign/i.test($window.navigator.userAgent)) {
            return {};
        }

        var deviceInfo = new $window.BSDeviceInfo();
        var vc = new VersionCompare();
        var isFirmware8AndAbove = meetsFirmwareVersion(deviceInfo.version, '8.0.0');
        var isFirmware850AndAbove = meetsFirmwareVersion(deviceInfo.version, '8.5.0');
        var appUpdateConfiguration = {};
        var supportVideoLayering = 'no';
        var defaultHwzValue = 'hwz="on"';
        var bsMessage = null;
        var defers = {};
        var remoteServiceEventHandler = null;
        var receiver = null;
        var msgCounter = 0;
        var hasInit = false;
        var readyQueue = [];
        var minVersionRequired = '6.1.76';
        var volume = 50;
        var MAX_VIDEO_LAYER = 2;
        var MIN_VIDEO_LAYER = 1;
        var defaultVideoLayers = MIN_VIDEO_LAYER;
        var IN_APP_BROWSER_ID_PREFIX = 'brightsign_inappbrowser_';
        var maxVideoLayersKey = 'brightsign.maxvideolayers';
        var inAppBrowserLoadStopCallback;
        var inAppBrowserLoadErrorCallback;
        var inAppBrowsers = [];
        var _server;
        var keyPairingTimeout = undefined;
        var nextPairingKey = undefined;
        var pairingKeyCode = undefined;
        var APPLICABLE_MODELS_FOR_HWZ = [{
            name: 'xt',
            series: '3'
        }, {
            name: 'xt',
            series: '4'
        }, {
            name: '4k',
            series: '2'
        }, {
            name: 'hd',
            series: '4'
        }];

        var deviceManager = {
            features: {
                enableTransitions: 'no',
                maxVideoLayers: defaultVideoLayers.toString(),
                reuseVideoLayer: 'yes',
                useLoadVideo: false,
                defaultVideoDirective: getVideoDirective(),
                requiresClearWebCache: true,
                maxWebViewCollection: null,
                supportsDashboardEmbed: false,
                supportsObjectFit: false,
                supportedRotations: [0, 90, 270]
            },
            reload: function() {
                sendBSMessage({
                    command: 'reload',
                });
            }
        };
        // max filename/directory character length
        var maxChar = 255;
        var PAIRING_KEY_CODE = {
            PAUSE_TOGGLE_1: 'pause_toggle_1',
            PAUSE_TOGGLE_2: 'pause_toggle_2'
        };

        var HOT_KEY = {
            PAUSE_TOGGLE: 'pause_toggle'
        };
        var RETRYABLE_DOWNLOAD_RESPONSE_CODE = [
            '-10001', // Cancelled
        ];
        var STRETCH = {
            UNIFORM: 'uniform',
            UNIFORM_FILL: 'uniformfill',
        };
        var SCHEDULED_TASKS = [{
            id: 'brightsign.clearChromiumCache',
            interval: '24:00:00',
            callback: clearChromiumCache,
        }];
        var PACKAGE_UPDATE_INFO_FILE_PATH = '/package-update-info.json';
        var ROLLBACK_INFO_FILE_PATH = 'diagnostics/rollback-info.json';
        var SCREENSHOT_CACHE_PATH = '/cache/screenshots/';
        var LOG_KEY = 'appspace.core.hal';
        var EXECUTE_UPDATE_DELAY = 0;

        var pairingTable = {};
        pairingTable[PAIRING_KEY_CODE.PAUSE_TOGGLE_1] = {
            delay: 200,
            keyCode: HOT_KEY.PAUSE_TOGGLE,
            pairingKey: PAIRING_KEY_CODE.PAUSE_TOGGLE_2
        };

        function isModelApplicable(model, applicableModel) {
            var series = model[model.length - 1];
            return dataUtil.includesValue(applicableModel.name, model) &&
                series === applicableModel.series;
        }

        function requiresHwz() {
            var model = getModel().toLowerCase();
            for (var i = 0; i < APPLICABLE_MODELS_FOR_HWZ.length; i++) {
                if (!isModelApplicable(model, APPLICABLE_MODELS_FOR_HWZ[i])) {
                    continue;
                }

                return true;
            }

            return false;
        }

        // Define the constructor function.
        function BrightsignDriver(config) {
            bsMessage = new $window.BSMessagePort();
            bsMessage.onbsmessage = process;

            if (isFirmware8AndAbove) {
                supportVideoLayering = 'yes';
                defaultHwzValue = requiresHwz() ?
                    'hwz="z-index:-1"' : '';
                defaultVideoLayers = 2;
                deviceManager.features.maxVideoLayers = defaultVideoLayers.toString();
            }

            if (isFirmware850AndAbove) {
                // Clear web cache is no longer required on BSFW >= 8.5
                deviceManager.features.requiresClearWebCache = false;
                deviceManager.features.maxWebViewCollection = 0;
                deviceManager.features.supportsDashboardEmbed = true;
                deviceManager.features.supportsObjectFit = true;
                // Web worker is crashing video wall worker device on BSFW >= 8.5
                deviceManager.features.webWorkerSupported = false;
                deviceManager.features.supportedRotations.push(180);
            }

            var notifyIsReady = function() {
                // notify isready
                hasInit = true;
                for (var i in readyQueue) {
                    readyQueue[i].resolve();
                }

                readyQueue = [];
            };

            processDeviceManager();
            var isPreviewMode = (config || {}).isPreviewMode;
            if (isFirmware8AndAbove && isPreviewMode) {
                notifyIsReady();
                return;
            }

            // reset
            sendBSMessage({
                command: 'init'
            }, function(re) {
                // check brightsign object is loadded
                var checker = setInterval(function() {
                    if (typeof BSIRReceiver !== 'undefined') {
                        clearInterval(checker);

                        // in previewmode, avoid to re-construct "BSIRReceiver", it will replace the parent version (static object)
                        if (!isPreviewMode) {
                            // try construct IRreceiver object
                            try {
                                receiver = new BSIRReceiver('IR-in', 'NEC32');
                            } catch (e) {
                                // failed on specific brightsign model, create dummy object
                                var ReceiverObject = function() { };

                                ReceiverObject.prototype.onremotedown = function() { };

                                ReceiverObject.prototype.onremoteup = function() { };

                                ReceiverObject.prototype.onremoterepeat = function() { };

                                receiver = new ReceiverObject();
                            }
                        }

                        notifyIsReady();
                    }
                }, 100);
            });

            setVolume(50);
        }

        function getVideoDirective() {
            // as-dual-video is use if >= 8.0 && < 8.5
            return isFirmware8AndAbove && !isFirmware850AndAbove
                ? MEDIA_DIRECTIVE.AS_DUAL_VIDEO
                : MEDIA_DIRECTIVE.AS_MEDIA_VIDEO;
        }

        function processDeviceManager() {
            // this should have been a dependency injection instead
            // all devices running version 8 and above should use load video & manual reload
            if (isFirmware8AndAbove) {
                deviceManager.features.useLoadVideo = true;
                deviceManager.reload = function() {
                    $window.location.reload(true);
                };
            }

            // xt devices running version 7 should enable transition
            if (meetsFirmwareVersion(deviceInfo.version, '7.1.0') && getModel().toLowerCase().indexOf('xt') >= 0) {
                deviceManager.features.enableTransitions = 'yes';
            }

            var videoLayers = getNativeLocalStorage().getItem(maxVideoLayersKey);
            if (videoLayers) {
                deviceManager.features.maxVideoLayers = videoLayers.toString();
            }
        }

        // brightscript callback handler
        function process(res) {
            var id = res.data.id;
            if (id.toLowerCase().indexOf('udpservermessage_') >= 0) {
                _server.onmessageReceived(res.data);
                return;
            } else if (id.toLowerCase() === 'html_widget_event') {
                processInAppBrowserEvent(res.data);
                return;
            }

            var defer = defers[id];

            if (defer !== undefined && defer !== null) {
                var data = res.data;

                // brightsign return "0","1" to represent bool
                // convert itstring to bool
                if (data['result']) {
                    if (data['result'] === '1') {
                        data['result'] = true;
                    } else if (data['result'] === '0') {
                        data['result'] = false;
                    }
                }

                defer(data);
                delete defers[id];
            }
        }

        function processInAppBrowserEvent(event) {
            if (!event || !event.message) {
                return;
            }

            switch (event.message) {
                case 'load-finished':
                    inAppBrowserLoadStopCallback(event);
                    break;
                case 'load-error':
                    inAppBrowserLoadErrorCallback(event);
            }
        }

        // assign timestamp to each
        function sendBSMessage(messages, callback) {
            var timeStamp = new Date().getTime().toString();

            // if reaching max int threshold, reset it
            if (msgCounter >= 9007199254740992) {
                msgCounter = 0;
            }

            msgCounter++;

            // create unique msgID by counter and timestamp
            var key = msgCounter + timeStamp;

            // set defer
            defers[key] = callback;

            // appending param for callback
            messages['id'] = key;

            // send to brightscript
            bsMessage.PostBSMessage(messages); // eslint-disable-line new-cap
        }

        function isReady() {
            var defer = $q.defer();

            // push to wait queue if not yet ready
            if (hasInit) {
                defer.resolve();
            } else {
                readyQueue.push(defer);
            }

            return defer.promise;
        }

        // Plaftorm
        // Returns the support level for a specific feature
        function getFeatureSupport(feature) {
            switch (feature.toLowerCase()) {
                case FEATURE_CODE.MEDIA_VIDEO_SWITCH_DELAY_DURATION:
                    return 100;
                case 'input.keyboard':
                    return 'none';
                case 'input.mouse':
                    return 'none';
                case 'file.localstorage':
                    return 'yes';
                case 'rendering.css3-transitions':
                    return deviceManager.features.enableTransitions;
                case 'rendering.video-layering':
                    return supportVideoLayering;
                case 'media.hls.supported':
                    return 'yes';
                case 'media.maxvideolayers':
                    return deviceManager.features.maxVideoLayers;
                case 'media.reusevideolayer':
                    return deviceManager.features.reuseVideoLayer;
                case 'media.video.use.load':
                    return deviceManager.features.useLoadVideo;
                case 'media.video.load.delay.duration':
                    return undefined;
                case 'media.video.transition.supported':
                    return false;
                case FEATURE_CODE.MAX_WEB_VIEW_COLLECTION:
                    return deviceManager.features.maxWebViewCollection;
                case FEATURE_CODE.DASHBOARD_EMBED_SUPPORTED:
                    return deviceManager.features.supportsDashboardEmbed;
                case FEATURE_CODE.CARD_TIMEOUT_DURATION:
                    return 60000;
                case FEATURE_CODE.WEB_WORKER_SUPPORTED:
                    return deviceManager.features.webWorkerSupported != undefined ? deviceManager.features.webWorkerSupported : !!$window.Worker;
                case FEATURE_CODE.SCREENSHOT_SUPPORTED:
                    return getScreenshotCapability();
                default:
                    return null;
            }
        }

        function getTransitionSupport(transition) {
            var support = true;
            var model = getModel().toLowerCase();
            if (model.indexOf('xd') == 0) {
                switch (transition) {
                    case 'fade':
                    case 'zoomfade':
                        support = false;
                        break;
                }
            }

            return support;
        }

        function getModel() {
            return (deviceInfo.model === undefined) ? '' : deviceInfo.model;
        }

        function getFirmware() {
            var promise = $q.defer();
            if (deviceInfo.version === undefined) {
                promise.resolve('-');
            } else {
                promise.resolve(deviceInfo.version);
            }

            return promise.promise;
        }

        // get minimmmum version required
        function getMinVersionRequired() {
            return minVersionRequired;
        }

        function cleanVersion(version) {
            var suffixStartsAt = version.indexOf('-');
            return suffixStartsAt > -1 ?
                version.substring(0, suffixStartsAt) :
                version;
        }

        function meetsFirmwareVersion(version, targetVersion) {
            return vc.compare(cleanVersion(version), targetVersion);
        }

        // Get if firmware meet minimum requirement
        function isMeetMinFirmware() {
            var promise = $q.defer();
            getFirmware().then(function(firmware) {
                var meetsMinimumVersion = meetsFirmwareVersion(firmware, minVersionRequired);
                if (meetsMinimumVersion) {
                    promise.resolve(true);
                    return;
                }

                promise.reject({
                    minFWVersion: getMinVersionRequired(),
                    currentFWVersion: firmware,
                });
            });

            return promise.promise;
        }

        function getSerialNumber() {
            return $q.resolve(deviceInfo.deviceUniqueId);
        }

        function getFingerPrint() {
            var defer = $q.defer();
            getSerialNumber().then(function(serialNo) {
                defer.resolve({
                    fingerprint: serialNo,
                    type: SYSTEM_DATA.FINGERPRINT_TYPE.HARDWARE
                });
            });
            return defer.promise;
        }

        function getCpuUsage() {
            var promise = $q.defer();

            var callback = function(res) {
                res = res['cpu'];
                var cpu = res.split(' ');
                var usage = 0;
                if (cpu[0]) {
                    // convert to 100% since returned value is a fraction of 1
                    usage = cpu[0] * 100;
                }

                promise.resolve({
                    cpuUsage: usage
                });
            };

            sendBSMessage({
                command: 'getCpuUsage'
            }, callback);

            return promise.promise;
        }

        function getCronJobs() {
            return SCHEDULED_TASKS;
        }

        function getMemoryUsage() {
            var promise = $q.defer();

            var callback = function(res) {
                var memArray = res['memory'];
                memArray = memArray.split('\n');

                var obj = {};
                if (memArray[0].indexOf('MemTotal') != -1 && memArray[1].indexOf('MemFree') != -1) {
                    // convert to bytes since the return value is in kB
                    var total = parseInt(memArray[0].split(':')[1].trim(), 10) * 1000;
                    var free = parseInt(memArray[1].split(':')[1].trim(), 10) * 1000;
                    var used = total - free;

                    obj.capacity = total;
                    obj.availableCapacity = used;
                }

                promise.resolve(obj);
            };

            sendBSMessage({
                command: 'getMemoryUsage'
            }, callback);

            return promise.promise;
        }

        function getAvailableStorage() {
            var defer = $q.defer();

            var callback = function(res) {
                // convert mebibyte to byte
                var storage = parseInt(res['available']) * 1048576;

                getUsedStorage().then(function(res2) {
                    var used = res2.used;
                    var total = used + storage;

                    defer.resolve({
                        used: used,
                        total: total
                    });
                });
            };

            sendBSMessage({
                command: 'getAvailableStorage'
            }, callback);

            return defer.promise;
        }

        function getUsedStorage() {
            var defer = $q.defer();

            var callback = function(res) {
                // convert mebibyte to byte
                var storage = parseInt(res['used']) * 1048576;

                defer.resolve({
                    used: storage
                });
            };

            sendBSMessage({
                command: 'getUsedStorage'
            }, callback);

            return defer.promise;
        }

        function getDisplayInfo() {
            var promise = $q.defer();

            var callback = function(res) {
                var x = res.x;
                var y = res.y;

                // Get current rotation to adjust resolution based on orientation
                getDisplayRotation().then(function(rotationResult) {
                    var rotation = rotationResult && rotationResult.rotation;
                    // Convert rotation to number to handle both string and number types
                    var rotationNumber = dataUtil.isNumber(rotation) ? parseInt(rotation) : undefined;
                    var portraitRotations = [90, 270];
                    // If rotation is 90 or 270 (portrait), swap x and y to match current orientation
                    return (rotationNumber !== undefined && portraitRotations.indexOf(rotationNumber) !== -1)
                        ? [{x: y, y: x}]
                        : [{x: x, y: y}];
                }, function() {
                    // If rotation retrieval fails, use original resolution
                    return [{x: x, y: y}];
                }).then(promise.resolve);
            };

            sendBSMessage({
                command: 'getDisplayResolution'
            }, callback);

            return promise.promise;
        }

        function setStartupURL(url) {
            var defer = $q.defer();

            var callback = function(res) {
                defer.resolve();
            };

            sendBSMessage({
                command: 'setStartupUrl',
                url: url
            }, callback);

            return defer.promise;
        }

        // accept both string, file
        // type: file/ command
        // command: path/ script
        function executePlatformScript(options) {
            var defer = $q.defer();
            var callback = function(res) {
                defer.resolve();
            };

            if (options.type === 'file') {
                sendBSMessage({
                    command: 'executeFile',
                    path: options.args
                }, callback);
            } else if (options.type === 'command') {
                // replace single to double quote
                // brightside doesn't support single quote string
                var command = options.args;
                command = command.split('\'').join('"');

                sendBSMessage({
                    command: 'executeScript',
                    script: command
                }, callback);
            }

            return defer.promise;
        }

        // Network
        function getIpAddress() {
            var defer = $q.defer();

            var callback = function(res) {
                if (res.result) {
                    defer.resolve({
                        address: res['address']
                    });
                } else {
                    defer.reject();
                }
            };

            sendBSMessage({
                command: 'getIpAddress'
            }, callback);

            return defer.promise;
        }

        function getMacAddress() {
            var defer = $q.defer();

            var callback = function(res) {
                defer.resolve(res.result ? res.mac : undefined);
            };

            sendBSMessage({
                command: 'getMacAddress'
            }, callback);

            return defer.promise;
        }

        function downloadHttp(remotePath, localPath, item) {
            var defer = $q.defer();
            var relPath = resolveRelative(localPath);
            var absPath = resolveUrl(localPath);
            var fileEntry = createFileEntry(absPath, false);
            var dirPath = FileUtil.getDirPath(localPath);
            item = item || {};
            var options = item.options || {};
            var headers = options.headers || {};

            // return file entry
            var callback = function(res) {
                if (res.result) {
                    defer.resolve(fileEntry);
                } else {
                    defer.reject({
                        code: convertResponseCodeToAppspaceErrorCode(res.errorcode),
                        error: '[ErrorCode: ' + res.errorcode + '] ' + res.message
                    });
                }
            };

            // check is file downloaded
            checkFile(relPath).then(function(res) {
                if (res.exists) {
                    defer.resolve(fileEntry);
                } else {
                    createDirectory(dirPath).then(
                        function() {
                            sendBSMessage({
                                command: 'downloadUrl',
                                remotePath: remotePath,
                                localPath: relPath,
                                headers: JSON.stringify(headers)
                            }, callback);
                        });
                }
            }, function(err) {
                defer.reject(err);
            });

            return defer.promise;
        }

        // Convert response code to appspace error code
        function convertResponseCodeToAppspaceErrorCode(responseCode) {
            return RETRYABLE_DOWNLOAD_RESPONSE_CODE.indexOf(responseCode) > -1 ?
                ASERRORCODE.RETRYABLE_DOWNLOAD_ERROR :
                responseCode; // return the actual response code for sync service to handle
        }

        // Restarts the application
        function reboot() {
            sendBSMessage({
                command: 'reboot',
            });
        }

        // Remote
        function isRemoteSupported() {
            return true;
        }

        function registerRemoteEvents(callback) {
            remoteServiceEventHandler = callback;

            // init remote receiver & bind event
            if (receiver) {
                receiver.onremotedown = onRemoteDown;
                receiver.onremoteup = onRemoteUp;
                receiver.onremoterepeat = onRemoteRepeat;
            }

            // bind keyboard handler
            $document.keydown(keydownHandler);
            $document.keyup(keyupHandler);
        }

        function unRegisterRemoteEvents(id) {
            remoteServiceEventHandler = null;

            // remove receiver
            if (receiver) {
                receiver.onremotedown = null;
                receiver.onremoteup = null;
                receiver.onremoterepeat = null;
            }

            // unbind keyboard
            $document.unbind('keydown', keydownHandler);
            $document.unbind('keyup', keyupHandler);
        }

        function createRemoteEvent(e) {
            var re = RemoteUtil.processDefaultKeyboardMapping(e);
            return re;
        }

        function keydownHandler(e) {
            var re = createRemoteEvent(e);
            if (re == null) {
                return;
            }

            re.eventType = REMOTEEVENTTYPE.buttonDown;
            // Notifice remote driver service
            if (remoteServiceEventHandler) {
                remoteServiceEventHandler(re);
            }

            // Prevent default if needed
            if (re.isDefaultPrevented()) {
                e.preventDefault(); // prevent the default action (scroll / move caret)
            }
        }

        function keyupHandler(e) {
            var re = createRemoteEvent(e);
            if (re == null) {
                return;
            }

            re.eventType = REMOTEEVENTTYPE.buttonUp;
            // Notifice remote driver service
            if (remoteServiceEventHandler) {
                remoteServiceEventHandler(re);
            }

            // Prevent default if needed
            if (re.isDefaultPrevented()) {
                e.preventDefault(); // prevent the default action (scroll / move caret)
            }
        }

        // adding 9000 in irTable default value for now. to avoid clash with other remote
        // TODO: investigate why previously we remove 9000
        // table: IR code - IR command
        var irTable = {
            '90001f': 'info',
            '900004': 'rewind',
            '900006': 'play',
            '900005': 'fastforward',
            '900010': 'input',
            '900047': 'menu',
            '900003': 'stop',
            '900002': 'pause',
            '900008': 'skipforward',
            '90001c': 'last',
            '90000a': 'guide',
            '900011': 'exit',
            '900050': 'red',
            '900052': 'green',
            '900054': 'yellow',
            '900056': 'blue',
            '900012': 'enter',
            '900015': 'up',
            '900016': 'down',
            '900013': 'left',
            '900014': 'right',
            '90000b': 'pageup',
            '90000c': 'pagedown',
            '900042': 'channelup',
            '900043': 'channeldown',
            '900001': 'mute',
            '900040': 'volumeup',
            '900041': 'volumedown',
            '90001d': 'number_0',
            '90000d': 'number_1',
            '90000e': 'number_2',
            '90000f': 'number_3',
            '90001e': 'number_4',
            '900017': 'number_5',
            '900018': 'number_6',
            '900019': 'number_7',
            '90001a': 'number_8',
            '90001b': 'number_9',
        };

        var appleRemoteGen3 = {
            'ee870b': 'up',
            'ee870d': 'down',
            'ee8708': 'left',
            'ee8707': 'right',
            'ee875d': 'enter',
            'ee8702': 'exit',
        };

        var appleRemoteGen2 = {
            'ee870a': 'up',
            'ee870c': 'down',
            'ee8709': 'left',
            'ee8706': 'right',
            'ee875c': 'enter',
            'ee8703': 'exit',
            'ee875f': PAIRING_KEY_CODE.PAUSE_TOGGLE_1,
            'ee8705': PAIRING_KEY_CODE.PAUSE_TOGGLE_2
        };

        var rokuRemote = {
            '6f9019': 'play',
            '6f9017': 'home',
            '6f901c': 'pause',
            '6f9015': 'exit',
            '6f9014': 'enter',
            '6f9012': 'up',
            '6f9013': 'down',
            '6f9010': 'left',
            '6f9011': 'right',
            '6f901b': 'rewind',
            '6f901a': 'fastforward',
            '6f9042': 'system',
            '6f9018': 'search',
            '6f9040': 'volumeup',
            '6f9041': 'volumedown',
            '6f901d': 'plus',
            '6f901e': 'shuffle',
            '6f9016': 'power',
            '6f901f': 'repeat'
        };

        var haivisionRemote = {
            '2bdca': 'up',
            '2bdd2': 'down',
            '2bd99': 'left',
            '2bdc1': 'right',
            '2bdce': 'enter',
            '2bdd0': 'exit',
            '2bd95': 'system'
        };

        $angular.extend(irTable, appleRemoteGen3);
        $angular.extend(irTable, appleRemoteGen2);
        $angular.extend(irTable, rokuRemote);
        $angular.extend(irTable, haivisionRemote);

        // table: IR command - hotkey command
        var hotkeyTable = {
            exit: REMOTEVCODE.back,
            enter: REMOTEVCODE.select,
            up: REMOTEVCODE.up,
            down: REMOTEVCODE.down,
            left: REMOTEVCODE.left,
            right: REMOTEVCODE.right,
            pageup: REMOTEVCODE.page_up,
            pagedown: REMOTEVCODE.page_down,
            menu: REMOTEVCODE.menu,
            system: REMOTEVCODE.system,
            play: REMOTEVCODE.play,
            pause: REMOTEVCODE.pause,
            guide: REMOTEVCODE.guide,
            number_0: REMOTEVCODE.no_0,
            number_1: REMOTEVCODE.no_1,
            number_2: REMOTEVCODE.no_2,
            number_3: REMOTEVCODE.no_3,
            number_4: REMOTEVCODE.no_4,
            number_5: REMOTEVCODE.no_5,
            number_6: REMOTEVCODE.no_6,
            number_7: REMOTEVCODE.no_7,
            number_8: REMOTEVCODE.no_8,
            number_9: REMOTEVCODE.no_9,
            channelup: REMOTEVCODE.channel_up,
            channeldown: REMOTEVCODE.channel_down,
            volumeup: REMOTEVCODE.volume_up,
            volumedown: REMOTEVCODE.volume_down,
            mute: REMOTEVCODE.mute,
            home: REMOTEVCODE.home
        };
        hotkeyTable[HOT_KEY.PAUSE_TOGGLE] = REMOTEVCODE.pause_toggle;

        function clearKeyPairing() {
            nextPairingKey = undefined;
            pairingKeyCode = undefined;
        }

        function clearKeyPairingTimeout() {
            if (!keyPairingTimeout) {
                return;
            }

            $timeout.cancel(keyPairingTimeout);
            keyPairingTimeout = undefined;
            clearKeyPairing();
        }

        function necFromNec32(code) {
            while (code.length < 8) {
                code = '0' + code;
            }

            var res = code[7] + code[4] + code[5] + code[2] + code[3];
            if (code[6] !== '0') {
                res = code[6] + res;
            }

            return res;
        }

        function hotkeyFromCode(code, nec32) {
            var longCode = code.toString(16); // hex string for codes from server

            if (nec32) {
                longCode = necFromNec32(longCode);
            }

            var irCommand = irTable[longCode];
            // hack to return nec32 menu key
            if (code === 3091660944) {
                return REMOTEVCODE.system;
            }

            // hack to return nec32 back key
            if (code === 3994091664) {
                return REMOTEVCODE.back;
            }

            if (!irCommand) {
                return null;
            }

            var pairing = pairingTable[irCommand];
            // Key has to be paired
            if (pairing) {
                clearKeyPairingTimeout();
                keyPairingTimeout = $timeout(clearKeyPairing, pairing.delay);
                nextPairingKey = pairing.pairingKey;
                pairingKeyCode = pairing.keyCode;
                return;
            }

            // Key is paired, use pairing key code as IR command
            if (nextPairingKey && nextPairingKey === irCommand) {
                irCommand = pairingKeyCode;
            }

            clearKeyPairingTimeout();
            return hotkeyTable[irCommand];
        }

        // block apple remote double firing event
        var blockKey = false;

        function onRemoteDown(e) {
            // block double key event
            if (blockKey) {
                return;
            }

            // apple remote hack
            if (e.code === 1499301870 || e.code === 1499367406) {
                blockKey = true;

                setTimeout(function() {
                    blockKey = false;
                }, 300);
            }

            var hotkey = hotkeyFromCode(e.code, e.irType === 'NEC32');
            createRemoteEventFromRemote(REMOTEEVENTTYPE.buttonDown, e.code, hotkey);
        }

        function onRemoteUp() {
            // supported on Iguanaworks device only
        }

        function onRemoteRepeat(e) {
            var hotkey = hotkeyFromCode(e.code, e.irType === 'NEC32');
            createRemoteEventFromRemote(REMOTEEVENTTYPE.buttonRepeat, e.code, hotkey);
        }

        function createRemoteEventFromRemote(eventType, code, hotkey) {
            var remoteEvent = new RemoteEvent();
            remoteEvent.eventType = eventType;
            remoteEvent.code = code;
            remoteEvent.vcode = hotkey;
            if (remoteServiceEventHandler) {
                remoteServiceEventHandler(remoteEvent);
            }
        }

        // File
        function createFileEntry(path, isDirectory) {
            var fileName = FileUtil.getBaseName(path);
            var absPath = resolveUrl(path);
            var fileEntry = {
                name: fileName,
                path: absPath,
                isDirectory: isDirectory,
            };

            return new FileEntry(fileEntry);
        }

        function createFileError(error, path) {
            var absPath = resolveUrl(path);
            var fileError = {
                message: error,
                path: absPath
            };

            return new FileError(fileError);
        }

        function shortenFilePath(path) {
            path = pathService.normalize(path);
            var paths = path.split('/');

            if (paths.length > 0) {
                for (var i in paths) {
                    if (paths[i].length > maxChar) {
                        paths[i] = md5(paths[i]);
                    }
                }

                path = paths.join('/');
            }

            return path;
        }

        function resolveUrl(path) {
            return shortenFilePath(path);
        }

        function resolveRelative(path) {
            return shortenFilePath(path);
        }

        function createDirectory(path) {
            var defer = $q.defer();
            var relPath = resolveRelative(path);
            var absPath = resolveUrl(path);
            var fileEntry = createFileEntry(absPath, true);

            // first check exists
            checkDirectory(relPath).then(function(res) {
                if (res.exists) {
                    defer.resolve({
                        created: false,
                        entry: fileEntry
                    });
                } else {
                    var dirs = relPath.split('/');

                    // remove first blank element
                    if (dirs[0] === '') {
                        dirs = dirs.slice(1);
                    }

                    // remove last blank element
                    if (dirs[dirs.length - 1] === '') {
                        dirs.pop();
                    }

                    dirs = dirs.reverse();

                    var ensureDir = function(path) {
                        var dirName = dirs.pop();
                        if (dirName) {
                            var currentPath = pathService.join(path, dirName);

                            var callback = function(res) {
                                if (dirs.length > 0) {
                                    ensureDir(currentPath);
                                } else {
                                    if (res.result) {
                                        defer.resolve({
                                            created: true,
                                            entry: fileEntry
                                        });
                                    } else {
                                        // fail to create directory
                                        var fileError = createFileError('File exists at location.', absPath);
                                        defer.reject(fileError);
                                    }
                                }
                            };

                            sendBSMessage({
                                command: 'createDirectory',
                                path: currentPath
                            }, callback);
                        } else {
                            defer.resolve({
                                created: true,
                                entry: fileEntry
                            });
                        }
                    };

                    ensureDir('/');
                }
            });

            return defer.promise;
        }

        function removeDirectory(path) {
            var defer = $q.defer();
            var absPath = resolveUrl(path);
            var relPath = resolveRelative(path);

            var callback = function(res) {
                if (res.result) {
                    defer.resolve();
                } else {
                    var fileError = createFileError('RemoveDirectory on file', absPath);
                    defer.reject(fileError);
                }
            };

            checkDirectory(relPath).then(function(res) {
                if (res.exists) {
                    sendBSMessage({
                        command: 'removeDirectory',
                        path: relPath
                    }, callback);
                } else {
                    defer.resolve();
                }
            }, function(err) {
                defer.reject(err);
            });

            return defer.promise;
        }

        function checkDirectory(path) {
            var defer = $q.defer();
            var relPath = resolveRelative(path);
            var absPath = resolveUrl(path);

            var callback = function(res) {
                if (res.result) {
                    defer.resolve({
                        exists: true,
                        path: absPath
                    });
                } else {
                    defer.resolve({
                        exists: false
                    });
                }
            };

            sendBSMessage({
                command: 'checkDirectory',
                path: relPath,
            }, callback);

            return defer.promise;
        }

        function enumerateDirectory(path) {
            var defer = $q.defer();
            var relPath = resolveRelative(path);
            var promises = [];

            var callback = function(res) {
                if (res.result) {
                    var files = res.files.split(',');
                    var fileEntries = [];

                    for (var i in files) {
                        // decide is file or directory
                        var fileName = files[i];
                        var entry;
                        var filePath = pathService.join(relPath, fileName);

                        var checkDir = function(path) {
                            var p = checkDirectory(path).then(function(res) {
                                if (res.exists) {
                                    entry = createFileEntry(path, true);
                                } else {
                                    entry = createFileEntry(path, false);
                                }

                                fileEntries.push(entry);
                            });
                            return p;
                        };

                        promises.push(checkDir(filePath));
                    }

                    // all obj
                    $q.all(promises).then(function(res) {
                        defer.resolve(fileEntries);
                    });
                } else {
                    defer.reject();
                }
            };

            // enumerateDirectory
            sendBSMessage({
                command: 'enumerateDirectory',
                path: relPath,
            }, callback);

            return defer.promise;
        }

        function checkFile(path) {
            var defer = $q.defer();
            var relPath = resolveRelative(path);
            var absPath = resolveUrl(path);

            var callback = function(res) {
                if (res.result) {
                    defer.resolve({
                        exists: true,
                        path: absPath
                    });
                } else {
                    defer.resolve({
                        exists: false
                    });
                }
            };

            // path: dirPath,
            // name: dirName
            sendBSMessage({
                command: 'checkFile',
                path: relPath
            }, callback);

            return defer.promise;
        }

        function createFile(path) {
            var defer = $q.defer();
            var absPath = resolveUrl(path);
            var relPath = resolveRelative(path);
            var fileEntry = createFileEntry(absPath, false);

            var callback = function(res) {
                if (res.result) {
                    defer.resolve({
                        created: true,
                        entry: fileEntry
                    });
                } else {
                    checkDirectory(relPath).then(function() {
                        var fileError;
                        if (res.exists) {
                            fileError = createFileError('Directory exists at location.', absPath);
                        } else {
                            fileError = createFileError('Parent directory not exists. Couldn\'t create file.', absPath);
                        }

                        defer.reject(fileError);
                    });
                }
            };

            // check file existent
            checkFile(relPath).then(function(res) {
                if (res.exists) {
                    // return file exists
                    defer.resolve({
                        created: false,
                        entry: fileEntry
                    });
                } else {
                    // create file
                    sendBSMessage({
                        command: 'createFile',
                        path: relPath
                    }, callback);
                }
            });

            return defer.promise;
        }

        function writeFile(path, content) {
            var defer = $q.defer();
            var relPath = resolveRelative(path);

            var callback = function(res) {
                if (res.result) {
                    defer.resolve();
                } else {
                    defer.reject();
                }
            };

            sendBSMessage({
                command: 'writeFile',
                path: relPath,
                content: content
            }, callback);

            return defer.promise;
        }

        function moveFile(sourcePath, destinationPath) {
            var defer = $q.defer();
            var dirPath = FileUtil.getDirPath(destinationPath);
            var absPath = resolveUrl(destinationPath);

            var callback = function(res) {
                if (res.result) {
                    var entry = createFileEntry(absPath, false);
                    defer.resolve(entry);
                } else {
                    var fileError = createFileError('move file error', absPath);
                    defer.reject(fileError);
                }
            };

            // create directory
            createDirectory(dirPath).then(function(res) {
                // move file
                sendBSMessage({
                    command: 'moveFile',
                    sourcePath: sourcePath,
                    destinationPath: destinationPath
                }, callback);
            });

            return defer.promise;
        }

        function removeFile(path) {
            var defer = $q.defer();
            var relPath = resolveRelative(path);
            var absPath = resolveUrl(path);

            var callback = function(res) {
                if (res.result) {
                    defer.resolve();
                } else {
                    var fileError = createFileError('remove file error', absPath);
                    defer.reject(fileError);
                }
            };

            // remove file
            sendBSMessage({
                command: 'removeFile',
                path: relPath
            }, callback);

            return defer.promise;
        }

        // Media
        function setDisplayRotation(rotation) {
            if (!dataUtil.includesValue(rotation, deviceManager.features.supportedRotations)) {
                return $q.reject({
                    message: 'Only landscape and portrait mode supported on brightsignOS'
                });
            }

            var defer = $q.defer();
            var callback = function(res) {
                if (res.result) {
                    defer.resolve();
                }
            };

            sendBSMessage({
                command: 'setDisplayRotation',
                rotation: rotation
            }, callback);

            return defer.promise;
        }

        function getDisplayRotation(rotation) {
            var defer = $q.defer();
            var callback = function(res) {
                if (res.result) {
                    // OLD CONDITION WHICH NO LONGET NEEDED WITH LATEST 6.1 FIRMWARE
                    // fake it as portrait mode
                    // if (res.rotation === 270) {
                    //    res.rotation = 90;
                    // };

                    defer.resolve({
                        rotation: res.rotation
                    });
                } else {
                    defer.reject({
                        message: 'Failed to retrieve display rotation'
                    });
                }
            };

            sendBSMessage({
                command: 'getDisplayRotation',
            }, callback);

            return defer.promise;
        }

        function setVideoStretch(videoElement, stretch) {
            if (!videoElement || !videoElement.attr || !videoElement.css) {
                return;
            }

            videoElement.attr('viewmode', getViewMode(stretch));
            videoElement.css('object-fit', getObjectFit(stretch));
        }

        function getViewMode(stretch) {
            // viewmode="scale-to-fill": Scales the video to fill the window. The aspect ratio of the source video is ignored, so the video may appear stretched.
            // viewmode="scale-to-fit": Letterboxes the video.
            // viewmode="scale-to-fill-and-crop": Scales the video to fill the window. The aspect ratio of the source video is maintained, so the video may be cropped.
            switch (stretch) {
                case STRETCH.UNIFORM:
                    return 'scale-to-fit';
                case STRETCH.UNIFORM_FILL:
                    return 'scale-to-fill-and-crop';
                default:
                    return 'scale-to-fill';
            }
        }

        function getObjectFit(stretch) {
            var objectFit = 'fill';
            if (!deviceManager.features.supportsObjectFit) {
                return objectFit;
            }

            switch (stretch) {
                case STRETCH.UNIFORM:
                    objectFit = 'contain';
                    break;
                case STRETCH.UNIFORM_FILL:
                    objectFit = 'cover';
                    break;
            }

            return objectFit;
        }

        function getMediaDirective(elementType, options) {
            var result = {
                directive: elementType,
                attributes: ''
            };
            options = options || {};
            if (elementType === MEDIA_DIRECTIVE.WEB && options.webEngine === MEDIA.WEB_ENGINE_IN_APP_BROWSER) {
                result.directive = MEDIA_DIRECTIVE.IN_APP_BROWSER;
            } else if (elementType == 'video') {
                var model = getModel().toLowerCase();
                var attributes = '';
                if (!isFirmware8AndAbove && options.layerindex) {
                    attributes += 'hwz="z-index:' + options.layerindex + '"';
                } else {
                    attributes += defaultHwzValue;
                }

                // Route audio to all outputs (required for AC3 audio)
                if (model.indexOf('sv-4k-row') >= 0 || (options.forceac3audio && (options.forceac3audio == 'true' || options.forceac3audio == true))) {
                    attributes += ' pcmaudio="hdmi;Analog"';
                }

                if ((options.closecaption == true || options.closecaption == 'true')) {
                    if (!options.closecaptioninfobrightsign) {
                        options.closecaptioninfobrightsign = ';';
                    }

                    attributes += ' preferredcaptions="' + options.closecaptioninfobrightsign + '"';
                }

                result.attributes = attributes;
            } else if (elementType == MEDIA_DIRECTIVE.AS_MEDIA_VIDEO) {
                result.directive = deviceManager.features.defaultVideoDirective;
            }

            return result;
        }

        function getVideoAttributes() {
            return defaultHwzValue;
        }

        var forceDisableCursor;

        function setCursorVisible(visible) {
            // if we're trying to show cursor but we have force to disable cursor, ignore
            if (visible && forceDisableCursor) {
                return;
            }

            var defer = $q.defer();
            var callback = function(res) {
                if (res.result) {
                    defer.resolve();
                } else {
                    defer.reject();
                }
            };

            sendBSMessage({
                command: 'setCursorVisible',
                visible: visible
            }, callback);

            return defer.promise;
        }

        function getCursorVisible(visible) {
            var defer = $q.defer();
            var callback = function(res) {
                if (res.result) {
                    defer.resolve({
                        visible: res.visible === '1' ? true : false
                    });
                } else {
                    defer.reject();
                }
            };

            sendBSMessage({
                command: 'getCursorVisible'
            }, callback);

            return defer.promise;
        }

        function enableCursorVisibility(enable) {
            forceDisableCursor = !enable;
        }

        // Returns the list of supported video protocols
        function getSupportedVideoProtocols() {
            return $q(function(resolve, reject) {
                setTimeout(
                    function() {
                        var protocols = ['http', 'https', 'hls', 'multicast'];
                        resolve(protocols);
                    }, 1);
            });
        }

        function getPreviousVolume() {
            if (previousVol) {
                volume = previousVol;
                previousVol = undefined;
            }

            return volume;
        }

        function volumeUp(offset) {
            volume = getPreviousVolume();
            var tempVol = volume + (offset || 1);
            volume = tempVol > 100 ? 100 : tempVol;
            setVolume(volume);
        }

        function volumeDown(offset) {
            volume = getPreviousVolume();
            var tempVol = volume - (offset || 1);
            volume = tempVol < 0 ? 0 : tempVol;
            setVolume(volume);
        }

        var previousVol;
        function toggleVolumeMute() {
            if (volume) {
                previousVol = volume;
                volume = 0;
            } else {
                volume = getPreviousVolume();
            }

            setVolume(volume);
        }

        function setVolume(volume) {
            sendBSMessage({
                command: 'setVolume',
                volume: volume
            });
        }

        function enableVirtualKeyboard(boolean) {
            var defer = $q.defer();
            var callback = function(res) {
                if (res.result) {
                    defer.resolve(res);
                }
            };

            if (boolean) {
                sendBSMessage({
                    command: 'createVirtualKeyboard',
                }, callback);
            } else {
                sendBSMessage({
                    command: 'removeVirtualKeyboard',
                }, callback);
            }

            return defer.promise;
        }

        function clearWebCache() {
            if ($window.gc) {
                $window.gc();
            }

            if (!deviceManager.features.requiresClearWebCache) {
                return;
            }

            sendBSMessage({
                command: 'clearWebCache',
            });
        }

        function getAppspaceDeviceType() {
            return 'BRIGHTSIGN';
        }

        function getAppUpdateCapabilities() {
            return {
                scheduling: true,
                frequency: true,
                downgrade: true,
                pinning: true,
            };
        }

        function executeUpdate() {
            logService.debug(LOG_KEY, 'Executing app update');
            var deferred = $q.defer();
            deferred.resolve();
            $timeout(function applyUpdate() {
                logService.debug(LOG_KEY, 'Rebooting to apply app update.');
                reboot();
            }, EXECUTE_UPDATE_DELAY);
            return deferred.promise;
        }

        function isAppUpdateEnabled() {
            return Boolean(appUpdateConfiguration.isEnabled);
        }

        function checkAppUpdateSupport() {
            var isEnabled = isAppUpdateEnabled();
            if (!isEnabled) {
                logService.debug(LOG_KEY, 'App update feature flag is disabled');
            }

            return $q.resolve(isEnabled);
        }

        function getAppUpdateInfoFilePath() {
            return resolveRelative(PACKAGE_UPDATE_INFO_FILE_PATH);
        }

        function createPackageUpdateInfoFile() {
            var deferred = $q.defer();
            var filePath = getAppUpdateInfoFilePath();
            createFile(filePath).then(function(result) {
                deferred.resolve(filePath);
            }, function(error) {
                deferred.reject('Failed to create package update info file: ' + error);
            });
            return deferred.promise;
        }

        function getUpdatePackageInfo() {
            var deferred = $q.defer();
            var errorMessage = '';
            var packageUpdateInfoFilePath = '';
            var packageUpdateInfo = {
                updateUrl: '',
            };
            var finalize = function() {
                if (errorMessage) {
                    logService.warn(LOG_KEY, 'Unable to get package update info: ' + errorMessage);
                }

                deferred.resolve({
                    updateInfo: packageUpdateInfo,
                    updateInfoFilePath: packageUpdateInfoFilePath
                });
            };

            createPackageUpdateInfoFile().then(function(updateInfoFilePath) {
                packageUpdateInfoFilePath = updateInfoFilePath;
                $http.get(updateInfoFilePath).then(function(res) {
                    var updateInfo = (res || {}).data;
                    if (dataUtil.isObjectEmptyOrUndefined(updateInfo)) {
                        errorMessage = 'Package update info not found';
                        return;
                    }

                    packageUpdateInfo = $angular.copy(updateInfo);
                }, function(error) {
                    errorMessage = 'Failed to get package update info: ' + JSON.stringify(error);
                }).finally(finalize);
            }, finalize);
            return deferred.promise;
        }

        function removePackageUpdateInfoFile() {
            var deferred = $q.defer();
            removeFile(getAppUpdateInfoFilePath()).then(deferred.resolve, deferred.resolve);
            return deferred.promise;
        }

        function getUpdateUrl() {
            var deferred = $q.defer();
            getUpdatePackageInfo().then(function(updatePackageInfo) {
                deferred.resolve(updatePackageInfo.updateInfo.updateUrl || '');
            });
            return deferred.promise;
        }

        function setUpdateUrl(url) {
            if (!url) {
                return $q.reject('Invalid update URL');
            }

            var deferred = $q.defer();
            var onFailed = function(error) {
                logService.debug(LOG_KEY, error);
                deferred.reject(error);
            };

            if (!isAppUpdateEnabled()) {
                var failureReason = 'App update feature flag is disabled';
                // Remove the update info file to prevent the update from being applied
                removePackageUpdateInfoFile().then(function() {
                    onFailed(failureReason);
                });
                return deferred.promise;
            }

            getUpdatePackageInfo().then(function(updatePackageInfo) {
                // Same unexecuted update URL has been set before
                if (updatePackageInfo.updateInfo.updateUrl === url && !updatePackageInfo.updateInfo.lastUpdated) {
                    logService.debug(LOG_KEY, 'Update URL is the same as the current one');
                    deferred.resolve();
                    return;
                }

                // Save the updateUrl without "lastUpdated" to ensure the update is applied immediately upon execution
                var packageUpdateInfo = {
                    updateUrl: url,
                };

                writeFile(updatePackageInfo.updateInfoFilePath, JSON.stringify(packageUpdateInfo)).then(deferred.resolve, function() {
                    onFailed('Failed to write package update info');
                });
            });
            return deferred.promise;
        }

        function setAppUpdateConfiguration(configuration) {
            if (dataUtil.isObjectEqual(configuration, appUpdateConfiguration)) {
                return $q.resolve();
            }

            appUpdateConfiguration = $angular.copy(configuration) || {};
            var isEnabled = isAppUpdateEnabled();
            var deferred = $q.defer();
            var cleanUpPackageUpdateInfo = isEnabled ?
                $q.resolve :
                removePackageUpdateInfoFile;

            var callback = function(res) {
                if (res.result) {
                    deferred.resolve();
                    return;
                }

                deferred.reject('setIsAppUpdateEnabled returned invalid result');
            };

            cleanUpPackageUpdateInfo().then(function() {
                sendBSMessage({
                    command: 'setIsAppUpdateEnabled',
                    isEnabled: isEnabled,
                }, callback);
            });
            return deferred.promise;
        }

        function getRollbackInfoFilePath() {
            return resolveRelative(ROLLBACK_INFO_FILE_PATH);
        }

        function getRollbackInfo() {
            var deferred = $q.defer();
            var onFailed = function(error) {
                var errorMessage = 'Unable to get rollback info due to ' + error;
                logService.error(LOG_KEY, errorMessage);
                deferred.reject(errorMessage);
            };

            $http.get(getRollbackInfoFilePath()).then(function(response) {
                var rollbackInfo = (response || {}).data;
                if (dataUtil.isObjectEmptyOrUndefined(rollbackInfo)) {
                    onFailed('Rollback info not found');
                    return;
                }

                deferred.resolve({
                    reason: rollbackInfo.reason,
                    status: rollbackInfo.status,
                    appVersion: rollbackInfo.appVersion,
                    buildVersion: rollbackInfo.buildVersion,
                    rollbackAppVersion: rollbackInfo.rollbackAppVersion,
                    rollbackBuildVersion: rollbackInfo.rollbackBuildVersion,
                    rollbackMethod: rollbackInfo.rollbackMethod,
                    timestamp: rollbackInfo.lastUpdated,
                });
            }, function(error) {
                if (!dataUtil.isString(error)) {
                    error = JSON.stringify(error);
                }

                onFailed(error);
            });
            return deferred.promise;
        }

        function getAppUpdateHandler() {
            return {
                setUpdateUrl: setUpdateUrl,
                getUpdateUrl: getUpdateUrl,
                executeUpdate: executeUpdate,
                isSupported: checkAppUpdateSupport,
                setUpdateConfiguration: setAppUpdateConfiguration,
            };
        }

        function getScreenshotCapability() {
            return true;
        }

        function startServer(config) {
            config = config || {port: null};
            if (!_server) {
                _server = new BrightsignWebSocketServer();
            }

            config.sendBsMessage = sendBSMessage;
            config.useUdp = config.forceUdp || !isFirmware8AndAbove;
            _server.start(config);
        }

        function stopServer() {
            if (!_server) {
                return;
            }

            _server.stop();
            _server = undefined;
        }

        function subscribeServerMessages(callback) {
            if (!_server) {
                return;
            }

            _server.subscribeToUpdates(callback);
        }

        function unSubscribeServerMessages(callback) {
            if (!_server) {
                return;
            }

            _server.unsubscribeFromUpdates(callback);
        }

        function isTransparentRequired(color) {
            return !isFirmware8AndAbove && color === SYSTEM_DATA.LAYOUT_BACKGROUND_TRANSPARENT_COLOR;
        }

        function setMaxVideoLayers(maxVideoLayers) {
            var videoLayers = parseInt(maxVideoLayers, 10);
            if (!dataUtil.isNumber(videoLayers)) {
                videoLayers = defaultVideoLayers;
            } else {
                videoLayers = Math.max(videoLayers, MIN_VIDEO_LAYER);
                videoLayers = Math.min(videoLayers, MAX_VIDEO_LAYER);
            }

            videoLayers = videoLayers.toString();
            if (deviceManager.features.maxVideoLayers === videoLayers) {
                return false;
            }

            deviceManager.features.maxVideoLayers = videoLayers;
            getNativeLocalStorage().setItem(maxVideoLayersKey, videoLayers);
            return true;
        }

        function screenshot() {
            logService.debug(LOG_KEY, 'Attempting to take screenshot');
            var deferred = $q.defer();
            var result = {
                isSuccess: false,
                filePath: '',
            };
            var onScreenshotComplete = function(res) {
                var screenshotPath = (res || {}).result;
                result = {
                    isSuccess: Boolean(screenshotPath),
                    filePath: screenshotPath || '',
                };
                if (!result.isSuccess) {
                    logService.error(LOG_KEY, 'Failed to take screenshot: ' + JSON.stringify(res));
                    deferred.reject(res);
                    return;
                }

                deferred.resolve(result);
            };

            sendBSMessage({
                command: 'screenshot'
            }, onScreenshotComplete);
            return deferred.promise;
        }

        function createBrowser(url, onLoadStop, onLoadError) {
            if (!url) {
                return;
            }

            var browser = {
                id: IN_APP_BROWSER_ID_PREFIX + Date.now(),
            };
            browser.dispose = disposeInAppBrowser.bind(null, inAppBrowsers, browser);
            inAppBrowserLoadStopCallback = onLoadStop;
            inAppBrowserLoadErrorCallback = onLoadError;

            sendBSMessage({
                command: 'createInAppBrowser',
                url: url
            });

            inAppBrowsers.push(browser);
            return browser;
        }

        function disposeInAppBrowser(browserCollection, browser) {
            for (var index = 0; index < browserCollection.length; index++) {
                if (browser.id !== browserCollection[index].id) {
                    continue;
                }

                browserCollection.splice(index, 1);
                break;
            }

            if (browserCollection.length > 0) {
                return;
            }

            inAppBrowserLoadStopCallback = undefined;
            inAppBrowserLoadErrorCallback = undefined;
            sendBSMessage({
                command: 'disposeInAppBrowser'
            });
        }

        function getNativeLocalStorage() {
            return $window.localStorage;
        }

        function reset() {
            // clean cached max video layers
            getNativeLocalStorage().removeItem(maxVideoLayersKey);
        }

        function getRuntimeEnvironments() {
            return ['brightsign'];
        }

        function getDefaultDrive() {
            var defer = $q.defer();
            var callback = function(response) {
                defer.resolve(!response || !response.result ? undefined : response.result);
            };

            sendBSMessage({
                command: 'getDefaultDrive'
            }, callback);

            return defer.promise;
        }

        function clearChromiumCache() {
            sendBSMessage({
                command: 'clearChromiumCache'
            });
        }

        // this should be called after sending the screenshot command
        function clearScreenshotCache() {
            var deferred = $q.defer();
            removeDirectory(SCREENSHOT_CACHE_PATH).then(function() {
                logService.debug(LOG_KEY, 'Screenshot cache cleared');
                deferred.resolve();
            }, function(error) {
                logService.warn(LOG_KEY, 'Failed to clear screenshot cache: ' + JSON.stringify(error));
                deferred.resolve();
            });

            return deferred.promise;
        }

        function getCommandHandler() {
            var commandHandler = {};
            commandHandler[OPERATION_COMMAND.SCREENSHOT] = {
                execute: function() {
                    var deferred = $q.defer();
                    screenshot().then(function(result) {
                        if (!result.filePath) {
                            deferred.reject('Invalid screenshot result');
                            return;
                        }

                        deferred.resolve(result.filePath);
                    }, function(error) {
                        // Convert error to string message if it's not already a string
                        var errorMessage = 'Invalid screenshot result';

                        if (dataUtil.isString(error)) {
                            errorMessage = error;
                        }

                        deferred.reject(errorMessage);
                    });
                    return deferred.promise;
                },
                postExecution: clearScreenshotCache,
            };

            return commandHandler;
        }

        // Define the instance methods and properties
        BrightsignDriver.prototype = {
            // Public interface
            id: 'browser.brightsign',
            isReady: isReady,
            platform: {
                isReady: isReady,
                reboot: reboot,
                reload: function() {
                    deviceManager.reload();
                },
                getFeatureSupport: getFeatureSupport,
                getModel: getModel,
                getFingerprint: getFingerPrint,
                getAvailableStorage: getAvailableStorage,
                getUsedStorage: getUsedStorage,
                setStartupURL: setStartupURL,
                executePlatformScript: executePlatformScript,
                getAppspaceDeviceType: getAppspaceDeviceType,
                getAppPackageType: getAppspaceDeviceType,
                getAppUpdateCapabilities: getAppUpdateCapabilities,
                getAppUpdateHandler: getAppUpdateHandler,
                getAppRollbackInfo: getRollbackInfo,
                getCommandHandler: getCommandHandler,
                getTransitionSupport: getTransitionSupport,
                getSerialNumber: getSerialNumber,
                getRuntimeEnvironments: getRuntimeEnvironments,
                getFirmware: getFirmware,
                isMeetMinFirmware: isMeetMinFirmware,
                getMinVersionRequired: getMinVersionRequired,
                getDisplayInfo: getDisplayInfo,
                getMemoryUsage: getMemoryUsage,
                getCpuUsage: getCpuUsage,
                getCronJobs: getCronJobs,
                enableVirtualKeyboard: enableVirtualKeyboard,
                clearWebCache: clearWebCache,
                isTransparentRequired: isTransparentRequired,
                reset: reset,
            },
            media: {
                setDisplayRotation: setDisplayRotation,
                getDisplayRotation: getDisplayRotation,
                getMediaDirective: getMediaDirective,
                setCursorVisible: setCursorVisible,
                getCursorVisible: getCursorVisible,
                enableCursorVisibility: enableCursorVisibility,
                getSupportedVideoProtocols: getSupportedVideoProtocols,
                setVideoStretch: setVideoStretch,
                volumeUp: volumeUp,
                volumeDown: volumeDown,
                toggleVolumeMute: toggleVolumeMute,
                setMaxVideoLayers: setMaxVideoLayers,
                createBrowser: createBrowser,
                getVideoAttributes: getVideoAttributes
            },
            file: {
                resolveUrl: resolveUrl,
                resolveRelative: resolveRelative,
                createDirectory: createDirectory,
                checkDirectory: checkDirectory,
                removeDirectory: removeDirectory,
                enumerateDirectory: enumerateDirectory,
                createFile: createFile,
                writeFile: writeFile,
                checkFile: checkFile,
                moveFile: moveFile,
                removeFile: removeFile,
                getDefaultDrive: getDefaultDrive,
            },
            network: {
                downloadHttp: downloadHttp,
                getIpAddress: getIpAddress,
                getMacAddress: getMacAddress,
                startServer: startServer,
                stopServer: stopServer,
                subscribeServerMessages: subscribeServerMessages,
                unSubscribeServerMessages: unSubscribeServerMessages
            },
            remote: {
                registerEvents: registerRemoteEvents,
                unRegisterEvents: unRegisterRemoteEvents,
                isSupported: isRemoteSupported
            }
        };

        // Return the constructor for the class
        return (BrightsignDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('ChromeDriverLocalStorage', ["$q", function($q) {
        if (typeof chrome !== 'object' || !chrome.storage || !chrome.storage.local) {
            return function() { };
        }

        var chromeStorage = chrome.storage.local;
        var keys = [];
        var storage = {};
        var hasInit = false;
        var me;

        var ChromeDriverLocalStorage = function(data) {
            for (var i in data) {
                keys.push(i);
                storage[i] = data[i];
            }

            me = this;
            me.length = keys.length;
        };

        // Loads all chrome storage into memory
        ChromeDriverLocalStorage.prototype.init = function() {
            var defer = $q.defer();

            if (hasInit) {
                defer.resolve('already initialized');
                return defer.promise;
            }

            // when file system ready, notify all
            chrome.storage.local.get(null, function(data) {
                for (var i in data) {
                    keys.push(i);
                    storage[i] = data[i];
                }

                // hasInit = true;
                me.length = keys.length;
                defer.resolve('initialized');
                hasInit = true;
            });
            return defer.promise;
        };

        ChromeDriverLocalStorage.prototype.getItem = function(key) {
            return storage[key];
        };

        ChromeDriverLocalStorage.prototype.setItem = function(key, value) {
            var obj = {};
            obj[key] = value;

            // avoid duplicate key
            if (storage[key] === undefined) {
                keys.push(key);
            }

            storage[key] = value;

            chromeStorage.set(obj);
            me.length = keys.length;
            return storage;
        };

        ChromeDriverLocalStorage.prototype.removeItem = function(key) {
            delete storage[key];
            for (var i in keys) {
                if (keys[i] === key) {
                    keys.splice(i, 1);
                }
            }

            chromeStorage.remove(key);
            me.length = keys.length;
            return storage;
        };

        ChromeDriverLocalStorage.prototype.key = function(index) {
            return keys[index];
        };

        return ChromeDriverLocalStorage;
    }]);

    app.factory('BrowserChromeAppDriver', ["$window", "$q", "$document", "$timeout", "pathService", "FileUtil", "FileEntry", "FileError", "SYSTEM_DATA", "FEATURE_CODE", "ChromeDriverLocalStorage", "RemoteEvent", "REMOTEVCODE", "REMOTEEVENTTYPE", "ChromeSocketServer", "VersionCompare", "logService", "RemoteUtil", "interactivityManager", "mediaUtil", "dataUtil", "TimeoutUtil", function($window, $q, $document, $timeout,
        pathService, FileUtil, FileEntry, FileError, SYSTEM_DATA, FEATURE_CODE,
        ChromeDriverLocalStorage, RemoteEvent, REMOTEVCODE, REMOTEEVENTTYPE, ChromeSocketServer, VersionCompare,
        logService, RemoteUtil, interactivityManager, mediaUtil, dataUtil, TimeoutUtil) {
        // filter this, if ChromeApp library not detected
        if (typeof chrome !== 'object' || !chrome.system) {
            return function() { };
        }

        // init file system
        var fs = null;
        var storage = null;
        var isKioskSession = false;
        var keyTimeoutId = null;
        var minVersionRequired = '46';
        var policy = null;
        var INVALID_INTERFACE_NAMES = new RegExp(/(br)\d|^(arc)|^(lo)|^(veth)/);
        var backgroundReload;
        var backgroundReboot;
        var remoteServiceEventHandler = null;
        var postMessagingSubscriber;
        var appPostMessageWindow;
        var appPostMessagingOrigin;
        var shouldMuteVideo = 'yes';
        var logKey = 'appspace.core.driver.chrome';
        var isEnterpriseSerialNumberAvailable = false;
        var printers = [];
        var PRINTABLE_MIME_TYPE = 'image/png';
        var PRINTING_PAPER_LABEL_NAME_WIDTH_REGEX = /W(\d+)/;
        var PRINTING_PAPER_LABEL_NAME_HEIGHT_REGEX = /H(\d+)/;
        var DEFAULT_PRINTING_CONFIGURATION = {
            PAPER_WIDTH: 62,
            PRINTABLE_SIZE_HEIGHT: 180,
            PRINTABLE_SIZE_WIDTH: 280,
        };

        // Define the constructor function.
        function BrowserChromeAppDriver() { }

        var readyPromise = $q.defer();
        function isReady() {
            return readyPromise.promise;
        }

        function getPrinters() {
            if (!chrome.printing || !chrome.printing.getPrinters) {
                return $q.reject('chrome.printing.getPrinters API is unavailable');
            }

            var deferred = $q.defer();
            chrome.printing.getPrinters(function(result) {
                if (dataUtil.isArray(result) && result.length > 0) {
                    deferred.resolve(result);
                    return;
                }

                deferred.reject('No printers found');
            });
            return deferred.promise;
        }

        function refreshPrintersAsync() {
            var $defer = $q.defer();
            getPrinters().then(function(printerList) {
                printers = printerList || [];
                $defer.resolve();
            }, $defer.reject);

            return $defer.promise;
        }

        function findPrinterAsync(uri) {
            var printerMatch;
            if (!uri) {
                logService.warn(logKey, 'No printer uri provided');
                return $q.resolve(printerMatch);
            }

            var searchByUri = function(uri) {
                for (var i = 0; i < printers.length; i++) {
                    var printer = printers[i];
                    if (!printer || !dataUtil.includesString(uri, printer.uri) || !printer.id) {
                        continue;
                    }

                    return $angular.copy(printer);
                }
            };

            printerMatch = searchByUri(uri);

            if (printerMatch) {
                return $q.resolve(printerMatch);
            }

            var $defer = $q.defer();
            logService.warn(logKey, 'Unable to find printer by uri: `' + uri + '`. Refreshing printers...');
            refreshPrintersAsync().then(function() {
                printerMatch = searchByUri(uri);
                if (printerMatch) {
                    $defer.resolve(printerMatch);
                    return;
                }

                var errorMessage = 'Unable to find printer by uri: `' + uri + '` after refreshing printers.';
                logService.error(logKey, errorMessage);
                $defer.reject(errorMessage);
            }, function(error) {
                logService.error(logKey, 'Failed to refresh printers: ', error);
                $defer.reject(error);
            });

            return $defer.promise;
        }

        function init() {
            // rejecting all promises, if timeout
            storage = new ChromeDriverLocalStorage();

            // return if there is no storage object
            // to handle in android
            if (!storage.init || !chrome.runtime || !chrome.runtime.getBackgroundPage) {
                readyPromise.reject();
                return;
            }

            isEnterpriseSerialNumberAvailable = Boolean(chrome.enterprise &&
                chrome.enterprise.deviceAttributes &&
                chrome.enterprise.deviceAttributes.getDeviceSerialNumber);

            // retrieve system parameter from background.js
            chrome.runtime.getBackgroundPage(function(res) {
                isKioskSession = res.isKioskSession;
                backgroundReload = isKioskSession && chrome.runtime && chrome.runtime.reload ? chrome.runtime.reload : res.restart;
                backgroundReboot = isKioskSession && chrome.runtime && chrome.runtime.restart ? chrome.runtime.restart : reload;
                // check whether we can play the video with audio if we're not in kiosk session
                var checkVideoPromise = $q.defer();
                if (!isKioskSession) {
                    mediaUtil.isVideoPlayableWithAudio().then(function(res) {
                        shouldMuteVideo = res ? 'no' : 'yes';
                        checkVideoPromise.resolve();
                    });
                } else {
                    shouldMuteVideo = 'no';
                    checkVideoPromise.resolve();
                }

                var onFileSystemRequested = function(fileSystem) {
                    fs = fileSystem;
                    readyPromise.resolve();
                };

                var onFileSystemRequestFailed = function() {
                    readyPromise.reject();
                };

                var onStorageInit = function() {
                    window.webkitRequestFileSystem(
                        window.PERSISTENT,
                        0,
                        onFileSystemRequested,
                        onFileSystemRequestFailed);
                };

                var initStorage = function() {
                    // when file system ready, notify all
                    storage.init().then(onStorageInit);
                };

                checkVideoPromise.promise.then(initStorage);
            });

            getPrinters().then(function(printerList) {
                printers = printerList || [];
            }, $angular.noop);
        }

        init();

        // convert cordova file entry to cordova
        function createFileEntry(entry) {
            return new FileEntry({
                name: entry.name,
                path: entry.toURL(),
                isDirectory: entry.isDirectory
            });
        }

        function createFileError(error, path) {
            var message;

            if (typeof error === 'string') {
                message = error;
            } else if (typeof error === 'object') {
                if (error['message'] !== undefined && error['message'] !== '') {
                    message = error['message'];
                } else if (error['name']) {
                    message = error['name'];
                }
            }

            return new FileError({
                message: message,
                path: path
            });
        }

        function getSerialNumber() {
            if (!isEnterpriseSerialNumberAvailable) {
                return $q.reject('chrome.enterprise, chrome.enterprise.deviceAttributes, or chrome.enterprise.getSerialNumber is not available');
            }

            var promise = $q.defer();
            var successCallback = function(serialNumber) {
                if (!serialNumber) {
                    promise.reject('No serial number');
                    return;
                }

                promise.resolve(serialNumber);
            };

            chrome.enterprise.deviceAttributes.getDeviceSerialNumber(successCallback);

            return promise.promise;
        }

        // Get the fingerprint for the device
        function getFingerprint() {
            if (!chrome.storage) {
                return $q.reject({
                    message: 'chrome storage is not available'
                });
            }

            var promise = $q.defer();
            var resolveFingerprint = function(fingerprintValue) {
                promise.resolve({
                    fingerprint: fingerprintValue,
                    type: isEnterpriseSerialNumberAvailable ?
                        SYSTEM_DATA.FINGERPRINT_TYPE.HARDWARE :
                        SYSTEM_DATA.FINGERPRINT_TYPE.GENERATED
                });
            };

            chrome.storage.local.get('chromeAppPlatformFingerprint', function(result) {
                if (result.chromeAppPlatformFingerprint) {
                    resolveFingerprint(result.chromeAppPlatformFingerprint);
                    return;
                }

                var fingerprint;
                getSerialNumber().then(function(serialNumber) {
                    fingerprint = serialNumber;
                }, function(errorMessage) {
                    isEnterpriseSerialNumberAvailable = false;
                    logService.info(logKey, errorMessage, ', generating uuid now.');
                    fingerprint = uuid.v1();
                }).finally(function() {
                    chrome.storage.local.set({
                        chromeAppPlatformFingerprint: fingerprint
                    });
                    resolveFingerprint(fingerprint);
                });
            });

            return promise.promise;
        }

        // Get Chrome OS version
        function getFirmware() {
            var promise = $q.defer();
            var index = navigator.userAgent.indexOf('Chrome');
            var version = navigator.userAgent.substring(index + 7).split(' ');
            promise.resolve(version[0] ? version[0] : '-');
            return promise.promise;
        }

        // get minimmmum version required
        function getMinVersionRequired() {
            return minVersionRequired;
        }

        // Get if firmware meet minimum requirement
        function isMeetMinFirmware() {
            var promise = $q.defer();
            var vc = new VersionCompare();
            getFirmware().then(function(fw) {
                var res = vc.compare(fw, minVersionRequired);
                if (res) {
                    promise.resolve();
                } else {
                    var err = {};
                    err.minFWVersion = getMinVersionRequired();
                    err.currentFWVersion = fw;
                    promise.reject(err);
                }
            });

            return promise.promise;
        }

        // Get CPU usage
        function getCpuUsage() {
            var defer = $q.defer();
            if (!chrome.system.cpu || !chrome.system.cpu.getInfo) {
                defer.resolve({
                    cpuUsage: 0
                });
                return;
            }

            chrome.system.cpu.getInfo(function(info) {
                var usage = 0;
                var totalCore = 0;
                for (var i = 0; i < info.processors.length; i++) {
                    var total = info.processors[i].usage.total;
                    // In certain case (specifically on Aopen devices),
                    // the return of one of the processors total is 0,
                    // which technically means that processor core is not being used.
                    if (total) {
                        usage = usage + 1 - info.processors[i].usage.idle / total;
                        totalCore++;
                    }
                }

                if (totalCore) {
                    usage = usage * 100 / totalCore;
                }

                defer.resolve({
                    cpuUsage: usage
                });
            });

            return defer.promise;
        }

        function getMemoryUsage() {
            var promise = $q.defer();

            if (!chrome.system.memory || !chrome.system.memory.getInfo) {
                promise.resolve({
                    memoryUsage: 0
                });
            } else {
                chrome.system.memory.getInfo(function(info) {
                    promise.resolve({
                        capacity: info.capacity,
                        availableCapacity: info.availableCapacity
                    });
                });
            }

            return promise.promise;
        }

        function getDisplayInfo() {
            var promise = $q.defer();

            if (!chrome.system.display || !chrome.system.display.getInfo) {
                promise.resolve({
                    display: 0,
                });
            } else {
                chrome.system.display.getInfo(function(info) {
                    var displayInfo = [];
                    for (var i = 0; i < info.length; i++) {
                        displayInfo.push({x: info[i].bounds.width, y: info[i].bounds.height});
                    }

                    promise.resolve(displayInfo);
                });
            }

            return promise.promise;
        }

        // Get policy
        function getAppPolicy() {
            var defer = $q.defer();

            if (policy) {
                defer.resolve(policy);
            } else {
                chrome.storage.managed.get(null, function(res) {
                    if (res && res.registration) {
                        res.registration.source = 'chrome';
                    }

                    defer.resolve(res);
                });
            }

            return defer.promise;
        }

        // Set app policy in memory
        function setAppPolicy(p) {
            if (typeof (p) === 'object') {
                policy = p;
            }
        }

        // Clear the app policy
        function clearAppPolicy() {
            policy = null;
        }

        // convert relative path to chrome app file system path
        function resolveUrl(path) {
            // already absolute path
            if (path.indexOf('filesystem:chrome-extension://') > -1) {
                return path;
            }

            // get path
            // path = decodeURIComponent(path);
            path = pathService.join(path);

            return 'filesystem:' + chrome.runtime.getURL('/persistent' + path);
        }

        // map absolute path to relative path for i/o operation
        function resolveRelative(path) {
            var baseURL = chrome.runtime.getURL('/persistent');
            var index = path.indexOf(baseURL);

            if (index > -1) {
                path = path.substring(index + baseURL.length);
            }

            return pathService.join(path);
        }

        // file functions
        function createDirectory(path) {
            var defer = $q.defer();
            path = resolveRelative(path);
            var fileName = FileUtil.getBaseName(path);

            // check directory and create it
            checkDirectory(path).then(function(res) {
                if (res.exists) {
                    var fileEntry = new FileEntry({
                        name: fileName,
                        isDirectory: true,
                        path: resolveUrl(path)
                    });

                    defer.resolve({
                        created: false,
                        entry: fileEntry
                    });
                } else {
                    var dirs = path.split('/');
                    dirs = dirs.reverse();

                    var createDir = function(path, callback) {
                        // console.log(dirs);
                        var folderName = dirs.pop();

                        // skip if empty
                        if (folderName === '') {
                            createDir(path, callback);
                            return;
                        }

                        path = pathService.join(path, folderName);
                        // console.log('dirPath >> ', path, folderName);

                        // get directory
                        fs.root.getDirectory(path, {
                            create: true
                        }, function(dirEntry) {
                            if (dirs.length < 1) {
                                callback(dirEntry);
                            } else {
                                createDir(path, callback);
                            }
                        }, function(err) {
                            if (err) {
                                if (err['name'] === 'TypeMismatchError') {
                                    // reconstruct error, as system fileerror doesn't provide set method
                                    err = {
                                        message: 'Directory exists on location.',
                                        name: 'TypeMismatchError'
                                    };
                                }
                            }

                            defer.reject(createFileError(err, path));
                        });
                    };

                    // init from root path
                    createDir('/', function(dirEntry) {
                        var entry = createFileEntry(dirEntry);
                        defer.resolve({
                            entry: entry,
                            created: true
                        });
                    });
                }
            }, defer.reject);

            return defer.promise;
        }

        function checkDirectory(path) {
            var defer = $q.defer();
            path = resolveRelative(path);

            fs.root.getDirectory(path, {}, function(dirEntry) {
                defer.resolve({
                    path: resolveUrl(path),
                    exists: true
                });
            }, function(err) {
                defer.resolve({
                    exists: false
                });
            });
            return defer.promise;
        }

        function removeDirectory(path) {
            var defer = $q.defer();
            path = resolveRelative(path);

            fs.root.getDirectory(path, {}, function(dirEntry) {
                dirEntry.removeRecursively(function() {
                    defer.resolve();
                }, defer.reject);
            }, defer.reject);

            return defer.promise;
        }

        function createFile(path) {
            var defer = $q.defer();
            path = resolveRelative(path);
            var fileName = FileUtil.getBaseName(path);

            checkFile(path).then(function(res) {
                if (res.exists) {
                    var fileEntry = new FileEntry({
                        name: fileName,
                        path: resolveUrl(path),
                        isDirectory: false
                    });
                    defer.resolve({
                        created: false,
                        entry: fileEntry
                    });
                } else {
                    fs.root.getFile(path, {
                        create: true
                    }, function(fileEntry) {
                        var entry = createFileEntry(fileEntry);
                        defer.resolve({
                            entry: entry,
                            created: false
                        });
                    }, function(err) {
                        if (err) {
                            if (err['name'] === 'TypeMismatchError') {
                                // reconstruct error, as system fileerror doesn't provide set method
                                err = {
                                    message: 'File exists on location.',
                                    name: 'TypeMismatchError'
                                };
                            }
                        }

                        defer.reject(createFileError(err, path));
                    });
                }
            }, function(err) {
                defer.reject(createFileError(err, path));
            });

            return defer.promise;
        }

        function checkFile(path) {
            var defer = $q.defer();
            path = resolveRelative(path);

            fs.root.getFile(path, {}, function(fileEntry) {
                defer.resolve({
                    path: resolveUrl(path),
                    exists: true
                });
            }, function(err) {
                defer.resolve({
                    exists: false
                });
            });

            return defer.promise;
        }

        function moveFile(sourcePath, destinationPath) {
            var defer = $q.defer();
            sourcePath = resolveRelative(sourcePath);
            destinationPath = resolveRelative(destinationPath);

            var newDirPath = FileUtil.getDirPath(destinationPath);
            var newFileName = FileUtil.getBaseName(destinationPath);

            // todo - should auto create directory
            fs.root.getFile(sourcePath, {}, function(srcEntry) {
                fs.root.getDirectory(newDirPath, {}, function(destEntry) {
                    srcEntry.moveTo(destEntry, newFileName, function(fileEntry) {
                        var entry = createFileEntry(fileEntry);
                        defer.resolve(entry);
                    }, function(err) {
                        defer.reject(createFileError(err, destinationPath));
                    });
                }, function(err) {
                    defer.reject(createFileError(err, destinationPath));
                });
            }, function(err) {
                defer.reject(createFileError(err, destinationPath));
            });

            return defer.promise;
        }

        function removeFile(path) {
            var defer = $q.defer();
            path = resolveRelative(path);

            fs.root.getFile(path, {}, function(fileEntry) {
                fileEntry.remove(function() {
                    defer.resolve();
                }, defer.reject);
            }, defer.reject);
            return defer.promise;
        }

        function writeFile(path, content) {
            var defer = $q.defer();
            path = resolveRelative(path);
            var dirPath = FileUtil.getDirPath(path);

            // check directory if not exists
            checkDirectory(dirPath).then(function(res) {
                if (res.exists) {
                    fs.root.getFile(path, {create: true}, function(fileEntry) {
                        var truncated = false;

                        // Create a FileWriter object for our FileEntry (log.txt).
                        fileEntry.createWriter(function(fileWriter) {
                            fileWriter.onwriteend = function(e) {
                                if (!truncated) {
                                    this.truncate(this.position);
                                    truncated = true;
                                    defer.resolve();
                                }
                            };

                            fileWriter.onerror = function(e) {
                                defer.reject(createFileError(e, path));
                            };

                            // blob only accept array, convert it
                            if (!$angular.isArray(content)) {
                                content = [content];
                            }

                            // Create a new Blob and write it to log.txt.
                            var blob = new Blob(content, {
                                type: 'text/plain'
                            });

                            fileWriter.write(blob);
                        }, function(err) {
                            defer.reject(createFileError(err, path));
                        });
                    }, function(err) {
                        defer.reject(createFileError(err, path));
                    });
                } else {
                    // directory not found
                    defer.reject(createFileError('Directory not found', path));
                }
            });

            return defer.promise;
        }

        function enumerateDirectory(path) {
            var defer = $q.defer();
            path = resolveRelative(path);

            fs.root.getDirectory(path, {}, function(fileEntry) {
                var dirReader = fileEntry.createReader();

                dirReader.readEntries(function(entries) {
                    var fileEntries = [];
                    for (var i in entries) {
                        var fileEntry = entries[i];
                        var entry = createFileEntry(fileEntry);

                        fileEntries.push(entry);
                    }

                    defer.resolve(fileEntries);
                }, defer.reject);
            }, defer.reject);

            return defer.promise;
        }

        function downloadHttp(remotePath, localPath, downloadItem) {
            var defer = $q.defer();
            localPath = resolveRelative(localPath);
            var dirPath = FileUtil.getDirPath(localPath);

            var rejectPromise = function(res) {
                if (res.type && res.type == 'error' && !res.code && res.total) {
                    res.requirereboot = true;
                    res.message = 'Possible XHR failure.';
                }

                defer.reject(res);
                xhr = null;
            };

            var xhr = new XMLHttpRequest();
            downloadItem.cancelDownload = function() {
                this.abort();
            }.bind(xhr);
            xhr.open('GET', remotePath, true);
            xhr.responseType = 'blob';
            xhr.onload = function() {
                if (this.status !== 200) {
                    defer.reject({
                        code: this.status,
                        message: this.statusText
                    });
                    return;
                }

                createDirectory(dirPath).then(function() {
                    fs.root.getFile(localPath, {
                        create: true
                    }, function(fileEntry) {
                        fileEntry.createWriter(function(writer) {
                            writer.onwrite = function() {
                                var file = createFileEntry(fileEntry);
                                defer.resolve(file);
                                xhr = null;
                            };

                            writer.onerror = rejectPromise;
                            writer.write(xhr.response);
                        }, rejectPromise);
                    }, rejectPromise);
                }, rejectPromise);
            };

            xhr.onerror = rejectPromise;
            xhr.send();
            return defer.promise;
        }

        // get used and total disk space
        function getAvailableStorage() {
            var defer = $q.defer();
            navigator.webkitPersistentStorage.queryUsageAndQuota(
                function(usedBytes, grantedBytes) {
                    defer.resolve({
                        used: usedBytes,
                        total: grantedBytes
                    });
                }, defer.reject);
            return defer.promise;
        }

        // Get the IP Address for the device
        function getIpAddress() {
            if (!chrome.system.network || !chrome.system.network.getNetworkInterfaces) {
                return $q.resolve({address: '127.0.0.1'});
            }

            var defer = $q.defer();
            var address;
            chrome.system.network.getNetworkInterfaces(function(interfaces) {
                for (var i = 0; i < interfaces.length; i++) {
                    var ip = interfaces[i];
                    if (!ip.name.match(INVALID_INTERFACE_NAMES) && ip.address.indexOf('.') > 0) {
                        address = ip.address;
                        break;
                    }
                }

                defer.resolve({
                    address: address || '127.0.0.1'
                });
            });

            return defer.promise;
        }

        // Returns the support level for a specific feature
        function getFeatureSupport(feature) {
            feature = feature.toLowerCase();
            switch (feature) {
                case 'input.keyboard':
                    return 'hardware';
                case 'input.mouse':
                    return 'hardware';
                case 'file.localstorage':
                    return 'yes';
                case 'rendering.css3-transitions':
                    return 'yes';
                case 'network.allowexternalresources':
                    return 'no';
                case 'media.hls.supported':
                    return 'no';
                case 'media.maxvideolayers':
                    return '99';
                case 'media.flash.local.playback':
                    return 'no';
                case 'network.maxconcurrentdownloads':
                    return 6;
                case 'media.card.onlineforcestream':
                    return 'yes';
                case 'media.video.should.mute':
                    // Videos running in kiosk session shouldn't have the block autoplay issue
                    return shouldMuteVideo;
                case FEATURE_CODE.MEDIA_REQUESTS_ALLOWED:
                    return true;
                default:
                    return null;
            }
        }

        // Returns the local storage object
        function getLocalStorage() {
            return storage;
        }

        // Returns whether the device supports remote control events
        function isRemoteSupported() {
            return true; // Yes as we map keyboard to use remote events
        }

        function createRemoteEvent(e) {
            var re = new RemoteEvent(e);
            switch (e.which) {
                case 166: // FireTV back button
                    re.vcode = re.shiftKey ? REMOTEVCODE.system : REMOTEVCODE.back;
                    break;
                case 16: // Back on Asus remote
                    re.vcode = REMOTEVCODE.back;
                    break;
                case 172: // Fire TV menu
                    re.vcode = REMOTEVCODE.system;
                    break;
            }

            // if device didn't implement
            if (!re.vcode) {
                re = RemoteUtil.processDefaultKeyboardMapping(e);
            }

            return re;
        }

        function cancelKeyTimeoutId() {
            TimeoutUtil.cancel(keyTimeoutId);
            keyTimeoutId = undefined;
        }

        function keydownHandler(e) {
            // delay to handle multiple event called in one key press
            cancelKeyTimeoutId();
            if (e.which == 16) {
                keyTimeoutId = $timeout(function() {
                    cancelKeyTimeoutId();
                    proceed();
                }, 5);
            } else {
                proceed();
            }

            function proceed() {
                var re = createRemoteEvent(e);
                if (re == null) {
                    return;
                }

                re.eventType = REMOTEEVENTTYPE.buttonDown;
                // Notifice remote driver service
                if (remoteServiceEventHandler) {
                    remoteServiceEventHandler(re);
                }

                // Prevent default if needed
                if (re.isDefaultPrevented()) {
                    e.preventDefault(); // prevent the default action (scroll / move caret)
                }
            }
        }

        function keyupHandler(e) {
            // delay to handle multiple event called in one key press
            cancelKeyTimeoutId();
            if (e.which == 16 && !e.key) {
                keyTimeoutId = $timeout(function() {
                    cancelKeyTimeoutId();
                    proceed();
                }, 5);
            } else {
                proceed();
            }

            function proceed() {
                var re = createRemoteEvent(e);
                if (re == null) {
                    return;
                }

                re.eventType = REMOTEEVENTTYPE.buttonUp;
                // Notifice remote driver service
                if (remoteServiceEventHandler) {
                    remoteServiceEventHandler(re);
                }

                // Prevent default if needed
                if (re.isDefaultPrevented()) {
                    e.preventDefault(); // prevent the default action (scroll / move caret)
                }
            }
        }

        // Registers a callback for remote events and returns an id to unsubscribe later
        function registerRemoteEvents(callback) {
            remoteServiceEventHandler = callback;
            $document.keydown(keydownHandler);
            $document.keyup(keyupHandler);
        }

        // Unregisters an existing callback for remote events
        function unRegisterRemoteEvents(id) {
            $document.unbind('keydown', keydownHandler);
            $document.unbind('keyup', keyupHandler);
        }

        // Returns a list of supported V codes
        function getSupportedRemoteVCodes() {
            var vcodes = [];
            return vcodes;
        }

        // Media
        function getMediaDirective(elementType, options) {
            options = options || {};
            options.requirelocalaccess = options.requirelocalaccess == 'true' || options.requirelocalaccess === true;
            var res = {
                directive: elementType,
                attributes: ''
            };
            // load using webview if we're streaming the content
            if (elementType == 'iframe' && !options.requirelocalaccess) {
                res.directive = 'webview';
                res.attributes = 'partition="trusted" autosize="off" minwidth="1" minheight="1" allowtransparency ';
                res.enablelayer = options.enablewebframelayer == 'true';
            }

            return res;
        }

        function setDisplayRotation(rotation) {
            var defer = $q.defer();
            // 0 - landscape
            // 90 - portrait
            // 180 - upside down landscape
            // 270 - upside portrait

            // if rotation value invalid, don't execute
            if (rotation !== 0 && rotation !== 90 && rotation !== 180 && rotation !== 270) {
                defer.reject('invalid rotation value:' + rotation);
            } else {
                // set each display rotation
                chrome.system.display.getInfo(function(displays) {
                    // get all display ids
                    for (var d in displays) {
                        var id = displays[d].id;

                        // set to new rotation
                        chrome.system.display.setDisplayProperties(id, {
                            rotation: rotation
                        },
                            function() {
                                defer.resolve();
                            });
                    }
                });
            }

            return defer.promise;
        }

        function getDisplayRotation() {
            var defer = $q.defer();

            // supporting only single display for now
            chrome.system.display.getInfo(function(displays) {
                if (displays.length <= 0) {
                    defer.reject();
                    return;
                }

                defer.resolve({rotation: displays[0].rotation});
            });
            return defer.promise;
        }

        // Reloads the application
        function reload() {
            if (backgroundReload) {
                backgroundReload();
            }
        }

        // Restarts the application
        function reboot() {
            if (backgroundReboot) {
                backgroundReboot();
            }
        }

        function getAppspaceDeviceType() {
            return 'CHROMEOS';
        }

        // script, defer
        // put exec id, validate so it doesn't double job
        function execScript(script, opts) {
            var execFile = '/_exec.js';
            var command = 'function _exec($eval){' + script + '}';
            var async = opts.async;
            var id = opts.id;

            var cleanup = function() {
                // cleanup
                var head = document.getElementsByTagName('head')[0];
                var execScript = document.getElementById('_exec_script');
                head.removeChild(execScript);
                _exec = null;
            };

            var callback = function() {
                // fire event
                if (typeof (_exec) === 'undefined' || _exec === null) {
                    window.$eval.error('No injected eval function found.', id);
                    return;
                }

                // execute event
                // test - fail eval and eval
                if (!async) {
                    try {
                        var result = _exec();
                        window.$eval.success(result, id);
                        cleanup();
                    } catch (e) {
                        window.$eval.error(e, id);
                        cleanup();
                    }
                } else {
                    try {
                        _exec({
                            success: function(result) {
                                window.$eval.success(result, id);
                                cleanup();
                            },
                            error: function(err) {
                                window.$eval.error(err, id);
                                cleanup();
                            }
                        });
                    } catch (e) {
                        window.$eval.error(e, id);
                        cleanup();
                    }
                }
            };

            var appendScript = function() {
                var path = resolveUrl(execFile);
                var head = document.getElementsByTagName('head')[0];
                var scriptSrc = document.createElement('script');
                scriptSrc.setAttribute('id', '_exec_script');
                scriptSrc.type = 'text/javascript';
                scriptSrc.src = path;
                scriptSrc.onreadystatechange = callback;
                scriptSrc.onload = callback;
                head.appendChild(scriptSrc);
            };

            // create javascript file
            createFile(execFile).then(function() {
                writeFile(execFile, command).then(function() {
                    appendScript();
                });
            });
        }

        var _server;
        function startServer(config) {
            _server = new ChromeSocketServer();
            _server.start(config.port);
        }

        function stopServer() {
            if (!_server) {
                return;
            }

            _server.stop();
        }

        function subscribeServerMessages(callback) {
            if (!_server) {
                return;
            }

            _server.subscribeToUpdates(callback);
        }

        function unSubscribeServerMessages(callback) {
            if (!_server) {
                return;
            }

            _server.unsubscribeFromUpdates(callback);
        }

        function getMaxLogDays() {
            // clear all logs up to 7 days ago.
            return 7;
        }

        function pingDevice() {
            var promise = $q.defer();
            try {
                if (isKioskSession) {
                    logService.debug(logKey, 'ping device');
                    // restart after 5 minutes (300 seconds)
                    chrome.runtime.restartAfterDelay(300, function(e) {
                        logService.debug(logKey, 'successfully ping device:', e);
                        promise.resolve(true);
                    });
                } else {
                    logService.debug(logKey, 'unable to ping device');
                    promise.reject({error: 'no chrome api'});
                }
            } catch (e) {
                logService.error(logKey, 'error when running pingDevice:', e);
                promise.reject({error: e});
            }

            return promise.promise;
        }

        function startPostMessaging() {
            if (!$window.addEventListener) {
                return;
            }

            $window.addEventListener('message', onParentMessage);
        }

        function stopPostMessaging() {
            if (!$window.removeEventListener) {
                return;
            }

            $window.removeEventListener('message', onParentMessage);
        }

        function subscribeToPostMessaging(callback) {
            postMessagingSubscriber = callback;
        }

        function raisePostMessage(data) {
            if (!appPostMessageWindow || !data) {
                return;
            }

            appPostMessageWindow.postMessage(data, appPostMessagingOrigin);
        }

        function onParentMessage(event) {
            if (!postMessagingSubscriber || !event) {
                return;
            }

            var message = event.data ? event.data.message : '';
            if (message.toLowerCase() == 'appspaceapp.webview.postmessage.init') {
                if (appPostMessageWindow) {
                    return;
                }

                appPostMessageWindow = event.source;
                appPostMessagingOrigin = event.origin;
                event.data.message = 'appspaceapp.postmessage.ready';
            }

            postMessagingSubscriber(event);
        }

        function shouldMuteVideoOnLoad() {
            // mute the video if we need to mute by default and user hasn't interacted with the DOM
            return shouldMuteVideo === 'yes' && !interactivityManager.isInteracted();
        }

        function getRuntimeEnvironments() {
            return ['chrome'];
        }

        function getRuntimeError() {
            return chrome.runtime.lastError ?
                chrome.runtime.lastError.message : '';
        }

        function submitPrintJob(jobRequest) {
            var deferred = $q.defer();
            chrome.printing.submitJob(jobRequest, function(response) {
                if (response) {
                    logService.info(logKey, 'Print job submitted:' + JSON.stringify(response));
                    deferred.resolve();
                    return;
                }

                var reason = 'Print job failed: ' + getRuntimeError();
                logService.error(logKey, reason);
                deferred.reject(reason);
            });
            return deferred.promise;
        }

        function createPrintTicket(printerInfo, options) {
            var paperWidth = DEFAULT_PRINTING_CONFIGURATION.PAPER_WIDTH;
            var paperHeight;
            if (options.paperLabelName && dataUtil.isString(options.paperLabelName)) {
                var widthMatch = options.paperLabelName.match(PRINTING_PAPER_LABEL_NAME_WIDTH_REGEX);
                if (widthMatch && widthMatch.length >= 2) {
                    paperWidth = parseInt(widthMatch[1], 10);
                }

                var heightMatch = options.paperLabelName.match(PRINTING_PAPER_LABEL_NAME_HEIGHT_REGEX);
                if (heightMatch && heightMatch.length >= 2) {
                    paperHeight = parseInt(heightMatch[1], 10);
                }
            }

            var mediaSize = findClosestMediaSize(printerInfo, {width: paperWidth, height: paperHeight});
            var ticket = {
                version: '1.0',
                print: {
                    copies: {
                        copies: 1
                    },
                    collate: {
                        collate: false
                    },
                    duplex: {
                        type: 'NO_DUPLEX'
                    },
                    page_orientation: {
                        type: 'LANDSCAPE'
                    },
                    fit_to_page: {
                        type: 'FIT_TO_PAGE'
                    },
                    media_size: {
                        width_microns: mediaSize.width,
                    },
                    color: {
                        type: 'STANDARD_MONOCHROME',
                    },
                    dpi: {
                        horizontal_dpi: 300,
                        vertical_dpi: 300,
                    }
                }
            };
            if (mediaSize.height) {
                ticket.print.media_size.height_microns = mediaSize.height;
            }

            return ticket;
        }

        /**
         * @description Search for closest media size for ticket creation. QL-820NWB has different media size. The ticket need to have exact media size, otherwise ChromeOS will reject the ticket.
         * @param {{capabilities: {printer: {media_size: {option: [{height_microns: number, width_microns: number}]}}}}} printerInfo response from chrome.printing.getPrinterInfo
         * @param {{width: number, height: number?}} paperLabelSize The paper label may not have height, in this case, the height will be undefined.
         * @return {{width: number, height: number?}} Closest media size in microns
         */
        function findClosestMediaSize(printerInfo, paperLabelSize) {
            var paperHeightInMicrons = paperLabelSize.height ? paperLabelSize.height * 1000 : 0;
            var paperWidthInMicrons = paperLabelSize.width * 1000;
            var lowestDifference = Infinity;
            var closestMediaSize = null;
            var mediaSizeOptions = printerInfo.capabilities.printer.media_size.option;

            for (var i = 0; i < mediaSizeOptions.length; i++) {
                var mediaSize = mediaSizeOptions[i];
                var heightMicrons = mediaSize['height_microns'] || 0;
                var widthMicrons = mediaSize['width_microns'];
                if (!widthMicrons) {
                    continue;
                }

                var differences = Math.sqrt(
                    Math.pow(Math.abs(heightMicrons - paperHeightInMicrons), 2) +
                    Math.pow(Math.abs(widthMicrons - paperWidthInMicrons), 2)
                );
                if (differences < lowestDifference) {
                    lowestDifference = differences;
                    closestMediaSize = mediaSize;
                }
            }

            return {width: closestMediaSize.width_microns, height: closestMediaSize.height_microns};
        }

        function adjustPrintoutScale(blob, options) {
            var targetWidth = DEFAULT_PRINTING_CONFIGURATION.PRINTABLE_SIZE_WIDTH;
            var targetHeight = DEFAULT_PRINTING_CONFIGURATION.PRINTABLE_SIZE_HEIGHT;
            if (options.printoutSize &&
                dataUtil.isNumber(options.printoutSize.width) &&
                dataUtil.isNumber(options.printoutSize.height)) {
                targetWidth = parseInt(options.printoutSize.width, 10);
                targetHeight = parseInt(options.printoutSize.height, 10);
            }

            var deferred = $q.defer();
            createImageBitmap(blob).then(function(imageBitmap) {
                var canvas = $document[0].createElement('canvas');
                canvas.width = targetWidth;
                canvas.height = targetHeight;
                var canvasContext = canvas.getContext('2d');
                canvasContext.drawImage(imageBitmap, 0, 0, targetWidth, targetHeight);
                canvas.toBlob(function(scaledBlob) {
                    deferred.resolve(scaledBlob);
                }, blob.type);
            });

            return deferred.promise;
        }

        function print(printerType, options) {
            options = options || {};
            var printOptions = options.printOptions || {};
            var deferred = $q.defer();

            findPrinterAsync(printOptions.printer).then(function(printer) {
                if (!printer) {
                    deferred.reject('No valid printer found');
                    return;
                }

                logService.info(logKey, 'Found printer: ' + JSON.stringify(printer));

                var convertedInput;
                if (options.input) {
                    convertedInput = dataUtil.convertBase64ToBlob(options.input, PRINTABLE_MIME_TYPE);
                }

                if (!convertedInput) {
                    deferred.reject('Invalid print input');
                    return;
                }

                if (!chrome.printing || !chrome.printing.submitJob) {
                    deferred.reject('chrome.printing.submitJob API is unavailable');
                    return;
                }

                getPrinterInfo(printer.id).then(function(printerInfo) {
                    if (!printerInfo) {
                        deferred.reject('Failed to get printer info.');
                        return;
                    }

                    if (printerInfo.status != chrome.printing.PrinterStatus.AVAILABLE) {
                        deferred.reject('Printer is not available. Status: ' + printerInfo.status);
                        return;
                    }

                    printer.printerInfo = printerInfo;

                    adjustPrintoutScale(convertedInput, printOptions).then(function(scaledInput) {
                        var jobRequest = {
                            job: {
                                title: options.title || 'BrowserChromeAppDriver print job',
                                printerId: printer.id,
                                ticket: createPrintTicket(printerInfo, printOptions),
                                contentType: PRINTABLE_MIME_TYPE,
                                document: scaledInput
                            }
                        };

                        submitPrintJob(jobRequest).then(deferred.resolve, deferred.reject);
                    });
                }, function(error) {
                    logService.error(logKey, 'error when printing: ', JSON.stringify(error));
                    deferred.reject(error.message);
                });
            }, function(error) {
                logService.error(logKey, 'Failed to find printer: ' + error);
                deferred.reject(error);
                return;
            });

            return deferred.promise;
        }

        function getPrinterInfo(printerId) {
            var deferred = $q.defer();
            try {
                chrome.printing.getPrinterInfo(printerId, function(printerInfo) {
                    deferred.resolve(printerInfo);
                });
            } catch (error) {
                logService.error(logKey, 'error when getting printer info: ', JSON.stringify(error));
                deferred.reject(error);
            }

            return deferred.promise;
        }

        // Define the instance methods and properties
        BrowserChromeAppDriver.prototype = {
            // Public interface
            id: 'browser.chrome.app',
            isReady: isReady,
            platform: {
                reload: reload,
                reboot: reboot,
                getFingerprint: getFingerprint,
                getCpuUsage: getCpuUsage,
                getAvailableStorage: getAvailableStorage,
                getMemoryUsage: getMemoryUsage,
                getDisplayInfo: getDisplayInfo,
                getFeatureSupport: getFeatureSupport,
                getRuntimeEnvironments: getRuntimeEnvironments,
                getLocalStorage: getLocalStorage,
                getSerialNumber: getSerialNumber,
                getAppPolicy: getAppPolicy,
                setAppPolicy: setAppPolicy,
                clearAppPolicy: clearAppPolicy,
                getAppspaceDeviceType: getAppspaceDeviceType,
                getAppPackageType: getAppspaceDeviceType,
                execScript: execScript,
                getFirmware: getFirmware,
                isMeetMinFirmware: isMeetMinFirmware,
                getMinVersionRequired: getMinVersionRequired,
                getMaxLogDays: getMaxLogDays,
                pingDevice: pingDevice,
                startPostMessaging: startPostMessaging,
                stopPostMessaging: stopPostMessaging,
                subscribeToPostMessaging: subscribeToPostMessaging,
                raisePostMessage: raisePostMessage,
                shouldMuteVideoOnLoad: shouldMuteVideoOnLoad
            },
            file: {
                resolveUrl: resolveUrl,
                resolveRelative: resolveRelative,
                createDirectory: createDirectory,
                checkDirectory: checkDirectory,
                removeDirectory: removeDirectory,
                enumerateDirectory: enumerateDirectory,
                createFile: createFile,
                checkFile: checkFile,
                moveFile: moveFile,
                removeFile: removeFile,
                writeFile: writeFile
            },
            network: {
                downloadHttp: downloadHttp,
                getIpAddress: getIpAddress,
                startServer: startServer,
                stopServer: stopServer,
                subscribeServerMessages: subscribeServerMessages,
                unSubscribeServerMessages: unSubscribeServerMessages
            },
            remote: {
                registerEvents: registerRemoteEvents,
                unRegisterEvents: unRegisterRemoteEvents,
                getSupportedEvents: getSupportedRemoteVCodes,
                isSupported: isRemoteSupported
            },
            media: {
                getMediaDirective: getMediaDirective,
                setDisplayRotation: setDisplayRotation,
                getDisplayRotation: getDisplayRotation,
                print: print,
            }
        };

        // Return the constructor for the class
        return (BrowserChromeAppDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('BrowserGenericDriver', ["$window", "$document", "$q", "$http", "$injector", "logService", "WebRTC", "WebSocketClient", "REMOTEEVENTTYPE", "RemoteUtil", "driverUtil", "FEATURE_CODE", function(
        $window,
        $document,
        $q,
        $http,
        $injector,
        logService,
        WebRTC,
        WebSocketClient,
        REMOTEEVENTTYPE,
        RemoteUtil,
        driverUtil,
        FEATURE_CODE
    ) {
        var platform = $window.navigator.platform;
        var remoteServiceEventHandler = null;
        var policy = null;
        var supportUnsafeEval = undefined;
        var cursorVisible;
        var socketClient;
        var logNamespace = 'appspace.core.hal';
        var pendingSubcriptions = [];
        // Define the constructor function.
        function BrowserGenericDriver() {
            try {
                new Function('return true');
                supportUnsafeEval = true;
            } catch (e) {
                supportUnsafeEval = false;
            }
        }

        function isReady() {
            var defer = $q.defer();
            defer.resolve();
            return defer.promise;
        }

        // Reloads the application
        function reload() {
            // Reloads the page
            $window.location.reload();
        }

        // Restarts the application
        function reboot() {
            // Default behaviour is to reload the page
            reload();
        }

        // Get the platform OS
        function getOS() {
            return platform;
        }

        // Get the browser name
        function getBrowserName() {
            return bowser.name;
        }

        // Get the browser version
        function getBrowserVersion() {
            return bowser.version;
        }

        function getFirmware() {
            var promise = $q.defer();
            promise.resolve('-');
            return promise.promise;
        }

        function getMinVersionRequired() {
            return '-';
        }

        function isMeetMinFirmware() {
            var promise = $q.defer();
            promise.resolve(true);
            return promise.promise;
        }

        // Get the browser engine
        function getBrowserEngine() {
            if (bowser.webkit) {
                return 'webkit';
            } else if (bowser.gecko) {
                return 'gecko';
            } else if (bowser.msie) {
                return 'msie';
            } else if (bowser.msedge) {
                return 'msedge';
            }
        }

        // Get the fingerprint for the device
        function getFingerprint() {
            // To maintain the existing implementation in generic browser
            return driverUtil.getFingerprint('browser-generic.platform.fingerprint');
        }

        // Get the display resolution
        function getDisplayResolution() {
            return {
                width: $window.screen.width,
                height: $window.screen.height
            };
        }

        // Get the IP Address for the device
        function getIpAddress() {
            var defer = $q.defer();
            var defaultIpAddress = '127.0.0.1';
            if (WebRTC) {
                var webRTC = new WebRTC();
                webRTC.getIpAddress(defaultIpAddress).then(
                    function(ipAddress) {
                        defer.resolve(ipAddress);
                    },
                    function(error) {
                        defer.resolve({address: defaultIpAddress});
                    }
                );
            } else {
                defer.resolve({address: defaultIpAddress});
            }

            return defer.promise;
        }

        // Get model name
        function getModel() {
            return (getOS() + ' ' + getBrowserName());
        }

        // Returns the list of supported video codecs (e.g. mpg4, mpg2, vob, etc..)
        function getSupportedVideoCodecs() {
            return $q(function(resolve, reject) {
                setTimeout(
                    function() {
                        var vidElement = document.createElement('video');
                        var codecs = [];
                        if (vidElement.canPlayType) {
                            // Check for MPEG-4 support
                            if (vidElement.canPlayType('video/mp4; codecs="mp4v.20.8"')) {
                                codecs.push('mpeg4');
                            }

                            // Check for h264 support
                            if (vidElement.canPlayType('video/mp4; codecs="avc1.42E01E"') ||
                                vidElement.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"')) {
                                codecs.push('h264');
                            }

                            // Check for Ogg support
                            if (vidElement.canPlayType('video/ogg; codecs="theora"')) {
                                codecs.push('ogg');
                            }

                            // Check for Vorbis support
                            if (vidElement.canPlayType('video/ogg; codecs="vorbis"')) {
                                codecs.push('vorbis');
                            }

                            // Check for Webm support
                            if (vidElement.canPlayType('video/webm; codecs="vp8, vorbis"')) {
                                codecs.push('webm');
                            }

                            resolve(codecs);
                        } else {
                            reject('Unable to enumerate supported video codecs');
                        }
                    }, 1);
            });
        }

        // Returns the list of supported video protocols
        function getSupportedVideoProtocols() {
            return $q(function(resolve, reject) {
                setTimeout(
                    function() {
                        var protocols = ['http', 'https', 'hls'];
                        resolve(protocols);
                    }, 1);
            });
        }

        // Returns whether the device supports remote control events
        function isRemoteSupported() {
            return true; // Yes as we map keyboard to use remote events
        }

        function createRemoteEvent(e) {
            var re = RemoteUtil.processDefaultKeyboardMapping(e);
            if (re) {
                re.iskeyboard = true;
            }

            return re;
        }

        function keydownHandler(e) {
            var re = createRemoteEvent(e);
            if (re == null) {
                return;
            }

            re.eventType = REMOTEEVENTTYPE.buttonDown;
            // Notifice remote driver service
            if (remoteServiceEventHandler) {
                remoteServiceEventHandler(re);
            }

            // Prevent default if needed
            if (re.isDefaultPrevented()) {
                e.preventDefault(); // prevent the default action (scroll / move caret)
            }
        }

        function keyupHandler(e) {
            var re = createRemoteEvent(e);
            if (re == null) {
                return;
            }

            re.eventType = REMOTEEVENTTYPE.buttonUp;
            // Notifice remote driver service
            if (remoteServiceEventHandler) {
                remoteServiceEventHandler(re);
            }

            // Prevent default if needed
            if (re.isDefaultPrevented()) {
                e.preventDefault(); // prevent the default action (scroll / move caret)
            }
        }

        // Registers a callback for remote events and returns an id to unsubscribe later
        function registerRemoteEvents(callback) {
            remoteServiceEventHandler = callback;
            $document.keydown(keydownHandler);
            $document.keyup(keyupHandler);
        }

        // Unregisters an existing callback for remote events
        function unRegisterRemoteEvents(id) {
            $document.unbind('keydown', keydownHandler);
            $document.unbind('keyup', keyupHandler);
        }

        // Returns a list of supported V codes
        function getSupportedRemoteVCodes() {
            var vcodes = [];
            return vcodes;
        }

        // Returns the support level for a specific feature
        function getFeatureSupport(feature) {
            feature = feature.toLowerCase();
            switch (feature) {
                case FEATURE_CODE.NETWORK_TRANSPARENT_CACHE:
                    return false;
                case FEATURE_CODE.UNREGISTER_SUPPORTED:
                    return 'yes';
                case 'runtime.unsafeeval':
                    return supportUnsafeEval;
                case 'input.keyboard':
                    return 'hardware';
                case 'input.mouse':
                    return 'hardware';
                case 'file.localstorage':
                    return 'no';
                case 'rendering.css3-transitions':
                    return 'yes';
                case 'rendering.video-layering':
                    return 'yes';
                case 'network.allowexternalresources':
                    return 'yes';
                case 'media.hls.supported':
                    if (bowser.name.toLowerCase() == 'safari') {
                        return 'yes';
                    } else {
                        return 'no';
                    }

                case 'media.timeout.duration':
                    return 15000;
                // The percentage of the media.timeout.duration.
                // Previously on 1.34, the timeout is 8 seconds. Now we increased it
                // to 15secs but need to retain the previous value, hence the 53% of 15secs.
                case 'media.card.timeout.percentage':
                    return 0.53;
                case 'media.maxvideolayers':
                    return '99';
                case 'input.touchevents':
                    return 'yes';
                case 'input.touchscreen':
                    return 'yes';
                case 'widget.datasource.maxtimeout':
                    return 5000;
                case 'network.maxconcurrentdownloads':
                    return 1;
                case 'media.rsscard.supportlocal.version':
                    return '1.5.0';
                case 'media.video.element.backgroundcolor':
                    return 'transparent';
                case FEATURE_CODE.WEB_WORKER_SUPPORTED:
                    return !!window.Worker;
                case FEATURE_CODE.RUNTIME_PERFORMANCE_API_SUPPORTED:
                    return true;
                case 'media.video.transition.supported':
                    return true;
                case 'signs.behaviour.reloadonresize':
                    return true;
                case 'media.animation.zoom.supported':
                    return true;
                case 'media.video.load.delay.duration':
                    return 1000;
                case FEATURE_CODE.STORAGE_MEMORY_CACHE_SUPPORT:
                    return true;
                default:
                    return null;
            }
        }

        // Check if the transistion is supported and return true or false.
        // Return all true if generic browser
        function getTransitionSupport(transition) {
            return true;
        }

        // Returns the local storage object
        function getLocalStorage() {
            return $window.localStorage;
        }

        function getSessionStorage() {
            return $window.sessionStorage;
        }

        function clearSessionStorage() {
            getSessionStorage().clear();
        }

        // Returns the mapping to system url
        function resolveUrl(path) {
            return path;
        }

        function readFile(path) {
            var defer = $q.defer();
            httpGet(path).then(function(res) {
                if (res.data) {
                    defer.resolve(res.data);
                } else {
                    defer.reject('data does not exist');
                }
            }, defer.reject);

            return defer.promise;
        }

        function getJSON(url) {
            var promise = $q.defer();
            $.getJSON(url).done(promise.resolve).fail(promise.reject);
            return promise.promise;
        }

        function startClient(server, deviceInfo) {
            if (socketClient) {
                stopClient();
            }

            socketClient = new WebSocketClient(server, deviceInfo);
            for (var i = 0; i < pendingSubcriptions.length; i++) {
                subscribeClientMessages(pendingSubcriptions[i]);
            }

            pendingSubcriptions = [];
            socketClient.connect();
        }

        function stopClient() {
            if (!socketClient) {
                return;
            }

            socketClient.disconnect();
            socketClient = undefined;
        }

        function subscribeClientMessages(callback) {
            if (!socketClient) {
                pendingSubcriptions.push(callback);
                logService.debug(logNamespace, 'BrowserGeneric: Pending subs added: ', pendingSubcriptions.length);
                return;
            }

            socketClient.subscribeToUpdates(callback);
        }

        function unsubscribeClientMessages(callback) {
            if (!socketClient) {
                var index = pendingSubcriptions.indexOf(callback);
                if (index > -1) {
                    pendingSubcriptions.splice(index, 1);
                    logService.debug(logNamespace, 'BrowserGeneric: Pending subs removed: ', pendingSubcriptions.length);
                }

                return;
            }

            socketClient.unsubscribeFromUpdates(callback);
        }

        function httpGet(url, config) {
            var defer = $q.defer();
            config = config || {};
            config.method = 'GET';
            config.url = url;
            $http(config).then(defer.resolve, defer.reject);
            return defer.promise;
        }

        // Get policy
        function getAppPolicy() {
            var defer = $q.defer();
            if (policy) {
                defer.resolve(policy);
            } else {
                var timeStamp = new Date().getTime();
                $http.get('policy.json?' + timeStamp).then(
                    function(response) {
                        if (response && response.data && response.data.registration) {
                            response.data.registration.source = 'local';
                        }

                        defer.resolve(response.data);
                    },
                    function(err) {
                        defer.reject();
                    }
                );
            }

            return defer.promise;
        }

        // Set app policy in memory
        function setAppPolicy(p) {
            if (typeof (p) === 'object') {
                policy = p;
            }
        }

        // Clear the app policy
        function clearAppPolicy() {
            policy = null;
        }

        // Get display rotation by looking at display resolution
        function getDisplayRotation() {
            var defer = $q.defer();
            var displayRes = getDisplayResolution();
            var rotation = 0;
            if (displayRes.width > displayRes.height) {
                rotation = 0;
            } else {
                rotation = 90;
            }

            defer.resolve({
                rotation: rotation
            });
            return defer.promise;
        }

        function getAppspaceDeviceType() {
            return 'HTML5CLIENT';
        }

        function execScript(script, opts) {
            // todo - implement this to embed scripting
            // parse back script to $eval.success/error,
            // so it could ignore multiple callback from client script
            var command = '(function(){' + script + '}())';
            try {
                var result = eval(command);
                if (!opts.async) {
                    // eslint-disable-next-line no-undef
                    $eval.success(result);
                }
            } catch (e) {
                // eslint-disable-next-line no-undef
                $eval.error(e);
            }
        }

        function getMediaDirective(elementType, options) {
            return {
                directive: elementType,
                attributes: ''
            };
        }

        function setVideoStretch(videoElement, stretch) {
            var objectFit = 'fill';
            switch (stretch) {
                case 'uniform':
                    objectFit = 'contain';
                    break;
                case 'uniformfill':
                    objectFit = 'cover';
                    break;
            }

            videoElement.css('object-fit', objectFit);
        }

        function processMediaURL(url, options) {
            return {url: url, isVideoSwitchEnabled: true};
        }

        function setCursorVisible(visible, forceUpdate) {
            if (!forceUpdate && cursorVisible == visible) {
                return;
            }

            cursorVisible = visible;
            $document[0].getElementsByTagName('body')[0].style.cursor = visible ? '' : 'none';
        }

        function getMemoryUsage() {
            var promise = $q.defer();
            promise.resolve('');
            return promise.promise;
        }

        function getDisplayInfo() {
            var promise = $q.defer();

            if (screen) {
                var displayInfo = [];
                var displayRes = getDisplayResolution();
                displayInfo.push({
                    x: displayRes.width,
                    y: displayRes.height
                });
                promise.resolve(displayInfo);
            } else {
                promise.resolve('');
            }

            return promise.promise;
        }

        function getCpuUsage() {
            var promise = $q.defer();
            promise.resolve('');
            return promise.promise;
        }

        function getAvailableStorage() {
            var promise = $q.defer();
            promise.resolve('');
            return promise.promise;
        }

        function getMaxLogDays() {
            return null;
        }

        function shouldMuteVideoOnLoad() {
            return false;
        }

        function getRuntimeEnvironments() {
            return ['javascript'];
        }

        function resolveContentPath(path) {
            return $q.resolve(path);
        }

        function updateApp() {
            return $q.resolve();
        }

        function processContentDirectiveOptions(content) {
            return content;
        }

        function processRequestResult(response, status, xhr) {
            return {
                response: response,
                status: status,
                xhr: xhr
            };
        }

        function request(configuration) {
            var promise = $q.defer();
            $.ajax(configuration.url, configuration).done(function(response, status, xhr) {
                promise.resolve(processRequestResult(response, status, xhr));
            }).fail(function(xhr, status, response) {
                promise.reject(processRequestResult(response, status, xhr));
            });
            return promise.promise;
        }

        function parseQrCode(imageData) {
            return !jsQR || !imageData || !imageData.data || !imageData.width || !imageData.height ?
                undefined :
                jsQR(imageData.data, imageData.width, imageData.height, {
                    inversionAttempts: 'dontInvert'});
        }

        function sanitizeContainerInlineStyling(inlineStyle) {
            return inlineStyle;
        }

        function getDeviceMenu() {
            return {};
        }

        function getPostMessagingService() {
            return $injector.get('postMessagingService');
        }

        function raisePostMessage(data) {
            getPostMessagingService().raiseParentMessage(data);
        }

        function supportsCardStreaming() {
            return true;
        }

        // Define the instance methods and properties
        BrowserGenericDriver.prototype = {
            // Public interface
            id: 'browser.generic',
            isReady: isReady,
            platform: {
                isReady: isReady,
                reload: reload,
                reboot: reboot,
                getOS: getOS,
                getBrowserName: getBrowserName,
                getBrowserVersion: getBrowserVersion,
                getBrowserEngine: getBrowserEngine,
                getFingerprint: getFingerprint,
                getModel: getModel,
                getCpuUsage: getCpuUsage,
                getMemoryUsage: getMemoryUsage,
                getAvailableStorage: getAvailableStorage,
                getDisplayInfo: getDisplayInfo,
                getFeatureSupport: getFeatureSupport,
                getRuntimeEnvironments: getRuntimeEnvironments,
                getLocalStorage: getLocalStorage,
                getSessionStorage: getSessionStorage,
                clearSessionStorage: clearSessionStorage,
                getAppPolicy: getAppPolicy,
                setAppPolicy: setAppPolicy,
                clearAppPolicy: clearAppPolicy,
                getAppspaceDeviceType: getAppspaceDeviceType,
                execScript: execScript,
                getFirmware: getFirmware,
                getTransitionSupport: getTransitionSupport,
                getMinVersionRequired: getMinVersionRequired,
                isMeetMinFirmware: isMeetMinFirmware,
                getMaxLogDays: getMaxLogDays,
                shouldMuteVideoOnLoad: shouldMuteVideoOnLoad,
                updateApp: updateApp,
                raisePostMessage: raisePostMessage,
                getPostMessagingService: getPostMessagingService,
                getDeviceMenu: getDeviceMenu
            },
            media: {
                getDisplayResolution: getDisplayResolution,
                getSupportedVideoCodecs: getSupportedVideoCodecs,
                getSupportedVideoProtocols: getSupportedVideoProtocols,
                getDisplayRotation: getDisplayRotation,
                getMediaDirective: getMediaDirective,
                setVideoStretch: setVideoStretch,
                processMediaURL: processMediaURL,
                setCursorVisible: setCursorVisible,
                resolveContentPath: resolveContentPath,
                processContentDirectiveOptions: processContentDirectiveOptions,
                parseQrCode: parseQrCode,
                sanitizeContainerInlineStyling: sanitizeContainerInlineStyling,
                supportsCardStreaming: supportsCardStreaming
            },
            file: {
                resolveUrl: resolveUrl,
                readFile: readFile
            },
            network: {
                getIpAddress: getIpAddress,
                httpGet: httpGet,
                getJSON: getJSON,
                startClient: startClient,
                stopClient: stopClient,
                subscribeClientMessages: subscribeClientMessages,
                unsubscribeClientMessages: unsubscribeClientMessages,
                request: request
            },
            remote: {
                registerEvents: registerRemoteEvents,
                unRegisterEvents: unRegisterRemoteEvents,
                getSupportedEvents: getSupportedRemoteVCodes,
                isSupported: isRemoteSupported
            }
        };
        // Return the constructor for the class
        return (BrowserGenericDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('BrowserSureVueDriver', ["$window", "$q", "$document", "$timeout", "$injector", "pathService", "networkContentRouterService", "FileUtil", "FileEntry", "FileError", "ChromeDriverLocalStorage", "RemoteEvent", "REMOTEVCODE", "REMOTEEVENTTYPE", "logService", "VersionCompare", "FEATURE_CODE", function($window, $q, $document, $timeout, $injector, pathService, networkContentRouterService, FileUtil, FileEntry, FileError, ChromeDriverLocalStorage, RemoteEvent, REMOTEVCODE, REMOTEEVENTTYPE, logService, VersionCompare, FEATURE_CODE) {
        var handler = 'http://localhost/api/v1/';
        var minVersionRequired = '1.0.0';
        var httpWrapperInstance;
        var retryRebootPromise;
        var rebootFailCount;
        var MAX_REBOOT_RETRY = 5;
        // Define the constructor function.
        function BrowserSureVueDriver() {

        }

        function getHttpWrapper() {
            if (!httpWrapperInstance) {
                var HttpWrapper = $injector.get('HttpWrapper');
                httpWrapperInstance = new HttpWrapper();
            }
            return httpWrapperInstance;
        }

        function reboot() {
            cancelRetryRebootTimeout();
            logService.debug('appspace.core.devicemanager', 'Surevue: System reboot');
            var httpWrapper = getHttpWrapper();
            var jsonObj = {
                id: 0,
                name: 'systemreset',
                data: ''
            };

            var onError = function(err) {
                logService.warn('appspace.core.devicemanager', 'SureVue: Reboot failed', err);
                rebootFailCount++;
                if (rebootFailCount >= MAX_REBOOT_RETRY) {
                    logService.warn('appspace.core.devicemanager', 'SureVue: Exceeds maximum number of reboot retries (' + rebootFailCount + ' times)');
                    return;
                }
                retryReboot();
            };

            httpWrapper.apiPost(handler, jsonObj).then(function(res) {
                logService.debug('appspace.core.devicemanager', 'Surevue: API call success with returned object... ', res);
                if (res && res.data && res.data.message == 'success') {
                    rebootFailCount = 0;
                    logService.debug('appspace.core.devicemanager', 'Surevue: Reboot success');
                    return;
                }
                onError(res);
            }, onError);
        }

        function retryReboot() {
            cancelRetryRebootTimeout();
            logService.debug('appspace.core.devicemanager', 'SureVue: Retry reboot in 1 seconds');
            retryRebootPromise = $timeout(reboot, 1000);
        }

        function cancelRetryRebootTimeout() {
            if (!retryRebootPromise) {
                return;
            }
            $timeout.cancel(retryRebootPromise);
            retryRebootPromise = undefined;
        }

        function reload() {
            // Reloads the page
            if (chrome.runtime && chrome.runtime.reload) {
                chrome.runtime.reload();
            }
        }

        // Get SureVue OS version
        function getFirmware() {
            var promise = $q.defer();

            var index = navigator.userAgent.toLowerCase().indexOf('surevue');
            var version = navigator.userAgent.substring(index + 11).split(' ');

            var httpWrapper = getHttpWrapper();
            var jsonObj = {
                id: 0,
                name: 'getgeneralsettings',
                data: ''
            };

            var version = '';
            httpWrapper.apiPost(handler, jsonObj).then(function(res) {
                if (res && res.data && res.data.message == 'success') {
                    var obj = JSON.parse(res.data.data);
                    version = obj.ClientVersion;
                } else {
                    version = '-';
                }

                promise.resolve(version);
            }, function(err) {
                promise.resolve('-');
            });

            return promise.promise;
        }

        // Get if firmware meet minimum requirement
        function isMeetMinFirmware() {
            var promise = $q.defer();
            // disable minimum firmware checking for surevue devices
            // until we found a better solution to fix http-request issue
            promise.resolve();
            return promise.promise;

            var vc = new VersionCompare();
            getFirmware().then(function(fw) {
                var res = vc.compare(fw, minVersionRequired);
                if (res) {
                    promise.resolve();
                } else {
                    var err = {};
                    err.minFWVersion = getMinVersionRequired();
                    err.currentFWVersion = fw;
                    promise.reject(err);
                }
            });

            return promise.promise;
        }

        // get minimmmum version required
        function getMinVersionRequired() {
            return minVersionRequired;
        }

        function getAppspaceDeviceType() {
            return 'SUREVUE';
        }

        function getCpuUsage() {
            var promise = $q.defer();

            var httpWrapper = getHttpWrapper();
            var jsonObj = {
                id: 0,
                name: 'getcpuusage',
                data: ''
            };

            httpWrapper.apiPost(handler, jsonObj).then(function(res) {
                if (res && res.data && res.data.message == 'success') {
                    var c = parseFloat(res.data.data);
                    logService.debug('appspace.core.devicemanager', 'Surevue: getCpuUsage success... ', c);
                    promise.resolve({
                        cpuUsage: c
                    });
                } else {
                    promise.reject('unable to retrieve cpu');
                }
            }, function(err) {
                logService.debug('appspace.core.devicemanager', 'Surevue: getCpuUsage fail');
                promise.resolve(0);
            });

            return promise.promise;
        }

        function getMemoryUsage() {
            var promise = $q.defer();

            var onRejected = function() {
                promise.resolve(0);
            };

            // memory returned by surevue are in MB
            // use decimal conversion to bytes
            // as devicemenu in signsHTML will calculate from bytes
            getTotalRam().then(function(total) {
                total = parseInt(total, 10) * 1000000;
                getFreeRam().then(function(free) {
                    free = parseInt(free, 10) * 1000000;
                    promise.resolve({
                        capacity: total,
                        availableCapacity: total - free
                    });
                }, onRejected);
            }, onRejected);

            return promise.promise;
        }

        function getTotalRam() {
            var promise = $q.defer();

            var httpWrapper = getHttpWrapper();
            var jsonObj = {
                id: 0,
                name: 'gettotalram',
                data: ''
            };

            httpWrapper.apiPost(handler, jsonObj).then(function(res) {
                if (res && res.data && res.data.message == 'success') {
                    logService.debug('appspace.core.devicemanager', 'Surevue: getTotalRam success... ', res.data.data);
                    promise.resolve(res.data.data);
                } else {
                    promise.reject('unable to retrieve RAM');
                }
            }, function(err) {
                logService.debug('appspace.core.devicemanager', 'Surevue: getTotalRam fail');
                promise.resolve(0);
            });

            return promise.promise;
        }

        function getFreeRam() {
            var promise = $q.defer();

            var httpWrapper = getHttpWrapper();
            var jsonObj = {
                id: 0,
                name: 'getfreeram',
                data: ''
            };

            httpWrapper.apiPost(handler, jsonObj).then(function(res) {
                if (res && res.data && res.data.message == 'success') {
                    logService.debug('appspace.core.devicemanager', 'Surevue: getfreeram success... ', res.data.data);
                    promise.resolve(res.data.data);
                } else {
                    promise.reject('unable to retrieve free RAM');
                }
            }, function(err) {
                logService.debug('appspace.core.devicemanager', 'Surevue: getfreeram fail');
                promise.resolve(0);
            });

            return promise.promise;
        }

        function getAvailableStorage() {
            var promise = $q.defer();

            var onRejected = function() {
                promise.resolve(0);
            };

            getTotalDiskSpace().then(function(total) {
                getFreeDiskSpace().then(function(free) {
                    promise.resolve({
                        total: total,
                        used: total - free
                    });
                }, onRejected);
            }, onRejected);

            return promise.promise;
        }

        function getTotalDiskSpace() {
            var promise = $q.defer();

            var httpWrapper = getHttpWrapper();
            var jsonObj = {
                id: 0,
                name: 'gettotaldiskspace',
                data: ''
            };

            httpWrapper.apiPost(handler, jsonObj).then(function(res) {
                if (res && res.data && res.data.message == 'success') {
                    var mb = parseInt(res.data.data, 10);
                    var bytes = mb * 1000000;
                    promise.resolve(bytes);
                } else {
                    promise.reject('unable to retrieve total disk space');
                }
            }, function(err) {
                logService.debug('appspace.core.devicemanager', 'Surevue: getTotalDiskSpace fail');
                promise.resolve(0);
            });

            return promise.promise;
        }

        function getFreeDiskSpace() {
            var promise = $q.defer();

            var httpWrapper = getHttpWrapper();
            var jsonObj = {
                id: 0,
                name: 'getfreediskspace',
                data: ''
            };

            httpWrapper.apiPost(handler, jsonObj).then(function(res) {
                if (res && res.data && res.data.message == 'success') {
                    var mb = parseInt(res.data.data, 10);
                    var bytes = mb * 1000000;
                    promise.resolve(bytes);
                } else {
                    promise.reject('unable to retrieve free disk space');
                }
            }, function(err) {
                logService.debug('appspace.core.devicemanager', 'Surevue: getFreeDiskSpace fail');
                promise.resolve(0);
            });

            return promise.promise;
        }

        function getDisplayInfo() {
            var promise = $q.defer();

            var httpWrapper = getHttpWrapper();
            var jsonObj = {
                id: 0,
                name: 'getdisplaysettings',
                data: ''
            };

            var displayinfo = [];
            httpWrapper.apiPost(handler, jsonObj).then(function(res) {
                if (!res || !res.data || !res.data.data || res.data.message !== 'success') {
                    promise.reject('unable to retrieve display settings');
                    return;
                }

                var data = JSON.parse(res.data.data);
                if (!data.DisplayPorts) {
                    promise.resolve(displayinfo);
                    return;
                }

                var displays = data.DisplayPorts;
                for (var i = 0; i < displays.length; i++) {
                    var display = displays[i];
                    var resolution = display.CurrentSettings.Resolution;
                    if (!display.Connected || !resolution) {
                        continue;
                    }

                    var resolutions = resolution.toLowerCase().split('x');
                    displayinfo.push({
                        x: resolutions[0],
                        y: resolutions[1]
                    });
                }

                promise.resolve(displayinfo);
            }, function(err) {
                logService.debug('appspace.core.devicemanager', 'Surevue: getdisplaysettings fail');
                promise.resolve(displayinfo);
            });

            return promise.promise;
        }

        function getModel() {
            return $window.navigator.appVersion;
        }

        function processMediaURL(url, options) {
            var res = {url: url};

            if (url.match(/udp:\/\//i)) {
                // remove protocol
                var udpURL = url.replace(/udp:\/\//i, '');
                // strip char after ?
                var stripEnd = udpURL.indexOf('?');
                if (stripEnd != -1) {
                    udpURL = udpURL.substring(0, stripEnd);
                }
                // strip trailing /
                if (udpURL.charAt(udpURL.length - 1) == '/') {
                    udpURL = udpURL.substring(0, udpURL.length - 1);
                }

                res.url = 'http://localhost:7070/udphelper/' + udpURL + '/out.m3u8';
            }

            return res;
        }

        function getSupportedVideoProtocols() {
            var promise = $q.defer();
            promise.resolve(['http', 'https', 'hls', 'multicast']);
            return promise.promise;
        }

        function pingDevice() {
            var promise = $q.defer();
            // need to implement this on surevue drive as chrome driver has this implementation
            // as of 23 Feb 2017
            // we want to avoid surevue from calling this as we want to implement only for chrome box devices due to time constraint
            promise.reject('not implemented');
            return promise.promise;
        }

        function getFeatureSupport(feature) {
            if (!feature) {
                return feature;
            }

            switch (feature.toLowerCase()) {
                case FEATURE_CODE.MAX_WEB_VIEW_COLLECTION:
                    return 0;
                default:
                    return null;
            }
        }

        // Define the instance methods and properties
        BrowserSureVueDriver.prototype = {
            // Public interface
            id: 'browser.chrome.mediavue',
            platform: {
                reboot: reboot,
                reload: reload,
                getFirmware: getFirmware,
                getAppspaceDeviceType: getAppspaceDeviceType,
                getCpuUsage: getCpuUsage,
                getMemoryUsage: getMemoryUsage,
                getAvailableStorage: getAvailableStorage,
                getDisplayInfo: getDisplayInfo,
                getModel: getModel,
                isMeetMinFirmware: isMeetMinFirmware,
                getMinVersionRequired: getMinVersionRequired,
                pingDevice: pingDevice,
                getFeatureSupport: getFeatureSupport,
            },
            file: {},
            remote: {},
            media: {
                processMediaURL: processMediaURL,
                getSupportedVideoProtocols: getSupportedVideoProtocols
            }
        };

        // Return the constructor for the class
        return (BrowserSureVueDriver);
    }]);
})(window.angular);

/* global FileTransfer, networkinterface, device */
(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.constant('$cordovaFileError', {
        1: 'NOT_FOUND_ERR',
        2: 'SECURITY_ERR',
        3: 'ABORT_ERR',
        4: 'NOT_READABLE_ERR',
        5: 'ENCODING_ERR',
        6: 'NO_MODIFICATION_ALLOWED_ERR',
        7: 'INVALID_STATE_ERR',
        8: 'SYNTAX_ERR',
        9: 'INVALID_MODIFICATION_ERR',
        10: 'QUOTA_EXCEEDED_ERR',
        11: 'TYPE_MISMATCH_ERR',
        12: 'PATH_EXISTS_ERR'
    });

    // todo - need refactor file error, file entry
    // todo - attach method error
    app.factory('CordovaDriver', ["$window", "$q", "ORIENTATION", "SYSTEM_DATA", "$cordovaFileError", "FileEntry", "FileError", "FileUtil", "WebRTC", "pathService", "logService", "displayUtil", "CordovaSocketServer", "cordovaPrinterFactory", function(
        $window,
        $q,
        ORIENTATION,
        SYSTEM_DATA,
        $cordovaFileError,
        FileEntry,
        FileError,
        FileUtil,
        WebRTC,
        pathService,
        logService,
        displayUtil,
        CordovaSocketServer,
        cordovaPrinterFactory) {
        // filter this, if cordova library not detected

        // Define the constructor function.
        function CordovaDriver() {
            if (!window.cordova) {
                var script = document.createElement('script');
                script.src = 'cordova.js';
                document.head.appendChild(script);
            }
        }

        // Resovles once cordoa is ready
        function isReady() {
            var defer = $q.defer();

            document.addEventListener('deviceready', function(e) {
                defer.resolve();
            }, false);

            return defer.promise;
        }

        // Network
        function downloadHttp(remotePath, localPath) {
            var defer = $q.defer();

            // todo - should autocreate for download http

            // prepare url
            localPath = resolveUrl(localPath);
            var dirPath = FileUtil.getDirPath(localPath);

            // check dir exist before proceed
            checkDirectory(dirPath).then(
                function(res) {
                    if (res.exists) {
                        var fileTransfer = new FileTransfer();
                        fileTransfer.download(remotePath, localPath,
                            function(res) {
                                // todo-test:
                                var entry = createFileEntry(res);
                                defer.resolve(entry);
                            },
                            function(err) {
                                var newErr = {
                                    code: err.http_status,
                                    error: err.exception,
                                    source: err.source,
                                    target: err.target
                                };
                                defer.reject(newErr);
                            },
                            false);
                    } else {
                        // have to standardize file output
                        defer.reject('directory not found');
                    }
                },
                function(err) {
                    defer.reject(err);
                });

            return defer.promise;
        }

        // Get the IP Address for the device
        function getIpAddress() {
            var defer = $q.defer();
            networkinterface.getIPAddress(function(ipAddress) {
                logService.debug('appspace.core.hal', 'Cordova IP: ' + ipAddress);
                defer.resolve({
                    address: ipAddress
                });
            }, function(error) {
                getIpAddressFromWebRTC(defer);
            });

            return defer.promise;
        }

        function getIpAddressFromWebRTC(defer) {
            if (WebRTC) {
                var webRTC = new WebRTC();
                webRTC.getIpAddress().then(
                    function(ipAddress) {
                        defer.resolve(ipAddress);
                    },
                    function(error) {
                        defer.reject();
                    }
                );
            } else {
                defer.reject();
            }
        }

        // Platform
        function reload() {
            // hard reload from the server
            $window.location.reload(true);
        }

        function getModel() {
            return device.model;
        }

        function getOS() {
            return device.platform;
        }

        function getFingerprint() {
            return $q.resolve({
                fingerprint: device.uuid,
                type: SYSTEM_DATA.FINGERPRINT_TYPE.HARDWARE
            });
        }

        function getAvailableStorage() {
            var defer = $q.defer();
            if (!chrome.system.storage || !chrome.system.storage.getInfo) {
                defer.resolve('');
            } else {
                chrome.system.storage.getInfo(function(info) {
                    var isStorageFound = false;
                    if (info && info.length > 0) {
                        for (var i = 0; i < info.length; i++) {
                            if (info[i].id && info[i].type === 'fixed') {
                                isStorageFound = true;
                                chrome.system.storage.getAvailableCapacity(info[i].id, function(storageInfo) {
                                    defer.resolve({
                                        used: info[i].capacity - storageInfo.availableCapacity,
                                        total: info[i].capacity
                                    });
                                });

                                break;
                            }
                        }
                    }

                    if (!isStorageFound) {
                        defer.resolve('');
                    }
                });
            }

            return defer.promise;
        }

        // convert cordova file entry to cordova
        function createFileEntry(entry) {
            // need to recreate native URL as cordova is giving extra slash.
            entry.nativeURL = pathService.join(cordova.file.dataDirectory, entry.fullPath);
            return new FileEntry({
                name: entry.name,
                path: entry.nativeURL,
                isDirectory: entry.isDirectory
            });
        }

        function createFileError(error, path) {
            return new FileError({
                message: $cordovaFileError[error.code],
                path: path
            });
        }

        function resolveUrl(path) {
            var baseUrl = cordova.file.dataDirectory;

            // slice base url
            if (baseUrl[baseUrl.length - 1] === '/') {
                baseUrl = baseUrl.slice(0, baseUrl.length - 1);
            }

            // strip last slash
            if (path[path.length - 1] === '/') {
                path = path.slice(0, path.length - 1);
            }

            // already absolute path
            if (path.indexOf(baseUrl) > -1) {
                return path;
            }

            path = pathService.join(baseUrl, path);
            return path;
        }

        function resolveRelative(path) {
            var baseURL = cordova.file.dataDirectory;
            var index = path.indexOf(baseURL);

            if (index > -1) {
                path = path.substring(index + baseURL.length);
            }

            return pathService.join(path);
        }

        function createDirectory(path) {
            var defer = $q.defer();
            var options = {
                create: true,
                exclusive: false
            };
            var systemPath = cordova.file.dataDirectory;
            var fileName = FileUtil.getBaseName(path);

            // remove file:///
            var relativePath = resolveRelative(path);
            var dirs = relativePath.split('/');

            // remove first blank element
            if (dirs[0] === '') {
                dirs = dirs.slice(1);
            }

            // remove last blank element
            if (dirs[dirs.length - 1] === '') {
                // dirs = dirs.slice(-1, 1);
                dirs.pop();
            }

            // reverse sequence
            dirs = dirs.reverse();

            // track current state
            var currentIndex = 0;

            var ensureDir = function(fs) {
                var dirName = dirs.pop();

                fs.getDirectory(dirName, options,
                    function(dirEntry) {
                        currentIndex++;

                        if (dirs.length > 0) {
                            ensureDir(dirEntry);
                        } else {
                            var entry = createFileEntry(dirEntry);
                            defer.resolve({
                                created: true,
                                entry: entry
                            });
                        }
                    },
                    function(error) {
                        var fileError = createFileError(error, path);
                        defer.reject(fileError);
                    });
            };

            // initiator
            checkDirectory(path).then(
                function(res) {
                    if (res.exists) {
                        var fileEntry = new FileEntry({
                            name: fileName,
                            path: path,
                            isDirectory: true
                        });
                        defer.resolve({
                            created: false,
                            entry: fileEntry
                        });
                    } else {
                        try {
                            $window.resolveLocalFileSystemURL(systemPath, function(fs) {
                                if (dirs.length > 0) {
                                    ensureDir(fs);
                                }
                            }, function(error) {
                                var fileError = createFileError(error, path);
                                defer.reject(fileError);
                            });
                        } catch (error) {
                            var fileError = createFileError(error, path);
                            defer.reject(fileError);
                        }
                    }
                },
                function(err) {
                    defer.reject(err);
                });

            return defer.promise;
        }

        function checkDirectory(path) {
            var defer = $q.defer();
            path = resolveUrl(path);

            try {
                $window.resolveLocalFileSystemURL(path, function(fileSystem) {
                    if (fileSystem.isDirectory === true) {
                        defer.resolve({
                            path: path,
                            exists: true
                        });
                    } else {
                        defer.resolve({
                            exists: false
                        });
                    }
                }, function(error) {
                    defer.resolve({
                        exists: false
                    });
                });
            } catch (error) {
                var fileError = createFileError(error, path);
                defer.reject(fileError);
            }

            return defer.promise;
        }

        function removeDirectory(path) {
            var defer = $q.defer();
            path = resolveUrl(path);

            var options = {
                create: false
            };

            var dirPath = FileUtil.getDirPath(path);
            var dirName = FileUtil.getBaseName(path);

            try {
                $window.resolveLocalFileSystemURL(dirPath, function(fileSystem) {
                    fileSystem.getDirectory(dirName, options,
                        function(dirEntry) {
                            // remove recursively file
                            dirEntry.removeRecursively(function() {
                                defer.resolve();
                            }, function(error) {
                                defer.reject();
                            });
                        },
                        function(error) {
                            defer.reject();
                        });
                }, function(error) {
                    defer.reject();
                });
            } catch (error) {
                defer.reject();
            }

            return defer.promise;
        }

        function enumerateDirectory(path) {
            var defer = $q.defer();
            path = resolveUrl(path);

            try {
                $window.resolveLocalFileSystemURL(path, function(fileSystem) {
                    if (fileSystem.isDirectory === true) {
                        var fileEntries = [];
                        var dirReader = fileSystem.createReader();

                        dirReader.readEntries(function(entries) {
                            for (var i in entries) {
                                var fileEntry = entries[i];
                                var entry = createFileEntry(fileEntry);

                                fileEntries.push(entry);
                            }

                            defer.resolve(fileEntries);
                        }, function(error) {
                            defer.reject();
                        });
                    } else {
                        defer.resolve();
                    }
                }, function(error) {
                    defer.reject();
                });
            } catch (error) {
                defer.reject();
            }

            return defer.promise;
        }

        function createFile(path) {
            var defer = $q.defer();
            path = resolveUrl(path);

            var options = {
                create: true,
                exclusive: false
            };

            var dirPath = FileUtil.getDirPath(path);
            var fileName = FileUtil.getBaseName(path);

            // check file exist
            checkFile(path).then(function(res) {
                if (res.exists) {
                    var fileEntry = new FileEntry({
                        name: fileName,
                        path: decodeURIComponent(path),
                        isDirectory: false
                    });
                    defer.resolve(fileEntry);
                } else {
                    try {
                        $window.resolveLocalFileSystemURL(dirPath, function(fileSystem) {
                            fileSystem.getFile(fileName, options,
                                function(fileEntry) {
                                    var entry = createFileEntry(fileEntry, {
                                        created: true
                                    });
                                    defer.resolve(entry);
                                },
                                function(error) {
                                    var fileError = createFileError(error, path);
                                    defer.reject(fileError);
                                });
                        }, function(error) {
                            var fileError = createFileError(error, path);
                            defer.reject(fileError);
                        });
                    } catch (error) {
                        var fileError = createFileError(error, path);
                        defer.reject(fileError);
                    }
                }
            },
                function(err) {
                    defer.reject(err);
                });
            return defer.promise;
        }

        function checkFile(path) {
            var defer = $q.defer();
            path = resolveUrl(path);

            try {
                $window.resolveLocalFileSystemURL(path, function(fileSystem) {
                    if (fileSystem.isFile === true) {
                        defer.resolve({
                            path: path,
                            exists: true
                        });
                    } else {
                        defer.resolve({
                            exists: false,
                        });
                    }
                }, function(error) {
                    defer.resolve({
                        exists: false,
                    });
                });
            } catch (error) {
                var fileError = createFileError(error, path);
                defer.reject(fileError);
            }

            return defer.promise;
        }

        function moveFile(sourcePath, destinationPath) {
            var defer = $q.defer();
            sourcePath = resolveUrl(sourcePath);
            destinationPath = resolveUrl(destinationPath);

            var options = {
                create: false
            };

            var oldDirPath = FileUtil.getDirPath(sourcePath);
            var oldFileName = FileUtil.getBaseName(sourcePath);
            var newDirPath = FileUtil.getDirPath(destinationPath);
            var newFileName = FileUtil.getBaseName(destinationPath);

            // todo - should auto create directory
            try {
                $window.resolveLocalFileSystemURL(oldDirPath, function(fileSystem) {
                    fileSystem.getFile(oldFileName, options, function(fileEntry) {
                        $window.resolveLocalFileSystemURL(newDirPath, function(newFileSystem) {
                            fileEntry.moveTo(newFileSystem, newFileName, function(fileEntry) {
                                var entry = createFile(fileEntry.nativeURL);
                                defer.resolve(entry);
                            }, function(error) {
                                var fileError = createFileError(error, destinationPath);
                                defer.reject(fileError);
                            });
                        }, function(error) {
                            var fileError = createFileError(error, destinationPath);
                            defer.reject(fileError);
                        });
                    }, function(error) {
                        var fileError = createFileError(error, destinationPath);
                        defer.reject(fileError);
                    });
                }, function(error) {
                    var fileError = createFileError(error, destinationPath);
                    defer.reject(fileError);
                });
            } catch (error) {
                var fileError = createFileError(error, destinationPath);
                defer.reject(fileError);
            }

            return defer.promise;
        }

        function removeFile(path) {
            var defer = $q.defer();
            path = resolveUrl(path);

            var options = {
                create: false
            };

            var dirPath = FileUtil.getDirPath(path);
            var fileName = FileUtil.getBaseName(path);

            try {
                $window.resolveLocalFileSystemURL(dirPath, function(fileSystem) {
                    fileSystem.getFile(fileName, options,
                        function(fileEntry) {
                            fileEntry.remove(function() {
                                defer.resolve();
                            }, function(error) {
                                defer.reject();
                            });
                        },
                        function(error) {
                            defer.reject();
                        });
                }, function(error) {
                    defer.reject();
                });
            } catch (error) {
                defer.reject();
            }

            return defer.promise;
        }

        // Returns the support level for a specific feature
        function getFeatureSupport(feature) {
            feature = feature.toLowerCase();
            switch (feature) {
                case 'input.keyboard':
                    return 'hardware';
                case 'input.touchscreen':
                    return 'yes';
                case 'input.mouse':
                    return 'hardware';
                case 'file.localstorage':
                    return 'yes';
                case 'media.hls.supported':
                    return 'yes';
                case 'media.maxvideolayers':
                    return '5';
                case 'rendering.css3-transitions':
                    return 'yes';
                case 'widget.datasource.maxtimeout':
                    return 60000;
                case 'display.force.rotation':
                    return 'yes';
                default:
                    return null;
            }
        }

        function getFirmware() {
            var promise = $q.defer();
            var firmware = '-';
            if (device && device.version) {
                firmware = device.version;
            }

            promise.resolve(firmware);
            return promise.promise;
        }

        function getMemoryUsage() {
            var promise = $q.defer();

            if (!chrome.system.memory || !chrome.system.memory.getInfo) {
                promise.resolve({
                    memoryUsage: 0
                });
            } else {
                chrome.system.memory.getInfo(function(info) {
                    promise.resolve({
                        capacity: info.capacity,
                        availableCapacity: info.availableCapacity
                    });
                });
            }

            return promise.promise;
        }

        // Get CPU usage
        function getCpuUsage() {
            var defer = $q.defer();
            if (!chrome.system.cpu || !chrome.system.cpu.getInfo) {
                defer.resolve({
                    cpuUsage: 0
                });
                return;
            }

            chrome.system.cpu.getInfo(function(info) {
                var usage = 0;
                for (var i = 0; i < info.processors.length; i++) {
                    usage = usage + 1 - info.processors[i].usage.idle / info.processors[i].usage.total;
                }

                usage = usage * 100 / info.processors.length;
                defer.resolve({
                    cpuUsage: usage
                });
            });

            return defer.promise;
        }

        function setDisplayRotation(rotation) {
            // 0 - landscape
            // 90 - portrait
            // 180 - upside down landscape
            // 270 - upside portrait
            return displayUtil.lockOrientation(rotation, {
                '0': ORIENTATION.landscape,
                '90': ORIENTATION.portrait,
                '180': ORIENTATION.landscape,
                '270': ORIENTATION.portrait
            });
        }

        function getDisplayRotation() {
            var defer = $q.defer();

            // 0 - landscape
            // 90 - portrait
            // 180 - upside down landscape
            // 270 - upside portrait

            var rotation = 0;
            if (screen && screen.orientation) {
                var screenOrientation = screen.orientation.type || screen.orientation;
                switch (screenOrientation.toLowerCase()) {
                    case 'portrait':
                    case 'portrait-primary':
                        rotation = 90;
                        break;

                    case 'landscape-secondary':
                        rotation = 180;
                        break;

                    case 'portrait-secondary':
                        rotation = 270;
                        break;

                    default:
                        break;
                }
            }

            defer.resolve({
                rotation: rotation
            });

            return defer.promise;
        }

        function hideSplashscreen() {
            if ($window.navigator.splashscreen) {
                $window.navigator.splashscreen.hide();
            }
        }

        function hideStatusBar() {
            if ($window.StatusBar) {
                $window.StatusBar.hide();
            }
        }

        function keepAwake() {
            if ($window.plugins && $window.plugins.insomnia) {
                $window.plugins.insomnia.keepAwake();
            }
        }

        var _server;
        function startServer(config) {
            if (_server) {
                var currentState = _server.currentState();
                if (currentState.started || currentState.starting) {
                    return;
                }
            }

            _server = new CordovaSocketServer();
            _server.start(config.port);
        }

        function stopServer() {
            if (!_server) {
                return;
            }

            _server.stop();
        }

        function subscribeServerMessages(callback) {
            if (!_server) {
                return;
            }

            _server.subscribeToUpdates(callback);
        }

        function unSubscribeServerMessages(callback) {
            if (!_server) {
                return;
            }

            _server.unsubscribeFromUpdates(callback);
        }

        function getRuntimeEnvironments() {
            return ['cordova'];
        }

        function print(printerType, options) {
            options = options || {};
            if (options.input) {
                return cordovaPrinterFactory.get(printerType).print(options);
            }

            // no input specified, take screenshot of whole screen
            var promise = $q.defer();
            // eslint-disable-next-line new-cap
            $window.navigator.screenshot.URI(function(error, result) {
                if (error || !result || !result.URI) {
                    promise.reject();
                    return;
                }

                var image = result.URI.split(',')[1];
                if (!image) {
                    promise.reject();
                    return;
                }

                options.input = 'base64://' + image;
                print(printerType, options).then(promise.resolve, promise.reject);
            });

            return promise.promise;
        }

        // Define the instance methods and properties
        CordovaDriver.prototype = {
            // Public interface
            id: 'browser.cordova',
            isReady: isReady,
            media: {
                getDisplayResolution: null,
                setDisplayRotation: setDisplayRotation,
                getDisplayRotation: getDisplayRotation,
                print: print,
            },
            file: {
                resolveUrl: resolveUrl,
                resolveRelative: resolveRelative,
                createDirectory: createDirectory,
                checkDirectory: checkDirectory,
                removeDirectory: removeDirectory,
                enumerateDirectory: enumerateDirectory,
                createFile: createFile,
                checkFile: checkFile,
                moveFile: moveFile,
                removeFile: removeFile
            },
            network: {
                downloadHttp: downloadHttp,
                getIpAddress: getIpAddress,
                startServer: startServer,
                stopServer: stopServer,
                subscribeServerMessages: subscribeServerMessages,
                unSubscribeServerMessages: unSubscribeServerMessages
            },
            platform: {
                reload: reload,
                getModel: getModel,
                getOS: getOS,
                getFingerprint: getFingerprint,
                getAvailableStorage: getAvailableStorage,
                getFirmware: getFirmware,
                getMemoryUsage: getMemoryUsage,
                getCpuUsage: getCpuUsage,
                getFeatureSupport: getFeatureSupport,
                getRuntimeEnvironments: getRuntimeEnvironments,
                hideSplashscreen: hideSplashscreen,
                hideStatusBar: hideStatusBar,
                keepAwake: keepAwake
            }
        };

        // Return the constructor for the class
        return (CordovaDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('CrestronDriver', ["$window", "$q", "logService", "TssOccupancyService", "crestronAndroidUtil", "displayUtil", "platformUtil", "ORIENTATION", "SYSTEM_DATA", "FEATURE_CODE", function(
        $window,
        $q,
        logService,
        TssOccupancyService,
        crestronAndroidUtil,
        displayUtil,
        platformUtil,
        ORIENTATION,
        SYSTEM_DATA,
        FEATURE_CODE) {
        var occupancyService = new TssOccupancyService(getCurrentOccupancySensorState);
        var loadedFingerprint;
        var NO_CRESTRON_PLUGIN_AVAILABLE_MESSAGE = 'No Crestron plugin available';
        var SENSOR_SIGNAL_KEY = 'OCC_SENSOR_STATE';
        var SENSOR_SIGNAL_VALUE = {
            UNKNOWN: 'unknown',
            UNOCCUPIED: 'unoccupied',
            OCCUPIED: 'occupied'
        };
        var SENSOR_SIGNAL_MAPPING = {
            0: SENSOR_SIGNAL_VALUE.UNKNOWN,
            1: SENSOR_SIGNAL_VALUE.UNOCCUPIED,
            2: SENSOR_SIGNAL_VALUE.OCCUPIED
        };
        var logkey = 'appspace.core.hal';
        var ETH0_PATH = 'file:///sys/class/net/eth0/address';
        var capabilities;
        var enableFileStorage;
        var crestronPlugin;
        var DEVICE_CONFIG = {
            DEFAULT: {
                ledConfig: {
                    red: {
                        controlName: 'USB_RED_LED_CONTROL',
                        brightnessName: 'USB_RED_LED_BRIGHTNESS'
                    },
                    green: {
                        controlName: 'USB_GREEN_LED_CONTROL',
                        brightnessName: 'USB_GREEN_LED_BRIGHTNESS'
                    },
                    blue: {
                        controlName: 'USB_BLUE_LED_CONTROL',
                        brightnessName: 'USB_BLUE_LED_BRIGHTNESS'
                    }
                },
                cardTimeoutDuration: 60000
            },
            DESK_PUCK: {
                ledConfig: {
                    red: {
                        controlName: 'RESERVE_LED_BAR_RED_CONTROL',
                        brightnessName: 'RESERVE_LED_BAR_RED_BRIGHTNESS'
                    },
                    green: {
                        controlName: 'RESERVE_LED_BAR_GREEN_CONTROL',
                        brightnessName: 'RESERVE_LED_BAR_GREEN_BRIGHTNESS'
                    },
                    blue: {
                        controlName: 'RESERVE_LED_BAR_BLUE_CONTROL',
                        brightnessName: 'RESERVE_LED_BAR_BLUE_BRIGHTNESS'
                    }
                },
                cardTimeoutDuration: 120000
            }
        };
        var DESK_PUCK_REGEX = /AM4\d{2,3}/i;
        var deviceConfig = DEVICE_CONFIG.DEFAULT;
        var isMacAddressAvailable = false;

        // Define the constructor function.
        function CrestronDriver(platformCapabilities) {
            capabilities = platformCapabilities || [];
            enableFileStorage = capabilities.indexOf('forcelocalstorage') > -1 ? 'yes' : 'no';
            $window.document.addEventListener('deviceready', function(e) {
                crestronPlugin = $window.cordova && $window.cordova.plugins ? $window.cordova.plugins.AppspaceCrestronController : undefined;
            }, false);
            var userAgent = platformUtil.getUserAgent() || '';
            var deskPuckMatches = userAgent.match(DESK_PUCK_REGEX) || [];
            if ((deskPuckMatches.length === 0)) {
                return;
            }

            deviceConfig = DEVICE_CONFIG.DESK_PUCK;
        }

        function setDisplayRotation(rotation) {
            // 0 - landscape
            // 90 - portrait
            // 180 - upside down landscape
            // 270 - upside portrait
            return displayUtil.lockOrientation(rotation, {
                '0': ORIENTATION['landscape-primary'],
                '90': ORIENTATION['portrait-primary'],
                '180': ORIENTATION['landscape-secondary'],
                '270': ORIENTATION['portrait-secondary']
            });
        }

        function getDisplayRotation() {
            var defer = $q.defer();
            // 0 - landscape
            // 90 - portrait
            // 180 - upside down landscape
            // 270 - upside portrait
            var rotation = 0;
            if ($window.screen && $window.screen.orientation) {
                var screenOrientation = $window.screen.orientation.type || $window.screen.orientation;
                switch (screenOrientation.toLowerCase()) {
                    case ORIENTATION['portrait']:
                    case ORIENTATION['portrait-primary']:
                        rotation = 90;
                        break;
                    case ORIENTATION['landscape-secondary']:
                        rotation = 180;
                        break;
                    case ORIENTATION['portrait-secondary']:
                        rotation = 270;
                        break;
                    case ORIENTATION['landscape']:
                    case ORIENTATION['landscape-primary']:
                        rotation = 0;
                        break;
                }
            }

            defer.resolve({
                rotation: rotation
            });

            return defer.promise;
        }

        function getAppspaceDeviceType() {
            return 'CRESTRON';
        }

        function getFingerprint() {
            if (loadedFingerprint) {
                return $q.resolve(loadedFingerprint);
            }

            var promise = $q.defer();
            var resolvePromise = function(res) {
                loadedFingerprint = {
                    fingerprint: res,
                    type: isMacAddressAvailable ?
                        SYSTEM_DATA.FINGERPRINT_TYPE.HARDWARE :
                        SYSTEM_DATA.FINGERPRINT_TYPE.GENERATED
                };
                promise.resolve(loadedFingerprint);
            };

            var onfail = function(e) {
                logService.warn(logkey, e);
                resolvePromise($window.uuid.v1());
            };

            getEthMacAddress().then(resolvePromise, onfail);
            return promise.promise;
        }

        function getEthMacAddress() {
            var promise = $q.defer();
            var fail = function(error) {
                isMacAddressAvailable = false;
                promise.reject(error);
            };

            var gotFile = function(fileEntry) {
                if (!fileEntry) {
                    fail('EthMACAddr: fileEntry missing');
                    return;
                }

                fileEntry.file(function(file) {
                    if (!file) {
                        fail('EthMACAddr: file missing');
                        return;
                    }

                    var reader = new $window.FileReader();
                    reader.onloadend = function(e) {
                        var result = (this.result || '').trim();
                        if (!result) {
                            fail('EthMACAddr: Eth MAC address is empty:' + JSON.stringify(e));
                            return;
                        }

                        promise.resolve(result);
                        isMacAddressAvailable = true;
                    };

                    reader.readAsText(file);
                });
            };

            $window.resolveLocalFileSystemURL(ETH0_PATH, gotFile, fail);
            return promise.promise;
        }

        function getFeatureSupport(feature) {
            if (!feature) {
                return null;
            }

            switch (feature.toLowerCase()) {
                case 'media.maxvideolayers':
                    return '1';
                case 'media.reusevideolayer':
                    return 'yes';
                case 'file.localstorage':
                    return enableFileStorage;
                case 'log.retain.history.window':
                    return 3;
                case 'support.lowbandwidth.mode':
                    return enableFileStorage !== 'yes';
                case FEATURE_CODE.CARD_TIMEOUT_DURATION:
                    return deviceConfig.cardTimeoutDuration;
                case FEATURE_CODE.REGISTRATION_UPDATE_SUPPORTED:
                    return true;
                default:
                    return null;
            }
        }

        function enableStatusLight(color) {
            if (!color) {
                return;
            }

            changeLEDColor(deviceConfig.ledConfig.red, color.r);
            changeLEDColor(deviceConfig.ledConfig.green, color.g);
            changeLEDColor(deviceConfig.ledConfig.blue, color.b);
        }

        function disableStatusLight() {
            changeLEDColor(deviceConfig.ledConfig.red);
            changeLEDColor(deviceConfig.ledConfig.green);
            changeLEDColor(deviceConfig.ledConfig.blue);
        }

        function changeLEDColor(colorConfig, intensity) {
            if (!crestronPlugin) {
                return;
            }

            // 3 is 1% brightness. Anything less than 3 will be less than 1 (e.g. 0.7)
            if (!intensity || intensity < 3) {
                crestronPlugin.sendBooleanSignalAsync(colorConfig.controlName, 'false');
                return;
            }

            // change to percentage
            crestronPlugin.sendBooleanSignalAsync(colorConfig.controlName, 'true').then(function(success) {
                if (!success) {
                    return;
                }

                crestronPlugin.sendIntegerSignalAsync(colorConfig.brightnessName, String(parseInt(intensity / 255 * 100, 10)));
            });
        }

        function getIntegerSignal(signal) {
            return crestronPlugin ?
                crestronPlugin.getIntegerSignalAsync(signal) :
                $q.reject(NO_CRESTRON_PLUGIN_AVAILABLE_MESSAGE);
        }

        function getCurrentOccupancySensorState() {
            var deferred = $q.defer();

            getIntegerSignal(SENSOR_SIGNAL_KEY).then(function(signal) {
                deferred.resolve(SENSOR_SIGNAL_MAPPING[signal] || SENSOR_SIGNAL_VALUE.UNKNOWN);
            }, deferred.reject);

            return deferred.promise;
        }

        function getOccupancyService() {
            return occupancyService;
        }

        function persistRegistrationInfo() {
            return crestronAndroidUtil.persistRegistrationInfo();
        }

        function getRuntimeEnvironments() {
            return ['crestron'];
        }

        function clearRegistrationInformation() {
            return crestronAndroidUtil.clearRegistrationInformation();
        }

        // Define the instance methods and properties
        CrestronDriver.prototype = {
            // Public interface
            id: 'browser.crestron',
            media: {
                setDisplayRotation: setDisplayRotation,
                getDisplayRotation: getDisplayRotation,
            },
            platform: {
                getAppspaceDeviceType: getAppspaceDeviceType,
                getAppPackageType: getAppspaceDeviceType,
                getFingerprint: getFingerprint,
                getFeatureSupport: getFeatureSupport,
                getRuntimeEnvironments: getRuntimeEnvironments,
                enableStatusLight: enableStatusLight,
                disableStatusLight: disableStatusLight,
                persistRegistrationInfo: persistRegistrationInfo,
                getCurrentOccupancySensorState: getCurrentOccupancySensorState,
                getOccupancyService: getOccupancyService,
                clearRegistrationInformation: clearRegistrationInformation
            },
            network: {
                getMacAddress: getEthMacAddress
            }
        };

        // Return the constructor for the class
        return (CrestronDriver);
    }]);
})(window.angular);

(function ($angular) {

    "use strict";

    var app = $angular.module('hal');

    app.factory("IECDriver", ["$window", "$document", "$q", "$timeout", "$http", "$injector", "RemoteEvent", "REMOTEVCODE", "REMOTEEVENTTYPE", "VersionCompare", "FEATURE_CODE", function ($window, $document, $q, $timeout, $http, $injector, RemoteEvent, 
        REMOTEVCODE, REMOTEEVENTTYPE, VersionCompare, FEATURE_CODE) {

        // All reference are based from the
        // Cisco Interactive Experience Platform Developer Guide
        // v 2.3.4
        var remoteServiceEventHandler = null;
        var minVersionRequired = "2.3.4";
        var policy = null;

        // Define the constructor function.
        function IECDriver() {
            // Listen to the mediaCache callbacks
            global.mediaCache.removed.connect(onRemoved);
            global.mediaCache.loadStarted.connect(onLoadStarted);
            global.mediaCache.loadFinished.connect(onLoadFinished);
            global.mediaCache.loadError.connect(onLoadError);
            global.mediaCache.locked.connect(onLocked);
            global.mediaCache.unlocked.connect(onUnLocked);
            global.mediaCache.cleared.connect(onCleared);
        }

        // private functions for mediaCache callbacks
        function onRemoved(url) {
            //console.log("removed cache " + url);
        }

        function onLoadStarted(url) {
            //console.log("load started " + url);
        }

        function onLoadFinished(url) {
            //console.log("load finished " + url);
            resolveUrlsPromises(url);
        }

        function onLoadError(url) {
            //console.log("load error " + url);
            resolveUrlsPromises(url);
        }

        function onLocked(url) {
            // console.log("locked file " + url);
        }

        function onUnLocked(url) {
            // console.log("unlocked file " + url);
        }

        function onCleared() {
            // console.log("clear everything");
        }

        // Resolve promises
        function resolveUrlsPromises(url, skipEncode) {
            if (!skipEncode) {
                url = encodeURI(url);
            }
            url = url.trim();

            var thecontent;
            for (var i = 0; i < urls.length; i++) {
                if (urls[i].id === url) {
                    thecontent = urls[i];
                    break;
                }
            }

            if (thecontent) {
                thecontent.promise.resolve(thecontent);
            }
        }

        function reboot() {
            // set directly on the global.registry object
            global.registry.setValue("power.reboot", "true");
        }

        //Get the IP Address for the device (pg. 111)
        function getIpAddress() {
            var promise = $q.defer();
            promise.resolve({ address: global.network.ip });
            return promise.promise;
        }

        // Get the firmware version
        function getFirmware() {
            var promise = $q.defer();
            if (global.device.version === undefined) {
                promise.resolve("-");
            } else {
                promise.resolve(global.device.version);
            }
            return promise.promise;
        }

        //Get if firmware meet minimum requirement
        function isMeetMinFirmware() {
            var promise = $q.defer();
            var vc = new VersionCompare();
            getFirmware().then(function (fw) {
                var opt = {};
                opt.lexicographical = true;
                var res = vc.compare(fw, minVersionRequired, opt);
                if (res) {
                    promise.resolve();
                }
                else {
                    var err = {};
                    err.minFWVersion = getMinVersionRequired();
                    err.currentFWVersion = fw;
                    promise.reject(err);
                }
            });

            return promise.promise;
        }

        // get minimmmum version required
        function getMinVersionRequired() {
            return minVersionRequired;
        }

        // return Cisco
        function getManufacturer() {
            return "Cisco";
        }

        // return the model name
        function getModel() {
            return (global.device.model === undefined) ? '' : global.device.model;
        }

        // return the cpu usage
        function getCpuUsage() {
            var promise = $q.defer();

            var cpuLoad = global.resources.cpuLoad;
            if (cpuLoad === undefined) {
                cpuLoad = 0;
            }
            promise.resolve({ cpuUsage: cpuLoad });

            return promise.promise;
        }

        function getMemoryUsage() {
            var promise = $q.defer();

            if (global.resources.memoryAllocated === undefined || global.resources.memoryTotal === undefined) {
                promise.resolve("");
            }
            else {
                var memoryTotal = global.resources.memoryTotal;
                var memoryUsed = global.resources.memoryAllocated / 100 * memoryTotal;
                promise.resolve({ capacity: memoryTotal, availableCapacity: memoryUsed });
            }

            return promise.promise;
        }

        function getAvailableStorage() {
            var promise = $q.defer();

            if (global.resources.storageFree === undefined ||
                global.resources.storageTotal === undefined) {
                promise.resolve("");
            }
            else {
                var totalStorage = global.resources.storageTotal;
                var totalUsed = totalStorage - global.resources.storageFree;

                promise.resolve({
                    used: totalUsed,
                    total: totalStorage
                });
            }
            return promise.promise;
        }

        function getDisplayInfo() {
            var promise = $q.defer();

            // pending resolution API from IEC Cisco
            // currently we can use window.innerHeight and window.innerWidth
            // but for now, return null
            if (window === undefined) {
                promise.resolve("");
            }
            else {
                var display = [];
                display.push({ x: window.screen.availWidth, y: window.screen.availHeight });

                promise.resolve(display);
            }

            return promise.promise;
        }

        function setDisplayRotation(rotation) {
            var promise = $q.defer();

            // if rotation value invalid
            if (rotation !== 0 && rotation !== 90 && rotation !== 180 && rotation !== 270) {
                promise.reject('invalid value for rotation:' + rotation);
            }
            else{
                global.display.rotationAngle = rotation;
                //delay resolve as rotation takes a while
                $timeout(function () {
                    promise.resolve();
                }, 5000);
            }
            return promise.promise;
        }

        // Get the serial number of the device. (pg. 93)
        function getFingerprint() {
            var promise = $q.defer();
            promise.resolve({ fingerprint: global.device.serialNumber });
            return promise.promise;
        }

        function getAppspaceDeviceType() {
            return "IEC";
        }

        function getMediaDirective(elementType, options) {
            var res = {};
            if (elementType == "as-media-video") {
                res.directive = "as-iec-media-video";
                res.attributes = "";
            }
            else {
                res.directive = elementType;
                res.attributes = "";
            }

            return res;
        }

        function getFeatureSupport(feature) {
            feature = feature.toLowerCase();

            switch (feature) {
                case "input.keyboard":
                    return "none";
                case "input.mouse":
                    return "hardware";
                case "file.localstorage":
                    return "no";
                case "media.maxvideolayers":
                    return "1";
                case "rendering.video-layering":
                    return "no";
                case "media.switchcontent.animate.supported":
                    return "no";
                case "rendering.css3-transitions":
                    return 'no';
                case "rendering.overlay-transition":
                    return 'no';
                case 'widget.datasource.maxtimeout':
                    return 60000;
                case 'rendering.support.viewport.width.height':
                    return 'no';
                case FEATURE_CODE.NETWORK_TRANSPARENT_CACHE:
                    return true;
                default:
                    return null;
            };
        }

        function isRemoteSupported() {
            return true;
        }

        // Remote API (pg. 98)
        function createRemoteEvent(e) {
            if (e === undefined) {
                return null;
            }

            var re = new RemoteEvent();
            re.code = e;
            // prevent system key, for back button especially
            //re.preventDefault();
            switch (e) {
                case "left": // left
                    re.vcode = REMOTEVCODE.left;
                    break;
                case "up":// up
                    re.vcode = REMOTEVCODE.up;
                    break;
                case "right": // right
                    re.vcode = REMOTEVCODE.right;
                    break;
                case "down": // down
                    re.vcode = REMOTEVCODE.down;
                    break;
                case "okay": // ENTER
                    re.vcode = REMOTEVCODE.select;
                    break;
                case "exit": // BACK
                    re.vcode = REMOTEVCODE.back;
                    break;
                case "menu": //menu
                    re.vcode = REMOTEVCODE.system;
                    break;
                case "mute": // MUTE
                    re.vcode = REMOTEVCODE.mute;
                    break;
                case "guide":
                    re.vcode = REMOTEVCODE.guide;
                    break;
                case "play":
                    re.vcode = REMOTEVCODE.play;
                    break;
                case "pause":
                    re.vcode = REMOTEVCODE.pause;
                    break;
                case "one":
                    re.vcode = REMOTEVCODE.no_1;
                    break;
                case "two":
                    re.vcode = REMOTEVCODE.no_2;
                    break;
                case "three":
                    re.vcode = REMOTEVCODE.no_3;
                    break;
                case "four":
                    re.vcode = REMOTEVCODE.no_4;
                    break;
                case "five":
                    re.vcode = REMOTEVCODE.no_5;
                    break;
                case "six":
                    re.vcode = REMOTEVCODE.no_6;
                    break;
                case "seven":
                    re.vcode = REMOTEVCODE.no_7;
                    break;
                case "eight":
                    re.vcode = REMOTEVCODE.no_8;
                    break;
                case "nine":
                    re.vcode = REMOTEVCODE.no_9;
                    break;
                case "zero":
                    re.vcode = REMOTEVCODE.no_0;
                    break;
                default:
                    return null; // exit this handler for other keys
            }

            return re;

        }

        var remoteKeysQueue;
        var lastKeyTime;
        var isProcessRemoteQueue;
        var lastKey;
        function onRemoteEvent(key, skey, config) {

            if (!remoteKeysQueue) {
                remoteKeysQueue = [];
            }

            var currTime = new Date();
            // if (lastKeyTime) {
            //     console.log('last key pressed interval:', (currTime.getTime() - lastKeyTime.getTime()));
            // }
            // if (lastKey) {
            //     console.log('last key pressed:', lastKey.key);
            // }
            // console.log('current key:', key)
            //if last key is not the same, push it
            if (!lastKey || (lastKey && lastKey.key != key)) {
                lastKey = { key: key, skey: skey, config: config };
                remoteKeysQueue.push(lastKey);
            }
            //if the last key is the same as current key AND last key is more than 140ms ago, push it into queue
            //if not, ignore it
            else if (lastKey && lastKey.key == key && lastKeyTime && (currTime.getTime() - lastKeyTime.getTime()) > 500) {
                lastKey = { key: key, skey: skey, config: config };
                remoteKeysQueue.push(lastKey);
            }

            lastKeyTime = new Date();
            //if no one is processing the remote queue, process it
            // console.log('is processing remote queue:', isProcessRemoteQueue);
            // console.log('queue length:', remoteKeysQueue.length);
            if (!isProcessRemoteQueue) {
                processRemoteQueues();
            }

        }
        var processQueuePromise;
        function processRemoteQueues() {
            isProcessRemoteQueue = true;
            if (processQueuePromise) {
                return;
            }
            processQueuePromise = $timeout(function () {
                var key = remoteKeysQueue.shift();
                // console.log('processing:',key);
                // console.log('queue left:',remoteKeysQueue.length);
                // console.log('clearing promise');
                processQueuePromise = undefined;
                if (key) {
                    processRemote(key.key, key.skey, key.config);
                }
                else {
                    // console.log('switching flag to false');
                    isProcessRemoteQueue = false;
                }
            }, 100);
        }

        function processRemote(key, skey, config) {
            // if(blockKey && previousKey && (previousKey == key)){
            //     return;
            // }else{
            //     if(blockKeyHandler){
            //         $timeout.cancel(blockKeyHandler);
            //     }

            //     previousKey = key;
            //     blockKey = true;
            //     blockKeyHandler = $timeout(function(){
            //         blockKey = false;
            //     }, 150, false);
            // }
            //create remote event
            // console.log(key + ' ' + skey);
            var re = createRemoteEvent(skey);
            if (re === null) {
                processRemoteQueues();
                return;
            }

            re.eventType = REMOTEEVENTTYPE.buttonDown;

            //notify remote driver
            if (remoteServiceEventHandler) {
                remoteServiceEventHandler(re);
            }

            processRemoteQueues();
        }

        function onRemoteErrorEvent(err) {
            console.log("IEC IR error " + err);
        }

        function registerRemoteEvents(callback) {
            //if we never bind before, bind
            if (!remoteServiceEventHandler) {
                global.ir.event.connect(onRemoteEvent);
                global.ir.error.connect(onRemoteErrorEvent);
                remoteServiceEventHandler = callback;
            }
        }

        function unRegisterRemoteEvents(id) {
            if (remoteServiceEventHandler) {
                remoteServiceEventHandler = null;
                //remove remote binding
                global.ir.error.disconnect(onRemoteErrorEvent);
                global.ir.event.disconnect(onRemoteEvent);
            }
        }

        //Returns the list of supported video protocols
        function getSupportedVideoProtocols() {
            return $q(function (resolve, reject) {
                var protocols = ["http", "https", "hls", "multicast"];
                resolve(protocols);
            });
        }

        function HttpWrapper() {
            var wrapper = $injector.get('HttpWrapper');
            return new wrapper();
        }

        var urls = [];
        function prefetchResources(manifest) {
            urls = [];
            var promise = $q.defer();
            var promises = [];

            // to check mediaCache is enabled or disabled
            // if disabled, the value will be -1
            var enabled = global.mediaCache.free == -1 ? false : true;
            if (!enabled) {
                promise.reject("Media Cache disabled");
            }
            else {
                if (manifest.files) {
                    var files = manifest.files;
                    // console.log(files);
                    $angular.forEach(files, function (value, key) {
                        var exists = global.mediaCache.contains(value.url);
                        if (!exists) {
                            var url = { id: value.url, promise: $q.defer() };
                            promises.push(url.promise.promise);
                            urls.push(url);
                            var httpwrapper = HttpWrapper();
                            //check header
                            httpwrapper.handlerHead(url.id).then(function (res) {
                                // console.log('res:',res.config.url);
                                // console.log('content-length:',res.headers('content-length'));
                                var contentLength = res.headers('content-length');
                                var urlToDownload = res.config.url;
                                // console.log('url:' + urlToDownload);
                                // console.log('length:',contentLength);
                                if (contentLength != undefined && contentLength != null && contentLength != 0) {
                                    // console.log('media cache load');
                                    global.mediaCache.load(urlToDownload);
                                } else {
                                    // console.log('resolve directly');
                                    resolveUrlsPromises(urlToDownload, true);
                                }
                            }, function (err) {
                                //console.log('err:',err);
                                var urlToDownload = err.config.url;
                                resolveUrlsPromises(urlToDownload, true);
                            })
                        }
                    });
                }

                $q.all(promises).then(function () {
                    promise.resolve();
                }, promise.reject);
            }

            return promise.promise;
        }

        function clearAllResources() {
            var promise = $q.defer();
            if (global) {
                if (global.mediaCache) { global.mediaCache.clear(); }
                if (global.networkCache) { global.networkCache.clear(); }
            }
            promise.resolve();

            return promise.promise;
        }

        //Get policy
        function getAppPolicy() {
            var defer = $q.defer();

            var onReceivedPolicy = function (policy, source) {
                if (policy && policy.registration) {
                    if (source) {
                        policy.registration.source = source;
                    }
                    //force to use local URL
                    if (policy.registration.appspace_url) {
                        var params = window.location.toString().split('/');
                        var serverUrl = "";
                        for (var i = 0; i < 3; i++) {
                            if (i == 0) {
                                serverUrl = params[i] + "//";
                            }
                            else {
                                serverUrl += params[i];
                            }
                        }
                        if (serverUrl) {
                            policy.registration.appspace_url = serverUrl;
                        }
                    }
                }
                defer.resolve(policy);
            }

            if (policy) {
                onReceivedPolicy(policy);
            }
            else {
                var timeStamp = new Date().getTime();
                $http.get("policy.json?" + timeStamp).then(
                    function (response) {
                        if (response && response.data) {
                            onReceivedPolicy(response.data, 'local');
                        } else {
                            defer.reject(null);
                        }
                    }, defer.reject);
            }

            return defer.promise;
        }

        //Set app policy in memory
        function setAppPolicy(p) {
            if (typeof (p) === "object") {
                policy = p;
            }
        }

        // Clear the app policy
        function clearAppPolicy() {
            policy = null;
        }

        //Define the instance methods and properties
        IECDriver.prototype = {
            //Public interface
            id: "browser.iec",
            platform: {
                reboot: reboot,
                getAppspaceDeviceType: getAppspaceDeviceType,
                getFingerprint: getFingerprint,
                getFeatureSupport: getFeatureSupport,
                getFirmware: getFirmware,
                getManufacturer: getManufacturer,
                getAvailableStorage: getAvailableStorage,
                getModel: getModel,
                getCpuUsage: getCpuUsage,
                getMemoryUsage: getMemoryUsage,
                getDisplayInfo: getDisplayInfo,
                isMeetMinFirmware: isMeetMinFirmware,
                getMinVersionRequired: getMinVersionRequired,
                getAppPolicy: getAppPolicy,
                setAppPolicy: setAppPolicy,
                clearAppPolicy: clearAppPolicy,
            },
            media: {
                getMediaDirective: getMediaDirective,
                setDisplayRotation: setDisplayRotation,
                getSupportedVideoProtocols: getSupportedVideoProtocols
            },
            network: {
                getIpAddress: getIpAddress,
                prefetchResources: prefetchResources,
                clearAllResources: clearAllResources
            },
            remote: {
                registerEvents: registerRemoteEvents,
                unRegisterEvents: unRegisterRemoteEvents,
                isSupported: isRemoteSupported
            }
        };
        //Return the constructor for the class
        return (IECDriver);
    }]);

})(window.angular);
(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('IOSDriver', ["$document", "$window", "$q", "logService", "UrlUtil", "FEATURE_CODE", "ApplicationStorage", "MemoryStorage", "CordovaStorage", "MEDIA_DIRECTIVE", "MEDIA", function(
        $document,
        $window,
        $q,
        logService,
        UrlUtil,
        FEATURE_CODE,
        ApplicationStorage,
        MemoryStorage,
        CordovaStorage,
        MEDIA_DIRECTIVE,
        MEDIA
    ) {
        var syncDisplay = true;
        var isReadyDefer;
        var storage;
        var isPreview;
        var IN_APP_BROWSER_CONFIG = {
            ID_PREFIX: 'ios_inappbrowser_',
            TARGET: '_blank',
            OPTIONS: 'location=no,toolbar=no',
            LOAD_STOP_EVENT: 'loadstop',
            LOAD_ERROR_EVENT: 'loadError'
        };
        var inAppBrowsers = [];

        function init() {
        }

        init();

        // Define the constructor function.
        function IOSDriver(config) {
            isPreview = (config || {}).isPreviewMode;
        }

        function isReady() {
            if (isPreview) {
                return $q.resolve();
            }

            if (isReadyDefer) {
                return isReadyDefer.promise;
            }

            isReadyDefer = $q.defer();
            var onDeviceReady = function(event) {
                storage = new ApplicationStorage(new MemoryStorage(), new CordovaStorage(($window.device || {}).uuid));
                storage.initialize($window.localStorage).then(isReadyDefer.resolve, function(error) {
                    throw new Error(error);
                });
            };

            $document[0].addEventListener('deviceready', onDeviceReady, false);
            return isReadyDefer.promise;
        }

        function getSingleAppMode() {
            var defer = $q.defer();
            $window.cordova.plugins.ASAM.isEnabled(defer.resolve);
            return defer.promise;
        }

        function enterSingleAppMode() {
            var defer = $q.defer();
            logService.debug('appspace.core.hal', 'Entering single app mode');
            $window.cordova.plugins.ASAM.toggle(true, function(result) {
                if (!result) {
                    logService.debug('appspace.core.hal', 'Failed to enter single app mode');
                    defer.reject();
                    return;
                }

                logService.debug('appspace.core.hal', 'Successfully entered single app mode');
                defer.resolve();
            });
            return defer.promise;
        }

        function exitSingleAppMode() {
            var defer = $q.defer();
            logService.debug('appspace.core.hal', 'Exiting single app mode');
            $window.cordova.plugins.ASAM.toggle(false, function(result) {
                if (!result) {
                    logService.debug('appspace.core.hal', 'Failed to exit single app mode');
                    defer.reject();
                    return;
                }

                logService.debug('appspace.core.hal', 'Successfully exited single app mode');
                defer.resolve();
            });
            return defer.promise;
        }

        function getAppspaceDeviceType() {
            return 'IOS';
        }

        function syncDisplayResolutionToOrientation() {
            if (!$window.location ||
                !$window.location.reload ||
                !$window.screen ||
                isNaN($window.screen.width) ||
                isNaN($window.screen.height) ||
                typeof $window.screen.orientation != 'string' ||
                !syncDisplay) {
                return;
            }

            var isResolutionPortrait = $window.innerHeight > $window.innerWidth;
            var isDevicePortrait = $window.screen.orientation.toLowerCase().indexOf('portrait') >= 0;

            if (isResolutionPortrait != isDevicePortrait) {
                logService.debug('appspace.core.media', 'Device resolution and orientation mismatch, device orientation: ' +
                    (isDevicePortrait ? 'portrait' : 'landscape') +
                    ', resolution orientation: ' +
                    (isResolutionPortrait ? 'portrait' : 'landscape'));
                // hard reload from the server
                $window.location.reload(true);
            }
        }

        function setSyncDisplayResolutionFlag(flag) {
            syncDisplay = flag;
        }

        // Returns the support level for a specific feature
        function getFeatureSupport(feature) {
            switch (feature.toLowerCase()) {
                case 'media.video.use.load':
                    return true;
                case 'statusbar.hideonsidemenuopen':
                    return true;
                case 'media.card.onlineforcestream':
                    return 'yes';
                case FEATURE_CODE.DEVICE_SUPPORT_SINGLE_APP_MODE:
                    return true;
                default:
                    return null;
            }
        }

        var isStatusBarExists = function() {
            return $window && $window.StatusBar;
        };

        function hideStatusBar() {
            if (!isStatusBarExists()) {
                return;
            }

            $window.StatusBar.hide();
            $window.StatusBar.overlaysWebView(true);
        }

        function showStatusBar() {
            if (!isStatusBarExists()) {
                return;
            }

            $window.StatusBar.show();
            $window.StatusBar.overlaysWebView(true);
        }

        function getRuntimeEnvironments() {
            return ['ios'];
        }

        function setBrightness(value) {
            var promise = $q.defer();
            $window.cordova.plugins.brightness.setBrightness(value,
                promise.resolve,
                promise.reject);
            return promise.promise;
        }

        function getBrightness() {
            var promise = $q.defer();
            $window.cordova.plugins.brightness.getBrightness(
                promise.resolve,
                promise.reject);
            return promise.promise;
        }

        function resolveContentPath(path) {
            return $q.resolve(!path || !UrlUtil.isFileProtocol(path) ? path : window.Ionic.WebView.convertFileSrc(path));
        }

        function getCordovaPlugin(plugin) {
            if (!$window.cordova || !$window.cordova.plugins) {
                return;
            }

            return $window.cordova.plugins[plugin];
        }

        function getSerialNumber() {
            var serialNumberPlugin = getCordovaPlugin('CDVSerialNumber');
            if (!serialNumberPlugin || !serialNumberPlugin.getSerialNumber) {
                return $q.reject('CDVSerialNumber Cordova plugin not found');
            }

            var promise = $q.defer();
            serialNumberPlugin.getSerialNumber(promise.resolve, promise.reject);
            return promise.promise;
        }

        function getLocalStorage() {
            return storage;
        }

        function getMediaDirective(elementType, options) {
            var result = {
                directive: elementType,
                attributes: ''
            };
            options = options || {};
            if (elementType === MEDIA_DIRECTIVE.WEB && options.webEngine === MEDIA.WEB_ENGINE_IN_APP_BROWSER) {
                result.directive = MEDIA_DIRECTIVE.IN_APP_BROWSER;
            }

            return result;
        }

        function createBrowser(url, onLoadStop, onLoadError) {
            var inAppBrowser = $window.cordova.InAppBrowser.open(url, IN_APP_BROWSER_CONFIG.TARGET, IN_APP_BROWSER_CONFIG.OPTIONS);
            inAppBrowser.id = IN_APP_BROWSER_CONFIG.ID_PREFIX + Date.now();
            inAppBrowser.addEventListener(IN_APP_BROWSER_CONFIG.LOAD_STOP_EVENT, onLoadStop);
            inAppBrowser.addEventListener(IN_APP_BROWSER_CONFIG.LOAD_ERROR_EVENT, onLoadError);
            inAppBrowser.dispose = disposeInAppBrowser.bind(null, onLoadStop, onLoadError, inAppBrowsers, inAppBrowser);
            inAppBrowsers.push(inAppBrowser);
            return inAppBrowser;
        }

        function disposeInAppBrowser(loadStopCallback, loadErrorCallback, browserCollection, inAppBrowser) {
            var unusedInAppBrowser;
            for (var index = 0; index < browserCollection.length; index++) {
                if (inAppBrowser.id !== browserCollection[index].id) {
                    continue;
                }

                unusedInAppBrowser = browserCollection.splice(index, 1)[0];
                break;
            }

            if (inAppBrowsers.length > 0) {
                return;
            }

            unusedInAppBrowser.removeEventListener(IN_APP_BROWSER_CONFIG.LOAD_STOP_EVENT, loadStopCallback);
            unusedInAppBrowser.removeEventListener(IN_APP_BROWSER_CONFIG.LOAD_ERROR_EVENT, loadErrorCallback);
            unusedInAppBrowser.close();
        }

        // Define the instance methods and properties
        IOSDriver.prototype = {
            // Public interface
            id: 'browser.ios',
            isReady: isReady,
            media: {
                syncDisplayResolutionToOrientation: syncDisplayResolutionToOrientation,
                setSyncDisplayResolutionFlag: setSyncDisplayResolutionFlag,
                resolveContentPath: resolveContentPath,
                getMediaDirective: getMediaDirective,
                createBrowser: createBrowser
            },
            platform: {
                getAppspaceDeviceType: getAppspaceDeviceType,
                getAppPackageType: getAppspaceDeviceType,
                getFeatureSupport: getFeatureSupport,
                getRuntimeEnvironments: getRuntimeEnvironments,
                hideStatusBar: hideStatusBar,
                showStatusBar: showStatusBar,
                setBrightness: setBrightness,
                getBrightness: getBrightness,
                getSingleAppMode: getSingleAppMode,
                enterSingleAppMode: enterSingleAppMode,
                exitSingleAppMode: exitSingleAppMode,
                getSerialNumber: getSerialNumber,
                getLocalStorage: getLocalStorage
            }
        };

        // Return the constructor for the class
        return (IOSDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('LgWebosDriver', ["$window", "$q", "$document", "$timeout", "$http", "$injector", "pathService", "FileUtil", "FileEntry", "FileError", "RemoteEvent", "REMOTEVCODE", "SYSTEM_DATA", "REMOTEEVENTTYPE", "logService", "WebosSocketServer", "RemoteUtil", "mediaUtil", "requestUtil", "UrlUtil", "dataUtil", "MEDIA_DIRECTIVE", "CONTENT_TYPE", "FEATURE_CODE", function($window, $q, $document, $timeout, $http, $injector,
        pathService, FileUtil, FileEntry, FileError, RemoteEvent, REMOTEVCODE, SYSTEM_DATA,
        REMOTEEVENTTYPE, logService, WebosSocketServer, RemoteUtil,
        mediaUtil, requestUtil, UrlUtil, dataUtil, MEDIA_DIRECTIVE, CONTENT_TYPE, FEATURE_CODE) {
        var versionUtil;
        var emulatorSerialNumber = 'SKJY1107';
        var loggerName = 'appspace.core.hal';
        var deviceStorageUriKey = 'lg-webos.platform.devicestorageuri';
        var deviceStorageUri;
        var supportFileLocalStorage = true;
        var fingerprintKey = 'browser-generic.platform.fingerprint';
        var CUSTOM_JS = {
            V3: './js/customjs/1.0/custom.js',
            V4: './js/customjs/1.3/custom.js',
            V6: './js/customjs/1.4/custom.js'
        };
        var storageTypes = {
            NONE: 'none',
            USB: 'usb',
            SDCARD: 'sdcard'
        };
        var SCAP_FILES = [
            'configuration.js',
            'deviceInfo.js',
            'inputSource.js',
            'power.js',
            'security.js',
            'signage.js',
            'sound.js',
            'storage.js',
            'time.js',
            'utility.js',
            'video.js'
        ];
        var WEBOS_SETTINGS = {
            '3.0': {
                SCAP_PATH: './js/cordova-cd/scap-1.5.0/',
                BACKGROUND_IMAGE_POSITION: 'absolute'
            },
            '4.0': {
                SCAP_PATH: './js/cordova-cd/scap-1.6.0/'
            },
            '4.1': {
                SCAP_PATH: './js/cordova-cd/scap-1.7.2/',
                ADDITIONAL_SCAP_FILES: [
                    'iot.js'
                ]
            },
            '6.0': {
                SCAP_PATH: './js/cordova-cd/scap-1.8.0/',
                ADDITIONAL_SCAP_FILES: [
                    'iot.js'
                ]
            }
        };
        var STREAM_PROTOCOL = {
            UDP: 'UDP',
            RTP: 'RTP',
            RTSP: 'RTSP'
        };
        var LG_MEDIA_TYPE = {
            MP4: 'mp4',
            MP2T: 'mp2t'
        };
        var DEVICE_ENVIRONMENT = {
            LGWEBOS: 'lgwebos',
            LGWEBOS3: 'lgwebos3',
            LGWEBOS4: 'lgwebos4',
            LGWEBOS4_1: 'lgwebos4.1',
            LGWEBOS6: 'lgwebos6'
        };
        var STREAM_PROTOCOLS = [STREAM_PROTOCOL.UDP, STREAM_PROTOCOL.RTP, STREAM_PROTOCOL.RTSP];
        var DEFAULT_PROIDIOM_SESSION_ID = 'DAWN7DEW_SWPROIDIOMWDAWN7DEW_SWPROIDIOMW';
        var DEFAULT_PROIDIOM_SESSION_VALUE = 'default';
        var DEFAULT_PROIDIOM_DRM_TYPE = 'PROIDIOM';
        var DEFAULT_PROIDIOM_TYPE = 'SW';
        var DEFAULT_PROIDIOM_TRANSPORT_TYPE = DEFAULT_PROIDIOM_DRM_TYPE + '_';
        var storageBaseUri;
        var storageWriteBaseUri;
        var storageAccessBaseUri;
        var storageRelativeBaseUri;
        var timerUpdateFileSupport;
        var logger = {
            debug: function(message, opt) {
                var msg = getAppspaceDeviceType() + ': ' + message;
                if (opt) {
                    logService.debug(loggerName, msg, opt);
                } else {
                    logService.debug(loggerName, msg);
                }
            },
        };

        if (!$window.webOS) {
            logger.debug('Missing webOS library.');
            return function() { };
        }

        var policy = null;
        var deviceInfo;
        var storage;
        var configuration;
        var power;
        var custom;
        var socketServer;
        var remoteServiceEventHandler = null;
        var device = {};
        var deviceSetting = {};
        var serverProperty = {};
        var hasInit = false;
        var isCheckingStorageAccess = false;
        var initDeviceInfo = false;
        var initStorageTypeInfo = false;
        var updateStorageInterval = 3000;
        var updateStorageTimeout = 40000;
        var isPreviewMode = false;
        var LgConfiguration;
        var LgCustom;
        var LgPower;
        var CHECK_STORAGE_MAX_RETRY_COUNT = 10;
        var EVENT = {
            LOAD: 'load',
            ERROR: 'error'
        };

        // Define the constructor function.
        function LgWebosDriver(config) {
            isPreviewMode = (config || {}).isPreviewMode;
            // if we're in preview mode in LG, meaning we're running signs in channel in iframe
            // just init normally without appending scripts and load the library from parent
            if (isPreviewMode) {
                onScapFilesAttached($window.top);
                return;
            }

            // 1) LG requires some time before it can execute the webOS methods.
            // 2) The webOS file loads fine regardless being injected in the body, head, or
            // compiled with our system file. Only the callback is not being called
            // 3) Happens on LG WebOS3 series D and C.
            // A docoument.ready will not work as it will eventually fail but
            // a simple timeout works.
            $timeout(function() {
                $window.webOS.deviceInfo(onDeviceInfoRetrieved);
            });
        }

        function getVersionUtil() {
            versionUtil = versionUtil || $injector.get('versionUtil');
            return versionUtil;
        }

        function onScapFilesAttached(windowObject) {
            windowObject = windowObject || $window;
            deviceInfo = new windowObject.DeviceInfo();
            storage = new windowObject.Storage();
            configuration = new windowObject.Configuration();
            power = new windowObject.Power();
            custom = new windowObject.Custom();
            LgConfiguration = windowObject.Configuration;
            LgCustom = windowObject.Custom;
            LgPower = windowObject.Power;
            if (!isPreviewMode) {
                socketServer = new WebosSocketServer();
            }

            initialize();
        }

        function finalizeScriptFiles() {
            device.environments.concat(DEVICE_ENVIRONMENT.LGWEBOS);
            attachScapFiles(deviceSetting).then(onScapFilesAttached);
        }

        function onDeviceInfoRetrieved(webOSDeviceInfo) {
            deviceSetting = WEBOS_SETTINGS['3.0'];
            device.environments = [DEVICE_ENVIRONMENT.LGWEBOS3];

            // if webos sdk is lower than 4, attach js files
            if (getVersionUtil().isLessThan(webOSDeviceInfo.sdkVersion, '4.0')) {
                loadScriptFile(CUSTOM_JS.V3).then(finalizeScriptFiles);
                return;
            }

            // if webos sdk is 4 or bigger, use custom library to get webos sdk version
            if (getVersionUtil().isLessThan(webOSDeviceInfo.sdkVersion, '6.0')) {
                loadScriptFile(CUSTOM_JS.V4).then(onCustomJsLoaded);
                return;
            }

            loadScriptFile(CUSTOM_JS.V6).then(onCustomJsLoaded);
        }

        function onCustomJsLoaded() {
            getWebOsVersion().then(onWebOsVersionRetrieved, function(error) {
                throw new Error('Fail to get webOS version:' + JSON.stringify(error));
            });
        }

        function onWebOsVersionRetrieved(version) {
            device.isWebOS4 = true;
            deviceSetting = WEBOS_SETTINGS['4.0'];
            device.environments = [DEVICE_ENVIRONMENT.LGWEBOS4];
            // overwrite settings if version is 4.1 or bigger
            if (getVersionUtil().isGreaterThanOrEqual(version, '6.0')) {
                deviceSetting = WEBOS_SETTINGS['6.0'];
                device.environments = [DEVICE_ENVIRONMENT.LGWEBOS6];
            } else if (getVersionUtil().isGreaterThanOrEqual(version, '4.1')) {
                deviceSetting = WEBOS_SETTINGS['4.1'];
                device.environments.push(DEVICE_ENVIRONMENT.LGWEBOS4_1);
            }

            finalizeScriptFiles();
        }

        function getWebOsVersion() {
            if (!$window.Custom) {
                return $q.reject('Custom not defined.');
            }

            var promise = $q.defer();
            // 1) LG requires some time before it can execute the webOS methods.
            // 2) The webOS file loads fine regardless being injected in the body, head, or
            // compiled with our system file. Only the callback is not being called
            // 3) Happens on LG WebOS3 series D and C.
            // A docoument.ready will not work as it will eventually fail but
            // a simple timeout works.
            $timeout(function() {
                custom = new $window.Custom();
                custom.Signage.getwebOSVersion(function(successObject) {
                    if (!successObject) {
                        promise.reject('getwebOSVersion successObject is falsy.');
                        return;
                    }

                    promise.resolve(successObject.webOSVersion);
                }, promise.reject);
            }, 0);
            return promise.promise;
        }

        function attachScapFiles(setting) {
            var promise = $q.defer();
            var promises = [];
            for (var i = 0; i < SCAP_FILES.length; i++) {
                promises.push(loadScriptFile(setting.SCAP_PATH + SCAP_FILES[i]));
            }

            // resolve the promise once the last script has loaded
            $q.allSettled(promises).then(function() {
                promise.resolve();
            });
            return promise.promise;
        }

        function loadScriptFile(file) {
            var promise = $q.defer();
            var scriptTag = attachFileToScript(file);
            scriptTag.onload = function(thisPromise) {
                thisPromise.resolve();
            }.bind(scriptTag, promise);
            return promise.promise;
        }

        function attachFileToScript(file) {
            var script = $document[0].createElement('script');
            script.src = file;
            $document[0].head.appendChild(script);
            return script;
        }

        function initialize() {
            if (hasInit) {
                return;
            }

            updateDeviceInfo();
            updateServerProperty();
        }

        function updateHasInit() {
            hasInit = initDeviceInfo && initStorageTypeInfo;
        }

        function updateDeviceInfo() {
            var onPlatformInfoRetrieved = function(res) {
                device.version = res.firmwareVersion;
                device.platform = res.modelName;
                device.model = res.manufacturer + '-' + res.modelName;
                device.serial = res.serialNumber;
                device.uuid = res.serialNumber;
                device.isEmulator = emulatorSerialNumber == res.serialNumber;
                if (device.isEmulator) {
                    logger.debug('Appspace is running on emulator.');
                    var fingerprint = $window.localStorage.getItem(fingerprintKey);
                    if (!fingerprint) {
                        fingerprint = uuid.v1();
                        $window.localStorage.setItem(fingerprintKey, fingerprint);
                    }

                    device.uuid = fingerprint;
                }

                initDeviceInfo = true;
                updateHasInit();
            };

            var onPlatformInfoRetrieveFailed = function(err) {
                logger.debug('Error retrieving device info.', err);
            };

            deviceInfo.getPlatformInfo(onPlatformInfoRetrieved, onPlatformInfoRetrieveFailed);
        }

        function updateServerProperty() {
            configuration.getServerProperty(function(property) {
                serverProperty = property;
                if (serverProperty.appLaunchMode == 'none') {
                    serverProperty.appLaunchMode = LgConfiguration.AppMode.LOCAL;
                }

                deviceStorageUri = $window.localStorage.getItem(deviceStorageUriKey);
                updateFileSupport();
            }, function(error) {
                logger.debug('Error retrieving server property.', error);
            });
        }

        function restartTimerUpdateFileSupport() {
            if (timerUpdateFileSupport) {
                $timeout.cancel(timerUpdateFileSupport);
                timerUpdateFileSupport = null;
            }

            timerUpdateFileSupport = $timeout(updateFileSupport, updateStorageInterval);
        }

        function updateFileSupport() {
            if (serverProperty.appLaunchMode == LgConfiguration.AppMode.REMOTE) {
                updateDeviceStorageInfo(null, LgConfiguration.AppMode.REMOTE);
                return;
            }

            var isLocal = serverProperty.appLaunchMode == LgConfiguration.AppMode.LOCAL;
            if (isLocal && !hasInit && deviceStorageUri == 'internal') {
                updateDeviceStorageInfo('internal', LgConfiguration.AppMode.LOCAL);
            }

            storage.getStorageInfo(function(res) {
                var expectedStorageUri = serverProperty.appLaunchMode == LgConfiguration.AppMode.USB ? 'usb:1' : deviceStorageUri;
                if (!hasInit && expectedStorageUri && res.externalMemory && res.externalMemory[expectedStorageUri]) {
                    updateDeviceStorageInfo(expectedStorageUri, serverProperty.appLaunchMode);
                }

                if (isLocal) {
                    var info = getStorageInfo(res.externalMemory);
                    if (!hasInit) {
                        updateStorageTimeout -= updateStorageInterval;
                        if (updateStorageTimeout <= 0 || (!deviceStorageUri && info.storageType == storageTypes.SDCARD)) {
                            updateDeviceStorageInfo(info.baseUri, LgConfiguration.AppMode.LOCAL);
                        }
                    } else {
                        updateDeviceStorageInfo(info.baseUri, LgConfiguration.AppMode.LOCAL);
                    }
                }

                if (isLocal || !hasInit) {
                    restartTimerUpdateFileSupport();
                }
            },
                function(err) {
                    restartTimerUpdateFileSupport();
                });
        }

        function updateDeviceStorageInfo(baseUri, appLaunchMode) {
            if (deviceStorageUri != baseUri) {
                deviceStorageUri = baseUri;
                logger.debug('Storage change from \'' + deviceStorageUri + '\' to \'' + baseUri + '\'');
                $window.localStorage.setItem(deviceStorageUriKey, deviceStorageUri);
            }

            switch (appLaunchMode) {
                case LgConfiguration.AppMode.LOCAL:
                    var info = resolveStorageInfo(baseUri);
                    supportFileLocalStorage = true;
                    storageBaseUri = baseUri;
                    storageWriteBaseUri = 'file://' + baseUri;
                    storageAccessBaseUri = 'http://127.0.0.1:9080';
                    storageRelativeBaseUri = './content';
                    if (info.serverRelativeUri && info.serverRelativeUri.length > 0) {
                        storageAccessBaseUri += '/' + info.serverRelativeUri;
                        storageRelativeBaseUri += '/' + info.serverRelativeUri;
                    }

                    break;

                case LgConfiguration.AppMode.USB:
                    supportFileLocalStorage = true;
                    storageBaseUri = 'usb:1';
                    storageWriteBaseUri = 'file://usb:1';
                    storageAccessBaseUri = 'http://127.0.0.1:9080/external/usb/1';
                    storageRelativeBaseUri = './content';
                    break;

                default:
                    supportFileLocalStorage = false;
                    storageBaseUri = '';
                    storageWriteBaseUri = '';
                    storageAccessBaseUri = '';
                    storageRelativeBaseUri = '';
                    break;
            }

            if (!hasInit && !isCheckingStorageAccess) {
                isCheckingStorageAccess = true;
                var retryPromise;
                var retryCount = 0;
                var cancelCheckStorageRetry = function() {
                    if (!retryPromise) {
                        return;
                    }

                    $timeout.cancel(retryPromise);
                    retryPromise = undefined;
                };

                var resolve = function() {
                    logger.debug('Device storage initialized to \'' + baseUri + '\'');
                    initStorageTypeInfo = true;
                    updateHasInit();
                    isCheckingStorageAccess = false;
                };

                var onStoragetUrlCheckFailed = function(e) {
                    cancelCheckStorageRetry();
                    if (retryCount >= CHECK_STORAGE_MAX_RETRY_COUNT) {
                        throw new Error('Storage is not accessible after checking for ' + retryCount + ' times. Please content the developer');
                    }

                    if (e.status === -1) {
                        logger.debug('Storage is not accessible. Retrying');
                        // retry after 1 seconds. In typical failure scenario, it can take up to 5-6 seconds for the web server to be ready
                        retryCount++;
                        retryPromise = $timeout(checkStorageAccess, 1000);
                        return;
                    }

                    resolve();
                };

                var checkStorageAccess = function() {
                    // check whether we can access the web server
                    // we're expecting the promise to be rejected as we're not requesting file
                    // e.g. http://127.0.0.1:9080
                    // if we get 404, means that the web server is ready.
                    // if we get -1, means that the web server is not ready.
                    // on c-series, it will resolve the request.
                    requestUtil.httpHead(storageAccessBaseUri).then(resolve, onStoragetUrlCheckFailed);
                };

                // if we're in preview mode, just resolve directly
                if (isPreviewMode) {
                    resolve();
                    return;
                }

                checkStorageAccess();
            }
        }

        function getStorageInfo(externalMemory) {
            var info = resolveStorageInfo('internal');
            if (externalMemory) {
                for (var key in externalMemory) {
                    if (Object.prototype.hasOwnProperty.call(externalMemory, key)) {
                        info = resolveStorageInfo(key);
                        if (info.storageType == storageTypes.SDCARD) {
                            break;
                        }
                    }
                }
            }

            return info;
        }

        function getStoragePort(uri) {
            var port;
            var str = uri.split(':');
            if (str.length > 1) {
                port = str[1];
            }

            return port;
        }

        function resolveStorageInfo(key) {
            var storageType = storageTypes.NONE;
            var baseUri = 'internal';
            if (key) {
                key = key.toString().toLowerCase();
                if (key.indexOf('usb:') == 0) {
                    baseUri = key;
                    storageType = storageTypes.USB;
                } else if (key.indexOf('sdcard:') == 0) {
                    baseUri = key;
                    storageType = storageTypes.SDCARD;
                }
            }

            var serverRelativeUri = '';
            var port = getStoragePort(baseUri);
            if (port) {
                serverRelativeUri += 'external/' + storageType + '/' + port;
            }

            return {
                storageType: storageType,
                baseUri: baseUri,
                port: port,
                serverRelativeUri: serverRelativeUri
            };
        }

        function isReady() {
            var defer = $q.defer();

            var hasInitTimer;
            var checkHasInit = function() {
                if (hasInitTimer) {
                    $timeout.cancel(hasInitTimer);
                    hasInitTimer = null;
                }

                if (hasInit) {
                    defer.resolve();
                } else {
                    hasInitTimer = $timeout(checkHasInit, 500);
                }
            };

            checkHasInit();
            return defer.promise;
        }

        // Get Web OS version
        function getFirmware() {
            var promise = $q.defer();
            var firmware = '-';
            if (device && device.version) {
                firmware = device.version;
            }

            promise.resolve(firmware);
            return promise.promise;
        }

        function getRuntimeEnvironments() {
            return device.environments;
        }

        // Set app policy in memory
        function setAppPolicy(p) {
            if (typeof (p) === 'object') {
                policy = p;
            }
        }

        // Clear the app policy
        function clearAppPolicy() {
            policy = null;
        }

        function getAppPolicy() {
            var promise = $q.defer();
            if (policy) {
                promise.resolve(policy);
            } else {
                // the absolute path is /procentric/scap/application/content/policy/policy.json
                var policyPath = 'file://usb:1/policy/policy.json';
                var options = {path: policyPath};

                var onFail = function(err) {
                    var errorCode = err.errorCode;
                    var errorText = err.errorText;
                    promise.reject(' Error Code [' + errorCode + ']: ' + errorText);
                };

                var onReadSuccess = function(res) {
                    var p = JSON.parse(res.data);
                    promise.resolve(p);
                };

                var onCheckSuccess = function(cbObject) {
                    if (!cbObject.exists) {
                        promise.reject('policy file not found');
                    } else {
                        options.position = 0;
                        options.encoding = 'utf8';
                        storage.readFile(onReadSuccess, onFail, options);
                    }
                };

                storage.exists(onCheckSuccess, onFail, options);
            }

            return promise.promise;
        }

        // Get CPU usage
        function getCpuUsage() {
            var defer = $q.defer();
            var options = {
                cpus: true,
                memory: false
            };
            if (device.isEmulator) {
                defer.resolve({
                    cpuUsage: 0
                });
            } else {
                deviceInfo.getSystemUsageInfo(function(res) {
                    // Get usage information in percentage
                    var usagePercentage = 0;
                    if (res.cpus) {
                        var total = 0;
                        var usage = 0;
                        var cpus = res.cpus;
                        for (var i = 0; i < cpus.length; i++) {
                            var cpu = cpus[i];
                            for (var key in cpu.times) {
                                if (Object.prototype.hasOwnProperty.call(cpu.times, key)) {
                                    switch (key) {
                                        case 'user':
                                        case 'sys':
                                            usage += cpu.times[key];
                                            total += cpu.times[key];
                                            break;

                                        default:
                                            total += cpu.times[key];
                                            break;
                                    }
                                }
                            }
                        }

                        usagePercentage = Math.round(100 * usage / total);
                    }

                    defer.resolve({
                        cpuUsage: usagePercentage
                    });
                },
                    function(err) {
                        logger.debug('Error retrieving cpu usage.', err);
                        defer.resolve({
                            cpuUsage: 0
                        });
                    }, options);
            }

            return defer.promise;
        }

        function getMemoryUsage() {
            var defer = $q.defer();
            var options = {
                cpus: false,
                memory: true
            };

            if (device.isEmulator) {
                defer.resolve({
                    memoryUsage: 0
                });
            } else {
                deviceInfo.getSystemUsageInfo(function(res) {
                    if (res.memory && res.memory.total && res.memory.free) {
                        defer.resolve({
                            capacity: res.memory.total,
                            availableCapacity: res.memory.free
                        });
                    } else {
                        logger.debug('Does not support retrieving memory usage.');
                        defer.resolve({
                            memoryUsage: 0
                        });
                    }
                },
                    function(err) {
                        logger.debug('Error retrieving memory usage.', err);
                        defer.resolve({
                            memoryUsage: 0
                        });
                    }, options);
            }

            return defer.promise;
        }

        // convert relative path to file system path
        function resolveUrl(path) {
            return resolvePath(storageAccessBaseUri, path);
        }

        // map absolute path to relative path for i/o operation
        function resolveRelative(path) {
            return resolvePath(storageRelativeBaseUri, path);
        }

        function resolvePath(basePath, path) {
            return basePath + '/' + getRelativePath(path);
        }

        function getRelativePath(path) {
            var result = path;

            // remove trailing slash
            while (result.length > 0 && result[result.length - 1] === '/') {
                result = result.substring(0, result.length - 1);
            }

            result = FileUtil.cleanPath(storageAccessBaseUri, result);
            result = FileUtil.cleanPath(storageWriteBaseUri, result);
            result = FileUtil.cleanPath(storageRelativeBaseUri, result);
            return result;
        }

        function resolveWriteFilePath(path) {
            return resolvePath(storageWriteBaseUri, path);
        }

        // file functions
        function createDirectory(path) {
            var defer = $q.defer();

            if (!supportFileLocalStorage) {
                defer.reject(new FileError({
                    message: 'Not supported.',
                    path: path
                }));
                return defer.promise;
            }

            var directoryPath = resolveWriteFilePath(path);
            var options = {
                path: directoryPath
            };
            storage.mkdir(function(res) {
                defer.resolve({
                    created: true,
                    entry: new FileEntry({
                        name: FileUtil.getBaseName(path),
                        path: resolveUrl(path),
                        isDirectory: true
                    })
                });
            },
                function(err) {
                    defer.reject(new FileError({
                        message: err.errorText,
                        path: path
                    }));
                }, options);

            return defer.promise;
        }

        function checkDirectory(path) {
            return checkFileDirectory(path);
        }

        function removeDirectory(path) {
            return removeFileDirectory(path, true);
        }

        function checkFile(path) {
            return checkFileDirectory(path);
        }

        function moveFile(sourcePath, destinationPath) {
            var defer = $q.defer();

            if (!supportFileLocalStorage) {
                defer.reject(new FileError({
                    message: 'Not supported.',
                    path: destinationPath
                }));
                return defer.promise;
            }

            var options = {
                oldPath: resolveWriteFilePath(sourcePath),
                newPath: resolveWriteFilePath(destinationPath)
            };

            storage.statFile(function(res) {
                var isDirectory = res && isTypeDirectory(res.type);
                storage.moveFile(function() {
                    defer.resolve(new FileEntry({
                        name: FileUtil.getBaseName(destinationPath),
                        path: resolveUrl(destinationPath),
                        isDirectory: isDirectory
                    }));
                },
                    function(err) {
                        defer.reject(new FileError({
                            message: err.errorText,
                            path: destinationPath
                        }));
                    }, options);
            },
                function(err) {
                    defer.reject(new FileError({
                        message: err.errorText,
                        path: destinationPath
                    }));
                }, {
                path: options.oldPath
            });

            return defer.promise;
        }

        function removeFile(path) {
            return removeFileDirectory(path, false);
        }

        function removeFileDirectory(path, isDirectory) {
            var defer = $q.defer();
            if (!supportFileLocalStorage) {
                defer.reject();
                return defer.promise;
            }

            var currentPath = resolveWriteFilePath(path);
            var options = {
                file: currentPath
            };
            if (isDirectory) {
                options.file += '/';
                options.recursive = true;
            }

            checkFileDirectory(path).then(function(res) {
                if (res && res.exists) {
                    storage.removeFile(function() {
                        defer.resolve();
                    },
                        function(err) {
                            defer.reject();
                        }, options);
                } else {
                    defer.resolve();
                }
            },
                function() {
                    defer.reject();
                });

            return defer.promise;
        }

        function checkFileDirectory(path) {
            var defer = $q.defer();

            if (!supportFileLocalStorage) {
                defer.reject(new FileError({
                    message: 'Not supported.',
                    path: path
                }));
                return defer.promise;
            }

            var currentPath = resolveWriteFilePath(path);
            var options = {
                path: currentPath
            };
            storage.exists(function(res) {
                if (res && res.exists) {
                    defer.resolve({
                        path: resolveUrl(path),
                        exists: true
                    });
                } else {
                    defer.resolve({
                        exists: false
                    });
                }
            },
                function(err) {
                    defer.reject(new FileError({
                        message: err.errorText,
                        path: path
                    }));
                }, options);

            return defer.promise;
        }

        function enumerateDirectory(path) {
            var defer = $q.defer();
            var options = {
                path: resolveWriteFilePath(path)
            };

            if (!supportFileLocalStorage) {
                defer.reject();
                return defer.promise;
            }

            storage.statFile(function(res) {
                if (res && isTypeDirectory(res.type)) {
                    storage.listFiles(function(res) {
                        if (res && res.files && res.files.length > 0) {
                            var fileEntries = [];
                            for (var i = 0; i < res.files.length; i++) {
                                var fileName = res.files[i].name;
                                var isDirectory = isTypeDirectory(res.files[i].type);
                                fileEntries.push(new FileEntry({
                                    name: fileName,
                                    path: pathService.join(resolveUrl(path), fileName),
                                    isDirectory: isDirectory
                                }));
                            }

                            defer.resolve(fileEntries);
                        } else {
                            defer.resolve();
                        }
                    },
                        function(err) {
                            defer.reject();
                        }, options);
                } else {
                    defer.resolve();
                }
            },
                function(err) {
                    defer.reject();
                }, options);

            return defer.promise;
        }

        function isTypeDirectory(str) {
            if (!str) {
                return false;
            }

            str = str.toLowerCase();
            return str == 'directory' || str == 'folder';
        }

        // download http data to file
        function downloadHttp(remotePath, localPath) {
            var defer = $q.defer();
            var options = {
                source: remotePath,
                destination: resolveWriteFilePath(localPath)
            };

            if (!supportFileLocalStorage) {
                var newErr = {
                    code: 'NOT_SUPPORTED',
                    error: 'Not supported.',
                    source: options.source,
                    target: options.target
                };
                defer.reject(newErr);

                return defer.promise;
            }

            storage.copyFile(function() {
                defer.resolve(new FileEntry({
                    name: FileUtil.getBaseName(localPath),
                    path: resolveUrl(localPath),
                    isDirectory: false
                }));
            },
                function(err) {
                    $http.head(remotePath).then(function(res) {
                        defer.reject({
                            code: err.errorCode,
                            error: 'Error Code: ' + err.errorCode + ', Message: ' + err.errorText,
                            source: options.source,
                            target: options.target
                        });
                    }, function(res) {
                        defer.reject({
                            code: res.status,
                            error: res.statusText,
                            source: options.source,
                            target: options.target
                        });
                    });
                }, options);

            return defer.promise;
        }

        // get used and total disk space
        function getAvailableStorage() {
            var defer = $q.defer();

            if (!supportFileLocalStorage) {
                defer.resolve('');
                return defer.promise;
            }

            storage.getStorageInfo(function(res) {
                if (!res || isNaN(res.total) || isNaN(res.free)) {
                    defer.resolve('');
                } else {
                    var freeSpace = parseInt(res.free);
                    var totalSpace = parseInt(res.total);

                    if (res.externalMemory && res.externalMemory[storageBaseUri]) {
                        var extStorage = res.externalMemory[storageBaseUri];
                        if (!isNaN(extStorage.total) && !isNaN(extStorage.free)) {
                            var extfreeSpace = parseInt(extStorage.free);
                            var extTotalSpace = parseInt(extStorage.total);
                            freeSpace += extfreeSpace;
                            totalSpace += extTotalSpace;
                        }
                    }

                    defer.resolve({
                        used: (totalSpace - freeSpace) * 1000,
                        total: totalSpace * 1000
                    });
                }
            }, function(error) {
                // Handle errors encountered while retrieving properties
                defer.resolve('');
            });

            return defer.promise;
        }

        // Get the IP Address for the device
        function getIpAddress() {
            var defer = $q.defer();
            deviceInfo.getNetworkInfo(function(res) {
                var ipAddress;
                if (res.wired && res.wired.state === 'connected') {
                    ipAddress = res.wired.ipAddress;
                } else if (res.wifi && res.wifi.state === 'connected') {
                    ipAddress = res.wifi.ipAddress;
                }

                if (ipAddress) {
                    logger.debug('Device IP is ' + ipAddress);
                    defer.resolve({
                        address: ipAddress
                    });
                } else {
                    logger.debug('No network information.');
                    defer.reject();
                }
            }, function(err) {
                logger.debug('Error retrieving device IP.', err);
                defer.reject();
            });

            return defer.promise;
        }

        function startServer(config) {
            socketServer.start(config.port);
        }

        function stopServer() {
            socketServer.stop();
        }

        function subscribeServerMessages(callback) {
            socketServer.subscribeToUpdates(callback);
        }

        function unSubscribeServerMessages(callback) {
            socketServer.unsubscribeFromUpdates(callback);
        }

        // Returns the support level for a specific feature
        function getFeatureSupport(feature) {
            feature = feature.toLowerCase();
            switch (feature) {
                case 'input.keyboard':
                    return 'none';
                case 'file.localstorage':
                    return supportFileLocalStorage ? 'yes' : 'no';
                case 'media.maxvideolayers':
                    return '1';
                case 'media.reusevideolayer':
                    return 'yes';
                case 'media.video.createbackground':
                    return 'yes';
                case 'media.webframe.support.polyfill':
                    return 'yes';
                case 'media.webframe.onload.windowfocus':
                    return 'yes';
                case 'contentpresenter.suppress.position':
                    return 'yes';
                case 'media.webframe.handle.keyboard':
                    return 'yes';
                case FEATURE_CODE.WEB_WORKER_SUPPORTED:
                    return (!Array.isArray(device.environments) || !device.environments.includes(DEVICE_ENVIRONMENT.LGWEBOS6)) && device.isWebOS4 && !!window.Worker;
                case FEATURE_CODE.RUNTIME_PERFORMANCE_API_SUPPORTED:
                    return false;
                case 'media.video.transition.supported':
                    return false;
                case 'background.channel.position':
                    return deviceSetting.BACKGROUND_IMAGE_POSITION;
                case FEATURE_CODE.REQUIRES_CONNECTION_FOR_PLAYBACK:
                    return true;
                case FEATURE_CODE.VIDEO_SYNCHRONIZED_SEEKING_SUPPORTED:
                    return false;
                case FEATURE_CODE.MAX_WEB_VIEW_COLLECTION:
                    return 0;
                default:
                    return null;
            }
        }

        // Media
        function getMediaDirective(elementType, options) {
            var result = {
                directive: elementType,
                attributes: '',
            };
            options = options || {};
            switch (elementType) {
                case MEDIA_DIRECTIVE.VIDEO:
                    result.directive = options.isaudio != 'true' ? MEDIA_DIRECTIVE.VIDEO : MEDIA_DIRECTIVE.AUDIO;
                    result.attributes = getVideoAttribute(options, result.directive);

                    break;
                case MEDIA_DIRECTIVE.AS_MEDIA_VIDEO:
                    result.directive = MEDIA_DIRECTIVE.AS_DUAL_VIDEO;
                    if (device.isWebOS4) {
                        result.attributes += 'switch-video-on-play="true" ';
                        if (options.previousContentType == CONTENT_TYPE.IMAGE) {
                            result.attributes += 'video-visible="false" ';
                        }
                    }

                    break;
            }

            return result;
        }

        function getVideoAttribute(options, directive) {
            // for LG, using uniform without texture will cause video cropping issue. But if we use texture, we have blurry issue
            // to reduce the issue, for fill, we remove texture (no crop issue), other than that, we use texture (to maintain aspect ratio without cropping)
            // --------------------------
            // return empty if texture is not supported
            // or directive is not video
            // or the stretch option is set to fill
            return !isTextureSupported() ||
                directive != 'video' || options.stretch === 'fill' ? '' : 'texture';
        }

        function isTextureSupported() {
            return !device.isWebOS4;
        }

        function setDisplayRotation(rotation) {
            var defer = $q.defer();
            if (!isNaN(rotation)) {
                var rotationValue = LgCustom.NATIVEPORTRAIT.OFF;
                switch (rotation.toString()) {
                    case '90':
                        rotationValue = LgCustom.NATIVEPORTRAIT.DEGREE_90;
                        break;

                    case '180':
                        rotationValue = LgCustom.NATIVEPORTRAIT.DEGREE_180;
                        break;

                    case '270':
                        rotationValue = LgCustom.NATIVEPORTRAIT.DEGREE_270;
                        break;

                    default:
                        break;
                }

                custom.Configuration.setNativePortraitMode(function() {
                    defer.resolve('success set rotation to: ' + rotationValue);
                }, function(err) {
                    defer.resolve('cannot set rotation due to error.');
                },
                    {
                        nativePortrait: rotationValue
                    });
            } else {
                defer.resolve('cannot set rotation due to invalid parameter.');
            }

            return defer.promise;
        }

        function getDisplayRotation() {
            var defer = $q.defer();
            var rotation = 0;
            custom.Configuration.getNativePortraitMode(function(res) {
                if (res && !isNaN(res.nativePortrait)) {
                    rotation = parseInt(res.nativePortrait);
                }

                defer.resolve({
                    rotation: rotation
                });
            },
                function(err) {
                    defer.resolve({
                        rotation: rotation
                    });
                }
            );

            return defer.promise;
        }

        // Reloads the application
        function reload() {
            // hard reload from the server
            $window.location.reload(true);
        }

        // Restarts the application
        function reboot() {
            var options = {
                powerCommand: LgPower.PowerCommand.REBOOT
            };
            power.executePowerCommand(function() {
                logger.debug('Success sending reboot command.');
            }, function(err) {
                logger.debug('Failed to call device reboot.', err);
            }, options);
        }

        function getAppspaceDeviceType() {
            return 'LGWEBOS';
        }

        function getModel() {
            return device.model;
        }

        function getOS() {
            return device.platform;
        }

        function getSerialNumber() {
            return $q.resolve(device.uuid);
        }

        function getFingerprint() {
            var defer = $q.defer();
            getSerialNumber().then(function(serialNo) {
                defer.resolve({
                    fingerprint: serialNo,
                    type: SYSTEM_DATA.FINGERPRINT_TYPE.HARDWARE
                });
            });
            return defer.promise;
        }

        // Returns whether the device supports remote control events
        function isRemoteSupported() {
            return true; // Yes as we map keyboard to use remote events
        }

        function createRemoteEvent(e) {
            var re = new RemoteEvent(e);
            switch (e.which) {
                case 415:
                    re.vcode = REMOTEVCODE.play;
                    break;
                case 19:
                    re.vcode = REMOTEVCODE.pause;
                    break;
                case 461: // LG back button
                    re.vcode = REMOTEVCODE.back;
                    re.isSystem = true;
                    break;
                case 405: // LG yellow button
                case 406: // LG blue button
                    re.vcode = REMOTEVCODE.system;
                    re.isSystem = true;
                    break;
            }

            // if device didn't implement
            if (!re.vcode) {
                re = RemoteUtil.processDefaultKeyboardMapping(e);
            }

            return re;
        }

        function keydownHandler(e) {
            var re = createRemoteEvent(e);
            if (re == null) {
                return;
            }

            re.eventType = REMOTEEVENTTYPE.buttonDown;
            // Notifice remote driver service
            if (remoteServiceEventHandler) {
                remoteServiceEventHandler(re);
            }

            // Prevent default if needed
            if (re.isDefaultPrevented()) {
                e.preventDefault(); // prevent the default action (scroll / move caret)
            }
        }

        function keyupHandler(e) {
            var re = createRemoteEvent(e);
            if (re == null) {
                return;
            }

            re.eventType = REMOTEEVENTTYPE.buttonUp;
            // Notifice remote driver service
            if (remoteServiceEventHandler) {
                remoteServiceEventHandler(re);
            }

            // Prevent default if needed
            if (re.isDefaultPrevented()) {
                e.preventDefault(); // prevent the default action (scroll / move caret)
            }
        }

        // Registers a callback for remote events and returns an id to unsubscribe later
        function registerRemoteEvents(callback) {
            remoteServiceEventHandler = callback;
            $document.keydown(keydownHandler);
            $document.keyup(keyupHandler);
        }

        // Unregisters an existing callback for remote events
        function unRegisterRemoteEvents(id) {
            $document.unbind('keydown', keydownHandler);
            $document.unbind('keyup', keyupHandler);
        }

        function getUrlInformation(url) {
            var result = {};
            // grab the protocol
            result.protocol = UrlUtil.getProtocolFromUrl(url).toUpperCase();
            // grab the base url
            result.url = UrlUtil.getBaseUrl(url);
            // add all the query string into the result
            var queryStringObject = UrlUtil.retrieveParamsFromUrl(url);
            return dataUtil.mergeObject(result, queryStringObject);
        }

        function isProidiomUrl(urlInformation) {
            return device.isWebOS4 && urlInformation.proidiomsession;
        }

        function processMediaURL(url) {
            var result = {url: url, isVideoSwitchEnabled: true};
            var urlInformation = getUrlInformation(url);
            var isProidiom = isProidiomUrl(urlInformation);
            // if the url is not proidiom and not streaming protocols, return default result directly
            if (!isProidiom && STREAM_PROTOCOLS.indexOf(urlInformation.protocol) === -1) {
                return result;
            }

            result = urlInformation;
            // if UDP/RTP/RTSP doesn't come with query string, auto set lgmediatype based on protocol
            if (!result.lgmediatype) {
                switch (result.protocol) {
                    case STREAM_PROTOCOL.UDP:
                    case STREAM_PROTOCOL.RTP:
                        result.lgmediatype = LG_MEDIA_TYPE.MP4;
                        break;
                    case STREAM_PROTOCOL.RTSP:
                        result.lgmediatype = LG_MEDIA_TYPE.MP2T;
                        break;
                }
            }

            result.options = {
                htmlMediaOption: {
                    useUMSMediaInfo: true
                },
                mediaTransportType: result.protocol
            };

            // rtp not supported for proidiom
            if (isProidiom && result.protocol != STREAM_PROTOCOL.RTP) {
                result.isVideoSwitchEnabled = false;
                var proidiomSessionId = urlInformation.proidiomsession === DEFAULT_PROIDIOM_SESSION_VALUE ?
                    DEFAULT_PROIDIOM_SESSION_ID : urlInformation.proidiomsession;
                result.options.mediaTransportType = DEFAULT_PROIDIOM_TRANSPORT_TYPE + result.protocol;
                result.options.option = {
                    drm: {
                        sessionID: proidiomSessionId,
                        proidiomType: DEFAULT_PROIDIOM_TYPE,
                        type: DEFAULT_PROIDIOM_DRM_TYPE,
                    }
                };
            }

            return result;
        }

        function getSupportedVideoProtocols() {
            var promise = $q.defer();
            promise.resolve(['http', 'https', 'hls', 'multicast']);
            return promise.promise;
        }

        function sanitizeContainerInlineStyling(inlineStyle) {
            return mediaUtil.removesBorderRadius(inlineStyle);
        }

        function getPnsMessageProcessor(message) {
            var promise = $q.defer();
            var reader = new $window.OriginalFileReader();
            reader.readAsText(message);
            reader.addEventListener(EVENT.ERROR, promise.reject);
            reader.addEventListener(EVENT.LOAD, function() {
                if (dataUtil.isString(reader.result)) {
                    promise.resolve(reader.result);
                    return;
                }

                promise.reject('Payload is not a string');
            }, false);
            return promise.promise;
        }

        // Define the instance methods and properties
        LgWebosDriver.prototype = {
            // Public interface
            id: 'browser.lg.webos',
            isReady: isReady,
            platform: {
                reload: reload,
                reboot: reboot,
                getOS: getOS,
                getFingerprint: getFingerprint,
                getModel: getModel,
                getCpuUsage: getCpuUsage,
                getAvailableStorage: getAvailableStorage,
                getMemoryUsage: getMemoryUsage,
                getFeatureSupport: getFeatureSupport,
                getAppspaceDeviceType: getAppspaceDeviceType,
                getAppPackageType: getAppspaceDeviceType,
                getFirmware: getFirmware,
                getSerialNumber: getSerialNumber,
                getRuntimeEnvironments: getRuntimeEnvironments,
                getAppPolicy: getAppPolicy,
                setAppPolicy: setAppPolicy,
                getPnsMessageProcessor: getPnsMessageProcessor,
                clearAppPolicy: clearAppPolicy
            },
            file: {
                resolveUrl: resolveUrl,
                resolveRelative: resolveRelative,
                createDirectory: createDirectory,
                checkDirectory: checkDirectory,
                removeDirectory: removeDirectory,
                enumerateDirectory: enumerateDirectory,
                checkFile: checkFile,
                moveFile: moveFile,
                removeFile: removeFile
            },
            network: {
                downloadHttp: downloadHttp,
                getIpAddress: getIpAddress,
                startServer: startServer,
                stopServer: stopServer,
                subscribeServerMessages: subscribeServerMessages,
                unSubscribeServerMessages: unSubscribeServerMessages
            },
            media: {
                getMediaDirective: getMediaDirective,
                setDisplayRotation: setDisplayRotation,
                getDisplayRotation: getDisplayRotation,
                processMediaURL: processMediaURL,
                getSupportedVideoProtocols: getSupportedVideoProtocols,
                sanitizeContainerInlineStyling: sanitizeContainerInlineStyling
            },
            remote: {
                registerEvents: registerRemoteEvents,
                unRegisterEvents: unRegisterRemoteEvents,
                isSupported: isRemoteSupported,
                createRemoteEvent: createRemoteEvent
            }
        };

        // Return the constructor for the class
        return (LgWebosDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('MercuryDriver', ["$window", "$injector", "$q", "SYSTEM_DATA", "CRESTRON_MODE", "CRESTRON_CHANNELS", "POST_MESSAGES", "postMessagingUtil", "POST_MESSAGES_ERROR", "driverUtil", function($window, $injector, $q, SYSTEM_DATA, CRESTRON_MODE, CRESTRON_CHANNELS, POST_MESSAGES,
        postMessagingUtil, POST_MESSAGES_ERROR, driverUtil) {
        var supportedLoadedChannel = [CRESTRON_CHANNELS.OCCUPIED, CRESTRON_CHANNELS.UNOCCUPIED];

        function MercuryDriver() {
        }

        function getAppspaceDeviceType() {
            return 'CRESTRON';
        }

        function getModel() {
            return 'Mercury';
        }

        function getFeatureSupport(feature) {
            if (!feature) {
                return null;
            }

            feature = feature.toLowerCase();
            switch (feature) {
                case 'services.enable.health':
                    return 'no';
                case 'services.enable.alerts':
                    return 'yes';
                case 'services.enable.pnclient':
                    return 'yes';
                case 'services.enable.pushupdates':
                    return 'yes';
                case 'services.disable.sync':
                    return getCrestronMode() == CRESTRON_MODE.STANDBY;
                case 'channel.viewer.interactive.disable': // if there's crestron mode, don't allow interactive
                    return isCrestronModeValid();
                case 'support.lowbandwidth.mode':
                    return true;
                default:
                    return null;
            }
        }

        function getSessionService() {
            return $injector.get('sessionService');
        }

        function getPostMessagingService() {
            return $injector.get('postMessagingService');
        }

        function getCrestronMode() {
            return getSessionService().get('crestronmode');
        }

        function getConfigurationService() {
            return $injector.get('coreConfigurationService');
        }

        function getSerialNumber() {
            return $q.resolve(getConfigurationService().getSerialNumber());
        }

        function updateSerialNumber(serialNumber) {
            getConfigurationService().setSerialNumber(serialNumber);
        }

        function isCrestronModeValid() {
            return Boolean(getCrestronMode());
        }

        function clearSessionStorage() {
            driverUtil.clearSessionStorage(function(urlParameters, sessionService) {
                // retain crestron's related flags
                if (!urlParameters['crestronmode']) {
                    return;
                }

                var crestronUrlParams = {};
                for (var key in urlParameters) {
                    if (SYSTEM_DATA.RETAINED_URL_PARAMS.indexOf(key) == -1) {
                        continue;
                    }

                    crestronUrlParams[key] = urlParameters[key];
                }

                sessionService.set('passiveregistration', (urlParameters.registrationmode || '').toLowerCase() == 'passive');
                sessionService.set('crestronmode', urlParameters.crestronmode);
                sessionService.set('urlparameters', crestronUrlParams);
            });
        }

        function clearWebCache() {
            raiseMessage(POST_MESSAGES.CLEAR_WEB_CACHE);
        }

        function raiseChannelLoaded(channelName) {
            var message = postMessagingUtil.getLoadedMessageFromCrestronMode(getCrestronMode());
            var error;
            // if channel is not supported, populate error
            if (supportedLoadedChannel.indexOf(channelName) === -1) {
                error = POST_MESSAGES_ERROR.LOADED_NON_CRESTRON_CHANNEL;
            }

            raiseMessage(message, error);
        }

        function raiseMessage(method, error) {
            var postMessagingService = getPostMessagingService();
            var event = {
                message: POST_MESSAGES.NAME,
                type: method
            };
            if (error) {
                event.error = error;
            }

            postMessagingService.raiseMessage(event);
        }

        // Define the instance methods and properties
        MercuryDriver.prototype = {
            // Public interface
            id: 'browser.mercury',
            platform: {
                getAppspaceDeviceType: getAppspaceDeviceType,
                getFeatureSupport: getFeatureSupport,
                getSerialNumber: getSerialNumber,
                updateSerialNumber: updateSerialNumber,
                clearSessionStorage: clearSessionStorage,
                getModel: getModel,
                clearWebCache: clearWebCache,
                raiseChannelLoaded: raiseChannelLoaded,
            },
        };

        // Return the constructor for the class
        return (MercuryDriver);
    }]);
})(window.angular);

(function ($angular) {

    "use strict";

    var app = $angular.module('hal');

    app.factory("NodeWebKitDriver", ["$window", "$q", function($window, $q) {

        // filter this, if NodeWebkit library not detected
        if (typeof process !== "object" || !process.versions || !process.versions["node-webkit"]) {
            return function() {};
        }

        // init common NodeWebkit modules
        var os = require('os');
        var fs = require('fs');
        var _path = require('path');

        // create root folder
        createDirectory("");

        // Define the constructor function.
        function NodeWebKitDriver() {

        }

        // helper functions and utils

        function resolvePath(path) {
            var root = process.cwd() + "/data/";
            root = _path.normalize(root);
            path = _path.normalize(path);
            path = root + path;
            path = path.replace("//", "/");
            path = path.replace("\\\\", "\\");
            return path;
        }

        function createDirHelper(dir) {
            try {
                fs.mkdirSync(resolvePath(dir));
            } catch(err) {
                if (err.code === "ENOENT") {
                    var slashIdx = dir.lastIndexOf(_path.sep);
                    if (slashIdx > 0) {
                        var parentDir = dir.substring(0, slashIdx);
                        createDirHelper(parentDir);
                        createDirHelper(dir);
                    } else {
                        throw err;
                    }
                }
                else if (err.code === "EEXIST") { return; }
                else { throw err; }
            }
        }

        function rmdirRecursive(dir, failSilent, clbk) {
            if (clbk === null || typeof clbk == 'undefined')
                clbk = function(err) {};

            fs.readdir(dir, function(err, files) {
                if (err && typeof failSilent === 'boolean' && !failSilent)
                    return clbk(err);

                if(typeof failSilent === 'function')
                    clbk = failSilent;

                (function rmFile(err){
                    if (err) return clbk(err);

                    var filename = files.shift();
                    if (filename === null || typeof filename == 'undefined')
                        return fs.rmdir(dir, clbk);

                    var file = dir + _path.sep + filename;
                    fs.lstat(file, function(err, stat){
                        if (err) return clbk(err);
                        if (stat.isDirectory())
                            rmdirRecursive(file, rmFile);
                        else
                            fs.unlink(file, rmFile);
                    });
                })();
            });
        }

        // main functions

        //Get the browser name
        function getBrowserName() {
            return "Node-Webkit";
        }

        //Get CPU usage
        function getCpuUsage() {
            return $q(function(resolve, reject) {
                resolve({cpuUsage: os.loadavg()[0]});
            });
        }

        // file functions

        function createDirectory(path) {
            var q = $q.defer();
            path = _path.normalize(path);
            try {
                createDirHelper(path);
                q.resolve();
            }
            catch(err) {
                q.reject(err);
            }
            return q.promise;
        }

        function checkDirectory(path) {
            var q = $q.defer();
            path = resolvePath(path);
            fs.lstat(path, function(err, stat){
                if (err) { q.reject(err); }
                else { q.resolve(stat && stat.isDirectory()); }
            });
            return q.promise;
        }

        function removeDirectory(path) {
            var q = $q.defer();
            path = resolvePath(path);
            rmdirRecursive(path, false, function(err) {
                if (err) { q.reject(err); }
                else { q.resolve(); }
            });
            return q.promise;
        }

        function createFile(path) {
            var q = $q.defer();
            createDirectory(_path.dirname(path));
            path = resolvePath(path);
            fs.writeFile(path, '', function(err) {
                if (err) { q.reject(err); }
                else { q.resolve(); }
            });
            return q.promise;
        }

        function checkFile(path) {
            var q = $q.defer();
            path = resolvePath(path);
            fs.lstat(path, function(err, stat){
                if (err) { q.reject(err); }
                else { q.resolve(!!stat); }
            });
            return q.promise;
        }

        function moveFile(sourcePath, destinationPath) {
            var q = $q.defer();
            createDirectory(_path.dirname(destinationPath));
            sourcePath = resolvePath(sourcePath);
            destinationPath = resolvePath(destinationPath);
            fs.rename(sourcePath, destinationPath, function(err) {
                if (err) { q.reject(err); }
                else { q.resolve(); }
            });
            return q.promise;
        }

        function removeFile(path) {
            var q = $q.defer();
            path = resolvePath(path);
            fs.unlink(path, function(err) {
                if (err) { q.reject(err); }
                else { q.resolve(); }
            });
            return q.promise;
        }

        // download http data to file
        function downloadHttp(remotePath, localPath) {
            var q = $q.defer();
            createDirectory(_path.dirname(localPath));
            remotePath = require('url').parse(remotePath);
            var http = remotePath.protocol === "http:" ? require('http') : require('https');
            var fStream = fs.createWriteStream(resolvePath(localPath));
            var req = http.get({host: remotePath.host, path: remotePath.pathname}, function(res) {
                res.on('data', function(data) {
                    fStream.write(data);
                }).on('end', function() {
                    fStream.end();
                    q.resolve();
                }).on('error', function(err) {
                    fStream.end();
                    q.reject(err);
                });
            });
            req.on('error', function(err) {
                fStream.end();
                q.reject(err);
            });
            return q.promise;
        }

        //Get the IP Address for the device
        function getIpAddress() {
            return $q(function(resolve, reject) {
                var interfaces = os.networkInterfaces();
                var addresses = [], address;
                for (var i in interfaces) {
                    for (var j in interfaces[i]) {
                        address = interfaces[i][j];
                        if (address.family === 'IPv4' && !address.internal) {
                            addresses.push(address.address);
                        }
                    }
                }
                address = addresses[0] || "127.0.0.1";
                resolve({address: address});
            });
        }

        //Define the instance methods and properties
        NodeWebKitDriver.prototype = {
            //Public interface
            id: "browser.nodewebkit",
            platform: {
                getBrowserName: getBrowserName,
                getCpuUsage: getCpuUsage
            },
            file: {
                createDirectory: createDirectory,
                checkDirectory: checkDirectory,
                removeDirectory: removeDirectory,
                createFile: createFile,
                checkFile: checkFile,
                moveFile: moveFile,
                removeFile: removeFile
            },
            network: {
                downloadHttp: downloadHttp,
                getIpAddress: getIpAddress
            }
        };

        //Return the constructor for the class
        return( NodeWebKitDriver );
    }]);

})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('PolyDriver', ["$window", "$q", "dataUtil", "driverUtil", "logService", "SYSTEM_DATA", "MEDIA_DIRECTIVE", function(
        $window,
        $q,
        dataUtil,
        driverUtil,
        logService,
        SYSTEM_DATA,
        MEDIA_DIRECTIVE
    ) {
        function PolyDriver() {
            this.id = 'browser.poly';

            this.platform = {
                getModel: getModel,
                getSerialNumber: getSerialNumber,
                getManufacturer: getManufacturer,
                getFingerprint: getFingerprint,
                getFirmware: getFirmware,
                getAppspaceDeviceType: getAppspaceDeviceType,
                getRuntimeEnvironments: getRuntimeEnvironments,
                clearSessionStorage: clearSessionStorage
            };

            this.network = {
                getMacAddress: getMacAddress
            };

            this.media = {
                getMediaDirective: getMediaDirective
            };

            var model;
            var serialNumber;
            var macAddress;
            var firmware;
            var LOG_KEY = 'appspace.core.hal';
            var VIDEO_ATTRIBUTES = 'crossorigin="anonymous" muted poster="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="'; // 1px transparent png

            function init() {
                var userAgent = ($window.navigator || {}).userAgent || '';
                var DEVICE_INFO_REGEX = /\(([^)]+)\)/g;
                var regexMatch;
                var lastMatch;
                while (!dataUtil.isNullOrUndefined(regexMatch = DEVICE_INFO_REGEX.exec(userAgent))) {
                    lastMatch = regexMatch;
                }

                if (!lastMatch) {
                    logService.error(LOG_KEY, 'User agent information for poly device not found');
                    return;
                }

                var userAgentSubstring = userAgent.substring(0, lastMatch.index);
                var tokens = userAgentSubstring.trim().split(' ');
                firmware = tokens[tokens.length - 1];
                var deviceInfoRaw = lastMatch[0];
                var deviceInfoArray = dataUtil.parseInlineStyle(deviceInfoRaw.substring(1, deviceInfoRaw.length - 1));
                if (!deviceInfoArray.length) {
                    logService.error(LOG_KEY, 'Error parsing user agent information for poly device');
                    return;
                }

                for (var i = 0; i < deviceInfoArray.length; i++) {
                    var deviceInfoObject = deviceInfoArray[i];
                    switch (deviceInfoObject.property) {
                        case 'model':
                            model = deviceInfoObject.value;
                            break;
                        case 'serial':
                            serialNumber = deviceInfoObject.value;
                            break;
                        case 'mac':
                            macAddress = deviceInfoObject.value;
                            break;
                    }
                }
            }

            function getManufacturer() {
                return 'POLY';
            }

            function getModel() {
                return model;
            }

            function getSerialNumber() {
                return $q.resolve(serialNumber);
            }

            function getFingerprint() {
                return $q.resolve({
                    fingerprint: serialNumber,
                    type: SYSTEM_DATA.FINGERPRINT_TYPE.HARDWARE
                });
            }

            function getFirmware() {
                return $q.resolve(firmware);
            }

            function getMacAddress() {
                return $q.resolve(macAddress);
            }

            function getMediaDirective(elementType) {
                var result = {
                    directive: elementType,
                    attributes: ''
                };

                if (elementType === MEDIA_DIRECTIVE.VIDEO) {
                    result.attributes = VIDEO_ATTRIBUTES;
                }

                return result;
            }

            function getAppspaceDeviceType() {
                return 'POLY';
            }

            function getRuntimeEnvironments() {
                return ['poly'];
            }

            function clearSessionStorage() {
                driverUtil.resetSessionStorage();
            }

            init();
        }

        // Return the constructor for the class
        return (PolyDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('PwaDriver', ["$q", "$window", "$document", "$injector", "$timeout", "logService", "navigationService", "pwaCacheService", "mediaUtil", "dataUtil", "UrlUtil", "ROUTE", "MEDIA_DIRECTIVE", "FEATURE_CODE", "DRIVER_ID", "CACHE_COMMAND", function(
        $q,
        $window,
        $document,
        $injector,
        $timeout,
        logService,
        navigationService,
        pwaCacheService,
        mediaUtil,
        dataUtil,
        UrlUtil,
        ROUTE,
        MEDIA_DIRECTIVE,
        FEATURE_CODE,
        DRIVER_ID,
        CACHE_COMMAND
    ) {
        var windowNavigator = $window.navigator || {};
        var hasServiceWorker = 'serviceWorker' in windowNavigator &&
            // some device like Zoom Rooms, it has serviceWorker but the serviceWorker.register is null
            windowNavigator.serviceWorker.register !== null;
        var logkey = 'appspace.core.hal';
        var isReadyPromise = $q.defer();
        var hasInit = false;
        var isAndroidDevice;
        var userAgent;
        var shouldMuteVideo = 'yes';
        var UNSUPPORTED_LOW_BANDWIDTH_PLATFORMS_REGEX = /^win|mac/gi;
        var ENFORCE_URL_POLICY_MODE = 'enforceUrlPolicy';
        var DEFAULT_REFERENCE_DEVICE_ID_KEY = 'referencedeviceid';
        var isLowBandwidthSupported = true;
        var isServiceWorkerRegistered = false;
        var isTransparentCacheSupported = false;
        var cacheManifest = {};
        var DEPRECATED_SERVICE_WORKER_FILE = 'sw.js';
        var DELAY_INITIALIZE_MESSAGE_CHANNEL = 1000;
        var MAX_CACHEABLE_FILE_SIZE = 300000000;
        var CACHE_REPOSITORY = {
            CONTENT: 'signs.content',
            FALLBACK_CONTENT: 'signs.fallbackcontent',
        };
        var CONTENT_PLAYBACK_PARAMS = ['deviceid', 'context'];
        var isMediaReadyPromise = $q.defer();

        // Define the constructor function.
        function PwaDriver() {
            try {
                var localDocument = $document[0];
                // manage security context (APC-5303)
                // eslint-disable-next-line no-self-assign
                localDocument.domain = localDocument.domain;
                log('debug', 'Set domain to self');
            } catch (exception) {
                log('warn', 'Unable to set document.domain:' + JSON.stringify(exception));
            }

            userAgent = windowNavigator.userAgent || '';
            isAndroidDevice = userAgent.match(/android/gi);
            // PWA running on windows and mac does not support low bandwidth mode
            isLowBandwidthSupported = !(windowNavigator.platform || '').match(UNSUPPORTED_LOW_BANDWIDTH_PLATFORMS_REGEX);
            mediaUtil.isVideoPlayableWithAudio().then(function(res) {
                shouldMuteVideo = res ? 'no' : 'yes';
                // Register service worker
                initializedServiceWorker();
            });
        }

        function initializedServiceWorker() {
            if (hasInit) {
                return;
            }

            hasInit = true;
            if (!hasServiceWorker) {
                log('debug', 'No service worker found');
                isReadyPromise.resolve();
                return;
            }

            var onError = function(error) {
                isServiceWorkerRegistered = false;
                log('error', 'Service worker is not registered. Error: ' + JSON.stringify(error));
                isReadyPromise.resolve();
            };

            var serviceWorker = $window.navigator.serviceWorker;
            cleanServiceWorkers().then(function(requiresReload) {
                if (requiresReload) {
                    log('info', 'Restarting the app after cleaning old service worker.');
                    reload();
                    return;
                }

                serviceWorker.register('./service-worker.js', {scope: '/'}).then(function() {
                    serviceWorker.ready.then(function(registration) {
                        isServiceWorkerRegistered = true;
                        log('info', 'Service worker registered with scope: ' + registration.scope);
                        isTransparentCacheSupported = true;
                        // requires delay to resolve race issue
                        // where unable to retrieve service worker controller
                        $timeout(function() {
                            getServiceWorkerMessageChannel().initialize();
                            getServiceWorkerMessageChannel().subscribe(serviceWorkerMessageChannelCallback);
                            getStorageLocalStorageService().isReady().then(function() {
                                updateRepositoryManifest(getFallbackContentManifest(), CACHE_REPOSITORY.FALLBACK_CONTENT);
                                updateRepositoryManifest(getContentManifest(), CACHE_REPOSITORY.CONTENT);
                                sendManifestToServiceWorker();
                                isMediaReadyPromise.resolve();
                            });
                        }, DELAY_INITIALIZE_MESSAGE_CHANNEL);
                        isReadyPromise.resolve();
                    }, onError);
                }, onError);
            });
        }

        function serviceWorkerMessageChannelCallback(data) {
            if (!data) {
                return;
            }

            switch (data.command) {
                case CACHE_COMMAND.LOG:
                    if (!data.level) {
                        return;
                    }

                    logService[data.level](logkey, data.message);
                    break;
            }
        }

        function cleanServiceWorkers() {
            var serviceWorker = $window.navigator.serviceWorker;
            var serviceWorkerPromises = [];
            var defer = $q.defer();
            var unregisteredOldSW = false;
            var finalize = function() {
                defer.resolve(unregisteredOldSW);
            };

            serviceWorker.getRegistrations().then(function(registrations) {
                if (registrations.length === 0) {
                    finalize();
                    return;
                }

                for (var i = 0; i < registrations.length; i++) {
                    var registration = registrations[i] || {};
                    if (!registration.active || !registration.active.scriptURL || !dataUtil.includesValue(DEPRECATED_SERVICE_WORKER_FILE, registration.active.scriptURL)) {
                        continue;
                    }

                    log('info', 'Service worker is unregistered with scope: ' + registration.active.scriptURL);
                    serviceWorkerPromises.push(registration.unregister());
                    unregisteredOldSW = true;
                    break;
                }

                $q.allSettled(serviceWorkerPromises).then(finalize, finalize);
            }, function(error) {
                log('info', 'Service worker failed to get registrations: ' + JSON.stringify(error));
                finalize();
            });
            return defer.promise;
        }

        function reload() {
            $window.location.reload();
        }

        function log(type, message) {
            logService.isReady().then(function() {
                logService[type](logkey, message);
            });
        }

        // Resovles once service worker is ready
        function isReady() {
            return isReadyPromise.promise;
        }

        function getAppspaceDeviceType() {
            return 'PWA';
        }

        // Returns the support level for a specific feature
        function getFeatureSupport(feature) {
            if (!feature) {
                return;
            }

            feature = feature.toLowerCase();
            switch (feature) {
                case FEATURE_CODE.NETWORK_TRANSPARENT_CACHE:
                    return isTransparentCacheSupported;
                case FEATURE_CODE.MAX_CACHEABLE_FILE_SIZE:
                    return MAX_CACHEABLE_FILE_SIZE;
                case 'media.timeout.duration':
                    return 60000; // default to a min as content is streamed
                case 'support.video.onload.event':
                    return isAndroidDevice ? 'no' : 'yes';
                case 'media.video.should.mute':
                    return shouldMuteVideo;
                case 'support.lowbandwidth.mode':
                    return isLowBandwidthSupported;
                default:
                    return null;
            }
        }

        function shouldMuteVideoOnLoad() {
            return shouldMuteVideo === 'yes';
        }

        function getReferenceDeviceId() {
            return getConfigurationService().getReferenceDeviceId();
        }

        function getReferenceDeviceName() {
            return getConfigurationService().getReferenceDeviceName();
        }

        function getIntegrationType() {
            return getConfigurationService().getIntegrationType();
        }

        function updateReferenceDeviceId(referenceDeviceIdKey) {
            saveQueryStringInformation(referenceDeviceIdKey || DEFAULT_REFERENCE_DEVICE_ID_KEY, 'setReferenceDeviceId');
        }

        function updateReferenceDeviceName() {
            saveQueryStringInformation('devicename', 'setReferenceDeviceName');
        }

        function updateIntegrationType() {
            saveQueryStringInformation('integrationtype', 'setIntegrationType');
        }

        function saveQueryStringInformation(queryStringKey, saveMethodName) {
            var value = getUrlParameter(queryStringKey);
            if (dataUtil.isNullOrUndefined(value)) {
                return;
            }

            getConfigurationService()[saveMethodName](value);
        }

        function getUrlParameter(key) {
            return getSessionService().getUrlParameters()[key];
        }

        function isUrlPolicyModeEnforced() {
            return getUrlParameter('registrationmode') === ENFORCE_URL_POLICY_MODE;
        }

        function getConfigurationService() {
            return $injector.get('coreConfigurationService');
        }

        function getSessionService() {
            return $injector.get('sessionService');
        }

        function getMediaDirective(elementType, options) {
            var result = {
                directive: elementType,
                attributes: '',
            };
            options = options || {};
            switch (elementType) {
                case MEDIA_DIRECTIVE.AS_MEDIA_VIDEO:
                    result.attributes += 'allow-play-through="true" ';
                    break;
                case MEDIA_DIRECTIVE.VIDEO:
                    result.attributes += 'crossorigin="anonymous" ';
                    break;
            }

            return result;
        }

        function getServiceWorkerController() {
            if (!isServiceWorkerRegistered) {
                return {};
            }

            var serviceWorker = $window.navigator.serviceWorker || {};
            return serviceWorker.controller || {};
        }

        function resolveContentPath(path) {
            return $q.resolve(!isServiceWorkerRegistered ? path : path.toLowerCase());
        }

        function isManifestValid(manifest) {
            return manifest && Array.isArray(manifest.files);
        }

        function purgeInvalidCacheFromRepository(manifest, repository) {
            if (!isManifestValid(manifest)) {
                return $q.reject('Invalid manifest object');
            }

            updateRepositoryManifest(manifest, repository);
            manifest = compileCacheManifest();
            var deferred = $q.defer();
            var message = {
                command: CACHE_COMMAND.PURGE,
                usedPaths: manifest.files,
            };
            sendMessageToServiceWorker(message).then(function(result) {
                logService.debug(logkey, 'Purge complete');
                deferred.resolve(result);
            }, function(error) {
                logService.warn(logkey, 'Failed to purge');
                deferred.reject(error);
            });

            pwaCacheService.clearContentCache(manifest);

            return deferred.promise;
        }

        function sendMessageToServiceWorker(message) {
            var controller = getServiceWorkerController();
            if (!controller.postMessage) {
                return $q.reject('Service worker controller is not available');
            }

            var deferred = $q.defer();
            var messageChannel = new $window.MessageChannel();
            messageChannel.port1.onmessage = function(event) {
                var data = (event || {}).data || {};
                if (data.error) {
                    deferred.reject(data);
                    return;
                }

                deferred.resolve(data);
            };

            controller.postMessage(message, [messageChannel.port2]);
            return deferred.promise;
        }

        function clearAllResources(repository) {
            var EMPTY_MANIFEST = {files: []};
            if (repository) {
                updateRepositoryManifest(EMPTY_MANIFEST, repository);
            } else {
                // Repository is not specified; remove all cached content
                cacheManifest = {};
            }

            var updatedManifest = compileCacheManifest();
            if (!updatedManifest || !updatedManifest.files || !updatedManifest.files.length) {
                // Remove all cached content if updated manifest is now empty
                var message = {
                    command: CACHE_COMMAND.REMOVE_ALL
                };
                pwaCacheService.clearAllResources();
                return sendMessageToServiceWorker(message);
            }

            return purgeInvalidCacheFromRepository(cacheManifest[repository], repository);
        }

        function isMediaReady() {
            return isMediaReadyPromise.promise;
        }

        function updateResourceManifest(manifest, repository) {
            updateRepositoryManifest(manifest, repository);
            sendManifestToServiceWorker();
        }

        function reboot() {
            navigationService.reloadView(ROUTE.SPLASH);
        }

        function getServiceWorkerMessageChannel() {
            return $injector.get('serviceWorkerMessageChannel');
        }

        function getAvailableStorage() {
            var deferred = $q.defer();
            var storageInfo = {
                used: '',
                total: ''
            };

            var resolveResult = function() {
                deferred.resolve(storageInfo);
                return;
            };

            if (!windowNavigator.storage || !windowNavigator.storage.estimate) {
                resolveResult();
                return deferred.promise;
            }

            windowNavigator.storage.estimate().then(function(storageEstimation) {
                if (!storageEstimation) {
                    resolveResult();
                    return;
                }

                storageInfo.used = storageEstimation.usage;
                storageInfo.total = storageEstimation.quota;
                resolveResult();
            });

            return deferred.promise;
        }

        function getStorageLocalStorageService() {
            return $injector.get('storageLocalStorageService');
        }

        function getContentFallbackService() {
            return $injector.get('contentFallbackService');
        }

        function getFallbackContentManifest() {
            return getContentFallbackService().getCacheManifest() || {};
        }

        function getContentManifest() {
            return dataUtil.tryParseJSON(getStorageLocalStorageService().get('device.sync', 'manifest')) || {};
        }

        function updateRepositoryManifest(manifest, repository) {
            repository = repository || CACHE_REPOSITORY.CONTENT;
            manifest = manifest || {};
            manifest.files = manifest.files || [];
            if (!isManifestValid(manifest)) {
                manifest.files = [];
            }

            cacheManifest[repository] = processFileUrlsInManifest(manifest);
        }

        function compileCacheManifest() {
            var compiledManifest = {};
            var compiledFiles = [];
            for (var key in cacheManifest) {
                var repositoryManifest = cacheManifest[key];
                compiledManifest = dataUtil.mergeObject(compiledManifest, repositoryManifest);
                compiledFiles = compiledFiles.concat(repositoryManifest.files);
            }

            compiledManifest.files = compiledFiles;
            compiledManifest.removableQueryParams = CONTENT_PLAYBACK_PARAMS;
            // Threshold of max file size of cache-able content
            compiledManifest.maxFileSize = MAX_CACHEABLE_FILE_SIZE;
            return compiledManifest;
        }

        function processFileUrlsInManifest(manifest) {
            if (!isManifestValid(manifest)) {
                return manifest;
            }

            var processedManifest = $angular.copy(manifest);
            var processedFiles = processedManifest.files || [];
            for (var i = 0; i < processedFiles.length; i++) {
                var file = processedFiles[i] || {};
                if (!file.url) {
                    continue;
                }

                file.url = UrlUtil.removeQueryString(file.url, CONTENT_PLAYBACK_PARAMS);
            }

            return processedManifest;
        }

        function sendManifestToServiceWorker() {
            var manifest = compileCacheManifest();
            if (!isManifestValid(manifest)) {
                logService.debug(logkey, 'Failed to send manifest to service worker: Manifest is invalid');
                return;
            }

            const message = {
                command: CACHE_COMMAND.SET_MANIFEST,
                manifest: manifest
            };

            getServiceWorkerMessageChannel().sendServiceWorkerMessage(message);
        }

        function postRegistrationCallback() {
            pwaCacheService.start();
            return $q.resolve();
        }

        // Define the instance methods and properties
        PwaDriver.prototype = {
            // Public interface
            id: DRIVER_ID.PWA,
            isReady: isReady,
            media: {
                resolveContentPath: resolveContentPath,
                clearContentCache: purgeInvalidCacheFromRepository,
                getMediaDirective: getMediaDirective,
                isReady: isMediaReady,
            },
            network: {
                clearAllResources: clearAllResources,
                updateResourceManifest: updateResourceManifest,
            },
            platform: {
                reboot: reboot,
                getAppspaceDeviceType: getAppspaceDeviceType,
                getAppPackageType: getAppspaceDeviceType,
                getAvailableStorage: getAvailableStorage,
                getFeatureSupport: getFeatureSupport,
                shouldMuteVideoOnLoad: shouldMuteVideoOnLoad,
                getReferenceDeviceId: getReferenceDeviceId,
                getReferenceDeviceName: getReferenceDeviceName,
                updateReferenceDeviceId: updateReferenceDeviceId,
                updateReferenceDeviceName: updateReferenceDeviceName,
                getIntegrationType: getIntegrationType,
                updateIntegrationType: updateIntegrationType,
                postRegistrationCallback: postRegistrationCallback,
                isUrlPolicyModeEnforced: isUrlPolicyModeEnforced
            },
        };

        // Return the constructor for the class
        return (PwaDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('QbicDriver', ["$q", "$injector", "SYSTEM_DATA", "dataUtil", "DateTimeUtil", function(
        $q,
        $injector,
        SYSTEM_DATA,
        dataUtil,
        DateTimeUtil
    ) {
        var HOST = 'http://localhost:8080';
        var URL = {
            GET_LED_TYPES: HOST + '/v1/led',
            GET_DEVICE_INFO: HOST + '/v1/info',
            SET_LED_COLOR: HOST + '/v1/led/{ledType}'
        };
        var DISABLE_LIGHT_COLOR = {
            RED: 0,
            GREEN: 0,
            BLUE: 0
        };
        var NEXT_REQUEST_SECONDS = 1;
        var lastRequestTime;
        var deviceInfo;

        function QbicDriver() {
        }

        function getHttpWrapper() {
            return new ($injector.get('HttpWrapper'))();
        }

        function getDeviceInfo() {
            var now = new Date();
            if (lastRequestTime && deviceInfo && Math.abs(new DateTimeUtil().getSecondsDifference(lastRequestTime, now)) < NEXT_REQUEST_SECONDS) {
                return $q.resolve(deviceInfo);
            }

            var defer = $q.defer();
            getHttpWrapper().handlerGet(URL.GET_DEVICE_INFO).then(function(result) {
                if (!result || !result.data || !dataUtil.isObject(result.data.results)) {
                    defer.reject('Invalid result');
                    return;
                }

                lastRequestTime = now;
                deviceInfo = result.data.results;
                defer.resolve(deviceInfo);
            }, defer.reject);
            return defer.promise;
        }

        function getModel() {
            var defer = $q.defer();
            getDeviceInfo().then(function(result) {
                defer.resolve(result.model_id);
            }, defer.reject);
            return defer.promise;
        }

        function getIpAddress() {
            var defer = $q.defer();
            getDeviceInfo().then(function(result) {
                defer.resolve({
                    address: result.ip_address
                });
            }, defer.reject);
            return defer.promise;
        }

        function getMacAddress() {
            var defer = $q.defer();
            getDeviceInfo().then(function(result) {
                defer.resolve(result.wifi_mac);
            }, defer.reject);
            return defer.promise;
        }

        function getSerialNumber() {
            var defer = $q.defer();
            getDeviceInfo().then(function(result) {
                defer.resolve(result.serial_number);
            }, defer.reject);
            return defer.promise;
        }

        function getFingerprint() {
            var defer = $q.defer();
            getSerialNumber().then(function(result) {
                defer.resolve({
                    fingerprint: result,
                    type: SYSTEM_DATA.FINGERPRINT_TYPE.HARDWARE
                });
            }, defer.reject);
            return defer.promise;
        }

        function getCpuUsage() {
            var defer = $q.defer();
            getDeviceInfo().then(function(result) {
                var cpuUsagePercent = (dataUtil.isString(result.cpu_usage_percent) ? result.cpu_usage_percent : '0').replace('%', '');
                defer.resolve({
                    cpuUsage: isNaN(cpuUsagePercent) ? 0 : parseFloat(cpuUsagePercent)
                });
            }, defer.reject);
            return defer.promise;
        }

        function getMemoryUsage() {
            var defer = $q.defer();
            getDeviceInfo().then(function(result) {
                defer.resolve({
                    capacity: dataUtil.convertMegabyteToByte(result.total_memory_size_mb),
                    availableCapacity: dataUtil.convertMegabyteToByte(result.available_memory_size_mb)
                });
            }, defer.reject);
            return defer.promise;
        }

        function getAvailableStorage() {
            var defer = $q.defer();
            getDeviceInfo().then(function(result) {
                defer.resolve({
                    used: dataUtil.convertMegabyteToByte(result.total_storage_size_mb - result.available_storage_size_mb),
                    total: dataUtil.convertMegabyteToByte(result.total_storage_size_mb)
                });
            }, defer.reject);
            return defer.promise;
        }

        function getFirmware() {
            var defer = $q.defer();
            getDeviceInfo().then(function(result) {
                defer.resolve(result.fw_version);
            }, defer.reject);
            return defer.promise;
        }

        function getSetLedColorUrl(ledType) {
            return URL.SET_LED_COLOR.replace('{ledType}', ledType);
        }

        function setLedColor(red, green, blue) {
            if (dataUtil.isNullOrUndefined(red) || dataUtil.isNullOrUndefined(green) || dataUtil.isNullOrUndefined(blue)) {
                return $q.reject('Invalid color');
            }

            var defer = $q.defer();
            getHttpWrapper().handlerGet(URL.GET_LED_TYPES).then(function(result) {
                if (!result || !result.data || !Array.isArray(result.data.results) || result.data.results.length === 0) {
                    defer.reject('Unknown LED Type');
                    return;
                }

                var ledTypes = result.data.results;
                getHttpWrapper().handlerPost(getSetLedColorUrl(ledTypes[0]), {
                    red: red,
                    green: green,
                    blue: blue
                }).then(defer.resolve, defer.reject);
            }, defer.reject);
            return defer.promise;
        }

        function enableStatusLight(color) {
            return color ? setLedColor(color.r, color.g, color.b) : $q.reject('Invalid color');
        }

        function disableStatusLight() {
            return setLedColor(DISABLE_LIGHT_COLOR.RED, DISABLE_LIGHT_COLOR.GREEN, DISABLE_LIGHT_COLOR.BLUE);
        }

        QbicDriver.prototype = {
            // Public interface
            id: 'browser.qbic',
            network: {
                getIpAddress: getIpAddress,
                getMacAddress: getMacAddress
            },
            platform: {
                getModel: getModel,
                getSerialNumber: getSerialNumber,
                getFingerprint: getFingerprint,
                getCpuUsage: getCpuUsage,
                getMemoryUsage: getMemoryUsage,
                getAvailableStorage: getAvailableStorage,
                getFirmware: getFirmware,
                enableStatusLight: enableStatusLight,
                disableStatusLight: disableStatusLight
            }
        };

        // Return the constructor for the class
        return (QbicDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('SSSDDriver', ["$q", "$timeout", "FileEntry", "FileError", "FileUtil", "pathService", "RemoteEvent", "REMOTEVCODE", "REMOTEEVENTTYPE", "VersionCompare", "TimeoutUtil", function($q, $timeout, FileEntry, FileError, FileUtil, pathService, RemoteEvent,
        REMOTEVCODE, REMOTEEVENTTYPE, VersionCompare, TimeoutUtil) {
        // filter this, if is not sssd
        if (typeof Common === 'undefined') {
            return function() { };
        }

        var appId = curWidget.id;
        var fs = new FileSystem();
        var widgetAPI = new Common.API.Widget();
        var tvKey = new Common.API.TVKeyValue();
        var remoteServiceEventHandler = null;
        var currentVideoDecoder = 0;
        // max filename/directory character length
        var maxChar = 255;
        var minVersionRequired = '0';

        // Samsung Smart Signage Displays
        function SSSDDriver() {

        }

        function shortenFilePath(path) {
            var paths = path.split('/');

            if (paths.length > 0) {
                for (var i in paths) {
                    if (paths[i].length > maxChar) {
                        paths[i] = md5(paths[i]);
                    }
                }
                path = paths.join('/');
            }

            return path;
        }

        // Convert to absolute path
        function resolveUrl(path) {
            var basePath = '/mtd_down/common/' + appId + '/';

            // shorten cache path
            if (path == '/cache') {
                path = '/c';
            }

            // already absolute path
            if (path.indexOf(basePath) === 0) {
                return path;
            }

            // remove possible hardcode absolute
            var sysPath = '/mtd_down/common/';
            var index = path.indexOf(sysPath);
            if (index === 0) {
                path = path.substring(index + sysPath.length);
            }

            // remove possible hardcode app path
            if (path.indexOf('/' + appId) === 0) {
                path = path.substring(appId.length + 1);
            }

            if (path.indexOf(appId) === 0) {
                path = path.substring(appId.length);
            }

            return shortenFilePath(pathService.join(basePath, path));
        }

        // Convert to relative path
        function resolveRelative(path) {
            var basePath = '/mtd_down/common';

            // shorten cache path
            if (path == '/cache') {
                path = '/c';
            }

            var index = path.indexOf(basePath);

            if (index === 0) {
                path = path.substring(index + basePath.length);
            }

            // app path
            if (path.indexOf('/' + appId) !== 0 && path.indexOf(appId) !== 0) {
                return shortenFilePath(pathService.join(appId, path));
            }

            return shortenFilePath(pathService.join(path));
        }


        // file plugin
        var plugin = document.getElementById('plugin-samsung-sef');
        plugin.Open('FileSystem', '1.000', 'FileSystem'); // eslint-disable-line new-cap
        plugin.Execute('SetWidgetInfo', 2, '/mtd_down/common/' + curWidget.id); // eslint-disable-line new-cap

        function getFilePlugin() {
            return plugin;
        }


        // init
        var isInit = false;
        var readyQueue = [];
        function getTimeFromPlugin() {
            var timePlugin = document.getElementById('plugin-samsung-time');
            timePlugin.Open('Time', '1.000', 'Time'); // eslint-disable-line new-cap
            var epochTime = timePlugin.Execute('GetEpochTime'); // eslint-disable-line new-cap
            var time = epochTime * 1000;
            timePlugin.Close(); // eslint-disable-line new-cap
            return time;
        }

        function init() {
            // Monkeypatch Date Constructor
            // Use plugin time if browser date is less than 2015

            window.OriginalDate = Date;
            var date = new window.OriginalDate();
            if (date.getFullYear() <= 2015) {
                window.Date = function() {
                    if (arguments.length == 0) {
                        var time = getTimeFromPlugin();
                        return new window.OriginalDate(time);
                    } else {
                        var mainArguments = Array.prototype.slice.call(arguments);// eslint-disable-line prefer-rest-params
                        mainArguments.splice(0, 0, this);
                        return new (Function.prototype.bind.apply(window.OriginalDate, mainArguments))();
                    }
                };
                window.Date.prototype = window.OriginalDate.prototype;
                window.Date.now = function() {
                    var time = getTimeFromPlugin();
                    return new window.OriginalDate(time).getTime();
                };
                window.Date.UTC = function() {
                    var time = getTimeFromPlugin();
                    return new window.OriginalDate(time).getTime();
                };
                window.Date.parse = function(p) {
                    return window.OriginalDate.parse(p);
                };
            }
            // timeout to wait in-case plugin not loaded
            widgetAPI.sendReadyEvent();
            setTimeout(function() {
                // in previewmode, avoid to re-construct "remotePlugin", it will replace the parent version (static object)
                if (document.URL.indexOf('previewappdisplay') == -1) {
                    registerRemotePlugin();
                }

                for (var i in readyQueue) {
                    if (readyQueue.hasOwnProperty(i)) {
                        readyQueue[i].resolve('resolved in queue');
                    }
                }
                isInit = true;
                readyQueue = [];
            }, 1000);
        }
        init();


        // Platform
        function isReady() {
            var defer = $q.defer();
            if (isInit) {
                defer.resolve('resolved in ready');
            } else {
                readyQueue.push(defer);
            }
            return defer.promise;
        }

        function getCpuUsage() {
            var defer = $q.defer();
            var cpuPlugin = document.getElementById('plugin-samsung-cpu');
            cpuPlugin.OnEvent = function(cpuUsage) {
                cpuUsage = parseFloat(cpuUsage);
                var idle = 100 - cpuUsage;

                cpuPlugin.Close(); // eslint-disable-line new-cap
                defer.resolve({
                    cpuUsage: cpuUsage,
                    idle: idle
                });
            };
            cpuPlugin.Open('LFD', '1.000', 'LFD'); // eslint-disable-line new-cap
            cpuPlugin.Execute('getCPUUsage', ''); // eslint-disable-line new-cap

            return defer.promise;
        }

        function getMemoryUsage() {
            var promise = $q.defer();
            var ramPlugin = document.getElementById('plugin-samsung-cpu');

            ramPlugin.OnEvent = function(ramUsage) {
                // null isNaN true,
                var usage = isNaN(ramUsage) || !ramUsage ? 0 : parseFloat(ramUsage);

                ramPlugin.Close(); // eslint-disable-line new-cap
                // since samsung return in percentage,
                // flag percentage to true to show in percentage format
                promise.resolve({
                    capacity: 100,
                    availableCapacity: usage,
                    percentage: true,
                });
            };

            ramPlugin.Open('LFD', '1.000', 'LFD'); // eslint-disable-line new-cap
            ramPlugin.Execute('getRamUsage', ''); // eslint-disable-line new-cap

            return promise.promise;
        }

        function getModel() {
            return webapis.tv.info.getModel();
        }

        function getFirmware() {
            var promise = $q.defer();

            var firmwarePlugin = document.getElementById('plugin-samsung-cpu');
            if (firmwarePlugin === undefined) {
                promise.resolve('-');
            } else {
                firmwarePlugin.Open('LFD', '1.000', 'LFD'); // eslint-disable-line new-cap
                var fw = firmwarePlugin.Execute('getSoftwareVersion'); // eslint-disable-line new-cap
                firmwarePlugin.Close(); // eslint-disable-line new-cap
                promise.resolve(fw);
            }

            return promise.promise;
        }

        // get minimmmum version required
        function getMinVersionRequired() {
            return minVersionRequired;
        }

        // Get if firmware meet minimum requirement
        function isMeetMinFirmware() {
            var promise = $q.defer();
            var vc = new VersionCompare();
            getFirmware().then(function(fw) {
                var fwArr = fw.split('-');
                var fwNumber = '0';
                for (var i = 0; i < fwArr.length; i++) {
                    if (!isNaN(parseFloat(fwArr[i]))) {
                        fwNumber = parseFloat(fwArr[i]);
                        break;
                    }
                }
                var res = vc.compare(fwNumber.toString(), minVersionRequired);
                if (res) {
                    promise.resolve();
                } else {
                    var err = {};
                    err.minFWVersion = getMinVersionRequired();
                    err.currentFWVersion = fw;
                    promise.reject(err);
                }
            });

            return promise.promise;
        }

        function getFingerprint() {
            var defer = $q.defer();

            defer.resolve({
                fingerprint: webapis.tv.info.getDeviceID()
            });

            return defer.promise;
        }

        function getManufacturer() {
            return 'Samsung';
        }

        function getAvailableStorage() {
            var defer = $q.defer();

            var plugin = getFilePlugin();
            var usedSpace = plugin.Execute('GetUsedSize'); // eslint-disable-line new-cap
            var totalSpace = plugin.Execute('GetTotalSize'); // eslint-disable-line new-cap

            // returns in bytes
            defer.resolve({
                used: usedSpace,
                total: totalSpace
            });

            return defer.promise;
        }

        function getDisplayInfo() {
            var promise = $q.defer();

            if (window.screen === undefined) {
                promise.resolve('');
            } else {
                var display = [];
                display.push({x: window.screen.availWidth, y: window.screen.availHeight});

                promise.resolve(display);
            }

            return promise.promise;
        }

        function getUsedStorage() {
            var defer = $q.defer();

            var plugin = getFilePlugin();
            var usedSpace = plugin.Execute('GetUsedSize'); // eslint-disable-line new-cap

            // returns in bytes
            defer.resolve({
                used: usedSpace
            });

            return defer.promise;
        }

        function getFeatureSupport(feature) {
            feature = feature ? feature.toLowerCase() : '';

            switch (feature) {
                case 'input.keyboard':
                    return 'none';
                case 'input.mouse':
                    return 'hardware';
                case 'file.localstorage':
                    return 'yes';
                case 'rendering.css3-transitions':
                    return 'no';
                case 'media.hls.supported':
                    return 'yes';
                case 'media.maxvideolayers':
                    return '1';
                case 'media.switchcontent.animate.supported':
                    return 'no';
                case 'media.video.decodeonpause.supported':
                    return 'no';
                case 'media.reusevideolayer':
                    return 'yes';
                case 'media.current.decoder':
                    // 4 - main decoder. 5 = sub decoder
                    if (currentVideoDecoder == 4) {
                        currentVideoDecoder = 5;
                    } else {
                        currentVideoDecoder = 4;
                    }
                    return currentVideoDecoder;
                case 'media.timeout.duration':
                    return 60000;
                case 'widget.datasource.maxtimeout':
                    return 60000;
                case 'rendering.support.viewport.width.height':
                    return 'no';
                default:
                    return null;
            }
        }

        function reboot() {
            var plugin = document.getElementById('plugin-samsung-cpu');
            plugin.Open('LFD', '1.000', 'LFD'); // eslint-disable-line new-cap
            plugin.Execute('rebootSystemCmd', ''); // eslint-disable-line new-cap
            plugin.Close(); // eslint-disable-line new-cap
        }

        function setDisplayOn(boolean) {
            if (boolean == 'false') {
                boolean = false;
            } else if (boolean == 'true') {
                boolean = true;
            }
            var panelState = (boolean) ? '00' : '01';

            var plugin = document.getElementById('plugin-samsung-cpu');
            plugin.Open('LFD', '1.000', 'LFD'); // eslint-disable-line new-cap
            // F9000100 - ON
            // F9000101 - OFF
            plugin.Execute('SendLFDMsg', 'F90001' + panelState); // eslint-disable-line new-cap
            plugin.Close(); // eslint-disable-line new-cap
        }

        // Network
        function getIpAddress() {
            var defer = $q.defer();

            var plugin = document.getElementById('plugin-samsung-network');
            plugin.Open('Network', '1.000', 'Network'); // eslint-disable-line new-cap

            // wired
            var ip = 0;
            var wiredIP = plugin.Execute('GetIP', 0); // eslint-disable-line new-cap
            var wirelessIP = plugin.Execute('GetIP', 1); // eslint-disable-line new-cap

            if (typeof wiredIP === 'string') {
                ip = wiredIP;
            } else if (typeof wirelessIP === 'string') {
                ip = wirelessIP;
            } else {
                // reject for failing to retrieve ip
                defer.reject();
            }

            // resolved finally
            defer.resolve({
                address: ip
            });

            return defer.promise;
        }

        function downloadHttp(remotePath, localPath) {
            var defer = $q.defer();
            var absPath = resolveUrl(localPath);
            var fileEntry = createFileEntry(absPath, false);
            var plugin = document.getElementById('plugin-samsung-download');

            // download event
            plugin.OnComplete = function(msg) {
                msg = msg.split('?');
                var status = msg[0];
                var result = msg[1];

                // download state
                if (status === '1000') {
                    // download complete without error
                    if (result === '1') {
                        defer.resolve(fileEntry);
                    } else {
                        if (typeof result == 'string' && !isNaN(result)) {
                            result = parseInt(result, 10);
                        }
                        defer.reject({
                            code: result
                        });
                    }

                    plugin.Close(); // eslint-disable-line new-cap
                    // progress code
                } else if (status === '1001') {
                    // console.log('progress', result);
                    // down speed
                } else if (status === '1002') {
                    // this only shows, when you testing speed with localPath as ??
                    // console.log('download speed', result);
                }
            };

            var exists = plugin['StartDownFile'];
            if (exists) {
                plugin.StartDownFile(remotePath, absPath, 0, 10); // eslint-disable-line new-cap
            }

            return defer.promise;
        }


        function createFileEntry(path, isDirectory) {
            var fileName = FileUtil.getBaseName(path);
            var absPath = resolveUrl(path);
            var fileEntry = {
                name: fileName,
                path: absPath,
                isDirectory: isDirectory,
            };

            return new FileEntry(fileEntry);
        }

        function createFileError(error, path) {
            var absPath = resolveUrl(path);
            var fileError = {
                message: error,
                path: absPath
            };

            return new FileError(fileError);
        }

        function createDirectory(path) {
            var defer = $q.defer();
            var absPath = resolveUrl(path);
            var relPath = resolveRelative(path);

            // check file exists, avoid duplicate of both directory and file at same file location
            checkFile(relPath).then(function(res) {
                if (res.exists) {
                    var fileError = createFileError('File exists on location', absPath);
                    defer.reject(fileError);
                } else {
                    checkAndCreateDirectory();
                }
            });

            // proceed to create directory
            function checkAndCreateDirectory() {
                var fileEntry = createFileEntry(absPath, true);

                checkDirectory(relPath).then(function(res) {
                    if (res.exists) {
                        defer.resolve({
                            created: false,
                            entry: fileEntry
                        });
                    } else {
                        var dirs = relPath.split('/');

                        // remove first blank element
                        if (dirs[0] === '') {
                            dirs = dirs.slice(1);
                        }

                        // remove last blank element
                        if (dirs[dirs.length - 1] === '') {
                            dirs.pop();
                        }

                        dirs = dirs.reverse();

                        // create directory, new = true, exist = false
                        var ensureDir = function(path) {
                            var dirName = dirs.pop();
                            var currentPath = pathService.join(path, dirName);

                            fs.createCommonDir(currentPath);

                            if (dirs.length > 0) {
                                ensureDir(currentPath);
                            } else {
                                defer.resolve({
                                    created: true,
                                    entry: fileEntry
                                });
                            }
                        };

                        // beginning to create directory
                        ensureDir('/');
                    }
                });
            }

            return defer.promise;
        }

        function checkDirectory(path) {
            var defer = $q.defer();
            var relPath = resolveRelative(path);
            var absPath = resolveUrl(path);

            // 0 - not exist, 1 - is directory, 2 - file
            var result = fs.isValidCommonPath(relPath);

            if (result === 1) {
                defer.resolve({
                    exists: true,
                    path: absPath
                });
            } else {
                defer.resolve({
                    exists: false
                });
            }

            return defer.promise;
        }

        function checkFile(path) {
            var defer = $q.defer();
            var absPath = resolveUrl(path);
            var relPath = resolveRelative(path);

            // 0 - not exist, 1 - is directory, 2 - file
            var result = fs.isValidCommonPath(relPath);

            if (result === 2) {
                defer.resolve({
                    exists: true,
                    path: absPath
                });
            } else {
                defer.resolve({
                    exists: false
                });
            }

            return defer.promise;
        }

        function removeDirectory(path) {
            var defer = $q.defer();
            var absPath = resolveUrl(path);
            checkDirectory(absPath).then(function(res) {
                var plugin = getFilePlugin();
                var result = plugin.Execute('Delete', absPath); // eslint-disable-line new-cap

                // success
                if (result === 1) {
                    defer.resolve();
                } else {
                    defer.reject('RemoveDirectory failed', absPath);
                }
            }, function(err) {
                var fileError = createFileError('RemoveDirectory on file', absPath);
                defer.reject(fileError);
            });

            return defer.promise;
        }

        // list file and directories under location
        function enumerateDirectory(path) {
            var defer = $q.defer();
            var tasks = [];
            var fileEntries = [];

            path = resolveUrl(path);

            // list directories
            var filePlugin = getFilePlugin();
            var result = filePlugin.Execute('GetListFiles', path); // eslint-disable-line new-cap

            // empty means no result
            if (result !== '' && result !== 1) {
                var entries = JSON.parse(result);

                for (var i in entries) {
                    if (entries.hasOwnProperty(i)) {
                        var item = entries[i];

                        // it returns zero to indificate last elemements
                        // ['abc.txt', 0]
                        if (item === 0) {
                            break;
                        }

                        // file path
                        var filePath = pathService.join(path, item);

                        var checkDirOrFile = function(filePath) {
                            var defer = $q.defer();

                            checkDirectory(filePath).then(function(res) {
                                if (res.exists) {
                                    var fileEntry = createFileEntry(filePath, true);
                                    fileEntries.push(fileEntry);
                                } else {
                                    checkFile(filePath).then(function(res) {
                                        if (res.exists) {
                                            var fileEntry = createFileEntry(filePath, false);
                                            fileEntries.push(fileEntry);
                                        }
                                    });
                                }
                                defer.resolve();
                            });

                            return defer.promise;
                        };

                        var t = checkDirOrFile(filePath);
                        tasks.push(t);
                    }
                }
            }

            // completed
            $q.all(tasks).then(function() {
                defer.resolve(fileEntries);
            });

            return defer.promise;
        }

        function createFile(path) {
            var defer = $q.defer();
            var absPath = resolveUrl(path);
            var relPath = resolveRelative(path);


            // check parent path exist, samsung auto create the directory
            // ensure consistency across all platform, create path before file
            var dirPath = FileUtil.getDirPath(path);
            checkDirectory(dirPath).then(function(res) {
                if (res.exists) {
                    checkDir();
                } else {
                    var fileError = createFileError('Directory path not exists. Do create it first.', absPath);
                    defer.reject(fileError);
                }
            });

            function checkDir() {
                // check directory exists, avoid duplicate of both directory and file at same file location
                checkDirectory(relPath).then(function(res) {
                    if (res.exists) {
                        var fileError = createFileError('Directory exists on location', absPath);
                        defer.reject(fileError);
                    } else {
                        checkAndCreateFile();
                    }
                });
            }

            function checkAndCreateFile() {
                // for common file path, there's no fail point
                checkFile(relPath).then(
                    function(res) {
                        var fileEntry = createFileEntry(absPath, false);
                        if (res.exists === false) {
                            // create file
                            var file = fs.openCommonFile(relPath, 'w');
                            fs.closeCommonFile(file);
                            defer.resolve({
                                created: true,
                                fileEntry: fileEntry
                            });
                        } else {
                            // return previous file path
                            defer.resolve({
                                created: false,
                                fileEntry: fileEntry
                            });
                        }
                    });
            }
            return defer.promise;
        }

        function writeFile(path, content) {
            var defer = $q.defer();
            path = resolveRelative(path);

            var file = fs.openCommonFile(path, 'w');
            var result = file.writeAll(content);

            if (result) {
                defer.resolve();
            } else {
                defer.reject();
            }
            fs.closeCommonFile(file);
            defer.resolve();

            return defer.promise;
        }

        function moveFile(sourcePath, destinationPath) {
            var defer = $q.defer();
            var plugin = getFilePlugin();
            var destinationDir = FileUtil.getDirPath(destinationPath);

            sourcePath = resolveUrl(sourcePath);
            destinationPath = resolveUrl(destinationPath);

            // check source isDirectory
            checkDirectory(sourcePath).then(function(res) {
                if (res.exists) {
                    // samsung auto create the last folder, pop last element,
                    // to change movedirectory behavior to become destination
                    var tmp = destinationPath.split('/');
                    if (tmp[tmp.length] === '') {
                        tmp.pop();
                    }
                    tmp.pop();

                    // new destPath
                    destinationPath = tmp.join('/');
                }
            }).finally(function() {
                // check directory
                var fileError;
                checkDirectory(destinationDir).then(function(res) {
                    if (res.exists) {
                        var result = plugin.Execute('Move', sourcePath, destinationPath); // eslint-disable-line new-cap
                        if (result === 1) {
                            var fileEntry = createFileEntry(destinationPath, false);
                            defer.resolve(fileEntry);
                        } else {
                            fileError = createFileError('MoveFile Error', destinationPath);
                            defer.reject(fileError);
                        }
                    } else {
                        fileError = createFileError('Directory not exist', destinationPath);
                        defer.reject(fileError);
                    }
                }, function(err) {
                    fileError = createFileError(err, destinationPath);
                    defer.reject(fileError);
                });
            });
            return defer.promise;
        }

        function removeFile(path) {
            var defer = $q.defer();
            var relPath = resolveRelative(path);

            var result = fs.deleteCommonFile(relPath);
            if (result) {
                defer.resolve();
            } else {
                defer.reject();
            }

            return defer.promise;
        }

        function registerRemotePlugin() {
            var remotePlugin = document.getElementById('plugin-samsung-remotecontrol');
            if (remotePlugin && remotePlugin.Open && remotePlugin.Execute) {
                remotePlugin.Open('AppCommon', '1.000', 'AppCommon'); // eslint-disable-line new-cap
                // 262 = PL_APPCOMMON_KEY_MENU
                remotePlugin.Execute('RegisterKey', 262); // eslint-disable-line new-cap
            }
        }

        // Remote Api
        // http://www.samsungdforum.com/B2B/Guide/ref50004/common_module_tvkeyvalue.html
        function createRemoteEvent(e) {
            // prevent crash
            if (e === undefined) {
                return null;
            }

            var re = new RemoteEvent();
            re.code = e.which;
            // prevent system key, for back button especially
            // re.preventDefault();

            switch (e.which) {
                case tvKey.KEY_LEFT: // left
                    re.vcode = REMOTEVCODE.left;
                    e.preventDefault();
                    break;
                case tvKey.KEY_UP: // up
                    re.vcode = REMOTEVCODE.up;
                    e.preventDefault();
                    break;
                case tvKey.KEY_RIGHT: // right
                    re.vcode = REMOTEVCODE.right;
                    e.preventDefault();
                    break;
                case tvKey.KEY_DOWN: // down
                    re.vcode = REMOTEVCODE.down;
                    e.preventDefault();
                    break;
                case tvKey.KEY_ENTER: // ENTER
                    re.vcode = REMOTEVCODE.select;
                    e.preventDefault();
                    break;
                case tvKey.KEY_RETURN:
                    re.vcode = REMOTEVCODE.back;
                    e.preventDefault();
                    break;
                case tvKey.KEY_MENU:
                    re.vcode = REMOTEVCODE.system;
                    e.preventDefault();
                    break;
                case tvKey.KEY_MUTE:
                    re.vcode = REMOTEVCODE.mute;
                    e.preventDefault();
                    break;
                case 7:// volume up
                    re.vcode = REMOTEVCODE.volume_up;
                    break;
                case 11:// volume down
                    re.vcode = REMOTEVCODE.volume_down;
                    break;
                // case tvKey.KEY_TOOLS: // Cntrl
                //     re.vcode = REMOTEVCODE.system;
                //     break;
                // case tvKey.KEY_INFO: // Cntrl
                //     re.vcode = REMOTEVCODE.back;
                //     break;
                default:
                    return null; // exit this handler for other keys
            }

            return re;
        }

        function keydownHandler(e) {
            var re = createRemoteEvent(e);
            if (re === null) {
                return;
            }
            re.eventType = REMOTEEVENTTYPE.buttonDown;

            // Notifice remote driver service
            if (remoteServiceEventHandler) {
                remoteServiceEventHandler(re);
            }

            // Prevent default if needed
            if (re.isDefaultPrevented()) {
                e.preventDefault(); // prevent the default action (scroll / move caret)
            }
        }

        function keyupHandler(e) {
            var re = createRemoteEvent(e);
            if (re === null) {
                return;
            }
            re.eventType = REMOTEEVENTTYPE.buttonUp;
            // Notifice remote driver service
            if (remoteServiceEventHandler) {
                remoteServiceEventHandler(re);
            }
            // Prevent default if needed
            if (re.isDefaultPrevented()) {
                e.preventDefault(); // prevent the default action (scroll / move caret)
            }
        }

        // Registers a callback for remote events and returns an id to unsubscribe later
        function registerRemoteEvents(callback) {
            remoteServiceEventHandler = callback;

            window.addEventListener('keydown', keydownHandler, false);
            window.addEventListener('keyup', keyupHandler, false);
        }

        // Unregisters an existing callback for remote events
        function unRegisterRemoteEvents(id) {
            remoteServiceEventHandler = null;

            window.removeEventListener('keydown', keydownHandler, false);
            window.removeEventListener('keyup', keyupHandler, false);
        }

        function isRemoteSupported() {
            return true; // Yes as we map keyboard to use remote events
        }

        function getAppspaceDeviceType() {
            return 'SAMSUNGTV';
        }

        // Media

        function getMediaDirective(elementType) {
            var res = {};
            elementType = elementType ? elementType.toLowerCase() : undefined;

            if (elementType == 'as-media-video') {
                res.directive = 'as-samsung-media-video';
                res.attributes = '';
            } else {
                res.directive = elementType;
                res.attributes = '';
            }

            return res;
        }

        // Returns the list of supported video protocols
        function getSupportedVideoProtocols() {
            return $q(function(resolve, reject) {
                var protocols = ['http', 'https', 'hls', 'multicast'];
                resolve(protocols);
            });
        }

        function getAudioPlugin() {
            var plugin = document.getElementById('plugin-samsung-cpu');
            plugin.Open('Audio', '1.000', 'Audio'); // eslint-disable-line new-cap
            return plugin;
        }

        function setVolume(volume, plugin) {
            var promise = $q.defer();
            var audPlugin = (!plugin) ? getAudioPlugin() : plugin;
            audPlugin.Execute('SetVolume', volume); // eslint-disable-line new-cap
            if (!plugin) {
                audPlugin.Close(); // eslint-disable-line new-cap
            }
            promise.resolve();
            return promise.promise;
        }
        var _volumeDelay;
        function clearVolumeDelay() {
            TimeoutUtil.cancel(_volumeDelay);
            _volumeDelay = undefined;
        }

        function volumeUp(offset) {
            // console.log('volume up:' + offset);
            var promise = $q.defer();
            if (_volumeDelay) {
                promise.resolve();
            } else {
                clearVolumeDelay();
                _volumeDelay = $timeout(function() {
                    var plugin = getAudioPlugin();
                    getCurrentVolume(plugin).then(function(currentVolume) {
                        var targetVolume = currentVolume + (offset || 1);
                        if (targetVolume > 100) {
                            targetVolume = 100;
                        }
                        if (targetVolume < 0) {
                            targetVolume = 0;
                        }
                        setVolume(targetVolume, plugin).then(function() {
                            getCurrentVolume(plugin).then(function(currentVolume) {
                                plugin.Close(); // eslint-disable-line new-cap
                                _volumeDelay = undefined;
                                promise.resolve(currentVolume);
                            });
                        });
                    });
                }, 100);
            }
            return promise.promise;
        }

        function volumeDown(offset) {
            // console.log('volume down:' + offset);
            var promise = $q.defer();
            if (_volumeDelay) {
                promise.resolve();
            } else {
                clearVolumeDelay();
                _volumeDelay = $timeout(function() {
                    var plugin = getAudioPlugin();
                    getCurrentVolume(plugin).then(function(currentVolume) {
                        var targetVolume = currentVolume - (offset || 1);
                        if (targetVolume > 100) {
                            targetVolume = 100;
                        }
                        if (targetVolume < 0) {
                            targetVolume = 0;
                        }
                        setVolume(targetVolume, plugin).then(function() {
                            getCurrentVolume(plugin).then(function(currentVolume) {
                                plugin.Close(); // eslint-disable-line new-cap
                                _volumeDelay = undefined;
                                promise.resolve(currentVolume);
                            });
                        });
                    });
                }, 100);
            }
            return promise.promise;
        }

        function getCurrentVolume(plugin) {
            var promise = $q.defer();
            var audPlugin = (!plugin) ? getAudioPlugin() : plugin;
            var volume = audPlugin.Execute('GetVolume'); // eslint-disable-line new-cap
            if (!plugin) {
                audPlugin.Close(); // eslint-disable-line new-cap
            }
            // console.log('get volume:', volume);
            promise.resolve(parseInt(volume, 10));
            return promise.promise;
        }

        function toggleVolumeMute() {
            var promise = $q.defer();
            var plugin = getAudioPlugin();
            var currentMute = plugin.Execute('GetUserMute'); // eslint-disable-line new-cap
            currentMute = parseInt(currentMute, 10);
            plugin.Execute('SetUserMute', (currentMute == 0) ? 1 : 0); // eslint-disable-line new-cap
            plugin.Close(); // eslint-disable-line new-cap
            promise.resolve();
            return promise.promise;
        }

        function getMaxLogDays() {
            // clear all logs up to 3 days ago.
            return 3;
        }

        SSSDDriver.prototype = {
            id: 'browser.sssd',
            isReady: isReady,
            file: {
                resolveUrl: resolveUrl,
                resolveRelative: resolveRelative,
                createDirectory: createDirectory,
                checkDirectory: checkDirectory,
                removeDirectory: removeDirectory,
                enumerateDirectory: enumerateDirectory,
                createFile: createFile,
                checkFile: checkFile,
                writeFile: writeFile,
                moveFile: moveFile,
                removeFile: removeFile
            },
            network: {
                downloadHttp: downloadHttp,
                getIpAddress: getIpAddress
            },
            remote: {
                registerEvents: registerRemoteEvents,
                unRegisterEvents: unRegisterRemoteEvents,
                // getSupportedEvents: getSupportedRemoteVCodes,
                isSupported: isRemoteSupported
            },
            platform: {
                reboot: reboot,
                setDisplayOn: setDisplayOn,
                getCpuUsage: getCpuUsage,
                getMemoryUsage: getMemoryUsage,
                getDisplayInfo: getDisplayInfo,
                getModel: getModel,
                getManufacturer: getManufacturer,
                getFingerprint: getFingerprint,
                getAvailableStorage: getAvailableStorage,
                getUsedStorage: getUsedStorage,
                getFeatureSupport: getFeatureSupport,
                getAppspaceDeviceType: getAppspaceDeviceType,
                getFirmware: getFirmware,
                isMeetMinFirmware: isMeetMinFirmware,
                getMinVersionRequired: getMinVersionRequired,
                getMaxLogDays: getMaxLogDays,
            },
            media: {
                getMediaDirective: getMediaDirective,
                getSupportedVideoProtocols: getSupportedVideoProtocols,
                setVolume: setVolume,
                volumeUp: volumeUp,
                volumeDown: volumeDown,
                getCurrentVolume: getCurrentVolume,
                toggleVolumeMute: toggleVolumeMute
            }
        };

        return SSSDDriver;
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('TizenDriver', ["$q", "$window", "$timeout", "$document", "FileEntry", "MemoryStorage", "ApplicationStorage", "PersistentFileStorage", "logService", "MEDIA", "MEDIA_DIRECTIVE", "RemoteEvent", "SYSTEM_DATA", "REMOTEVCODE", "ASERRORCODE", "FEATURE_CODE", "PLATFORM_TYPE", "REMOTEEVENTTYPE", "RemoteUtil", "UrlUtil", "dataUtil", "mediaUtil", "$http", function(
        $q,
        $window,
        $timeout,
        $document,
        FileEntry,
        MemoryStorage,
        ApplicationStorage,
        PersistentFileStorage,
        logService,
        MEDIA,
        MEDIA_DIRECTIVE,
        RemoteEvent,
        SYSTEM_DATA,
        REMOTEVCODE,
        ASERRORCODE,
        FEATURE_CODE,
        PLATFORM_TYPE,
        REMOTEEVENTTYPE,
        RemoteUtil,
        UrlUtil,
        dataUtil,
        mediaUtil,
        $http) {
        var loggerName = 'appspace.core.hal';
        var fingerprintKey = 'browser-generic.platform.fingerprint';
        var manufacturer = 'Samsung';
        var isEmulator = !$window.b2bapis;
        var supportFileLocalStorage = isEmulator ? 'no' : 'yes';
        var remoteServiceEventHandler;
        var internalStorage = 'internal0';
        var device = {};
        var fullRootPath = '';
        var policy = null;
        var storageInfoPromise = $q.defer();
        var keyBatches = ['ColorF2Yellow', 'ColorF3Blue', 'MediaPlay', 'MediaPause'];
        var tizenApi;
        var webApis;
        var b2bApis;
        var fileSystemApi;
        var VIDEO_ELEMENT = 'video';
        var VIDEO_STYLES = 'width:100%;height:100%;position:absolute;z-index:-1';
        var VIDEO_ATTRIBUTES = 'muted';
        var IN_APP_BROWSER_ID_PREFIX = 'tizen_inappbrowser_';
        var inAppBrowsers = [];
        var DELAY_LOAD_DURATION = 200;
        var UDP_URL_REGEX = /^udp\:\/\//i;
        var TIZEN_OS_USER_AGENT_REGEX = /Tizen\s+(\d+\.\d+)/i;
        var TIZEN_SYSTEM_INFO_PLATFORM_VERSION_KEY = 'http://tizen.org/feature/platform.version';
        var CONTENT_LENGTH_HEADER = 'Content-Length';
        var currentAppRotation;
        var APP_TO_TV_ROTATION_MAPPING = {
            'DEFAULT': {
                '-90': 'DEGREE_90',
                '0': 'DEGREE_0',
                '180': 'DEGREE_180',
                '90': 'DEGREE_270', // in SSSP6, the portrait value is inversed
                '270': 'DEGREE_90',
            },
            'TIZEN_6.5': {
                '0': 'LANDSCAPE',
                '180': 'LANDSCAPE', // inverse-landscape is not supported
                '90': 'PORTRAIT',
                '270': 'PORTRAIT' // inverse-portrait is not supported
            }
        };
        var TV_TO_APP_ROTATION_MAPPING = {
            'DEFAULT': {
                'DEGREE_0': 0,
                'LANDSCAPE': 0,
                'DEGREE_270': 90,
                'DEGREE_180': 180,
                'DEGREE_90': 270,
                'PORTRAIT': 270,
            },
            'TIZEN_6.5': {
                'LANDSCAPE': [0, 180],
                'PORTRAIT': [90, 270],
                'DEGREE_0': [0, 180],
                'DEGREE_90': [90, 270],
                'DEGREE_180': [0, 180],
                'DEGREE_270': [90, 270],
            }
        };
        var storage;
        var APP_ASSETS = {
            PATH: 'assets/',
            DIRECTORY: 'wgt-package/'
        };
        var FILE_ACCESS = {
            READ: 'r',
            WRITE: 'w'
        };
        var UNICODE_ENCODING = 'UTF-8';
        var CARD_TIMEOUT_DURATION = 60000;
        var CANCEL_DOWNLOAD_TIMEOUT_DURATION = 30000;
        var EXECUTE_UPDATE_DELAY = 0;

        // Define the constructor function.
        function TizenDriver() {
            // filter this, if Tizen library not detected
            if (!$window.tizen || !$window.webapis) {
                logService.debug(loggerName, 'Missing Tizen APIs.');
                throw new Error('Missing window.tizen and window.webapis. Please contact the developer');
            }

            tizenApi = $window.tizen;
            webApis = $window.webapis;
            b2bApis = isEmulator ? {} : $window.b2bapis;
            fileSystemApi = tizenApi.filesystem;

            updateDeviceInfo();
            updateStorageInfo();
        }

        function resolvePath(path, accessMode) {
            var deferred = $q.defer();
            if ((path || '').indexOf(APP_ASSETS.PATH) === 0) {
                // Prepend asset-file path with app assets directory so that it can be resolved correctly
                path = APP_ASSETS.DIRECTORY + path;
            }

            // Access mode truthy checking is required as the filesystem.resolve API will
            // throw an error when attempting to parse undefined to access mode enum
            if (accessMode) {
                fileSystemApi.resolve(cleanPath(path), deferred.resolve, deferred.reject, accessMode);
            } else {
                fileSystemApi.resolve(cleanPath(path), deferred.resolve, deferred.reject);
            }

            return deferred.promise;
        }

        function getFullRootPath() {
            var deferred = $q.defer();

            resolvePath(internalStorage).then(function(root) {
                deferred.resolve(root.fullPath);
            }, deferred.reject);

            return deferred.promise;
        }

        function checkFileDirectory(path) {
            var deferred = $q.defer();
            resolvePath(path).then(function(response) {
                deferred.resolve({
                    path: response.fullPath,
                    exists: true,
                    isDirectory: response.isDirectory
                });
            }, function() {
                deferred.resolve({
                    exists: false
                });
            });
            return deferred.promise;
        }

        function enumerateDirectory(path) {
            var deferred = $q.defer();

            resolvePath(path).then(function(fileItem) {
                fileItem.listFiles(function(files) {
                    var fileEntries = [];
                    for (var i = 0; i < files.length; i++) {
                        var file = files[i];
                        fileEntries.push(new FileEntry({
                            name: file.name,
                            path: file.fullPath,
                            isDirectory: file.isDirectory
                        }));
                    }

                    deferred.resolve(fileEntries);
                }, deferred.reject);
            }, deferred.reject);
            return deferred.promise;
        }

        function cleanPath(path) {
            return path ? path.replace(/\/+$/, '') : path;
        }

        function getParentDirectoryPath(path) {
            var sequence = cleanPath(path).split('/');
            if (!sequence.length) {
                return null;
            }

            sequence.pop();
            return sequence.join('/');
        }

        function getChildDirectoryPath(path) {
            var sequence = cleanPath(path).split('/');
            if (!sequence.length) {
                return null;
            }

            return sequence.pop();
        }

        function moveFile(oldPath, newPath) {
            var deferred = $q.defer();

            resolvePath(oldPath).then(function(oldPathResolve) {
                createDirectoryIfNotExists(getParentDirectoryPath(newPath)).then(function() {
                    oldPathResolve.parent.moveTo(oldPath, newPath, true, function() {
                        resolvePath(newPath).then(function(newFile) {
                            deferred.resolve(new FileEntry({
                                name: newFile.name,
                                path: newFile.fullPath,
                                isDirectory: newFile.isDirectory
                            }));
                        }, deferred.reject);
                    }, deferred.reject);
                }, deferred.reject);
            }, deferred.reject);

            return deferred.promise;
        }

        function createDirectoryIfNotExists(path) {
            var deferred = $q.defer();
            resolvePath(path).then(deferred.resolve, function() {
                var parent = getParentDirectoryPath(path);
                if (!parent) {
                    deferred.reject();
                    return;
                }

                createDirectoryIfNotExists(parent).then(function(parentFile) {
                    var child = getChildDirectoryPath(path);
                    var newFolder = parentFile.createDirectory(child);
                    if (!newFolder) {
                        deferred.reject();
                        return;
                    }

                    deferred.resolve(newFolder);
                }, deferred.reject);
            });
            return deferred.promise;
        }

        function createDirectory(path) {
            var deferred = $q.defer();
            createDirectoryIfNotExists(path).then(function(folder) {
                deferred.resolve({
                    created: false,
                    entry: new FileEntry({
                        name: folder.name,
                        path: folder.path,
                        isDirectory: true
                    })
                });
            }, deferred.reject);
            return deferred.promise;
        }

        function removeFile(path) {
            var deferred = $q.defer();
            resolvePath(path).then(function(fileObj) {
                fileObj.parent.deleteFile(fileObj.fullPath, deferred.resolve, deferred.reject);
            }, deferred.reject);
            return deferred.promise;
        }

        function removeDirectory(path) {
            var deferred = $q.defer();
            resolvePath(path).then(function(fileObj) {
                fileObj.parent.deleteDirectory(fileObj.fullPath, true, deferred.resolve, deferred.reject);
            }, deferred.reject);
            return deferred.promise;
        }

        function resolveRelative(path) {
            if (!path) {
                return;
            }

            return path.replace(fullRootPath, internalStorage);
        }

        function resolveUrl(path) {
            if (!path) {
                return;
            }

            if (path.startsWith(fullRootPath)) {
                return path;
            }

            var root = fullRootPath.split('/');
            var relative = path.split('/');
            relative.shift();

            for (var i = 0; i < relative.length; i++) {
                if (!relative[i]) {
                    continue;
                }

                root.push(relative[i]);
            }

            return root.join('/');
        }

        function getDownloadError(error, headResult, availableStorage, source, target) {
            var errorCode = headResult.status || error.code;
            var errorMessage = headResult.statusText || error.message;
            var fileSize = headResult.headers && headResult.headers(CONTENT_LENGTH_HEADER);
            if (dataUtil.isNumber(fileSize) && availableStorage < fileSize) {
                errorCode = ASERRORCODE.INSUFFICIENT_AVAILABLE_STORAGE;
                errorMessage = 'Insufficient storage available';
            }

            return {
                code: errorCode,
                error: 'Error Code: ' + errorCode + ', Message: ' + errorMessage,
                source: source,
                target: target
            };
        }

        function downloadHttp(remoteUrl, localUrl, downloadItem) {
            downloadItem = downloadItem || {};
            var deferred = $q.defer();
            var folder = getParentDirectoryPath(localUrl);
            var filename = getChildDirectoryPath(localUrl);
            createDirectoryIfNotExists(folder).then(function(parentFolder) {
                var req = new tizenApi.DownloadRequest(remoteUrl, parentFolder.fullPath, filename, 'ALL');
                req.httpHeader['Pragma'] = 'no-cache';
                var cancelDownloadTimer;
                var downloadedSize = 0;
                var totalSize = 0;
                var startCancelDownloadTimer = function() {
                    abortCancelDownloadTimer(cancelDownloadTimer);
                    cancelDownloadTimer = $timeout(function() {
                        cancelDownload(downloadId);
                        deferred.reject({
                            code: ASERRORCODE.DOWNLOAD_TIMEOUT,
                            error: 'Download timed out after ' + CANCEL_DOWNLOAD_TIMEOUT_DURATION + 'ms ' +
                                '(Progress: ' + downloadedSize + '/' + totalSize + ' bytes received)',
                            source: remoteUrl,
                            target: folder,
                        });
                    }, CANCEL_DOWNLOAD_TIMEOUT_DURATION);
                };

                var reject = function(id, error) {
                    abortCancelDownloadTimer(cancelDownloadTimer);
                    error = error || {};
                    var processRejectedObject = function(res) {
                        res = res || {};
                        getStorageSize().then(function(storage) {
                            deferred.reject(getDownloadError(error, res, storage.free, remoteUrl, folder));
                        });
                    };

                    $http.head(remoteUrl).then(processRejectedObject, processRejectedObject);
                };

                var listener = {
                    onpaused: reject,
                    oncanceled: reject,
                    oncompleted: function(id, path) {
                        abortCancelDownloadTimer(cancelDownloadTimer);
                        deferred.resolve(new FileEntry({
                            name: filename,
                            path: path,
                            isDirectory: false
                        }));
                    },
                    onfailed: reject,
                    onprogress: function(id, received, total) {
                        // Let timer continue running since the download is not progressing
                        if (id !== downloadId || downloadedSize === received) {
                            return;
                        }

                        downloadedSize = received;
                        totalSize = total;
                        // Restart timer to cancel the download if no progress is made at the next interval
                        startCancelDownloadTimer();
                    },
                };

                var downloadId = tizenApi.download.start(req, listener);
                downloadItem.cancelDownload = function() {
                    abortCancelDownloadTimer(cancelDownloadTimer);
                    cancelDownload(downloadId);
                };

                // Start timer to cancel non-progressive download
                startCancelDownloadTimer();
            });
            return deferred.promise;
        }

        function cancelDownload(downloadId) {
            if (dataUtil.isNullOrUndefined(downloadId)) {
                return;
            }

            tizenApi.download.cancel(downloadId);
        }

        function abortCancelDownloadTimer(timer) {
            if (!timer) {
                return;
            }

            $timeout.cancel(timer);
            timer = undefined;
        }

        function isReady() {
            return $q.all([storageInfoPromise.promise]);
        }

        function getFirmware() {
            return $q.resolve(device.firmwareVersion);
        }

        function getCpuUsage() {
            var deferred = $q.defer();

            var resolveObject = function(cpuUsage) {
                deferred.resolve({
                    cpuUsage: cpuUsage
                });
            };

            tizenApi.systeminfo.getPropertyValue('CPU', function(data) {
                resolveObject(Math.round(data.load * 100));
            }, function() {
                resolveObject(0);
            });

            return deferred.promise;
        }

        function getMemoryUsage() {
            var deferred = $q.defer();

            var resolveObject = function(availableCapacity, capacity) {
                deferred.resolve({
                    capacity: capacity,
                    availableCapacity: availableCapacity
                });
            };

            try {
                resolveObject(tizenApi.systeminfo.getAvailableMemory(), tizenApi.systeminfo.getTotalMemory());
            } catch (err) {
                logService.debug(loggerName, 'Does not support retrieving memory usage.');
                resolveObject(0, 0);
            }

            return deferred.promise;
        }

        function getStorageSize() {
            var deferred = $q.defer();
            var freeSpace = 0;
            var totalSpace = 0;

            var resolveObject = function(used, total, free) {
                deferred.resolve({
                    used: used || 0,
                    total: total || 0,
                    free: free || 0
                });
            };

            tizenApi.systeminfo.getPropertyValue('STORAGE', function(data) {
                if (!data || !data.units || !Array.isArray(data.units)) {
                    resolveObject();
                    return;
                }

                for (var i in data.units) {
                    var currentStorage = data.units[i];
                    freeSpace += currentStorage.availableCapacity;
                    totalSpace += currentStorage.capacity;
                }

                resolveObject(totalSpace - freeSpace, totalSpace, freeSpace);
            }, resolveObject);

            return deferred.promise;
        }

        function getAvailableStorage() {
            var deferred = $q.defer();
            getStorageSize().then(function(result) {
                deferred.resolve({
                    used: result.used,
                    total: result.total
                });
            });
            return deferred.promise;
        }

        function getIpAddress() {
            var ip;
            try {
                ip = b2bApis.b2bcontrol.getIPAddress();
            } catch (err) {
                logService.debug(loggerName, 'Could not call b2bApis.b2bcontrol.getIPAddress');
                ip = '127.0.0.1';
            }

            return $q.resolve({address: ip});
        }

        // Returns the support level for a specific feature
        function getFeatureSupport(feature) {
            switch (feature.toLowerCase()) {
                case 'input.keyboard':
                    return 'none';
                case 'file.localstorage':
                    return supportFileLocalStorage;
                case 'media.maxvideolayers':
                    return '2';
                case 'media.reusevideolayer':
                    return 'no';
                case 'media.video.createbackground':
                    return 'yes';
                case 'media.webframe.support.polyfill':
                    return 'yes';
                case 'media.webframe.onload.windowfocus':
                    return 'yes';
                case 'contentpresenter.suppress.position':
                    return 'yes';
                case 'media.webframe.handle.keyboard':
                    return 'yes';
                case 'network.maxconcurrentdownloads':
                    return 1;
                case 'media.card.onlineforcestream':
                    return 'no';
                case 'media.hls.supported':
                    return 'yes';
                case 'media.video.use.load':
                    return 'yes';
                case 'media.video.transition.supported':
                    return false;
                case 'media.video.removezindexonplay':
                    return true;
                case 'media.video.requirepauseonload':
                    return true;
                case 'support.video.onload.event':
                    return 'no';
                case FEATURE_CODE.DEVICE_REBOOT_DELAY:
                    return 10000;
                case 'signs.behaviour.reloadonresize':
                    return false;
                case FEATURE_CODE.RELOAD_ON_ORIENTATION_CHANGE:
                    return true;
                case FEATURE_CODE.CARD_TIMEOUT_DURATION:
                    return CARD_TIMEOUT_DURATION;
                case FEATURE_CODE.DASHBOARD_EMBED_SUPPORTED:
                    return device.features.supportsDashboardEmbed;
                case FEATURE_CODE.MEDIA_SUPPORT_DATA_PROTOCOL:
                    return 'no';
                default:
                    return null;
            }
        }

        function updateStorageInfo() {
            getFullRootPath().then(function(path) {
                fullRootPath = path;
                if (isEmulator) {
                    storage = $window.localStorage;
                    storageInfoPromise.resolve();
                    return;
                }

                storage = new ApplicationStorage(
                    new MemoryStorage(),
                    new PersistentFileStorage({
                        directory: fullRootPath,
                    }));
                storage.initialize($window.localStorage, {
                    clearAfterMigration: true
                }).then(storageInfoPromise.resolve, function(error) {
                    throw new Error('Failed to initialize storage: ' + JSON.stringify(error));
                });
            });
        }

        function getSerialNumberWithPromise() {
            return $q.resolve(device.serial);
        }

        function getSerialNumber() {
            return b2bApis.b2bcontrol.getSerialNumber();
        }

        function processFeatures() {
            device.features = {
                displayRotation: {
                    appToTvMapping: APP_TO_TV_ROTATION_MAPPING.DEFAULT,
                    tvToAppMapping: TV_TO_APP_ROTATION_MAPPING.DEFAULT,
                    processMenuOrientation: function(orientation) {
                        return device.features.displayRotation.tvToAppMapping[orientation];
                    }
                },
                supportsDashboardEmbed: false,
            };

            if (device.osVersion < 6.5) {
                return;
            }

            device.features.supportsDashboardEmbed = true;
            device.features.displayRotation.appToTvMapping = APP_TO_TV_ROTATION_MAPPING['TIZEN_6.5'];
            device.features.displayRotation.tvToAppMapping = TV_TO_APP_ROTATION_MAPPING['TIZEN_6.5'];
            device.features.displayRotation.processMenuOrientation = function(orientation) {
                var mappedRotations = device.features.displayRotation.tvToAppMapping[orientation];

                if (dataUtil.isNullOrUndefined(mappedRotations)) {
                    logService.debug(loggerName, 'Could not find mapped rotation for orientation: ' + orientation);
                    return;
                }

                // Since Tizen 6.5 only supports "LANDSCAPE" or "PORTRAIT" orientation,
                // simply return the previously set rotation value to avoid rotation check failure,
                // else, return the first mapped rotation value
                return dataUtil.includesValue(currentAppRotation, mappedRotations) ?
                    currentAppRotation : mappedRotations[0];
            };
        }

        function updateDeviceInfo() {
            device.firmwareVersion = webApis.productinfo.getFirmware();
            device.platform = webApis.productinfo.getRealModel();
            device.model = manufacturer + '-' + device.platform;
            device.serial = isEmulator ? '' : getSerialNumber();
            device.uuid = device.serial;
            device.isEmulator = isEmulator;
            device.osVersion = getTizenOsVersion();
            processFeatures();

            if (!device.isEmulator) {
                return;
            }

            logService.debug(loggerName, 'Appspace is running on emulator.');
            var fingerprint = $window.localStorage.getItem(fingerprintKey);
            if (!fingerprint) {
                fingerprint = uuid.v1();
                $window.localStorage.setItem(fingerprintKey, fingerprint);
            }

            device.serial = fingerprint;
            device.uuid = fingerprint;
        }

        // Reloads the application
        function reload() {
            // hard reload from the server
            $window.location.reload(true);
        }

        // Restarts the application
        function reboot() {
            if (isEmulator) {
                logService.debug(loggerName, 'Not executing reboot as in emulator');
                return;
            }

            b2bApis.b2bcontrol.rebootDevice(function() {
                logService.debug(loggerName, 'Success sending reboot command.');
            }, function(err) {
                logService.debug(loggerName, 'Failed to call device reboot.', err);
            });
        }

        function executeUpdate() {
            logService.debug(loggerName, 'Executing app update');
            var deferred = $q.defer();
            deferred.resolve();
            $timeout(function applyUpdate() {
                logService.debug(loggerName, 'Rebooting to apply app update.');
                reboot();
            }, EXECUTE_UPDATE_DELAY);
            return deferred.promise;
        }

        function getAppspaceDeviceType() {
            return 'TIZENTV';
        }

        function getModel() {
            return device.model;
        }

        function getOS() {
            return device.platform;
        }

        function getFingerprint() {
            return $q.resolve({
                fingerprint: device.uuid,
                type: SYSTEM_DATA.FINGERPRINT_TYPE.HARDWARE
            });
        }

        // Returns whether the device supports remote control events
        function isRemoteSupported() {
            return true; // Yes as we map keyboard to use remote events
        }

        function createRemoteEvent(e) {
            var re = new RemoteEvent(e);
            switch (e.which) {
                case 415:
                    re.vcode = REMOTEVCODE.play;
                    break;
                case 19:
                    re.vcode = REMOTEVCODE.pause;
                    break;
                case 10009:
                case 461: // Samsung back button
                    re.vcode = REMOTEVCODE.back;
                    re.isSystem = true;
                    break;
                case 405: // Samsung yellow button
                case 406: // Samsung blue button
                    re.vcode = REMOTEVCODE.system;
                    re.isSystem = true;
                    break;
            }

            // if device didn't implement
            return re.vcode ? re : RemoteUtil.processDefaultKeyboardMapping(e);
        }

        function keyHandler(e) {
            var re = createRemoteEvent(e);
            if (!re) {
                return;
            }

            re.eventType = e.data.type;
            // Notify remote driver service
            if (remoteServiceEventHandler) {
                remoteServiceEventHandler(re);
            }

            // Prevent default if needed
            if (re.isDefaultPrevented()) {
                e.preventDefault(); // prevent the default action (scroll / move caret)
            }
        }

        // Registers a callback for remote events and returns an id to unsubscribe later
        function registerRemoteEvents(callback) {
            remoteServiceEventHandler = callback;
            tizenApi.tvinputdevice.registerKeyBatch(keyBatches);
            $document.bind('keydown', {type: REMOTEEVENTTYPE.buttonDown}, keyHandler);
            $document.bind('keyup', {type: REMOTEEVENTTYPE.buttonUp}, keyHandler);
        }

        // Unregisters an existing callback for remote events
        function unRegisterRemoteEvents(id) {
            $document.unbind('keydown', keyHandler);
            $document.unbind('keyup', keyHandler);
            tizenApi.tvinputdevice.unregisterKeyBatch(keyBatches);
        }

        // Set app policy in memory
        function setAppPolicy(p) {
            if (typeof (p) === 'object') {
                policy = p;
            }
        }

        // Clear the app policy
        function clearAppPolicy() {
            policy = null;
        }

        function getAppPolicy() {
            if (policy) {
                return $q.resolve(policy);
            }

            if (isEmulator) {
                return $q.reject('not supported in emulator');
            }

            var promise = $q.defer();
            var policyPath;

            var onFail = function(err) {
                var errorCode = err.code;
                var errorText = err.name;
                promise.reject(' Error Code [' + errorCode + ']: ' + errorText);
            };

            policyPath = cleanPath(b2bApis.b2bcontrol.getURLLauncherAddress()) + '/policy/policy.json';
            var onReadSuccess = function(res) {
                if (!res || !res.data) {
                    promise.reject('bad response');
                    return;
                }

                var result = {};
                try {
                    result = typeof (res.data) === 'object' ? res.data : JSON.parse(res.data);
                } catch (error) {
                    var errorMessage = 'Error while parsing policy data from ' + policyPath;
                    logService.warn(loggerName, errorMessage);
                    promise.reject(errorMessage);
                    return;
                }

                promise.resolve(result);
            };

            $http.get(policyPath).then(onReadSuccess, onFail);
            return promise.promise;
        }

        function writeFile(filePath, data) {
            if (device.isEmulator) {
                return $q.reject('File writing is not supported on emulator');
            }

            if (!filePath) {
                return $q.reject('Missing file path');
            }

            var deferred = $q.defer();
            var fileName = getChildDirectoryPath(filePath);
            var fileDirectory = getParentDirectoryPath(filePath);
            var onFailed = function(errorMessage) {
                deferred.reject('Failed to write file ' + fileName + ' at ' + fileDirectory + ' due to: ' + errorMessage);
            };

            var doWriteFile = function() {
                createDirectoryIfNotExists(fileDirectory).then(function(fileDirectory) {
                    var createdFile = fileDirectory.createFile(fileName);
                    createdFile.openStream(FILE_ACCESS.WRITE, function(fileSystem) {
                        fileSystem.write(data);
                        fileSystem.close();
                        deferred.resolve();
                    }, function(error) {
                        onFailed('Error occurred when opening file stream: ' + JSON.stringify(error));
                    }, UNICODE_ENCODING);
                }, onFailed);
            };

            removeFile(filePath).then(doWriteFile, doWriteFile);
            return deferred.promise;
        }

        function readFile(filePath) {
            if (device.isEmulator) {
                return $q.reject('File reading is not supported on emulator.');
            }

            var deferred = $q.defer();
            var onFailed = function(errorMessage) {
                deferred.reject('Failed to read file due to: ' + errorMessage);
            };

            resolvePath(filePath, FILE_ACCESS.READ).then(function(fileDirectory) {
                if (!fileDirectory || !fileDirectory.openStream || !fileDirectory.fileSize) {
                    onFailed('File not found or empty');
                    return;
                }

                fileDirectory.openStream(FILE_ACCESS.READ, function(fileStream) {
                    var data = fileStream.read(fileDirectory.fileSize);
                    fileStream.close();
                    if (!dataUtil.isObject(data)) {
                        data = dataUtil.tryParseJSON(data);
                    }

                    deferred.resolve(data);
                }, function(error) {
                    onFailed('Failed to open stream: ' + JSON.stringify(error));
                }, UNICODE_ENCODING);
            }, onFailed);
            return deferred.promise;
        }

        function setDisplayRotation(rotation) {
            if (!b2bApis.b2bcontrol || !b2bApis.b2bcontrol.setMenuOrientation) {
                return $q.reject('No API to rotate screen');
            }

            if (dataUtil.isNullOrUndefined(rotation)) {
                return $q.reject('Rotation value is null or undefined');
            }

            var rotationValue = device.features.displayRotation.appToTvMapping[rotation.toString()];
            if (!rotationValue) {
                return $q.reject('Rotation value not valid:' + rotation);
            }

            var promise = $q.defer();
            var onSuccess = function() {
                var message = 'Successfully rotated to ' + rotationValue;
                logService.debug(loggerName, message);
                promise.resolve(message);
                currentAppRotation = rotation;
            };

            var onFail = function(error) {
                var message = 'Failed to rotate to ' + rotationValue;

                if (error) {
                    message += '. Error: ' + JSON.stringify(error);
                }

                logService.warn(loggerName, message);
                promise.reject(message);
            };

            try {
                b2bApis.b2bcontrol.setMenuOrientation(rotationValue, onSuccess, onFail);
            } catch (error) {
                onFail(error.message);
            }

            return promise.promise;
        }

        function getDisplayRotation() {
            if (!b2bApis.b2bcontrol || !b2bApis.b2bcontrol.getMenuOrientation) {
                return $q.reject('No API to retrieve screen rotation');
            }

            var tvRotation;

            try {
                tvRotation = b2bApis.b2bcontrol.getMenuOrientation();
            } catch (error) {
                logService.warn(loggerName, 'Failed to get menu orientation:', error.message);
                return $q.reject('Failed to get menu orientation');
            }

            var rotationValue = device.features.displayRotation.processMenuOrientation(tvRotation);
            return dataUtil.isNullOrUndefined(rotationValue) ?
                $q.reject('No rotation mapping for ' + tvRotation) :
                $q.resolve(rotationValue);
        }

        function getMediaDirective(elementType, options) {
            options = options || {};
            var result = {
                directive: elementType,
                attributes: '',
                styles: '',
                options: {
                    supportAnimationFrame: true,
                    supportSwapWithVideo: true,
                    delayLoadNextItem: undefined
                }
            };
            if (elementType === MEDIA_DIRECTIVE.WEB && options.webEngine === MEDIA.WEB_ENGINE_IN_APP_BROWSER) {
                result.directive = MEDIA_DIRECTIVE.IN_APP_BROWSER;
                result.options.supportAnimationFrame = false;
                result.options.supportSwapWithVideo = false;
                result.options.delayLoadNextItem = DELAY_LOAD_DURATION;
            } else if (elementType === MEDIA_DIRECTIVE.AS_MEDIA_VIDEO && UDP_URL_REGEX.test(options.mediaUrl || '')) {
                result.directive = MEDIA_DIRECTIVE.AS_TIZEN_VIDEO;
            } else if (elementType === VIDEO_ELEMENT) {
                result.attributes = VIDEO_ATTRIBUTES;
                result.styles = VIDEO_STYLES;
                // if we're playing audio content, use audio element
                if (options && options.isaudio) {
                    result.directive = 'audio';
                    result.options.useVideoLoad = true;
                }
            }

            return result;
        }

        function getRuntimeEnvironments() {
            return ['tizen'];
        }

        function createBrowser(url, onLoadStop, onLoadError) {
            var inAppBrowser = $window.open(url);
            inAppBrowser.id = IN_APP_BROWSER_ID_PREFIX + Date.now();
            inAppBrowser.addEventListener('DOMContentLoaded', onLoadStop);
            inAppBrowser.onerror = onLoadError;
            inAppBrowser.dispose = disposeInAppBrowser.bind(null, onLoadStop, inAppBrowsers, inAppBrowser);
            inAppBrowsers.push(inAppBrowser);
            return inAppBrowser;
        }

        function disposeInAppBrowser(onLoadStopCallback, browserCollection, inAppBrowser) {
            var unusedInAppBrowser;
            for (var index = 0; index < browserCollection.length; index++) {
                if (inAppBrowser.id !== browserCollection[index].id) {
                    continue;
                }

                unusedInAppBrowser = browserCollection.splice(index, 1)[0];
                break;
            }

            if (!unusedInAppBrowser) {
                return;
            }

            unusedInAppBrowser.removeEventListener('DOMContentLoaded', onLoadStopCallback);
            unusedInAppBrowser.close();
        }

        function sanitizeContainerInlineStyling(inlineStyle) {
            return mediaUtil.removesBorderRadius(inlineStyle);
        }

        function getSupportedVideoProtocols() {
            return $q.resolve(['http', 'https', 'hls', 'multicast']);
        }

        function getTizenOsVersion() {
            if (tizenApi && tizenApi.systeminfo && tizenApi.systeminfo.getCapability) {
                return parseFloat(tizenApi.systeminfo.getCapability(TIZEN_SYSTEM_INFO_PLATFORM_VERSION_KEY));
            }

            if ($window.bowser) {
                logService.debug(loggerName, 'Unable to retrieve Tizen OS version with system info API, fallback to bowser instead');
                return parseFloat($window.bowser.osversion);
            }

            if ($window.navigator && $window.navigator.userAgent) {
                logService.debug(loggerName, 'Unable to retrieve Tizen OS version with system info API and bowser, fallback to userAgent instead');
                var matches = $window.navigator.userAgent.match(TIZEN_OS_USER_AGENT_REGEX);
                if (matches.length === 2) {
                    return parseFloat(matches[1]);
                } else {
                    logService.warn(loggerName, 'Unable to retrieve Tizen OS version from user agent. Reason: Unmatched regex.');
                }
            }

            return 0;
        }

        function getLocalStorage() {
            return storage;
        }

        function setUpdateUrl(url) {
            if (!url) {
                return $q.reject('Invalid update URL');
            }

            var deferred = $q.defer();
            b2bApis.b2bcontrol.setURLLauncherAddress(url, deferred.resolve, function(error) {
                deferred.reject('setURLLauncherAddress failed with: ' + JSON.stringify(error));
            });
            return deferred.promise;
        }

        function getUpdateUrl() {
            var deferred = $q.defer();
            try {
                deferred.resolve(b2bApis.b2bcontrol.getURLLauncherAddress());
            } catch (error) {
                deferred.reject('getURLLauncherAddress failed with: ' + error.message);
            }

            return deferred.promise;
        }

        function isAppUpdateSupported(softwareUpdateUrl) {
            if (!softwareUpdateUrl) {
                return $q.resolve(false);
            }

            var softwareUpdateUrlAnatomy = UrlUtil.retrieveUrlAnatomy(softwareUpdateUrl.toLowerCase());
            var deferred = $q.defer();
            var isSupported = false;
            var finalize = function() {
                deferred.resolve(isSupported);
            };

            getUpdateUrl().then(function(currentUpdateUrl) {
                if (!currentUpdateUrl) {
                    isSupported = false;
                    finalize();
                    return;
                }

                var currentUpdateUrlAnatomy = UrlUtil.retrieveUrlAnatomy(currentUpdateUrl.toLowerCase());
                isSupported = Boolean(currentUpdateUrl && dataUtil.includesString(
                    softwareUpdateUrlAnatomy.ip, currentUpdateUrlAnatomy.ip));
                if (!isSupported) {
                    logService.debug(loggerName, 'Software update release channel workflow is only supported for Appspace-hosted URL');
                }

                finalize();
            }, finalize);
            return deferred.promise;
        }

        function getAppUpdateCapabilities() {
            return {
                scheduling: true,
                frequency: true,
                downgrade: true,
                pinning: true,
            };
        }

        function getAppUpdateHandler() {
            return {
                setUpdateUrl: setUpdateUrl,
                getUpdateUrl: getUpdateUrl,
                executeUpdate: executeUpdate,
                isSupported: isAppUpdateSupported,
            };
        }

        function getSoftwareUpdateConfiguration() {
            return {
                setUpdateUrl: setUpdateUrl,
                getUpdateUrl: getUpdateUrl,
                packageType: PLATFORM_TYPE.SAMSUNG_TIZEN,
                deviceType: getAppspaceDeviceType(),
                executeUpdate: reboot,
            };
        }

        // Define the instance methods and properties
        TizenDriver.prototype = {
            // Public interface
            id: 'browser.tizen',
            isReady: isReady,
            platform: {
                reload: reload,
                reboot: reboot,
                getOS: getOS,
                getFingerprint: getFingerprint,
                getModel: getModel,
                getCpuUsage: getCpuUsage,
                getAvailableStorage: getAvailableStorage,
                getMemoryUsage: getMemoryUsage,
                getFeatureSupport: getFeatureSupport,
                getAppspaceDeviceType: getAppspaceDeviceType,
                getAppPackageType: getAppspaceDeviceType,
                getFirmware: getFirmware,
                getSerialNumber: getSerialNumberWithPromise,
                getRuntimeEnvironments: getRuntimeEnvironments,
                getAppPolicy: getAppPolicy,
                setAppPolicy: setAppPolicy,
                clearAppPolicy: clearAppPolicy,
                getLocalStorage: getLocalStorage,
                getAppUpdateCapabilities: getAppUpdateCapabilities,
                getAppUpdateHandler: getAppUpdateHandler,
                getSoftwareUpdateConfiguration: getSoftwareUpdateConfiguration,
            },
            media: {
                setDisplayRotation: setDisplayRotation,
                getDisplayRotation: getDisplayRotation,
                getMediaDirective: getMediaDirective,
                createBrowser: createBrowser,
                sanitizeContainerInlineStyling: sanitizeContainerInlineStyling,
                getSupportedVideoProtocols: getSupportedVideoProtocols,
            },
            file: {
                resolveUrl: resolveUrl,
                resolveRelative: resolveRelative,
                createDirectory: createDirectory,
                checkDirectory: checkFileDirectory,
                removeDirectory: removeDirectory,
                enumerateDirectory: enumerateDirectory,
                checkFile: checkFileDirectory,
                moveFile: moveFile,
                removeFile: removeFile,
                readFile: readFile,
                writeFile: writeFile
            },
            network: {
                downloadHttp: downloadHttp,
                getIpAddress: getIpAddress
            },
            remote: {
                registerEvents: registerRemoteEvents,
                unRegisterEvents: unRegisterRemoteEvents,
                isSupported: isRemoteSupported,
                createRemoteEvent: createRemoteEvent
            }
        };

        // Return the constructor for the class
        return (TizenDriver);
    }]);
})(window.angular);

(function($angular) {

    "use strict";

    var app = $angular.module('hal');


    app.factory("VirtualDriver", ["$window", "$q", "FileEntry", "FileError", function($window, $q, FileEntry, FileError) {

        var fileSystem = {
            '/': {}
        };

        // Define the constructor function.
        function VirtualDriver() {

        }

        //Resovles once cordoa is ready
        function isReady() {
            var defer = $q.defer();
            defer.resolve();
            return defer.promise;
        }

        // media
        function getDisplayResolution(){
            return {width:1200, height:768};
        }

        // utility
        function splitPath(path) {
            // remove last character of /
            if (path.slice(-1) === '/') {
                path = path.slice(0, path.length - 1);
            };

            var paths = path.split('/');
            paths[0] = '/';
            return paths;
        }

        function getDirPath(path) {
            var lastMatch = path.match(/([^\/]*)\/*$/);
            if (lastMatch) {
                return path.slice(0, path.indexOf(lastMatch[0]));
            };
        }

        function getBaseName(path) {
            var lastMatch = path.match(/([^\/]*)\/*$/);
            if (lastMatch) {
                return lastMatch.length > 0 ? lastMatch[1] : lastMatch[0];
            };
        }

        function createFileError(path) {
            var fileError = new FileError({
                message: 'file not found',
                path: path
            });
            return fileError;
        }

        function ensureDirectory(path) {
            var q = $q.defer();
            var dirs = splitPath(path);
            var fileName = getBaseName(path);
            var currentDir = fileSystem;
            var created = false;

            var fileError = createFileError(path);

            // create directory
            for (var i = 0; i < dirs.length; i++) {
                var dir = dirs[i];

                if (currentDir[dir] === undefined) {
                    currentDir[dir] = {};
                    created = true;
                }
                currentDir = currentDir[dir];
            }

            var fileEntry = new FileEntry({
                name: fileName,
                path: path,
                isDirectory: true
            });

            q.resolve({
                created: created,
                entry: fileEntry
            });

            return q.promise;
        }

        function ensureFile(path) {
            var q = $q.defer();
            var dirs = splitPath(path);
            var currentDir = fileSystem;
            var fileName = getBaseName(path);
            var fileError = createFileError(path);
            var created = false;

            for (var i = 0; i < dirs.length; i++) {
                var dir = dirs[i];

                // directory not exist
                if (currentDir[dir] === undefined) {
                    q.reject({
                        error: fileError
                    });
                    return q.promise;
                }

                // last element set file
                if (i === dirs.length - 2) {
                    var lastFile = currentDir[dir][dirs[i + 1]];

                    if (lastFile === undefined) {
                        currentDir[dir][dirs[i + 1]] = 'file';
                        created = true;
                    }

                    var fileEntry = new FileEntry({
                        name: fileName,
                        path: path,
                        isDirectory: false
                    });

                    q.resolve({
                        created: created,
                        entry: fileEntry
                    });
                    return q.promise;
                };

                currentDir = currentDir[dir];
            }

            q.reject({
                error: fileError
            });
            return q.promise;
        }

        function deleteFile(path) {
            var q = $q.defer();

            var dirs = splitPath(path);
            var fileName = getBaseName(path);
            var currentDir = fileSystem;

            // first, find the sourcepath
            for (var i = 0; i < dirs.length; i++) {
                var dir = dirs[i];

                if (currentDir[dir] === undefined) {
                    q.resolve();
                    return q.promise;
                }

                if (i === dirs.length - 2) {
                    var lastFile = currentDir[dir][dirs[i + 1]];
                    if (lastFile !== 'file') {
                        q.resolve();
                        return q.promise;
                    } else {
                        delete currentDir[dir][dirs[i + 1]];

                        // return fileentry
                        var fileEntry = new FileEntry({
                            path: path,
                            name: fileName,
                            isDirectory: false
                        });

                        q.resolve();
                        return q.promise;
                    }
                };
                currentDir = currentDir[dir];
            };

            return q.promise;
        }

        // Network
        function downloadHttp(remotePath, localPath) {
            var q = $q.defer();

            // todo - temporarry auto create folder
            // var dirPath = getDirPath(localPath);

            // createDirectory(dirPath).then(function(res) {
            //     createFile(localPath)
            //         .then(function(res) {
            //             q.resolve(res);
            //         }, function(err) {
            //             q.reject(err)
            //         });
            // }, function(err) {
            //     q.reject(err);
            // });
            q.resolve([remotePath, localPath]);


            return q.promise;
        }

        function getIpAddress() {
            var q = $q.defer();
            q.resolve({
                address: "127.0.0.1"
            });
            return q.promise;
        }

        // Platform
        function getModel() {
            return "virtual";
        }

        function getOS() {
            return "virtual OS";
        }

        function getFingerprint() {
            var q = $q.defer();
            q.resolve({
                fingerprint: "12sadsaqwebk"
            });
            return q.promise;
        }

        function getLocalStorage() {
            var q = $q.defer();
            q.resolve({
                data: "data"
            });
            return q.promise;
        }

        // todo: this methods, not officially documented in cordova
        // might be break in other platform
        // tested with real android device for now
        // android, ios emulator returns false
        function getAvailableStorage() {
            var q = $q.defer();
            q.resolve({
                used: "10000",
                total: "50000000"
            });
            return q.promise;
        }

        // file
        function createDirectory(path) {
            var q = $q.defer();
            var dirPath = getDirPath(path);

            ensureDirectory(path).then(
                function(res) {
                    q.resolve(res);
                },
                function(err) {
                    q.reject(err);
                });
            return q.promise;
        }

        function checkDirectory(path) {
            var q = $q.defer();
            var dirs = splitPath(path);
            var currentDir = fileSystem;

            for (var i = 0; i < dirs.length; i++) {
                var dir = dirs[i];

                if (currentDir[dir] === undefined) {
                    q.reject({
                        exists: false
                    });
                    return q.promise;
                }
                currentDir = currentDir[dir];
            }
            q.resolve({
                exists: true
            });
            return q.promise;
        }

        function removeDirectory(path) {
            var q = $q.defer();
            var dirs = splitPath(path);
            var currentDir = fileSystem;

            for (var i = 0; i < dirs.length; i++) {
                var dir = dirs[i];

                if (i === dirs.length - 2) {
                    // last element
                    var lastDir = dirs[dirs.length - 1];

                    if (currentDir[dir][lastDir] !== undefined) {
                        delete currentDir[dir][lastDir];
                        q.resolve();
                        return q.promise;
                    };
                };

                currentDir = currentDir[dir];
            }

            q.reject();
            return q.promise;
        }

        function enumerateDirectory(path) {
            var q = $q.defer();
            var dirs = splitPath(path);
            var currentDir = fileSystem;
            var files = [];

            for (var i = 0; i < dirs.length; i++) {
                var dir = dirs[i];

                // file not exists
                if (currentDir[dir] === undefined) {
                    q.reject({
                        exists: false
                    });
                    return q.promise;
                }

                if (i === dirs.length - 1) {
                    for (var obj in currentDir[dir]) {
                        var isDirectory = false;
                        var file = currentDir[dir][obj];

                        if (typeof file === 'object') {
                            isDirectory = true;
                        };

                        var fileEntry = new FileEntry({
                            name: obj,
                            isDirectory: isDirectory,
                            path: path + "/" + obj
                        });
                        files.push(fileEntry);
                    };
                    q.resolve(files);
                    return q.promise;
                };
                currentDir = currentDir[dir];
            };

            q.resolve(files);

            return q.promise;
        }

        function createFile(path) {
            var q = $q.defer();

            ensureFile(path).then(function(res) {
                q.resolve(res);
            }, function(err) {
                q.reject(err);
            })

            return q.promise;
        }

        function checkFile(path) {
            var q = $q.defer();
            var dirs = splitPath(path);
            var currentDir = fileSystem;

            for (var i = 0; i < dirs.length; i++) {
                var dir = dirs[i];

                if (currentDir[dir] === undefined) {
                    q.resolve({
                        exists: false
                    });
                    return q.promise;
                }

                if (i === dirs.length - 1) {
                    if (currentDir[dir] === 'file') {
                        q.resolve({
                            exists: true
                        });
                        return q.promise;
                    };
                };

                currentDir = currentDir[dir];
            }
            q.resolve({
                exists: false
            });
            return q.promise;
        }

        function moveFile(sourcePath, destinationPath) {
            var q = $q.defer();

            // first delete the file
            checkFile(sourcePath).then(
                function(res) {
                    createFile(destinationPath).then(
                        function(res) {
                            var fileEntry = res;
                            removeFile(sourcePath).then(function(res) {
                                q.resolve(fileEntry);
                            }, function(err) {
                                q.reject(err);
                            });
                        },
                        function(err) {
                            q.reject(err);
                        });
                },
                function(err) {
                    q.reject(err);
                }
            );

            return q.promise;
        }

        function removeFile(path) {
            var q = $q.defer();
            deleteFile(path).then(function(res) {
                q.resolve();
            }, function(err) {
                q.reject();
            });
            return q.promise;
        }

        function getCPUUsage() {
            var q = $q.defer();
            q.resolve( {system: 12, idle: 88} );
            return q.promise;
        }

        function getUsedStorage() {
            var q = $q.defer();
            q.resolve({used: 343} );
            return q.promise;
        }

        function getManufacturer() {
            return "Dell";
        }

        function getAppPolicy() {
            return "policy string";
        }

        function setStartupURL(url) {
            return "setStartupURL " + url;
        }

        //Returns the support level for a specific feature
        function getFeatureSupport(feature) {
            feature = feature.toLowerCase();
            switch(feature) {
                case "input.keyboard":
                return "hardware";
                case "input.mouse":
                return "hardware";
                case "file.localstorage":
                return "yes";
                case "media.maxvideolayers":
                return "5";
                default:
                return null;
            }
        }

        function getBrowserVersion(){
            return "47.0";
        }

        function getBrowserName(){
            return "Chrome";
        }

        function getBrowserEngine(){
            return "webkit";
        }

        function getAppspaceDeviceType() {
            return "virtual";
        }

        //Define the instance methods and properties
        VirtualDriver.prototype = {
            //Public interface
            id: "browser.test",
            isReady: isReady,
            media: {
                getDisplayResolution: getDisplayResolution
            },
            file: {
                createDirectory: createDirectory,
                checkDirectory: checkDirectory,
                removeDirectory: removeDirectory,
                enumerateDirectory: enumerateDirectory,
                createFile: createFile,
                checkFile: checkFile,
                moveFile: moveFile,
                removeFile: removeFile
            },
            network: {
                downloadHttp: downloadHttp,
                getIpAddress: getIpAddress
            },
            platform: {
                getModel: getModel,
                getOS: getOS,
                getFingerprint: getFingerprint,
                getAvailableStorage: getAvailableStorage,
                getFeatureSupport: getFeatureSupport,
                getCPUUsage: getCPUUsage,
                getUsedStorage: getUsedStorage,
                getManufacturer: getManufacturer,
                getAppPolicy: getAppPolicy,
                setStartupURL: setStartupURL,
                getLocalStorage: getLocalStorage,
                getBrowserVersion : getBrowserVersion,
                getBrowserName : getBrowserName,
                getBrowserEngine : getBrowserEngine,
                getAppspaceDeviceType: getAppspaceDeviceType
            }
        };

        //Return the constructor for the class
        return (VirtualDriver);
    }]);

})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('Webview2Driver', ["$window", "$q", "$timeout", "$document", "dataUtil", "driverUtil", "FileUtil", "logService", "RemoteUtil", "MEDIA", "SYSTEM_DATA", "MEDIA_DIRECTIVE", "REMOTEVCODE", "REMOTEEVENTTYPE", function(
        $window,
        $q,
        $timeout,
        $document,
        dataUtil,
        driverUtil,
        FileUtil,
        logService,
        RemoteUtil,
        MEDIA,
        SYSTEM_DATA,
        MEDIA_DIRECTIVE,
        REMOTEVCODE,
        REMOTEEVENTTYPE) {
        var bridgeInstance;
        var promises = {};
        var timeoutCallbacks = {};
        var LOG_KEY = 'appspace.core.hal';
        var DEFAULT_IP_ADDRESS = '127.0.0.1';
        var TIMEOUT_DURATION = 5000;
        var STICKY_KEY_TIMEOUT_DURATION = 200;
        var basePath;
        var remoteServiceEventHandler = null;
        var isStickyKeyFired = false;
        var stickyKeyFiredTimeout;
        var inAppBrowserLoadStopCallback;
        var inAppBrowserLoadErrorCallback;
        var inAppBrowsers = [];
        var IN_APP_BROWSER_ID_PREFIX = 'webview2_inappbrowser_';
        var DISABLED_STICKY_KEYS = [REMOTEVCODE.select, REMOTEVCODE.system, REMOTEVCODE.toggle_keyboard];
        var DEVICE_INFO = {
            GET_IP_ADDRESS: 'getIpAddress',
            GET_BASE_PATH: 'getBasePath',
            ENUMERATE_DIRECTORY: 'enumerateDirectory',
            CREATE_DIRECTORY: 'createDirectory',
            CHECK_DIRECTORY: 'checkDirectory',
            REMOVE_DIRECTORY: 'removeDirectory',
            CHECK_FILE: 'checkFile',
            MOVE_FILE: 'moveFile',
            REMOVE_FILE: 'removeFile',
            DOWNLOAD_HTTP: 'downloadHttp',
            GET_FILE_JSON: 'getFileJson',
            RESOLVE_CONTENT_PATH: 'resolveContentPath',
            START_WEBSOCKET_SERVER: 'startWebsocketServer',
            STOP_WEBSOCKET_SERVER: 'stopWebsocketServer',
            KEEP_AWAKE: 'keepAwake'
        };
        var EVENT = {
            DOM_CONTENT_LOADED: 'DOMContentLoaded',
            ON_RECEIVED_APP_MESSAGE: 'OnReceivedAppMessage',
            ON_RECEIVED_DOWNLOAD_APP_MESSAGE: 'OnReceivedDownloadAppMessage',
            ON_RECEIVED_CPU_APP_MESSAGE: 'OnReceivedCpuAppMessage',
            ON_RECEIVED_STORAGE_APP_MESSAGE: 'OnReceivedStorageAppMessage',
            ON_RECIEVED_MEMORY_APP_MESSAGE: 'OnReceivedMemoryAppMessage',
            ON_RECIEVED_IP_ADDRESS_APP_MESSAGE: 'OnReceivedIpAddressAppMessage',
            ON_RECEIVED_MAC_ADDRESS_APP_MESSAGE: 'OnReceivedMacAddressAppMessage',
            ON_RECEIVED_FIRMWARE_APP_MESSAGE: 'OnReceivedFirmwareAppMessage',
            ON_RECEIVED_WEBSOCKET_APP_MESSAGE: 'OnReceivedWebsocketAppMessage',
            ON_RECEIVED_GET_DISPLAY_ROTATION_APP_MESSAGE: 'OnReceivedGetDisplayRotationAppMessage',
            ON_RECEIVED_SET_DISPLAY_ROTATION_APP_MESSAGE: 'OnReceivedSetDisplayRotationAppMessage',
            ON_RECEIVED_REGISTRATION_POLICY_APP_MESSAGE: 'OnReceivedRegistrationPolicyAppMessage',
            ON_RECEIVED_REGISTRATION_INFO_APP_MESSAGE: 'OnReceivedRegistrationInfoAppMessage',
            ON_RECEIVED_CLEAR_REGISTRATION_INFO_APP_MESSAGE: 'OnReceivedClearRegistrationInfoAppMessage',
            ON_RECEIVED_OPEN_IN_APP_BROWSER_APP_MESSAGE: 'OnReceivedOpenInAppBrowserAppMessage',
            ON_RECEIVED_CLOSE_IN_APP_BROWSER_APP_MESSAGE: 'OnReceivedCloseInAppBrowserAppMessage'
        };
        var PROMISE_METHOD = {
            RESOLVE: 'resolve',
            REJECT: 'reject'
        };
        var websocketSubsribers = [];
        var registrationPolicy;

        function Webview2Driver() {
            $document.ready(initialize);
        }

        function prepareBridgeMessage(command, payload) {
            var message = {
                commandId: getCommandId(),
                command: command,
                payload: payload
            };
            var defer = $q.defer();
            promises[message.commandId] = defer;
            var timeoutCallback = $timeout(function() {
                defer.reject('Message request timeout.');
            }, TIMEOUT_DURATION);
            timeoutCallbacks[message.commandId] = timeoutCallback;
            return {
                defer: defer,
                message: message
            };
        }

        function executeBridgeMethod(method) {
            var bridgeMessage = prepareBridgeMessage();
            bridgeInstance.async()[method](JSON.stringify(bridgeMessage.message));
            return bridgeMessage.defer.promise;
        }

        function processMessagePromise(commandId, method, argument, logError) {
            if (method === PROMISE_METHOD.REJECT && logError) {
                logService.error(LOG_KEY, argument);
            }

            promises[commandId][method](argument);
            delete promises[commandId];
        }

        function setDisplayRotation(rotation) {
            if (!dataUtil.isNumber(rotation)) {
                return $q.reject('Unsupported rotation for Webview2: ' + rotation);
            }

            var bridgeMessage = prepareBridgeMessage();
            bridgeMessage.message.rotation = parseInt(rotation, 10);
            bridgeInstance.async().sendSetDisplayRotationMessageToApp(JSON.stringify(bridgeMessage.message));
            return bridgeMessage.defer.promise;
        }

        function getDisplayRotation() {
            var bridgeMessage = prepareBridgeMessage();
            bridgeInstance.async().sendGetDisplayRotationMessageToApp(JSON.stringify(bridgeMessage.message));
            return bridgeMessage.defer.promise;
        }

        function getIpAddress() {
            var bridgeMessage = prepareBridgeMessage();
            bridgeInstance.async().sendIpAddressMessageToApp(JSON.stringify(bridgeMessage.message));
            return bridgeMessage.defer.promise;
        }

        function getMacAddress() {
            return executeBridgeMethod('sendMacAddressMessageToApp');
        }

        function getFirmware() {
            var bridgeMessage = prepareBridgeMessage();
            bridgeInstance.async().sendFirmwareMessageToApp(JSON.stringify(bridgeMessage.message));
            return bridgeMessage.defer.promise;
        }

        function getFingerprint() {
            // Fingerprint retrieval starting with MAC address, and then generated GUID as the fallback
            var promise = $q.defer();
            var getGeneratedFingerprint = function() {
                driverUtil.getFingerprint().then(promise.resolve);
            };

            getMacAddress().then(function(macAddress) {
                if (!driverUtil.isFingerprintFormatValid(macAddress)) {
                    logService.warn(LOG_KEY, 'MAC address retrieved is of unsupported fingerprint format. Defaulting to generated fingerprint...');
                    getGeneratedFingerprint();
                    return;
                }

                promise.resolve({
                    fingerprint: macAddress,
                    type: SYSTEM_DATA.FINGERPRINT_TYPE.HARDWARE
                });
            }, getGeneratedFingerprint);
            return promise.promise;
        }

        function getAppPolicy() {
            return registrationPolicy ?
                $q.resolve(registrationPolicy) :
                executeBridgeMethod('getRegistrationPolicy');
        }

        function setAppPolicy(policy) {
            if (!dataUtil.isObject(policy)) {
                return;
            }

            registrationPolicy = policy;
        }

        function clearAppPolicy() {
            registrationPolicy = undefined;
        }

        function getRegistrationInformation() {
            var bridgeMessage = prepareBridgeMessage();
            bridgeInstance.async().sendRegistrationInfoMessageToApp(JSON.stringify(bridgeMessage.message));
            return bridgeMessage.defer.promise;
        }

        function clearRegistrationInformation() {
            var bridgeMessage = prepareBridgeMessage();
            bridgeInstance.async().sendClearRegistrationInfoMessageToApp(JSON.stringify(bridgeMessage.message));
            return bridgeMessage.defer.promise;
        }

        function getAppspaceDeviceType() {
            return 'UWP';
        }

        function getFeatureSupport(feature) {
            feature = feature.toLowerCase();
            switch (feature) {
                case 'file.localstorage':
                    return 'yes';
                case 'network.maxconcurrentdownloads':
                    return 4;
                default:
                    return null;
            }
        }

        function getRuntimeEnvironments() {
            return ['uwp'];
        }

        function getBasePath() {
            var defer = $q.defer();
            var onSuccess = function(data) {
                if (!data || !data.Result) {
                    defer.reject('Base URL not found');
                    return;
                }

                defer.resolve(data.Result.Path);
            };

            var onFailed = function(error) {
                defer.reject('Failed to get base URL: ' + error);
            };

            var payload = {};
            sendMessage(DEVICE_INFO.GET_BASE_PATH, payload).then(onSuccess, onFailed);
            return defer.promise;
        }

        function resolveUrl(path) {
            return path.includes(basePath) ? path : basePath + '/' + FileUtil.cleanPath(basePath, path);
        }

        function enumerateDirectory(path) {
            var defer = $q.defer();
            var onSuccess = function(data) {
                if (!data || !data.Result) {
                    defer.reject('Directory not found.');
                    return;
                }

                var directory = [];
                for (var i = 0; i < data.Result.Files.length; i++) {
                    directory.push({
                        name: data.Result.Files[i].Name,
                        path: data.Result.Files[i].Path,
                        isDirectory: data.Result.Files[i].IsDirectory
                    });
                }

                defer.resolve(directory);
            };

            var onFailed = function(error) {
                defer.reject('Failed to enumerate directory: ' + error);
            };

            var payload = {
                path: path
            };

            sendMessage(DEVICE_INFO.ENUMERATE_DIRECTORY, payload).then(onSuccess, onFailed);
            return defer.promise;
        }

        function createDirectory(path) {
            var defer = $q.defer();
            var onSuccess = function(data) {
                if (!data || !data.Result) {
                    defer.reject('Directory not found.');
                    return;
                }

                defer.resolve({
                    created: true,
                    entry: {
                        name: data.Result.Name,
                        path: data.Result.Path,
                        isDirectory: data.Result.IsDirectory
                    }
                });
            };

            var onFailed = function(error) {
                defer.reject('Failed to create directory: ' + error);
            };

            var payload = {
                path: path
            };

            sendMessage(DEVICE_INFO.CREATE_DIRECTORY, payload).then(onSuccess, onFailed);
            return defer.promise;
        }

        function checkDirectory(path) {
            var defer = $q.defer();
            var onSuccess = function(data) {
                if (!data || !data.Result) {
                    defer.reject('Directory not found.');
                    return;
                }

                defer.resolve({
                    name: data.Result.Name,
                    path: data.Result.Path,
                    isDirectory: data.Result.IsDirectory
                });
            };

            var onFailed = function(error) {
                defer.reject('Failed to check directory: ' + error);
            };

            var payload = {
                path: path
            };

            sendMessage(DEVICE_INFO.CHECK_DIRECTORY, payload).then(onSuccess, onFailed);
            return defer.promise;
        }

        function removeDirectory(path) {
            var defer = $q.defer();
            var onSuccess = function() {
                defer.resolve();
            };

            var onFailed = function(error) {
                defer.reject('Failed to remove directory: ' + error);
            };

            var payload = {
                path: path
            };

            sendMessage(DEVICE_INFO.REMOVE_DIRECTORY, payload).then(onSuccess, onFailed);
            return defer.promise;
        }

        function checkFile(path) {
            var defer = $q.defer();
            var onSuccess = function(data) {
                if (!data || !data.Result) {
                    defer.reject('File not found.');
                    return;
                }

                defer.resolve({
                    path: data.Result.Path,
                    exists: data.Result.IsExist
                });
            };

            var onFailed = function(error) {
                defer.reject('Failed to check file: ' + error);
            };

            var payload = {
                path: path
            };

            sendMessage(DEVICE_INFO.CHECK_FILE, payload).then(onSuccess, onFailed);
            return defer.promise;
        }

        function moveFile(source, destination) {
            var defer = $q.defer();
            var onSuccess = function(data) {
                if (!data || !data.Result) {
                    defer.reject('File not found.');
                    return;
                }

                defer.resolve({
                    name: data.Result.Name,
                    path: data.Result.Path,
                    isDirectory: data.Result.IsDirectory
                });
            };

            var onFailed = function(error) {
                defer.reject('Failed to move file: ' + error);
            };

            var payload = {
                source: source,
                destination: destination
            };

            sendMessage(DEVICE_INFO.MOVE_FILE, payload).then(onSuccess, onFailed);
            return defer.promise;
        }

        function removeFile(path) {
            var defer = $q.defer();
            var onSuccess = function() {
                defer.resolve();
            };

            var onFailed = function(error) {
                defer.reject('Failed to remove file: ' + error);
            };

            var payload = {
                path: path
            };

            sendMessage(DEVICE_INFO.REMOVE_FILE, payload).then(onSuccess, onFailed);
            return defer.promise;
        }

        function downloadHttp(remotePath, localPath) {
            var defer = $q.defer();
            var message = {
                commandId: getCommandId(),
                source: remotePath,
                destination: FileUtil.getDirPath(localPath),
                name: FileUtil.getBaseName(localPath)
            };

            promises[message.commandId] = defer;
            bridgeInstance.async().sendDownloadMessageToApp(JSON.stringify(message));
            return defer.promise;
        }

        function resolveContentPath(path) {
            var defer = $q.defer();
            var onSuccess = function(data) {
                if (!data || !data.Result) {
                    defer.reject('Path not found.');
                    return;
                }

                defer.resolve(data.Result.Path);
            };

            var onFailed = function(error) {
                defer.reject('Failed to resolve content path: ' + error);
            };

            var payload = {
                path: path
            };

            sendMessage(DEVICE_INFO.RESOLVE_CONTENT_PATH, payload).then(onSuccess, onFailed);
            return defer.promise;
        }

        function keepAwake() {
            var defer = $q.defer();

            var onFailed = function(error) {
                defer.reject('Failed to request display active: ' + error);
            };

            var payload = {};

            sendMessage(DEVICE_INFO.KEEP_AWAKE, payload).then(defer.resolve, onFailed);

            return defer.promise;
        }

        function getCpuUsage() {
            var bridgeMessage = prepareBridgeMessage();
            bridgeInstance.async().sendCpuMessageToApp(JSON.stringify(bridgeMessage.message));
            return bridgeMessage.defer.promise;
        }

        function getAvailableStorage() {
            var bridgeMessage = prepareBridgeMessage();
            bridgeInstance.async().sendStorageMessageToApp(JSON.stringify(bridgeMessage.message));
            return bridgeMessage.defer.promise;
        }

        function getMemoryUsage() {
            var bridgeMessage = prepareBridgeMessage();
            bridgeInstance.async().sendMemoryMessageToApp(JSON.stringify(bridgeMessage.message));
            return bridgeMessage.defer.promise;
        }

        function createBrowser(url, onLoadStop, onLoadError) {
            var bridgeMessage = prepareBridgeMessage();
            bridgeMessage.message.url = url;
            bridgeInstance.async().sendOpenInAppBrowserMessageToApp(JSON.stringify(bridgeMessage.message));
            var browser = {
                id: IN_APP_BROWSER_ID_PREFIX + Date.now(),
            };
            inAppBrowserLoadStopCallback = onLoadStop;
            inAppBrowserLoadErrorCallback = onLoadError;
            browser.dispose = disposeInAppBrowser.bind(null, inAppBrowsers, browser, bridgeMessage);
            inAppBrowsers.push(browser);
            return browser;
        }

        function disposeInAppBrowser(browserCollection, browser, bridgeMessage) {
            for (var index = 0; index < browserCollection.length; index++) {
                if (browser.id !== browserCollection[index].id) {
                    continue;
                }

                browserCollection.splice(index, 1);
                break;
            }

            if (browserCollection.length > 0 ) {
                return;
            }

            inAppBrowserLoadStopCallback = undefined;
            inAppBrowserLoadErrorCallback = undefined;
            bridgeInstance.async().sendCloseInAppBrowserMessageToApp(JSON.stringify(bridgeMessage.message));
        }

        function parseMessage(message) {
            var appMessage = dataUtil.tryParseJSON(message);
            if (!appMessage) {
                logService.error(LOG_KEY, 'Failed to parse app message: ' + message);
                return;
            }

            if (!promises[appMessage.CommandId]) {
                logService.error(LOG_KEY, 'Failed to resolve app message: Missing command guid');
                return;
            }

            $timeout.cancel(timeoutCallbacks[appMessage.CommandId]);
            delete timeoutCallbacks[appMessage.CommandId];

            return appMessage;
        }

        function parseWebsocketMessage(message) {
            var appMessage = dataUtil.tryParseJSON(message);
            if (!appMessage) {
                logService.error(LOG_KEY, 'Failed to parse web socket message: ' + message);
                return;
            }

            return appMessage;
        }

        function receiveMessage(message) {
            var appMessage = parseMessage(message);
            if (!appMessage) {
                return;
            }

            $timeout.cancel(timeoutCallbacks[appMessage.CommandId]);
            delete timeoutCallbacks[appMessage.CommandId];

            if (!appMessage.IsSuccess) {
                promises[appMessage.CommandId].reject('Failed to retrieve information: ' + appMessage.Message);
                delete promises[appMessage.CommandId];
                return;
            }

            promises[appMessage.CommandId].resolve(appMessage);
            delete promises[appMessage.CommandId];
        }

        function onReceivedDownloadHttpMessage(message) {
            var appMessage = parseMessage(message);
            if (!appMessage) {
                return;
            }

            if (!appMessage.IsSuccess) {
                var error = {
                    errorMessage: appMessage.ErrorMessage,
                    code: appMessage.ErrorCode
                };

                promises[appMessage.CommandId].reject(error);
                delete promises[appMessage.CommandId];
                return;
            }

            promises[appMessage.CommandId].resolve(appMessage);
            delete promises[appMessage.CommandId];
        }

        function onReceivedCpuUsageMessage(message) {
            var appMessage = parseMessage(message);
            if (!appMessage) {
                return;
            }

            promises[appMessage.CommandId].resolve({
                cpuUsage: appMessage.CpuUsage
            });
            delete promises[appMessage.CommandId];
        }

        function onReceivedFirmwareMessage(message) {
            var appMessage = parseMessage(message);
            if (!appMessage) {
                return;
            }

            promises[appMessage.CommandId].resolve(appMessage.Firmware);
            delete promises[appMessage.CommandId];
        }

        function onReceivedStorageMessage(message) {
            var appMessage = parseMessage(message);
            if (!appMessage) {
                return;
            }

            if (!appMessage.IsSuccess) {
                promises[appMessage.CommandId].resolve();
                delete promises[appMessage.CommandId];
                return;
            }

            promises[appMessage.CommandId].resolve({
                used: appMessage.Used,
                total: appMessage.Total
            });
            delete promises[appMessage.CommandId];
        }

        function onReceivedMemoryUsageMessage(message) {
            var appMessage = parseMessage(message);
            if (!appMessage) {
                return;
            }

            if (!appMessage.IsSuccess) {
                promises[appMessage.CommandId].resolve({
                    memoryUsage: 0
                });
                delete promises[appMessage.CommandId];
                return;
            }

            promises[appMessage.CommandId].resolve({
                capacity: appMessage.Capacity,
                availableCapacity: appMessage.AvailableCapacity
            });
            delete promises[appMessage.CommandId];
        }

        function onReceivedIpAddressMessage(message) {
            var appMessage = parseMessage(message);
            if (!appMessage) {
                return;
            }

            if (!appMessage.IpAddress) {
                promises[appMessage.CommandId].resolve({
                    address: DEFAULT_IP_ADDRESS
                });
                delete promises[appMessage.CommandId];
                return;
            }

            promises[appMessage.CommandId].resolve({
                address: appMessage.IpAddress
            });
            delete promises[appMessage.CommandId];
        }

        function onReceivedMacAddressMessage(message) {
            var appMessage = parseMessage(message);
            if (!appMessage) {
                return;
            }

            if (!appMessage.IsSuccess) {
                processMessagePromise(appMessage.CommandId, PROMISE_METHOD.REJECT, 'Failed to get MAC address.', true);
                return;
            }

            processMessagePromise(appMessage.CommandId, PROMISE_METHOD.RESOLVE, appMessage.MacAddress);
        }

        function onReceivedGetDisplayRotationMessage(message) {
            var appMessage = parseMessage(message);
            if (!appMessage) {
                return;
            }

            if (!appMessage.IsSuccess) {
                logService.error(LOG_KEY, 'Failed to get display rotation.');
                promises[appMessage.CommandId].reject('Failed to get display rotation.');
                delete promises[appMessage.CommandId];
                return;
            }

            promises[appMessage.CommandId].resolve({
                rotation: appMessage.Rotation.toString()
            });
            delete promises[appMessage.CommandId];
        }

        function onReceivedOpenInAppBrowserMessage(message) {
            var appMessage = parseMessage(message);
            if (!appMessage) {
                return;
            }

            var event = {
                url: appMessage.Url,
                message: appMessage.Message
            };

            if (!appMessage.IsSuccess) {
                inAppBrowserLoadErrorCallback(event);
                logService.error(LOG_KEY, 'Failed to open in-app browser.');
                delete promises[appMessage.CommandId];
                return;
            }

            inAppBrowserLoadStopCallback(event);
            promises[appMessage.CommandId].resolve();
            delete promises[appMessage.CommandId];
        }

        function onReceivedCloseInAppBrowserMessage(message) {
            var appMessage = parseMessage(message);
            if (!appMessage) {
                return;
            }

            if (!appMessage.IsSuccess) {
                logService.error(LOG_KEY, 'Failed to close in-app browser.');
                delete promises[appMessage.CommandId];
                return;
            }

            logService.debug(LOG_KEY, 'Clear in-app browser successfully.');
            promises[appMessage.CommandId].resolve();
            delete promises[appMessage.CommandId];
        }

        function onReceivedSetDisplayRotationMessage(message) {
            var appMessage = parseMessage(message);
            if (!appMessage) {
                return;
            }

            if (!appMessage.IsSuccess) {
                logService.error(LOG_KEY, 'Failed to set display rotation.');
                promises[appMessage.CommandId].reject('Failed to set display rotation.');
                delete promises[appMessage.CommandId];
                return;
            }

            promises[appMessage.CommandId].resolve();
            delete promises[appMessage.CommandId];
        }

        function onReceivedRegistrationPolicyMessage(message) {
            var appMessage = parseMessage(message);
            if (!appMessage) {
                return;
            }

            if (!appMessage.RegistrationPolicy) {
                processMessagePromise(appMessage.CommandId, PROMISE_METHOD.REJECT, 'Retrieved registration policy is undefined');
                return;
            }

            var registrationPolicy = dataUtil.tryParseJSON(appMessage.RegistrationPolicy);
            if (registrationPolicy) {
                processMessagePromise(appMessage.CommandId, PROMISE_METHOD.RESOLVE, registrationPolicy);
                return;
            }

            processMessagePromise(appMessage.CommandId, PROMISE_METHOD.REJECT, 'Failed to parse registration policy');
        }

        function onReceivedRegistrationInfoMessage(message) {
            var appMessage = parseMessage(message);
            if (!appMessage) {
                return;
            }

            if (!appMessage.RegistrationInfo) {
                promises[appMessage.CommandId].reject('Retrieved registration information is undefined');
                delete promises[appMessage.CommandId];
                return;
            }

            var registrationInfo = dataUtil.tryParseJSON(appMessage.RegistrationInfo);
            registrationInfo ? promises[appMessage.CommandId].resolve(registrationInfo) :
            promises[appMessage.CommandId].reject('Failed to parse registration information');
            delete promises[appMessage.CommandId];
        }

        function onReceivedClearRegistrationInfoMessage(message) {
            var appMessage = parseMessage(message);
            if (!appMessage) {
                return;
            }

            if (!appMessage.IsSuccess) {
                logService.error(LOG_KEY, 'Failed to clear registration information.');
                promises[appMessage.CommandId].reject('Failed to parse registration information');
                delete promises[appMessage.CommandId];
                return;
            }

            logService.debug(LOG_KEY, 'Registration information cleared.');
            promises[appMessage.CommandId].resolve();
            delete promises[appMessage.CommandId];
        }

        function onReceivedWebsocketAppMessage(appMessage) {
            var websocketMessage = parseWebsocketMessage(appMessage);
            if (!websocketMessage) {
                return;
            }

            if (websocketMessage.IsClientMessage) {
                notifySubscribers(websocketMessage.Message);
                return;
            }

            if (!promises[websocketMessage.CommandId]) {
                logService.error(LOG_KEY, 'Failed to resolve app message: Missing command guid');
                return;
            }

            $timeout.cancel(timeoutCallbacks[websocketMessage.CommandId]);
            delete timeoutCallbacks[websocketMessage.CommandId];

            if (!websocketMessage.IsSuccess) {
                promises[websocketMessage.CommandId].reject('Failed to retrieve information: ' + websocketMessage.Message);
                delete promises[websocketMessage.CommandId];
                return;
            }

            promises[websocketMessage.CommandId].resolve(websocketMessage);
            delete promises[websocketMessage.CommandId];
        }

        function getCommandId() {
            return $window.uuid.v1();
        }

        function sendMessage(command, payload, timeoutDuration) {
            var defer = $q.defer();
            var commandMessage = {
                commandId: getCommandId(),
                command: command,
                payload: payload
            };

            promises[commandMessage.commandId] = defer;
            var timeoutCallback = $timeout(function() {
                defer.reject('Message request timeout.');
            }, timeoutDuration || TIMEOUT_DURATION);
            timeoutCallbacks[commandMessage.commandId] = timeoutCallback;
            bridgeInstance.async().sendMessageToApp(JSON.stringify(commandMessage));
            return defer.promise;
        }

        function isReady() {
            var promise = $q.defer();
            getBasePath().then(function(result) {
                basePath = result;
            }, function(error) {
                logService.error(LOG_KEY, 'Failed to get base path due to: ' + error);
            }).finally(promise.resolve);
            return promise.promise;
        }

        function addBridgeListener(event, callback) {
            bridgeInstance.removeEventListener(event, callback);
            bridgeInstance.addEventListener(event, callback);
        }

        function initializeBridgeListeners() {
            addBridgeListener(EVENT.ON_RECEIVED_APP_MESSAGE, receiveMessage);
            addBridgeListener(EVENT.ON_RECEIVED_DOWNLOAD_APP_MESSAGE, onReceivedDownloadHttpMessage);
            addBridgeListener(EVENT.ON_RECEIVED_CPU_APP_MESSAGE, onReceivedCpuUsageMessage);
            addBridgeListener(EVENT.ON_RECEIVED_STORAGE_APP_MESSAGE, onReceivedStorageMessage);
            addBridgeListener(EVENT.ON_RECIEVED_MEMORY_APP_MESSAGE, onReceivedMemoryUsageMessage);
            addBridgeListener(EVENT.ON_RECIEVED_IP_ADDRESS_APP_MESSAGE, onReceivedIpAddressMessage);
            addBridgeListener(EVENT.ON_RECEIVED_MAC_ADDRESS_APP_MESSAGE, onReceivedMacAddressMessage);
            addBridgeListener(EVENT.ON_RECEIVED_FIRMWARE_APP_MESSAGE, onReceivedFirmwareMessage);
            addBridgeListener(EVENT.ON_RECEIVED_REGISTRATION_POLICY_APP_MESSAGE, onReceivedRegistrationPolicyMessage);
            addBridgeListener(EVENT.ON_RECEIVED_REGISTRATION_INFO_APP_MESSAGE, onReceivedRegistrationInfoMessage);
            addBridgeListener(EVENT.ON_RECEIVED_CLEAR_REGISTRATION_INFO_APP_MESSAGE, onReceivedClearRegistrationInfoMessage);
            addBridgeListener(EVENT.ON_RECEIVED_WEBSOCKET_APP_MESSAGE, onReceivedWebsocketAppMessage);
            addBridgeListener(EVENT.ON_RECEIVED_SET_DISPLAY_ROTATION_APP_MESSAGE, onReceivedSetDisplayRotationMessage);
            addBridgeListener(EVENT.ON_RECEIVED_GET_DISPLAY_ROTATION_APP_MESSAGE, onReceivedGetDisplayRotationMessage);
            addBridgeListener(EVENT.ON_RECEIVED_OPEN_IN_APP_BROWSER_APP_MESSAGE, onReceivedOpenInAppBrowserMessage);
            addBridgeListener(EVENT.ON_RECEIVED_CLOSE_IN_APP_BROWSER_APP_MESSAGE, onReceivedCloseInAppBrowserMessage);
        }

        function initialize() {
            $window.chrome.webview.hostObjects.options.defaultSyncProxy = true;
            bridgeInstance = $window.chrome.webview.hostObjects.sync.BridgeInstance;
            initializeBridgeListeners();
        }

        function startServer(config) {
            var serverConfig = {};
            var onSuccess = function() {
                logService.debug(LOG_KEY, 'Websocket server started.');
            };

            var onFailed = function(err) {
                logService.error(LOG_KEY, 'Websocket server failed to start due to:' + JSON.stringify(err));
            };

            if (!config.port) {
                onFailed('Port is missing.');
                return;
            }

            serverConfig.Port = config.port;
            sendWebsocketMessageToApp(DEVICE_INFO.START_WEBSOCKET_SERVER, serverConfig).then(onSuccess, onFailed);
        }

        function sendWebsocketMessageToApp(command, payload) {
            var bridgeMessage = prepareBridgeMessage(command, payload);
            bridgeInstance.async().sendWebsocketMessageToApp(JSON.stringify(bridgeMessage.message));
            return bridgeMessage.defer.promise;
        }

        function stopServer() {
            var onSuccess = function() {
                logService.debug(LOG_KEY, 'Websocket server stopped.');
            };

            var onFailed = function(err) {
                logService.error(LOG_KEY, 'Websocket server failed to stop due to:' + JSON.stringify(err));
            };

            sendWebsocketMessageToApp(DEVICE_INFO.STOP_WEBSOCKET_SERVER, {}).then(onSuccess, onFailed);
        }

        function notifySubscribers(message) {
            for (var i = 0; i < websocketSubsribers.length; i++) {
                var subscriber = websocketSubsribers[i];

                if (!subscriber) {
                    return;
                }

                subscriber(message);
            }
        }

        function subscribeServerMessages(callback) {
            websocketSubsribers.push(callback);
        }

        function unSubscribeServerMessages(callback) {
            for (var i = 0; i < websocketSubsribers.length; i++) {
                if (websocketSubsribers[i] === callback) {
                    websocketSubsribers.splice(i, 1);
                    return;
                }
            }
        }

        function cancelStickyKeyFiredTimeout() {
            if (!stickyKeyFiredTimeout) {
                return;
            }

            $timeout.cancel(stickyKeyFiredTimeout);
            stickyKeyFiredTimeout = undefined;
        }

        function createRemoteEvent(e) {
            var re = RemoteUtil.processDefaultKeyboardMapping(e);
            if (re) {
                re.iskeyboard = true;

                cancelStickyKeyFiredTimeout();
                if (DISABLED_STICKY_KEYS.indexOf(re.vcode) > -1 && isStickyKeyFired) {
                    isStickyKeyFired = false;
                    return null;
                }

                isStickyKeyFired = true;
                // isStickyKeyFired to prevent sticky key firing 2 times on webview2. timeout isStickyKeyFired to re-enabled sticky key firing after 200ms.
                stickyKeyFiredTimeout = $timeout(function() {
                    isStickyKeyFired = false;
                }, STICKY_KEY_TIMEOUT_DURATION);
            }

            return re;
        }

        function processRemoteEvent(eventType, event) {
            var re = createRemoteEvent(event);
            if (re === null) {
                return;
            }

            re.eventType = eventType;

            // Notify remote driver service
            if (remoteServiceEventHandler) {
                remoteServiceEventHandler(re);
            }

            // Prevent default if needed
            if (!re.isDefaultPrevented()) {
                return;
            }

            event.preventDefault(); // prevent the default action (scroll / move caret)
        }

        function keydownHandler(e) {
            processRemoteEvent(REMOTEEVENTTYPE.buttonDown, e);
        }

        function keyupHandler(e) {
            processRemoteEvent(REMOTEEVENTTYPE.buttonUp, e);
        }

        // Registers a callback for remote events and returns an id to unsubscribe later
        function registerRemoteEvents(callback) {
            remoteServiceEventHandler = callback;
            $document.keydown(keydownHandler);
            $document.keyup(keyupHandler);
        }

        // Unregisters an existing callback for remote events
        function unRegisterRemoteEvents() {
            $document.unbind('keydown', keydownHandler);
            $document.unbind('keyup', keyupHandler);
        }

        function getMediaDirective(elementType, options) {
            var result = {
                directive: elementType,
                attributes: ''
            };
            options = options || {};
            if (elementType === MEDIA_DIRECTIVE.WEB && options.webEngine === MEDIA.WEB_ENGINE_IN_APP_BROWSER) {
                result.directive = MEDIA_DIRECTIVE.IN_APP_BROWSER;
            }

            return result;
        }

        Webview2Driver.prototype = {
            // Public interface
            id: 'windows.uwp',
            platform: {
                getAppspaceDeviceType: getAppspaceDeviceType,
                getFeatureSupport: getFeatureSupport,
                getRuntimeEnvironments: getRuntimeEnvironments,
                getCpuUsage: getCpuUsage,
                getAvailableStorage: getAvailableStorage,
                getMemoryUsage: getMemoryUsage,
                getFirmware: getFirmware,
                getFingerprint: getFingerprint,
                getAppPolicy: getAppPolicy,
                setAppPolicy: setAppPolicy,
                clearAppPolicy: clearAppPolicy,
                getRegistrationInformation: getRegistrationInformation,
                clearRegistrationInformation: clearRegistrationInformation,
                keepAwake: keepAwake
            },
            file: {
                resolveUrl: resolveUrl,
                resolveRelative: resolveUrl,
                createDirectory: createDirectory,
                checkDirectory: checkDirectory,
                removeDirectory: removeDirectory,
                enumerateDirectory: enumerateDirectory,
                checkFile: checkFile,
                moveFile: moveFile,
                removeFile: removeFile
            },
            network: {
                getIpAddress: getIpAddress,
                getMacAddress: getMacAddress,
                downloadHttp: downloadHttp,
                startServer: startServer,
                stopServer: stopServer,
                subscribeServerMessages: subscribeServerMessages,
                unSubscribeServerMessages: unSubscribeServerMessages
            },
            media: {
                resolveContentPath: resolveContentPath,
                setDisplayRotation: setDisplayRotation,
                getDisplayRotation: getDisplayRotation,
                createBrowser: createBrowser,
                getMediaDirective: getMediaDirective
            },
            remote: {
                registerEvents: registerRemoteEvents,
                unRegisterEvents: unRegisterRemoteEvents
            },
            isReady: isReady
        };

        return (Webview2Driver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('WindowsUwpDriver', ["$window", "$http", "$q", "$injector", "$timeout", "platformUtil", "UwpSocketServer", "VIDEO_STRETCH_MODE", "STORAGE_COLLECTION_KEY", "STORAGE_KEY", "POST_MESSAGES", "dataUtil", "logService", "driverUtil", "FEATURE_CODE", function($window, $http, $q, $injector, $timeout, platformUtil,
        UwpSocketServer, VIDEO_STRETCH_MODE, STORAGE_COLLECTION_KEY, STORAGE_KEY, POST_MESSAGES,
        dataUtil, logService, driverUtil, FEATURE_CODE) {
        // filter this, if windows runtime is not detected
        if (typeof Windows !== 'object' || !Windows.System) {
            return function() { };
        }

        var lastCpuCheckDate;
        var lastTotalCpuTime;
        var canCalculateCpuUsage = false;
        var socketServer = new UwpSocketServer();
        var postMessagingSubscriber;
        var postMessages;
        var IFRAME_ELEMENT = 'iframe';
        var VIDEO_ELEMENT = 'video';
        var VIDEO_ATTRIBUTES = 'muted';
        var SEND_PLATFORM_MESSAGE_TIMEOUT_DURATION = 30000;
        var registrationInfoFileName = 'registration.txt';
        var CREATE_FILE_CONFIG = {create: true, exclusive: false};
        var EDIT_FILE_CONFIG = {create: false};

        function init() {
        }

        init();

        // Define the constructor function.
        function WindowsUwpDriver() {
            if (Windows.System.Diagnostics.ProcessDiagnosticInfo) {
                var cpuUsageReport = Windows.System.Diagnostics.ProcessDiagnosticInfo.getForCurrentProcess().cpuUsage.getReport();
                if (cpuUsageReport) {
                    lastCpuCheckDate = new Date();
                    lastTotalCpuTime = cpuUsageReport.kernelTime + cpuUsageReport.userTime;
                    canCalculateCpuUsage = true;
                }
            }
        }

        // get used and total disk space
        function getAvailableStorage() {
            var defer = $q.defer();
            if (!Windows.Storage || !Windows.Storage.ApplicationData || !Windows.Storage.ApplicationData.current) {
                defer.resolve('');
            } else {
                var applicationData = Windows.Storage.ApplicationData.current;
                var freeSpaceProperty = 'System.FreeSpace';
                var totalSpaceProperty = 'System.Capacity';
                applicationData.localFolder.getBasicPropertiesAsync().then(function(basicProperties) {
                    // Get extra properties
                    return basicProperties.retrievePropertiesAsync([freeSpaceProperty, totalSpaceProperty]);
                }).done(function(extraProperties) {
                    var freeSpace = extraProperties[freeSpaceProperty];
                    var totalSpace = extraProperties[totalSpaceProperty];
                    if (!freeSpace || !totalSpace) {
                        defer.resolve('');
                    } else {
                        defer.resolve({
                            used: totalSpace - freeSpace,
                            total: totalSpace
                        });
                    }
                }, function(error) {
                    // Handle errors encountered while retrieving properties
                    defer.resolve('');
                });
            }

            return defer.promise;
        }

        function getMemoryUsage() {
            var promise = $q.defer();

            if (!Windows.System.MemoryManager) {
                promise.resolve({
                    memoryUsage: 0
                });
            } else {
                promise.resolve({
                    capacity: Windows.System.MemoryManager.appMemoryUsageLimit,
                    availableCapacity: Windows.System.MemoryManager.appMemoryUsage
                });
            }

            return promise.promise;
        }

        // Get CPU usage
        function getCpuUsage() {
            var defer = $q.defer();
            if (!canCalculateCpuUsage) {
                defer.resolve({
                    cpuUsage: 0
                });
            } else {
                var cpuUsageReport = Windows.System.Diagnostics.ProcessDiagnosticInfo.getForCurrentProcess().cpuUsage.getReport();
                if (cpuUsageReport) {
                    var currentCpuCheckDate = new Date();
                    var currentTotalCpuTime = cpuUsageReport.kernelTime + cpuUsageReport.userTime;
                    var usage = 0;
                    if (currentTotalCpuTime > lastTotalCpuTime) {
                        usage = (currentTotalCpuTime - lastTotalCpuTime) * 100 / (currentCpuCheckDate - lastCpuCheckDate);
                    }

                    lastCpuCheckDate = currentCpuCheckDate;
                    lastTotalCpuTime = currentTotalCpuTime;

                    defer.resolve({
                        cpuUsage: usage
                    });
                } else {
                    defer.resolve({
                        cpuUsage: 0
                    });
                }
            }

            return defer.promise;
        }

        function getFingerprint() {
            return driverUtil.getFingerprint();
        }

        // Get the IP Address for the device
        function getIpAddress() {
            var defer = $q.defer();
            var res = {address: '127.0.0.1'};
            if (Windows.Networking &&
                Windows.Networking.Connectivity &&
                Windows.Networking.Connectivity.NetworkInformation &&
                Windows.Networking.Connectivity.NetworkInformation.getInternetConnectionProfile()) {
                var connectionProfile = Windows.Networking.Connectivity.NetworkInformation.getInternetConnectionProfile();
                var hostNames = Windows.Networking.Connectivity.NetworkInformation.getHostNames();
                for (var i = 0; i < hostNames.length; i++) {
                    var hostname = hostNames[i];
                    if (hostname.ipInformation &&
                        hostname.ipInformation.networkAdapter &&
                        hostname.ipInformation.networkAdapter.networkAdapterId == connectionProfile.networkAdapter.networkAdapterId &&
                        hostname.canonicalName) {
                        res.address = hostname.canonicalName;
                        break;
                    }
                }
            }

            defer.resolve(res);
            return defer.promise;
        }

        function getAppspaceDeviceType() {
            return 'UWP';
        }

        // Returns the support level for a specific feature
        function getFeatureSupport(feature) {
            feature = feature.toLowerCase();
            switch (feature) {
                case 'input.touchevents':
                    return 'no';
                case 'input.touchscreen':
                    return 'yes';
                case FEATURE_CODE.MEDIA_SUPPORT_DATA_PROTOCOL:
                    return 'no';
                case 'media.card.onlineforcestream':
                    return 'yes';
                case 'media.video.use.load':
                    return true;
                case 'media.maxvideolayers':
                    return '99';
                case 'media.video.stretch.mode':
                    return VIDEO_STRETCH_MODE.SCALE;
                case 'media.animation.persistclass':
                    return true;
                case 'media.animation.initialposition':
                    return 'absolute';
                case 'media.animation.initialzindex':
                    return 'unset';
                case 'media.animation.zoom.supported':
                    return false;
                case 'channel.viewer.requires.overlay':
                    return true;
                default:
                    return null;
            }
        }

        // Media
        function getMediaDirective(elementType, options) {
            var res = {
                directive: elementType,
                attributes: ''
            };

            switch (elementType) {
                case IFRAME_ELEMENT:
                    var appDirectory = cordova.file.applicationDirectory;
                    // check if the source is from app directory, if not then use microsoft webview
                    if (!options ||
                        !options.url ||
                        !appDirectory ||
                        options.url.substr(0, appDirectory.length - 1) !== appDirectory.substr(0, appDirectory.length - 1)) {
                        res.directive = 'x-ms-webview';
                    }

                    break;
                case VIDEO_ELEMENT:
                    res.attributes = VIDEO_ATTRIBUTES;
                    break;
            }

            return res;
        }

        function startServer(config) {
            socketServer.start(config.port);
        }

        function stopServer() {
            socketServer.stop();
        }

        function subscribeServerMessages(callback) {
            socketServer.subscribeToUpdates(callback);
        }

        function unSubscribeServerMessages(callback) {
            socketServer.unsubscribeFromUpdates(callback);
        }

        function readFile(path) {
            var defer = $q.defer();
            httpGet(path).then(function(res) {
                if (res.data) {
                    defer.resolve(res.data);
                } else {
                    defer.reject('data does not exist');
                }
            }, defer.reject);

            return defer.promise;
        }

        function getJSON(url) {
            var promise = $q.defer();
            httpGet(url).then(function(response) {
                if (response && response.data) {
                    promise.resolve(response.data);
                } else {
                    promise.reject(response);
                }
            }, promise.reject);
            return promise.promise;
        }

        function httpGet(url, config) {
            var defer = $q.defer();
            config = config || {};
            config.method = 'GET';
            config.url = url;
            if (url.indexOf('ms-appdata') != 0) {
                $http(config).then(defer.resolve, defer.reject);
            } else {
                $window.resolveLocalFileSystemURL(url, function(fileEntry) {
                    fileEntry.file(function(file) {
                        var reader = new $window.FileReader();
                        reader.onloadend = function() {
                            // this is simulate http get data
                            var response = {
                                config: config,
                                status: 200,
                                statusText: 'OK'
                            };

                            // remove BOM character
                            var res = this.result;
                            if (res.charCodeAt(0) === 0xFEFF) {
                                res = res.substr(1);
                            }

                            // See if the results is a valid JSON object, if not return data directly
                            try {
                                response.data = JSON.parse(res);
                            } catch (e) {
                                response.data = res;
                            }

                            defer.resolve(response);
                        };

                        reader.readAsText(file);
                    }, defer.reject);
                }, defer.reject);
            }

            return defer.promise;
        }

        function startPostMessaging() {
            // uwp specific workflow
            $window.msPostMessaging = globalMsPostMessaging;
            $window.msRetrieveMessaging = globalMsRetrieveMessaging;
        }

        // global function for ms-webview post messaging
        function globalMsPostMessaging(message) {
            if (!postMessagingSubscriber || !message) {
                return;
            }

            var messageObject = JSON.parse(message);
            var event = {
                data: messageObject
            };
            event.data.message = event.data.message || '';
            if (event.data.message.toLowerCase() == 'appspaceapp.mswebview.postmessage.init') {
                // if post messages already been defined, meaning we already get init message. so ignore.
                if (postMessages) {
                    return;
                }

                postMessages = [];
                event.data.message = 'appspaceapp.postmessage.ready';
            }

            postMessagingSubscriber(event);
        }

        // global function for ms-webview retrieve messaging
        function globalMsRetrieveMessaging() {
            if (!postMessages) {
                return [];
            }

            var message = JSON.stringify(postMessages);
            postMessages = [];
            return message;
        }

        function subscribeToPostMessaging(callback) {
            postMessagingSubscriber = callback;
        }

        function stopPostMessaging() {
            $window.msPostMessaging = undefined;
            $window.msRetrieveMessaging = undefined;
            postMessages = undefined;
        }

        function raisePostMessage(data) {
            if (!postMessages || !data) {
                return;
            }

            postMessages.push(data);
        }

        function getRuntimeEnvironments() {
            return ['uwp'];
        }

        function createFile(fileSystem, data) {
            var defer = $q.defer();
            var writeFile = function(fileEntry) {
                fileEntry.createWriter(function(fileWriter) {
                    fileWriter.onwriteend = function() {
                        defer.resolve(data);
                    };

                    fileWriter.onerror = function(error) {
                        defer.reject(error);
                    };

                    fileWriter.write(data);
                });
            };

            fileSystem.getFile(registrationInfoFileName, CREATE_FILE_CONFIG, writeFile, defer.reject);
            return defer.promise;
        }

        function storeRegistrationInfoInFile(data) {
            var defer = $q.defer();
            $window.resolveLocalFileSystemURL(cordova.file.dataDirectory, function(fs) {
                createFile(fs, data).then(defer.resolve, defer.reject);
            }, defer.reject);

            return defer.promise;
        }

        function persistRegistrationInfo(registrationInfo) {
            if (!registrationInfo || !dataUtil.isObject(registrationInfo)) {
                return $q.reject('Invalid registration information');
            }

            if (!registrationInfo.tokens) {
                return $q.reject('Invalid registration info token');
            }

            if (!registrationInfo.deviceId) {
                return $q.reject('Invalid registration info device ID');
            }

            if (!registrationInfo.appspaceUrl) {
                return $q.reject('Invalid registration URL');
            }

            var storedRegistrationInfo = getCoreConfigurationService().get(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.REGISTRATION_INFO);
            var strRegistrationInfo = JSON.stringify(registrationInfo);
            var hashedRegistrationInfo = dataUtil.toHash(strRegistrationInfo);

            if (storedRegistrationInfo === hashedRegistrationInfo) {
                return $q.reject('Registration information is identical to stored information');
            }

            var defer = $q.defer();
            var persistRegistrationInfoCallback = function(data) {
                if (!data) {
                    defer.reject('Invalid persisted registration information');
                    return;
                }

                getCoreConfigurationService().set(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.REGISTRATION_INFO, dataUtil.toHash(data));
                defer.resolve();
            };

            storeRegistrationInfoInFile(strRegistrationInfo).then(persistRegistrationInfoCallback, defer.reject);
            return defer.promise;
        }

        function clearRegistrationInformation() {
            var defer = $q.defer();
            // clear registration info file
            window.resolveLocalFileSystemURL(cordova.file.dataDirectory, function(dir) {
                dir.getFile(registrationInfoFileName, EDIT_FILE_CONFIG, function(fileEntry) {
                    fileEntry.remove(defer.resolve, defer.reject, defer.reject);
                });
            });

            return defer.promise;
        }

        function persistRegistrationInfoOnBoot(registrationInfo) {
            return persistRegistrationInfo(registrationInfo);
        }

        function getCoreConfigurationService() {
            return $injector.get('coreConfigurationService');
        }

        // Define the instance methods and properties
        WindowsUwpDriver.prototype = {
            // Public interface
            id: 'windows.uwp',
            platform: {
                getCpuUsage: getCpuUsage,
                getAvailableStorage: getAvailableStorage,
                getMemoryUsage: getMemoryUsage,
                getFingerprint: getFingerprint,
                getAppspaceDeviceType: getAppspaceDeviceType,
                getAppPackageType: getAppspaceDeviceType,
                getFeatureSupport: getFeatureSupport,
                getRuntimeEnvironments: getRuntimeEnvironments,
                startPostMessaging: startPostMessaging,
                stopPostMessaging: stopPostMessaging,
                subscribeToPostMessaging: subscribeToPostMessaging,
                raisePostMessage: raisePostMessage,
                persistRegistrationInfo: persistRegistrationInfo,
                clearRegistrationInformation: clearRegistrationInformation,
                persistRegistrationInfoOnBoot: persistRegistrationInfoOnBoot
            },
            network: {
                getIpAddress: getIpAddress,
                startServer: startServer,
                stopServer: stopServer,
                subscribeServerMessages: subscribeServerMessages,
                unSubscribeServerMessages: unSubscribeServerMessages,
                httpGet: httpGet,
                getJSON: getJSON
            },
            media: {
                getMediaDirective: getMediaDirective
            },
            file: {
                readFile: readFile
            }
        };

        // Return the constructor for the class
        return (WindowsUwpDriver);
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var app = $angular.module('hal');
    app.factory('halDriverService', ["$q", "$window", "logService", "platformUtil", "BrowserGenericDriver", "CordovaDriver", "BrowserChromeAppDriver", "SSSDDriver", "BrightsignDriver", "IECDriver", "BrowserSureVueDriver", "WindowsUwpDriver", "AndroidDriver", "CrestronDriver", "CrestronSignsDriver", "PwaDriver", "IOSDriver", "LgWebosDriver", "MercuryDriver", "PreviewDriver", "EditorDriver", "Ie11Driver", "TizenDriver", "AirMediaDriver", "CiscoWebexDriver", "CiscoWebexBoardDriver", "CiscoWebexRoomKitDriver", "NmeDriver", "ElectronDriver", "MacosDriver", "WindowsDriver", "LinuxDriver", "MersiveDriver", "embedProfileService", "AndroidTvDriver", "QbicDriver", "PolyDriver", "CiscoWebexNavigatorDriver", "PLATFORM_TYPE", "LogitechDriver", "GoogleMeetDriver", "CrestronConnectDriver", "Webview2Driver", "NeatDriver", "MtrDriver", "IAdeaDriver", "SureVueElectronDriver", "ZoomRoomsDriver", "AmazonSignageDriver", function(
        $q,
        $window,
        logService,
        platformUtil,
        BrowserGenericDriver,
        CordovaDriver,
        BrowserChromeAppDriver,
        SSSDDriver,
        BrightsignDriver,
        IECDriver,
        BrowserSureVueDriver,
        WindowsUwpDriver,
        AndroidDriver,
        CrestronDriver,
        CrestronSignsDriver,
        PwaDriver,
        IOSDriver,
        LgWebosDriver,
        MercuryDriver,
        PreviewDriver,
        EditorDriver,
        Ie11Driver,
        TizenDriver,
        AirMediaDriver,
        CiscoWebexDriver,
        CiscoWebexBoardDriver,
        CiscoWebexRoomKitDriver,
        NmeDriver,
        ElectronDriver,
        MacosDriver,
        WindowsDriver,
        LinuxDriver,
        MersiveDriver,
        embedProfileService,
        AndroidTvDriver,
        QbicDriver,
        PolyDriver,
        CiscoWebexNavigatorDriver,
        PLATFORM_TYPE,
        LogitechDriver,
        GoogleMeetDriver,
        CrestronConnectDriver,
        Webview2Driver,
        NeatDriver,
        MtrDriver,
        IAdeaDriver,
        SureVueElectronDriver,
        ZoomRoomsDriver,
        AmazonSignageDriver
    ) {
        var drivers = [];
        var iframeForceGenericDrivers = ['androidtv', 'android', 'ios', 'crestron', 'nme', 'macos', 'windows', 'webview2', 'linux', 'debian', 'logitech', PLATFORM_TYPE.AIRMEDIA3K, PLATFORM_TYPE.NEAT, PLATFORM_TYPE.IADEA, PLATFORM_TYPE.AMAZON_SIGNAGE];
        var isPreview = embedProfileService.isPreviewMode();
        var IFRAME_SUFFIX = '-iframe';
        var EMBEDDEDED_PLATFORM = {};
        EMBEDDEDED_PLATFORM[PLATFORM_TYPE.SUREVUE] = SureVueElectronDriver;

        function installPlatformDriver(platform) {
            // Load driver based on platform
            installDriver(new BrowserGenericDriver());
            var platformLowerCase = platform.toLowerCase();
            switch (platformLowerCase) {
                case PLATFORM_TYPE.LOGITECH:
                    installDriver(new CordovaDriver());
                    installDriver(new AndroidDriver());
                    installDriver(new LogitechDriver());
                    break;
                case PLATFORM_TYPE.NEAT:
                    installDriver(new CordovaDriver());
                    installDriver(new AndroidDriver());
                    installDriver(new NeatDriver());
                    break;
                case PLATFORM_TYPE.IADEA:
                    installDriver(new CordovaDriver());
                    installDriver(new AndroidDriver());
                    installDriver(new IAdeaDriver());
                    break;
                case PLATFORM_TYPE.AMAZON_SIGNAGE:
                    installDriver(new CordovaDriver());
                    installDriver(new AndroidDriver());
                    installDriver(new AmazonSignageDriver());
                    break;
                case 'androidtv':
                    installDriver(new CordovaDriver());
                    installDriver(new AndroidDriver());
                    installDriver(new AndroidTvDriver());
                    break;
                case 'android':
                    installDriver(new CordovaDriver());
                    installDriver(new AndroidDriver());
                    break;
                case 'crestron':
                    installCrestronDrivers();
                    break;
                case PLATFORM_TYPE.AIRMEDIA3K:
                    installCrestronDrivers();
                    installDriver(new AirMediaDriver());
                    installDriver(new CrestronConnectDriver());
                    break;
                case PLATFORM_TYPE.CCE:
                    installCrestronDrivers();
                    installDriver(new CrestronConnectDriver());
                    break;
                case 'nme':
                    installCrestronDrivers();
                    installDriver(new NmeDriver());
                    break;
                case 'crestron-iframe':
                case PLATFORM_TYPE.AIRMEDIA3K + IFRAME_SUFFIX:
                case PLATFORM_TYPE.CCE + IFRAME_SUFFIX:
                    installDriver(new CrestronSignsDriver());
                    break;
                case 'pwa':
                    installDriver(new PwaDriver());
                    break;
                case 'mercury':
                    installDriver(new PwaDriver());
                    installDriver(new MercuryDriver());
                    break;
                case 'airmedia':
                    installDriver(new PwaDriver());
                    installDriver(new MercuryDriver());
                    installDriver(new AirMediaDriver());
                    break;
                case 'mersive':
                    installDriver(new PwaDriver());
                    installDriver(new MersiveDriver());
                    break;
                case 'ie11':
                    installDriver(new PwaDriver());
                    installDriver(new Ie11Driver());
                    break;
                case 'webexboard':
                    installDriver(new PwaDriver());
                    installDriver(new CiscoWebexDriver());
                    installDriver(new CiscoWebexBoardDriver());
                    break;
                case 'webexroomkit':
                    installDriver(new PwaDriver());
                    installDriver(new CiscoWebexDriver());
                    installDriver(new CiscoWebexRoomKitDriver());
                    break;
                case PLATFORM_TYPE.WEBEXROOMNAVIGATOR:
                    installDriver(new PwaDriver());
                    installDriver(new CiscoWebexDriver());
                    installDriver(new CiscoWebexNavigatorDriver({drivers: drivers}));
                    break;
                case PLATFORM_TYPE.WEBEX:
                    installDriver(new PwaDriver());
                    installDriver(new CiscoWebexDriver());
                    break;
                case 'chromeapp':
                case 'cros':
                    installDriver(new BrowserChromeAppDriver());
                    break;
                case 'surevue':
                    installDriver(new BrowserChromeAppDriver());
                    installDriver(new BrowserSureVueDriver());
                    break;
                case 'brightsign':
                    installDriver(new BrightsignDriver({isPreviewMode: isPreview}));
                    break;
                case 'ios':
                    installDriver(new CordovaDriver());
                    installDriver(new IOSDriver());
                    break;
                case 'ios-iframe':
                    installDriver(new IOSDriver({isPreviewMode: isPreview}));
                    break;
                case 'uwp':
                    installDriver(new CordovaDriver());
                    installDriver(new WindowsUwpDriver());
                    break;
                case 'sssd':
                    installDriver(new SSSDDriver());
                    break;
                case 'iec':
                    installDriver(new IECDriver());
                    break;
                case 'webos':
                    installDriver(new LgWebosDriver({isPreviewMode: isPreview}));
                    break;
                case 'tizen':
                    installDriver(new TizenDriver());
                    break;
                case 'macos':
                    installDriver(new ElectronDriver(platformLowerCase));
                    installDriver(new MacosDriver());
                    break;
                case 'windows':
                    installDriver(new ElectronDriver(platformLowerCase));
                    installDriver(new WindowsDriver());
                    break;
                case 'linux':
                case 'debian':
                    installDriver(new ElectronDriver(platformLowerCase));
                    installDriver(new LinuxDriver());
                    break;
                case 'qbic':
                    installDriver(new PwaDriver());
                    installDriver(new QbicDriver());
                    break;
                case 'poly':
                    installDriver(new PwaDriver());
                    installDriver(new PolyDriver());
                    break;
                case PLATFORM_TYPE.GOOGLEMEET:
                    installDriver(new PwaDriver());
                    installDriver(new GoogleMeetDriver());
                    break;
                case PLATFORM_TYPE.WEBVIEW2:
                    installDriver(new Webview2Driver());
                    break;
                case PLATFORM_TYPE.MTR:
                    installDriver(new PwaDriver());
                    installDriver(new MtrDriver());
                    break;
                case PLATFORM_TYPE.ZOOM_ROOMS:
                    var pwaDriver = new PwaDriver();
                    installDriver(pwaDriver);
                    installDriver(new ZoomRoomsDriver({baseDriver: pwaDriver}));
                    break;
            }

            if (isPreview) {
                installDriver(new PreviewDriver());
            }

            if (embedProfileService.isEditorMode()) {
                installDriver(new EditorDriver());
            }
        }

        function installCrestronDrivers() {
            installDriver(new CordovaDriver());
            installDriver(new AndroidDriver());
            installDriver(new CrestronDriver(platformUtil.getCapabilities()));
            installDriver(new CrestronSignsDriver());
        }

        function init() {
            autoInstallPlatformDriver();
            installEmbeddedPlatformDriver();
        }

        function installEmbeddedPlatformDriver() {
            var embeddedPlatform = platformUtil.getEmbeddedPlatform();
            if (!embeddedPlatform) {
                return;
            }

            var mappedDriver = EMBEDDEDED_PLATFORM[embeddedPlatform];
            if (!mappedDriver) {
                return;
            }

            // eslint-disable-next-line new-cap
            installDriver(new mappedDriver());
        }

        // Initialise the service
        init();

        // Auto install platform drivers
        function autoInstallPlatformDriver() {
            var userAgent = $window.navigator.userAgent;

            logService.debug(
                'appspace.core.hal',
                'Platform user agent: ' + userAgent
            );
            var platform = platformUtil.getPlatform();
            // if we're loading this app in an iframe on android or ios, use generic driver
            if ($window.self !== $window.top && iframeForceGenericDrivers.indexOf(platform) > -1) {
                platform = platform + IFRAME_SUFFIX;
            }

            logService.info('appspace.core.hal', 'Platform detected as: ' + platform);

            installPlatformDriver(platform);
        }

        // Installs a driver
        function installDriver(driver) {
            if (driver.id == undefined || driver.id == null) {
                console.log('Failed to install driver: ', driver); // eslint-disable-line
                // driver does not contain a valid driver
                return false;
            }

            // Remove the driver if it already exists
            removeDriver(driver.id);

            // Add the driver
            drivers.push(driver);
            logService.debug('appspace.core.hal', 'Installed driver: ' + driver.id);

            return true;
        }

        // Find driver by Id
        function findDriver(id) {
            for (var i = 0; i < drivers.length; i++) {
                var d = drivers[i];
                if (d.id == id) {
                    return d;
                }
            }

            return null;
        }

        // Find driver by Id
        function removeDriver(id) {
            for (var i = 0; i < drivers.length; i++) {
                var d = drivers[i];
                if (d.id == id) {
                    drivers.splice(i, 1);
                    return true;
                }
            }

            return false;
        }

        // Returns all installed drivers
        function getDrivers() {
            return drivers;
        }

        // Checks with the current driver if its ready
        function isReady() {
            var defer = $q.defer();
            var isDriverReady = findImplementation(null, 'isReady');
            logService.isReady().finally(function() {
                if (isDriverReady) {
                    isDriverReady().then(defer.resolve);
                    return;
                }

                defer.resolve();
            });
            return defer.promise;
        }

        // Scans the installed drivers for the implementation of a specific function in a service
        function findImplementations(service, functionName) {
            var implementations = [];
            for (var i = drivers.length - 1; i >= 0; i--) {
                var driver = drivers[i];
                var functionImplementation;
                if (service) {
                    // Get the service interface
                    var serviceInterface = driver[service];
                    // Check if the service interface is implemented
                    if (serviceInterface === undefined) {
                        // Skip this driver
                        continue;
                    }

                    // Check if the function is implemented
                    functionImplementation = serviceInterface[functionName];
                    if (functionImplementation === undefined) {
                        // Skip this driver
                        continue;
                    }
                } else {
                    // Function is not under a service
                    // Check if the function is implemented
                    functionImplementation = driver[functionName];
                    if (functionImplementation === undefined) {
                        // Skip this driver
                        continue;
                    }
                }

                service = '' + service;
                // Found the function implementation in this driver
                logService.trace(
                    'appspace.core.hal',
                    'Found implementations for: ' + service + '->' + functionName
                );
                implementations.push(functionImplementation);
            }

            return implementations;
        }

        // Scans the installed drivers for the implementation of a specific function in a service
        function findImplementation(service, functionName) {
            for (var i = drivers.length - 1; i >= 0; i--) {
                var driver = drivers[i];
                var functionImplementation;
                if (service) {
                    // Get the service interface
                    var serviceInterface = driver[service];
                    // Check if the service interface is implemented
                    if (serviceInterface === undefined) {
                        // Skip this driver
                        continue;
                    }

                    // Check if the function is implemented
                    functionImplementation = serviceInterface[functionName];
                    if (functionImplementation === undefined) {
                        // Skip this driver
                        continue;
                    }
                } else {
                    // Function is not under a service
                    // Check if the function is implemented
                    functionImplementation = driver[functionName];
                    if (functionImplementation === undefined) {
                        // Skip this driver
                        continue;
                    }
                }

                service = '' + service;
                // Found the function implementation in this driver
                // logService.trace("appspace.core.hal", "Found implementation for: " + service + "->" + functionName);
                return functionImplementation;
            }

            service = '' + service;
            // logService.trace("appspace.core.hal", "Could not find implementation for: " + service + "->" + functionName);
            return undefined;
        }

        return {
            // Public interface
            isReady: isReady,
            installDriver: installDriver,
            removeDriver: removeDriver,
            findDriver: findDriver,
            getDrivers: getDrivers,
            findImplementation: findImplementation,
            findImplementations: findImplementations
        };
    }]);
})(window.angular);

/* eslint-disable prefer-rest-params */
(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.service('pathService', function() {
        // to cut off prefix that compatible with different file system
        // file:/// or filesystem:chrome//
        function excludePrefix(path, defaultPrefix) {
            var prefix = '/';
            if (defaultPrefix !== undefined) {
                prefix = defaultPrefix;
            }

            // remove trailing slash
            path = path.replace(/\/+$/, '');

            // extract prefix with double slash
            var prefixIndex = path.indexOf('//');
            var prefixLength = 2;

            // cordova file system take precendence
            // file:////
            var canBeUrl = true;
            if (path.indexOf('///') > -1) {
                prefixIndex = path.indexOf('///');
                prefixLength = 3;
                canBeUrl = false;
            }

            var isUrl = false;
            if (prefixIndex > -1) {
                var index = prefixIndex + prefixLength;
                prefix = path.slice(0, index);
                path = path.slice(index);
                isUrl = canBeUrl && (prefix.indexOf('http://') == 0 || prefix.indexOf('https://') == 0);
            }

            return {
                prefix: prefix,
                path: path,
                isUrl: isUrl
            };
        }

        // join multiple arguments for path
        function join() {
            var path = '';

            // temporary fix
            if (arguments[0] == null) {
                arguments[0] = '';
            }

            var obj = excludePrefix(arguments[0]);
            var prefix = obj.prefix;
            arguments[0] = obj.path;

            // joining string
            for (var i = 0; i < arguments.length; i++) {
                var item = arguments[i];
                if (!$angular.isString(item)) {
                    throw new Error('Arguments of path.join must be strings');
                }

                if (item) {
                    item = normalizeWithoutPrefix(item, obj.isUrl);
                    if (path === '') {
                        path = item;
                    } else {
                        if (path[path.length - 1] === '/') {
                            path = path + item;
                        } else {
                            path = path + '/' + item;
                        }
                    }
                }
            }

            return prefix + path;
        }

        function sanitize(path, isUrl) {
            // exclude prefix for sanitize
            var obj = excludePrefix(path, '');
            var prefix = obj.prefix;
            path = obj.path;

            // Remove file name characters that are invalid
            path = path.split('|').join('_');
            path = path.split('*').join('_');
            path = path.split('<').join('_');
            path = path.split('>').join('_');
            path = path.split(',').join('_');
            path = path.split('#').join('_');
            path = path.split('$').join('_');
            path = path.split('!').join('_');
            path = path.split('`').join('_');
            path = path.split('\'').join('_');
            path = path.split('{').join('_');
            path = path.split('}').join('_');
            path = path.split('"').join('_');
            path = path.split('\\').join('_');
            path = path.split('@').join('_');

            // Remove characters that is invalid for files but not url
            isUrl = isUrl || obj.isUrl;
            if (!isUrl) {
                path = path.split(':').join('_');
                path = path.split('?').join('_');
                path = path.split('%').join('_');
                path = path.split('=').join('_');
                path = path.split('+').join('_');
                path = path.split('&').join('_');
            }

            return prefix + path;
        }

        function normalizeWithoutPrefix(path, isUrl) {
            var result = [];

            // remove invalid system path character
            path = sanitize(path, isUrl);
            var arr = path.split('/');

            for (var i = 0; i < arr.length; i++) {
                var item = arr[i];

                // skip if is empty
                if (item === '' || item === '.') {
                    continue;
                }

                result.push(item);
            }

            return result.join('/');
        }

        // rebuild valid path
        function normalize(path) {
            var obj = excludePrefix(path);
            var prefix = obj.prefix;

            path = normalizeWithoutPrefix(obj.path, obj.isUrl);

            return prefix + path;
        }

        return {
            join: join,
            normalize: normalize,
            sanitize: sanitize
        };
    });

    app.factory('FileEntry', function() {
        var FileEntry = function(file) {
            this.path = file.path;
            this.name = file.name;
            this.isDirectory = file.isDirectory;
        };

        return FileEntry;
    });

    app.factory('FileError', function() {
        var FileError = function(error) {
            this.message = error.message;
            this.path = error.path;
        };

        return FileError;
    });

    app.factory('halFileService', ["$q", "$window", "halDriverService", function($q, $window, halDriverService) {
        function init() {

        }

        // Initialise the service
        init();

        // Returns a platform specific path to a system directory
        // "data" - Persistent and private data storage within the application's sandbox using internal or external memory (auto selected)
        // "internalData" - Persistent and private data storage within the application's sandbox using internal memory
        // "externallData" - Persistent and private data storage within the application's sandbox using external memory
        // "cache" - Directory for cached data files or any files that your app can re-create easily. The OS may delete these files when the device runs low on storage, nevertheless, apps should not rely on the OS to delete files in here.
        function getSystemPath(name) {
            var di = halDriverService.findImplementation('file', 'getSystemPath');
            return di === undefined ? null : di(name);
        }

        // Creates a directory on the local storage
        function createDirectory(path) {
            var di = halDriverService.findImplementation('file', 'createDirectory');
            return di === undefined ? null : di(path);
        }

        // Creates a files on the local storage
        function createFile(path) {
            var di = halDriverService.findImplementation('file', 'createFile');
            return di === undefined ? null : di(path);
        }

        // Read a files on the local storage
        function readFile(path) {
            var di = halDriverService.findImplementation('file', 'readFile');
            return di === undefined ? null : di(path);
        }

        // Write content on file
        function writeFile(path, content) {
            var di = halDriverService.findImplementation('file', 'writeFile');
            return di === undefined ? null : di(path, content);
        }

        // Checks if a file exists in local storage
        function checkFile(path) {
            var di = halDriverService.findImplementation('file', 'checkFile');
            return di === undefined ? null : di(path);
        }

        // Moves or renames a file on a local storage.
        function moveFile(sourcePath, destinationPath) {
            var di = halDriverService.findImplementation('file', 'moveFile');
            return di === undefined ? null : di(sourcePath, destinationPath);
        }

        // Deletes a file on local storage
        function removeFile(path) {
            var di = halDriverService.findImplementation('file', 'removeFile');
            return di === undefined ? null : di(path);
        }

        // Checks if a directory exists in local storage
        function checkDirectory(path) {
            var di = halDriverService.findImplementation('file', 'checkDirectory');
            return di === undefined ? null : di(path);
        }

        // Deletes a directory and its content on local storage
        function removeDirectory(path) {
            var di = halDriverService.findImplementation('file', 'removeDirectory');
            return di === undefined ? null : di(path);
        }

        // Read a directory and it's content
        function enumerateDirectory(path) {
            var di = halDriverService.findImplementation('file', 'enumerateDirectory');
            return di === undefined ? null : di(path);
        }

        // Move directory and it's content
        function moveDirectory(sourcePath, destinationPath) {
            var di = halDriverService.findImplementation('file', 'moveDirectory');
            return di === undefined ? null : di(sourcePath, destinationPath);
        }

        // Convert any path to system url
        function resolveUrl(path) {
            var di = halDriverService.findImplementation('file', 'resolveUrl');
            return di === undefined ? null : di(path);
        }

        // Convert relative url
        function resolveRelative(path) {
            var di = halDriverService.findImplementation('file', 'resolveUrl');
            return di === undefined ? null : di(path);
        }

        function getDefaultDrive() {
            var di = halDriverService.findImplementation('file', 'getDefaultDrive');
            return di === undefined ? $q.resolve(undefined) : di();
        }

        return {
            // Public interface
            getSystemPath: getSystemPath,
            resolveUrl: resolveUrl,
            resolveRelative: resolveRelative,
            createDirectory: createDirectory,
            createFile: createFile,
            readFile: readFile,
            writeFile: writeFile,
            checkFile: checkFile,
            moveFile: moveFile,
            removeFile: removeFile,
            checkDirectory: checkDirectory,
            removeDirectory: removeDirectory,
            enumerateDirectory: enumerateDirectory,
            moveDirectory: moveDirectory,
            getDefaultDrive: getDefaultDrive,
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('halMediaService', ["halDriverService", "logService", "PLAYER_PROPERTY_KEYS", "PRINTER_TYPE", "dataUtil", "$q", function(halDriverService, logService, PLAYER_PROPERTY_KEYS, PRINTER_TYPE, dataUtil, $q) {
        var appDisplayState = true;
        var appDisplayUpdateSubscribers = [];
        var LOG_KEY = 'appspace.core.media';

        function init() {

        }

        // Initialise the service
        init();

        // Returns the current display resolution
        function getDisplayResolution() {
            var di = halDriverService.findImplementation('media', 'getDisplayResolution');
            return di === undefined ? null : di();
        }

        // Returns the list of supported video codecs (e.g. mpg4, mpg2, vob, etc..)
        function getSupportedVideoCodecs() {
            var di = halDriverService.findImplementation('media', 'getSupportedVideoCodecs');
            return di === undefined ? null : di();
        }

        // Returns the list of supported video protocols (e.g hls, mms, rest, file, etc...)
        function getSupportedVideoProtocols() {
            var di = halDriverService.findImplementation('media', 'getSupportedVideoProtocols');
            return di === undefined ? null : di();
        }

        // Returns the media directive for a specific element type
        function getMediaDirective(elementType, options) {
            var di = halDriverService.findImplementation('media', 'getMediaDirective');
            return di === undefined ? {directive: elementType, attributes: ''} : di(elementType, options);
        }

        // Returns the video attributes
        function getVideoAttributes() {
            var di = halDriverService.findImplementation('media', 'getVideoAttributes');
            return di === undefined ? null : di();
        }

        // Set the display rotation
        function setDisplayRotation(rotation) {
            if (typeof rotation === 'string' || rotation instanceof String) {
                var orientation = rotation.toLowerCase();
                if (orientation == 'landscape') {
                    rotation = 0;
                } else if (orientation == 'portrait') {
                    rotation = 90;
                }
            }

            var di = halDriverService.findImplementation('media', 'setDisplayRotation');
            return di === undefined ? null : di(rotation);
        }

        // Get current display rotation
        function getDisplayRotation() {
            var di = halDriverService.findImplementation('media', 'getDisplayRotation');
            return di === undefined ? null : di();
        }

        function setCursorVisible(enable, force) {
            var di = halDriverService.findImplementation('media', 'setCursorVisible');
            return di === undefined ? null : di(enable, force);
        }

        function enableCursorVisibility(enable) {
            var di = halDriverService.findImplementation('media', 'enableCursorVisibility');
            return di === undefined ? null : di(enable);
        }

        function getCursorVisible() {
            var di = halDriverService.findImplementation('media', 'getCursorVisible');
            return di === undefined ? null : di();
        }

        function processMediaURL(url, options) {
            var di = halDriverService.findImplementation('media', 'processMediaURL');
            return di === undefined ? null : di(url, options);
        }

        function setVolume(volume) {
            var di = halDriverService.findImplementation('media', 'setVolume');
            return di === undefined ? null : di(volume);
        }

        function volumeUp(offset) {
            var di = halDriverService.findImplementation('media', 'volumeUp');
            return di === undefined ? null : di(offset);
        }

        function volumeDown(offset) {
            var di = halDriverService.findImplementation('media', 'volumeDown');
            return di === undefined ? null : di(offset);
        }

        function getCurrentVolume(offset) {
            var di = halDriverService.findImplementation('media', 'getCurrentVolume');
            return di === undefined ? null : di();
        }

        function toggleVolumeMute() {
            var di = halDriverService.findImplementation('media', 'toggleVolumeMute');
            return di === undefined ? null : di();
        }

        function syncDisplayResolutionToOrientation() {
            var di = halDriverService.findImplementation('media', 'syncDisplayResolutionToOrientation');
            return di === undefined ? null : di();
        }

        function setSyncDisplayResolutionFlag(flag) {
            var di = halDriverService.findImplementation('media', 'setSyncDisplayResolutionFlag');
            return di === undefined ? null : di(flag);
        }

        function setVideoStretch(videoElement, stretch) {
            var di = halDriverService.findImplementation('media', 'setVideoStretch');
            return di === undefined ? null : di(videoElement, stretch);
        }

        function setCameraOptions(options) {
            var di = halDriverService.findImplementation('media', 'setCameraOptions');
            return di === undefined ? null : di(options);
        }

        function setAppDisplayState(val) {
            appDisplayState = val;
            notifyAppDisplayUpdateSubscribers();
        }

        function getAppDisplayState() {
            return appDisplayState;
        }

        function subscribeAppDisplayUpdate(callback) {
            if (appDisplayUpdateSubscribers.indexOf(callback) == -1) {
                appDisplayUpdateSubscribers.push({callback: callback});
            }
        }

        function unsubscribeAppDisplayUpdate(callback) {
            var index = appDisplayUpdateSubscribers.indexOf(callback);
            if (index != -1) {
                appDisplayUpdateSubscribers.splice(index, 1);
            }
        }

        function notifyAppDisplayUpdateSubscribers() {
            for (var i = 0; i < appDisplayUpdateSubscribers.length; i++) {
                var subscriber = appDisplayUpdateSubscribers[i];
                if (subscriber) {
                    subscriber.callback(appDisplayState);
                }
            }
        }

        function setMaxVideoLayers(maxVideoLayers) {
            var di = halDriverService.findImplementation('media', 'setMaxVideoLayers');
            return di === undefined ? null : di(maxVideoLayers);
        }

        function resolveContentPath(path) {
            var di = halDriverService.findImplementation('media', 'resolveContentPath');
            return di === undefined ? null : di(path);
        }

        function print(appProperties, options) {
            var printerType = appProperties[PLAYER_PROPERTY_KEYS.DEVICE_PRINTER_TYPE] || PRINTER_TYPE.GENERIC;
            var propertyOptions = {};
            try {
                propertyOptions = JSON.parse(appProperties[PLAYER_PROPERTY_KEYS.DEVICE_PRINTER_OPTIONS]);
            } catch (error) {
                logService.error(LOG_KEY, 'Error while parsing JSON from device.printer.options');
            }

            if (dataUtil.isObject(propertyOptions)) {
                Object.assign(options.printOptions, propertyOptions);
            }

            var di = halDriverService.findImplementation('media', 'print');
            return di === undefined ? null : di(printerType, options);
        }

        function processContentDirectiveOptions(content) {
            var di = halDriverService.findImplementation('media', 'processContentDirectiveOptions');
            return di === undefined ? null : di(content);
        }

        function createBrowser(url, onLoadStop, onLoadError) {
            var di = halDriverService.findImplementation('media', 'createBrowser');
            return di === undefined ? null : di(url, onLoadStop, onLoadError);
        }

        function parseQrCode(imageData) {
            var di = halDriverService.findImplementation('media', 'parseQrCode');
            return di === undefined ? null : di(imageData);
        }

        function sanitizeContainerInlineStyling(inlineStyle) {
            var method = halDriverService.findImplementation('media', 'sanitizeContainerInlineStyling');
            return !method ? null : method(inlineStyle);
        }

        function clearContentCache(manifest) {
            var di = halDriverService.findImplementation('media', 'clearContentCache');
            return di === undefined ? null : di(manifest);
        }

        function supportsCardStreaming() {
            var di = halDriverService.findImplementation('media', 'supportsCardStreaming');
            return di === undefined ? null : di();
        }

        function getDeviceMute() {
            var method = halDriverService.findImplementation('media', 'getDeviceMute');
            return method === undefined ? undefined : method();
        }

        function processContentState(state) {
            var method = halDriverService.findImplementation('media', 'processContentState');
            return method === undefined ? false : method(state);
        }

        function isReady() {
            var di = halDriverService.findImplementation('media', 'isReady');
            return di === undefined ? $q.resolve() : di();
        }

        return {
            // Public interface
            getMediaDirective: getMediaDirective,
            getDisplayResolution: getDisplayResolution,
            getSupportedVideoCodecs: getSupportedVideoCodecs,
            getSupportedVideoProtocols: getSupportedVideoProtocols,
            getVideoAttributes: getVideoAttributes,
            setDisplayRotation: setDisplayRotation,
            getDisplayRotation: getDisplayRotation,
            setCursorVisible: setCursorVisible,
            getCursorVisible: getCursorVisible,
            enableCursorVisibility: enableCursorVisibility,
            processMediaURL: processMediaURL,
            setAppDisplayState: setAppDisplayState,
            getAppDisplayState: getAppDisplayState,
            setVideoStretch: setVideoStretch,
            setCameraOptions: setCameraOptions,
            subscribeAppDisplayUpdate: subscribeAppDisplayUpdate,
            unsubscribeAppDisplayUpdate: unsubscribeAppDisplayUpdate,
            setVolume: setVolume,
            volumeUp: volumeUp,
            volumeDown: volumeDown,
            getCurrentVolume: getCurrentVolume,
            toggleVolumeMute: toggleVolumeMute,
            syncDisplayResolutionToOrientation: syncDisplayResolutionToOrientation,
            setSyncDisplayResolutionFlag: setSyncDisplayResolutionFlag,
            setMaxVideoLayers: setMaxVideoLayers,
            resolveContentPath: resolveContentPath,
            print: print,
            processContentDirectiveOptions: processContentDirectiveOptions,
            createBrowser: createBrowser,
            parseQrCode: parseQrCode,
            sanitizeContainerInlineStyling: sanitizeContainerInlineStyling,
            clearContentCache: clearContentCache,
            supportsCardStreaming: supportsCardStreaming,
            getDeviceMute: getDeviceMute,
            processContentState: processContentState,
            isReady: isReady,
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var app = $angular.module('hal');

    app.factory('halNetworkService', ["$q", "$window", "halDriverService", function($q, $window, halDriverService) {
        function init() {}

        // Initialise the service
        init();

        // Get the device's current network ip address
        function getIpAddress() {
            var di = halDriverService.findImplementation('network', 'getIpAddress');
            return di === undefined ? null : di();
        }

        // Downloads a remote file to local storage
        function downloadHttp(sourceUrl, localPath, downloadItem) {
            if (decodeURI(sourceUrl) === sourceUrl) {
                sourceUrl = encodeURI(sourceUrl);
            }

            var di = halDriverService.findImplementation('network', 'downloadHttp');
            return di === undefined ? null : di(sourceUrl, localPath, downloadItem);
        }

        function postDownloadCleanUp(path) {
            var di = halDriverService.findImplementation('network', 'postDownloadCleanUp');
            return di === undefined ? $q.resolve() : di(path);
        }

        function prefetchResources(manifest) {
            var di = halDriverService.findImplementation(
                'network',
                'prefetchResources'
            );
            return di === undefined ? null : di(manifest);
        }

        function clearAllResources(manifest) {
            var di = halDriverService.findImplementation(
                'network',
                'clearAllResources'
            );
            return di === undefined ? $q.resolve() : di(manifest);
        }

        function startServer(config) {
            var di = halDriverService.findImplementation('network', 'startServer');
            return di === undefined ? null : di(config);
        }

        function stopServer() {
            var di = halDriverService.findImplementation('network', 'stopServer');
            return di === undefined ? null : di();
        }

        function subscribeServerMessages(callback) {
            var di = halDriverService.findImplementation(
                'network',
                'subscribeServerMessages'
            );
            return di === undefined ? null : di(callback);
        }

        function unSubscribeServerMessages(callback) {
            var di = halDriverService.findImplementation(
                'network',
                'unSubscribeServerMessages'
            );
            return di === undefined ? null : di(callback);
        }

        function httpGet(url, config) {
            var di = halDriverService.findImplementation('network', 'httpGet');
            return di === undefined ? null : di(url, config);
        }

        function getJSON(url) {
            var di = halDriverService.findImplementation('network', 'getJSON');
            return di === undefined ? null : di(url);
        }

        function startClient(server, deviceinfo) {
            var di = halDriverService.findImplementation('network', 'startClient');
            return di === undefined ? null : di(server, deviceinfo);
        }

        function stopClient() {
            var di = halDriverService.findImplementation('network', 'stopClient');
            return di === undefined ? null : di();
        }

        function subscribeClientMessages(callback) {
            var di = halDriverService.findImplementation(
                'network',
                'subscribeClientMessages'
            );
            return di === undefined ? null : di(callback);
        }

        function unsubscribeClientMessages(callback) {
            var di = halDriverService.findImplementation(
                'network',
                'unsubscribeClientMessages'
            );
            return di === undefined ? null : di(callback);
        }

        function request(configuration) {
            var di = halDriverService.findImplementation('network', 'request');
            return di === undefined ? null : di(configuration);
        }

        function updateResourceManifest(manifest, repository) {
            var driverImplementation = halDriverService.findImplementation('network', 'updateResourceManifest');
            return driverImplementation === undefined ? null : driverImplementation(manifest, repository);
        }

        function processNetworkConnectionState(state) {
            var driverImplementation = halDriverService.findImplementation('network', 'processNetworkConnectionState');
            return driverImplementation === undefined ? null : driverImplementation(state);
        }

        return {
            // Public interface
            getIpAddress: getIpAddress,
            downloadHttp: downloadHttp,
            prefetchResources: prefetchResources,
            clearAllResources: clearAllResources,
            startServer: startServer,
            stopServer: stopServer,
            subscribeServerMessages: subscribeServerMessages,
            unSubscribeServerMessages: unSubscribeServerMessages,
            httpGet: httpGet,
            getJSON: getJSON,
            startClient: startClient,
            stopClient: stopClient,
            subscribeClientMessages: subscribeClientMessages,
            unsubscribeClientMessages: unsubscribeClientMessages,
            request: request,
            processNetworkConnectionState: processNetworkConnectionState,
            postDownloadCleanUp: postDownloadCleanUp,
            updateResourceManifest: updateResourceManifest
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('halPlatformService', ["dataUtil", "halDriverService", "$q", "SYSTEM_DATA", "FEATURE_CODE", function(
        dataUtil,
        halDriverService,
        $q,
        SYSTEM_DATA,
        FEATURE_CODE) {
        var CLAIMABLE_IDS_DEVICE_INFO_PATHS = [
            'hardware.macAddress',
            'hardware.serialNumber',
            'hardware.fingerprint.fingerprint',
            'appspace.referenceDeviceId',
        ];
        var IMPLEMENTATION_NOT_FOUND_MESSAGE = 'Implementation not found.';

        // Reloads the application
        function reload() {
            var di = halDriverService.findImplementation('platform', 'reload');
            return di === undefined ? null : di();
        }

        // Restarts the application
        function reboot() {
            var di = halDriverService.findImplementation('platform', 'reboot');
            return di === undefined ? null : di();
        }

        // Set power on
        function setDisplayOn(boolean) {
            var di = halDriverService.findImplementation('platform', 'setDisplayOn');
            return di === undefined ? null : di(boolean);
        }

        // Get the platform OS
        function getOS() {
            var di = halDriverService.findImplementation('platform', 'getOS');
            return di === undefined ? null : di();
        }

        // Get the platform OS
        function getCpuUsage() {
            var di = halDriverService.findImplementation('platform', 'getCpuUsage');
            return di === undefined ? null : di();
        }

        // Get the browser name
        function getBrowserName() {
            var di = halDriverService.findImplementation('platform', 'getBrowserName');
            return di === undefined ? null : di();
        }

        // Get the browser version
        function getBrowserVersion() {
            var di = halDriverService.findImplementation('platform', 'getBrowserVersion');
            return di === undefined ? null : di();
        }

        // Get the browser engine
        function getBrowserEngine() {
            var di = halDriverService.findImplementation('platform', 'getBrowserEngine');
            return di === undefined ? null : di();
        }

        // Get the fingerprint for the device
        function getFingerprint() {
            var di = halDriverService.findImplementation('platform', 'getFingerprint');
            return di === undefined ? null : di();
        }

        // Get the device model
        function getModel() {
            var di = halDriverService.findImplementation('platform', 'getModel');
            return di === undefined ? null : di();
        }

        // Get the device manufacturer
        function getManufacturer() {
            var di = halDriverService.findImplementation('platform', 'getManufacturer');
            return di === undefined ? null : di();
        }

        // Get the device firmware
        function getFirmware() {
            var di = halDriverService.findImplementation('platform', 'getFirmware');
            return di === undefined ? null : di();
        }

        function getReferenceDeviceId() {
            var di = halDriverService.findImplementation('platform', 'getReferenceDeviceId');
            return di === undefined ? null : di();
        }

        function getReferenceDeviceIdKey() {
            var di = halDriverService.findImplementation('platform', 'getReferenceDeviceIdKey');
            return di === undefined ? null : di();
        }

        function getReferenceDeviceName() {
            var di = halDriverService.findImplementation('platform', 'getReferenceDeviceName');
            return di === undefined ? null : di();
        }

        function updateReferenceDeviceId(key) {
            var di = halDriverService.findImplementation('platform', 'updateReferenceDeviceId');
            return di === undefined ? null : di(key);
        }

        function updateReferenceDeviceName() {
            var di = halDriverService.findImplementation('platform', 'updateReferenceDeviceName');
            return di === undefined ? null : di();
        }

        function getIntegrationType() {
            var di = halDriverService.findImplementation('platform', 'getIntegrationType');
            return di === undefined ? null : di();
        }

        function updateIntegrationType() {
            var di = halDriverService.findImplementation('platform', 'updateIntegrationType');
            return di === undefined ? null : di();
        }

        // Get the device firmware
        function isMeetMinFirmware() {
            var di = halDriverService.findImplementation('platform', 'isMeetMinFirmware');
            return di === undefined ? null : di();
        }

        function getMinVersionRequired() {
            var di = halDriverService.findImplementation('platform', 'getMinVersionRequired');
            return di === undefined ? null : di();
        }

        function executeImplementation(service, methodName) {
            var implementations = halDriverService.findImplementations(service, methodName);
            for (var i = 0; i < implementations.length; i++) {
                var implementation = implementations[i];
                var result = implementation();
                if (dataUtil.isNullOrUndefined(result)) {
                    continue;
                }

                return result;
            }

            return null;
        }

        // Returns the available disk storage
        function getAvailableStorage() {
            return executeImplementation('platform', 'getAvailableStorage');
        }

        // Returns the used disk storage
        function getUsedStorage() {
            var di = halDriverService.findImplementation('platform', 'getUsedStorage');
            return di === undefined ? null : di();
        }

        // Returns the current CPU usage
        function getCPUUsage() {
            var di = halDriverService.findImplementation('platform', 'getCPUUsage');
            return di === undefined ? null : di();
        }

        function getMemoryUsage() {
            var di = halDriverService.findImplementation('platform', 'getMemoryUsage');
            return di === undefined ? null : di();
        }

        function getDisplayInfo() {
            var di = halDriverService.findImplementation('platform', 'getDisplayInfo');
            return di === undefined ? null : di();
        }

        // Returns the local storage object
        function getLocalStorage() {
            var di = halDriverService.findImplementation('platform', 'getLocalStorage');
            return di === undefined ? null : di();
        }

        function getSessionStorage() {
            var di = halDriverService.findImplementation('platform', 'getSessionStorage');
            return di === undefined ? null : di();
        }

        // Return the app policy object
        function getAppPolicy() {
            var di = halDriverService.findImplementation('platform', 'getAppPolicy');
            return di === undefined ? null : di();
        }

        // Set the app policy object in memory
        function setAppPolicy(policy) {
            var di = halDriverService.findImplementation('platform', 'setAppPolicy');
            return di === undefined ? null : di(policy);
        }

        // Clear the app policy object from memory
        function clearAppPolicy() {
            var di = halDriverService.findImplementation('platform', 'clearAppPolicy');
            return di === undefined ? null : di();
        }

        // Returns the support level for a specific feature
        function getFeatureSupport(feature) {
            var implementations = halDriverService.findImplementations('platform', 'getFeatureSupport');
            for (var i = 0; i < implementations.length; i++) {
                var di = implementations[i];
                var support = di(feature);
                if (dataUtil.isNullOrUndefined(support)) {
                    continue;
                }

                return support;
            }

            return null;
        }

        function getSerialNumber() {
            var di = halDriverService.findImplementation('platform', 'getSerialNumber');
            return di === undefined ? $q.resolve() : di();
        }

        function updateSerialNumber(serialNumber) {
            var di = halDriverService.findImplementation('platform', 'updateSerialNumber');
            return di === undefined ? null : di(serialNumber);
        }

        function getMacAddress() {
            var di = halDriverService.findImplementation('network', 'getMacAddress');
            return di === undefined ? $q.resolve() : di();
        }

        function getIpAddress() {
            var di = halDriverService.findImplementation('network', 'getIpAddress');
            return di === undefined ? $q.resolve() : di();
        }

        function getDefaultDrive() {
            var di = halDriverService.findImplementation('file', 'getDefaultDrive');
            return di === undefined ? $q.resolve(undefined) : di();
        }

        function getRuntimeEnvironments() {
            var implementations = halDriverService.findImplementations('platform', 'getRuntimeEnvironments');
            var runtimeEnvironments = [];
            for (var i = 0; i < implementations.length; i++) {
                var driverRuntime = implementations[i]();
                if (!driverRuntime) {
                    continue;
                }

                runtimeEnvironments = runtimeEnvironments.concat(driverRuntime);
            }

            return runtimeEnvironments;
        }

        function getDeviceInfoPromises(deviceInfo, modelResult) {
            var promises = [];
            var getResolutionPromise = getDisplayInfo();
            promises.push(getResolutionPromise);
            getResolutionPromise.then(function(resolution) {
                if (!resolution || !resolution.length) {
                    return;
                }

                var finalResolution = resolution[resolution.length - 1];
                deviceInfo.hardware.resolution = finalResolution.x + ' x ' + finalResolution.y;
            });

            var getStoragePromise = getAvailableStorage();
            promises.push(getStoragePromise);
            getStoragePromise.then(function(storage) {
                deviceInfo.hardware.storage = dataUtil.formatBytes(storage.total, 2);
            });

            var getFirmwarePromise = getFirmware();
            promises.push(getFirmwarePromise);
            getFirmwarePromise.then(function(firmware) {
                deviceInfo.software.firmware = firmware;
            });

            var getSerialNumberPromise = getSerialNumber();
            promises.push(getSerialNumberPromise);
            getSerialNumberPromise.then(function(serial) {
                deviceInfo.hardware.serialNumber = serial;
            });

            var getMemoryPromise = getMemoryUsage();
            promises.push(getMemoryPromise);
            getMemoryPromise.then(function(memory) {
                deviceInfo.hardware.memory = dataUtil.formatBytes(memory.capacity, 2);
            });

            var getMacAddressPromise = getMacAddress();
            promises.push(getMacAddressPromise);
            getMacAddressPromise.then(function(macAddress) {
                deviceInfo.hardware.macAddress = macAddress;
            });

            var getFingerprintPromise = getFingerprint();
            promises.push(getFingerprintPromise);
            getFingerprintPromise.then(function(fingerprint) {
                deviceInfo.hardware.fingerprint = fingerprint;
            });

            var getIpAddressPromise = getIpAddress();
            promises.push(getIpAddressPromise);
            getIpAddressPromise.then(function(result) {
                if (!result || !result.address) {
                    return;
                }

                deviceInfo.hardware.ipAddress = result.address;
            }, $angular.noop);

            var getDefaultDrivePromise = getDefaultDrive();
            promises.push(getDefaultDrivePromise);
            getDefaultDrivePromise.then(function(defaultDrive) {
                deviceInfo.hardware.defaultDrive = defaultDrive;
            });

            if (dataUtil.isPromise(modelResult)) {
                promises.push(modelResult.then(function(model) {
                    deviceInfo.hardware.model = model;
                }));
            }

            return promises;
        }

        // Returns hardware, software, and appspace registration information about a device
        function getDeviceInfo() {
            // $asCoreFrameworkService can be undefined in user mode as user mode doesn't instantiate coreFrameworkService
            if (!window.$asCoreFrameworkService) {
                return $q.resolve({});
            }

            var promise = $q.defer();
            var modelResult = getModel();
            var getConfig = window.$asCoreFrameworkService.getServices().storage.storageLocalStorageService.get;
            var deviceInfo = {
                version: '1.1',
                hardware: {
                    memory: undefined,
                    model: dataUtil.isPromise(modelResult) ? undefined : modelResult,
                    storage: undefined,
                    serialNumber: undefined,
                    macAddress: undefined,
                    resolution: undefined,
                    fingerprint: undefined,
                    defaultDrive: undefined,
                    ipAddress: SYSTEM_DATA.DEFAULT_IP_ADDRESS,
                },
                software: {
                    firmware: undefined,
                    OSVersion: getOS(),
                    appVersion: getConfig('appspace', 'appVersion'),
                    appPackageType: getAppPackageType(),
                    runtimeEnvironments: getRuntimeEnvironments()
                },
                appspace: {
                    deviceGuid: getConfig('device', 'playerguid'),
                    accountId: getConfig('appspace', 'accountId'),
                    instanceGuid: getConfig('appspace', 'instanceGuid'),
                    instanceUrl: getConfig('appspace', 'appspaceURL'),
                    referenceDeviceId: getReferenceDeviceId(),
                    referenceDeviceName: getReferenceDeviceName(),
                    integrationType: getIntegrationType(),
                    deviceType: getAppspaceDeviceType()
                }
            };
            var promises = getDeviceInfoPromises(deviceInfo, modelResult);

            $q.allSettled(promises).then(function() {
                processClaimableIds(deviceInfo);
                promise.resolve(deviceInfo);
            });
            return promise.promise;
        }

        function processClaimableIds(deviceInfo) {
            var claimableIds = [];
            for (var i = 0; i < CLAIMABLE_IDS_DEVICE_INFO_PATHS.length; i++) {
                var claimableId = dataUtil.getObjectValueAtPath(deviceInfo,
                    CLAIMABLE_IDS_DEVICE_INFO_PATHS[i]);
                if (!claimableId) {
                    continue;
                }

                claimableIds.push(claimableId);
            }

            if (!claimableIds.length) {
                return;
            }

            deviceInfo.claimableIds = claimableIds;
        }

        function getTransitionSupport(transition) {
            var di = halDriverService.findImplementation('platform', 'getTransitionSupport');
            return di === undefined ? null : di(transition);
        }

        function setStartupURL(url) {
            var di = halDriverService.findImplementation('platform', 'setStartupURL');
            return di === undefined ? null : di(url);
        }

        // accept both string, file
        // type: file/ command
        // command: path/ script
        function executePlatformScript(args) {
            var di = halDriverService.findImplementation('platform', 'executePlatformScript');
            return di === undefined ? null : di(args);
        }

        function getAppspaceDeviceType() {
            var di = halDriverService.findImplementation('platform', 'getAppspaceDeviceType');
            return di === undefined ? null : di();
        }

        function getAppPackageType() {
            var di = halDriverService.findImplementation('platform', 'getAppPackageType');
            return di === undefined ? null : di();
        }

        function getAppUpdateHandler() {
            var di = halDriverService.findImplementation('platform', 'getAppUpdateHandler');
            return di === undefined ? null : di();
        }

        function getAppRollbackInfo() {
            var di = halDriverService.findImplementation('platform', 'getAppRollbackInfo');
            return di === undefined ? $q.reject(IMPLEMENTATION_NOT_FOUND_MESSAGE) : di();
        }

        function getCommandHandler() {
            var implementations = halDriverService.findImplementations('platform', 'getCommandHandler');
            var inheritedHandler = {};
            for (var i = 0; i < implementations.length; i++) {
                var implementation = implementations[i];
                var result = implementation();
                if (dataUtil.isNullOrUndefined(result)) {
                    continue;
                }

                inheritedHandler = dataUtil.mergeObject(inheritedHandler, result);
            }

            return inheritedHandler;
        }

        function getCommandHandlerFunction(payload, functionName) {
            var operation = dataUtil.isString(payload) ? payload : dataUtil.getObjectValueAtPath(payload, 'operation');
            if (!operation) {
                return;
            }

            var handler = getCommandHandler();
            var commandHandler = handler[operation] || {};
            return commandHandler[functionName];
        }

        function executeCommand(payload) {
            var operation = dataUtil.isString(payload) ? payload : dataUtil.getObjectValueAtPath(payload, 'operation');
            if (!operation) {
                return $q.reject('Command operation is required for command execution');
            }

            var handler = getCommandHandlerFunction(payload, 'execute');
            return handler ? handler(payload) : $q.reject('Unable to find execution handler for command: ' + operation);
        }

        function postCommandExecution(payload) {
            var operation = dataUtil.isString(payload) ? payload : dataUtil.getObjectValueAtPath(payload, 'operation');
            if (!operation) {
                return $q.reject('Command operation is required for post command execution');
            }

            var handler = getCommandHandlerFunction(payload, 'postExecution');
            return handler ? handler(payload) : $q.resolve();
        }

        function execScript(script, opts) {
            var di = halDriverService.findImplementation('platform', 'execScript');
            return di === undefined ? null : di(script, opts);
        }

        function enableVirtualKeyboard(boolean) {
            var di = halDriverService.findImplementation('platform', 'enableVirtualKeyboard');
            return di === undefined ? null : di(boolean);
        }

        function getMaxLogDays() {
            var di = halDriverService.findImplementation('platform', 'getMaxLogDays');
            return di === undefined ? null : di();
        }

        function pingDevice() {
            var di = halDriverService.findImplementation('platform', 'pingDevice');
            return di === undefined ? null : di();
        }

        function hideSplashscreen() {
            var di = halDriverService.findImplementation('platform', 'hideSplashscreen');
            return di === undefined ? null : di();
        }

        function hideStatusBar() {
            var di = halDriverService.findImplementation('platform', 'hideStatusBar');
            return di === undefined ? null : di();
        }

        function showStatusBar() {
            var di = halDriverService.findImplementation('platform', 'showStatusBar');
            return di === undefined ? null : di();
        }

        function keepAwake() {
            var di = halDriverService.findImplementation('platform', 'keepAwake');
            return di === undefined ? null : di();
        }

        function subscribeToStandby(callback) {
            var di = halDriverService.findImplementation('platform', 'subscribeToStandby');
            return di === undefined ? null : di(callback);
        }

        function subscribeToPostMessaging(callback) {
            var di = halDriverService.findImplementation('platform', 'subscribeToPostMessaging');
            return di === undefined ? null : di(callback);
        }

        function startPostMessaging() {
            var di = halDriverService.findImplementation('platform', 'startPostMessaging');
            return di === undefined ? null : di();
        }

        function stopPostMessaging() {
            var di = halDriverService.findImplementation('platform', 'stopPostMessaging');
            return di === undefined ? null : di();
        }

        function raisePostMessage(message) {
            var di = halDriverService.findImplementation('platform', 'raisePostMessage');
            return di === undefined ? null : di(message);
        }

        function clearSessionStorage() {
            var di = halDriverService.findImplementation('platform', 'clearSessionStorage');
            return di === undefined ? null : di();
        }

        function isTransparentRequired(color) {
            var di = halDriverService.findImplementation('platform', 'isTransparentRequired');
            return di === undefined ? false : di(color);
        }

        function getDeviceCapabilities() {
            var shouldMuteMethod = halDriverService.findImplementation('platform', 'shouldMuteVideoOnLoad');
            var appUpdateCapabilities = getAppUpdateCapabilities() || {};
            return {
                shouldMuteOnLoad: shouldMuteMethod === undefined ? true : shouldMuteMethod(),
                concurrentVideoObject: parseInt(getFeatureSupport(FEATURE_CODE.MAX_VIDEO_LAYERS), 10),
                supportVideoTransition: isFeatureSupported(FEATURE_CODE.RENDERING_CSS3_TRANSITIONS),
                supportFileCaching: isFeatureSupported(FEATURE_CODE.FILE_LOCAL_STORAGE),
                supportServiceWorkerCaching: getFeatureSupport(FEATURE_CODE.NETWORK_TRANSPARENT_CACHE),
                supportVideoLayering: isFeatureSupported(FEATURE_CODE.RENDERING_VIDEO_LAYERING),
                supportPendingNativeLoad: true,
                supportDashboardEmbed: getFeatureSupport(FEATURE_CODE.DASHBOARD_EMBED_SUPPORTED),
                appUpdate: {
                    scheduling: Boolean(appUpdateCapabilities.scheduling),
                    frequency: Boolean(appUpdateCapabilities.frequency),
                    downgrade: Boolean(appUpdateCapabilities.downgrade),
                    pinning: Boolean(appUpdateCapabilities.pinning),
                },
                conference: Boolean(getFeatureSupport(FEATURE_CODE.CONFERENCE_SUPPORTED)),
                screenshot: Boolean(getFeatureSupport(FEATURE_CODE.SCREENSHOT_SUPPORTED)),
            };
        }

        function isFeatureSupported(featureKey) {
            return getFeatureSupport(featureKey) === 'yes';
        }

        function enableStatusLight(color) {
            var di = halDriverService.findImplementation('platform', 'enableStatusLight');
            return di === undefined ? false : di(color);
        }

        function disableStatusLight() {
            var di = halDriverService.findImplementation('platform', 'disableStatusLight');
            return di === undefined ? false : di();
        }

        function reset() {
            var di = halDriverService.findImplementation('platform', 'reset');
            return di === undefined ? false : di();
        }

        function clearWebCache() {
            var di = halDriverService.findImplementation('platform', 'clearWebCache');
            return di === undefined ? false : di();
        }

        function persistRegistrationInfo(registrationInfo) {
            var di = halDriverService.findImplementation('platform', 'persistRegistrationInfo');
            return di === undefined ? $q.reject() : di(registrationInfo);
        }

        function raiseChannelLoaded(channelName) {
            var di = halDriverService.findImplementation('platform', 'raiseChannelLoaded');
            return di === undefined ? false : di(channelName);
        }

        function closeApplication() {
            var di = halDriverService.findImplementation('platform', 'closeApplication');
            return di === undefined ? false : di();
        }

        function setBrightness(value) {
            var di = halDriverService.findImplementation('platform', 'setBrightness');
            return di === undefined ? false : di(value);
        }

        function getBrightness() {
            var di = halDriverService.findImplementation('platform', 'getBrightness');
            return di === undefined ? false : di();
        }

        function getAppUpdateCapabilities() {
            var di = halDriverService.findImplementation('platform', 'getAppUpdateCapabilities');
            return di === undefined ? {} : di();
        }

        function updateApp(baseUrl) {
            var di = halDriverService.findImplementation('platform', 'updateApp');
            return di === undefined ? false : di(baseUrl);
        }

        function getSoftwareUpdateConfiguration() {
            var di = halDriverService.findImplementation('platform', 'getSoftwareUpdateConfiguration');
            return di === undefined ? null : di();
        }

        function isUrlPolicyModeEnforced() {
            var di = halDriverService.findImplementation('platform', 'isUrlPolicyModeEnforced');
            return di === undefined ? false : di();
        }

        function isClaimDeviceMode() {
            var di = halDriverService.findImplementation('platform', 'isClaimDeviceMode');
            return di === undefined ? false : di();
        }

        function claimDevice() {
            var di = halDriverService.findImplementation('platform', 'claimDevice');
            return di === undefined ? $q.reject(IMPLEMENTATION_NOT_FOUND_MESSAGE) : di();
        }

        function getSingleAppMode() {
            var di = halDriverService.findImplementation('platform', 'getSingleAppMode');
            return di === undefined ? false : di();
        }

        function enterSingleAppMode() {
            var di = halDriverService.findImplementation('platform', 'enterSingleAppMode');
            return di === undefined ? false : di();
        }

        function exitSingleAppMode() {
            var di = halDriverService.findImplementation('platform', 'exitSingleAppMode');
            return di === undefined ? false : di();
        }

        function getCurrentOccupancySensorState() {
            var di = halDriverService.findImplementation('platform', 'getCurrentOccupancySensorState');
            return di === undefined ? false : di();
        }

        function getOccupancyService() {
            var di = halDriverService.findImplementation('platform', 'getOccupancyService');
            return di === undefined ? false : di();
        }

        function getDeviceMenu() {
            var implementations = halDriverService.findImplementations('platform', 'getDeviceMenu');
            var finalMenu = {};
            for (var i = 0; i < implementations.length; i++) {
                var implementation = implementations[i];
                var result = implementation();
                if (dataUtil.isNullOrUndefined(result)) {
                    continue;
                }

                finalMenu = dataUtil.mergeObject(finalMenu, result);
            }

            return finalMenu;
        }

        function getCronJobs() {
            var di = halDriverService.findImplementation('platform', 'getCronJobs');
            return di === undefined ? [] : di();
        }

        function subscribeToPlatformMessages(callback) {
            var di = halDriverService.findImplementation('platform', 'subscribeToPlatformMessages');
            return di === undefined ? false : di(callback);
        }

        function unsubscribeFromPlatformMessages(callback) {
            var di = halDriverService.findImplementation('platform', 'unsubscribeFromPlatformMessages');
            return di === undefined ? false : di(callback);
        }

        function sendPlatformMessage(payload) {
            var di = halDriverService.findImplementation('platform', 'sendPlatformMessage');
            return di === undefined ? false : di(payload);
        }

        function getRegistrationInformation() {
            var di = halDriverService.findImplementation('platform', 'getRegistrationInformation');
            return di === undefined ? $q.reject() : di();
        }

        function getRegistrationFilePath() {
            var di = halDriverService.findImplementation('platform', 'getRegistrationFilePath');
            return di === undefined ? '' : di();
        }

        function persistRegistrationInfoOnBoot(registrationInfo) {
            var di = halDriverService.findImplementation('platform', 'persistRegistrationInfoOnBoot');
            return di === undefined ? $q.reject() : di(registrationInfo);
        }

        function clearRegistrationInformation() {
            var di = halDriverService.findImplementation('platform', 'clearRegistrationInformation');
            return di === undefined ? $q.reject() : di();
        }

        function processPostMessagingMessage(event, eventOrigin) {
            var di = halDriverService.findImplementation('platform', 'processPostMessagingMessage');
            return di === undefined ? false : di(event, eventOrigin);
        }

        function getPnsMessageProcessor() {
            var di = halDriverService.findImplementation('platform', 'getPnsMessageProcessor');
            return di === undefined ? false : di;
        }

        function initialize() {
            var di = halDriverService.findImplementation('platform', 'initialize');
            return di === undefined ? $q.resolve() : di();
        }

        function postRegistrationCallback() {
            var di = halDriverService.findImplementation('platform', 'postRegistrationCallback');
            return di === undefined ? $q.resolve() : di();
        }

        function getRegistrationError() {
            var di = halDriverService.findImplementation('platform', 'getRegistrationError');
            return di === undefined ? false : di();
        }

        function postUnregistrationCallback() {
            var di = halDriverService.findImplementation('platform', 'postUnregistrationCallback');
            return di === undefined ? $q.resolve() : di();
        }

        return {
            // Public interface
            reload: reload,
            reboot: reboot,
            setDisplayOn: setDisplayOn,
            getOS: getOS,
            getBrowserName: getBrowserName,
            getBrowserVersion: getBrowserVersion,
            getBrowserEngine: getBrowserEngine,
            getFingerprint: getFingerprint,
            getCpuUsage: getCpuUsage,
            getModel: getModel,
            getManufacturer: getManufacturer,
            getFirmware: getFirmware,
            getAvailableStorage: getAvailableStorage,
            getUsedStorage: getUsedStorage,
            getCPUUsage: getCPUUsage,
            getMemoryUsage: getMemoryUsage,
            getDisplayInfo: getDisplayInfo,
            getLocalStorage: getLocalStorage,
            getSessionStorage: getSessionStorage,
            getFeatureSupport: getFeatureSupport,
            getSerialNumber: getSerialNumber,
            updateSerialNumber: updateSerialNumber,
            getMacAddress: getMacAddress,
            getDeviceInfo: getDeviceInfo,
            getReferenceDeviceId: getReferenceDeviceId,
            getReferenceDeviceIdKey: getReferenceDeviceIdKey,
            updateReferenceDeviceId: updateReferenceDeviceId,
            getReferenceDeviceName: getReferenceDeviceName,
            updateReferenceDeviceName: updateReferenceDeviceName,
            getIntegrationType: getIntegrationType,
            updateIntegrationType: updateIntegrationType,
            getAppPolicy: getAppPolicy,
            setAppPolicy: setAppPolicy,
            clearAppPolicy: clearAppPolicy,
            setStartupURL: setStartupURL,
            executePlatformScript: executePlatformScript,
            getAppspaceDeviceType: getAppspaceDeviceType,
            getAppPackageType: getAppPackageType,
            getAppUpdateHandler: getAppUpdateHandler,
            getAppRollbackInfo: getAppRollbackInfo,
            execScript: execScript,
            getTransitionSupport: getTransitionSupport,
            enableVirtualKeyboard: enableVirtualKeyboard,
            isMeetMinFirmware: isMeetMinFirmware,
            getMinVersionRequired: getMinVersionRequired,
            getMaxLogDays: getMaxLogDays,
            pingDevice: pingDevice,
            hideSplashscreen: hideSplashscreen,
            hideStatusBar: hideStatusBar,
            showStatusBar: showStatusBar,
            keepAwake: keepAwake,
            subscribeToStandby: subscribeToStandby,
            subscribeToPostMessaging: subscribeToPostMessaging,
            startPostMessaging: startPostMessaging,
            stopPostMessaging: stopPostMessaging,
            raisePostMessage: raisePostMessage,
            clearSessionStorage: clearSessionStorage,
            isTransparentRequired: isTransparentRequired,
            getDeviceCapabilities: getDeviceCapabilities,
            enableStatusLight: enableStatusLight,
            disableStatusLight: disableStatusLight,
            reset: reset,
            clearWebCache: clearWebCache,
            persistRegistrationInfo: persistRegistrationInfo,
            raiseChannelLoaded: raiseChannelLoaded,
            closeApplication: closeApplication,
            setBrightness: setBrightness,
            getBrightness: getBrightness,
            updateApp: updateApp,
            getAppUpdateCapabilities: getAppUpdateCapabilities,
            getSoftwareUpdateConfiguration: getSoftwareUpdateConfiguration,
            isUrlPolicyModeEnforced: isUrlPolicyModeEnforced,
            isClaimDeviceMode: isClaimDeviceMode,
            getSingleAppMode: getSingleAppMode,
            enterSingleAppMode: enterSingleAppMode,
            exitSingleAppMode: exitSingleAppMode,
            getCurrentOccupancySensorState: getCurrentOccupancySensorState,
            getOccupancyService: getOccupancyService,
            getDeviceMenu: getDeviceMenu,
            getCronJobs: getCronJobs,
            subscribeToPlatformMessages: subscribeToPlatformMessages,
            unsubscribeFromPlatformMessages: unsubscribeFromPlatformMessages,
            sendPlatformMessage: sendPlatformMessage,
            persistRegistrationInfoOnBoot: persistRegistrationInfoOnBoot,
            clearRegistrationInformation: clearRegistrationInformation,
            getRegistrationInformation: getRegistrationInformation,
            getRegistrationFilePath: getRegistrationFilePath,
            processPostMessagingMessage: processPostMessagingMessage,
            getPnsMessageProcessor: getPnsMessageProcessor,
            initialize: initialize,
            postRegistrationCallback: postRegistrationCallback,
            getRegistrationError: getRegistrationError,
            postUnregistrationCallback: postUnregistrationCallback,
            claimDevice: claimDevice,
            executeCommand: executeCommand,
            postCommandExecution: postCommandExecution,
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.constant('REMOTEEVENTTYPE', {
        buttonDown: 1,
        buttonUp: 2,
        buttonRepeat: 3
    });

    app.constant('REMOTEVCODE', {
        up: 1,
        down: 2,
        left: 3,
        right: 4,
        select: 5,
        back: 6,
        menu: 7,
        power: 8,
        mute: 9,
        page_up: 10,
        page_down: 11,
        system: 12,
        esc: 13,
        no_0: 14,
        no_1: 15,
        no_2: 16,
        no_3: 17,
        no_4: 18,
        no_5: 19,
        no_6: 20,
        no_7: 21,
        no_8: 22,
        no_9: 23,
        play: 24,
        pause: 25,
        guide: 26,
        volume_up: 27,
        volume_down: 28,
        channel_up: 29,
        channel_down: 30,
        toggle_keyboard: 31,
        shift: 32,
        minus: 33,
        plus: 34,
        equal: 35,
        home: 36,
        pause_toggle: 37
    });

    app.factory('RemoteEvent', function() {
        function RemoteEvent(e) {
            // default parameter
            this.code = e && e.which ? e.which : null; // The native code
            this.altKey = e && e.altKey ? e.altKey : false;
            this.ctrlKey = e && e.ctrlKey ? e.ctrlKey : false;
            this.shiftKey = e && e.shiftKey ? e.shiftKey : false;
            this.eventType = e && e.eventtype ? e.eventtype : null; // Type of event
            this.vcode = e && e.vcode ? e.vcode : null; // The virtual code
            this.defaultPrevented = false; // Prevent further processing
            this.iskeyboard = false;
        }

        // Define the instance methods and properties
        RemoteEvent.prototype = {
            eventType: 0,
            code: 0,
            vcode: null
        };

        // Trigger prevent default
        RemoteEvent.prototype.preventDefault = function() {
            this.defaultPrevented = true;
        };

        // Check prevent default
        RemoteEvent.prototype.isDefaultPrevented = function() {
            return this.defaultPrevented;
        };

        return RemoteEvent;
    });

    app.factory('RemoteUtil', ["RemoteEvent", "REMOTEVCODE", function(RemoteEvent, REMOTEVCODE) {
        function processDefaultKeyboardMapping(e) {
            if (!e) {
                return null;
            }

            var re = new RemoteEvent();
            re.code = e.which;
            re.altKey = e.altKey;
            re.ctrlKey = e.ctrlKey;
            re.shiftKey = e.shiftKey;
            // numbers
            // 48 is 0 & 57 is 9
            if (e.which >= 48 && e.which <= 57) {
                re.vcode = 14 + (e.which - 48);
                return re;
            }

            switch (e.which) {
                case 36: // home
                    // mimic guide
                    re.vcode = REMOTEVCODE.guide;
                    break;
                case 37: // left
                    re.vcode = REMOTEVCODE.left;
                    break;
                case 38: // up
                    re.vcode = REMOTEVCODE.up;
                    break;
                case 39: // right
                    re.vcode = REMOTEVCODE.right;
                    break;
                case 40: // down
                    re.vcode = REMOTEVCODE.down;
                    break;
                case 13: // ENTER
                    re.vcode = REMOTEVCODE.select;
                    break;
                case 82: // ctrl + alt + r
                    re.vcode = re.altKey && re.ctrlKey ? REMOTEVCODE.select : null;
                    break;
                case 8: // back space
                case 93: // Command / Windows
                case 27: // Esc
                    re.vcode = REMOTEVCODE.back;
                    break;
                case 77: // ctrl+alt+m
                    re.vcode = re.altKey && re.ctrlKey ? REMOTEVCODE.system : null;
                    break;
                case 75: // ctrl+alt+k
                    re.vcode = re.altKey && re.ctrlKey ? REMOTEVCODE.toggle_keyboard : null;
                    break;
                case 117:// Shift+F6 or Hang Up icon on Asus remote control
                    re.vcode = re.shiftKey ? REMOTEVCODE.back : null;
                    break;
                case 112:// Shift+F1 or Burger icon on Asus remote control
                    re.vcode = re.shiftKey ? REMOTEVCODE.system : null;
                    break;
                case 16: // shift
                    re.vcode = REMOTEVCODE.shift;
                    break;
                case 107: // plus at numpad
                    re.vcode = REMOTEVCODE.plus;
                    break;
                case 109: // minus at numpad
                    re.vcode = REMOTEVCODE.minus;
                    break;
                case 187: // shift + plus
                case 61: // shift + plus on firefox
                    re.vcode = re.shiftKey ? REMOTEVCODE.plus : REMOTEVCODE.equal;
                    break;
                case 189: // minus
                case 173: // minus on firefox
                    re.vcode = !re.shiftKey ? REMOTEVCODE.minus : null;
                    break;
                }

            return (re.vcode == null) ? null : re;
        }

        return {
            processDefaultKeyboardMapping: processDefaultKeyboardMapping
        };
    }]);

    app.factory('halRemoteService', ["$rootScope", "$q", "$window", "$timeout", "halDriverService", "logService", "REMOTEEVENTTYPE", function($rootScope, $q, $window, $timeout, halDriverService, logService, REMOTEEVENTTYPE) {
        var handlerRunningId = 1;
        function init() {

        }

        var subscribers = [];
        var lastEventTime = null;

        // Initialise the service
        init();

        // Returns whether the device supports remote control events
        function isSupported() {
            var di = halDriverService.findImplementation('remote', 'isSupported');
            return di === undefined ? false : di();
        }

        function createRemoteEvent(e) {
            var di = halDriverService.findImplementation('remote', 'createRemoteEvent');
            return di === undefined ? null : di(e);
        }

        // Registers a callback for remote events and returns an id to unsubscribe later
        function registerEvents(callback) {
            var di = halDriverService.findImplementation('remote', 'registerEvents');
            // Check does the device support register events
            if (di === undefined) {
                return null;
            } else {
                // Is this our first subscriber
                if (subscribers.length == 0) {
                    // Subscribe to driver remote events
                    di(onRemoteEvent);
                }

                var subscriber = {callback: callback, id: handlerRunningId++};
                subscribers.push(subscriber);
                return subscriber.callback;
            }
        }

        // Unregisters an existing callback for remote events
        function unRegisterEvents(callback) {
            var di = halDriverService.findImplementation('remote', 'unRegisterEvents');
            // Check does the device support register events
            if (di === undefined) {
                return null;
            } else {
                // Find subsriber based on handler
                for (var i = 0; i < subscribers.length; i++) {
                    if (subscribers[i].callback == callback) {
                        subscribers.splice(i, 1);
                        break;
                    }
                }

                // Is this our last subscriber
                if (subscribers.length == 0) {
                    // Unsubscribe to driver remote events
                    di(onRemoteEvent);
                }

                return;
            }
        }

        // Returns a list of supported V codes
        function getSupportedVCodes() {
            var di = halDriverService.findImplementation('remote', 'getSupportedVCodes');
            return di === undefined ? null : di();
        }

        // Returns the timestamp for the last remote event
        function getLastEventTime() {
            return lastEventTime;
        }

        // Reset the last event time to null to remove indication of remote event
        function resetLastEventTime() {
            lastEventTime = null;
        }

        // Receives hardware remote events and forwards to subscribers
        function onRemoteEvent(remoteEvent, ignoreLastEventTime) {
            // Validate event
            if (remoteEvent.type < 1 || remoteEvent.vCode < 1) {
                // Invalid event
                // return;
            }

            if (ignoreLastEventTime != true) {
                lastEventTime = new Date();
            }

            logService.trace('appspace.core.hal', 'On remote event: type: ' + remoteEvent.eventType + ', code: ' + remoteEvent.code + ', vcode: ' + remoteEvent.vcode);

            // Call $rootScope.$apply to ensure callback is call in a new cycle
            $rootScope.$apply(function() {
                // Find subsriber based on handler
                for (var i = subscribers.length - 1; i >= 0; i--) {
                    var subscriber = subscribers[i];
                    subscriber.callback(remoteEvent);
                    if (remoteEvent.isDefaultPrevented()) {
                        // No further processing needed
                        return;
                    }
                }
            });
        }

        function raiseRemoteEvent(e, ignore) {
            ignore = ignore === undefined ? true : ignore;
            $timeout(function() {
                onRemoteEvent(e, ignore);
            }, 0);
        }

        function handleSystemKeydownEvent(e) {
            handleSystemEvent(e, REMOTEEVENTTYPE.buttonDown);
        }

        function handleSystemKeyupEvent(e) {
            handleSystemEvent(e, REMOTEEVENTTYPE.buttonup);
        }

        function handleSystemEvent(e, type) {
            var event = createRemoteEvent(e);
            if (event && event.isSystem) {
                event.eventType = type;
                raiseRemoteEvent(event, false);
                e.preventDefault();
            }
        }

        return {
            // Public interface
            registerEvents: registerEvents,
            unRegisterEvents: unRegisterEvents,
            getSupportedEvents: getSupportedVCodes,
            isSupported: isSupported,
            getLastEventTime: getLastEventTime,
            resetLastEventTime: resetLastEventTime,
            raiseRemoteEvent: raiseRemoteEvent,
            handleSystemKeydownEvent: handleSystemKeydownEvent,
            handleSystemKeyupEvent: handleSystemKeyupEvent
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('FileUtil', function() {
        var SPLIT_CONFIGURATION = {
            FORWARDSLASH: {
                delimiter: '/',
                trailingRegex: /\/+$/,
                leadingRegex: /^\//
            },
            BACKSLASH: {
                delimiter: '\\',
                trailingRegex: /\\+$/,
                leadingRegex: /^\\/
            }
        };

        function getDirPath(path) {
            return splitPath(path, SPLIT_CONFIGURATION.FORWARDSLASH);
        }

        function getContentPath(path) {
            return splitPath(path, getPathDelimeter(path));
        }

        function getPathDelimeter(path) {
            return path.indexOf(SPLIT_CONFIGURATION.BACKSLASH.delimiter) !== -1 ? SPLIT_CONFIGURATION.BACKSLASH : SPLIT_CONFIGURATION.FORWARDSLASH;
        }

        function splitPath(path, configuration) {
            // remove trailing slash
            path = path.replace(configuration.trailingRegex, '');
            return path.slice(0, path.lastIndexOf(configuration.delimiter) + 1);
        }

        function getBaseName(path) {
            // remove trailing slash
            path = path.replace(/\/+$/, '');
            return path.slice(path.lastIndexOf('/') + 1, path.length);
        }

        function getFileExtension(file) {
            // if filename is empty
            if (!file) {
                return null;
            }

            // if filename doesn't contain ., return null
            if (file.indexOf('.') == -1) {
                return null;
            }

            var segs = file.split('.');
            return segs[segs.length - 1];
        }

        function getFilePriority(file) {
            var mediaTypePriorities = {
                // images
                bmp: 6,
                jpg: 6,
                jpeg: 6,
                tiff: 6,
                png: 6,
                gif: 6,
                // videos
                mpg: 5,
                mpeg: 5,
                wmv: 5,
                mov: 5,
                mpg4: 5,
                mp4: 5,
                mpg2: 5,
                mp2: 5,
                ts: 5,
                wav: 5,
                mp3: 5,
                aac: 5,
                flac: 5,
                ogg: 5,
                mkv: 5,
                webm: 5,
                // cards
                json: 4,
                css: 3,
                js: 2,
                html: 1
            };
            return mediaTypePriorities[getFileExtension(file)] || 0;
        }

        function removeTrailingSlash(path, delimiter) {
            return path ? path.replace(delimiter.trailingRegex, '') : path;
        }

        function removeLeadingSlash(path, delimiter) {
            return path ? path.replace(delimiter.leadingRegex, '') : path;
        }

        function cleanPath(basePath, fullPath) {
            if (!basePath || !basePath.length || !fullPath || !fullPath.length) {
                return fullPath;
            }

            var result = fullPath;

            if (!fullPath.indexOf(basePath + '/')) {
                result = fullPath.substring(basePath.length + 1);
            }

            // remove leading slashes
            while (result.length > 1 && result[0] === '/') {
                result = result.substring(1);
            }

            return result === '/' ? '' : result;
        }

        function toDoubleBackslash(path) {
            return !path || !path.replace ? path : path.replace(/\\/g, '\\\\');
        }

        function join(sourcePath, targetPath) {
            var configuration = getPathDelimeter(sourcePath);
            return removeTrailingSlash(sourcePath, configuration) +
                configuration.delimiter +
                removeLeadingSlash(targetPath, configuration);
        }

        return {
            getDirPath: getDirPath,
            getContentPath: getContentPath,
            getBaseName: getBaseName,
            getFileExtension: getFileExtension,
            getFilePriority: getFilePriority,
            cleanPath: cleanPath,
            toDoubleBackslash: toDoubleBackslash,
            join: join
        };
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('crestronAndroidUtil', ["$q", "$window", "$timeout", function($q, $window, $timeout) {
        var DEVICE_READY_EVENT = 'deviceready';
        var crestronPlugin = {};
        var isReadyPromise = $q.defer();
        var PERSIST_DATA_DELAY = 1000;

        $window.document.removeEventListener(DEVICE_READY_EVENT, onDeviceReady);
        $window.document.addEventListener(DEVICE_READY_EVENT, onDeviceReady, false);

        function onDeviceReady() {
            crestronPlugin = (($window.cordova || {}).plugins || {}).AppspaceCrestronController;
            isReadyPromise.resolve();
        }

        function isReady() {
            return isReadyPromise.promise;
        }

        function invokePluginMethod(methodName) {
            var method = crestronPlugin[methodName];
            if (typeof method === 'function') {
                return method.apply($window, Array.prototype.splice.call(arguments, 1));
            }

            var errorCallback = Array.prototype.splice.call(arguments, 2, 1)[0];
            return typeof errorCallback === 'function' ?
                errorCallback(methodName + ' method not found') :
                undefined;
        }

        function persistRegistrationInfo() {
            var promise = $q.defer();
            $timeout(function() {
                invokePluginMethod('callCacheAction', true, promise.resolve, promise.reject);
            }, PERSIST_DATA_DELAY);
            return promise.promise;
        }

        function clearRegistrationInformation() {
            return persistRegistrationInfo();
        }

        return {
            isReady: isReady,
            invokePluginMethod: invokePluginMethod,
            clearRegistrationInformation: clearRegistrationInformation,
            persistRegistrationInfo: persistRegistrationInfo
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('driverUtil', ["$injector", "$q", "$window", "dataUtil", "SYSTEM_DATA", function(
        $injector,
        $q,
        $window,
        dataUtil,
        SYSTEM_DATA) {
        var FINGERPRINT_KEY = {
            DEFAULT: 'platform.fingerprint',
            GENERIC: 'browser-generic.platform.fingerprint',
        };
        var registrationUtil;
        var halPlatformService;
        var coreFrameworkService;

        function getHalPlatformService() {
            if (!halPlatformService) {
                halPlatformService = $injector.get('halPlatformService');
            }

            return halPlatformService;
        }

        function getCoreFrameworkService() {
            if (!coreFrameworkService) {
                coreFrameworkService = $injector.get('coreFrameworkService');
            }

            return coreFrameworkService;
        }

        function getSessionService() {
            return $injector.get('sessionService');
        }

        function clearSessionStorage(onSessionCleared) {
            var sessionService = getSessionService();
            var sessionStorage = sessionService.getSessionStorage();
            if (!sessionStorage) {
                return;
            }

            var urlParameters = sessionService.getUrlParameters();
            sessionStorage.clear();
            if (!onSessionCleared) {
                return;
            }

            onSessionCleared(urlParameters, sessionService);
        }

        function getLocalStorageItem(key) {
            var localStorage = getHalPlatformService().getLocalStorage();
            return localStorage.getItem(key);
        }

        function setLocalStorageItem(key, value) {
            var localStorage = getHalPlatformService().getLocalStorage();
            localStorage.setItem(key, value);
        }

        function removeLocalStorageItem(key) {
            var localStorage = getHalPlatformService().getLocalStorage();
            localStorage.removeItem(key);
        }

        function getSerialNumber() {
            return getHalPlatformService().getSerialNumber();
        }

        function getReferenceDeviceId() {
            return getHalPlatformService().getReferenceDeviceId();
        }

        // Get the fingerprint for the device
        function getFingerprint(key) {
            key = key || FINGERPRINT_KEY.DEFAULT;
            var fingerprint = getLocalStorageItem(key);
            if (!fingerprint) {
                fingerprint = uuid.v1();
                setLocalStorageItem(key, fingerprint);
            }

            return $q.resolve({
                fingerprint: fingerprint,
                type: SYSTEM_DATA.FINGERPRINT_TYPE.GENERATED
            });
        }

        function getGeneratedFingerprint() {
            var generatedFingerprint = {
                fingerprint: '',
                key: '',
            };
            var defaultFingerprint = getLocalStorageItem(FINGERPRINT_KEY.DEFAULT);
            if (defaultFingerprint) {
                generatedFingerprint.fingerprint = defaultFingerprint;
                generatedFingerprint.key = FINGERPRINT_KEY.DEFAULT;
            } else {
                var genericFingerprint = getLocalStorageItem(FINGERPRINT_KEY.GENERIC);
                if (genericFingerprint) {
                    generatedFingerprint.fingerprint = genericFingerprint;
                    generatedFingerprint.key = FINGERPRINT_KEY.GENERIC;
                }
            }

            return generatedFingerprint;
        }

        function removeGeneratedFingerprint(key) {
            removeLocalStorageItem(key);
        }

        function getRegistrationUtil() {
            if (!registrationUtil) {
                registrationUtil = $injector.get('RegistrationUtil');
            }

            return registrationUtil;
        }

        function resetSessionStorage(persistUrlParameterKeys) {
            clearSessionStorage(function(urlParameters, sessionService) {
                getRegistrationUtil().setPassiveMode(urlParameters);
                var urlParametersToSave = {registrationmode: urlParameters.registrationmode};
                if (dataUtil.isArray(persistUrlParameterKeys)) {
                    for (var i = 0; i < persistUrlParameterKeys.length; i++) {
                        var key = (persistUrlParameterKeys[i] || '').toLowerCase();
                        if (!key) {
                            continue;
                        }

                        urlParametersToSave[key] = urlParameters[key];
                    }
                }

                sessionService.set('urlparameters', urlParametersToSave);
            });
        }

        // Find driver by Id
        function findDriver(id, drivers) {
            if (!id || !drivers) {
                return null;
            }

            for (var i = 0; i < drivers.length; i++) {
                var driver = drivers[i];
                if (!driver || !driver.id || driver.id !== id) {
                    continue;
                }

                return driver;
            }

            return null;
        }

        function isFingerprintFormatValid(fingerprint) {
            return Boolean(fingerprint && !dataUtil.containsWhitespace(fingerprint));
        }

        function getOS() {
            return getHalPlatformService().getOS();
        }

        function getBrowserName() {
            return getHalPlatformService().getBrowserName();
        }

        function getModel(platformModel) {
            return platformModel || (getOS() + ' ' + getBrowserName());
        }

        function getSelfUpdateBaseUrl() {
            return getCoreFrameworkService().getSelfUpdateUrl() || $window.SELF_UPDATE_BASE_URL || '';
        }

        function getSelfUpdateConfiguration() {
            var baseUpdateUrl = getSelfUpdateBaseUrl();
            return {
                baseUpdateUrl: baseUpdateUrl,
                isSupportedByDevice: getHalPlatformService().getFeatureSupport('device.supportselfupdate'),
                updateApp: function() {
                    return getHalPlatformService().updateApp(baseUpdateUrl);
                },
            };
        }

        function getIntegrationType() {
            return getHalPlatformService().getIntegrationType() || '';
        }

        return {
            resetSessionStorage: resetSessionStorage,
            clearSessionStorage: clearSessionStorage,
            findDriver: findDriver,
            getSerialNumber: getSerialNumber,
            getReferenceDeviceId: getReferenceDeviceId,
            isFingerprintFormatValid: isFingerprintFormatValid,
            getFingerprint: getFingerprint,
            getModel: getModel,
            getIntegrationType: getIntegrationType,
            getGeneratedFingerprint: getGeneratedFingerprint,
            removeGeneratedFingerprint: removeGeneratedFingerprint,
            getSelfUpdateConfiguration: getSelfUpdateConfiguration
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('ipcRendererUtil', ["$window", "$q", "dataUtil", function($window, $q, dataUtil) {
        function isExists() {
            return Boolean($window.ipcRenderer);
        }

        function invoke(message) {
            var promise = $q.defer();
            // eslint-disable-next-line prefer-rest-params
            $window.ipcRenderer.invoke(message, Array.prototype.slice.call(arguments))
                .then(promise.resolve, function(error) {
                    // Error invoking remote method 'message': JSON stringify
                    var matchResult = error.message.match(new RegExp('Error invoking remote method \'' + message + '\':\\s(.+)'));
                    if (matchResult.length < 2) {
                        promise.reject(error);
                        return;
                    }

                    var result = dataUtil.tryParseJSON(matchResult[1]);
                    if (!result) {
                        promise.reject(error);
                        return;
                    }

                    promise.reject(result);
                });
            return promise.promise;
        }

        return {
            invoke: invoke,
            isExists: isExists
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.factory('platformUtil', ["$q", "$document", "$window", "$location", "dataUtil", "PLATFORM_TYPE", "INTEGRATION_TYPE", function($q, $document, $window, $location, dataUtil, PLATFORM_TYPE, INTEGRATION_TYPE) {
        var supportedPackageFor = {
            crestron: 'crestron',
            pwa: 'pwa',
            macos: 'macos',
            windows: 'windows',
            ios: 'ios',
            linux: 'linux',
            debian: 'debian',
            androidtv: 'androidtv',
            logitech: 'logitech',
            webview2: 'webview2',
            neat: 'neat',
            iadea: 'iadea',
            android: 'android'
        };
        var pwaWithDrivers = [
            PLATFORM_TYPE.MERCURY,
            PLATFORM_TYPE.WEBEXBOARD,
            PLATFORM_TYPE.WEBEXROOMKIT,
            PLATFORM_TYPE.WEBEXROOMNAVIGATOR,
            PLATFORM_TYPE.WEBEX,
            PLATFORM_TYPE.IE11,
            PLATFORM_TYPE.MERSIVE,
            PLATFORM_TYPE.GOOGLEMEET,
            PLATFORM_TYPE.MTR,
            PLATFORM_TYPE.QBIC,
            PLATFORM_TYPE.ZOOM_ROOMS
        ];
        var crestronPlatforms = [
            PLATFORM_TYPE.AIRMEDIA200,
            PLATFORM_TYPE.AIRMEDIA300,
            PLATFORM_TYPE.AIRMEDIA,
            PLATFORM_TYPE.AIRMEDIA3K,
            PLATFORM_TYPE.CCE,
        ];
        var polyPlatforms = [PLATFORM_TYPE.POLY];

        function getSupportedPackageFor() {
            return supportedPackageFor[getPackageType()];
        }

        function getPackageType() {
            $window.appspace = $window.appspace || {};
            return $window.appspace.packageFor;
        }

        function getEmbeddedPlatform() {
            var urlParameters = getUrlParameters() || {};
            return (urlParameters['embeddedplatform'] || '').toLowerCase();
        }

        function getCapabilities() {
            $window.appspace = $window.appspace || {};
            return $window.appspace.capabilities;
        }

        function getUserAgent() {
            return $window.navigator.userAgent;
        }

        function toQueryObject(queryString) {
            var result = {};
            var splits = queryString.split('&');
            for (var i = 0; i < splits.length; i++) {
                var keyValuePair = dataUtil.getKeyValuePairFromString(splits[i]);
                if (!keyValuePair) {
                    continue;
                }

                result[keyValuePair.key.toLowerCase()] = decodeURI(keyValuePair.value);
            }

            return result;
        }

        function getUrlParametersFromSessionStorage() {
            var DEFAULT_URL_PARAMETERS = {};
            var urlParameters = DEFAULT_URL_PARAMETERS;
            if (!$window.sessionStorage) {
                return urlParameters;
            }

            urlParameters = $window.sessionStorage.getItem('urlparameters');
            return urlParameters ? JSON.parse(urlParameters) : DEFAULT_URL_PARAMETERS;
        }

        function getCurrentUrlParameters() {
            return $location.search() || toQueryObject(
                ($window.location.search || '').substr(1));
        }

        function getUrlParameters() {
            // Get URL parameters from session storage if
            // current URL parameters are empty
            var currentUrlParameters = getCurrentUrlParameters();
            return Object.keys(currentUrlParameters).length > 0 ?
                currentUrlParameters : getUrlParametersFromSessionStorage();
        }

        function detectPlatform(userAgent, packageType) {
            userAgent = userAgent || getUserAgent();
            // detection of platform via user agent
            // note: order is important
            var browsers = {
                nme: /NME-/i,
                mercury: /mercury/i,
                airmedia3k: /AM3X00/i,
                airmedia300: /AM-300/i,
                airmedia200: /AM-200/i,
                cce: /CCE/i,
                airmedia: /AM-\d{3,4}(?:-WF)?(?:-I)?/i,
                mersive: /solstice pod/i,
                qbic: /SmartAPI/i,
                poly: /poly/i,
                iadea: /ADAPI/i,
                amazonsignage: /AFKLNK/i,
                android: /android/i,
                surevue: /SureVue/i,
                webos: /Web0S/i,
                tizen: /tizen/i,
                uwp: /MSAppHost/i,
                CrOS: /CrOS/i,
                brightsign: /BrightSign/i,
                sssd: /SmartTV/i,
                iec: /IEC/i,
                webexboard: /cisco (?:webex\s)?board/i,
                webexroomkit: /cisco (?:webex\s)?room kit/i,
                webexroomnavigator: /cisco (?:webex\s)?room navigator/i,
                webex: /roomos/i, // non-identified roomos devices. must follow after other webex devices.
                microsoftteamsrooms: /teams/i,
                chrome: /chrome/i,
                safari: /safari/i,
                firefox: /firefox/i,
                ie: /internet explorer/i,
                ie11: /Trident\/.*rv:([0-9]{1,}[\.0-9]{0,})/i,// eslint-disable-line
            };

            var browser = 'generic';
            for (var key in browsers) {
                if (browsers[key].test(userAgent)) {
                    browser = key;
                    break;
                }
            }

            var urlParameters = getUrlParameters();
            // detect is chrome app
            if (browser === 'chrome' && typeof chrome === 'object' && chrome.storage && chrome.storage.local) {
                browser = 'chromeApp';
            } else if (browser === 'CrOS' && urlParameters.google_meet_integration_id) {
                browser = PLATFORM_TYPE.GOOGLEMEET;
            }

            if (urlParameters && urlParameters.integrationtype) {
                switch (urlParameters.integrationtype) {
                    case INTEGRATION_TYPE.MTR:
                        browser = PLATFORM_TYPE.MTR;
                        break;
                    case INTEGRATION_TYPE.ZOOM:
                    case INTEGRATION_TYPE.ZOOM_ROOMS:
                        browser = PLATFORM_TYPE.ZOOM_ROOMS;
                        break;
                }
            }

            // Android package on Qbic is not supported
            if (browser === PLATFORM_TYPE.QBIC && packageType === PLATFORM_TYPE.ANDROID) {
                browser = PLATFORM_TYPE.ANDROID;
            }

            // detection of node webkit
            var isNode = (typeof process !== 'undefined' && typeof require !== 'undefined');
            if (isNode) {
                var isNodeWebkit = false;
                try {
                    isNodeWebkit = (typeof require('nw.gui') !== 'undefined');
                } catch (e) {
                    isNodeWebkit = false;
                }

                if (isNodeWebkit) {
                    browser = 'nodewebkit';
                }
            }

            return browser;
        }

        function getPlatform() {
            var userAgent = getUserAgent();
            var platform = getSupportedPackageFor();
            var detectedPlatform = detectPlatform(userAgent, getPackageType());

            if (!platform) {
                return detectedPlatform;
            }

            if (platform === supportedPackageFor.crestron && detectedPlatform === PLATFORM_TYPE.NME) {
                return PLATFORM_TYPE.NME;
            }

            if (platform === supportedPackageFor.crestron && detectedPlatform === PLATFORM_TYPE.CCE) {
                return PLATFORM_TYPE.CCE;
            }

            if (platform === supportedPackageFor.crestron && detectedPlatform === PLATFORM_TYPE.AIRMEDIA3K) {
                return PLATFORM_TYPE.AIRMEDIA3K;
            }

            if (platform === supportedPackageFor.android && detectedPlatform === PLATFORM_TYPE.AMAZON_SIGNAGE) {
                return PLATFORM_TYPE.AMAZON_SIGNAGE;
            }

            if (platform !== supportedPackageFor.pwa) {
                return platform;
            }

            // platform running on pwa with its own specific driver
            if (pwaWithDrivers.indexOf(detectedPlatform) !== -1) {
                return detectedPlatform;
            }

            if (crestronPlatforms.indexOf(detectedPlatform) !== -1) {
                return PLATFORM_TYPE.AIRMEDIA;
            }

            if (polyPlatforms.indexOf(detectedPlatform) !== -1) {
                return PLATFORM_TYPE.POLY;
            }

            return platform;
        }

        function loadScriptFile(file) {
            var promise = $q.defer();
            var scriptTag = attachFileToScript(file);
            scriptTag.onload = function(thisPromise) {
                thisPromise.resolve();
            }.bind(scriptTag, promise);
            return promise.promise;
        }

        function attachFileToScript(file) {
            var script = $document[0].createElement('script');
            script.src = file;
            $document[0].head.appendChild(script);
            return script;
        }

        return {
            detectPlatform: detectPlatform,
            getPackageFor: getSupportedPackageFor,
            getPlatform: getPlatform,
            getEmbeddedPlatform: getEmbeddedPlatform,
            getCapabilities: getCapabilities,
            getUserAgent: getUserAgent,
            loadScriptFile: loadScriptFile
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.constant('CRESTRON_INTENT_MESSAGE', {
        STARTED_VIDEO: 'started_video',
        STOPPED_VIDEO: 'stopped_video',
        UPDATE_VIDEO: 'update_video',
        START_VIDEO: 'start_video',
        PLAY_VIDEO: 'play_video',
        PAUSE_VIDEO: 'pause_video',
        STOP_VIDEO: 'stop_video',
        CRESTRON_READY: 'crestron_ready',
        CRESTRON_READY_ACK: 'crestron_ready_ack',
        THIRD_PARTY_READY: '3rdparty_ready',
        THIRD_PARTY_READY_ACK: '3rdparty_ready_ack',
        PAUSE_APP: 'pause_app',
        RESUME_APP: 'resume_app',
        PAUSED_APP: 'paused_app',
        RESUMED_APP: 'resumed_app',
        CLEAR_THIRD_PARTY_STATE: 'clear_3rd_party_state',
        RETRIEVE_THIRD_PARTY_STATE: 'retrieve_3rd_party_state',
        CLEARED_THIRD_PARTY_STATE: 'cleared_3rd_party_state',
        RETRIEVED_THIRD_PARTY_STATE: 'retrieved_3rd_party_state'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');
    app.constant('DEVICE_TYPE', {
        LOGITECHTAPSCHEDULER: 'LOGITECHTAPSCHEDULER',
        LOGITECH: 'LOGITECH',
        SUREVUE: 'SUREVUE',
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');
    app.constant('DMIS_URIS', {
        DEVELOPMENT: 'https://device-management.services.cloud.dev.appspace.com',
        QA: 'https://device-management.services.cloud.qa.appspace.com',
        STAGE: 'https://device-management.services.cloud.stage.appspace.com',
        PRODUCTION: 'https://device-management.services.cloud.appspace.com'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');
    app.constant('ENVIRONMENT_MAPPING', {
        'cloud-dev11.dev.appspace.com': 'DEVELOPMENT',
        'localhost': 'DEVELOPMENT',
        'cloud-qa-15.dev.appspace.com': 'QA',
        'cloud.stage.appspace.com': 'STAGE',
        'cloud.appspace.com': 'PRODUCTION'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.constant('FEATURE_CODE', {
        APP_EXIT_ON_BACK: 'app.exitonback',
        DEVICE_SUPPORT_SINGLE_APP_MODE: 'device.supportsingleappmode',
        VIDEO_REQUIRE_PAUSE_ON_LOAD: 'media.video.requirepauseonload',
        REUSE_VIDEO_LAYER: 'media.reusevideolayer',
        MEDIA_TIMEOUT_DURATION: 'media.timeout.duration',
        MEDIA_VIDEO_SWITCH_DELAY_DURATION: 'media.video.switchdelayduration',
        VIDEO_SYNCHRONIZED_SEEKING_SUPPORTED: 'media.video.synchronizedseeking.supported',
        CARD_TIMEOUT_DURATION: 'media.card.timeout.duration',
        CARD_TIMEOUT_PERCENTAGE: 'media.card.timeout.percentage',
        MAX_VIDEO_LAYERS: 'media.maxvideolayers',
        MAX_WEB_VIEW_COLLECTION: 'media.maxwebviewcollection',
        RENDERING_VIDEO_LAYERING: 'rendering.video-layering',
        RENDERING_CSS3_TRANSITIONS: 'rendering.css3-transitions',
        FILE_LOCAL_STORAGE: 'file.localstorage',
        RUNTIME_PERFORMANCE_API_SUPPORTED: 'runtime.performance-api.supported',
        REQUIRES_CONNECTION_FOR_PLAYBACK: 'device.requiresconnectionforplayback',
        NETWORK_TRANSPARENT_CACHE: 'network.transparentcache',
        DASHBOARD_EMBED_SUPPORTED: 'media.card.dashboard.embed.supported',
        ENFORCED_REGISTRATION_MODE: 'device.enforcedregistrationmode',
        REGISTRATION_TOKEN_REQUIRED: 'device.registrationtoken.required',
        REGISTRATION_INFO_PERSISTENCE_ON_BOOT_SUPPORTED: 'device.persistregistrationinfoonboot.supported',
        REGISTRATION_UPDATE_SUPPORTED: 'device.registrationupdate.supported',
        STANDBY_CONFIGURATION: 'device.standby.configuration',
        MEDIA_CARD_FORCESTREAM: 'media.card.onlineforcestream',
        MEDIA_CARD_LOAD_RSS_LOCALLY_MAX_VERSION: 'media.rsscard.supportlocal.version',
        WEB_WORKER_SUPPORTED: 'runtime.webworkers.supported',
        STORAGE_MEMORY_CACHE_SUPPORT: 'storage.memorycache.support',
        DEVICE_REBOOT_DELAY: 'device.reboot.delay',
        RELOAD_ON_ORIENTATION_CHANGE: 'signs.behaviour.reloadonorientationchange',
        MEDIA_REQUESTS_ALLOWED: 'media.requests.allowed',
        CONFERENCE_SUPPORTED: 'conference.supported',
        MEDIA_SUPPORT_DATA_PROTOCOL: 'media.supportdataprotocol',
        SCREENSHOT_SUPPORTED: 'screenshot.supported',
        DEVICE_MENU_SUPPORT: 'support.device.menu',
        UNREGISTER_SUPPORTED: 'device.unregistersupported',
        CHANNEL_VIEWER_INTERACTIVE_DISABLE: 'channel.viewer.interactive.disable',
        LOW_BANDWIDTH_MODE_SUPPORTED: 'support.lowbandwidth.mode',
        MAX_CACHEABLE_FILE_SIZE: 'media.maxcacheablesize',
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.constant('IPC_COMMAND', {
        GET_SERIAL_NUMBER: 'getSerialNumber',
        REMOVE_FILE: 'removeFile',
        MOVE_FILE: 'moveFile',
        RESTART: 'restart',
        GET_ACTIVE_NETWORK_CONNECTION: 'getActiveNetworkConnection',
        GET_ENVIRONMENT_VARIABLES: 'getEnvironmentVariables',
        DOWNLOAD: 'download',
        GET_DEFAULT_PATH: 'getDefaultPath',
        CREATE_DIRECTORY: 'createDirectory',
        CHECK_FILE_DIRECTORY: 'checkFileDirectory',
        REMOVE_DIRECTORY: 'removeDirectory',
        ENUMERATE_DIRECTORY: 'enumerateDirectory',
        RESOLVE_CONTENT_PATH: 'resolveContentPath',
        CLOSE_APPLICATION: 'closeApplication',
        CHECK_APP_UPDATE: 'checkAppUpdate',
        DOWNLOAD_APP_UPDATE: 'downloadAppUpdate',
        UPDATE_AND_RESTART_APP: 'updateAndRestartApp',
        GET_CPU_USAGE: 'getCpuUsage',
        GET_MEMORY_USAGE: 'getMemoryUsage',
        GET_AVAILABLE_STORAGE: 'getAvailableStorage',
        GET_FIRMWARE: 'getFirmware',
        GET_UUID: 'getUUID',
        GET_OS_SERIAL_NUMBER: 'getOsSerialNumber',
        REBOOT: 'reboot'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.constant('KEYBOARD_CODE', {
        ESC: 27
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('core');

    app.constant('OPERATION_COMMAND', {
        SCREENSHOT: 'screenshot',
    });
})(window.angular);


(function($angular) {
    'use strict';

    var app = $angular.module('hal');

    app.constant('PLATFORM_TYPE', {
        MERCURY: 'mercury',
        NME: 'nme',
        WEBEXBOARD: 'webexboard',
        WEBEXROOMKIT: 'webexroomkit',
        WEBEXROOMNAVIGATOR: 'webexroomnavigator',
        WEBEX: 'webex',
        IE11: 'ie11',
        MERSIVE: 'mersive',
        AIRMEDIA3K: 'airmedia3k',
        AIRMEDIA200: 'airmedia200',
        AIRMEDIA300: 'airmedia300',
        AIRMEDIA: 'airmedia',
        CCE: 'cce',
        BRIGHTSIGN: 'brightsign',
        IEC: 'iec',
        SAMSUNG_SSSP3: 'sssd',
        SAMSUNG_TIZEN: 'tizen',
        WEBEX_BOARD: 'webexboard',
        WEBEX_ROOM_KIT: 'webexroomkit',
        WEBOS: 'webos',
        ANDROID: 'android',
        QBIC: 'qbic',
        POLY: 'poly',
        LOGITECH: 'logitech',
        GOOGLEMEET: 'googlemeet',
        WEBVIEW2: 'webview2',
        CHROME_APP: 'chromeapp',
        IOS: 'ios',
        PREVIEW: 'preview',
        CROS: 'cros',
        SUREVUE: 'surevue',
        IOS_IFRAME: 'ios-iframe',
        NEAT: 'neat',
        MTR: 'mtr',
        IADEA: 'iadea',
        MICROSOFT_TEAMS_ROOMS: 'microsoftteamsrooms',
        ZOOM_ROOMS: 'zoomrooms',
        AMAZON_SIGNAGE: 'amazonsignage',
    });
})(window.angular);

(function($angular) {

    "use strict";

	//declare hal module and its dependencies
    var app = $angular.module('job',['core','storage','hal']);

})(window.angular);
(function($angular) {
    'use strict';

    var app = $angular.module('job');

    app.service('jobSchedulerService', ["$q", "$interval", "$timeout", "$filter", "Job", "coreConfigurationService", "storageSqlDbService", "storageObjectDbService", "halDriverService", "coreFrameworkService", function($q, $interval, $timeout, $filter, Job, coreConfigurationService, storageSqlDbService, storageObjectDbService, halDriverService, coreFrameworkService) {
        // public interface
        return {
            start: start,
            stop: stop,
            addJob: add,
            remove: remove,
            isReady: isReady,
            addJobProvider: addJobProvider,
            subscribe: subscribe,
            clear: clear,
            getJobs: getJobs,
        };

        function openDB() {
            // resolve true when it was successfully opened,
            // false if was opened previously.
            var promise = $q.defer();

            if (localdb) {
                promise.resolve(false);
            } else {
                var openRequest = storageObjectDbService.indexDB.open('appspace.core.job', 2);
                openRequest.onsuccess = function(e) {
                    localdb = e.target.result;
                    promise.resolve(true);
                };

                openRequest.onupgradeneeded = function(e) {
                    var db = e.target.result;
                    // database upgrade
                    for (var i = (e.oldVersion + 1); i <= e.newVersion; i++) {
                        switch (i) {
                            case 1:
                                var historyTable = db.createObjectStore('history', {
                                    keyPath: 'ticks'
                                });
                                historyTable.createIndex('jobid', 'jobid', {
                                    unique: false
                                });
                                historyTable.createIndex('state', 'state', {
                                    unique: false
                                });
                                historyTable.createIndex('instanceid', 'instanceid', {
                                    unique: false
                                });
                                historyTable.createIndex('meta', 'meta', {
                                    unique: false
                                });
                                var currentTable = db.createObjectStore('current', {
                                    keyPath: 'jobid'
                                });
                                currentTable.createIndex('instanceid', 'instanceid', {
                                    unique: false
                                });
                                currentTable.createIndex('state', 'state', {
                                    unique: false
                                });
                                currentTable.createIndex('lastStateUpdate', 'lastStateUpdate', {
                                    unique: false
                                });
                                var jobTable = db.createObjectStore('jobs', {
                                    keyPath: 'id'
                                });
                                jobTable.createIndex('job', 'job', {
                                    unique: false
                                });
                                break;
                            case 2:
                                db.deleteObjectStore('history');
                                break;
                        }
                    }
                };
            }

            return promise.promise;
        }

        function closeDB() {
            if (localdb) {
                localdb.close();
                localdb = undefined;
            }
        }

        function start() {
            readyPromise = $q.defer();
            coreFrameworkService.isReady().then(function() {
                if (!isInit) {
                    isInit = true;
                    jobs = [];
                    providers = {};
                    subscribers = {};

                    openDB().then(function() {
                        loadJobsFromStorage().then(function() {
                            startTimer();
                            readyPromise.resolve();
                        });
                    });
                } else {
                    readyPromise.resolve();
                }
            });
            return readyPromise.promise;
        }

        function isReady() {
            var promise;
            if (!readyPromise) {
                promise = $q.defer();
                promise.reject();
            } else {
                promise = readyPromise;
            }

            return promise.promise;
        }

        function clear() {
            clearDB();
            jobs = [];
        }

        function clearDB() {
            var promise = $q.defer();
            openDB().then(function(isDBReopen) {
                var current = localdb.transaction('current', 'readwrite').objectStore('current');
                var currentRequest = current.clear();
                var jobs = localdb.transaction('jobs', 'readwrite').objectStore('jobs');
                var jobsRequest = jobs.clear();

                var currentPromise = $q.defer();
                var jobsPromise = $q.defer();

                var promises = [currentPromise, jobsPromise];

                currentRequest.onsuccess = function(event) {
                    currentPromise.resolve();
                };

                jobsRequest.onsuccess = function(event) {
                    jobsPromise.resolve();
                };

                $q.all(promises).then(function() {
                    if (isDBReopen) {
                        closeDB();
                    }

                    promise.resolve();
                });
            });
            return promise.promise;
        }

        function add(job) {
            if (!isInit) {
                return;
            }

            if (!job) {
                return;
            }

            if (!job.id) {
                job.id = uuid.v1();
            }

            var found = false;
            // modify if job already exists
            for (var i = 0; i < jobs.length; i++) {
                var check = jobs[i];
                if (check.id == job.id) {
                    check = job;
                    check.save(localdb);
                    found = true;
                    break;
                }
            }

            if (found == false && job.save) {
                if (job.schedule) {
                    job.schedule.start.setSeconds(0);
                    job.schedule.start.setMilliseconds(0);
                    job.schedule.end.setSeconds(59);
                    job.schedule.end.setMilliseconds(999);
                }

                job.save(localdb);
                jobs.push(job);
            }

            return job;
        }

        function remove(id) {
            if (!isInit) {
                return;
            }

            var indexToRemove = -1;
            for (var i = 0; i < jobs.length; i++) {
                var job = jobs[i];
                if (job.id == id) {
                    indexToRemove = i;
                    break;
                }
            }

            if (indexToRemove != -1) {
                jobs.splice(indexToRemove, 1);
                var transaction = localdb.transaction('jobs', 'readwrite');
                var objectStore = transaction.objectStore('jobs');
                objectStore.delete(id);
                var transaction2 = localdb.transaction('current', 'readwrite');
                var objectStore2 = transaction2.objectStore('current');
                objectStore2.delete(id);
            }
        }

        function addJobProvider(collection, method, provider) {
            if (!providers[collection]) {
                providers[collection] = {};
            }

            providers[collection][method] = provider;
        }

        function subscribe(collection, callback) {
            if (!subscribers[collection]) {
                subscribers[collection] = [];
            }

            if (subscribers[collection].indexOf(callback) > -1) {
                return;
            }

            subscribers[collection].push(callback);
        }

        function getJobs() {
            return jobs;
        }

        var timerPromise;
        var jobs;
        var providers;
        var isInit;
        var localdb;
        var readyPromise;
        var subscribers;

        function loadJobsFromStorage() {
            var createJob = function(jobRaw) {
                var job = new Job();
                job.parse(jobRaw.job);
                job.loadAfterStartup = false;
                return job;
            };

            var promise = $q.defer();
            jobs = [];
            var transaction = localdb.transaction('jobs');
            var objectStore = transaction.objectStore('jobs');
            // if get all is supported
            if (objectStore.getAll) {
                var getRequest = objectStore.getAll();
                getRequest.onsuccess = function(e) {
                    if (e.target && e.target.result) {
                        var jobsStorage = e.target.result;
                        var promises = [];
                        for (var i = 0; i < jobsStorage.length; i++) {
                            var jobPromise = loadLastState(createJob(jobsStorage[i])).then(function(result) {
                                jobs.push(result);
                            });
                            promises.push(jobPromise);
                        }
                    }

                    $q.all(promises).then(function() {
                        promise.resolve();
                    });
                };

                getRequest.onerror = function(e) {
                    promise.resolve();
                };
            } else {
                var tempJobs = [];
                transaction.oncomplete = function(evt) {
                    var promises = [];
                    for (var i = 0; i < tempJobs.length; i++) {
                        var job = tempJobs[i];
                        var jobPromise = loadLastState(job).then(function(result) {
                            jobs.push(result);
                        });
                        promises.push(jobPromise);
                    }

                    $q.all(promises).then(function() {
                        promise.resolve();
                    });
                };

                var cursorRequest = objectStore.openCursor();
                cursorRequest.onsuccess = function(event) {
                    var cursor = event.target.result;
                    if (cursor) {
                        tempJobs.push(createJob(cursor.value));
                        cursor.continue();
                    }
                };

                promise.resolve();
            }

            return promise.promise;
        }

        function loadLastState(job) {
            var promise = $q.defer();
            var objectStore = localdb.transaction('current').objectStore('current');
            var getRequest = objectStore.get(job.id);
            getRequest.onsuccess = function(e) {
                if (e.target && e.target.result) {
                    job.state = e.target.result;
                    if (job.state.lastStateUpdate != 0) {
                        job.state.lastStateUpdate = new Date(job.state.lastStateUpdate);
                    } else {
                        job.state.lastStateUpdate = null;
                    }
                }

                promise.resolve(job);
            };

            getRequest.onerror = function() {
                promise.resolve(job);
            };

            return promise.promise;
        }

        function resolvePendingJobState() {
            var promise = $q.defer();
            var promises = [];

            if (!subscribers) {
                return;
            }

            // check for any jobs that is not running but the state is currently running
            for (var i = 0; i < jobs.length; i++) {
                var job = jobs[i];
                if (!job.state) {
                    continue;
                }

                // grab the subsribers for the job
                var callbacks = subscribers[job.provider.collection];

                // if there are no callbacks for this job, skip first
                if (!callbacks) {
                    continue;
                }

                // if there is no job provider for this job, skip first
                var provider = providers[job.provider.collection];
                if (!provider || !provider[job.provider.method]) {
                    continue;
                }

                // if state is running & isrunning flag is false, resolve it (reboot before task complete)
                if (job.state.state == 'running' && job.isRunning == false) {
                    var p = job.postExecute('Task completed after reboot.', provider[job.provider.method]);

                    p.then(function(res) {
                        sendJobResponseToSubscriber({
                            owner: res.owner,
                            result: res.result
                        });
                    });

                    promises.push(p);
                }
            }

            $q.all(promises).then(function() {
                promise.resolve();
            });

            return promise.promise;
        }

        function startTimer() {
            timerPromise = $interval(checkTimer, 5000, 0, false);
        }

        function stop() {
            isInit = false;
            closeDB();
            stopTimer();
        }

        function stopTimer() {
            $interval.cancel(timerPromise);
            timerPromise = null;
        }

        function checkTimer() {
            if (!jobs) {
                return;
            }

            // resolve pending job state
            resolvePendingJobState().then(function() {
                var currentDate = new Date();
                var validJobs = [];
                for (var i = 0; i < jobs.length; i++) {
                    var job = jobs[i];
                    // check job object validity
                    if (!job || !job.schedule) {
                        continue;
                    }

                    // if job is running, skip
                    if (job.isRunning) {
                        continue;
                    }

                    // if job is valid now, execute it
                    if (job.schedule && job.isValid(currentDate)) {
                        validJobs.push(job);
                    }
                }

                // we use this pattern to avoid multiple jobs running at the same ticks
                var currentJobIndex = -1;
                var executeNextJob = function() {
                    var nextJob = validJobs[++currentJobIndex];
                    if (nextJob) {
                        executeJob(nextJob).then(executeNextJob);
                    }
                };

                if (validJobs.length > 0) {
                    executeNextJob();
                }
            });
        }

        function executeJob(job) {
            var promise = $q.defer();
            $timeout(function() {
                var provider = providers[job.provider.collection];
                if (!provider || !provider[job.provider.method]) {
                    return;
                }

                job.db = localdb;
                job.startExecute(provider[job.provider.method]).then(sendJobResponseToSubscriber);
                promise.resolve();
            }, 100);
            return promise.promise;
        }

        function sendJobResponseToSubscriber(res) {
            var job = res.owner;
            var callbacks = subscribers[job.provider.collection];
            if (callbacks) {
                for (var i = 0; i < callbacks.length; i++) {
                    var callback = callbacks[i];
                    callback({
                        response: res.result,
                        job: job
                    });
                }
            }

            job.saveState('end');

            if (job.schedule.pattern.type == 'once') {
                remove(job.id);
            }
        }
    }]);

    app.factory('Job', ["JobSchedule", "coreConfigurationService", "$q", "$timeout", "halPlatformService", "logService", function(JobSchedule, coreConfigurationService, $q, $timeout, halPlatformService, logService) {
        function Job(db) {
            this.id = null;
            this.schedule = new JobSchedule();
            this.isRunning = false;
            this.state = null;
            this.db = db;
            this.runningInstanceId;
            this.provider;
            this.parameters;
            this.name;

            this.jobProvider;
            this.meta;
            this.loadAfterStartup = true;
        }

        function logError(errorMessage, error) {
            logService.error('appspace.job.services', errorMessage, error);
        }

        Job.prototype.isValid = function(currentTime) {
            var valid = this.schedule.isValid(currentTime);
            if (this.schedule.pattern.type == 'onboot') {
                // job is added after startup, then ignore.
                if (this.loadAfterStartup) {
                    return false;
                } else {
                    // only run on boot if we load the task from DB/storage
                    // set loadAfterStartup to true to prevent multiple execution for onboot task
                    this.loadAfterStartup = true;
                    return true;
                }
            } else if (valid && this.state && this.state.lastStateUpdate) { // is schedule is valid, check if we already execute in the current minute block
                if (currentTime.getHours() == this.state.lastStateUpdate.getHours() &&
                    currentTime.getMinutes() == this.state.lastStateUpdate.getMinutes() &&
                    currentTime.getDate() == this.state.lastStateUpdate.getDate() &&
                    currentTime.getMonth() == this.state.lastStateUpdate.getMonth() &&
                    currentTime.getFullYear() == this.state.lastStateUpdate.getFullYear()) {
                    valid = false;
                }
            }

            return valid;
        };

        Job.prototype.startExecute = function(Provider) {
            var promise = $q.defer();
            this.jobProvider = new Provider();
            var onPostExecute = function(ret) {
                promise.resolve({
                    owner: ret.owner,
                    result: ret.result
                });
            };

            var onExecute = function(ret) {
                ret.owner.postExecute(ret.result).then(onPostExecute);
            };

            var onPreExecute = function(ret) {
                ret.owner.execute().then(onExecute);
            };

            this.preExecute().then(onPreExecute);
            return promise.promise;
        };

        Job.prototype.preExecute = function() {
            // console.log(this.id + ' pre execute');
            var promise = $q.defer();
            this.isRunning = true;
            var instanceid = new Date().getTime();
            var owner = this;
            this.runningInstanceId = instanceid;
            this.saveState('start');
            // delay so that start & running won't be in the same ticks
            $timeout(function() {
                if (owner.jobProvider.preExecute) {
                    owner.jobProvider.preExecute(owner).then(function(res) {
                        promise.resolve({
                            owner: owner,
                            preExecute: res
                        });
                    });
                } else {
                    promise.resolve({
                        owner: owner,
                        result: null
                    });
                }
            }, 1);
            return promise.promise;
        };

        Job.prototype.execute = function() {
            var promise = $q.defer();
            var owner = this;
            this.saveState('running');
            // delay so that running & post won't be in the same ticks
            $timeout(function() {
                if (owner.jobProvider.execute) {
                    owner.jobProvider.execute(owner).then(function(response) {
                        promise.resolve({
                            owner: owner,
                            result: response
                        });
                    }, function(err) {
                        promise.resolve({
                            owner: owner,
                            result: err
                        });
                    });
                } else {
                    promise.resolve({
                        owner: owner,
                        result: null
                    });
                }
            }, 1);

            return promise.promise;
        };

        Job.prototype.postExecute = function(result, Provider) {
            var promise = $q.defer();
            this.isRunning = false;
            if (!this.jobProvider && Provider) {
                this.jobProvider = new Provider();
            }

            if (this.jobProvider && this.jobProvider.postExecute) {
                this.jobProvider.postExecute({job: this, result: result});
            }

            promise.resolve({
                owner: this,
                result: result
            });

            return promise.promise;
        };

        Job.prototype.saveState = function(state) {
            var d = new Date();
            if (!this.state) {
                this.state = {};
            }

            this.state.state = state;
            this.state.lastStateUpdate = d;
            saveCurrentInstance(this.db, this.id, this.runningInstanceId, state, d);
        };

        Job.prototype.parse = function(str) {
            var obj = JSON.parse(str);
            for (var key in obj) {
                if (key == 'db') {
                    continue;
                }

                if (key == 'schedule') {
                    this.schedule = new JobSchedule();
                    this.schedule.start = new Date(obj.schedule.start);
                    this.schedule.end = new Date(obj.schedule.end);
                    this.schedule.pattern = obj.schedule.pattern;
                    continue;
                }

                this[key] = obj[key];
            }
        };

        Job.prototype.save = function(db) {
            if (!db) {
                db = this.db;
            }

            if (!db) {
                return;
            }

            var transaction = db.transaction(['jobs'], 'readwrite');
            var objectStore = transaction.objectStore('jobs');
            objectStore.put({
                id: this.id,
                job: JSON.stringify(this)
            });

            transaction.onerror = function(err) {
                logError('Failed to save job into object store: ', err);
            };

            transaction.onabort = function(err) {
                logError('Saving job into object store aborted: ', err);
            };
        };

        function saveCurrentInstance(db, jobid, instanceid, state, currentTime) {
            if (!db) {
                return;
            }

            var transaction = db.transaction(['current'], 'readwrite');
            var objectStore = transaction.objectStore('current');
            objectStore.put({
                instanceid: instanceid,
                jobid: jobid,
                state: state,
                lastStateUpdate: currentTime.getTime()
            });
            transaction.onerror = function(err) {
                logError('Failed to save current job instance into object store: ', err);
            };

            transaction.onabort = function(err) {
                logError('Saving current job instance into object store aborted: ', err);
            };
        }

        return Job;
    }]);

    app.factory('JobSchedule', function() {
        function JobSchedule() {
            this.start = new Date();
            this.end = new Date();
            this.pattern = {
                type: 'daily', // support once/daily/weekly/monthly
                recurrence: 0, // days
                repeatInterval: 0, // in minutes
                repeatDuration: 0, // in minutes
                day: [7] // 7 = everyday, 0 - 6 = sunday to saturday
            };
        }

        JobSchedule.prototype.isValid = function(currentDate) {
            var valid = false;

            // if run once, just run it
            if (this.pattern.type == 'once') {
                return true;
            } else if (this.pattern.type == 'onboot') {
                return true;
            } else if (currentDate.getTime() >= this.start.getTime() && currentDate.getTime() < this.end.getTime()) {
                // check whether we should run today based on recurrence
                var recurrenceValid = true;
                if (this.pattern.recurrence != 0) {
                    var oneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds
                    var firstDate = new Date(currentDate.getTime());
                    var secondDate = this.start;
                    var diffDays = Math.floor(Math.abs((firstDate.getTime() - secondDate.getTime()) / (oneDay)));
                    recurrenceValid = (diffDays % this.pattern.recurrence == 0);
                }

                if (recurrenceValid == false) {
                    return false;
                }

                // grab today & yesterday task based on start boundary
                // we grab yesterday boundary to cater for cross day events
                var todayStartBoundary = new Date(currentDate.getTime());
                todayStartBoundary.setHours(this.start.getHours());
                todayStartBoundary.setMinutes(this.start.getMinutes());
                todayStartBoundary.setSeconds(0);
                todayStartBoundary.setMilliseconds(0);

                var todayEndBoundary = new Date(todayStartBoundary.getTime());
                todayEndBoundary.setMinutes(todayEndBoundary.getMinutes() + this.pattern.repeatDuration);
                todayEndBoundary.setSeconds(59);
                todayEndBoundary.setMilliseconds(999);

                var yesterdayStartBoundary = new Date(currentDate.getTime());
                yesterdayStartBoundary.setDate(yesterdayStartBoundary.getDate() - 1);
                yesterdayStartBoundary.setHours(this.start.getHours());
                yesterdayStartBoundary.setMinutes(this.start.getMinutes());
                yesterdayStartBoundary.setSeconds(0);
                yesterdayStartBoundary.setMilliseconds(0);

                var yesterdayEndBoundary = new Date(yesterdayStartBoundary.getTime());
                yesterdayEndBoundary.setMinutes(yesterdayEndBoundary.getMinutes() + this.pattern.repeatDuration);
                yesterdayEndBoundary.setSeconds(59);
                yesterdayEndBoundary.setMilliseconds(999);

                var checkStart = null;
                var checkEnd = null;
                // console.log(todayStartBoundary);
                // console.log(todayEndBoundary);
                // check if current time is valid for today boundary or yesterday's
                if (currentDate.getTime() >= todayStartBoundary.getTime() && currentDate.getTime() < todayEndBoundary.getTime()) {
                    checkStart = todayStartBoundary;
                    checkEnd = todayEndBoundary;
                } else if (currentDate.getTime() >= yesterdayStartBoundary.getTime() && currentDate.getTime() < yesterdayEndBoundary.getTime()) {
                    checkStart = yesterdayStartBoundary;
                    checkEnd = yesterdayEndBoundary;
                }

                if (checkStart && checkEnd) {
                    // check day pattern
                    var run = ((this.pattern.day.length == 1 && this.pattern.day[0] == 7) /* everyday*/ || (this.pattern.day.length > 0 && this.pattern.day.indexOf(currentDate.getDay()) != -1) /* custom day*/);

                    if (run == true) {
                        // check whether we should run in this minute (using minute block checking)
                        // minute block = if the current minute since the start of the checking can be divided by the repeat pattern minute cleanly, it's valid
                        // e.g. current minute since start = 10, repeat pattern = every 5 minute, 10%5 = 0, therefore it's valid to run the task
                        var currentMinuteInBlock = currentDate.getTime() - checkStart.getTime();
                        currentMinuteInBlock = Math.floor(currentMinuteInBlock / 1000 / 60);
                        // console.log('checking minute block:' + currentMinuteInBlock + " " + this.pattern.repeatInterval);
                        // console.log('today end boundary:' + todayEndBoundary);
                        valid = (currentMinuteInBlock == 0 && this.pattern.repeatInterval == 0) /* 0 % 0 will return undefined */ || (currentMinuteInBlock % this.pattern.repeatInterval == 0);
                    }
                }
            }

            return valid;
        };

        return JobSchedule;
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('job');

    app.service('admService', ["$q", "$timeout", "ADM_EVENT", "STORAGE_KEY", "STORAGE_COLLECTION_KEY", "coreConfigurationService", "coreFrameworkService", "jobSchedulerService", "halPlatformService", "admAPIService", "logService", "dataUtil", "Job", "JobSchedule", "HttpWrapper", function(
        $q,
        $timeout,
        ADM_EVENT,
        STORAGE_KEY,
        STORAGE_COLLECTION_KEY,
        coreConfigurationService,
        coreFrameworkService,
        jobSchedulerService,
        halPlatformService,
        admAPIService,
        logService,
        dataUtil,
        Job,
        JobSchedule,
        HttpWrapper) {
        var TASK_QUEUE_COMMAND = {
            ADD: 'add',
            UPDATE: 'update',
            REMOVE: 'remove'
        };
        var NOT_FOUND_INDEX = -1;
        var subscribers = [];

        // public interface
        return {
            start: start,
            isReady: isReady,
            stop: stop,
            clear: clear,
            cleanUpInvalidTasks: cleanUpInvalidTasksInIndexedDb,
            saveQueueMarker: saveQueueMarker,
            getQueueMarker: retrieveCachedTaskQueueTime,
            subscribe: subscribe,
            unsubscribe: unsubscribe,
        };

        function start() {
            readyPromise = $q.defer();
            responseQueue = [];
            acknowledgeQueue = [];
            lastRetrievedTaskTime = new Date().getTime();
            startProcessResponseQueue();
            startProcessAcknowledgeQueue();
            retrieveTaskFromAppspaceDuration = 50 * 1000;// every 50 seconds
            // retrieveTaskFromAppspaceDuration = 5;//testing value
            isRetrievingTask = false;
            logkey = 'appspace.job.admservice';
            logService.info(logkey, 'ADM Service started');
            logService.info(logkey, 'Waiting for external dependencies to start');
            coreFrameworkService.isReady().then(function() {
                deviceType = halPlatformService.getAppspaceDeviceType();
                jobSchedulerService.isReady().then(function() {
                    logService.info(logkey, 'External dependencies started');
                    jobSchedulerService.addJobProvider('adm', 'adm.http', HTTPADMTaskFactory);
                    jobSchedulerService.addJobProvider('adm', 'adm.script', ScriptADMTaskFactory);
                    jobSchedulerService.addJobProvider('adm', 'adm.rs232', RS232ADMTaskFactory);
                    jobSchedulerService.subscribe('adm', onJobSchedulerTaskExecuted);
                    isExtensionReady().then(function() {
                        logService.info(logkey, 'ADM Extension Ready');
                        startTimer();
                        readyPromise.resolve();
                    });
                });
            });
        }

        function retrieveCachedTaskQueueTime() {
            var res = coreConfigurationService.get(STORAGE_COLLECTION_KEY.ADM, STORAGE_KEY.ADM_QUEUE_TIME);
            return res ? parseInt(res, 10) : 0;
        }

        function notifySubscribers(event, data) {
            for (var i = 0; i < subscribers.length; i++) {
                subscribers[i](event, data);
            }
        }

        function subscribe(callback) {
            if (!callback || subscribers.indexOf(callback) !== NOT_FOUND_INDEX) {
                return;
            }

            subscribers.push(callback);
        }

        function unsubscribe(callback) {
            var subscriberIndex = subscribers.indexOf(callback);
            if (subscriberIndex === NOT_FOUND_INDEX) {
                return;
            }

            subscribers.splice(subscriberIndex, 1);
        }

        function stop() {
            logService.info(logkey, 'ADM Service stopped');
            stopTimer();
            cancelProcessResponseQueueTimeout();
        }

        function clear() {
            // clear any pending queues
            responseQueue = [];
            acknowledgeQueue = [];
            jobSchedulerService.isReady().then(function() {
                jobSchedulerService.clear();
            });
        }

        function isReady() {
            return readyPromise.promise;
        }

        var readyPromise;
        var logkey;
        var timerPromise;
        var retrieveTaskFromAppspaceDuration;
        var lastRetrievedTaskTime;
        var isRetrievingTask;
        var deviceType;
        var responseQueue;
        var acknowledgeQueue;
        var processResponseQueuePromise;
        var processAcknowledgeQueuePromise;

        function isExtensionReady(promise) {
            logService.info(logkey, 'Check if ADM extension is available');
            if (!promise) {
                promise = $q.defer();
            }

            var onCheck = function(res) {
                logService.info(logkey, 'ADM server response : ' + res);
                if (res == false) {
                    logService.info(logkey, 'Restart ADM extension checking in 10 minutes');
                    $timeout(function() {
                        isExtensionReady(promise);
                    }, 10 * 60 * 1000);
                } else {
                    promise.resolve();
                }
            };

            admAPIService.isExtensionAvailable().then(onCheck, function(err) {
                onCheck(false);
            });

            return promise.promise;
        }

        function startTimer() {
            stopTimer();
            timerPromise = $timeout(doTimer, 1000, false);
        }

        function stopTimer() {
            if (timerPromise) {
                $timeout.cancel(timerPromise);
                timerPromise = null;
            }
        }

        function doTimer() {
            var current = new Date().getTime();
            // retrieve task
            if (isRetrievingTask == false && current - lastRetrievedTaskTime >= retrieveTaskFromAppspaceDuration) {
                isRetrievingTask = true;
                var currentQueueTime = retrieveCachedTaskQueueTime();
                retrieveTaskFromAppspace(currentQueueTime).then(function(latestQueueTime) {
                    lastRetrievedTaskTime = new Date().getTime();
                    isRetrievingTask = false;
                    if (currentQueueTime != latestQueueTime) {
                        saveQueueMarker(latestQueueTime, false);
                        notifySubscribers(ADM_EVENT.QUEUE_MARKER_UPDATED, latestQueueTime);
                    }
                });
            }

            startTimer();
        }

        function cleanUpInvalidTasksInIndexedDb() {
            var processTaskQueues = function(taskQueues) {
                var validTask = {};
                for (var i = 0; i < taskQueues.length; i++) {
                    var taskQueue = taskQueues[i];

                    if (!taskQueue || !taskQueue.DeployedTaskId || !taskQueue.TaskQueueCommand) {
                        continue;
                    }

                    var taskKey = 'adm_' + taskQueue.DeployedTaskId;
                    switch (taskQueue.TaskQueueCommand.toLowerCase()) {
                        case TASK_QUEUE_COMMAND.ADD:
                        case TASK_QUEUE_COMMAND.UPDATE:
                            validTask[taskKey] = taskQueue;
                            break;
                        case TASK_QUEUE_COMMAND.REMOVE:
                            delete validTask[taskKey];
                            break;
                    }
                }

                return validTask;
            };

            var filterInvalidTasks = function(validTasks, jobs) {
                var invalidTasks = {};
                for (var i = 0; i < jobs.length; i++) {
                    var job = jobs[i];

                    if (!job || !job.id || validTasks[job.id]) {
                        continue;
                    }

                    invalidTasks[job.id] = job;
                }

                return invalidTasks;
            };

            admAPIService.retrieveTasks(0).then(function(taskQueues) {
                var validTasks = processTaskQueues(taskQueues);
                var jobs = jobSchedulerService.getJobs();
                var invalidTasks = filterInvalidTasks(validTasks, jobs);
                logService.debug(logkey, 'Found ' + Object.keys(invalidTasks).length + ' invalid tasks in indexedDB');
                for (var key in invalidTasks) {
                    var job = invalidTasks[key];
                    jobSchedulerService.remove(job.id);
                    logService.debug(logkey, 'Removed invalid task ' + job.id + ' from indexedDB');
                }
            });
        }

        function saveQueueMarker(timestamp, pendingRecurringTasks) {
            coreConfigurationService.set(STORAGE_COLLECTION_KEY.ADM, STORAGE_KEY.ADM_QUEUE_TIME, timestamp);
            coreConfigurationService.set(STORAGE_COLLECTION_KEY.ADM, STORAGE_KEY.ADM_PENDING_RECURRING_TASKS, Boolean(pendingRecurringTasks));
        }

        function retrieveTaskFromAppspace(currentQueueTime) {
            var promise = $q.defer();
            var pendingRecurringTasks = dataUtil.parseBoolean(coreConfigurationService.get(STORAGE_COLLECTION_KEY.ADM, STORAGE_KEY.ADM_PENDING_RECURRING_TASKS));
            admAPIService.retrieveTasks(currentQueueTime, pendingRecurringTasks).then(function(tasks) {
                // Reset pending recurring tasks flag after tasks retrieval
                saveQueueMarker(currentQueueTime, false);
                var queueTime = currentQueueTime;
                var finalTasks = {};
                var updateCount = 0;
                var addCount = 0;
                var removeCount = 0;
                var acknowledgePromises = [];
                for (var i = 0; i < tasks.length; i++) {
                    var task = tasks[i];
                    switch (task.TaskQueueCommand.toLowerCase()) {
                        case 'remove':
                            removeCount++;
                            finalTasks['adm_' + task.DeployedTaskId] = null;
                            delete finalTasks['adm_' + task.DeployedTaskId];
                            removeTask(task.DeployedTaskId);
                            break;
                        case 'add':
                            addCount++;
                            finalTasks['adm_' + task.DeployedTaskId] = task;
                            break;
                        case 'update':
                            updateCount++;
                            finalTasks['adm_' + task.DeployedTaskId] = task;
                            break;
                    }

                    var d = task.QueueDate.split('/').join('').split('Date(').join('').split(')').join('');
                    d = parseInt(d, 10);
                    if (d > queueTime) {
                        queueTime = d;
                    }

                    // queue task acknowledgement
                    var acknowledge = {
                        id: task.Id,
                        promise: $q.defer()
                    };
                    acknowledgePromises.push(acknowledge.promise.promise);
                    acknowledgeQueue.push(acknowledge);
                }

                if (tasks.length > 0) {
                    logService.info(logkey, 'Received ' + tasks.length + ' task queues');
                    logService.info(logkey, 'Task Queues Summary: ' + addCount + ' added, ' + updateCount + ' updated, ' + removeCount + ' removed');
                }

                // wait for all queue has been acknowledge
                // before continuing the process
                $q.allSettled(acknowledgePromises).then(function() {
                    commitTasks(finalTasks);
                    tasks = [];
                    acknowledgePromises = [];
                    promise.resolve(queueTime);
                });
            }, function(err) {
                promise.resolve(currentQueueTime);
            });

            return promise.promise;
        }

        function commitTasks(tasks) {
            for (var key in tasks) {
                var taskRaw = tasks[key];
                var job = transformTaskReponse(taskRaw);
                jobSchedulerService.addJob(job);
            }
        }

        function removeTask(id) {
            jobSchedulerService.remove('adm_' + id);
        }

        function onJobSchedulerTaskExecuted(response) {
        }

        function cancelProcessResponseQueueTimeout() {
            if (processResponseQueuePromise) {
                $timeout.cancel(processResponseQueuePromise);
                processResponseQueuePromise = null;
            }
        }

        function startProcessResponseQueue(delay) {
            cancelProcessResponseQueueTimeout();
            processResponseQueuePromise = $timeout(processResponseQueue, delay || 1000);
        }

        function cancelProcessAcknowledgeQueueTimeout() {
            if (processAcknowledgeQueuePromise) {
                $timeout.cancel(processAcknowledgeQueuePromise);
                processAcknowledgeQueuePromise = null;
            }
        }

        function startProcessAcknowledgeQueue(delay) {
            cancelProcessAcknowledgeQueueTimeout();
            processAcknowledgeQueuePromise = $timeout(processAcknowledgeQueue, delay || 1000);
        }

        function processResponseQueue() {
            if (responseQueue.length == 0) {
                startProcessResponseQueue();
                return;
            }

            var response = responseQueue[0];
            logService.debug(logkey, 'Sending job response for task ' + response.taskId);
            admAPIService.sendResponse(response.postdata).then(function(res) {
                logService.debug(logkey, 'Successfully sent job response for task ' + response.taskId);
                // remove the first response from queue
                responseQueue.shift();
                startProcessResponseQueue();
            }, function(err) {
                logService.warn(logkey, 'Failed to send job response for task ' + response.taskId + '. Retrying');
                startProcessResponseQueue(5000);
            });
        }

        function processAcknowledgeQueue() {
            if (acknowledgeQueue.length == 0) {
                startProcessAcknowledgeQueue();
                return;
            }

            var ack = acknowledgeQueue[0];
            logService.debug(logkey, 'Sending queue acknowledgement for task ' + ack.id);
            admAPIService.acknowledgeQueue(ack.id).then(function(res) {
                logService.debug(logkey, 'Successfully sent queue acknowledgement for queue ' + ack.id);
                // remove the first response from queue
                ack.promise.resolve();
                acknowledgeQueue.shift();
                startProcessAcknowledgeQueue();
            }, function(err) {
                logService.warn(logkey, 'Failed to send queue acknowledgement for queue ' + ack.id + '. Retrying');
                startProcessAcknowledgeQueue(5000);
            });
        }

        function queueJobResponse(taskId, response) {
            var deviceguid = coreConfigurationService.get('device', 'playerguid');

            if (typeof response == 'object') {
                response = JSON.stringify(response);
            }

            var TaskRequest = {
                Response: {Raw: response, DeployedTaskId: taskId, ClientGuid: deviceguid},
                DeviceType: deviceType
            };

            var postdata = JSON.stringify(TaskRequest);
            responseQueue.push({taskId: taskId, postdata: postdata});
        }

        function transformTaskReponse(taskRaw) {
            var job = new Job();
            var task = taskRaw.DeployedTask;
            job.name = task.Name;
            var globalParameters = [];
            for (var i = 0; i < task.Parameters.length; i++) {
                var param = task.Parameters[i];
                globalParameters.push({key: param.Placeholder, value: param.ParamValue});
            }

            job.meta = {id: task.Id, globalParameters: globalParameters};
            if (!task.Schedule) {
                return null;
            }

            var def = getDefinition(task.Definitions);
            if (!def) {
                return null;
            }

            var providerMethod = 'adm.script';
            switch (def.protocol) {
                case 'http':
                    providerMethod = 'adm.http';
                    break;
                case 'shell':
                case 'script':
                    providerMethod = 'adm.script';
                    break;
                case 'rs232':
                    providerMethod = 'adm.rs232';
                    break;
                // right now, only support http & script/shell
                default:
                    return null;
            }

            job.id = 'adm_' + task.Id;
            job.parameters = def.parameters;
            job.provider = {collection: 'adm', method: providerMethod};
            job.schedule = new JobSchedule();
            if (task.Schedule.RepetitionPattern) {
                job.schedule.pattern.repeatDuration = convertJsonTimespanToMinutes(task.Schedule.RepetitionPattern.RepeatDuration);
                job.schedule.pattern.repeatInterval = convertJsonTimespanToMinutes(task.Schedule.RepetitionPattern.RepeatInterval);
            }

            job.schedule.start = convertStringToDate(task.Schedule.StartBoundary, task.Schedule.IsLocalTime);
            if (!job.schedule.start) {
                job.schedule.start = new Date(1900, 0, 1, 0, 0, 0, 0);
            }

            job.schedule.end = convertStringToDate(task.Schedule.EndBoundary, task.Schedule.IsLocalTime);
            if (!job.schedule.end) {
                job.schedule.end = new Date(8000, 11, 31, 23, 59, 59, 999);
            }

            // TaskScheduleType:Undefined = 0,Run_Once = 1,Run_Time = 2,Run_Daily = 3,
            // Run_Weekly = 4,Run_Monthly = 5,On_Idle = 6,On_SystemStart = 7,On_SystemStop = 8,
            // On_Frequency = 9,On_Demand = 10,
            switch (task.TaskScheduleType) {
                case 1:// run once
                    job.schedule.pattern.type = 'once';
                    break;
                case 3:// daily
                    job.schedule.pattern.type = 'daily';
                    job.schedule.pattern.recurrence = task.Schedule.DaysInterval;
                    break;
                case 4:// weekly
                    job.schedule.pattern.type = 'weekly';
                    job.schedule.pattern.day = convertBitWiseDayToArray(task.Schedule.DaysOfTheWeek);
                    break;
                case 5:// monthly
                    job.schedule.pattern.type = 'monthly';
                    break;
                case 7:// on boot
                    job.schedule.pattern.type = 'onboot';
                    break;
                case 9:// frequency
                    job.schedule.pattern.type = 'frequency';
                    break;
                case 2:// time
                default:
                    job.schedule.pattern.type = 'none';
                    break;
            }

            // }
            return job;
        }

        function getDefinition(definitions) {
            var res;
            for (var i = 0; i < definitions.length; i++) {
                var def = definitions[i];
                if (def.ForDevice.toLowerCase() == deviceType.toLowerCase()) {
                    var definition = {protocol: null};
                    switch (def.TaskCommand.Protocol) {
                        case 1:
                            definition.protocol = 'http';
                            break;
                        case 3:
                            definition.protocol = 'shell';
                            break;
                        case 4:
                            definition.protocol = 'rs232';
                            break;
                        case 5:
                            definition.protocol = 'socket';
                            break;
                        default:
                            continue;
                    }

                    definition.parameters = [];
                    var j;
                    var p;
                    for (j = 0; j < def.Parameters.length; j++) {
                        p = def.Parameters[j];
                        definition.parameters.push({key: p.Placeholder, value: p.ParamValue});
                    }

                    for (j = 0; j < def.TaskCommand.Properties.length; j++) {
                        p = def.TaskCommand.Properties[j];
                        definition.parameters.push({key: p.Key, value: p.Value});
                    }

                    res = definition;
                    break;
                }
            }

            return res;
        }

        function convertJsonTimespanToMinutes(timestamp) {
            var res = timestamp;
            res = res.replace('P', '');
            var day = 0;
            var hour = 0;
            var minutes = 0;

            var dayIndex = res.indexOf('D');
            if (dayIndex > -1) {
                day = res.substring(0, dayIndex + 1);
                res = res.replace(day, '');
                day = parseInt(day.replace('D', ''), 10);
            }

            res = res.replace('T', '');
            var hourIndex = res.indexOf('H');
            if (hourIndex > -1) {
                hour = res.substring(0, hourIndex + 1);
                res = res.replace(hour, '');
                hour = parseInt(hour.replace('H', ''), 10);
            }

            var minuteIndex = res.indexOf('M');
            if (minuteIndex > -1) {
                minutes = res.substring(0, minuteIndex + 1);
                res = res.replace(minutes, '');
                minutes = parseInt(minutes.replace('M', ''), 10);
            }

            var totalMinutes = day * 24 * 60;
            totalMinutes += (hour * 60);
            totalMinutes += minutes;
            return totalMinutes;
        }

        function convertBitWiseDayToArray(bitwise) {
            var DaysOfTheWeek = {};
            DaysOfTheWeek[0] = (0x1);
            DaysOfTheWeek[1] = (0x2);
            DaysOfTheWeek[2] = (0x4);
            DaysOfTheWeek[3] = (0x8);
            DaysOfTheWeek[4] = (0x10);
            DaysOfTheWeek[5] = (0x20);
            DaysOfTheWeek[6] = (0x40);
            DaysOfTheWeek[7] = (0x7F);
            var res = [];
            for (var i = 0; i < 7; i++) {
                if ((DaysOfTheWeek[i] & bitwise) == DaysOfTheWeek[i]) {
                    res.push(i);
                }
            }

            return res;
        }

        function convertStringToDate(datestring, islocal) {
            if (!datestring) {
                return null;
            }

            var ticks = parseInt(datestring.substring(6, datestring.length - 2), 10);
            var date = new Date(ticks);
            if (islocal == true) {
                date = new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), 0);
            }

            return date;
        }

        function HTTPADMTaskFactory() {
            this.preExecute = function() {
                var promise = $q.defer();
                promise.resolve();
                return promise.promise;
            };

            this.execute = function(job) {
                var promise = $q.defer();
                var localparams = job.parameters;
                var globalParameters = job.meta.globalParameters;
                var header = {};
                var method = '';
                var url = '';
                var body = '';
                var params = {};
                var i;
                var p;
                // load global params
                if (globalParameters) {
                    for (i = 0; i < globalParameters.length; i++) {
                        p = globalParameters[i];
                        params[p.key] = p.value;
                    }
                }

                // overwrite global params with local params
                if (localparams) {
                    for (i = 0; i < localparams.length; i++) {
                        p = localparams[i];
                        params[p.key] = p.value;
                    }
                }

                for (var key in params) {
                    var value = params[key];
                    switch (key.toLowerCase()) {
                        case 'url.header':
                            var headers = value.split(';');
                            for (var j = 0; j < headers.length; j++) {
                                var headerParam = headers[j];
                                var splits = headerParam.split('=');
                                if (splits.length == 2) {
                                    var headerKey = replaceStringWithParams(splits[0], params);
                                    var headerValue = replaceStringWithParams(splits[1], params);
                                    header[headerKey] = headerValue;
                                }
                            }

                            break;
                        case 'url.method':
                            method = value.toUpperCase();
                            break;
                        case 'url.url':
                            url = replaceStringWithParams(value, params);
                            break;
                        case 'url.body':
                            body = replaceStringWithParams(value, params);
                            break;
                    }
                }

                var httpWrapper = new HttpWrapper();
                var handler;
                switch (method) {
                    case 'GET':
                        handler = httpWrapper.apiGet(url, null, header);
                        break;
                    case 'PUT':
                        handler = httpWrapper.apiPut(url, body, header);
                        break;
                    case 'POST':
                        handler = httpWrapper.apiPost(url, body, header);
                        break;
                    case 'DELETE':
                        handler = httpWrapper.apiDelete(url, null, header, body);
                        break;
                }

                var resolvePromise = function(str) {
                    logService.info(logkey, 'Job ' + job.meta.id + ' : ' + job.name + ' executed');
                    logService.debug(logkey, 'Job ' + job.meta.id + ' : ' + job.name + ' response length:' + str.length);
                    promise.resolve(str);
                };

                if (handler) {
                    handler.then(function(res) {
                        var response = '';
                        if (res.data) {
                            response = res.data;
                        } else {
                            response = 'code ' + res.status + ' ' + res.statusText;
                        }

                        resolvePromise(response);
                    }, function(err) {
                        resolvePromise('error: code ' + err.status + ' ' + err.statusText);
                    });
                } else {
                    resolvePromise('invalid HTTP method : ' + method);
                }

                return promise.promise;
            };

            function replaceStringWithParams(str, params) {
                for (var pKey in params) {
                    var value = params[pKey];
                    var key = '{{' + pKey + '}}';
                    str = str.split(key).join(value);
                }

                return str;
            }

            this.postExecute = function(resultObject) {
                queueJobResponse(resultObject.job.meta.id, resultObject.result);
            };
        }

        function ScriptADMTaskFactory() {
            this.execute = function(job) {
                var promise = $q.defer();
                logService.debug(logkey, 'Executing job ' + job.meta.id + ' - ' + job.name);
                var localparams = job.parameters;
                var globalParameters = job.meta.globalParameters;
                var params = {};
                var i;
                var p;
                // load global params
                if (globalParameters) {
                    for (i = 0; i < globalParameters.length; i++) {
                        p = globalParameters[i];
                        params[p.key] = p.value;
                    }
                }

                // overwrite global params with local params
                if (localparams) {
                    for (i = 0; i < localparams.length; i++) {
                        p = localparams[i];
                        params[p.key] = p.value;
                    }
                }

                var response = {
                    write: function(str) {
                        logService.info(logkey, 'Job ' + job.meta.id + ' : ' + job.name + ' executed');
                        promise.resolve(str);
                    }
                };
                // get the script
                var script;
                for (var key in params) {
                    var value = params[key];
                    if (key == 'body') {
                        script = value;
                    }
                }

                // inject globar var
                script = 'var __jobId = "' + job.meta.id + '"; var __jobName = "' + job.name + '";' + script;
                // replace global parameters
                script = replaceStringWithParams(script, params);

                if (script) {
                    var evalPromise = coreFrameworkService.eval(script);

                    evalPromise.then(function(result) {
                        response.write(result);
                    }, function(err) {
                        response.write(err.toString());
                    });
                } else {
                    response.write(null);
                }

                return promise.promise;
            };

            function replaceStringWithParams(str, params) {
                for (var pKey in params) {
                    var value = params[pKey];
                    var key = '{{' + pKey + '}}';
                    str = str.split(key).join(value);
                }

                return str;
            }

            this.preExecute = function() {
                var promise = $q.defer();
                promise.resolve('pre');
                return promise.promise;
            };

            this.postExecute = function(resultObject) {
                queueJobResponse(resultObject.job.meta.id, resultObject.result);
            };
        }

        function RS232ADMTaskFactory() {
            this.execute = function(job) {
                var promise = $q.defer();
                var localparams = job.parameters;
                var globalParameters = job.meta.globalParameters;
                var params = {};
                var i;
                var p;
                // load global params
                if (globalParameters) {
                    for (i = 0; i < globalParameters.length; i++) {
                        p = globalParameters[i];
                        params[p.key] = p.value;
                    }
                }

                // overwrite global params with local params
                if (localparams) {
                    for (i = 0; i < localparams.length; i++) {
                        p = localparams[i];
                        params[p.key] = p.value;
                    }
                }

                var execute;
                var config;
                switch (deviceType.toLowerCase()) {
                    case 'brightsign':
                        execute = executeBrightsign;
                        config = {};
                        if (params.baudrate != undefined) {
                            config.baudrate = params.baudrate;
                        }

                        if (params.command != undefined) {
                            config.command = params.command;
                        }

                        break;
                    case 'surevue':
                        execute = executeSurevue;
                        config = {};
                        if (params.baudrate != undefined) {
                            config.baudrate = params.baudrate;
                        }

                        if (params.command != undefined) {
                            config.command = params.command;
                        }

                        break;
                    case 'chromeos':
                        execute = executeChrome;
                        config = {};
                        if (params.baudrate != undefined) {
                            config.baudrate = params.baudrate;
                        }

                        if (params.command != undefined) {
                            config.command = params.command;
                        }

                        break;
                    case 'iec':
                        execute = executeIEC;
                        config = {};
                        if (params.baudrate != undefined) {
                            config.baudrate = params.baudrate;
                        }

                        if (params.command != undefined) {
                            config.command = params.command;
                        }

                        if (params.devicename != undefined) {
                            config.devicename = params.devicename;
                        }

                        break;
                    default:
                        break;
                }

                if (execute) {
                    execute(config).then(function(res) {
                        promise.resolve(res);
                    });
                } else {
                    promise.resolve('no rs232 executor for ' + deviceType);
                }

                return promise.promise;
            };

            function executeBrightsign(config) {
                var promise = $q.defer();
                if (config.baudrate == undefined) {
                    config.baudrate = 9600;
                }

                if (config.command == undefined) {
                    config.command = '';
                }

                var initSerialScript = 'm.serial = CreateObject("roSerialPort", 0, ' + config.baudrate + ')';
                var initSerialObject = 'm.serialdata = CreateObject("roByteArray")';
                var setSerialObject = 'm.serialdata.fromhexstring("' + config.command + '")';
                var sendBlock = 'm.serial.SendBlock(m.serialdata)';
                var execList = [initSerialObject, setSerialObject, sendBlock];
                var execListCount = 0;
                var platform = window.$asCoreFrameworkService.getServices().hal.halPlatformService;

                platform.executePlatformScript({type: 'command', args: initSerialScript});
                runScript();

                function runScript() {
                    if (execListCount >= execList.length) {
                        promise.resolve();
                        return;
                    }

                    var timeoutId = setTimeout(function() {
                        platform.executePlatformScript({type: 'command', args: execList[execListCount]});
                        clearTimeout(timeoutId);
                        execListCount++;
                        runScript();
                    }, 1000);
                }

                return promise.promise;
            }

            function executeChrome(config) {
                var promise = $q.defer();
                if (config.baudrate == undefined) {
                    config.baudrate = 9600;
                }

                if (config.command == undefined) {
                    config.command = '';
                }

                var serialPath = '';
                var connectionId;
                var command = hex2a(config.command);
                var baudrate = parseInt(config.baudrate);
                var serialPaths = [];

                // start here - get all active connection, then kill it
                chrome.serial.getConnections(function(connectionInfos) {
                    for (var i = 0; i < connectionInfos.length; i++) {
                        var conObj = connectionInfos[i];
                        chrome.serial.disconnect(conObj.connectionId, onDisconnect);
                    }

                    chrome.serial.getDevices(onGetDevices);
                });

                var onGetDevices = function(ports) {
                    for (var i = 0; i < ports.length; i++) {
                        var port = ports[i];
                        var portpath = port.path;
                        // path example = /dev/cu.Bluetooth-Incoming-Port
                        // path example = /dev/usb0 <- in chromebox
                        // displayName: USB-Serial Controller D, path example: /dev/cu.usbserial
                        // note : not all port have displayName, cannot be use to detect which serial port

                        // some of serial port path dont have "serial" word, ex: "USB0" , so we just assume any port without bluetooth word is serial port
                        if (portpath && portpath.toLowerCase().indexOf('bluetooth') == -1) {
                            // add to array, will connect and send data to all posible serial port. Some case we will get 2 port that have almost same path
                            // example: tty/usb0 and tty/usb1
                            serialPaths.push(portpath);
                        }
                    }

                    doConnect();
                };

                var doConnect = function() {
                    if (serialPaths.length == 0) {
                        promise.resolve('success');
                        return;
                    }

                    serialPath = serialPaths.shift();
                    if (serialPath != '') {
                        chrome.serial.connect(serialPath, {bitrate: baudrate}, onConnect);
                    } else {
                        promise.resolve('rs232 port not found in device');
                    }
                };

                var onConnect = function(connectionInfo) {
                    connectionId = connectionInfo.connectionId;
                    var arrayBuffer = convertStringToArrayBuffer(command);
                    chrome.serial.send(connectionId, arrayBuffer, onSend);
                };

                var onSend = function(res) {
                    chrome.serial.flush(connectionId, onFlush);
                };

                var onFlush = function() {
                    chrome.serial.disconnect(connectionId, onDisconnect);
                };

                var onDisconnect = function(result) {
                    if (result) {
                        // console.log("Disconnected from the serial port");
                    } else {
                        // console.log("Disconnect failed");
                    }

                    doConnect();
                };

                return promise.promise;
            }

            function executeSurevue(config) {
                var promise = $q.defer();
                if (config.baudrate == undefined) {
                    config.baudrate = 9600;
                }

                if (config.command == undefined) {
                    config.command = '';
                }

                var command = hexStrToAscii(config.command);
                config.baudrate = parseInt(config.baudrate);
                var jsonObj = {
                    id: 0,
                    name: 'serialportwriteline',
                    data: command
                };

                var httpWrapper = new HttpWrapper();
                var handler = 'http://localhost/api/v1/';
                httpWrapper.apiPost(handler, jsonObj).then(function(res) {
                    if (res && res.data && res.data.message == 'success') {
                        // console.log("Surevue: serialportwriteline success... ", res);
                        promise.resolve(res.data.data);
                    } else {
                        // console.log('unable to serialportwriteline');
                        promise.reject('unable to serialportwriteline');
                    }
                }, function(err) {
                    // console.log("Surevue: executeSurevue fail");
                    promise.resolve(0);
                });

                return promise.promise;
            }

            var iecDevice;

            function executeIEC(config) {
                var devices = global.serialPorts.availableDevices;
                var promise = $q.defer();
                if (config.baudrate == undefined) {
                    config.baudrate = 9600;
                }

                if (config.command == undefined) {
                    config.command = '';
                }

                if (config.devicename == undefined) {
                    config.devicename = '*';
                }

                var command = hexStrToAscii(config.command);
                config.baudrate = parseInt(config.baudrate);

                logService.info(logkey, 'ADM - executeIEC ' + config.baudrate + ' : ' + config.command + ' : ' + config.devicename);
                var isDeviceAvailable = false;

                for (var i = 0; i < devices.length; i++) {
                    var device = devices[i];
                    global.serialPorts.deleteAll();
                    iecDevice = global.serialPorts.port(device);
                    if (iecDevice) {
                        if (config.devicename == '*' || config.devicename.toLowerCase() == device.toLowerCase()) {
                            iecDevice.baudRate = config.baudrate;
                            iecDevice.dataBits = 8; // 5,6,7,8
                            iecDevice.flowControl = 0; // 0=none, 1=Hardware ,2=Software
                            iecDevice.parity = 0; // 0=no, 1=Even ,2=Odd ,3=Space ,4= ,5=Mark
                            iecDevice.stopBits = 1; // 1=1, 2=3, 3=1.5
                            iecDevice.write(command);
                            isDeviceAvailable = true;
                        }
                    }
                }

                if (isDeviceAvailable) {
                    promise.resolve('Success');
                } else {
                    promise.reject('no available port found');
                }

                return promise.promise;
            }

            // hexString to arrays of charcode
            function hex2a(hex) {
                var str = [];
                for (var i = 0; i < hex.length; i += 2) {
                    var v = parseInt(hex.substr(i, 2), 16);
                    if (v == 1) {
                        v = 0x01;
                    } else if (v == 0) {
                        v = 0x00;
                    }

                    str.push(v);
                }

                return str;
            }

            // hexString string to ascii
            function hexStrToAscii(hexx) {
                var hex = hexx.toString();// force conversion
                var str = '';
                for (var i = 0; i < hex.length; i += 2) {
                    var v = parseInt(hex.substr(i, 2), 16);
                    if (v == 1) {
                        v = 0x01;
                    } else if (v == 0) {
                        v = 0x00;
                    }

                    str += String.fromCharCode(v);
                }

                return str;
            }

            // convert to array buffer
            function convertStringToArrayBuffer(str) {
                var buf = new ArrayBuffer(str.length);
                var bufView = new Uint8Array(buf);
                for (var i = 0; i < str.length; i++) {
                    bufView[i] = str[i];
                }

                return buf;
            }

            this.preExecute = function() {
                var promise = $q.defer();
                promise.resolve();
                return promise.promise;
            };

            this.postExecute = function(resultObject) {
                queueJobResponse(resultObject.job.meta.id, resultObject.result);
            };
        }
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    // declare media module and its dependencies
    var app = $angular.module('media', ['network', 'storage', 'ngSanitize', 'hal']);

    app.filter('trusted', ['$sce', function($sce) {
        return function(url) {
            return $sce.trustAsResourceUrl(url);
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('media');
    app.constant('CONTENT_TYPE', {
        VIDEO: 'video',
        AUDIO: 'audio',
        IMAGE: 'image',
        SIGNS: 'signs',
        CARD: 'card',
        WEB: 'web page',
        WEB_SHORTEN: 'web',
        TEXT: 'text',
        HTML: 'html text',
        HTLM_SHORTEN: 'htmltext',
        FLASH: 'flash',
        EXTERNAL_CONTENT: 'external content',
        LOW_BANDWIDTH_THUMBNAIL: 'lowbandwidththumbnail'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('media');
    app.constant('MEDIA', {
        WEB_ENGINE_IN_APP_BROWSER: 'inappbrowser',
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('media');
    app.constant('MEDIA_DIRECTIVE', {
        WEB: 'as-media-web',
        IN_APP_BROWSER: 'as-in-app-browser',
        CARD: 'as-media-card',
        AS_MEDIA_VIDEO: 'as-media-video',
        AS_DUAL_VIDEO: 'as-dual-video',
        VIDEO: 'video',
        AUDIO: 'audio',
        AS_TIZEN_VIDEO: 'as-tizen-video',
        AS_CRESTRON_VIDEO: 'as-crestron-video'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('media');
    app.constant('TRANSITION', {
        CROSS_FADE: 'fadeIn,fadeOut',
        LEFT_SWIPE: 'slideInLeft,',
        RIGHT_SWIPE: 'slideInRight,',
        TOP_SWIPE: 'slideInDown,',
        BOTTOM_SWIPE: 'slideInUp,',
        CENTER_HORIZONTAL_EXPAND: 'horizontalZoom,fadeOut',
        CENTER_VERTICAL_EXPAND: 'verticalZoom,fadeOut',
        ROTATE_LEFT: 'rotateCounterClockwise,fadeOut',
        FADE_IN: 'fadeIn,',
        LEFT_SWIPE_FADE: 'slideInLeft,fadeOut',
        RIGHT_SWIPE_FADE: 'slideInRight,fadeOut',
        LEFT_SWITCH: 'slideInLeft,slideOutLeft',
        RIGHT_SWITCH: 'slideInRight,slideOutRight',
        TOP_SWITCH: 'slideInDown,slideOutUp',
        BOTTOM_SWITCH: 'slideInUp,slideOutDown',
        LEFT_PUSH: 'slideInLeft,slideOutRight',
        RIGHT_PUSH: 'slideInRight,slideOutLeft',
        ZOOM_IN_FROM_BACK: 'zoomIn,zoomOutFront',
        ZOOM_IN_FROM_FRONT: 'zoomInFront,zoomOut',
        TOP_PUSH: 'slideInDown,slideOutDown',
        BOTTOM_PUSH: 'slideInUp,slideOutUp',
        ROTATE_RIGHT: 'rotateClockwise,fadeOut',
        CUBE_LEFT: 'cube-left,',
        CUBE_RIGHT: 'cube-right,',
        CUBE_UP: 'cube-up,',
        CUBE_DOWN: 'cube-down,',
        CUBE_RANDOM: 'cube-random,',
        NONE: ',',
        NOT_SET: null
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('media');
    app.directive('asMediaCard', ["$q", "FileUtil", "FEATURE_CODE", "PLAYER_PROPERTY_KEYS", "deviceTokenService", "cardContentCacheService", "occupancyService", "versionUtil", function(
        $q,
        FileUtil,
        FEATURE_CODE,
        PLAYER_PROPERTY_KEYS,
        deviceTokenService,
        cardContentCacheService,
        occupancyService,
        versionUtil
    ) {
        return {
            restrict: 'EA',
            scope: {
                source: '@', // source(s) of the video
                contentId: '@',
                contentNumericId: '@',
                preferPoster: '@', // uses the thumbnail for non dynamic cards
                originalMediaUrl: '@',
                onApiReady: '&',
                options: '=',
                onLoad: '&',
                onComplete: '&',
                onLoadError: '&',
                onPlaybackError: '&',
                onActionReceived: '&',
                onMetadataLoaded: '&'
            },

            template: '<div style="width:100%; height:100%;"> </div>',

            link: function(scope, element) {
                scope.$on('$destroy', function() {
                    scope.destroy();
                });
                element.on('$destroy', function() {
                    scope.destroy();
                });

                scope.$watch('source', function(newVal) {
                    if (!newVal) {
                        return;
                    }

                    return scope.setSource(newVal);
                });

                // Construct the api interface
                scope.api = {
                    setSource: scope.setSource,
                    setData: scope.setData,
                    getCardInfo: scope.getCardInfo,
                    getSource: scope.getSource,
                    isLoading: scope.isLoading,
                    destroy: scope.destroy,
                    play: scope.play,
                    pause: scope.pause,
                    seek: scope.seek,
                    reload: scope.reload,
                    unload: scope.unload,
                    preUnload: scope.preUnload,
                    executeAction: scope.executeAction,
                    updateDeviceProperties: scope.updateDeviceProperties,
                    updateResourceManifest: scope.updateResourceManifest
                };

                scope.init();

                if (scope.onApiReady) {
                    scope.onApiReady({api: scope.api});
                }
            },

            controller:
                ["$scope", "$compile", "$element", "$timeout", "$attrs", "halDriverService", "halPlatformService", "logService", "halNetworkService", "SYSTEM_DATA", "broadcastService", "dataUtil", "embedProfileService", "mediaUtil", "halMediaService", "deviceIdentityService", "UrlUtil", "coreConfigurationService", "syncService", "pushNotificationClientService", "POST_MESSAGES", "cardUtil", function($scope,
                    $compile,
                    $element,
                    $timeout,
                    $attrs,
                    halDriverService,
                    halPlatformService,
                    logService,
                    halNetworkService,
                    SYSTEM_DATA,
                    broadcastService,
                    dataUtil,
                    embedProfileService,
                    mediaUtil,
                    halMediaService,
                    deviceIdentityService,
                    UrlUtil,
                    coreConfigurationService,
                    syncService,
                    pushNotificationClientService,
                    POST_MESSAGES,
                    cardUtil
                ) {
                    var isLoading = false;
                    var srcFile = '';
                    var indexPage = '';
                    var debugName = '';
                    var loaded = false;
                    var cardInfo = null;
                    var cardThemeInfo = null;
                    var cardConfiguration = {};
                    var cardInitTimeout = null;
                    var cardPath = '';
                    var cardOptions = null;
                    var webApi = null;
                    var webFrame = null;
                    var poster = null;
                    var hasCardInit = false;
                    var loadTimeoutDuration = 0;
                    var isAppspaceCard = false;
                    var cardMetadata;
                    var requireLocalAccess;
                    var constellationTokens;
                    var pushUpdateTopic = {};
                    var executeDeviceMappings = {
                        enablestatuslight: enableStatusLight,
                        disablestatuslight: disableStatusLight,
                        print: print,
                        setbrightness: halPlatformService.setBrightness,
                        getbrightness: getBrightness,
                        setcontainerstyle: setContainerStyle,
                    };
                    var getFeatureSupport = function(key) {
                        return halPlatformService.getFeatureSupport(FEATURE_CODE[key]);
                    };

                    var supportVideoLayering = getFeatureSupport('RENDERING_VIDEO_LAYERING') !== 'no';
                    var SUPPORTED_DIRECTIVE_OPTIONS = ['allowmediarequest', SYSTEM_DATA.FORCE_IFRAME_KEY];
                    var POST_MESSAGE_TYPE = {
                        DEVICE: 'device'
                    };
                    var directiveOptions = mediaUtil.convertAttributesToOptions($attrs);
                    var appspaceUrl;
                    var hasCardSentLoaded = false;
                    var LOG_KEY = 'appspace.core.media';
                    var RESPONSE_POSTFIX = '.response';
                    var RESOLVE_CONTENT_PATH_MESSAGE = 'api.resolvecontentpath';
                    var RESOLVE_CONTENT_PATH_MESSAGE_RESPONSE = RESOLVE_CONTENT_PATH_MESSAGE + RESPONSE_POSTFIX;
                    var OCCUPANCY_MESSAGE = 'occupancy';
                    var INDEX_FILE = 'index.html';
                    var isStatusLightInUse = false;
                    var SUPPORTED_SERVICE_VERSIONS = {
                        CLOUD: {
                            MIN: '8.10.0-ac.2.6',
                            MAX: '8.10.0'
                        },
                        GLOBAL: '8.10.0'
                    };

                    $scope.init = function() {
                        // Get the media load timeout, and set that to the max load duration
                        var mediaTimeout = getFeatureSupport('MEDIA_TIMEOUT_DURATION');
                        appspaceUrl = coreConfigurationService.get('appspace', 'appspaceURL');
                        var getCardTimeoutDuration = function() {
                            var timeoutDuration = cardUtil.getCardTimeoutDuration();
                            if (timeoutDuration) {
                                return timeoutDuration;
                            }

                            timeoutDuration = parseInt(getFeatureSupport('CARD_TIMEOUT_PERCENTAGE') * mediaTimeout);
                            return Math.max(timeoutDuration, SYSTEM_DATA.MIN_TIMEOUT_DURATION);
                        };

                        loadTimeoutDuration = getCardTimeoutDuration();
                    };

                    $scope.destroy = function() {
                        clearPushUpdateTopics();
                        isLoading = false;
                        $scope.unload();
                        destroyWebFrame();
                        destroyPoster();
                        $element.empty(); // Parent of the video element (optional);
                        broadcastService.disableBackmostVideoLayer();
                        occupancyService.unsubscribe(onOccupancyStatusUpdate);
                    };

                    function destroyCommonObject() {
                        if (webApi) {
                            webApi.removeEventListener('messages', onCardWindoMessage);
                            webApi.destroy();
                            webApi = null;
                        }

                        if (poster) {
                            poster.scope.$destroy();
                            poster.el.remove();
                            poster = null;
                        }
                    }

                    function destroyWebFrame() {
                        destroyCommonObject();
                        if (webFrame) {
                            webFrame.scope.$destroy();
                            webFrame.el.remove();
                            webFrame = null;
                        }
                    }

                    function destroyPoster() {
                        destroyCommonObject();
                    }

                    $scope.unload = function() {
                        cancelOnLoadDelayTimer();

                        if (loaded && srcFile.length > 0) {
                            loaded = false;
                            logService.debug(LOG_KEY, 'Card unload: ' + debugName);
                        }

                        srcFile = '';
                        isLoading = false;
                    };

                    $scope.preUnload = function() {
                        if (!isStatusLightInUse) {
                            return;
                        }

                        // Only disable status light upon card pre-unload provided that
                        // the status light was in use
                        disableStatusLight();
                    };

                    $scope.executeAction = function(param) {
                        sendPostMessage({message: 'executeaction', parameters: param});
                    };

                    $scope.updateDeviceProperties = function(properties) {
                        sendPostMessage({message: 'updatedeviceproperties', parameters: properties});
                    };

                    $scope.updateResourceManifest = function(manifest) {
                        sendPostMessage({
                            message: POST_MESSAGES.UPDATE_RESOURCE_MANIFEST,
                            parameters: {
                                manifest: manifest
                            }
                        });
                    };

                    $scope.isLoading = function() {
                        return isLoading;
                    };

                    $scope.getCardInfo = function() {
                        return cardInfo;
                    };

                    $scope.setData = function(o) {
                        $scope.data = o;
                    };

                    $scope.onWebApiReady = function(api) {
                        webApi = api;
                        webApi.addEventListener('messages', onCardWindoMessage);

                        // Attempt to load the card html into the web browser
                        webApi.setUrl(indexPage).then(onWebPageLoad, onError);
                    };

                    $scope.onPosterLoad = function() {
                        logService.debug(LOG_KEY, 'Card poster loaded: ' + debugName);
                        onLoaded(sendOnLoad);
                    };

                    $scope.onPosterLoadError = function() {
                        logService.debug(LOG_KEY, 'Failed to load card poster: ' + debugName);
                        createWebFrame();
                    };

                    function onLoaded(callback) {
                        cancelOnLoadDelayTimer();

                        if (srcFile.length == 0) {
                            return;
                        }

                        loaded = true;
                        // ignore on load if card is not loading
                        if (!isLoading) {
                            return;
                        }

                        callback();
                    }

                    function processCardLoadedEvent() {
                        // only process load if card has sent init
                        if (!hasCardInit) {
                            return;
                        }

                        sendOnLoad();
                    }

                    function sendOnLoad() {
                        isLoading = false;
                        logService.debug(LOG_KEY, 'Card fully loaded and ready: ' + debugName);
                        // Card is loaded and can be played
                        if (!$scope.onLoad) {
                            return;
                        }

                        $scope.onLoad({
                            sender: $scope, msg: 'Load success: ' + debugName,
                            metadata: cardMetadata
                        });
                    }

                    function onError(e) {
                        if (srcFile.length == 0) {
                            return;
                        }

                        // Error when loading card
                        if (isLoading) {
                            raiseError($scope.onLoadError, 'load');
                            return;
                        }

                        // Error during playback
                        raiseError($scope.onPlaybackError, 'playback');
                    }

                    function raiseError(callback, errorType) {
                        var errorMessage = 'Card ' + errorType + ' error: ' + debugName;
                        logService.error(LOG_KEY, errorMessage);
                        if (!callback) {
                            return;
                        }

                        callback({sender: $scope, msg: errorMessage});
                    }

                    function onEnded() {
                        if (srcFile.length == 0) {
                            return;
                        }

                        logService.debug(LOG_KEY, 'Card complete: ' + debugName);
                        // Card has competed
                        if ($scope.onComplete) {
                            $scope.onComplete({sender: $scope, msg: 'Card Complete: ' + debugName});
                        }
                    }

                    function onWebPageLoad() {
                        // Use the media load timeout
                        logService.debug(LOG_KEY, 'Card page loaded into browser: ' + debugName);
                        if (!isAppspaceCard) {
                            logService.info(LOG_KEY, 'Loading non appspace card');
                            logService.debug(LOG_KEY, 'Starting load timeout timer');
                            startOnLoadDelayTimer(loadTimeoutDuration);
                        }
                    }

                    function cancelOnLoadDelayTimer() {
                        if (cardInitTimeout) {
                            $timeout.cancel(cardInitTimeout);
                            cardInitTimeout = null;
                        }
                    }

                    function onLoadTimeout() {
                        logService.debug(LOG_KEY, 'Card does not fire load event, loading due to timeout of [' + loadTimeoutDuration + ']ms');
                        onLoaded(sendOnLoad);
                    }

                    function startOnLoadDelayTimer(duration) {
                        cancelOnLoadDelayTimer();
                        if (duration == undefined) {
                            duration = 10;
                        }

                        cardInitTimeout = $timeout(onLoadTimeout, duration);
                    }

                    function onOccupancyStatusUpdate(status) {
                        logService.info(LOG_KEY, 'Occupancy sensor status update: ' + status);
                        sendPostMessage({
                            message: POST_MESSAGES.NOTIFY,
                            event: OCCUPANCY_MESSAGE,
                            data: status
                        });
                    }

                    function subscribeToOccupancyStatus(postMessageEvent, method) {
                        if (postMessageEvent !== OCCUPANCY_MESSAGE) {
                            return;
                        }

                        occupancyService[method](onOccupancyStatusUpdate);
                    }

                    function onCardWindoMessage(event) {
                        if (!event.data || !event.data.message) {
                            return;
                        }

                        // Process the card message
                        switch (event.data.message.toLowerCase()) {
                            case 'executedevice':
                                processExecuteDevice(event.data);
                                break;
                            case 'onapiready':
                                sendInitConfig(event.data.runtimeConfiguration);
                                break;
                            case 'loaded':
                                hasCardSentLoaded = true;
                                onLoaded(processCardLoadedEvent);
                                break;
                            case 'error':
                                onError();
                                break;
                            case 'complete':
                                onEnded();
                                break;
                            case 'api.xhr':
                                onXHRRequest(event.data);
                                break;
                            case 'api.httprequest':
                                onHttpRequest(event.data);
                                break;
                            case 'api.cachecontent':
                                onCacheContent(event.data);
                                break;
                            case RESOLVE_CONTENT_PATH_MESSAGE:
                                onResolveContentUrl(event.data);
                                break;
                            case 'executeaction':
                                if ($scope.onActionReceived) {
                                    $scope.onActionReceived({action: event.data.action});
                                }

                                break;
                            case 'onconstellationtokensrefresh':
                                if (!$scope.contentId) {
                                    return;
                                }

                                deviceTokenService.saveContentConstellationTokens($scope.contentId, $scope.contentNumericId, event.data.tokens);
                                break;
                            case 'subscribe':
                                subscribeToOccupancyStatus(event.data.event, 'subscribe');
                                break;
                            case 'unsubscribe':
                                subscribeToOccupancyStatus(event.data.event, 'unsubscribe');
                                break;
                            case 'pendingnativeload':
                                if (hasCardSentLoaded || !webApi || !webApi.processPendingNativeLoad) {
                                    return;
                                }

                                hasCardSentLoaded = true;
                                webApi.processPendingNativeLoad(function() {
                                    onLoaded(processCardLoadedEvent);
                                });
                                break;
                        }
                    }

                    function processExecuteDevice(data) {
                        var method = executeDeviceMappings[data.action];
                        if (!method) {
                            return;
                        }

                        method(data.parameters);
                    }

                    function getBrightness() {
                        var brightness;
                        halPlatformService.getBrightness().then(function(value) {
                            brightness = value;
                        }, $angular.noop).finally(function() {
                            sendPostMessage({
                                message: POST_MESSAGES.BRIGHTNESS,
                                data: brightness,
                                type: POST_MESSAGE_TYPE.DEVICE
                            });
                        });
                    }

                    function setContainerStyle(parameters) {
                        if (!parameters || !parameters.property || !parameters.value) {
                            return;
                        }

                        webApi.setStyle(parameters.property, parameters.value);
                    }

                    function processRuntimeConfiguration(runtimeConfiguration) {
                        if (runtimeConfiguration) {
                            processVideoLayer(runtimeConfiguration);
                            processContainVideoAndAudio(runtimeConfiguration);
                            processAllowVideoScrubbing(runtimeConfiguration);
                        }

                        cardMetadata = {
                            contentInfo: {
                                cardTemplateId: cardInfo.Id,
                            },
                            hasNaturalDuration: cardInfo.NaturalDuration === true,
                            containsVideo: cardConfiguration.containsVideo,
                            containsAudio: cardConfiguration.containsAudio,
                            allowVideoScrubbing: cardConfiguration.allowVideoScrubbing
                        };

                        if (cardThemeInfo) {
                            cardMetadata.contentInfo.cardThemeId = cardThemeInfo.themeId;
                            cardMetadata.contentInfo.cardThemeName = cardThemeInfo.name;
                        }

                        if (!$scope.onMetadataLoaded) {
                            return;
                        }

                        $scope.onMetadataLoaded({metadata: cardMetadata});
                    }

                    function sendInitConfig(runtimeConfiguration) {
                        if (hasCardInit) {
                            logService.warn(LOG_KEY, 'Refuse to send init config, card has been init');
                            return;
                        }

                        processRuntimeConfiguration(runtimeConfiguration);
                        hasCardInit = true;
                        var onSchemaLoad = function(schema) {
                            var initConfig = {
                                properties: undefined,
                                deviceCapabilities: undefined,
                                deviceIdentity: undefined,
                                api: {
                                    'xhr': true,
                                    'httprequest': true,
                                    'cachecontent': true
                                },
                                attributes: {
                                    video: ''
                                },
                                resourceManifest: syncService.getManifest()
                            };

                            // For now we have no version restrictions, the only requirement is that there be a schema version property
                            if ($scope.data && schema && schema.version) {
                                // Player properties supplied to the card for model substitution
                                initConfig.properties = $scope.data.properties;
                            }

                            var capabilites = halPlatformService.getDeviceCapabilities();

                            isServiceWorkerCachingSupported(capabilites).then(function(supported) {
                                capabilites.supportServiceWorkerCaching = supported;
                                // Set the device capabilites regardless of any settings from the card
                                initConfig.deviceCapabilities = Object.assign({}, capabilites, {
                                    timeoutDuration: cardUtil.getCardTimeoutDuration()
                                });

                                if (cardMetadata.containsVideo || cardMetadata.containsBackgroundVideo) {
                                    initConfig.attributes.video = halMediaService.getVideoAttributes();
                                }

                                if (constellationTokens) {
                                    initConfig.constellationTokens = constellationTokens;
                                }

                                // Load device identity
                                deviceIdentityService.generateDeviceIdentity().then(function(deviceIdentity) {
                                    initConfig.deviceIdentity = deviceIdentity;
                                    logService.debug(LOG_KEY, 'Sending init to card');
                                    // Send init event to card api
                                    sendPostMessage({message: 'api.init', config: initConfig});
                                    if (!hasCardSentLoaded) {
                                        return;
                                    }

                                    // Call send on load to ensure load event is sent
                                    // for scenarios where we received loaded event before onapiready event
                                    onLoaded(processCardLoadedEvent);
                                });
                            });
                        };

                        // Load the schema
                        halNetworkService.getJSON(resolveUrl('schema.json')).then(onSchemaLoad, onSchemaLoad);
                    }

                    function isServiceWorkerCachingSupported(capabilites) {
                        if (!capabilites.supportServiceWorkerCaching) {
                            return $q.resolve(false);
                        }

                        var defer = $q.defer();

                        versionUtil.isVersionSupported(SUPPORTED_SERVICE_VERSIONS, LOG_KEY).then(function(supported) {
                            defer.resolve(!supported);
                        });

                        return defer.promise;
                    }

                    function wrapDeferResponse(requestId, eventType, response, status, xhr) {
                        processResponse(requestId,
                            eventType,
                            JSON.parse(JSON.stringify(response)),
                            status,
                            xhr);
                    }

                    function processResponse(requestId, eventType, response, status, xhr) {
                        var objStatus = JSON.parse(JSON.stringify(status));
                        var objXhr = JSON.parse(JSON.stringify(xhr));
                        var objHeaders = JSON.parse(JSON.stringify(xhr.getAllResponseHeaders()));
                        sendPostMessage({
                            message: 'api.response',
                            event: eventType,
                            requestId: requestId,
                            result: response,
                            status: objStatus,
                            xhr: objXhr,
                            responseHeaders: objHeaders
                        });
                    }

                    function onXHRRequest(request) {
                        doAjaxRequest(request, wrapDeferResponse);
                    }

                    function onHttpRequest(request) {
                        doAjaxRequest(request, processResponse);
                    }

                    function onCacheContent(eventObject) {
                        if (!eventObject || !eventObject.data || !eventObject.data.manifest || !$scope.originalMediaUrl) {
                            return;
                        }

                        cardContentCacheService.cacheByManifest($scope.originalMediaUrl, $scope.contentNumericId, eventObject.data.manifest)
                            .then($angular.noop, $angular.noop);
                    }

                    function onResolveContentUrl(eventObject) {
                        var sendResponse = function(path) {
                            eventObject = eventObject || {};
                            var callback = eventObject.callback || {};
                            callback.result = {
                                contentUrl: path || '',
                                remoteUrl: eventObject.request
                            };
                            sendPostMessage({
                                message: RESOLVE_CONTENT_PATH_MESSAGE_RESPONSE,
                                callback: callback
                            });
                        };

                        if (!eventObject || !eventObject.request || !$scope.originalMediaUrl) {
                            sendResponse();
                            return;
                        }

                        cardContentCacheService.resolveContentUrl($scope.originalMediaUrl, $scope.contentNumericId, eventObject.request)
                            .then(sendResponse, function() {
                                sendResponse(eventObject.request);
                            });
                    }

                    function doAjaxRequest(request, callback) {
                        $.ajax(request.url, request.settings)
                            .done(function(response, status, xhr) {
                                callback(request.$requestId, 'done', response, status, xhr);
                            })
                            .fail(function(xhr, status, response) {
                                callback(request.$requestId, 'fail', response, status, xhr);
                            });
                    }

                    function loadThemeInfo() {
                        // on failure, just load the manifest file
                        halNetworkService.getJSON(resolveUrl('theme.json')).then(onThemeLoad, loadManifestFile);
                    }

                    function onThemeLoad(theme) {
                        cardThemeInfo = cardUtil.processThemeInformation(theme);
                        loadManifestFile();
                    }

                    function loadManifestFile() {
                        // Load the manifest
                        halNetworkService.getJSON(resolveUrl('manifest.json')).then(onCardManifestLoad, onError);
                    }

                    function isStreamingFromAppspace(url) {
                        return appspaceUrl && dataUtil.includesString(appspaceUrl.toLowerCase(), url.toLowerCase());
                    }

                    function resolveUrl(relativeUrl) {
                        var url = cardPath + relativeUrl;
                        if (!isStreamingFromAppspace(url)) {
                            return url;
                        }

                        // if we're streaming the card from Appspace, add deviceId
                        return UrlUtil.appendDeviceInformation(url);
                    }

                    function areScopesValid(scopes) {
                        return Boolean(scopes && (scopes.device || scopes.default));
                    }

                    function onCardManifestLoad(manifest) {
                        cardInfo = manifest;
                        isAppspaceCard = cardInfo.Developer && cardInfo.Developer.toLowerCase() === 'appspace';
                        cardInfo.Network = cardInfo.Network || {};
                        cardInfo.Network.RequiresConnection = cardInfo.Network.RequiresConnection || false;
                        cardInfo.Hardware = cardInfo.Hardware || {};
                        cardInfo.Hardware.RequiresCamera = cardInfo.Hardware.RequiresCamera || false;
                        cardInfo.TopicSubscriptions = cardInfo.TopicSubscriptions || [];
                        $scope.preferPoster = $scope.preferPoster == 'true';
                        requireLocalAccess = cardInfo.Network.RequiresConnection === false && !cardPath.match(/^(http|https):\/\//i);
                        var requireTokens = areScopesValid(cardInfo.ConstellationScopes) || areScopesValid(cardInfo.CloudScopes);
                        var queryParams = {
                            playback: 'device',
                            isPreview: embedProfileService.isPreviewMode(),
                        };
                        var isStreaming = isStreamingFromAppspace(srcFile);
                        if (isStreaming) {
                            var sourceQueryParams = UrlUtil.retrieveParamsFromUrl(srcFile);
                            if (sourceQueryParams.context) {
                                queryParams.context = sourceQueryParams.context;
                            }

                            if (sourceQueryParams.channelid) {
                                queryParams.channelid = sourceQueryParams.channelid;
                            }
                        }

                        // Get path to index page
                        var indexPageUrl = UrlUtil.appendQueryString('index.html', queryParams);
                        if (isStreaming) {
                            indexPageUrl = UrlUtil.appendDeviceInformation(indexPageUrl);
                        }

                        indexPage = resolveUrl(indexPageUrl);

                        // Let the web frame know if it requires a network connection / camera
                        cardOptions = 'requirelocalaccess=' + requireLocalAccess;
                        cardOptions += ' requirecamera=' + cardInfo.Hardware.RequiresCamera;
                        cardOptions += ' subject-id="' + $scope.contentId;
                        cardOptions += '" subject-type="Content"';
                        cardOptions += ' iscardreloadenabled=' + Boolean(cardInfo.ReloadsOnReconnect);

                        var directiveOptionsKeys = Object.keys(directiveOptions);
                        for (var i = 0; i < SUPPORTED_DIRECTIVE_OPTIONS.length; i++) {
                            var option = SUPPORTED_DIRECTIVE_OPTIONS[i];
                            if (!dataUtil.includesValue(option, directiveOptionsKeys)) {
                                continue;
                            }

                            cardOptions += ' ' + option + '=' + directiveOptions[option];
                        }

                        processPushUpdateTopics();

                        if (!requireTokens) {
                            processLoadCard();
                            return;
                        }

                        processCardTokens().finally(processLoadCard);
                    }

                    function processPushUpdateTopics() {
                        clearPushUpdateTopics();
                        var topics = cardInfo.TopicSubscriptions;
                        if (!topics.length) {
                            return;
                        }

                        var subscribedTopics = [];
                        for (var j = 0; j < topics.length; j++) {
                            var topic = topics[j];
                            // Example: resource.occupied, resource.unoccupied
                            var parsedTopic = topic.split('.');
                            if (parsedTopic.length !== 2) {
                                continue;
                            }

                            var topicType = parsedTopic[0];
                            var eventType = parsedTopic[1];
                            pushUpdateTopic[topicType] = pushUpdateTopic[topicType] || [];
                            if (pushUpdateTopic[topicType].indexOf(eventType) === -1) {
                                pushUpdateTopic[topicType].push(eventType);
                            }

                            if (subscribedTopics.indexOf(topicType) >= 0) {
                                continue;
                            }

                            subscribedTopics.push(topicType);
                            pushNotificationClientService.subscribeToTopic(topicType, onReceivedTopicMessage);
                            logService.debug(LOG_KEY, 'Topic \'' + topicType + '\' subscribed for card \'' + cardInfo.Id + '\': ' + debugName);
                        }
                    }

                    function clearPushUpdateTopics() {
                        for (var topicType in pushUpdateTopic) {
                            pushNotificationClientService.unsubscribeFromTopic(topicType, onReceivedTopicMessage);
                            pushUpdateTopic[topicType] = [];
                            logService.debug(LOG_KEY, 'Topic \'' + topicType + '\' unsubscribed for card \'' + cardInfo.Id + '\': ' + debugName);
                        }

                        pushUpdateTopic = {};
                    }

                    function onReceivedTopicMessage(message) {
                        var topic = message.topic;
                        var pushTopic = pushUpdateTopic[topic.key];
                        // ignore if we have not subscribed to this topic
                        if (!pushTopic || pushTopic.indexOf(topic.event) === -1) {
                            logService.debug(LOG_KEY, 'Ignoring topic \'' + topic.key + '\', event \'' + topic.event + '\' for card \'' + cardInfo.Id + '\': ' + debugName);
                            return;
                        }

                        sendPostMessage({message: POST_MESSAGES.ON_TOPIC_RECEIVED, data: message.payload});
                        logService.debug(LOG_KEY, 'Topic \'' + topic.key + '\', event \'' + topic.event + '\' sent to card \'' + cardInfo.Id + '\': ' + debugName);
                    }

                    function processLoadCard() {
                        // Check if we prefer posters for local cards
                        if (requireLocalAccess &&
                            (($scope.options && $scope.options.preferPoster) || $scope.preferPoster)) {
                            createPoster();
                            return;
                        }

                        createWebFrame();
                    }

                    function processCardTokens() {
                        if (!$scope.contentId) {
                            return $q.resolve();
                        }

                        var promise = $q.defer();
                        deviceTokenService.getContentConstellationTokens($scope.contentId, $scope.contentNumericId).then(function(tokens) {
                            constellationTokens = tokens;
                            promise.resolve();
                        }, function(error) {
                            var errorMessage = 'Fail to retrieve content constellation tokens: ' + error;
                            logService.warn(LOG_KEY, errorMessage);
                            promise.reject();
                        });
                        return promise.promise;
                    }

                    function processVideoLayer(runtimeConfiguration) {
                        // if card contains background video and device doesn't support video layering, enable the backmost video layer support
                        if (supportVideoLayering ||
                            !dataUtil.parseBoolean(runtimeConfiguration.containsBackgroundVideo)) {
                            broadcastService.disableBackmostVideoLayer();
                            return;
                        }

                        broadcastService.enableBackmostVideoLayer();
                    }

                    function processContainVideoAndAudio(runtimeConfiguration) {
                        cardConfiguration.containsVideo = dataUtil.parseBoolean(runtimeConfiguration.containsVideo);
                        cardConfiguration.containsAudio = dataUtil.parseBoolean(runtimeConfiguration.containsAudio);
                    }

                    function processAllowVideoScrubbing(runtimeConfiguration) {
                        cardConfiguration.allowVideoScrubbing = dataUtil.parseBoolean(runtimeConfiguration.allowVideoScrubbing);
                    }

                    function createWebFrame() {
                        // If already loaded, unload the current webframe
                        destroyWebFrame();
                        destroyPoster();

                        webFrame = {};
                        webFrame.scope = $scope.$new();
                        var template = '<as-media-web on-api-ready="onWebApiReady(api)" ' + cardOptions + ' ></as-media-web>';
                        webFrame.el = $compile(template)(webFrame.scope);
                        // Add the item directive to the element
                        $($element.children()[0]).append(webFrame.el);
                    }

                    function createPoster() {
                        // If already loaded, unload the current webframe
                        destroyWebFrame();
                        destroyPoster();

                        var thumbnail = resolveUrl('_thumbnail.jpg');
                        poster = {};
                        poster.scope = $scope.$new();
                        var template = '<div as-media-image style="width:100%; height:100%; position:absolute;" url="' + thumbnail + '" on-image-load="onPosterLoad()" on-image-load-error="onPosterLoadError()"></as-media-image>';
                        poster.el = $compile(template)(poster.scope);
                        // Add the item directive to the element
                        $($element.children()[0]).append(poster.el);
                    }

                    $scope.getSource = function(src) {
                        return srcFile;
                    };

                    $scope.setSource = function(src) {
                        requireLocalAccess = undefined;
                        constellationTokens = undefined;
                        halPlatformService.clearWebCache();
                        // Standardize source URL to make sure that the source URL ends with the standard index file so that
                        // the URL will be resolved correctly for manifest file loading afterwards
                        if (src.charAt(src.length - 1) === '/') {
                            src += INDEX_FILE;
                        }

                        cardPath = FileUtil.getContentPath(src);

                        cancelOnLoadDelayTimer();

                        // Load the model
                        loadThemeInfo();

                        loaded = false;
                        isLoading = true;
                        debugName = srcFile = src;
                    };

                    $scope.play = function() {
                        if (!loaded) {
                            return;
                        }

                        logService.debug(LOG_KEY, 'Card play: ' + debugName);
                        sendPostMessage({message: 'play'});
                    };

                    $scope.pause = function() {
                        if (!loaded) {
                            return;
                        }

                        logService.debug(LOG_KEY, 'Card pause: ' + debugName);
                        sendPostMessage({message: 'pause'});
                    };

                    $scope.seek = function(duration) {
                        if (!loaded || !duration || !cardConfiguration.allowVideoScrubbing) {
                            return;
                        }

                        logService.debug(LOG_KEY, 'Card seek: ' + debugName);
                        sendPostMessage({message: 'seek', duration: duration});
                    };

                    $scope.reload = function() {
                        if (!dataUtil.isValueBooleanTrue(cardInfo.ReloadsOnReconnect)) {
                            return;
                        }

                        webApi.setUrl(srcFile);
                    };

                    function sendPostMessage(param) {
                        if (webApi && webApi.postMessage) {
                            webApi.postMessage(param);
                        }
                    }

                    function print(options) {
                        options.printOptions = options.printOptions || {};
                        syncService.getAppProperties().then(function(appProperties) {
                            halMediaService.print(appProperties, options);
                        }, function(error) {
                            logService.error(LOG_KEY, 'Error while retrieving app properties');
                        });
                    }

                    function getPlatformImplementation(key) {
                        return halDriverService.findImplementation('platform', key);
                    }

                    function getStatusLightImplementation(name) {
                        var implementation = getPlatformImplementation(name);
                        if (!implementation) {
                            return;
                        }

                        isStatusLightInUse = true;
                        return implementation;
                    }

                    function enableStatusLight(configuration) {
                        var implementation = getStatusLightImplementation('enableStatusLight');
                        if (!implementation) {
                            return $q.reject('Implementation not available');
                        }

                        var deferred = $q.defer();
                        syncService.getAppProperties().then(function(appProperties) {
                            configuration = configuration || {};
                            appProperties = appProperties || {};
                            configuration.colorPalette = appProperties[PLAYER_PROPERTY_KEYS.STATUS_LIGHT_COLOR_PALETTE];
                            implementation(configuration).then(deferred.resolve, deferred.reject);
                        }, function(error) {
                            var errorMessage = 'Unable to execute enableStatusLight due to an error while retrieving app properties: ' +
                                JSON.stringify(error);
                            logService.error(errorMessage);
                            deferred.reject(errorMessage);
                        });
                        return deferred.promise;
                    }

                    function disableStatusLight() {
                        var implementation = getStatusLightImplementation('disableStatusLight');
                        if (!implementation) {
                            return $q.reject('Implementation not available');
                        }

                        return implementation();
                    }
                }]
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('media');
    app.directive('asCrestronVideo', ["halVideoLayerManager", "dataUtil", function(halVideoLayerManager, dataUtil) {
        return {
            restrict: 'EA',
            scope: {
                source: '@',
                stretch: '@',
                onApiReady: '&',
                onVideoLoad: '&',
                onVideoComplete: '&',
                onVideoLoadError: '&',
                onVideoPlaybackError: '&',
            },
            template: function(element, attrs) {
                return '<div id="player" style="width: 100%; height: 100%"></div>';
            },
            link: function(scope, element, attrs) {
                scope.$on('$destroy', scope.destroy);
                element.on('$destroy', scope.destroy);

                scope.$watch('source', function(newVal) {
                    if (!newVal) {
                        return;
                    }

                    return scope.setSource(newVal);
                });

                scope.api = {
                    setSource: scope.setSource,
                    allowReuse: scope.allowReuse,
                    getSource: scope.getSource,
                    isLoading: scope.isLoading,
                    seek: scope.seek,
                    destroy: scope.destroy,
                    getPosition: scope.getPosition,
                    play: scope.play,
                    pause: scope.pause,
                    unload: scope.unload,
                    lostLayer: scope.lostLayer,
                    mediaDirective: 'as-crestron-video',
                    freeze: scope.freeze
                };

                scope.init();

                if (scope.onApiReady) {
                    scope.onApiReady({api: scope.api});
                }

                halVideoLayerManager.registerVideoLayer(scope.api);
            },
            controller: ["$scope", "$element", "$timeout", "logService", "$interval", "halPlatformService", "CRESTRON_INTENT_MESSAGE", function($scope, $element, $timeout, logService, $interval, halPlatformService, CRESTRON_INTENT_MESSAGE) {
                var LOG_KEY = 'appspace.core.media';
                var debugName;
                var positioningListener;
                var POSITIONING_LISTENER_INTERVAL_MILLISECONDS = 500;
                var currentPosition = {
                    left: 0,
                    top: 0,
                    width: 0,
                    height: 0
                };
                var sourceFile;
                var isLoaded = false;
                var isLoading = false;
                var isUnloaded = false;
                var videoOptions = {
                    autoplay: true,
                    fullscreen: false
                };
                function sendPlatformMessage(type, data, option) {
                    halPlatformService.sendPlatformMessage(getPlatformMessage(
                        type,
                        data,
                        getVideoOptions(option)));
                }

                $scope.init = function() {
                    halPlatformService.subscribeToPlatformMessages(onPlatformMessages);
                    checkVideoSize();
                    positioningListener = $interval(checkVideoSize, POSITIONING_LISTENER_INTERVAL_MILLISECONDS);
                };

                function onPlatformMessages(payload) {
                    payload = payload || {};
                    var type = payload.type;
                    var error = payload.error;
                    switch (type) {
                        case CRESTRON_INTENT_MESSAGE.STARTED_VIDEO:
                            if (!error) {
                                raiseVideoLoad();
                            } else {
                                raiseVideoLoadError(error);
                            }

                            break;
                        case CRESTRON_INTENT_MESSAGE.STOPPED_VIDEO:
                            isLoaded = false;
                            if (!isUnloaded) {
                                // Restart video when it's stopped abruptly
                                logService.error('Restarting video due to: ' + JSON.stringify(error));
                                sendPlatformMessage(
                                    CRESTRON_INTENT_MESSAGE.START_VIDEO,
                                    sourceFile
                                );
                            }

                            if (!$scope.onVideoComplete) {
                                return;
                            }

                            $timeout(function() {
                                $scope.onVideoComplete({sender: $scope, msg: 'Video Complete: ' + debugName});
                            }, 0);

                            break;
                        default:
                            break;
                    }
                }

                function getPlatformMessage(type, data, option) {
                    return {
                        type: type,
                        data: data,
                        option: option
                    };
                }

                function checkVideoSize() {
                    var player = $element.find('#player');
                    var offset = player.offset();
                    var innerWidth = $element.innerWidth();
                    var innerHeight = $element.innerHeight();
                    if (currentPosition.left === offset.left && currentPosition.top === offset.top && currentPosition.width === innerWidth && currentPosition.height === innerHeight) {
                        return;
                    }

                    currentPosition.left = offset.left;
                    currentPosition.top = offset.top;
                    currentPosition.width = innerWidth;
                    currentPosition.height = innerHeight;
                    sendPlatformMessage(
                        CRESTRON_INTENT_MESSAGE.UPDATE_VIDEO,
                        sourceFile,
                        {
                            positionX: currentPosition.left,
                            positionY: currentPosition.top,
                            width: currentPosition.width,
                            height: currentPosition.height
                        }
                    );
                }

                $scope.destroy = function() {
                    halPlatformService.unsubscribeFromPlatformMessages(onPlatformMessages);
                    $interval.cancel(positioningListener);
                    if (isUnloaded) {
                        return;
                    }

                    $scope.unload();
                };

                function getVideoOptions(options) {
                    videoOptions = dataUtil.mergeObject(videoOptions, options || {});
                    return videoOptions;
                }

                $scope.setSource = function(url) {
                    debugName = sourceFile = url;
                    isLoading = true;
                    isUnloaded = false;
                    isLoaded = false;
                    checkVideoSize();
                    sendPlatformMessage(
                        CRESTRON_INTENT_MESSAGE.START_VIDEO,
                        url,
                        {
                            positionX: currentPosition.left,
                            positionY: currentPosition.top,
                            width: currentPosition.width,
                            height: currentPosition.height
                        }
                    );
                };

                function raiseVideoLoadError(error) {
                    if (!$scope.onVideoLoadError) {
                        return;
                    }

                    $timeout(function() {
                        $scope.onVideoLoadError({sender: $scope, msg: 'playback error: ' + debugName + '. Error source: ' + JSON.stringify(error)});
                    }, 0);
                }

                $scope.allowReuse = function() {
                    return true;
                };

                $scope.getSource = function() {
                    return sourceFile;
                };

                $scope.isLoading = function() {
                    return isLoading;
                };

                $scope.seek = function(currentTime) {
                    sendPlatformMessage(
                        CRESTRON_INTENT_MESSAGE.UPDATE_VIDEO,
                        sourceFile,
                        {
                            start: currentTime
                        }
                    );
                };

                function raiseVideoPlaybackError() {
                    if (!$scope.onVideoPlaybackError) {
                        return;
                    }

                    $scope.onVideoPlaybackError({sender: $scope, msg: 'playback error: ' + debugName});
                }

                function raiseVideoLoad() {
                    if (isLoaded) {
                        return;
                    }

                    isLoading = false;
                    isLoaded = true;
                    if (!$scope.onVideoLoad) {
                        return;
                    }

                    $scope.$evalAsync(function() {
                        $scope.onVideoLoad({sender: $scope, msg: 'Load success: ' + debugName});
                    });
                }

                $scope.play = function() {
                    logService.debug(LOG_KEY, 'Video play: ' + debugName);
                    if (isLoaded) {
                        return;
                    }

                    isLoading = true;
                    isUnloaded = false;
                    isLoaded = false;
                    // Start video if it's not loaded yet or
                    // it was previously stopped due to layout change
                    sendPlatformMessage(
                        CRESTRON_INTENT_MESSAGE.START_VIDEO,
                        sourceFile
                    );
                };

                $scope.pause = function() {
                    logService.debug(LOG_KEY, 'Video pause: ' + debugName);
                };

                $scope.freeze = function() {
                    logService.debug(LOG_KEY, 'Video freeze: ' + debugName);
                    // Destroy video player when going off-screen due to layout changes
                    $scope.destroy();
                };

                $scope.unload = function() {
                    isUnloaded = true;
                    isLoaded = false;
                    logService.debug(LOG_KEY, 'Video unload: ' + debugName);
                    sendPlatformMessage(
                        CRESTRON_INTENT_MESSAGE.STOP_VIDEO,
                        sourceFile
                    );
                };

                $scope.lostLayer = function() {
                    logService.debug(LOG_KEY, 'Crestron video lost layer: ' + debugName);
                    $scope.destroy();
                    if (isLoaded) {
                        raiseVideoPlaybackError();
                        return;
                    }

                    raiseVideoLoadError();
                };
            }]
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var getElement = $angular.element;
    $angular.module('media').directive('asDualVideo', function(halVideoLayerManager, halMediaService) {
        return {
            restrict: 'EA',
            scope: {
                source: '@', // source(s) of the video
                stretch: '@', // determines how to stretch the content based on its aspect ratio
                videoVisible: '<',
                switchVideoOnPlay: '<',
                onApiReady: '&',
                options: '=',
                onVideoLoad: '&',
                onVideoComplete: '&',
                onVideoLoadError: '&',
                onVideoPlaybackError: '&',
                onPlaybackProgress: '&'
            },
            template: function(element, attrs) {
                function generateVideoElement(mDirective) {
                    return '<' + mDirective.directive
                        + ' ' + mDirective.attributes
                        + ' style="position:absolute;width:100%;height:100%;visibility:hidden">'
                        + '</' + mDirective.directive + '>';
                }

                function generateVideoElements(mDirective) {
                    var result = generateVideoElement(mDirective);
                    result += generateVideoElement(mDirective);
                    return result;
                }

                var options = {};
                for (var a in attrs) {
                    if (a.indexOf('$') == -1) {
                        options[a] = attrs[a];
                    }
                }

                var mediaDirective = halMediaService.getMediaDirective('video', options);
                element.mediaDirective = mediaDirective.directive;
                return generateVideoElements(mediaDirective);
            },
            link: function(scope, element, attrs) {
                var elementType = element.mediaDirective || 'video';
                scope.videoObj1 = element.find(elementType)[0];
                scope.videoObj2 = element.find(elementType)[1];

                // set default stretch value
                if (!attrs.stretch) {
                    scope.setStretch('fill');
                }

                if (!(attrs.preload === undefined)) {
                    scope.videoObj1.preload = attrs.preload;
                    scope.videoObj2.preload = attrs.preload;
                }

                if (!(attrs.poster === undefined)) {
                    scope.videoObj1.poster = attrs.poster;
                    scope.videoObj2.poster = attrs.poster;
                }

                if (!(attrs.controls === undefined)) {
                    scope.videoObj1.controls = true;
                    scope.videoObj2.controls = true;
                }

                if (!(attrs.loop === undefined)) {
                    scope.videoObj1.loop = true;
                    scope.videoObj2.loop = true;
                }

                if (!(attrs.muted === undefined)) {
                    scope.videoObj1.muted = attrs.muted;
                    scope.videoObj2.muted = attrs.muted;
                }

                if (!(attrs.volume === undefined)) {
                    scope.videoObj1.volume = attrs.volume;
                    scope.videoObj2.volume = attrs.volume;
                }

                scope.$on('$destroy', function() {
                    scope.destroy();
                });
                element.on('$destroy', function() {
                    scope.destroy();
                });

                scope.$watch('stretch', function(newVal) {
                    if (!newVal) {
                        return;
                    }

                    scope.setStretch(newVal);
                });

                scope.$watch('source', function(newVal) {
                    if (!newVal) {
                        return;
                    }

                    return scope.setSource(newVal);
                });

                scope.api = {
                    setSource: scope.setSource,
                    allowReuse: scope.allowReuse,
                    getSource: scope.getSource,
                    isLoading: scope.isLoading,
                    setStretch: scope.setStretch,
                    destroy: scope.destroy,
                    play: scope.play,
                    pause: scope.pause,
                    unload: scope.unload,
                    setVolume: scope.setVolume,
                    mute: scope.setMute,
                    lostLayer: scope.lostLayer,
                    seek: scope.seek,
                    getPosition: scope.getPosition,
                    mediaDirective: elementType,
                    preloadContentItem: scope.preloadContentItem
                };

                scope.init();

                if (scope.onApiReady) {
                    scope.onApiReady({api: scope.api});
                }

                // Regiser the video layer
                halVideoLayerManager.registerVideoLayer(scope.api);
            },
            controller: function($scope, $element, $timeout, $interval, logService, halPlatformService, FEATURE_CODE) {
                var isLoading;
                var isLoaded;
                var isPreloading;
                var preloadUrl;
                var debugName;
                var srcFile;
                var currentVideoObject;
                var currentStretch;
                var currentVideoMarker;
                var previousVideoMarker;
                var freezeCounter;
                var checkFreezeIntervalHandler;
                var isVideoSwitchEnabled = true;
                var loadDelayDuration = halPlatformService.getFeatureSupport('media.video.load.delay.duration');
                var DEFAULT_SWITCH_DELAY_DURATION = 0;
                var forceMute = false;
                var switchDelayDuration = halPlatformService.getFeatureSupport(FEATURE_CODE.MEDIA_VIDEO_SWITCH_DELAY_DURATION) || DEFAULT_SWITCH_DELAY_DURATION;
                $scope.switchVideoOnPlay = Boolean($scope.switchVideoOnPlay);
                $scope.isLoading = function() {
                    return isLoading;
                };

                $scope.allowReuse = function(mediaItem) {
                    var isNewContentVideoSwitchSupported = true;
                    // if the next content doesn't allow video switching, we need to send back not supported
                    if (mediaItem && mediaItem.content && mediaItem.content.mediaUrl) {
                        var sourceObject = halMediaService.processMediaURL(mediaItem.content.mediaUrl);
                        isNewContentVideoSwitchSupported = sourceObject.isVideoSwitchEnabled;
                    }

                    // using source element will have impact on loading of the next video
                    return $scope.videoObj1 && $scope.videoObj2 && isVideoSwitchEnabled && isNewContentVideoSwitchSupported;
                };

                $scope.init = function() {
                    $scope.videoObj1.addEventListener('loadeddata', onLoaddata, false);
                    $scope.videoObj1.addEventListener('error', onError, false);
                    $scope.videoObj1.addEventListener('ended', onEnded, false);
                    $scope.videoObj1.addEventListener('timeupdate', onTimeupdate, false);

                    $scope.videoObj2.addEventListener('loadeddata', onLoaddata, false);
                    $scope.videoObj2.addEventListener('error', onError, false);
                    $scope.videoObj2.addEventListener('ended', onEnded, false);
                    $scope.videoObj2.addEventListener('timeupdate', onTimeupdate, false);

                    if (!$scope.switchVideoOnPlay) {
                        return;
                    }

                    $scope.videoObj1.addEventListener('play', onPlay, false);
                    $scope.videoObj2.addEventListener('play', onPlay, false);
                };

                $scope.destroy = function() {
                    isVideoSwitchEnabled = true;
                    cancelCheckFreeze();
                    resetVideoState();
                    $scope.unload();
                    halVideoLayerManager.unregisterVideoLayer($scope.api);
                    $element.empty();
                };

                $scope.lostLayer = function(src) {
                    logService.debug('appspace.core.media', 'Dual Video lost layer: ' + debugName);
                    var wasloaded = isLoaded;
                    $scope.destroy();
                    $timeout(function() {
                        if (wasloaded) {
                            if ($scope.onVideoPlaybackError) {
                                $scope.onVideoPlaybackError({sender: $scope, msg: 'playback error: ' + debugName});
                            }
                        } else {
                            if ($scope.onVideoLoadError) {
                                $scope.onVideoLoadError({sender: $scope, msg: 'playback error: ' + debugName});
                            }
                        }
                    }, 2000);
                };

                $scope.getSource = function() {
                    return srcFile;
                };

                $scope.preloadContentItem = function(src) {
                    isPreloading = true;
                    var altVideoObject = getNextVideoObject();
                    preloadUrl = (halMediaService.processMediaURL(src) || {}).url;
                    altVideoObject.src = preloadUrl;
                };

                $scope.setSource = function(src) {
                    var currentSrc = $scope.getSource();

                    if (currentSrc && src && currentSrc.toLowerCase() === src.toLowerCase()) {
                        logService.debug('appspace.core.media', 'Same video src. Reusing DOM element. ' + src);
                        isLoading = true;
                        $scope.seek(0);
                        $scope.play();
                        onLoaddata();
                        return;
                    }

                    isVideoSwitchEnabled = true;
                    currentVideoObject = getNextVideoObject();
                    logService.debug('appspace.core.media', 'Different video source, switching video.');

                    if ($scope.videoVisible === false) {
                        hideElement(currentVideoObject);
                    }

                    if (!currentVideoObject) {
                        return;
                    }

                    resetVideoState();
                    isLoading = true;

                    var sourceObject = halMediaService.processMediaURL(src);
                    debugName = srcFile = sourceObject.url;

                    if (!sourceObject.options) {
                        // If preloading and current video is already on the next video object, then fire load event
                        if (isPreloading && currentVideoObject.getAttribute('src') === srcFile) {
                            onLoaddata();
                            return;
                        }

                        currentVideoObject.src = srcFile;
                        return;
                    }

                    isVideoSwitchEnabled = sourceObject.isVideoSwitchEnabled;
                    getElement(currentVideoObject).empty();
                    currentVideoObject.pause();
                    currentVideoObject.load(); // loading video with an empty source
                    var options = escape(JSON.stringify(sourceObject.options));
                    var source = document.createElement('source');
                    source.setAttribute('type', 'video/' + sourceObject.lgmediatype + ';mediaOption=' + options);
                    source.setAttribute('src', srcFile);
                    currentVideoObject.appendChild(source);
                    logService.debug('appspace.core.media', 'Setting custom property for LG playback, options:' + options);
                };

                $scope.setStretch = function(stretch) {
                    stretch = (stretch || '').toLowerCase();
                    // Check do we already have the correct stretch
                    if (currentStretch == stretch) {
                        return;
                    }

                    // Update current stretch
                    currentStretch = stretch;
                    var stretchValue = 'fill';
                    switch (stretch) {
                        case 'uniform':
                            stretchValue = 'contain';
                            break;
                        case 'uniformfill':
                            stretchValue = 'cover';
                            break;
                    }

                    getElement('video').css('object-fit', stretchValue);
                };

                // unload everything
                $scope.unload = function() {
                    logService.debug('appspace.core.media', 'Video unload: ' + debugName);
                    unloadVideo($scope.videoObj1);
                    unloadVideo($scope.videoObj2);
                    $scope.videoObj1 = null;
                    $scope.videoObj2 = null;
                    // Unregister this layer
                    halVideoLayerManager.unregisterVideoLayer($scope.api);
                    isLoaded = false;
                    isLoading = false;
                    srcFile = '';
                };

                $scope.play = function() {
                    $scope.setMute(forceMute);
                    startCheckFreeze();
                    currentVideoObject.play();
                    logService.debug('appspace.core.media', 'Video play: ' + debugName);
                };

                $scope.pause = function() {
                    cancelCheckFreeze();
                    currentVideoObject.pause();
                    logService.debug('appspace.core.media', 'Video pause: ' + debugName);
                };

                $scope.setVolume = function(volume) {
                    if (!currentVideoObject) {
                        return;
                    }

                    if (isNaN(volume)) {
                        return;
                    } else if (volume > 1) {
                        volume = 1;
                    } else if (volume < 0) {
                        volume = 0;
                    }

                    currentVideoObject.volume = volume;
                };

                $scope.setMute = function(shouldMute, isTemporary) {
                    if (!currentVideoObject) {
                        return;
                    }

                    currentVideoObject.muted = shouldMute;
                    if (isTemporary) {
                        if ($scope.videoObj1) {
                            $scope.videoObj1.muted = shouldMute;
                        }

                        if ($scope.videoObj2) {
                            $scope.videoObj2.muted = shouldMute;
                        }
                        
                        return;
                    }

                    forceMute = shouldMute;
                };

                $scope.seek = function(currentTime) {
                    if (!currentVideoObject) {
                        return;
                    }

                    currentVideoObject.currentTime = currentTime;
                };

                $scope.getPosition = function() {
                    if (!currentVideoObject) {
                        return;
                    }

                    return currentVideoObject.currentTime;
                };

                function unloadVideo(video) {
                    if (video) {
                        getElement(video).empty();
                        video.pause();
                        video.src = ''; // passing empty value
                        video.load(); // loading video with an empty source
                        video.removeEventListener('loadeddata', onLoaddata);
                        video.removeEventListener('error', onError);
                        video.removeEventListener('ended', onEnded);
                        video.removeEventListener('timeupdate', onTimeupdate);
                        if ($scope.switchVideoOnPlay) {
                            video.removeEventListener('play', onPlay);
                        }

                        video.remove();
                    }
                }

                function resetVideoState() {
                    currentVideoMarker = 0;
                    previousVideoMarker = 0;
                    freezeCounter = 0;

                    isLoaded = false;
                    isLoading = false;
                }

                function cancelCheckFreeze() {
                    if (checkFreezeIntervalHandler) {
                        $interval.cancel(checkFreezeIntervalHandler);
                        checkFreezeIntervalHandler = null;
                    }
                }

                function startCheckFreeze() {
                    cancelCheckFreeze();
                    checkFreezeIntervalHandler = $interval(checkFreeze, 1000);
                }

                function checkFreeze() {
                    if (!currentVideoMarker) {
                        return;
                    }

                    if (currentVideoMarker != previousVideoMarker) {
                        freezeCounter = 0;
                        previousVideoMarker = currentVideoMarker;
                    } else {
                        freezeCounter++;
                        if (freezeCounter > 20) {
                            cancelCheckFreeze();
                            var errorMessage = 'Video freezes: ' + debugName;
                            var errorData = {
                                currentTime: currentVideoMarker,
                                previousTime: previousVideoMarker,
                                freezeCounter: freezeCounter
                            };
                            logService.error('appspace.core.media', 'Video freezes: ' + debugName, errorData);
                            // send the playbackerror if the video freezes more than 20 seconds
                            if ($scope.onVideoPlaybackError) {
                                $scope.onVideoPlaybackError({sender: $scope, msg: errorMessage + ':' + JSON.stringify(errorData)});
                            }
                        }
                    }
                }

                function getNextVideoObject() {
                    if (currentVideoObject == $scope.videoObj1) {
                        return $scope.videoObj2;
                    } else {
                        return $scope.videoObj1;
                    }
                }

                function onLoaddata(event) {
                    isLoaded = true;

                    if (pauseVideoPreloading(event) || !isLoading) {
                        return;
                    }

                    logService.debug('appspace.core.media', 'Video loaded and ready (by data loaded): ' + debugName);
                    sendOnLoad();
                }

                function onError(e) {
                    if (!srcFile.length ||
                        (e.srcElement && srcFile != e.srcElement.src)) {
                        return;
                    }

                    logService.error('appspace.core.media', 'Video error: ' + debugName, e);
                    var event;
                    var message = {sender: $scope};

                    // if we have loaded before, use video playback error
                    if (isLoaded) {
                        event = $scope.onVideoPlaybackError;
                        message.msg = 'Playback error: ' + debugName;
                    } else { // if we haven't load before, use video load error
                        event = $scope.onVideoLoadError;
                        message.msg = 'Load error: ' + debugName;
                    }

                    $timeout(function() {
                        event(message);
                    }, 0);
                }

                function onEnded(e) {
                    if (srcFile.length == 0) {
                        return;
                    }

                    logService.debug('appspace.core.media', 'Video complete: ' + debugName);
                    // Video has competed
                    if ($scope.onVideoComplete) {
                        $timeout(function() {
                            $scope.onVideoComplete({sender: $scope, msg: 'Video Complete: ' + debugName});
                        }, 0);
                    }
                }

                function onPlay(event) {
                    switchVideoDisplay();
                }

                function pauseVideoPreloading(event) {
                    if (!isPreloading || !event || !event.target || event.target.getAttribute('src') !== preloadUrl) {
                        return false;
                    }

                    logService.debug('appspace.core.media', 'Video preloaded: ' + preloadUrl);
                    event.target.pause();
                    return true;
                }

                function onTimeupdate(event) {
                    if (!isNaN(currentVideoObject.currentTime)) {
                        currentVideoMarker = currentVideoObject.currentTime;
                    }

                    if (pauseVideoPreloading(event)) {
                        return;
                    }

                    // if we haven't loaded but we start getting time update, send video load first
                    if (isLoading && !isNaN(currentVideoObject.duration)) {
                        logService.debug('appspace.core.media', 'Video loaded and ready (by Time update): ' + debugName);
                        // Video is loaded and can be played
                        sendOnLoad();
                    }

                    // give progress event if we have currenttime & total duration
                    if ($scope.onPlaybackProgress && !isNaN(currentVideoObject.duration) && !isNaN(currentVideoObject.currentTime)) {
                        $scope.onPlaybackProgress({
                            sender: $scope, progress: {
                                position: currentVideoObject.currentTime, duration: currentVideoObject.duration
                            }
                        });
                        $scope.$apply();
                    }
                }

                function sendOnLoad() {
                    isLoading = false;
                    if (!$scope.onVideoLoad) {
                        return;
                    }

                    var executeLoad = function() {
                        if (!$scope.switchVideoOnPlay) {
                            switchVideoDisplay();
                        }

                        $scope.onVideoLoad({
                            sender: $scope,
                            msg: 'Load success: ' + debugName
                        });
                    };

                    if (loadDelayDuration === undefined) {
                        executeLoad();
                        return;
                    }

                    $timeout(executeLoad, loadDelayDuration);
                }

                function showElement(element) {
                    if (!element) {
                        return;
                    }

                    getElement(element).css('visibility', 'visible');
                    $scope.setMute(forceMute);
                }

                function hideElement(element) {
                    if (!element) {
                        return;
                    }

                    getElement(element).css('visibility', 'hidden');
                    element.src = '';
                    element.muted = true;
                }

                function switchVideoElement(elementToShow, elementToHide) {
                    showElement(elementToShow);
                    $timeout(function() {
                        hideElement(elementToHide);
                    }, switchDelayDuration);
                }

                function switchVideoDisplay() {
                    // We reset preloading flag when we switch video display
                    isPreloading = false;
                    preloadUrl = undefined;

                    if (switchDelayDuration > 0) {
                        if (currentVideoObject === $scope.videoObj1) {
                            switchVideoElement($scope.videoObj1, $scope.videoObj2);
                        } else {
                            switchVideoElement($scope.videoObj2, $scope.videoObj1);
                        }

                        return;
                    }

                    if (currentVideoObject === $scope.videoObj1) {
                        hideElement($scope.videoObj2);
                        showElement($scope.videoObj1);
                    } else {
                        hideElement($scope.videoObj1);
                        showElement($scope.videoObj2);
                    }
                }
            }
        };
    });
})(window.angular);

(function($angular) {
    'use strict';
    var app = $angular.module('media');


    /**
     * @directive
     * @type {Function}
     */
    app.directive('asIecMediaVideo', ["halVideoLayerManager", function(halVideoLayerManager) {
        return {
            restrict: 'EA',
            scope: {
                source: '@',
                stretch: '@',
                onApiReady: '&',
                options: '=',
                onVideoLoad: '&',
                onVideoComplete: '&',
                onVideoLoadError: '&',
                onVideoPlaybackError: '&',
                onPlaybackProgress: '&'
            },

            template: '<div style="width:100%; height:100%;"></div>',

            link: function(scope, element, attrs) {
                // Get a reference to the video holder
                scope.videoHolder = element.find('div')[0];

                scope.$on('$destroy', function() {
                    scope.destroy();
                });
                element.on('$destroy', function() {
                    scope.destroy();
                });
                if (!(attrs.muted === undefined)) {
                    scope.videoObj.muted = true;
                }

                scope.$watch('source', function(newVal) {
                    if (!newVal) {
                        return;
                    }

                    return scope.setSource(newVal);
                });

                scope.api = {
                    setSource: scope.setSource,
                    allowReuse: scope.allowReuse,
                    getSource: scope.getSource,
                    isLoading: scope.isLoading,
                    setStretch: scope.setStretch,
                    destroy: scope.destroy,
                    play: scope.play,
                    pause: scope.pause,
                    unload: scope.unload,
                    setVolume: scope.setVolume,
                    mute: scope.setMute,
                    lostLayer: scope.lostLayer
                };

                scope.init();

                if (scope.onApiReady) {
                    scope.onApiReady({api: scope.api});
                }

                halVideoLayerManager.registerVideoLayer(scope.api);
            },

            controller: ['$scope', '$element', '$q', '$timeout', 'halVideoLayerManager', 'logService', function($scope, $element, $q, $timeout, halVideoLayerManager, logService) {
                var isLoading = false;
                var loaded = false;
                var debugName = '';
                var srcFile = '';
                var timeoutUpdateHandler;
                var timeoutCheckPluginHandler;
                var isDestroyed = true;
                var onEndedSent = false;
                var videoId = '';
                var params = [];

                var LOG_KEY = 'appspace.core.media';
                function onLoaddata() {
                    if (srcFile.length == 0) {
                        return;
                    }

                    loaded = true;
                    if (isLoading) {
                        logService.debug(LOG_KEY, 'Video loaded and ready (by data loaded): ' + debugName);
                        // Video is loaded and can be played
                        $timeout(function() {
                            if ($scope.onVideoLoad) {
                                // prevent $digest already in progress
                                // http://stackoverflow.com/questions/12729122/angularjs-prevent-error-digest-already-in-progress-when-calling-scope-apply
                                $timeout(function() {
                                    // $scope.$apply();
                                    $scope.onVideoLoad({sender: $scope, msg: 'Load success: ' + debugName});
                                }, 1);
                            }

                            checkPluginAvailable();
                        }, 1);
                        isLoading = false;
                    }
                }

                function onTimeupdate() {
                    var dom = document.getElementById(videoId);

                    if (timeoutUpdateHandler) {
                        $timeout.cancel(timeoutUpdateHandler);
                    }

                    if (!dom) {
                        return;
                    }

                    // Video has completed
                    if ($scope.onPlaybackProgress) {
                        $scope.onPlaybackProgress({
                            sender: $scope, progress: {
                                position: dom.time / 1000, duration: dom.length / 1000
                            }
                        });
                        $scope.$apply();

                        // fail-safe, if we call the device menu while the video is playing,
                        // onEnded listener will not be called.
                        // need to investigate further on IEC
                        if (dom.time && (dom.time != -1 && dom.length != -1) && dom.time >= dom.length && !onEndedSent) {
                            onEnded();
                        }
                    }

                    timeoutUpdateHandler = $timeout(onTimeupdate, 500);
                }

                function onEnded() {
                    if (srcFile.length == 0) {
                        return;
                    }

                    logService.debug(LOG_KEY, 'Video complete: ' + debugName);
                    // Video has competed
                    if ($scope.onVideoComplete) {
                        if (!onEndedSent) {
                            onEndedSent = true;
                            $scope.onVideoComplete({sender: $scope, msg: 'Video Complete: ' + debugName});
                            $scope.$apply();
                        }
                    }
                }

                function onError(e) {
                    if (srcFile.length == 0) {
                        return;
                    }

                    logService.error(LOG_KEY, 'Video error: ' + debugName, e);
                    // Error when loading video
                    if ($scope.onVideoLoadError) {
                        $scope.onVideoLoadError({sender: $scope, msg: 'Load error: ' + debugName});
                        $scope.$apply();
                    }
                }

                function onMuted(mute) {
                    if (srcFile.length == 0) {
                        return;
                    }

                    logService.debug(LOG_KEY, 'Video muted = ' + mute + ' : ' + debugName);
                }

                function onVolumeChanged(volume) {
                    if (srcFile.length == 0) {
                        return;
                    }

                    logService.debug(LOG_KEY, 'Video volume change to = ' + volume + ' : ' + debugName);
                }

                function checkPluginAvailable() {
                    if (timeoutCheckPluginHandler) {
                        $timeout.cancel(timeoutCheckPluginHandler);
                    }

                    var dom = document.getElementById(videoId);
                    if (dom && dom.ready) {
                        isDestroyed = false;
                        logService.debug(LOG_KEY, 'IEC plugin ' + videoId + ' loaded: ' + debugName);
                        dom.error.connect(onError);
                        dom.endReached.connect(onEnded);
                        dom.muted.connect(onMuted);
                        dom.volumeChanged(onVolumeChanged);
                        onTimeupdate();
                        return;
                    } else if (dom) {
                        timeoutCheckPluginHandler = $timeout(checkPluginAvailable, 500);
                    }
                }

                $scope.init = function() {
                    logService.debug(LOG_KEY, 'IEC video player initialize: ' + debugName);
                    // create the video plugin dynamically
                    videoId = 'iec_' + new Date().getTime();
                    var vid = document.createElement('object');
                    vid.setAttribute('id', videoId);
                    $scope.videoHolder.appendChild(vid);
                };

                $scope.setSource = function(src) {
                    var dom = document.getElementById(videoId);
                    if (!dom) {
                        logService.debug(LOG_KEY, 'IEC player unavailable: ' + debugName);
                        return;
                    }

                    loaded = false;
                    debugName = srcFile = src;
                    isLoading = true;

                    params = [
                        {name: 'url', value: src},
                        {name: 'progressIndicatorVisible', value: 'false'}
                    ];

                    logService.debug(LOG_KEY, 'setting source for IEC player: ' + debugName);

                    var param = '';
                    for (var i = 0; i < params.length; i++) {
                        param = document.createElement('param');
                        param.setAttribute('name', params[i].name);
                        param.setAttribute('value', params[i].value);
                        params[i].dom = param;
                        dom.appendChild(param);
                    }

                    // Always set the attribute after setting the param url.
                    // If we set the these attributes before setting the url,
                    // high chance the video is playing without visual.
                    // Still this need to be investigate further but for now,
                    // this will show the video fine (after an image to video)
                    dom.setAttribute('classid', 'videoplayer');
                    dom.setAttribute('type', 'application/x-qt-plugin');
                    dom.setAttribute('width', '100%');
                    dom.setAttribute('height', '100%');

                    // This does not work. With or without it will autoplay.
                    //
                    // param = document.createElement("param");
                    // param.setAttribute("name" , "autoPlay");
                    // param.setAttribute("value", "true");
                    // dom.appendChild(param);

                    onLoaddata();
                };

                $scope.allowReuse = function() {

                };

                $scope.getSource = function() {
                    return srcFile;
                };

                $scope.isLoading = function() {
                    return isLoading;
                };

                $scope.setStretch = function() {

                };

                $scope.destroy = function() {
                    if (timeoutCheckPluginHandler) {
                        $timeout.cancel(timeoutCheckPluginHandler);
                    }

                    if (!$scope.videoHolder || isDestroyed) {
                        return;
                    }

                    isDestroyed = true;
                    logService.debug(LOG_KEY, 'IEC video player destroy: ' + debugName);
                    $scope.videoHolder.remove();
                    halVideoLayerManager.unregisterVideoLayer($scope.api);
                };

                $scope.play = function() {
                    var dom = document.getElementById(videoId);
                    if (!dom || !dom.play) {
                        return;
                    }

                    logService.debug(LOG_KEY, 'IEC video start playing: ' + debugName);
                    dom.resume();
                };

                $scope.pause = function() {
                    var dom = document.getElementById(videoId);
                    if (!dom || !dom.pause) {
                        return;
                    }

                    logService.debug(LOG_KEY, 'IEC video pause: ' + debugName);
                    dom.pause();
                };

                $scope.unload = function() {
                    var dom = document.getElementById(videoId);
                    if (dom && dom.pause && srcFile.length > 0) {
                        loaded = false;
                        logService.debug(LOG_KEY, 'Video unload: ' + debugName);

                        dom.pause();

                        // remove params
                        for (var i = 0; i < params.length; i++) {
                            dom.removeChild(params[i].dom);
                        }

                        // Unregister this layer
                        halVideoLayerManager.unregisterVideoLayer($scope.api);
                    }

                    srcFile = '';
                    isLoading = false;
                };

                $scope.setVolume = function(volume) {
                    var dom = document.getElementById(videoId);
                    if (!dom) {
                        return;
                    }

                    if (isNaN(volume)) {
                        return;
                    }

                    if (volume >= 1) {
                        volume = volume * 100;
                    } else if (volume < 0) {
                        volume = 0;
                    }

                    dom.volume = volume;
                };

                $scope.setMute = function(shouldMute) {
                    var dom = document.getElementById(videoId);
                    if (!dom) {
                        return;
                    }

                    dom.isMuted = shouldMute;
                };

                $scope.lostLayer = function() {
                    $scope.destroy();
                    var handler;
                    if (loaded) {
                        handler = $scope.onVideoPlaybackError;
                    } else {
                        handler = $scope.onVideoLoadError;
                    }

                    if (handler) {
                        handler({sender: $scope, msg: 'playback error:' + videoId});
                    }
                };
            }]
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('media');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('asMediaImage', ["CONTENT_STRETCH", function(CONTENT_STRETCH) {
        return {
            restrict: 'EA',
            scope: {
                url: '@', // url of the image
                stretch: '@', // determines how to stretch the content based on its aspect ratio
                onApiReady: '&',
                onImageLoad: '&',
                onImageLoadError: '&',
            },

            template: '<img style="width:100%; height:100%;"/>',

            link: function(scope, element, attrs) {
                // set default stretch value
                if (!attrs.stretch) {
                    scope.setStretch(CONTENT_STRETCH.UNIFORM);
                }

                scope.$on('$destroy', function() {
                    // console.log("asMediaImage destroy");
                    scope.destroy();
                });
                element.on('$destroy', function() {
                    // console.log("asMediaImage element destroy");
                    scope.destroy(true);
                });
                scope.$watch('stretch', function(newVal) {
                    if (!newVal) {
                        return;
                    }

                    scope.setStretch(newVal);
                });

                scope.$watch('url', function(newVal) {
                    if (!newVal) {
                        return;
                    }

                    return scope.setUrl(newVal);
                });

                scope.api = {
                    destroy: scope.destroy,
                    setUrl: scope.setUrl,
                    isLoading: scope.isLoading,
                    setStretch: scope.setStretch
                };

                scope.init();

                if (scope.onApiReady) {
                    scope.onApiReady({api: scope.api});
                }
            },

            controller: ["$scope", "$element", "$q", "networkContentRouterService", function($scope, $element, $q, networkContentRouterService) {
                var isLoading = false;
                var imgElem;

                $scope.init = function() {
                    imgElem = $($element.find('img'));
                    $scope.setStretch($scope.stretch);
                };

                $scope.destroy = function() {
                    imgElem = null;
                    $element.empty();
                };

                $scope.isLoading = function() {
                    return isLoading;
                };

                $scope.setUrl = function(url) {
                    if (!imgElem) {
                        if ($scope.onImageLoadError) {
                            $scope.onImageLoadError({sender: $scope, message: 'Image element does not exists'});
                        }

                        return $q.reject('image element not exists');
                    }

                    var promise = $q.defer();
                    isLoading = true;

                    var processLoadError = function(message) {
                        $scope.$evalAsync(function() {
                            isLoading = false;
                            promise.reject('Image failed to load');
                            if ($scope.onImageLoadError) {
                                $scope.onImageLoadError({sender: $scope, message: 'Load error: ' + message + '(' + url + ')'});
                            }
                        });
                    };

                    var setImageElementSrc = function(imageUrl) {
                        /* In Brightsign devices, there are cases where the image from cache hits the
                         * timeout value to load the image. Content presenter will remove the element but
                         * the promise from networkContentRouterService is still processing. APC-4323
                        */
                        if (!imgElem) {
                            processLoadError('imgElem was removed prematurely');
                            return;
                        }

                        url = imageUrl;
                        imgElem.attr('src', url);
                    };

                    imgElem.on('load', function() {
                        // Force a layout reflow
                        imgElem[0].offsetLeft;
                        $scope.$evalAsync(function() {
                            isLoading = false;
                            promise.resolve('image loaded');
                            if ($scope.onImageLoad) {
                                $scope.onImageLoad({sender: $scope, msg: 'Load success: ' + url});
                            }
                        });
                    });

                    imgElem.on('error', function(e) {
                        processLoadError('Failed to load the image; error: ' + e);
                    });

                    networkContentRouterService.resolve(url, 'framework.media.image', {type: 'image'})
                        .then(setImageElementSrc, function(err) {
                            // Failed to route, use original url
                            setImageElementSrc(url);
                        });

                    return promise.promise;
                };

                $scope.setStretch = function(stretch) {
                    if (imgElem.length <= 0) {
                        return;
                    }

                    var objectFit = CONTENT_STRETCH.CONTAIN;
                    switch ((stretch || '').toLowerCase()) {
                        case CONTENT_STRETCH.UNIFORMFILL:
                            objectFit = CONTENT_STRETCH.COVER;
                            break;
                        case CONTENT_STRETCH.FILL:
                            objectFit = CONTENT_STRETCH.FILL;
                            break;
                    }

                    imgElem[0].style.objectFit = objectFit;
                };
            }]
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var app = $angular.module('media');
    app.directive('asInAppBrowser', ["halMediaService", function(halMediaService) {
        return {
            restrict: 'EA',
            scope: {
                url: '@',
                onApiReady: '&',
                onWebLoad: '&',
                onWebLoadError: '&',
            },

            link: function(scope, element) {
                scope.$on('$destroy', function() {
                    scope.destroy();
                });

                element.on('$destroy', function() {
                    scope.destroy();
                });

                scope.$watch('url', function(newVal) {
                    if (!newVal) {
                        return;
                    }

                    return scope.setUrl(newVal);
                });

                scope.api = {
                    destroy: scope.destroy,
                    setUrl: scope.setUrl,
                    isLoading: scope.isLoading,
                    reload: scope.reload
                };

                if (!scope.onApiReady) {
                    return;
                }

                scope.onApiReady({api: scope.api});
            },

            controller: ["$scope", "$q", "logService", "dataUtil", "PLAYER_PROPERTY_KEYS", function($scope, $q, logService, dataUtil, PLAYER_PROPERTY_KEYS) {
                var inAppBrowserWindow = undefined;
                var isLoading = false;
                var currentUrl = '';
                var id = 'inappbrowser_' + Date.now();

                $scope.destroy = function() {
                    if (!inAppBrowserWindow) {
                        return;
                    }

                    dispose();
                };

                $scope.isLoading = function() {
                    return isLoading;
                };

                $scope.setUrl = function(url) {
                    isLoading = true;
                    logService.debug('appspace.core.media', 'Launching url: ' + url);
                    inAppBrowserWindow = halMediaService.createBrowser(url, onLoadStop, onLoadError);
                    currentUrl = url;
                    return $q.resolve(url);
                };

                function dispose() {
                    if (!inAppBrowserWindow) {
                        return;
                    }

                    inAppBrowserWindow.dispose();
                    inAppBrowserWindow = undefined;
                    currentUrl = undefined;
                }

                function onLoadStop(event) {
                    logService.debug('appspace.core.media', 'Loadstop event, url: ' + event.url);
                    isLoading = false;
                    if (!$scope.onWebLoad) {
                        return;
                    }

                    $scope.onWebLoad();
                }

                function onLoadError(event) {
                    logService.debug('appspace.core.media', 'Loaderror event, url: ' + event.url + '. ' + event.message);
                    isLoading = false;
                    if (!$scope.onWebLoadError) {
                        return;
                    }

                    $scope.onWebLoadError();
                }

                $scope.reload = function(playerProperties) {
                    if (!dataUtil.isValueTrue(playerProperties[PLAYER_PROPERTY_KEYS.MEDIA_RELOAD_ON_RECONNECT])) {
                        return;
                    }

                    var reloadUrl = currentUrl;
                    $scope.destroy();
                    $scope.setUrl(reloadUrl);
                };
            }]
        };
    }]);
})(window.angular);

(function ($angular) {

    "use strict";

    var app = $angular.module('media');

    /**
     * @directive
     * @type {Function}
     */
	//todo : interact with the flash content
	//todo : sending custom parameter & attribute & flashvar
    app.directive('asMediaFlash', function () {
        return {
            restrict: "EA",
            scope: {
                url: '@', //url of the image
                onApiReady: '&',
                onFlashLoad: '&',
                onFlashLoadError: '&',
            },
            template: '<div id="{{id}}" style="width:100%;height:100%"></div>',
            link: function (scope, element, attrs) {

                scope.$on('$destroy', function() {
                    //console.log("asMediaFlash destroy");
                    scope.destroy();
                });
                element.on('$destroy', function() {
                    //console.log("asMediaFlash element destroy");
                    scope.destroy();
                });

                scope.$watch('url', function (newVal) {
                    if (!newVal) {
                        return;
                    }
                    return scope.setUrl(newVal);
                });

                scope.api = {
                    destroy: scope.destroy,
                    setUrl: scope.setUrl,
                    isLoading: scope.isLoading,
                    callFunction: scope.callFunction,

                }

                if (scope.onApiReady) {
                    scope.onApiReady({ api: scope.api });
                }
            },
            controller: ['$scope', '$element', '$q', '$timeout', 'networkContentRouterService', function ($scope, $element, $q, $timeout, networkContentRouterService) {
                $scope.id;
                var flashElem;
                var isFlashLoading = false;

                $scope.destroy = function () {
                    if ($scope.id && swfobject) {
                        swfobject.removeSWF($scope.id);
                        $scope.id = null;
                    }
                    $element.empty();
                }

                $scope.isLoading = function () {
                    return isFlashLoading;
                };

                $scope.callFunction = function (func, params) {
                    if (flashElem && flashElem[func]) {
                        if (params) {
                            flashElem[func](params);
                        } else {
                            flashElem[func]();
                        }
                    }
                }

                $scope.setUrl = function (url, flashvarsP, paramsP, attributesP) {
                    var d = new Date();
                    isFlashLoading = true;
                    $scope.id = "flashdiv_" + Math.round(Math.random() * 1000) + "_" + d.getTime();
                    if (swfobject) {
                        $timeout(function () {
                            var flashvars = (!flashvarsP) ? {} : flashvarsP;
                            var params = (!paramsP) ? {} : paramsP;
                            var attributes = (!attributesP) ? {} : attributesP;

                            swfobject.embedSWF(url, $scope.id, "100%", "100%", "10.0.0", null,
                                flashvars, params, attributes,
                                function (e) {
                                    if (e && e.success == true) {
                                        if ($scope.onFlashLoad) {
                                            $scope.onFlashLoad({ sender: $scope, msg: "Load success: " + url });
                                        }
                                        flashElem = e.ref;
                                    } else {
                                        if ($scope.onFlashLoadError) {
                                            $scope.onFlashLoadError({ sender: $scope, msg: "Load error: " + url });
                                        }
                                    }
                                    isFlashLoading = false;
                                });
                        }, 0);
                    }
                    else {
                        if ($scope.onFlashLoadError) {
                            $scope.onFlashLoadError({ sender: $scope, msg: "swfobject is undefined" });
                        }
                    }

                }

            }]
        }
    });
})(window.angular);
(function ($angular) {

    "use strict";

    var app = $angular.module('media');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('asMediaHtml', function () {
        return {
            restrict: "EA",
            scope: {
                url: '@', //url of the web page
                onApiReady: '&',
                onWebLoad: '&',
                onWebLoadError: '&'
            },

            template: '<div style="width:100%;height:100%;overflow:hidden" ng-bind-html="htmlCode"></div>',

            link: function (scope, element, attrs) {

                scope.$on('$destroy', function () {
                    //console.log("asMediaHtmls destroy");
                    scope.destroy();
                });

                element.on('$destroy', function() {
                    //console.log("asMediaHtml element destroy");
                    scope.destroy();
                });

                scope.$watch('url', function (newVal) {
                    if (!newVal) {
                        return;
                    }
                    return scope.setUrl(newVal);
                });

                scope.api = {
                    destroy: scope.destroy,
                    setUrl: scope.setUrl,
                    isLoading: scope.isLoading
                };

                if (scope.onApiReady) {
                    scope.onApiReady({ api: scope.api });
                }
            },

            controller: ['$scope', '$element', '$q', '$sce', '$compile', '$timeout' , 'HttpWrapper',
                function ($scope, $element, $q, $sce, $compile, $timeout, HttpWrapper) {
                    var isLoading = false;
					$scope.htmlCode = "";

                    $scope.destroy = function () {
                        $element.off('load');
                        $element.empty(); // Parent of the video element (optional);
                    }
                    $scope.isLoading = function () {
                        return isLoading;
                    };

                    $scope.onloadCallback = function () {
                        //work in progress
                    }

                    $scope.setUrl = function (url) {
                        //var defer = $q.defer();
						var wrapper = new HttpWrapper();
                        //var loadingUrl = $sce.trustAsResourceUrl(url);

						wrapper.handlerGet(url).then(function(res){
							if (res && res.data){
								$scope.htmlCode = $sce.trustAsHtml(res.data);
							}
							if ($scope.onWebLoad){
								setTimeout(function(){$scope.onWebLoad()},10);
							}
							//defer.resolve();
						},function(err){
							if ($scope.onWebLoadError){
								$scope.onWebLoadError(err);
							}
							//defer.reject(err);
						});

                        //defer.resolve($scope.webUrl);
                        //temporary. still finding a way to listen to iframe onload
                        // $timeout(function () {
                        //     if ($scope.onWebLoad) {
                        //         $scope.onWebLoad();
                        //     }
                        // }, 500);
                        //return defer.promise;
                    };
                }]
        };
    });
})(window.angular);
(function($angular) {
    'use strict';

    var app = $angular.module('media');
    app.directive('asMediaLowBandwidthThumbnail', function() {
        return {
            restrict: 'EA',
            scope: {
                url: '@',
                onLoad: '&',
                onError: '&',
                onApiReady: '&'
            },
            templateUrl: '/modules/media/directives/asMediaLowBandwidthThumbnail.html',
            link: function(scope) {
                scope.$watch('url', function(newValue) {
                    return newValue ? scope.setUrl(newValue) : undefined;
                });

                scope.api = {
                    destroy: scope.destroy,
                    setUrl: scope.setUrl,
                    isLoading: scope.isLoading
                };

                scope.onApiReady({api: scope.api});
            },
            controller: ["$scope", "$attrs", "mediaUtil", "dataUtil", "lowBandwidthService", function($scope, $attrs, mediaUtil, dataUtil, lowBandwidthService) {
                var imageApi;
                var options = mediaUtil.convertAttributesToOptions($attrs);
                $scope.lowBandwidthMessage = lowBandwidthService.getMessage();
                $scope.isMaskEnabled = !dataUtil.isValueFalse(options.lowbandwidthmask);
                $scope.hideLowBandwidthMessage;

                lowBandwidthService.isMessageHidden().then(function(hide) {
                    $scope.hideLowBandwidthMessage = hide;
                });

                $scope.setUrl = function(url) {
                    if (!imageApi) {
                        $scope.onError({message: 'No image API'});
                        return;
                    }

                    imageApi.setUrl(url);
                };

                $scope.onImageApiReady = function(api) {
                    imageApi = api;
                };

                $scope.onImageLoaded = function() {
                    $scope.onLoad();
                };

                $scope.onImageLoadError = function(message) {
                    $scope.onError({message: message});
                };

                $scope.$on('$destroy', function() {
                    if (!imageApi) {
                        return;
                    }

                    imageApi.destroy();
                    imageApi = undefined;
                });
            }]
        };
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('media');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('asSamsungMediaVideo', ["halMediaService", "halVideoLayerManager", "TimeoutUtil", function(halMediaService, halVideoLayerManager, TimeoutUtil) {
        return {
            restrict: 'EA',
            scope: {
                source: '@', // source(s) of the video
                stretch: '@', // determines how to stretch the content based on its aspect ratio
                onApiReady: '&',
                options: '=',
                onVideoLoad: '&',
                onVideoComplete: '&',
                onVideoLoadError: '&',
                onVideoPlaybackError: '&',
                onPlaybackProgress: '&'
            },

            template: '<div style="width:100%;height:100%;position:absolute"><video style="width:100%; height:100%;position:absolute;"><source src="http:///" type="video/mp4"/></video></div>',


            link: function(scope, element, attrs) {
                // Get a reference to the video object
                scope.videoObj = element.find('video')[0];

                // set default stretch value
                if (!attrs.stretch) {
                    scope.setStretch('fill');
                }
                // Check for video attributes
                if (!(attrs.autoplay === undefined)) {
                    scope.videoObj.autoplay = true;
                }
                if (!(attrs.preload === undefined)) {
                    scope.videoObj.preload = attrs.preload;
                }
                if (!(attrs.poster === undefined)) {
                    scope.videoObj.poster = attrs.poster;
                }
                if (!(attrs.controls === undefined)) {
                    scope.videoObj.controls = true;
                }
                if (!(attrs.loop === undefined)) {
                    scope.videoObj.loop = true;
                }
                if (!(attrs.muted === undefined)) {
                    scope.videoObj.muted = true;
                }

                scope.$on('$destroy', function() {
                    scope.destroy();
                });
                element.on('$destroy', function() {
                    scope.destroy();
                });

                scope.$watch('stretch', function(newVal) {
                    if (!newVal) {
                        return;
                    }
                    scope.setStretch(newVal);
                });

                scope.$watch('source', function(newVal) {
                    if (!newVal) {
                        return;
                    }
                    return scope.setSource(newVal);
                });

                // Construct the api interface
                scope.api = {
                    setSource: scope.setSource,
                    allowReuse: scope.allowReuse,
                    getSource: scope.getSource,
                    isLoading: scope.isLoading,
                    setStretch: scope.setStretch,
                    destroy: scope.destroy,
                    play: scope.play,
                    pause: scope.pause,
                    unload: scope.unload,
                    setVolume: scope.setVolume,
                    mute: scope.setMute,
                    lostLayer: scope.lostLayer
                };

                scope.init();

                if (scope.onApiReady) {
                    scope.onApiReady({api: scope.api});
                }

                // Regiser the video layer
                halVideoLayerManager.registerVideoLayer(scope.api);
            },

            controller: ['$scope', '$element', '$q', '$timeout', 'halPlatformService', 'halVideoLayerManager', 'logService', function($scope, $element, $q, $timeout, halPlatformService, halVideoLayerManager, logService) {
                var _isLoading;
                var srcFile = '';
                var pluginId = '';
                var loaded = false;
                var loadLayoutTimer = null;
                var samsungPlugin = {};
                var mediauuid = uuid.v1();
                var playInitiated;
                var previousPos;
                var setPositionHandler;
                var decoder;
                var totalDuration;
                var _rotation;
                var loadDate;
                var initPluginDate;
                var endDate;

                var SEF_EVENT_TYPE = {
                    CONNECTION_FAILED: 1,
                    AUTHENTICATION_FAILED: 2,
                    STREAM_NOT_FOUND: 3,
                    NETWORK_DISCONNECTED: 4,
                    NETWORK_SLOW: 5,
                    RENDER_ERROR: 6,
                    RENDERING_START: 7,
                    RENDERING_COMPLETE: 8,
                    STREAM_INFO_READY: 9,
                    DECODING_COMPLETE: 10,
                    BUFFERING_START: 11,
                    BUFFERING_COMPLETE: 12,
                    BUFFERING_PROGRESS: 13,
                    CURRENT_DISPLAY_TIME: 14,
                    AD_START: 15,
                    AD_END: 16,
                    RESOLUTION_CHANGED: 17,
                    BITRATE_CHANGED: 18,
                    SUBTITLE: 19,
                    CUSTOM: 20
                };


                $scope.isLoading = function() {
                    return _isLoading;
                };

                function initSamsungPlugin() {
                    var promise = $q.defer();
                    initPluginDate = new Date();
                    decoder = halPlatformService.getFeatureSupport('media.current.decoder');
                    var d = new Date();
                    _rotation = 0;
                    pluginId = 'plugin-samsung-video-' + decoder;
                    $scope.videoObj.id = 'vid-' + pluginId;
                    samsungPlugin.sef = document.getElementById(pluginId);
                    if (samsungPlugin.sef) {
                        var res = samsungPlugin.sef.Open('Player', '1.0', 'Player');
                        samsungPlugin.sef.OnEvent = function(event, data1, data2) {
                            onSamsungEvent(event, data1, data2);
                        };
                    }
                    logService.debug('appspace.core.media', 'ssspv - Plugin init');
                    promise.resolve();
                    return promise.promise;
                }

                function onLoaddata() {
                    if (srcFile.length == 0) {
                        return;
                    }

                    if (samsungPlugin && samsungPlugin.sef) {
                        totalDuration = samsungPlugin.sef.Execute('GetDuration');
                    }

                    loaded = true;
                    if (_isLoading) {
                        var cTime = new Date();
                        logService.debug('appspace.core.media', 'ssspv - Video loaded and ready (by data loaded): ' + pluginId + ' ' + (cTime.getTime() - loadDate.getTime()) + 'ms since load initiated');
                        // Video is loaded and can be played
                        if ($scope.onVideoLoad) {
                            $scope.onVideoLoad({sender: $scope, msg: 'Load success: ' + pluginId});
                            $scope.$apply();
                        }
                        _isLoading = false;
                    }
                }
                function onError(e) {
                    if (srcFile.length == 0) {
                        return;
                    }
                    var cTime = new Date();
                    logService.error('appspace.core.media', 'ssspv - Video error: ' + pluginId + ' ' + (cTime.getTime() - loadDate.getTime()) + 'ms since load initiated', e);
                    // Error when loading video
                    if ($scope.onVideoLoadError) {
                        $scope.onVideoLoadError({sender: $scope, msg: 'Load error: ' + pluginId});
                        $scope.$apply();
                    }
                }
                function onEnded() {
                    if (srcFile.length == 0) {
                        return;
                    }
                    endDate = new Date();
                    logService.debug('appspace.core.media', 'ssspv - Video complete: ' + pluginId);
                    // Video has competed
                    if ($scope.onVideoComplete) {
                        $scope.onVideoComplete({sender: $scope, msg: 'Video Complete: ' + pluginId});
                        $scope.$apply();
                    }
                }


                function onTimeupdate(currentTime) {
                    if (_isLoading) {
                        var cTime = new Date();
                        logService.debug('appspace.core.media', 'ssspv - Video loaded and ready (by time update): ' + pluginId + ' ' + (cTime.getTime() - loadDate.getTime()) + 'ms since load initiated');
                        // Video is loaded and can be played
                        if ($scope.onVideoLoad) {
                            $scope.onVideoLoad({sender: $scope, msg: 'Load success: ' + pluginId});
                            $scope.$apply();
                        }
                        _isLoading = false;
                    }
                    // Video progress
                    if ($scope.onPlaybackProgress && totalDuration && currentTime) {
                        $scope.onPlaybackProgress({
                            sender: $scope, progress: {
                                position: currentTime / 1000, duration: totalDuration / 1000
                            }
                        });
                        $scope.$apply();
                    }
                }


                $scope.init = function() {
                    playInitiated = false;
                };


                function resetSamsungPlugin() {
                    samsungPlugin = {};
                }

                $scope.destroy = function() {
                    _isLoading = false;

                    if (loadLayoutTimer) {
                        $timeout.cancel(loadLayoutTimer);
                        loadLayoutTimer = null;
                    }

                    if ($scope.videoObj) {
                        $scope.unload();
                        $scope.videoObj.remove();
                        $scope.videoObj = null;
                        closePlugin();
                        var cTime = new Date();
                        var message = '';
                        if (endDate) {
                            message = ' ' + (cTime.getTime() - endDate.getTime()) + 'ms since video completed';
                        }
                        logService.debug('appspace.core.media', 'ssspv - Video destroy: ' + pluginId + message);
                    }
                    pluginId = undefined;

                    // Unregister this layer
                    halVideoLayerManager.unregisterVideoLayer($scope.api);

                    $element.empty(); // Parent of the video element (optional);
                };

                function closePlugin() {
                    if (samsungPlugin.sef) {
                        $timeout.cancel(setPositionHandler);
                        logService.debug('appspace.core.media', 'ssspv - Plugin closed: ' + pluginId);
                        samsungPlugin.sef.OnEvent = null;
                        samsungPlugin.sef.Close();
                        samsungPlugin.sef = null;
                    }
                }

                $scope.lostLayer = function(src) {
                    logService.debug('appspace.core.media', 'ssspv - Video lost layer: ' + pluginId);
                    var wasLoaded = loaded;
                    // Destroy
                    $scope.destroy();
                    // Send lost playback in 2 secs
                    loadLayoutTimer = $timeout(function() {
                        if (wasLoaded) {
                            if ($scope.onVideoPlaybackError) {
                                $scope.onVideoPlaybackError({sender: $scope, msg: 'playback error: ' + pluginId});
                            }
                        } else {
                            if ($scope.onVideoLoadError) {
                                $scope.onVideoLoadError({sender: $scope, msg: 'playback error: ' + pluginId});
                            }
                        }
                    }, 2000);

                    return;
                };

                $scope.getSource = function(src) {
                    return srcFile;
                };

                $scope.allowReuse = function() {
                    return false;
                };

                $scope.setSource = function(src) {
                    _isLoading = true;
                    initSamsungPlugin().then(function() {
                        if (!samsungPlugin.sef) {
                            return;
                        }

                        loaded = false;
                        srcFile = src;
                        var rendererType = 0;
                        if (src.indexOf('.m3u8') > -1) {
                            // http://www.samsungdforum.com/B2B/Guide/ref50007/index.html
                            src += '|COMPONENT=HLS';
                            // rendererType = 4;
                        }

                        samsungPlugin.sef.Execute('InitPlayer', src);
                        samsungPlugin.sef.Execute('SetInitialBufferSize', 2000 * 1024);
                        // set rotation
                        var displayRes = halMediaService.getDisplayResolution();
                        if (displayRes.width > displayRes.height) {
                            _rotation = 0;
                        } else {
                            _rotation = 270;
                        }
                        // decoder type hardcoded to 4 (main) as 5 (secondary) is buggy
                        var decoderType = '4';
                        samsungPlugin.sef.Execute('SetDecoderType', '3', decoderType);
                        samsungPlugin.sef.Execute('SetRendererType', '3', rendererType);
                        logService.debug('appspace.core.media', 'ssspv - Init samsung plugin: ' + pluginId + '(decoder type:' + decoderType + ';renderer type:' + rendererType + ';rotation:' + _rotation + ')');
                        logService.debug('appspace.core.media', 'ssspv - Set Source:' + src);
                        setPluginPosition({
                            x: 0, y: 0, width: 10, height: 10
                        });
                        samsungPlugin.sef.Execute('SetRotation', _rotation);
                        samsungPlugin.sef.Execute('StartPlayback');
                        loadDate = new Date();
                    });
                };

                function onSamsungEvent(event, data1, data2) {
                    var errorMessage;
                    switch (event) {
                        case SEF_EVENT_TYPE.STREAM_INFO_READY:
                            logService.debug('appspace.core.media', 'Samsung plugin event - STREAM INFO READY: ' + pluginId);
                            onLoaddata();
                            break;

                        case SEF_EVENT_TYPE.DECODING_COMPLETE:
                            logService.debug('appspace.core.media', 'Samsung plugin event - DECODING COMPLETE: ' + pluginId);
                            break;

                        case SEF_EVENT_TYPE.BUFFERING_COMPLETE:
                            logService.debug('appspace.core.media', 'Samsung plugin event - BUFFERING COMPLETE: ' + pluginId);
                            break;

                        case SEF_EVENT_TYPE.CURRENT_DISPLAY_TIME:
                            // logService.debug("appspace.core.media", "Samsung plugin event - CURRENT_DISPLAY_TIME: " + pluginId);
                            onTimeupdate(data1);
                            break;

                        case SEF_EVENT_TYPE.RENDERING_COMPLETE:
                            logService.debug('appspace.core.media', 'Samsung plugin event - RENDERING COMPLETE: ' + pluginId);
                            onEnded();
                            break;

                        case SEF_EVENT_TYPE.NETWORK_DISCONNECTED:
                            errorMessage = 'Samsung plugin event - NETWORK DISCONNECTED';
                            break;

                        case SEF_EVENT_TYPE.CONNECTION_FAILED:
                            errorMessage = 'Samsung plugin event - CONNECTION FAILED';
                            break;

                        case SEF_EVENT_TYPE.STREAM_NOT_FOUND:
                            errorMessage = 'Samsung plugin event - STREAM NOT FOUND';
                            break;

                        case SEF_EVENT_TYPE.NETWORK_SLOW:
                            errorMessage = 'Samsung plugin event - NETWORK SLOW';
                            break;
                    }

                    if (errorMessage) {
                        onError(errorMessage);
                    }
                }


                $scope.play = function() {
                    if (!samsungPlugin.sef) {
return;
}
                    samsungPlugin.sef.Execute('Resume');
                    logService.debug('appspace.core.media', 'ssspv - Video Resume: ' + pluginId);
                    startSetRealPosition();
                };

                $scope.pause = function() {
                    if (!samsungPlugin.sef) {
return;
}
                    // samsungPlugin.sef.Execute('Pause');
                    // Use stop instead of pause. "pause" does not work on the samsungplugin.sef until further information is gathered.
                    samsungPlugin.sef.Execute('Stop');
                    logService.debug('appspace.core.media', 'ssspv - Video pause: ' + pluginId);
                };

                function startSetRealPosition() {
                    TimeoutUtil.cancel(setPositionHandler);

                    setPositionHandler = $timeout(function() {
                        setRealPosition().then(startSetRealPosition);
                    }, 100, false);
                }

                function getLandscapePluginPosition() {
                    var mediaPos = {x: 0, y: 0, width: 10, height: 10};
                    // getting the screen max width and height
                    var topBody = document.body;
                    var topBodyRectVal = topBody.getBoundingClientRect();
                    var maxWidth = topBodyRectVal.width;
                    var maxHeight = screen.height;

                    // these are the maximum value of the width and height that
                    // samsung plugin can accept.
                    var samsungMaxWidth = 960;
                    var samsungMaxHeight = 540;

                    // get the ratio against samsung max width and height
                    var widthRatio = maxWidth / samsungMaxWidth;
                    var heightRatio = maxHeight / samsungMaxHeight;

                    // get the location of samsung-cutout
                    var videoHolder = document.getElementById('vid-' + pluginId).parentNode;
                    var rectval = videoHolder.getBoundingClientRect();

                    // if (rectval) {
                    //     mediaPos.x = rectval.left / widthRatio;
                    //     mediaPos.y = rectval.top / heightRatio;
                    //     mediaPos.xRight = rectval.right / widthRatio;
                    //     mediaPos.yBottom = rectval.bottom / heightRatio;

                    //     if (mediaPos.x < 0) {
                    //         rectval.width -= Math.abs(rectval.left);
                    //         mediaPos.x = 0;
                    //     }
                    //     else if (mediaPos.xRight > samsungMaxWidth) {
                    //         rectval.width = rectval.width - (rectval.right - maxWidth);
                    //     }

                    //     mediaPos.width = rectval.width / widthRatio;


                    //     if (mediaPos.y < 0) {
                    //         rectval.height -= Math.abs(rectval.top);
                    //         mediaPos.y = 0;
                    //     }
                    //     else if (mediaPos.yBottom > samsungMaxHeight) {
                    //         rectval.height = rectval.height - (rectval.bottom - maxHeight);
                    //     }
                    //     mediaPos.height = rectval.height / heightRatio;

                    // }
                    if (rectval) {
                        mediaPos.width = rectval.width;
                        mediaPos.height = rectval.height;
                        mediaPos.x = rectval.left;
                        mediaPos.y = rectval.top;
                    }
                    mediaPos.baseRes = 1080;
                    return mediaPos;
                }

                function getPortraitPluginPosition() {
                    var mediaPos = {x: 0, y: 0, width: 10, height: 10};
                    // getting the screen max width and height
                    var topBody = document.body;
                    var topBodyRectVal = topBody.getBoundingClientRect();
                    var maxWidth = topBodyRectVal.width;
                    var maxHeight = screen.height;
                    // NOTE : plugin still sees the screen as landscape. so we need to invert everything
                    var videoHolder = document.getElementById('vid-' + pluginId).parentNode;
                    var rectval = videoHolder.getBoundingClientRect();
                    if (rectval) {
                        mediaPos.width = rectval.height;
                        mediaPos.height = rectval.width;
                        mediaPos.x = rectval.top;
                        mediaPos.y = maxWidth - rectval.left - rectval.width;
                    }
                    mediaPos.baseRes = 1080;
                    return mediaPos;
                }

                function getCutoutPositionForPortraitContainer() {
                    var videoHolder = document.getElementById('vid-' + pluginId).parentNode;
                    var rectval = videoHolder.getBoundingClientRect();


                    var targetWidth = (rectval.width * 6) + 'px';
                    var targetHeight = (rectval.height * 2) + 'px';
                    var targetLeft = (-1 * (rectval.width / 2)) + 'px';// move it up to avoid the bar
                    var targetTop = (-1 * (rectval.height / 2)) + 'px';// move it up to avoid the bar


                    return {width: targetWidth, height: targetHeight, left: targetLeft, top: targetTop};
                }

                function getCutoutPositionForLandscapeContainer() {
                    var videoHolder = document.getElementById('vid-' + pluginId).parentNode;
                    var rectval = videoHolder.getBoundingClientRect();


                    var targetWidth = (rectval.width * 2) + 'px';
                    var targetHeight = (rectval.height * 2) + 'px';
                    var targetLeft = (-1 * (rectval.width / 2)) + 'px';// move it up to avoid the bar
                    var targetTop = (-1 * (rectval.height / 2)) + 'px';// move it up to avoid the bar


                    return {width: targetWidth, height: targetHeight, left: targetLeft, top: targetTop};
                }

                function getCutoutPositionForSquareContainer() {
                    var videoHolder = document.getElementById('vid-' + pluginId).parentNode;
                    var rectval = videoHolder.getBoundingClientRect();


                    var targetWidth = (rectval.width * 2) + 'px';
                    var targetHeight = (rectval.height * 2) + 'px';
                    var targetLeft = 0 + 'px'; // always at the left most of the container
                    var targetTop = (-1 * (rectval.height / 2)) + 'px';// move it up to avoid the bar


                    return {width: targetWidth, height: targetHeight, left: targetLeft, top: targetTop};
                }

                function setCutoutPosition() {
                    var videoHolder = document.getElementById('vid-' + pluginId).parentNode;
                    var rectval = videoHolder.getBoundingClientRect();
                    var cutoutPos = {};
                    if (rectval.width > rectval.height) {// landscape container
                        cutoutPos = getCutoutPositionForLandscapeContainer();
                    } else if (rectval.width == rectval.height) {// square container
                        cutoutPos = getCutoutPositionForSquareContainer();
                    } else {// portrait container
                        cutoutPos = getCutoutPositionForPortraitContainer();
                    }

                    var video = document.getElementById('vid-' + pluginId);
                    var rectval2 = {
                        left: video.style.left,
                        top: video.style.top,
                        width: video.style.width,
                        height: video.style.height
                    };

                    if (rectval2.width == cutoutPos.width && rectval2.height == cutoutPos.height && rectval2.left == cutoutPos.left && rectval2.top == cutoutPos.top) {
                        return;
                    }

                    $('#vid-' + pluginId).css({
                        'width': cutoutPos.width,
                        'height': cutoutPos.height,
                        'left': cutoutPos.left,
                        'top': cutoutPos.top
                    });
                }

                function setRealPosition() {
                    var promise = $q.defer();
                    var mediaPos = (_rotation == 0) ? getLandscapePluginPosition() : getPortraitPluginPosition();
                    setPluginPosition(mediaPos);
                    setCutoutPosition();
                    promise.resolve();
                    return promise.promise;
                }

                function setPluginPosition(pos) {
                    if (!pos.width) {
                        pos.width = 10;
                    }
                    if (!pos.height) {
                        pos.height = 10;
                    }

                    if (previousPos && previousPos.x == pos.x && previousPos.y == pos.y && previousPos.width == pos.width && previousPos.height == pos.height) {
                        return;
                    }

                    previousPos = pos;
                    var res;
                    // http://www.samsungdforum.com/B2B/Guide/tut20009/index.html
                    res = samsungPlugin.sef.Execute('SetDisplayArea', pos.x, pos.y, pos.width, pos.height, pos.baseRes);
                    logService.debug('appspace.core.media', 'ssspv - Setting display area x:' + pos.x + ' y:' + pos.y + ' width:' + pos.width + ' height:' + pos.height + '   :' + pluginId);
                }


                $scope.setVolume = function(volume) {
                };

                $scope.setMute = function(shouldMute) {
                };

                $scope.unload = function() {
                    if (!pluginId) {
                        logService.debug('appspace.core.media', 'ssspv - Video already unload');
                        return;
                    }
                    loaded = false;
                    logService.debug('appspace.core.media', 'ssspv - Video unload: ' + pluginId);

                    if (samsungPlugin.sef) {
                        samsungPlugin.sef.Execute('Stop');
                    }

                    // Unregister this layer
                    halVideoLayerManager.unregisterVideoLayer($scope.api);

                    srcFile = '';
                    _isLoading = false;
                };

                $scope.setStretch = function(stretch) {
                };
            }]
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('media');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('asMediaText', ["dataUtil", function(dataUtil) {
        return {
            restrict: 'EA',
            scope: {
                text: '@', // the text to display
                fillHeight: '@', // if true, height is 100%. if not, no height
                useNonBreakingSpace: '@', // if true, empty space will be replace with &nbsp;
                encoded: '@', // if true, encoding to HTML is not required anymore
                onApiReady: '&',
            },
            // template explaination
            // use white-space : pre-wrap to support multiple spaces in html element & auto wrap
            template: '<div ng-attr-style="{{whiteSpaceStyling}}width:100%;{{heightStyle}}" ng-bind-html="textToDisplay"></div>',

            link: function(scope, element, attrs) {
                scope.fillHeight = !attrs.fillHeight ? 'true' : scope.fillHeight;

                scope.useNonBreakingSpace = !attrs.useNonBreakingSpace ? 'false' : scope.useNonBreakingSpace;

                scope.encoded = attrs.encoded === 'true';

                scope.$on('$destroy', function() {
                    scope.destroy();
                });

                element.on('$destroy', function() {
                    scope.destroy();
                });

                scope.$watch('fillHeight', function(newVal) {
                    if (!newVal) {
                        return;
                    }
                    scope.setFillHeight(newVal);
                });

                scope.$watch('text', function(newVal) {
                    if (dataUtil.isNullOrUndefined(newVal)) {
                        return;
                    }
                    scope.setText(newVal);
                });

                scope.api = {
                    setText: scope.setText,
                    destroy: scope.destroy
                };

                if (scope.onApiReady) {
                    scope.onApiReady({api: scope.api});
                }
            },

            controller: ["$scope", "$element", "$sce", function($scope, $element, $sce) {
                $scope.destroy = function() {
                    $scope.textToDisplay = undefined;
                    $scope.whiteSpaceStyling = undefined;
                    $scope.encoded = undefined;
                    $scope.heightStyle = undefined;
                    $scope.api = undefined;
                    if (!$element) {
                        return;
                    }
                    $element.empty();
                    $element = undefined;
                };

                $scope.setFillHeight = function(newVal) {
                    $scope.heightStyle = (newVal || '').toLowerCase() != 'false' ? 'height:100%' : '';
                };

                $scope.setText = function(text) {
                    if (!$scope.encoded) {
                        // replace special characters
                        text = dataUtil.encodeHtml(text);
                    }
                    $scope.whiteSpaceStyling = $scope.useNonBreakingSpace.toLowerCase() != 'false' ?
                        'white-space:pre-wrap;' :
                        'white-space:pre;';
                    $scope.textToDisplay = $sce.trustAsHtml(text);
                };
            }]
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('media');
    app.directive('asTizenVideo', ["halVideoLayerManager", function(halVideoLayerManager) {
        return {
            restrict: 'EA',
            scope: {
                source: '@',
                stretch: '@',
                onApiReady: '&',
                onVideoLoad: '&',
                onVideoComplete: '&',
                onVideoLoadError: '&',
                onVideoPlaybackError: '&',
            },
            template: function(element, attrs) {
                return '<object id="avPlayer" type="application/avplayer" style="width: 100%; height: 100%"></object>';
            },
            link: function(scope, element, attrs) {
                scope.$on('$destroy', scope.destroy);
                element.on('$destroy', scope.destroy);

                scope.$watch('source', function(newVal) {
                    if (!newVal) {
                        return;
                    }
                    return scope.setSource(newVal);
                });

                scope.api = {
                    setSource: scope.setSource,
                    allowReuse: scope.allowReuse,
                    getSource: scope.getSource,
                    isLoading: scope.isLoading,
                    seek: scope.seek,
                    destroy: scope.destroy,
                    getPosition: scope.getPosition,
                    play: scope.play,
                    pause: scope.pause,
                    unload: scope.unload,
                    lostLayer: scope.lostLayer,
                    mediaDirective: 'as-tizen-video'
                };

                scope.init();

                if (scope.onApiReady) {
                    scope.onApiReady({api: scope.api});
                }

                halVideoLayerManager.registerVideoLayer(scope.api);
            },
            controller: ["$scope", "$window", "$element", "$timeout", "$q", "logService", "$interval", function($scope, $window, $element, $timeout, $q, logService, $interval) {
                var LOG_KEY = 'appspace.core.media';
                var debugName;
                var positioningListener;
                var POSITIONING_LISTENER_INTERVAL_MILLISECONDS = 500;
                var currentPosition = {
                    left: 0,
                    top: 0,
                    width: 0,
                    height: 0
                }
                var sourceFile;
                var currentPlayTime = 0;
                var isLoaded = false;
                var isLoading = false;
                var playerReadyDefer = $q.defer();
                var player;
                var listener = {
                    oncurrentplaytime: function(currentTime) {
                        currentPlayTime = currentTime;
                    },
                    onerror: raiseVideoLoadError,
                    onerrormsg: raiseVideoLoadError,
                    onstreamcompleted: function() {
                        if (!$scope.onVideoComplete) {
                            return;
                        }

                        $timeout(function() {
                            $scope.onVideoComplete({sender: $scope, msg: 'Video Complete: ' + debugName});
                        }, 0);
                    }
                };

                $scope.init = function() {
                    if (!$window.webapis || !$window.webapis.avplay) {
                        logService.error(LOG_KEY, 'Missing Tizen APIs to play video.');
                        return;
                    }

                    player = $window.webapis.avplay;
                    positioningListener = $interval(checkVideoSize, POSITIONING_LISTENER_INTERVAL_MILLISECONDS);
                };

                function checkVideoSize() {
                    playerReadyDefer.promise.then(function() {
                        try {
							var avPlayer = $element.find('#avPlayer');
                            var offset = avPlayer.offset();
                            var innerWidth = $element.innerWidth();
                            var innerHeight = $element.innerHeight();
                            if (currentPosition.left === offset.left && currentPosition.top === offset.top && currentPosition.width === innerWidth && currentPosition.height === innerHeight) {
                                return;
                            }

                            currentPosition.left = offset.left;
                            currentPosition.top = offset.top;
                            currentPosition.width = innerWidth;
                            currentPosition.height = innerHeight;
                            player.setDisplayRect(currentPosition.left, currentPosition.top, currentPosition.width, currentPosition.height);
                        } catch (error) {
                            logService.error(LOG_KEY, 'Error while resizing video. Error source: ' + JSON.stringify(error));
                        }
                    });
                };
                
                $scope.destroy = function() {
                    $interval.cancel(positioningListener);
                    $scope.unload();
                };

                $scope.setSource = function(url) {
                    debugName = sourceFile = url;
                    var onError = function(error) {
                        logService.error(LOG_KEY, 'Error while preparing URL: ' + url + '. Error source: ' + JSON.stringify(error));
                        raiseVideoLoadError();
                    }

                    try
                    {
                        player.open(url);
                        currentPlayTime = 0;
                        player.setListener(listener);
                        isLoading = true;
                        player.prepareAsync(function() {
                            playerReadyDefer.resolve();
                            raiseVideoLoad();
                        }, onError);
                    }
                    catch (error)
                    {
                        onError(error);
                    }
                };

                function raiseVideoLoadError() {
                    if (!$scope.onVideoLoadError) {
                        return;
                    }

                    $timeout(function() {
                        $scope.onVideoLoadError({sender: $scope, msg: 'playback error: ' + debugName});
                    }, 0);
                }

                $scope.allowReuse = function() {
                    return true;
                }

                $scope.getSource = function() {
                    return sourceFile;
                }

                $scope.isLoading = function() {
                    return isLoading;
                }

                $scope.seek = function(currentTime) {
                    var onError = function(error) {
                        logService.error(LOG_KEY, 'Error while skipping playback to a specific timestamp. Error source: ' + JSON.stringify(error));
                        raiseVideoPlaybackError();
                    }

                    try
                    {
                        player.seekTo(currentTime * 1000, function() {
                        }, onError);
                    }
                    catch (error)
                    {
                        onError(error);
                    }
                }

                function raiseVideoPlaybackError() {
                    if (!$scope.onVideoPlaybackError) {
                        return;
                    }

                    $scope.onVideoPlaybackError({sender: $scope, msg: 'playback error: ' + debugName});
                }

                function raiseVideoLoad() {
                    isLoading = false;
                    isLoaded = true;
                    if (!$scope.onVideoLoad) {
                        return;
                    }

                    $scope.$evalAsync(function() {
                        $scope.onVideoLoad({sender: $scope, msg: 'Load success: ' + debugName});
                    });
                }

                $scope.getPosition = function() {
                    return currentPlayTime / 1000;
                }

                $scope.play = function() {
                    playerReadyDefer.promise.then(function() {
                        try
                        {
                            player.play();
                        }
                        catch (error)
                        {
                            logService.error(LOG_KEY, 'Error while playing the video. Error source: ' + JSON.stringify(error));
                            raiseVideoPlaybackError();
                        }
                    });
                }

                $scope.pause = function() {
                    try
                    {
                        player.pause();
                    }
                    catch (error)
                    {
                        logService.error(LOG_KEY, 'Error while pausing the video. Error source: ' + JSON.stringify(error));
                    }
                };

                $scope.unload = function() {
                    try
                    {
                        player.close();
                    }
                    catch (error)
                    {
                        logService.error(LOG_KEY, 'Error while closing the video. Error source: ' + JSON.stringify(error));
                    }
                };

                $scope.lostLayer = function() {
                    logService.debug(LOG_KEY, 'Tizen video lost layer: ' + debugName);
                    $scope.unload();
                    $timeout(function() {
                        if (isLoaded) {
                            raiseVideoPlaybackError();
                            return;
                        } 
                        
                        raiseVideoLoadError();
                    }, 2000);
                };
            }]
        }
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('media');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('asMediaVideo', ["halMediaService", "halVideoLayerManager", "halPlatformService", "FileUtil", "mediaUtil", function(halMediaService, halVideoLayerManager, halPlatformService, FileUtil, mediaUtil) {
        return {
            restrict: 'EA',
            scope: {
                source: '@', // source(s) of the video
                stretch: '@', // determines how to stretch the content based on its aspect ratio
                allowPlayThrough: '<',
                onApiReady: '&',
                options: '=',
                onVideoLoad: '&',
                onVideoComplete: '&',
                onVideoLoadError: '&',
                onVideoPlaybackError: '&',
                onPlaybackProgress: '&'
            },
            template: function(element, attrs) {
                // Extract the directive attributes and pass to hal get media directive
                var mediaDirective = halMediaService.getMediaDirective('video',
                    mediaUtil.convertAttributesToOptions(attrs));
                element.mediaDirective = mediaDirective.directive;
                var styles = mediaDirective.styles || 'width:100%; height:100%;';
                var tmp = '<' + mediaDirective.directive + ' ' + (mediaDirective.attributes || '') + ' style="' + styles + '"></' + mediaDirective.directive + '>';
                return tmp;
            },

            link: function(scope, element, attrs) {
                var elementType = element.mediaDirective || 'video';

                // Get a reference to the video object
                scope.videoObj = element.find(elementType)[0];
                scope.videoJq = element.find(elementType);
                scope.hasBackground = elementType == 'video' && halPlatformService.getFeatureSupport('media.video.createbackground') == 'yes';

                // set default stretch value
                if (!attrs.stretch) {
                    scope.setStretch('fill');
                }
                // Check for video attributes
                if (!(attrs.autoplay === undefined)) {
                    scope.videoObj.autoplay = true;
                }
                if (!(attrs.preload === undefined)) {
                    scope.videoObj.preload = attrs.preload;
                }
                if (!(attrs.poster === undefined)) {
                    scope.videoObj.poster = attrs.poster;
                }
                if (!(attrs.controls === undefined)) {
                    scope.videoObj.controls = true;
                }
                if (!(attrs.loop === undefined)) {
                    scope.videoObj.loop = true;
                }
                if (!(attrs.muted === undefined)) {
                    scope.videoObj.muted = attrs.muted;
                }
                if (!(attrs.volume === undefined)) {
                    scope.videoObj.volume = attrs.volume;
                }
                if (attrs.playsinline != 'false') {
                    scope.videoObj.setAttribute('playsinline', 'playsinline');
                    scope.videoObj.setAttribute('webkit-playsinline', 'webkit-playsinline');
                }

                scope.$on('$destroy', function() {
                    scope.destroy();
                });
                element.on('$destroy', function() {
                    scope.destroy();
                });

                scope.$watch('stretch', function(newVal) {
                    if (!newVal) {
                        return;
                    }
                    scope.setStretch(newVal);
                });

                scope.$watch('source', function(newVal) {
                    if (!newVal) {
                        return;
                    }
                    return scope.setSource(newVal);
                });

                // Construct the api interface
                scope.api = {
                    setSource: scope.setSource,
                    allowReuse: scope.allowReuse,
                    getSource: scope.getSource,
                    isLoading: scope.isLoading,
                    seek: scope.seek,
                    getPosition: scope.getPosition,
                    setStretch: scope.setStretch,
                    destroy: scope.destroy,
                    play: scope.play,
                    pause: scope.pause,
                    unload: scope.unload,
                    setVolume: scope.setVolume,
                    mute: scope.setMute,
                    lostLayer: scope.lostLayer,
                    mediaDirective: elementType
                };

                scope.init();

                if (scope.onApiReady) {
                    scope.onApiReady({api: scope.api});
                }

                // Regiser the video layer
                halVideoLayerManager.registerVideoLayer(scope.api);
            },

            controller: ["$scope", "$element", "$timeout", "$attrs", "halPlatformService", "halVideoLayerManager", "logService", "interactivityManager", "VIDEO_STRETCH_MODE", function(
                $scope,
                $element,
                $timeout,
                $attrs,
                halPlatformService,
                halVideoLayerManager,
                logService,
                interactivityManager,
                VIDEO_STRETCH_MODE
            ) {
                var VIDEO_EVENT_CODE = {
                    LOADED_METADATA: 'loadedmetadata',
                    LOADED_DATA: 'loadeddata',
                    ERROR: 'error',
                    ENDED: 'ended',
                    TIME_UPDATE: 'timeupdate',
                    CAN_PLAY_THROUGH: 'canplaythrough'
                };
                var isLoading = false;
                var videoJs = null;
                var currentStretch = null;
                var srcFile = '';
                var ended = false;
                var debugName = '';
                var loaded = false;
                var metadataLoaded = false;
                var loadLayoutTimer = null;
                var isHls = null;
                var currTime = 0;
                var checkFreezeInterval = null;
                var containerWidth = 0;
                var containerHeight = 0;
                var freezeCounter = 0;
                var prevTimer = 0;
                var hasVideoDimensionError = false;
                var hasError = false;
                var videoElementBGColor = halPlatformService.getFeatureSupport('media.video.element.backgroundcolor');
                var logKey = 'appspace.core.media';
                var mediaDirective = halMediaService.getMediaDirective('video',
                    mediaUtil.convertAttributesToOptions($attrs));
                var platformSupportVideoLoaded = (mediaDirective && mediaDirective.options && mediaDirective.options.useVideoLoad) ||
                    halPlatformService.getFeatureSupport('support.video.onload.event') != 'no';
                var forceMute = false;
                var platformSupportUseLoad = halPlatformService.getFeatureSupport('media.video.use.load');
                var removeZIndexOnPlay = halPlatformService.getFeatureSupport('media.video.removezindexonplay');
                var requirePauseOnLoad = halPlatformService.getFeatureSupport('media.video.requirepauseonload');
                $scope.allowPlayThrough = Boolean($scope.allowPlayThrough);
                $scope.isLoading = function() {
                    return isLoading;
                };

                function onLoadmetadata() {
                    metadataLoaded = true;
                    // this is to handle video stretch for browser that do not support object-fit, i.e. uwp
                    tryCssTransform(true);
                }

                function raiseVideoLoad() {
                    loaded = true;
                    if (!$scope.onVideoLoad) {
                        return;
                    }
                    $scope.$evalAsync(function() {
                        $scope.onVideoLoad({sender: $scope, msg: 'Load success: ' + debugName});
                    });
                }

                function onLoaddata() {
                    if (srcFile.length == 0) {
                        logService.debug(logKey, 'Ignoring load event as source is empty');
                        return;
                    }

                    if (!platformSupportVideoLoaded) {
                        logService.debug(logKey, 'Platform doesn\'t support video load event. Waiting for time update.');
                        return;
                    }

                    metadataLoaded = true;
                    if (isLoading) {
                        logService.debug(logKey, 'Video loaded and ready (by data loaded): ' + debugName);
                        // Video is loaded and can be played
                        raiseVideoLoad();
                        isLoading = false;
                    }

                    resetBackground();
                    // this is to handle video stretch for browser that do not support object-fit, i.e. uwp
                    tryCssTransform();
                }

                function onError(e) {
                    hasError = true;
                    if (srcFile.length == 0) {
                        return;
                    }
                    logService.error(logKey, 'Video error: ' + debugName, e);

                    var event;
                    var message;
                    // if we have loaded before, use video playback error
                    if (loaded) {
                        event = $scope.onVideoPlaybackError;
                        message = {sender: $scope, msg: 'Playback error: ' + debugName};
                    } else {
                        // if we haven't load before, use video load error
                        event = $scope.onVideoLoadError;
                        message = {sender: $scope, msg: 'Load error: ' + debugName};
                    }

                    // check if function is available
                    if (event) {
                        $timeout(function() {
                            event(message);
                        }, 0);
                    }
                }
                function onEnded() {
                    if (srcFile.length == 0 || ended) {
                        return;
                    }

                    ended = true;
                    logService.debug(logKey, 'Video complete: ' + debugName);
                    if ($scope.videoObj && $scope.hasBackground) {
                        if ($scope.videoObj.style.objectFit == 'contain') {
                            $scope.videoObj.style.backgroundColor = videoElementBGColor;
                            var canvas = document.createElement('canvas');
                            canvas.width = $scope.videoObj.videoWidth;
                            canvas.height = $scope.videoObj.videoHeight;
                            var ctx = canvas.getContext('2d');
                            ctx.fillStyle = 'black';
                            ctx.fillRect(0, 0, canvas.width, canvas.height);

                            $scope.videoObj.style.backgroundImage = 'url(' + canvas.toDataURL('image/png') + ')';
                            $scope.videoObj.style.backgroundSize = 'contain';
                            $scope.videoObj.style.backgroundRepeat = 'no-repeat';
                            $scope.videoObj.style.backgroundPosition = 'center';
                        } else {
                            $scope.videoObj.style.backgroundImage = 'none';
                            $scope.videoObj.style.backgroundColor = 'black';
                        }
                    } else {
                        resetBackground();
                    }

                    // Video has competed
                    if ($scope.onVideoComplete) {
                        $timeout(function() {
                            $scope.onVideoComplete({sender: $scope, msg: 'Video Complete: ' + debugName});
                        }, 0);
                    }
                }

                function resetBackground() {
                    if ($scope.videoObj) {
                        $scope.videoObj.style.backgroundImage = 'none';
                        $scope.videoObj.style.backgroundColor = videoElementBGColor;
                    }
                }

                function onTimeupdate() {
                    var canPlaybackProgress = true;

                    var validate = function(value) {
                        if (value !== null && !isNaN(value)) {
                            return true;
                        }

                        canPlaybackProgress = false;
                        return false;
                    };

                    var videoObj = $scope.videoObj;
                    if (validate(videoObj.currentTime)) {
                        currTime = videoObj.currentTime;
                    }
                    // if we haven't loaded but we start getting time update, send video load first
                    // if platform doesn't support video load event, only send video load event if current time is greater than 0
                    if (isLoading && validate(videoObj.duration) && (platformSupportVideoLoaded || !platformSupportVideoLoaded && currTime > 0)) {
                        logService.debug(logKey, 'Video loaded and ready (by Time update): ' + debugName);
                        // Video is loaded and can be played
                        isLoading = false;
                        if (requirePauseOnLoad) {
                            $scope.videoObj.pause();
                        }
                        raiseVideoLoad();
                    }

                    if (hasVideoDimensionError) {
                        return;
                    }

                    // If the video loads without any height and width, throw error. (For H265 cases)
                    if (videoObj.currentTime > 0 && FileUtil.getFileExtension(srcFile) === 'mp4' && !getVideoHeight() && !getVideoWidth()) {
                        onError('Fail to load the video due to invalid video width and height');
                        hasVideoDimensionError = true;
                        return;
                    }

                    // for testing only as this will produce a lot of logs
                    // give progress event if we have currenttime & total duration
                    if (canPlaybackProgress && $scope.onPlaybackProgress) {
                        $scope.onPlaybackProgress({
                            sender: $scope, progress: {
                                position: videoObj.currentTime, duration: videoObj.duration
                            }
                        });
                        $scope.$apply();
                    }

                    // perform css transform for browser that does not support object-fit when height or with changes, i.e. uwp
                    tryCssTransform();
                }

                $scope.init = function() {
                    $scope.videoObj.addEventListener(VIDEO_EVENT_CODE.LOADED_METADATA, onLoadmetadata, false);
                    $scope.videoObj.addEventListener(VIDEO_EVENT_CODE.LOADED_DATA, onLoaddata, false);
                    $scope.videoObj.addEventListener(VIDEO_EVENT_CODE.ERROR, onError, false);
                    $scope.videoObj.addEventListener(VIDEO_EVENT_CODE.ENDED, onEnded, false);
                    $scope.videoObj.addEventListener(VIDEO_EVENT_CODE.TIME_UPDATE, onTimeupdate, false);
                    if (!$scope.allowPlayThrough) {
                        return;
                    }

                    $scope.videoObj.addEventListener(VIDEO_EVENT_CODE.CAN_PLAY_THROUGH, $scope.videoObj.play, false);
                };

                $scope.destroy = function() {
                    isLoading = false;

                    if (loadLayoutTimer) {
                        $timeout.cancel(loadLayoutTimer);
                        loadLayoutTimer = null;
                    }

                    cancelCheckFreeze();

                    if ($scope.videoJq) {
                        $scope.videoJq.remove();
                        $scope.videoJq = null;
                    }

                    if ($scope.videoObj) {
                        logService.debug(logKey, 'Video destroy: ' + debugName);
                        $scope.videoObj.removeEventListener(VIDEO_EVENT_CODE.LOADED_METADATA, onLoadmetadata);
                        $scope.videoObj.removeEventListener(VIDEO_EVENT_CODE.LOADED_DATA, onLoaddata);
                        $scope.videoObj.removeEventListener(VIDEO_EVENT_CODE.ERROR, onError);
                        $scope.videoObj.removeEventListener(VIDEO_EVENT_CODE.ENDED, onEnded);
                        $scope.videoObj.removeEventListener(VIDEO_EVENT_CODE.TIME_UPDATE, onTimeupdate);
                        if ($scope.allowPlayThrough) {
                            $scope.videoObj.removeEventListener(VIDEO_EVENT_CODE.CAN_PLAY_THROUGH, $scope.videoObj.play);
                        }

                        $scope.unload();
                        if (videoJs) {
                            videoJs.dispose();
                            videoJs = null;
                        }

                        $scope.videoObj.remove();
                        $scope.videoObj = null;
                        clearMediaTouchSubscription();
                    }

                    // Unregister this layer
                    halVideoLayerManager.unregisterVideoLayer($scope.api);

                    $element.empty(); // Parent of the video element (optional);
                    // $element.remove();
                };

                $scope.lostLayer = function(src) {
                    logService.debug(logKey, 'Video lost layer: ' + debugName);
                    var wasLoaded = loaded;
                    // Destroy
                    $scope.destroy();

                    // Send lost playback in 2 secs
                    loadLayoutTimer = $timeout(function() {
                        if (wasLoaded) {
                            if ($scope.onVideoPlaybackError) {
                                $scope.onVideoPlaybackError({sender: $scope, msg: 'playback error: ' + debugName});
                            }
                        } else {
                            if ($scope.onVideoLoadError) {
                                $scope.onVideoLoadError({sender: $scope, msg: 'playback error: ' + debugName});
                            }
                        }
                    }, 2000);
                };

                $scope.getSource = function(src) {
                    return srcFile;
                };

                $scope.allowReuse = function() {
                    if (videoJs || $scope.videoObj == null) {
                        return false;
                    } else {
                        return true;
                    }
                };

                function clearMediaTouchSubscription() {
                    interactivityManager.unsubscribe(onTouchOnce);
                }

                function onTouchOnce() {
                    if (forceMute) {
                        return;
                    }
                    clearMediaTouchSubscription();
                    setMute(false);
                }

                $scope.setSource = function(src) {
                    if (!$scope.videoObj) {
                        return;
                    }

                    if (!interactivityManager.isInteracted()) {
                        interactivityManager.subscribe(onTouchOnce);
                    }

                    resetVideoState();
                    isLoading = true;
                    hasError = false;
                    hasVideoDimensionError = false;
                    ended = false;

                    var sourceObject = halMediaService.processMediaURL(src);

                    debugName = srcFile = sourceObject.url;

                    isHls = srcFile.toLowerCase().indexOf('.m3u8') > 0 ? true : false;
                    var supportHls = halPlatformService.getFeatureSupport('media.hls.supported') == 'yes' ? true : false;

                    // Check is the source for a HLS stream
                    var source;
                    if (isHls && !supportHls) {
                        logService.debug(logKey, 'Loading HLS source using video JS: ' + debugName);

                        // Remove any previous sources
                        $($scope.videoObj).empty();
                        doPauseVideo();
                        $scope.videoObj.src = ''; // passing empty value
                        $scope.videoObj.load(); // loading video with an empty source

                        // Create a source based on apple type
                        source = document.createElement('source');
                        source.src = srcFile;
                        source.type = 'application/vnd.apple.mpegURL';
                        $scope.videoObj.appendChild(source);
                        // Create source based on mpegUrl
                        source = document.createElement('source');
                        source.src = srcFile;
                        source.type = 'application/x-mpegURL';
                        $scope.videoObj.appendChild(source);

                        // If the video js object has not been created previously, we create it now
                        if (!videoJs) {
                            // Use VideoJS to playback
                            videoJs = videojs($scope.videoObj, {}, function() {
                                // Player (this) is initialized and ready.
                                // Check if there was an error
                                var error = videoJs.error();
                                if (error) {
                                    onError(error);
                                    return;
                                }
                            });
                        }
                    } else if (sourceObject.LGMediaType && sourceObject.protocol) {
                        // LG workflow for streaming
                        // Remove any previous sources
                        $($scope.videoObj).empty();
                        doPauseVideo();
                        $scope.videoObj.load(); // loading video with an empty source
                        var options = {};
                        options.htmlMediaOption = {};
                        options.htmlMediaOption.useUMSMediaInfo = true;
                        options.mediaTransportType = sourceObject.protocol;
                        source = document.createElement('source');
                        source.setAttribute('type', 'video/' + sourceObject.LGMediaType + ';mediaOption=' + escape(JSON.stringify(options)));
                        source.setAttribute('src', srcFile);
                        $scope.videoObj.appendChild(source);
                        logService.debug(logKey, 'Setting custom property for LG stream playback, mediaTransportType:' + sourceObject.protocol + ' mediaType:' + sourceObject.LGMediaType);
                    } else {
                        // Defatult behaviour
                        $scope.videoObj.src = srcFile;
                    }

                    // if platform doesn't support video load event, directly call play
                    if (!platformSupportVideoLoaded) {
                        $scope.videoObj.play();
                        return;
                    }

                    if (platformSupportUseLoad) {
                        $scope.videoObj.load();
                        return;
                    }

                    // STAN - only follow this logic if auto play is disabled
                    if (!$scope.videoObj.autoplay) {
                        var onVideoPlayError = function(e) {
                            logService.error(logKey, 'Video error', e);
                        };

                        try {
                            var itemMute = $scope.videoObj.muted;
                            var playPromise = doPlayVideo(itemMute);
                            if (!playPromise) {
                                doPauseVideo();
                                return;
                            }

                            playPromise.then(doPauseVideo, onVideoPlayError);
                        } catch (e) {
                            onVideoPlayError(e);
                        }
                    }
                };

                var doPauseVideo = function() {
                    $scope.videoObj.pause();
                };

                var doPlayVideo = function(itemMute) {
                    setMute(
                        itemMute ||
                        forceMute ||
                        (halPlatformService.getFeatureSupport('media.video.should.mute') === 'yes' && !interactivityManager.isInteracted())
                    );
                    return $scope.videoObj.play();
                };

                // check if the HLS stream freezes for more than 20 seconds.
                var checkFreeze = function() {
                    // waiting video to load
                    if (currTime == 0 && prevTimer == 0) {
                        return;
                    }

                    if (currTime && (currTime != prevTimer)) {
                        if (freezeCounter > 0) {
                            freezeCounter = 0;
                        }
                        prevTimer = currTime;
                    } else {
                        freezeCounter++;
                        if (freezeCounter > 20) {
                            cancelCheckFreeze();
                            var errorMessage = 'Video freezes: ' + debugName;
                            var errorData = {
                                currentTime: currTime,
                                previousTime: prevTimer,
                                freezeCounter: freezeCounter
                            };
                            logService.error(logKey, 'Video freezes: ' + debugName, errorData);
                            // send the playbackerror if the video freezes more than 20 seconds
                            if ($scope.onVideoPlaybackError) {
                                $scope.onVideoPlaybackError({sender: $scope, msg: errorMessage + ':' + JSON.stringify(errorData)});
                            }
                        }
                    }
                };

                function cancelCheckFreeze() {
                    if (checkFreezeInterval) {
                        clearInterval(checkFreezeInterval);
                        checkFreezeInterval = null;
                    }
                }

                function startCheckFreeze() {
                    cancelCheckFreeze();
                    checkFreezeInterval = setInterval(checkFreeze, 1000);
                }

                function resetVideoState() {
                    currTime = 0;
                    prevTimer = 0;
                    freezeCounter = 0;

                    loaded = false;
                    metadataLoaded = false;
                    isLoading = false;
                }

                $scope.play = function() {
                    if (!$scope.videoObj) {
                        return;
                    }

                    if (hasError) {
                        onError('Fail to play the video due to previous error event');
                        return;
                    }
                    startCheckFreeze();
                    doPlayVideo();
                    logService.debug(logKey, 'Video play: ' + debugName);
                    if (!removeZIndexOnPlay) {
                        return;
                    }

                    $scope.videoObj.style.zIndex = '';
                };

                $scope.pause = function() {
                    if (!$scope.videoObj) {
                        return;
                    }
                    cancelCheckFreeze();
                    doPauseVideo();
                    logService.debug(logKey, 'Video pause: ' + debugName);
                };

                $scope.setVolume = function(volume) {
                    if (!$scope.videoObj) {
                        return;
                    }

                    if (isNaN(volume)) {
                        return;
                    } else if (volume > 1) {
                        volume = 1;
                    } else if (volume < 0) {
                        volume = 0;
                    }

                    $scope.videoObj.volume = volume;
                };

                function setMute(value) {
                    $scope.videoObj.muted = value;
                }

                $scope.setMute = function(shouldMute, isTemporary) {
                    if (!$scope.videoObj) {
                        return;
                    }

                    if (!isTemporary) {
                        forceMute = shouldMute;
                    }

                    setMute(shouldMute);
                };

                $scope.seek = function(currentTime) {
                    if (!$scope.videoObj) {
                        return;
                    }
                    $scope.videoObj.currentTime = currentTime;
                };

                $scope.getPosition = function() {
                    if (!$scope.videoObj) {
                        return;
                    }
                    return $scope.videoObj.currentTime;
                };

                $scope.unload = function() {
                    if ($scope.videoObj && srcFile.length > 0) {
                        loaded = false;
                        metadataLoaded = false;
                        logService.debug(logKey, 'Video unload: ' + debugName);

                        $($scope.videoObj).empty();
                        doPauseVideo();
                        $scope.videoObj.src = ''; // passing empty value
                        $scope.videoObj.load(); // loading video with an empty source

                        // Unregister this layer
                        halVideoLayerManager.unregisterVideoLayer($scope.api);
                    }
                    srcFile = '';
                    isLoading = false;
                    hasError = false;
                };

                $scope.setStretch = function(stretch) {
                    if (!$scope.videoObj) {
                        return;
                    }

                    stretch = (stretch || '').toLowerCase();

                    // Check do we already have the correct stretch
                    if (currentStretch == stretch) {
                        return;
                    }

                    // Update current stretch
                    currentStretch = stretch;
                    halMediaService.setVideoStretch($element.find('video'), stretch);

                    // try perform transform when the stretch changes.
                    tryCssTransform(true);
                };

                function getVideoWidth() {
                    return $scope.videoObj.videoWidth;
                }

                function getVideoHeight() {
                    return $scope.videoObj.videoHeight;
                }

                function tryCssTransform(force) {
                    // if driver prefers object-fit and we already have objectFit attribute, don't do anything
                    // if metadata hasn't been loaded, don't do anything
                    if (!metadataLoaded || (halPlatformService.getFeatureSupport('media.video.stretch.mode') != VIDEO_STRETCH_MODE.SCALE && $scope.videoObj.style.objectFit)) {
                        return;
                    }
                    var currentWidth = $scope.videoJq.width();
                    var currentHeight = $scope.videoJq.height();
                    if (!force && currentWidth === containerWidth && currentHeight === containerHeight) {
                        return;
                    }
                    containerWidth = currentWidth;
                    containerHeight = currentHeight;
                    var videoWidth = getVideoWidth();
                    var videoHeight = getVideoHeight();
                    var scaleX = 1;
                    var scaleY = 1;
                    var translateX = '0';
                    var translateY = '0';

                    if ((currentStretch === 'fill' || currentStretch === 'cover') && videoWidth > 0 && videoHeight > 0 && containerWidth > 0 && containerHeight > 0) {
                        if (videoWidth / containerWidth > videoHeight / containerHeight) {
                            scaleY = containerHeight * videoWidth / (videoHeight * containerWidth);
                            if (currentStretch === 'cover') {
                                scaleX = scaleY;
                                translateX = '-50%';
                            }
                        } else {
                            scaleX = containerWidth * videoHeight / (videoWidth * containerHeight);
                            if (currentStretch === 'cover') {
                                scaleY = scaleX;
                                translateY = '-50%';
                            }
                        }
                    }
                    logService.debug(logKey, 'Set video stretch using scale');
                    $scope.videoJq.css('transform', 'scale(' + scaleX + ',' + scaleY + ') translate(' + translateX + ',' + translateY + ')');
                }
            }]
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var app = $angular.module('media');
    app.directive('asMediaWeb', ["halMediaService", "postMessagingService", "dataUtil", "webViewManager", "deviceTokenService", "POST_MESSAGES", "PLAYER_PROPERTY_KEYS", function(
        halMediaService,
        postMessagingService,
        dataUtil,
        webViewManager,
        deviceTokenService,
        POST_MESSAGES,
        PLAYER_PROPERTY_KEYS
    ) {
        return {
            restrict: 'EA',
            scope: {
                url: '@', // url of the web page
                scroll: '@',
                onApiReady: '&',
                onWebLoad: '&',
                onEndOfPlaylist: '&',
                onWebLoadError: '&',
                onWebPlaybackError: '&',
                subjectId: '@',
                subjectType: '@'
            },

            template: function(scope, attrs) {
                // Extract the directive attributes and pass to hal get media directive
                var options = {};
                for (var a in attrs) {
                    if (a.indexOf('$') == -1) {
                        options[a] = attrs[a];
                    }
                }

                var mediaDirective = halMediaService.getMediaDirective('iframe', options);
                return mediaDirective.enablelayer ?
                    '<div style="width:100%; height:100%; background-color:rgba(255, 0, 0, 0); position:absolute">&nbsp;</div>' : '';
            },

            link: function(scope, element, attrs) {
                // Extract the directive attributes
                var options = {};
                for (var a in attrs) {
                    if (a.indexOf('$') == -1) {
                        options[a] = attrs[a];
                    }
                }

                scope.setOptions(options);
                scope.mediaDirective = halMediaService.getMediaDirective('iframe', options);
                scope.scrolling = '';
                if (!attrs.scroll) {
                    scope.scrolling = 'no';
                }

                scope.$on('$destroy', function() {
                    // console.log("asMediaWeb destroy");
                    scope.destroy();
                });

                element.on('$destroy', function() {
                    // console.log("asMediaWeb element destroy");
                    scope.destroy(true);
                });

                scope.$watch('url', function(newVal) {
                    if (!newVal) {
                        return;
                    }

                    return scope.setUrl(newVal);
                });

                scope.$watch('scroll', function(newVal) {
                    if (!newVal) {
                        return;
                    }

                    return scope.setScrolling(newVal);
                });

                scope.api = {
                    destroy: scope.destroy,
                    setUrl: scope.setUrl,
                    isLoading: scope.isLoading,

                    postMessage: scope.postMessage,
                    addEventListener: scope.addEventListener,
                    removeEventListener: scope.removeEventListener,

                    setData: scope.setData,
                    reload: scope.reload,
                    processPendingNativeLoad: scope.processPendingNativeLoad,
                    setStyle: scope.setStyle
                };

                scope.init();

                if (scope.onApiReady) {
                    scope.onApiReady({api: scope.api});
                }
            },

            controller: ['$window', '$scope', '$element', '$q', '$sce', '$compile', '$timeout', '$interval', 'logService', 'halPlatformService',
                function($window, $scope, $element, $q, $sce, $compile, $timeout, $interval, logService, halPlatformService) {
                    var isLoading = false;
                    var coverLayer = $element.children('div')[0];
                    var webFrame;
                    var resizeInterval;
                    var firedLoadComplete = false;
                    var eventListeners = {};
                    var options = {};
                    var onLoadedByTimerPromise;
                    var msWebviewPollingTimer;
                    var msWebviewNavigated = false;
                    var msWebviewPostMessagingInit = false;
                    var msWebviewPostMessagingTicks;
                    var keyboardHiddenTimer;
                    var webviewPostMessageTimeoutPromise;
                    var webviewPostMessageTimeoutCount;
                    var webviewIsProcessing;
                    var webviewPostMessaingRetryLimit;
                    var PENDING_NATIVE_LOAD_TIMEOUT_DURATION = 10000;
                    var MS_WEB_VIEW_NAVIGATION_COMPLETED_EVENT = 'MSWebViewNavigationCompleted';
                    var LOG_KEY = 'appspace.core.media';
                    var id = 'asWeb-' + new Date().getTime();
                    $scope.setData = function(o) {
                        $scope.data = o;
                    };

                    $scope.setOptions = function(opt) {
                        options = opt;
                    };

                    $scope.destroy = function(ignoreElement) {
                        try {
                            postMessagingService.unregisterContent(id);
                            for (var key in eventListeners) {
                                eventListeners[key] = [];
                                eventListeners[key] = null;
                            }

                            eventListeners = {};
                            eventListeners = null;
                            options = {};
                            options = null;
                            $scope.webUrl = null;

                            firedLoadComplete = false;
                            if (onLoadedByTimerPromise) {
                                $timeout.cancel(onLoadedByTimerPromise);
                                onLoadedByTimerPromise = null;
                            }

                            if (resizeInterval) {
                                $interval.cancel(resizeInterval);
                                resizeInterval = null;
                            }

                            // uwp specific
                            cancelMsWebviewPollingTimer();
                            msWebviewPostMessagingTicks = null;
                            msWebviewPostMessagingInit = false;

                            // Remove Event handler
                            var eventMethod = window.addEventListener ? 'removeEventListener' : 'detachEvent';
                            var evremove = window[eventMethod];
                            var messageEvent = eventMethod == 'detachEvent' ? 'onmessage' : 'message';
                            // Remove window event listener for commands
                            evremove(messageEvent, onWindowMessage);

                            if (webFrame && !ignoreElement) {
                                if (webFrame.removeEventListener) {
                                    webFrame.removeEventListener('loadstop', $scope.onloadCallback);
                                    webFrame.removeEventListener('permissionrequest', onpermissionRequest);
                                    webFrame.removeEventListener('blur', addLayer);
                                    webFrame.removeEventListener('mouseout', addLayer);

                                    // uwp-specific load event
                                    webFrame.removeEventListener('MSWebViewContentLoading', $scope.onloadCallback);
                                    webFrame.removeEventListener('MSWebViewNavigationCompleted', initMSPostMessaging);
                                } else if (webFrame.detachEvent) {
                                    webFrame.detachEvent('loadstop', $scope.onloadCallback);
                                    webFrame.detachEvent('permissionrequest', onpermissionRequest);
                                }

                                webViewManager.releaseWebView(webFrame).finally(function() {
                                    webFrame = undefined;
                                });
                            }

                            if (coverLayer && coverLayer.removeEventListener) {
                                coverLayer.removeEventListener('touchstart', removeLayer);
                                coverLayer.removeEventListener('mouseover', removeLayer);
                            }

                            if ($element) {
                                $element.off('load');
                                $element.empty(); // Parent of the video element (optional);
                                if (!ignoreElement) {
                                    if ($element.remove) {
                                        $element.remove();
                                    }

                                    $element = null;
                                }
                            }

                            handleIFrameKeyboardEvent(false);
                        } catch (e) {
                            logService.debug(LOG_KEY, 'Unable to destroy web directive', e);
                        }
                    };

                    $scope.init = function() {
                        var configuration = {
                            scrolling: $scope.scrolling,
                            initialWidth: $($window).width(),
                            initialHeight: $($window).height(),
                        };
                        if (options && options.requirecamera === 'true') {
                            configuration.attributes = {
                                allow: 'camera *'
                            };
                        }

                        webFrame = webViewManager.getWebView($scope.mediaDirective, configuration);
                        $element.append(webFrame);
                        startResize();
                        // Create IE + others compatible event handler
                        var eventMethod = window.addEventListener ? 'addEventListener' : 'attachEvent';
                        var eventer = window[eventMethod];
                        var messageEvent = eventMethod == 'attachEvent' ? 'onmessage' : 'message';
                        // Set up parent window event listener for commands
                        eventer(messageEvent, onWindowMessage, false);

                        if (coverLayer) {
                            addEventListener(webFrame, 'blur', addLayer);
                        }

                        postMessagingService.registerContent(id, postMessageToContent);
                    };

                    $scope.processPendingNativeLoad = function(onLoadedCallback) {
                        if (!webFrame) {
                            return;
                        }

                        var loadTimeout;
                        var originalOnloadCallback = webFrame.onload;
                        var originalOnloadstopCallback = webFrame.onloadstop;

                        var onNativeLoad = function(event) {
                            if (!loadTimeout) {
                                return;
                            }

                            $timeout.cancel(loadTimeout);
                            loadTimeout = undefined;
                            if (webFrame) {
                                webFrame.onload = originalOnloadCallback;
                                webFrame.onloadstop = originalOnloadstopCallback;
                                webFrame.removeEventListener(MS_WEB_VIEW_NAVIGATION_COMPLETED_EVENT, onNativeLoad, false);
                            }

                            if (!onLoadedCallback) {
                                return;
                            }

                            onLoadedCallback(event);
                        };

                        loadTimeout = $timeout(onNativeLoad, PENDING_NATIVE_LOAD_TIMEOUT_DURATION);
                        webFrame.onload = onNativeLoad;
                        webFrame.onloadstop = onNativeLoad;
                        webFrame.addEventListener(MS_WEB_VIEW_NAVIGATION_COMPLETED_EVENT, onNativeLoad, false);
                    };

                    $scope.isLoading = function() {
                        return isLoading;
                    };

                    function addLayer() {
                        coverLayer.style.display = 'block';
                        logService.debug(LOG_KEY, 'Unfocus ', webFrame.src);
                    }

                    function removeLayer() {
                        coverLayer.style.display = 'none';
                        webFrame.focus();
                        logService.debug(LOG_KEY, 'Focusing on ', webFrame.src);
                    }

                    function addEventListener(domElement, event, callback) {
                        if (domElement && domElement.addEventListener && domElement.removeEventListener) {
                            domElement.removeEventListener(event, callback);
                            domElement.addEventListener(event, callback);
                        }
                    }

                    $scope.onloadCallback = function(reason) {
                        // if we're not loading anything, don't process any on load events
                        if (!webviewIsProcessing) {
                            initWebViewPostMessaging();
                        }

                        if (!isLoading) {
                            return;
                        }

                        if (coverLayer) {
                            addEventListener(coverLayer, 'touchstart', removeLayer);
                            addEventListener(coverLayer, 'mouseover', removeLayer);
                            addEventListener(webFrame, 'mouseout', addLayer);
                        }

                        // if web view, send inital message to webview content to establish connection
                        if (webFrame && $scope.mediaDirective.directive == 'iframe') {
                            if (halPlatformService.getFeatureSupport('media.webframe.support.polyfill') == 'yes' &&
                                webFrame.contentWindow &&
                                !webFrame.contentWindow.String.prototype.endsWith) {
                                webFrame.contentWindow.String.prototype.endsWith = function(str) {
                                    if (!this || !str || this.length == 0 || str.length == 0 || this.length < str.length) {
                                        return false;
                                    }

                                    return this.substr(this.length - str.length) === str;
                                };
                            }

                            hideKeyboard(1000);
                            // To ensure we detach from the event listener first before attaching
                            handleIFrameKeyboardEvent(false);
                            handleIFrameKeyboardEvent(true);
                        }

                        isLoading = false;
                        if (!reason) {
                            reason = 'event';
                        } else if (!dataUtil.isString(reason)) {
                            reason = JSON.stringify(reason);
                        }

                        if (onLoadedByTimerPromise) {
                            $timeout.cancel(onLoadedByTimerPromise);
                            onLoadedByTimerPromise = null;
                        }

                        if (firedLoadComplete) {
                            logService.debug(LOG_KEY, 'Web page already loaded (by ' + reason + '): ' + $scope.webUrl);
                            return;
                        }

                        firedLoadComplete = true;
                        if ($scope.onWebLoad) {
                            logService.debug(LOG_KEY, 'Sending web page loaded (by ' + reason + '): ' + $scope.webUrl);
                            $scope.onWebLoad();
                        }
                    };

                    function startResize() {
                        // call resize once
                        $scope.resize();
                        stopResize();
                        resizeInterval = $interval($scope.resize, 100);
                    }

                    function stopResize() {
                        if (!resizeInterval) {
                            return;
                        }

                        $interval.cancel(resizeInterval);
                        resizeInterval = undefined;
                    }

                    function handleIFrameKeyboardEvent(add) {
                        if (halPlatformService.getFeatureSupport('media.webframe.handle.keyboard') == 'yes' &&
                            webFrame.contentWindow &&
                            webFrame.contentWindow.document &&
                            $window.top.$asCoreFrameworkService &&
                            $window.top.$asCoreFrameworkService.getServices) {
                            var listenerFunction = add ? 'addEventListener' : 'removeEventListener';
                            var iframeDocument = webFrame.contentWindow.document;
                            var remoteService = $window.top.$asCoreFrameworkService.getServices().hal.halRemoteService;
                            iframeDocument[listenerFunction]('keydown', remoteService.handleSystemKeydownEvent);
                            iframeDocument[listenerFunction]('keyup', remoteService.handleSystemKeyupEvent);
                        }
                    }

                    // Called when there is a window event
                    function onWindowMessage(e) {
                        // Check is this event for our iframe
                        if (webFrame.contentWindow != e.source) {
                            return;
                        }

                        cancelCheckPostMessageReply(true);

                        if (e && e.data && e.data.message) {
                            switch (e.data.message) {
                                case 'appspaceappready':
                                    postMessageToContent({message: 'setproperties', properties: $scope.data.properties});
                                    break;
                                case 'api.requestconstellationtokens':
                                    var callback = e.data.callback || {};
                                    deviceTokenService.requestConstellationTokens($scope.subjectId,
                                        $scope.subjectType, e.data.context
                                    ).then(function(result) {
                                        callback.result = result;
                                    }, function(error) {
                                        callback.errorMessage = typeof error === 'string' ? error: JSON.stringify(error);
                                    }).finally(function() {
                                        postMessageToContent({message: 'api.requestconstellationtokens.response',
                                            callback: callback});
                                    });

                                    break;
                                case POST_MESSAGES.NAME:
                                    processAppspaceMessages(e.data);
                                    break;
                            }
                        }

                        invokeListenersCallback(e);
                    }

                    function processAppspaceMessages(event) {
                        if (!event || !event.type) {
                            return;
                        }

                        var eventType = dataUtil.isString(event.type) ?
                            event.type.toLowerCase() : '';
                        switch (eventType) {
                            case POST_MESSAGES.WIDGET_END_OF_PLAYLIST:
                                if ($scope.onEndOfPlaylist) {
                                    $scope.onEndOfPlaylist();
                                }

                                break;
                        }
                    }

                    function initMSPostMessaging() {
                        // Check if it was navigated successfully and the post messaging was never initialized
                        if (msWebviewNavigated && !msWebviewPostMessagingInit) {
                            postMsWebviewMessages(
                                webFrame,
                                'msPostMessaging',
                                {message: 'appspaceapp.mswebview.postmessage.init'},
                                function() {
                                    logService.debug(LOG_KEY, 'UWP: msPostMessaging success');
                                    msWebviewPostMessagingInit = true;
                                    pollMsWebviewMessages(webFrame);
                                }, function(err) {
                                    logService.error(LOG_KEY, 'UWP: msPostMessaging error', err);
                                });
                        }
                    }

                    function initWebViewPostMessaging() {
                        if ($scope.mediaDirective.directive == 'webview') {
                            logService.debug(LOG_KEY, 'Establishing messaging with page');
                            webviewPostMessaingRetryLimit = webviewPostMessageTimeoutCount > 0 ? 1 : 50;
                            webviewPostMessageTimeoutCount = 0;
                            webviewIsProcessing = true;
                            startInitPostMessage();
                        }
                    }

                    function startInitPostMessage() {
                        cancelCheckPostMessageReply();
                        postMessageToContent({message: 'appspaceapp.webview.postmessage.init'});
                        webviewPostMessageTimeoutPromise = $timeout(function() {
                            webviewPostMessageTimeoutCount++;
                            if (webviewPostMessageTimeoutCount >= webviewPostMessaingRetryLimit) {
                                webviewIsProcessing = false;
                                var errorMessage = 'Unable to establish messaging to page after ' + webviewPostMessageTimeoutCount + ' tries';
                                if (!firedLoadComplete && $scope.onWebLoadError) {
                                    $scope.onWebLoadError({
                                        error: errorMessage
                                    });
                                } else if ($scope.onWebPlaybackError) {
                                    $scope.onWebPlaybackError({
                                        error: errorMessage
                                    });
                                }

                                logService.warn(LOG_KEY, errorMessage);
                                return;
                            }

                            startInitPostMessage();
                        }, 100);
                    }

                    function cancelCheckPostMessageReply(success) {
                        if (webviewPostMessageTimeoutPromise) {
                            $timeout.cancel(webviewPostMessageTimeoutPromise);
                            webviewPostMessageTimeoutPromise = undefined;
                            if (success) {
                                logService.debug(LOG_KEY, 'Successfully establish messaging with page');
                            }
                        }
                    }

                    $scope.postMessage = postMessageToContent;

                    function postMessageToContent(message) {
                        if (!webFrame) {
                            return;
                        }

                        if ($scope.mediaDirective.directive == 'x-ms-webview') {
                            postMsWebviewMessages(webFrame, 'msPostMessaging', message);
                        } else if (webFrame.contentWindow && webFrame.contentWindow.postMessage) {
                            webFrame.contentWindow.postMessage(message, '*');
                        }
                    }

                    function postMsWebviewMessages(msWebview, methodName, message, onsuccess, onerror) {
                        if (!msWebview || !msWebview.invokeScriptAsync) {
                            return;
                        }

                        var msPostMessage = message ? msWebview.invokeScriptAsync(methodName, JSON.stringify(message)) : msWebview.invokeScriptAsync(methodName);
                        msPostMessage.oncomplete = function(e) {
                            if (onsuccess) {
                                onsuccess(e);
                            }
                        };

                        msPostMessage.onerror = function(e) {
                            if (onerror) {
                                onerror(e);
                            }
                        };

                        msPostMessage.start();
                    }

                    function pollMsWebviewMessages(msWebview) {
                        cancelMsWebviewPollingTimer();
                        if (!msWebviewPostMessagingTicks || msWebview.msWebviewPostMessagingTicks != msWebviewPostMessagingTicks) {
                            return;
                        }

                        var restartPollTimer = function() {
                            cancelMsWebviewPollingTimer();
                            msWebviewPollingTimer = $timeout(function() {
                                pollMsWebviewMessages(msWebview);
                            }, 20);
                        };

                        var onPollSuccess = function(e) {
                            if (!msWebviewPostMessagingTicks || msWebview.msWebviewPostMessagingTicks != msWebviewPostMessagingTicks) {
                                return;
                            }

                            if (e.srcElement && e.srcElement.result) {
                                var messages = JSON.parse(e.srcElement.result);
                                if (Array.isArray(messages)) {
                                    for (var k = 0; k < messages.length; k++) {
                                        e.data = messages[k];
                                        invokeListenersCallback(e);
                                        e.data = null;
                                    }
                                }
                            }

                            restartPollTimer();
                        };

                        var onPollError = function() {
                            restartPollTimer();
                        };

                        postMsWebviewMessages(msWebview, 'msRetrieveMessaging', null, onPollSuccess, onPollError);
                    }

                    function cancelMsWebviewPollingTimer() {
                        if (msWebviewPollingTimer) {
                            $timeout.cancel(msWebviewPollingTimer);
                            msWebviewPollingTimer = null;
                        }
                    }

                    function invokeListenersCallback(e) {
                        var listeners = getEventCallbacks('messages') || [];
                        // Pass the event to all listeners
                        for (var i = 0; i < listeners.length; i++) {
                            // Invoke callback
                            listeners[i](e);
                        }

                        postMessagingService.raiseContentMessage(e);
                    }

                    function getEventCallbacks(event) {
                        event = event.toLowerCase();
                        return eventListeners[event];
                    }

                    $scope.addEventListener = function(event, callback) {
                        event = event.toLowerCase();
                        var listeners = eventListeners[event];
                        // Chech have we a listener chain setup?
                        if (!listeners) {
                            // If not create one
                            listeners = [callback];
                            eventListeners[event] = listeners;
                            return;
                        }

                        // Check if this callback has already been added
                        for (var i = 0; i < listeners.length; i++) {
                            if (listeners[i] == callback) {
                                return;
                            }
                        }

                        // Add callback to listener chain
                        listeners.push(callback);
                    };

                    $scope.removeEventListener = function(event, callback) {
                        event = event.toLowerCase();
                        var listeners = eventListeners[event];
                        // Chech have we a listener chain setup?
                        if (!listeners) {
                            // No, we dont have this callback then
                            return;
                        }

                        // Enumerate to find the callback
                        for (var i = 0; i < listeners.length; i++) {
                            if (listeners[i] == callback) {
                                listeners.splice(i);
                                return;
                            }
                        }
                    };

                    $scope.resize = function() {
                        if (webFrame) {
                            var parent = webFrame.parentNode;
                            var parentWidth = null;
                            var parentHeight = null;
                            // Enumerate through prents until we have a width and height
                            while (parent && (!parentWidth || !parentHeight)) {
                                // Found a parent with a width
                                if (!parentWidth && parent.style && parent.style.width) {
                                    parentWidth = Math.ceil(parent.offsetWidth);
                                }

                                // Found a parent with a height
                                if (!parentHeight && parent.style && parent.style.height) {
                                    parentHeight = Math.ceil(parent.offsetHeight);
                                }

                                // Move to next parent
                                parent = parent.parentNode;
                            }

                            parent = null;
                            // Resize browser if we have a width && height
                            if (parentWidth && parentHeight) {
                                if (parentWidth > 0 && parentHeight > 0) {
                                    // console.log(parentWidth + ", " + parentHeight);
                                    if (webFrame.style.width != parentWidth + 'px') {
                                        webFrame.style.width = parentWidth + 'px';
                                    }

                                    if (webFrame.style.height != parentHeight + 'px') {
                                        webFrame.style.height = parentHeight + 'px';
                                    }
                                }
                            }
                        }
                    };

                    $scope.setUrl = function(url) {
                        var defer = $q.defer();
                        var isFlashFile = false;
                        var loadIntoMemory = false;
                        var isRemoteUrl = true;
                        cancelMsWebviewPollingTimer();

                        // Resize webframe to div
                        $scope.resize();

                        // Is it a flash file?
                        if (url.toLowerCase().indexOf('.swf') > 0) {
                            isFlashFile = true;
                        }

                        // Check if the file is from local storage
                        if (url.toLowerCase().indexOf('http://') == -1 && url.toLowerCase().indexOf('https://') == -1) {
                            isRemoteUrl = false;
                        }

                        // If its a flash file running in a web view we attempt to download the file into memory, and convert to an object URL. Get round webview restrictions
                        if (isFlashFile && $scope.mediaDirective.directive == 'webview') {
                            loadIntoMemory = true;
                        }

                        // If load into memory, request form storage and create an objectURL
                        if (loadIntoMemory) {
                            if (isRemoteUrl) {
                                logService.debug(LOG_KEY, 'Webview loading flash via dynamic web page:' + url);
                                var stringHTML = '<html><head></head><body style="margin:0px;background-color:black"><object width="100%" height="100%" data="' + url + '"></object></body></html>';
                                webFrame.src = 'data:text/html;charset=utf-8,' + encodeURIComponent(stringHTML);
                            } else {
                                logService.debug(LOG_KEY, 'Webview loading flash into memory: ' + url);
                                var xhr = new XMLHttpRequest();
                                xhr.open('GET', url, true);
                                xhr.responseType = 'blob';
                                xhr.onreadystatechange = function() {
                                    // ignore ready state that is not 4(done)
                                    if (xhr.readyState != 4) {
                                        return;
                                    }

                                    if (xhr.readyState == 4 && xhr.status == 200) {
                                        var blob = new Blob([this.response], {type: 'application/x-shockwave-flash'});
                                        webFrame.src = window.URL.createObjectURL(blob);
                                        logService.debug(LOG_KEY, 'Webview loaded flash into memory: ' + url);
                                    } else {
                                        defer.reject('failed to load flash content into memory');
                                        logService.debug(LOG_KEY, 'Webview failed to load flash into memory: ' + url);
                                    }
                                };

                                xhr.send();
                            }
                        }

                        isLoading = true;
                        firedLoadComplete = false;
                        webviewIsProcessing = false;
                        if (webFrame) {
                            // Support transparency
                            if (webFrame.style) {
                                webFrame.style.backgroundColor = 'transparent';
                                webFrame.allowTransparency = 'true';
                            }

                            addEventListener(webFrame, 'permissionrequest', onpermissionRequest);

                            if ($scope.mediaDirective.directive == 'x-ms-webview') {
                                msWebviewPostMessagingInit = false;
                                msWebviewNavigated = false;
                                var ticks = (new Date()).getTime();
                                msWebviewPostMessagingTicks = ticks;
                                webFrame.msWebviewPostMessagingTicks = ticks;
                                addEventListener(webFrame, 'MSWebViewNavigationCompleted', initMSPostMessaging);
                                addEventListener(webFrame, 'MSWebViewContentLoading', $scope.onloadCallback);

                                // Disable alert and confirm popups in webview.
                                webFrame.invokeScriptAsync('eval', 'window.alert = function () {}');
                                webFrame.invokeScriptAsync('eval', 'window.confirm = function () {}');
                                if (url.indexOf('ms-appx://') === -1) {
                                    var navigateToWeb = function() {
                                        msWebviewNavigated = true;
                                        webFrame.navigate(url);
                                    };

                                    if (url.match(/^(http|https):\/\//i)) {
                                        // Disable authentication popups.
                                        var filter = new Windows.Web.Http.Filters.HttpBaseProtocolFilter();
                                        filter.allowUI = false;
                                        var httpClient = new Windows.Web.Http.HttpClient(filter);
                                        var request = new Windows.Web.Http.HttpRequestMessage(Windows.Web.Http.HttpMethod.head, new Windows.Foundation.Uri(url));
                                        var onFailed = function(reason) {
                                            if (onLoadedByTimerPromise) {
                                                $timeout.cancel(onLoadedByTimerPromise);
                                            }

                                            $scope.onloadCallback(reason);
                                        };

                                        httpClient.sendRequestAsync(request).done(function(e) {
                                            if (e.statusCode != 401) {
                                                navigateToWeb();
                                                return;
                                            }

                                            onFailed(e.reasonPhrase);
                                        }, function(error) {
                                            onFailed(error.message);
                                        });
                                    } else {
                                        navigateToWeb();
                                    }

                                    loadIntoMemory = true;
                                }
                            }

                            if (!loadIntoMemory) {
                                // Load directly if not in memory
                                webFrame.src = url;
                            }

                            $scope.webUrl = $sce.trustAsResourceUrl(url);

                            if (onLoadedByTimerPromise) {
                                $timeout.cancel(onLoadedByTimerPromise);
                                onLoadedByTimerPromise = null;
                            }

                            logService.debug(LOG_KEY, 'Loading webpage using '+ $scope.mediaDirective.directive + ' for ' + $scope.webUrl);

                            addEventListener(webFrame, 'loadstop', $scope.onloadCallback);

                            webFrame.onload = $scope.onloadCallback;
                            // temporary. still finding a way to listen to iframe onload
                            onLoadedByTimerPromise = $timeout(function() {
                                $scope.onloadCallback('timer');
                            }, 6000);

                            defer.resolve($scope.webUrl);
                        } else {
                            defer.reject('webview already destroyed');
                        }

                        return defer.promise;
                    };

                    function hideKeyboard(duration) {
                        if (keyboardHiddenTimer) {
                            $timeout.cancel(keyboardHiddenTimer);
                            keyboardHiddenTimer = null;
                        }

                        if (halPlatformService.getFeatureSupport('media.webframe.onload.windowfocus') !== 'yes') {
                            return;
                        }

                        if (!duration) {
                            duration = 500;
                        }

                        // alway call once before timer;
                        window.top.focus();
                        keyboardHiddenTimer = $timeout(window.top.focus, duration);
                    }

                    var onpermissionRequest = function(e) {
                        logService.debug(LOG_KEY, 'receive permission request (' + e.permission + ') from web page : ' + e.url);
                        if (e.permission == 'loadplugin') {
                            logService.debug(LOG_KEY, 'allow loadplugin request');
                            e.request.allow();
                        } else if (e.permission == 'media') {
                            if (options && (options.allowmediarequest == true || options.allowmediarequest == 'true')) {
                                logService.debug(LOG_KEY, 'allow media request');
                                e.request.allow();
                            } else {
                                logService.debug(LOG_KEY, 'deny media request');
                                e.request.deny();
                            }
                        } else {
                            logService.debug(LOG_KEY, 'ignoring request');
                        }
                    };

                    $scope.setScrolling = function(scroll) {
                        var res;
                        if (scroll == 'true' || scroll == 'yes') {
                            res = 'yes';
                        } else if (scroll == 'false' || scroll == 'no') {
                            res = 'no';
                        } else {
                            res = 'auto';
                        }

                        $scope.scrolling = res;
                    };

                    $scope.reload = function(playerProperties) {
                        if (!dataUtil.isValueTrue(playerProperties[PLAYER_PROPERTY_KEYS.MEDIA_RELOAD_ON_RECONNECT])) {
                            return;
                        }

                        $scope.setUrl(webFrame.src);
                    };

                    $scope.setStyle = function(property, value) {
                        if (!webFrame || !property || !value) {
                            return;
                        }

                        webFrame.style[property] = value;
                    };
                }]
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var app = $angular.module('hal');

    app.service('interactivityManager', ["$window", function($window) {
        var isInteractedFlag = false;
        var isStartedFlag = false;
        var subscribers = [];

        function start() {
            if (isStartedFlag) {
                return;
            }

            isStartedFlag = true;
            if ('ontouchstart' in $window) {
                $window.addEventListener('touchstart', onInteracted);
            }

            $window.addEventListener('mousedown', onInteracted);
        }

        function stop() {
            if (!isStartedFlag) {
                return;
            }

            isStartedFlag = false;
            if ('ontouchstart' in $window) {
                $window.removeEventListener('touchstart', onInteracted);
            }

            $window.removeEventListener('mousedown', onInteracted);
        }

        function isStarted() {
            return isStartedFlag;
        }

        function isInteracted() {
            return isInteractedFlag;
        }

        function onInteracted() {
            isInteractedFlag = true;
            notifySubscribers();
        }

        function subscribe(callback) {
            if (!callback || subscribers.indexOf(callback) > -1) {
                return false;
            }
            subscribers.push(callback);
            return true;
        }

        function unsubscribe(callback) {
            var index = subscribers.indexOf(callback);
            if (index === -1) {
                return false;
            }
            subscribers.splice(index, 1);
            return true;
        }

        function notifySubscribers() {
            for (var i = 0; i < subscribers.length; i++) {
                subscribers[i]();
            }
        }

        return {
            start: start,
            stop: stop,
            isStarted: isStarted,
            isInteracted: isInteracted,
            subscribe: subscribe,
            unsubscribe: unsubscribe
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('media');

    app.service('lowBandwidthService', ["$q", "interactivityManager", "$injector", "halPlatformService", "halRemoteService", "lowBandwidthUtil", "$timeout", "dataUtil", "FEATURE_CODE", "PLAYER_PROPERTY_KEYS", function(
        $q,
        interactivityManager,
        $injector,
        halPlatformService,
        halRemoteService,
        lowBandwidthUtil,
        $timeout,
        dataUtil,
        FEATURE_CODE,
        PLAYER_PROPERTY_KEYS
    ) {
        var interactionTimeoutPromise;
        var isInteracted = false;
        var isDynamicLoadingSupported = false;
        var isServiceWorkerCachingSupported = false;
        var isFileCachingSupported = false;
        var isLowBandwidthModeSupported = false;
        var INTERACTION_TIMEOUT = 60000;
        var DEFAULT_MAX_CACHEABLE_FILE_SIZE = 300000000;
        var CDN_PROPERTY_KEY = 'player.cdn.service';
        var LOW_BANDWIDTH_MODE_PROPERTY_KEY = 'player.streaming.lowbandwidthmode';
        var APPSPACE_INSTANCE_TYPE_PROPERTY_KEY = 'appspace.instance.type';
        var ENABLE_VALUE = 'enable';
        var INSTANCE_ONPREMISE_VALUE = 'onpremise';
        var LOW_BANDWIDTH_MESSAGE = 'Device is in low bandwidth mode; video playback is disabled';
        var maxCacheableFileSize = DEFAULT_MAX_CACHEABLE_FILE_SIZE;

        function start() {
            interactivityManager.subscribe(onScreenInteracted);
            halRemoteService.registerEvents(onScreenInteracted);
            isServiceWorkerCachingSupported = getCapability('supportServiceWorkerCaching');
            isFileCachingSupported = getCapability('supportFileCaching');
            isLowBandwidthModeSupported = halPlatformService.getFeatureSupport(FEATURE_CODE.LOW_BANDWIDTH_MODE_SUPPORTED) !== false;
            isDynamicLoadingSupported = isServiceWorkerCachingSupported && isLowBandwidthModeSupported;
            maxCacheableFileSize = halPlatformService.getFeatureSupport(FEATURE_CODE.MAX_CACHEABLE_FILE_SIZE) || DEFAULT_MAX_CACHEABLE_FILE_SIZE;
        }

        function stop() {
            interactivityManager.unsubscribe(onScreenInteracted);
            halRemoteService.unRegisterEvents(onScreenInteracted);
            isDynamicLoadingSupported = false;
            isServiceWorkerCachingSupported = false;
            isFileCachingSupported = false;
            isLowBandwidthModeSupported = false;
            maxCacheableFileSize = DEFAULT_MAX_CACHEABLE_FILE_SIZE;
        }

        function cancelInteractionTimeout() {
            if (!interactionTimeoutPromise) {
                return;
            }

            $timeout.cancel(interactionTimeoutPromise);
            interactionTimeoutPromise = undefined;
        }

        function onScreenInteracted() {
            cancelInteractionTimeout();
            isInteracted = true;
            interactionTimeoutPromise = $timeout(function onInteractionTimeout() {
                isInteracted = false;
            }, INTERACTION_TIMEOUT);
        }

        function isDeviceLowBandwidth() {
            var promise = $q.defer();
            var onInstanceRetrieved = function() {
                promise.resolve(!isFileCachingSupported && isLowBandwidthModeSupported);
            };

            var onInstanceInfoNotExists = function() {
                promise.resolve(false);
            };

            getInstanceType().then(onInstanceRetrieved, onInstanceInfoNotExists);
            return promise.promise;
        }

        function getInstanceType() {
            return getProperty(APPSPACE_INSTANCE_TYPE_PROPERTY_KEY);
        }

        function isEnabled() {
            var promise = $q.defer();

            var onConfigurationRetrieved = function(value) {
                // if property exists, return based on the value
                promise.resolve(dataUtil.toLowerCase(value) === ENABLE_VALUE);
            };

            var onInstanceRetrieved = function(type) {
                // if type is on prem, disable low bandwidth
                if (type === INSTANCE_ONPREMISE_VALUE) {
                    promise.resolve(false);
                    return;
                }

                // if on public/private, continue checking CDN
                getProperty(CDN_PROPERTY_KEY).then(onCdnPropertyExists, onCdnPropertyNotExists);
            };

            var onInstanceNotExists = function() {
                promise.resolve(false);
            };

            var onConfigurationNotExists = function() {
                // if configuration doesn't exist, check if we're running onpremise or not
                getInstanceType().then(onInstanceRetrieved, onInstanceNotExists);
            };

            var onCdnPropertyExists = function() {
                // if cdn exists, disable low bandwidth mode
                promise.resolve(false);
            };

            var onCdnPropertyNotExists = function() {
                // if cdn doesn't exists, enable low bandwidth
                promise.resolve(true);
            };

            var onDeviceLowBandwidthChecked = function(isLowBandwidth) {
                // if device not in low bandwidth mode, disable low bandwidth
                if (!isLowBandwidth) {
                    promise.resolve(false);
                    return;
                }

                // check the low bandwidth configuration
                getProperty(LOW_BANDWIDTH_MODE_PROPERTY_KEY).then(onConfigurationRetrieved, onConfigurationNotExists);
            };

            isDeviceLowBandwidth().then(onDeviceLowBandwidthChecked);

            return promise.promise;
        }

        function getProperty(propertyKey) {
            var configurationService = $injector.get('configurationService');
            if (!configurationService) {
                return $q.reject();
            }

            return configurationService.getProperty(propertyKey);
        }

        function processContentItem(contentItem) {
            var promise = $q.defer();
            isEnabled().then(function(enabled) {
                // if low bandwidth is enabled & user hasn't interact, process it
                promise.resolve(enabled && !isInteracted ?
                    lowBandwidthUtil.processContentItem(contentItem, isDynamicLoadingSupported, maxCacheableFileSize) :
                    contentItem);
            });
            return promise.promise;
        }

        function resetInteraction() {
            cancelInteractionTimeout();
            isInteracted = false;
        }

        function getCapability(key) {
            return halPlatformService.getDeviceCapabilities()[key];
        }

        function getMessage() {
            return LOW_BANDWIDTH_MESSAGE +
                (isServiceWorkerCachingSupported ? ' for large files.' : ' on this device.');
        }

        function isMessageHidden() {
            var deferred = $q.defer();
            var finalizeValue = function(value) {
                deferred.resolve(dataUtil.isValueTrue(value));
            };

            var onPropertyFailedToRetrieve = function() {
                deferred.resolve(false);
            };

            getProperty(PLAYER_PROPERTY_KEYS.HIDE_LOW_BANDWIDTH_MESSAGE).then(
                finalizeValue, onPropertyFailedToRetrieve);
            return deferred.promise;
        }

        return {
            start: start,
            stop: stop,
            isEnabled: isEnabled,
            isDeviceLowBandwidth: isDeviceLowBandwidth,
            processContentItem: processContentItem,
            resetInteraction: resetInteraction,
            getMessage: getMessage,
            isMessageHidden: isMessageHidden
        };
    }]);
})(window.angular);

(function ($angular) {

    "use strict";

  var app = $angular.module('hal');
  
  app.factory("halVideoLayerManager", ["$q", "$window", "halDriverService", "halPlatformService", "logService", function($q, $window,  halDriverService, halPlatformService, logService) {

    //Maintains an array of active video layers
    var videoLayers = [];
    var maxLayers = 5;
  
    function init() {
        
      var ml = halPlatformService.getFeatureSupport("media.maxvideolayers");
      if(ml && ml.length > 0) {
          maxLayers = parseInt(ml);
      }
      
      ///maxLayers = 1;
      //get max layers from driver
      logService.debug("appspace.core.media", "VLM max layers: " + maxLayers);
    }
    
    //Gets the current active video layers
    function getActiveLayers() {
        return videoLayers;
    }
    
    function getMaxLayers() {
        return maxLayers;
    }
    
    //Checks active layers and removes any if we exceed limit
    function ensureLayerRestrictions() {
        //Keep removing layers until we meet the requirement
        while(videoLayers.length > maxLayers) {
            //Get last added target
            var oldestLayer = videoLayers[0];
            logService.debug("appspace.core.media", "VLM evict layer: " + oldestLayer.target.getSource());
            //Signal video to terminate
            oldestLayer.target.lostLayer();
            //Unregister layer
            unregisterVideoLayer(oldestLayer.target);
        }
    }
    
    function resetLayers() {
        videoLayers = [];
    }
    
    function registerVideoLayer(target) {
        //Create a new video layer target
        var layer = {
            target: target,
            time: new Date()
        }
        logService.debug("appspace.core.media", "VLM register layer: " + target.getSource());
        videoLayers.push(layer);
        //Remove layers is meet max
        ensureLayerRestrictions();
        return true;
    }
    
    function unregisterVideoLayer(target) {
        //Find the layer for this target
        for(var i = 0; i < videoLayers.length; i++) {
            var l = videoLayers[i];
            if(l.target == target) {
                //Found the target, we can remove
                videoLayers.splice(i, 1);
                logService.debug("appspace.core.media", "VLM unregister layer: " + l.target.getSource());
                return true;                
            }
        }
        //logService.trace("appspace.core.media", "VLM could not unregister layer");
        return false;
    }
    
     halDriverService.isReady().then(function(){
        //Initialise the service
        init();
     });
    
    return {
      //Public interface
      getMaxLayers: getMaxLayers,
      getActiveLayers: getActiveLayers,
      registerVideoLayer: registerVideoLayer,
      unregisterVideoLayer: unregisterVideoLayer,
      resetLayers: resetLayers
    };
  }]);
})(window.angular);
(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('hal');

    app.service('webViewManager', ["$q", "$timeout", "logService", "dataUtil", "halPlatformService", "FEATURE_CODE", function($q, $timeout, logService, dataUtil, halPlatformService, FEATURE_CODE) {
        var MAX_SAVED_COLLECTION = 5;
        var RESOLVE_DELAY_ON_DESTROY = 500;
        var DEFAULT_WIDTH = 0;
        var DEFAULT_HEIGHT = 0;
        var DEFAULT_SCROLLING_VALUE = 'no';
        var LOG_KEY = 'appspace.core.media.webviewmanager';
        var unusedWebViewCollections = {};
        var webViewCount = 0;

        function init() {
            var platformCollection = halPlatformService.getFeatureSupport(FEATURE_CODE.MAX_WEB_VIEW_COLLECTION);
            if (dataUtil.isNullOrUndefined(platformCollection)) {
                return;
            }

            MAX_SAVED_COLLECTION = platformCollection;
        }

        function getWebView(mediaDirective, configurations) {
            if (!mediaDirective) {
                return;
            }

            configurations = configurations || {};
            var collection = getCollection(mediaDirective.directive) || [];
            if (!collection.length) {
                return createWebView(mediaDirective.directive, mediaDirective.attributes, configurations);
            }

            var webView = collection.shift();
            logService.debug(LOG_KEY, 'Reusing web element ' + webView.id + ' from collection');
            webView.width = getWidth(configurations) + 'px';
            webView.height = getHeight(configurations) + 'px';
            webView.scrolling = getScrolling(configurations);
            return dataUtil.mergeObject(webView, configurations.attributes || {});
        }

        function getWidth(configurations) {
            return configurations.initialWidth || DEFAULT_WIDTH;
        }

        function getHeight(configurations) {
            return configurations.initialHeight || DEFAULT_HEIGHT;
        }

        function getScrolling(configurations) {
            return configurations.scrolling || DEFAULT_SCROLLING_VALUE;
        }

        function getAttributes(configurations) {
            if (!configurations.attributes) {
                return '';
            }

            var attributes = configurations.attributes;
            var res = [];
            for (var key in attributes) {
                if (!attributes[key]) {
                    continue;
                }

                res.push(key + '=' + '"' + attributes[key] + '"');
            }

            return res.join(' ');
        }

        function createWebView(element, attributes, configurations) {
            var newViewCount = ++webViewCount;
            var id = 'appspace-webview-' + newViewCount;
            logService.debug(LOG_KEY, 'Creating web element ' + id);
            return $angular.element('<' + element + ' '
                + 'id="' + id + '" '
                + attributes + ' '
                + 'frameBorder="0" '
                + 'width="' + getWidth(configurations) + 'px" '
                + 'height="' + getHeight(configurations) + 'px" '
                + getAttributes(configurations) + ' '
                + 'scrolling="' + getScrolling(configurations) + '"></' + element + '>')[0];
        }

        function getCollection(type) {
            return unusedWebViewCollections[type.toLowerCase()];
        }

        function saveCollection(type, webView) {
            var collection = getCollection(type);
            var currentId = webView.id;

            if (!collection) {
                logService.debug(LOG_KEY, 'Saving web element ' + currentId + ' into collection');
                unusedWebViewCollections[type.toLowerCase()] = [webView];
                return true;
            }

            // avoid adding the same directive
            for (var i = 0; i < collection.length; i++) {
                if (collection[i].id === currentId) {
                    return false;
                }
            }

            logService.debug(LOG_KEY, 'Saving web element ' + currentId + ' into collection');
            collection.push(webView);
            return true;
        }

        function releaseWebView(webView) {
            if (!webView) {
                return $q.reject('empty-webview');
            }

            var type = webView.tagName;
            var collection = getCollection(type) || [];
            var promise = $q.defer();
            disposeWebView(webView).then(function() {
                // don't do anything if we can't store anymore
                if (collection.length >= MAX_SAVED_COLLECTION) {
                    webView = undefined;
                    // hardcoded true as we already release the web view
                    promise.resolve(true);
                    return;
                }

                promise.resolve(saveCollection(type, webView));
            });

            return promise.promise;
        }

        function disposeWebView(webView) {
            var promise = $q.defer();
            webView.remove();
            webView.onload = null;
            webView.src = 'about:blank';
            $timeout(function() {
                promise.resolve(true);
            }, RESOLVE_DELAY_ON_DESTROY);
            return promise.promise;
        }

        return {
            init: init,
            getWebView: getWebView,
            releaseWebView: releaseWebView
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('media');

    app.factory('cardUtil', ["halPlatformService", "VersionCompare", "FEATURE_CODE", "halMediaService", "syncService", "PLAYER_PROPERTY_KEYS", "dataUtil", function(
        halPlatformService,
        VersionCompare,
        FEATURE_CODE,
        halMediaService,
        syncService,
        PLAYER_PROPERTY_KEYS,
        dataUtil) {
        function isStreamingCard(manifest) {
            if (!manifest) {
                return false;
            }

            manifest.Network = manifest.Network || {};
            return isCardForcedToStream(manifest) ||
                isStreamingRssCard(manifest) ||
                isDeviceForcingCardToStream(manifest);
        }

        function isCardForcedToStream(manifest) {
            return manifest.Network.Streaming && halMediaService.supportsCardStreaming();
        }

        function isStreamingRssCard(manifest) {
            var RSS_CARD_ID = 'appspace.card.rss';
            if (manifest.Id !== RSS_CARD_ID) {
                return false;
            }

            var maxVersionSupportsLocal = halPlatformService.getFeatureSupport(FEATURE_CODE.MEDIA_CARD_LOAD_RSS_LOCALLY_MAX_VERSION);
            var vc = new VersionCompare();
            // only 1.4.0 and below rss card are streamable
            // note this is a refactored code from app.controller.js
            return !vc.compare(manifest.Version, maxVersionSupportsLocal);
        }

        function isDeviceForcingCardToStream(manifest) {
            var IS_FEATURE_SUPPORTED = 'yes';
            // if the card is an online card and device doesn't support local playback
            // stream it directly
            var requiresConnection = manifest.Network.RequiresConnection;
            var forcestreamPlayback = halPlatformService.getFeatureSupport(FEATURE_CODE.MEDIA_CARD_FORCESTREAM) === IS_FEATURE_SUPPORTED;
            return Boolean(requiresConnection && forcestreamPlayback);
        }

        function getCardTimeoutDuration() {
            var timeoutDuration = syncService.getCurrentAppProperty(PLAYER_PROPERTY_KEYS.CARD_TIMEOUT_DURATION);
            if (dataUtil.isNumber(timeoutDuration)) {
                return timeoutDuration;
            }

            return halPlatformService.getFeatureSupport(FEATURE_CODE.CARD_TIMEOUT_DURATION);
        }

        function processThemeInformation(theme) {
            if (!theme || Object.keys(theme).length === 0) {
                return theme;
            }

            var EMPTY_GUID = '00000000-0000-0000-0000-000000000000';
            // if theme is not a valid guid, set themeId to empty guid
            if (theme.themeId && !dataUtil.isValidGuid(theme.themeId)) {
                theme.themeId = EMPTY_GUID;
            }

            return theme;
        }

        return {
            isStreamingCard: isStreamingCard,
            getCardTimeoutDuration: getCardTimeoutDuration,
            processThemeInformation: processThemeInformation,
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('media');

    app.factory('lowBandwidthUtil', ["CONTENT_TYPE", "syncService", "mediaUtil", "UrlUtil", function(
        CONTENT_TYPE,
        syncService,
        mediaUtil,
        UrlUtil) {
        var PREVIEW_QUALITY = 'high';
        var DEFAULT_THUMBNAIL_DURATION = 4;

        function shouldShowVideoThumbnail(contentUrl, isDynamicLoadingSupported, maxCacheableFileSize) {
            if (!isDynamicLoadingSupported) {
                return true;
            }

            var pathName = UrlUtil.getPathName(contentUrl).toLowerCase();
            if (!pathName) {
                return false;
            }

            var contentFileSizeMap = syncService.getContentFileSizeMap();
            if (!contentFileSizeMap) {
                return false;
            }

            var fileSize = contentFileSizeMap[pathName];
            // Show thumbnail if the content file size exceeds the max cacheable file size
            return fileSize && fileSize > maxCacheableFileSize;
        }

        function processContentItem(contentItem, isDynamicLoadingSupported, maxCacheableFileSize) {
            if (!contentItem || contentItem.mediaType !== CONTENT_TYPE.VIDEO) {
                return contentItem;
            }

            var mediaId = contentItem.contentId;
            if (!mediaId || !shouldShowVideoThumbnail(contentItem.mediaUrl, isDynamicLoadingSupported, maxCacheableFileSize)) {
                return contentItem;
            }

            contentItem.mediaUrl = mediaUtil.getContentPreviewUrlFromId(mediaId, PREVIEW_QUALITY);
            contentItem.mediaType = CONTENT_TYPE.LOW_BANDWIDTH_THUMBNAIL;
            // if the current content is in synchronized playback, don't modify its duration
            if (!contentItem.data || !contentItem.data.isSynchronizedPlayback) {
                contentItem.duration = DEFAULT_THUMBNAIL_DURATION;
            }

            return contentItem;
        }

        return {
            processContentItem: processContentItem
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('media');

    app.factory('mediaUtil', ["$q", "$injector", "dataUtil", "UrlUtil", "FileUtil", "SYSTEM_DATA", function(
        $q,
        $injector,
        dataUtil,
        UrlUtil,
        FileUtil,
        SYSTEM_DATA) {
        var MAX_ERROR_COUNT = 10;
        var APPSPACE_CONTENT_URL_REGEX = '/app/fastnetdata/ug(?:%20| )media/\\d+/(\\d+)/';
        var START_PREFIX_REGEX = '^';
        var CASE_INSENSITIVE_FLAG_REGEX = 'i';
        var REGISTERED_APPSPACE_CONTENT_URL = APPSPACE_CONTENT_URL_REGEX + '.+';
        var ANY_APPSPACE_CONTENT_URL_REGEX = new RegExp(START_PREFIX_REGEX + '(?:http|https)://.+' + APPSPACE_CONTENT_URL_REGEX, CASE_INSENSITIVE_FLAG_REGEX);
        var BORDER_RADIUS_KEY = 'border-radius';

        function isImageAvailable(url) {
            var promise = $q.defer();
            var img = $('<img style="display:none; position:absolute;"  />');
            img.load(function() {
                img.remove();
                promise.resolve(true);
            }).error(function() {
                img.remove();
                promise.resolve(false);
            });

            img.appendTo($('head'));
            img.attr('src', url);
            return promise.promise;
        }

        function isVideoPlayableWithAudio() {
            var promise = $q.defer();
            var errorCounter = 0;

            // There is a possiblity when launching the browser,
            // a timeout will hit when trying to play the video.
            // Will try for max 10 times before concluding the failure
            var tryPlayVideo = function() {
                canAutoplay.video().then(function(result) {
                    if (!result ||
                        (result.error && result.error instanceof DOMException) ||
                        errorCounter > MAX_ERROR_COUNT) {
                        promise.resolve(false);
                        return;
                    }

                    if (result.result) {
                        promise.resolve(true);
                        return;
                    }

                    errorCounter++;
                    tryPlayVideo();
                });
            };

            tryPlayVideo();
            return promise.promise;
        }

        function convertAttributesToOptions(attributes) {
            var options = {};
            if (!dataUtil.isObject(attributes)) {
                return options;
            }

            for (var attribute in attributes) {
                if (attribute.indexOf('$') !== -1) {
                    continue;
                }

                options[attribute] = attributes[attribute];
            }

            return options;
        }

        function getContentPreviewUrlFromId(contentId, previewType, uniqueId) {
            var rootPath = getRegisteredAppspaceUrl() + '/app/';
            var url = rootPath + 'imagepreview.axd?mid=' + contentId +
                '&profileName=' + convertPreviewTypeToHandlerValue(previewType);

            if (uniqueId) {
                url += '&_uniqueId=' + uniqueId;
            }

            return url;
        }

        function getContentPreviewUrlFromRelativePath(path) {
            return path ? getRegisteredAppspaceUrl() + path : '';
        }

        function convertPreviewTypeToHandlerValue(previewType) {
            var res = 'image-mid';
            switch ((previewType || '').toLowerCase()) {
                case 'low':
                    res = 'image-low';
                    break;
                case 'high':
                    res = 'image-high';
                    break;
            }

            return res;
        }

        function getRegisteredAppspaceUrl() {
            var coreConfigurationService = $injector.get('coreConfigurationService');
            return coreConfigurationService ?
                coreConfigurationService.get('appspace', 'appspaceURL') : '';
        }

        function getMediaIdFromUrl(url) {
            if (!url) {
                return;
            }

            var appspaceUrl = getRegisteredAppspaceUrl();
            if (!appspaceUrl) {
                return;
            }

            var match = url.match(new RegExp(
                START_PREFIX_REGEX + UrlUtil.removeTrailingSlash(appspaceUrl) + REGISTERED_APPSPACE_CONTENT_URL,
                CASE_INSENSITIVE_FLAG_REGEX));
            return match ? match[1] : undefined;
        }

        function isAppspaceContentUrl(url) {
            return !dataUtil.isNullOrUndefined(getMediaIdFromUrl(url));
        }

        function processForceIframeOption(options, property) {
            if (!options) {
                return;
            }

            options[SYSTEM_DATA.FORCE_IFRAME_KEY] = !dataUtil.isNullOrUndefined(property) && property === SYSTEM_DATA.IFRAME_ELEMENT;
        }

        function isAppspaceStreamingContentUrl(url) {
            // if URL contain valid content id
            var match = url.match(ANY_APPSPACE_CONTENT_URL_REGEX);
            return (match && match.length >= 1) || false;
        }

        function containsBorderRadius(style) {
            return Boolean(style && style[BORDER_RADIUS_KEY]);
        }

        function removesBorderRadius(style) {
            if (!containsBorderRadius(style)) {
                return style;
            }

            delete style[BORDER_RADIUS_KEY];
            return style;
        }

        function generateContentUrl(contentId, contentBaseUrl, context) {
            if (!contentId || !contentBaseUrl) {
                return;
            }

            var mediaUrl = contentBaseUrl + contentId + '/';
            mediaUrl = UrlUtil.appendDeviceInformation(mediaUrl);
            return UrlUtil.appendQueryString(mediaUrl, {
                context: context,
            });
        }

        function toCssUrl(url) {
            return url ?
                'url(\'' + FileUtil.toDoubleBackslash(url) + '\')' :
                '';
        }

        return {
            isImageAvailable: isImageAvailable,
            isVideoPlayableWithAudio: isVideoPlayableWithAudio,
            convertAttributesToOptions: convertAttributesToOptions,
            getContentPreviewUrlFromId: getContentPreviewUrlFromId,
            getContentPreviewUrlFromRelativePath: getContentPreviewUrlFromRelativePath,
            convertPreviewTypeToHandlerValue: convertPreviewTypeToHandlerValue,
            getMediaIdFromUrl: getMediaIdFromUrl,
            toCssUrl: toCssUrl,
            generateContentUrl: generateContentUrl,
            isAppspaceContentUrl: isAppspaceContentUrl,
            processForceIframeOption: processForceIframeOption,
            isAppspaceStreamingContentUrl: isAppspaceStreamingContentUrl,
            containsBorderRadius: containsBorderRadius,
            removesBorderRadius: removesBorderRadius
        };
    }]);
})(window.angular);

angular.module("media").run(["$templateCache", function($templateCache) {$templateCache.put("/modules/media/directives/asMediaLowBandwidthThumbnail.html","<as-media-image \n    on-image-load=\"onImageLoaded()\" \n    on-api-ready=\"onImageApiReady(api)\"\n    class=\"low-bandwidth-image\" \n    ng-class=\"{ \'low-bandwidth-image-blur\' : isMaskEnabled }\"\n    on-image-load-error=\"onImageLoadError(message)\"\n    stretch=\"uniformfill\">\n</as-media-image>\n<div class=\"low-bandwidth-message-container\" ng-if=\"isMaskEnabled && !hideLowBandwidthMessage\">\n    <as-message-box\n        class=\"low-bandwidth-message\"\n        message=\"lowBandwidthMessage\"\n    ></as-message-box>\n</div>");}]);
(function($angular) {
    'use strict';

    // declare network module and its dependencies
    var app = $angular.module('network', ['hal', 'core', 'ngPromiseExtras']);


    // by appending promise._cachePromises, it will applicable to code pattern of
    // var a = contentCache.prefetch()
    // a.abortPrefetch(a);
    // it will not last if code pattern of returning promise of chaining then
    // var a = contentCache.prefetch().then()
    // following update to make it happens
    app.config(["$provide", function($provide) {
        $provide.decorator('$q', ["$delegate", "$location", function($delegate, $location) {
            var defer = $delegate.defer;

            $delegate.defer = function() {
                var deferred = defer();
                var extraParam = {};

                // append extra data to promise
                deferred.promise.append = function(key, value) {
                    deferred.promise[key] = value;
                    extraParam[key] = value;
                };

                // extend promise.then for appending extra parameters
                var then = $angular.bind(deferred.promise, deferred.promise.then);
                deferred.promise.then = function(thenFn, errFn, notifyFn) {
                    var res = then(thenFn, errFn, notifyFn);
                    for (var key in extraParam) {
                        res[key] = extraParam[key];
                    }
                    return res;
                };
                return deferred;
            };
            return $delegate;
        }]);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('network');

    // todo: priority
    // other cache clear method, LRU, MDQ
    app.factory('DownloadItem', function() {
        var DownloadItem = function(repository, url, size, promise, targetPath, options) {
            this.repository = repository;
            this.url = url;
            this.size = size;
            this.promises = [];
            this.priority = 0;
            this.fileIndex = null;
            this.targetPath = targetPath;
            this.options = options || {};
            if (promise !== undefined) {
                this.addPromise(promise);
            }
        };

        DownloadItem.prototype.addPromise = function(promise) {
            this.promises.push(promise);
        };

        return DownloadItem;
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('network');

    app.factory('OrderedHash', ["$window", function($window) {
        var OrderedHash = function() {
            this.array = [];
            this.hash = {};
        };

        OrderedHash.prototype.add = function(key, obj) {
            if (!key) {
                throw new Error('Key must be supply');
            }

            if (typeof obj !== 'object') {
                throw new Error('Only type object supported');
            }

            // store internal key
            obj['_id'] = key;
            this.array.push(obj);
            this.hash[key] = obj;
        };

        OrderedHash.prototype.remove = function(key) {
            for (var i in this.array) {
                if (this.array[i]._id === key) {
                    this.array.splice(i, 1);
                }
            }

            delete this.hash[key];
        };

        OrderedHash.prototype.get = function(key) {
            return this.hash[key];
        };

        OrderedHash.prototype.getAt = function(pos) {
            return this.array[pos];
        };

        OrderedHash.prototype.pop = function() {
            if (this.array.length < 1) {
                return;
            }

            var item;
            var firstObject = this.array.splice(0, 1);

            if (firstObject.length > 0) {
                item = firstObject[0];
            }

            delete this.hash[item['_id']];
            return item;
        };

        OrderedHash.prototype.length = function() {
            return this.array.length;
        };

        OrderedHash.prototype.debug = function() {
            $window.console.log('array>>', this.array);
            $window.console.log('obj>>', this.hash);
        };

        OrderedHash.prototype.getArray = function() {
            return this.array;
        };

        OrderedHash.prototype.getHash = function() {
            return this.hash;
        };

        return OrderedHash;
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('network');

    app.factory('WebSocketClient', ["$window", "$timeout", "logService", function(
        $window,
        $timeout,
        logService
    ) {
        function WebSocketClient(server, deviceInfo) {
            var logNamespace = 'appspace.core.network';
            var url;
            var socket;
            var subscribers = [];
            var errorCount = 0;
            var numOfRetries = 6;
            var retryTimeout = 30 * 1000;
            var timeoutHandler;
            var currentRef = this;

            if (!server || typeof(server) !== 'object') {
                logService.error(logNamespace, 'WebSocket Client: No valid server provided');
                return;
            }

            url = server.protocol + server.ip + ':' + server.port + (deviceInfo ? '?deviceInfo=' + deviceInfo : '');
            logService.debug(logNamespace, 'WebSocket Client: Url set to: ', url);

            this.connect = function(server, deviceInfo) {
                cancelTimeout();

                logService.debug(logNamespace, 'WebSocket Client: Connecting to: ', url);
                socket = new $window.WebSocket(url);
                socket.addEventListener('open', onOpen);
                socket.addEventListener('message', onMessageReceive);
                socket.addEventListener('close', onClose);
                socket.addEventListener('error', onError);
            };

            this.disconnect = function() {
                if (!socket) {
                    return;
                }

                logService.info(logNamespace, 'WebSocket Client: Disconnecting from: ', url);
                cancelTimeout();
                socket.removeEventListener('open', onOpen);
                socket.removeEventListener('message', onMessageReceive);
                socket.removeEventListener('close', onClose);
                socket.removeEventListener('error', onError);
                socket.close();
            };

            this.subscribeToUpdates = function(callback) {
                if (!callback || typeof(callback) !== 'function' ||
                    subscribers.indexOf(callback) >= 0) {
                    return;
                }

                subscribers.push(callback);
                logService.trace(logNamespace, 'WebSocket Client: Callback added, current subscribers: ', subscribers.length);
            };

            this.unsubscribeFromUpdates = function(callback) {
                for (var i = 0; i < subscribers.length; i++) {
                    if (subscribers[i] == callback) {
                        subscribers.splice(i, 1);
                        logService.trace(logNamespace, 'WebSocket Client: Callback removed, current subscribers: ', subscribers.length);
                        return;
                    }
                }
            };

            function cancelTimeout() {
                if (timeoutHandler) {
                    $timeout.cancel(timeoutHandler);
                    timeoutHandler = undefined;
                }
            }

            function onOpen(event) {
                logService.debug(logNamespace, 'WebSocket Client: Connected to: ', url);
                errorCount = 0;
            }

            function onMessageReceive(event) {
                if (!event || !event.data) {
                    logService.error(logNamespace, 'WebSocket Client: Incorrect event received');
                    return;
                }

                logService.debug(logNamespace, 'WebSocket Client: Message received: ', event.data);
                var message = JSON.parse(decodeURIComponent(event.data));
                if (!message || !message.data || !message.name || message.name !== 'arc') {
                    return;
                }

                logService.debug(logNamespace, 'WebSocket Client: Notify subscribers with: ', message.data);
                notifySubscribers(message.data);
            }

            function onClose(event) {
                logService.debug(logNamespace, 'WebSocket Client: Close: ', event);
                if (errorCount > numOfRetries) {
                    logService.debug(logNamespace, 'WebSocket Client: Close for good due to: ', errorCount > 0 ? 'Error exceeded' : 'Proper socket close');
                    return;
                }

                timeoutHandler = $timeout(currentRef.connect, retryTimeout);
            }

            function onError(event) {
                logService.error(logNamespace, 'WebSocket Client: Error: ', event);
                errorCount++;
            }

            function notifySubscribers(message) {
                for (var i = 0; i < subscribers.length; i++) {
                    subscribers[i](message);
                }
            }
        }

        return (WebSocketClient);
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('network');

    app.factory('ExternalNodeWebSocketServer', ["logService", "$q", "$window", function(logService, $q, $window) {
        function ExternalNodeWebSocketServer(setupCallback, cleanupCallback) {
            var subscribers = [];
            var logKey = 'appspace.core.network';
            var socket;
            var DEFAULT_ADDRESS = 'http://127.0.0.1:55530';
            var RESTART_INTERVAL = 500;
            var MAX_RETRY_COUNT = 5;
            var ioConfig = {
                reconnection: true,
                reconnectionDelay: RESTART_INTERVAL,
                reconnectionAttempts: MAX_RETRY_COUNT
            };
            var IO_EVENT_ONMESSAGE = 'appspace-message';
            var IO_EVENT_ONCONNECT = 'connect';
            var IO_EVENT_ONDISCONNECT = 'disconnect';
            var startSocketPromise;
            var io = $window.io;

            function onSocketMessage(message) {
                logService.debug(logKey, 'Received message \'' + message + '\' at ' + (new Date()));
                for (var i = 0; i < subscribers.length; i++) {
                    var subcriber = subscribers[i];
                    subcriber(message);
                }
            }

            function onSocketOpen() {
                logService.debug(logKey, 'Start node websocket server success');
                startSocketPromise.resolve();
            }

            function onSocketClose() {
                logService.debug(logKey, 'Node websocket server closed');
            }

            function startSocket(config) {
                // If socket has been started before, return the promise directly
                if (startSocketPromise) {
                    logService.debug(logKey, 'Node websocket server already started');
                    return startSocketPromise.promise;
                }

                logService.debug(logKey, 'Starting node websocket server');
                startSocketPromise = $q.defer();
                var webSocketUrl = DEFAULT_ADDRESS;
                ioConfig.query = 'externalport=' + config.port;
                socket = io.connect(webSocketUrl, ioConfig);
                socket.on(IO_EVENT_ONCONNECT, onSocketOpen);
                socket.on(IO_EVENT_ONMESSAGE, onSocketMessage);
                socket.on(IO_EVENT_ONDISCONNECT, onSocketClose);
                return startSocketPromise.promise;
            }

            function stopSocket() {
                if (!socket) {
                    return;
                }

                logService.debug(logKey, 'Stopping node websocket server');
                socket.close();
                socket = undefined;
                startSocketPromise = undefined;
                subscribers = [];
            }

            this.start = function(config) {
                if (!setupCallback) {
                    startSocket(config);
                    return;
                }

                setupCallback().then(function() {
                    startSocket(config);
                }, $angular.noop);
            };

            this.stop = function() {
                stopSocket();
                if (!cleanupCallback) {
                    return;
                }

                cleanupCallback();
            };

            this.subscribeToUpdates = function(callback) {
                if (subscribers.indexOf(callback) > -1) {
                    return;
                }

                subscribers.push(callback);
            };

            this.unsubscribeFromUpdates = function(callback) {
                for (var i = 0; i < subscribers.length; i++) {
                    if (subscribers[i] != callback) {
                        continue;
                    }

                    subscribers.splice(i, 1);
                    break;
                }
            };
        }

        return (ExternalNodeWebSocketServer);
    }]);
})(window.angular);


(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('network');

    app.factory('BrightsignWebSocketServer', ["ExternalNodeWebSocketServer", "BrightsignSocketServer", function(ExternalNodeWebSocketServer, BrightsignSocketServer) {
        function BrightsignWebSocketServer() {
            var currentServer = getDefaultServer();

            this.start = function(config) {
                if (!config.sendBsMessage) {
                    throw new Error('sendBsMessage is not available');
                }

                currentServer = config.useUdp ?
                    new BrightsignSocketServer() :
                    new ExternalNodeWebSocketServer();
                currentServer.start(config);
            };

            this.stop = function() {
                currentServer.stop();
                currentServer = getDefaultServer();
            };

            this.onmessageReceived = function(data) {
                if (!currentServer.onmessageReceived) {
                    return;
                }

                currentServer.onmessageReceived(data);
            };

            this.subscribeToUpdates = function(callback) {
                currentServer.subscribeToUpdates(callback);
            };

            this.unsubscribeFromUpdates = function(callback) {
                currentServer.unsubscribeFromUpdates(callback);
            };

            function getDefaultServer() {
                return {
                    stop: $angular.noop,
                    onmessageReceived: $angular.noop,
                    subscribeToUpdates: $angular.noop,
                    unsubscribeFromUpdates: $angular.noop
                };
            }
        }
        return (BrightsignWebSocketServer);
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('network');

    app.factory('BrightsignSocketServer', function() {
        function BrightsignSocketServer() {
            var subscribers = [];
            var sendBsMessage = $angular.noop;
            var defers = {};

            this.start = function(config) {
                startServer(config);
            };

            this.stop = function() {
                stopServer();
                subscribers = [];
            };

            function startServer(config) {
                sendBsMessage = config.sendBsMessage;

                var callback = function(res) {
                    // console.log("startServerCallback");
                };

                sendBsMessage({
                    command: 'startServer',
                    port: config.port
                }, callback);
            }

            function stopServer() {
                var callback = function(res) {
                    // console.log("stopServerCallback");
                };

                sendBsMessage({
                    command: 'stopServer'
                }, callback);
            }

            this.onmessageReceived = function(data) {
                var id = data.id;
                id = id.toLowerCase().replace('udpservermessage_', '');
                var defer = defers[id];
                if (defer !== undefined) {
                    defer(data);
                    delete defers[id];
                    return;
                }

                notifySubscribers(data['message']);
            };

            this.subscribeToUpdates = function(callback) {
                subscribers.push(callback);
            };

            this.unsubscribeFromUpdates = function(callback) {
                for (var i = 0; i < subscribers.length; i++) {
                    if (subscribers[i] == callback) {
                        subscribers.splice(i, 1);
                        return;
                    }
                }
            };

            function notifySubscribers(message) {
                // console.log("notifySubscribers : " + message)
                for (var i = 0; i < subscribers.length; i++) {
                    var sub = subscribers[i];
                    sub(message);
                }
            }
        }
        return (BrightsignSocketServer);
    });
})(window.angular);


(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('network');

    app.factory('WebosSocketServer', ["$timeout", "logService", function($timeout, logService) {
        /**
         */
        function WebosSocketServer() {
            var subscribers = [];
            var request;
            var heartbeatInterval = 30000;
            var restartInterval = 500;
            var logKey = 'appspace.core.network';
            var heartbeatTimeout;
            var lastHeartbeat;

            /**
             * Notify subscribers
             * @param  {string} message
             */
            function notifySubscribers(message) {
                for (var i = 0; i < subscribers.length; i++) {
                    var sub = subscribers[i];
                    sub(message);
                }
            }

            /**
             * Handles response from LG services
             * @param  {Object} response
             */
            function handleResponse(response) {
                lastHeartbeat = new Date();
                switch (response.type) {
                    case 'reconnected':
                    case 'startsuccess':
                        logService.debug(logKey, 'LGWEBOS: ' + response.message + ' (' + (new Date()) + ')');
                        checkHeartbeat();
                        break;

                    case 'messagesuccess':
                        logService.debug(logKey, 'LGWEBOS: Received message \'' + response.message + '\' at ' + (new Date()));
                        notifySubscribers(response.message);
                        break;
                }
            }

            /**
             * Cancel the Heartbeat Timeout
             */
            function cancelHeartbeatTimeout() {
                if (heartbeatTimeout) {
                    $timeout.cancel(heartbeatTimeout);
                    heartbeatTimeout = null;
                }
            }

            /**
             * Check the hearbeat at LG services
             */
            function checkHeartbeat() {
                cancelHeartbeatTimeout();
                if (!lastHeartbeat || ((new Date()).getTime() - lastHeartbeat.getTime()) > (1000 + heartbeatInterval)) {
                    reconnectToService();
                } else {
                    heartbeatTimeout = $timeout(checkHeartbeat, heartbeatInterval);
                }
            }

            /**
             * Cancel the checking timer and stop the hearbeat 
             */
            function stopHeartbeat() {
                cancelHeartbeatTimeout();
                if (request) {
                    request.cancel();
                    request = null;
                }

                lastHeartbeat = null;
            }

            /**
             * Creates and call a request to a method declared in the JS service
             * @param  {string} method the name of the method
             * @param  {Object} parameters the message payload to be sent to the JS service
             * @param  {callback} success callback function for success
             * @param  {callback} failed callback function for error
             * @param  {boolean} subscribe flag to subscribe to the method
             * @return {Object} Request object
             */
            function createRequest(method, parameters, success, failed, subscribe) {
                subscribe = subscribe || false;
                var config = {
                    method: method,
                    onSuccess: success,
                    onFailure: failed,
                    subscribe: subscribe,
                    resubscribe: false,
                };

                if (parameters) {
                    config.parameters = parameters;
                }

                return webOS.service.request('luna://com.lg.app.signage.websocketserver', config);
            }

            /**
             * Connect to the LG JS service
             * @param  {object} parameters the message payload
             * @param  {function} success success callback function
             * @param  {function} failed failure callback function
             */
            function connectToService(parameters, success, failed) {
                stopHeartbeat();
                request = createRequest(
                    'start',
                    parameters,
                    success,
                    failed,
                    true);
            }

            /**
             */
            function reconnectToService() {
                logService.debug(logKey, 'LGWEBOS: Reconnecting to service.');
                var parameters = {
                    heartbeatInterval: heartbeatInterval
                };

                var success = function(response) {
                    handleResponse(response);
                };

                var failed = function() {
                    $timeout(reconnectToService, restartInterval);
                };

                connectToService(parameters, success, failed);
            }

            /**
             * Start the server with the port provided
             * @param  {int} port
             */
            function startServer(port) {
                logService.debug(logKey, 'LGWEBOS: Starting websocket server.');
                var parameters = {
                    heartbeatInterval: heartbeatInterval,
                    operation: 'start',
                    port: port
                };

                var success = function(response) {
                    if (response.type == 'startfailed') {
                        failed({errorText: response.message});
                    } else {
                        handleResponse(response);
                    }
                };

                var failed = function(err) {
                    logService.debug(logKey, 'LGWEBOS: Start server failed due to \'' + err.errorText + '\' Retrying in ' + restartInterval + 'ms.');
                    $timeout(function() {
                        startServer(port);
                    }, restartInterval);
                };

                connectToService(parameters, success, failed);
            }

            /**
             */
            function stopServer() {
                logService.debug(logKey, 'LGWEBOS: Stoping websocket server.');
                createRequest(
                    'stop',
                    function() {
                        logService.debug(logKey, 'LGWEBOS: Stop websocket server success');
                    },
                    function() {
                        logService.debug(logKey, 'LGWEBOS: Stop websocket server failed. Retrying in ' + restartInterval + 'ms.');
                        $timeout(stopServer, restartInterval);
                    }
                );
            }

            this.start = function(port) {
                startServer(port);
            };

            this.stop = function() {
                stopHeartbeat();
                stopServer();
            };

            this.subscribeToUpdates = function(callback) {
                subscribers.push(callback);
            };

            this.unsubscribeFromUpdates = function(callback) {
                for (var i = 0; i < subscribers.length; i++) {
                    if (subscribers[i] == callback) {
                        subscribers.splice(i, 1);
                        return;
                    }
                }
            };
        }

        return (WebosSocketServer);
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('network');

    app.factory('CordovaSocketServer', ["$window", "$timeout", "logService", "DateTimeUtil", function($window, $timeout, logService, DateTimeUtil) {
        /**
         * Initialization
         */
        function CordovaSocketServer() {
            var wsServer;
            var connectedSockets = [];
            var subscribers = [];
            var starting = false;
            var started = false;
            var dateTimeUtil = new DateTimeUtil();

            this.currentState = function() {
                return {started: started, starting: starting};
            };

            this.start = function(port) {
                if (cordova && cordova.plugins && cordova.plugins.wsserver) {
                    logService.info('appspace.core.network', 'Cordova socket server : started');
                    wsServer = cordova.plugins.wsserver;
                    starting = true;
                    started = false;
                    wsServer.start(port, {
                        // WebSocket Server handlers
                        'onFailure': onStop,
                        // WebSocket Connection handlers
                        'onOpen': onWebSocketOpen,
                        'onMessage': onWebSocketMessage,
                        'onClose': onWebSocketClose,
                        'tcpNoDelay': true // disables Nagle's algorithm.
                    }, function onStart(addr, port) {
                        starting = false;
                        started = true;
                        logService.debug('appspace.core.network', 'Listening on %s:%d', addr, port);
                    }, function onDidNotStart(reason) {
                        starting = false;
                        logService.warn('appspace.core.network', 'Did not start. Reason: %s', reason);
                    });
                }
            };

            var onStop = function(addr, port, reason) {
                started = false;
                logService.error('Stopped listening on %s:%d. Reason: %s', addr, port, reason);
            };

            this.stop = function(restart) {
                logService.debug('appspace.core.network', 'Stopping cordova socket server');
                if (wsServer) {
                    wsServer.stop(onStop);
                }
            };

            var onWebSocketOpen = function(conn) {
                // check for payload
                // if socket is started without any payload, the conn.resource is defaulted to '/'
                // hence the checking for .length > 1
                if (conn && conn.resource && conn.resource.length > 1) {
                    // send success message
                    sendResponse(conn.uuid);
                    // notify subscribers
                    notifySubscribers(conn.resource.substring(1));
                    // close connection, close(id, code, message)
                    wsServer.close({'uuid': conn.uuid}, 1234, 'payload sent');
                    return true;
                }

                connectedSockets.push(conn);
            };

            var onWebSocketClose = function(conn, code, reason) {
                for (var i=0; i < connectedSockets.length; i++) {
                    if (connectedSockets[i] == conn) {
                        connectedSockets.splice(i, 1);
                        break;
                    }
                }
            };

            var onWebSocketMessage = function(conn, msg) {
                for (var i=0; i < connectedSockets.length; i++) {
                    var currentSocket = connectedSockets[i];
                    if (currentSocket == conn) {
                        sendResponse(currentSocket.uuid);
                        notifySubscribers(msg);
                        break;
                    }
                }
            };

            var sendResponse = function(id) {
                if (!wsServer) {
                    return;
                }

                wsServer.send({'uuid': id}, 'success ' + (dateTimeUtil.getTimeOrigin() + $window.performance.now()));
            };

            this.subscribeToUpdates = function(callback) {
                subscribers.push(callback);
            };

            this.unsubscribeFromUpdates = function(callback) {
                for (var i = 0; i < subscribers.length; i++) {
                    if (subscribers[i] == callback) {
                        subscribers.splice(i, 1);
                        return;
                    }
                }
            };

            /**
             * Notify subscribers
             * @param  {string} message
             */
            function notifySubscribers(message) {
                for (var i = 0; i < subscribers.length; i++) {
                    var sub = subscribers[i];
                    sub(message);
                }
            }
        }

        return (CordovaSocketServer);
    }]);
})(window.angular);


(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('network');

    app.factory('UwpSocketServer', ["$q", "$timeout", "logService", function($q, $timeout, logService) {
        function UwpSocketServer() {
            // A list of connected websockets.
            var connectedSockets = [];
            var subscribers = [];
            var listener;
            var bufferSize = 1024;
            var sha1 = new Sha1();
            var logKey = 'appspace.core.network';
            var newLine = '\r\n';
            var webSocketMagicString = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
            var connectionResponseHeader = 'HTTP/1.1 101 Switching Protocols' + newLine + 'Connection: Upgrade' + newLine + 'Upgrade: websocket' + newLine + 'Sec-WebSocket-Accept: ';

            function logDebug(message) {
                logService.debug(logKey, 'UWP: ' + message);
            }

            function onError(message, err) {
                logService.debug(logKey, 'UWP ERROR: ' + message, err);
            }
            function notifySubscribers(message) {
                for (var i = 0; i < subscribers.length; i++) {
                    var sub = subscribers[i];
                    sub(message);
                }
            }

            function onConnectionReceived(e) {
                var failed = function(err) {
                    onError('Connection received failed', err);
                    complete();
                    removeSocket(socket);
                };

                var success = function(data) {
                    var response = generateConnectionResponse(data);
                    if (!response) {
                        failed('Generating response failed');
                        return;
                    }

                    complete();
                    writeConnection(socket, response);
                };

                var complete = function() {
                    reader.detachStream();
                    reader.close();
                    reader = null;
                };

                var socket = e.socket;
                connectedSockets.push(socket);
                var reader = new Windows.Storage.Streams.DataReader(socket.inputStream);
                reader.inputStreamOptions = Windows.Storage.Streams.InputStreamOptions.partial;
                readConnection(reader).then(success, failed);
            }

            function readConnection(reader) {
                var defer = $q.defer();
                var success = function() {
                    var size = reader.unconsumedBufferLength;
                    var result = reader.readString(size);
                    if (bufferSize != size) {
                        defer.resolve(result);
                    } else {
                        readConnection(reader).then(function(data) {
                            defer.resolve(result + data);
                        }, defer.reject);
                    }
                };

                reader.loadAsync(bufferSize).done(success, defer.reject);
                return defer.promise;
            }

            function writeConnection(socket, response) {
                var failed = function(err) {
                    onError('Websocket handshake failed', err);
                    complete();
                    removeSocket(socket);
                };

                var complete = function() {
                    writer.detachStream();
                    writer.close();
                    writer = null;
                };

                var listen = function() {
                    logDebug('Client connected');
                    complete();
                    onMessageReceived(socket);
                };

                var writer = new Windows.Storage.Streams.DataWriter(socket.outputStream);
                writer.unicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.utf8;
                writer.byteOrder = Windows.Storage.Streams.ByteOrder.littleEndian;
                writer.writeString(response);
                writer.storeAsync().done(listen, failed);
            }

            function onMessageReceived(socket) {
                var disconnected = function() {
                    logDebug('Client disconnected');
                    complete();
                    removeSocket(socket);
                };

                var received = function(data) {
                    if (!data || data.length == 0 || data[0] == 136) {
                        disconnected();
                        return;
                    }

                    var message = decodeMessage(data);
                    notifySubscribers(message);
                    complete();
                    var response = 'success ' + new Date().getTime();
                    sendBytes(socket, response);
                    onMessageReceived(socket);
                };

                var complete = function() {
                    reader.detachStream();
                    reader.close();
                    reader = null;
                };

                var reader = new Windows.Storage.Streams.DataReader(socket.inputStream);
                reader.inputStreamOptions = Windows.Storage.Streams.InputStreamOptions.partial;
                readBytes(reader).then(received, disconnected);
            }

            function readBytes(reader) {
                var defer = $q.defer();
                var success = function() {
                    var size = reader.unconsumedBufferLength;
                    if (size == 0) {
                        defer.resolve([]);
                        return;
                    }

                    var result = new Array(size);
                    reader.readBytes(result);
                    if (bufferSize != size) {
                        defer.resolve(result);
                    } else {
                        readBytes(reader).then(function(data) {
                            defer.resolve(result.concat(data));
                        }, defer.reject);
                    }
                };

                reader.loadAsync(bufferSize).done(success, defer.reject);
                return defer.promise;
            }

            function sendBytes(socket, message) {
                var failed = function(err) {
                    onError('Failed send message: ' + message, err);
                    complete();
                };

                var complete = function() {
                    writer.detachStream();
                    writer.close();
                    writer = null;
                };

                var writer = new Windows.Storage.Streams.DataWriter(socket.outputStream);
                writer.unicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.utf8;
                writer.byteOrder = Windows.Storage.Streams.ByteOrder.littleEndian;
                var data = encodeMessage(message);
                writer.writeBytes(data);
                writer.storeAsync().done(complete, failed);
            }

            function decodeMessage(data) {
                var datalength = data[1] & 127;
                var indexFirstMask = 2;
                if (datalength == 126) {
                    indexFirstMask = 4;
                } else if (datalength == 127) {
                    indexFirstMask = 10;
                }

                var masks = data.slice(indexFirstMask, indexFirstMask + 4);
                var i = indexFirstMask + 4;
                var index = 0;
                var output = '';
                while (i < data.length) {
                    output += String.fromCharCode(data[i++] ^ masks[index++ % 4]);
                }

                return output;
            }

            function encodeMessage(message) {
                var bytesFormatted = [];
                bytesFormatted[0] = 129;
                if (message.length <= 125) {
                    bytesFormatted[1] = message.length;
                } else if (message.length >= 126 && message.length <= 65535) {
                    bytesFormatted[1] = 126;
                    bytesFormatted[2] = ( message.length >> 8 ) & 255;
                    bytesFormatted[3] = ( message.length ) & 255;
                } else {
                    bytesFormatted[1] = 127;
                    bytesFormatted[2] = ( message.length >> 56 ) & 255;
                    bytesFormatted[3] = ( message.length >> 48 ) & 255;
                    bytesFormatted[4] = ( message.length >> 40 ) & 255;
                    bytesFormatted[5] = ( message.length >> 32 ) & 255;
                    bytesFormatted[6] = ( message.length >> 24 ) & 255;
                    bytesFormatted[7] = ( message.length >> 16 ) & 255;
                    bytesFormatted[8] = ( message.length >> 8 ) & 255;
                    bytesFormatted[9] = ( message.length ) & 255;
                }
                for (var i = 0; i < message.length; i++) {
                    bytesFormatted.push(message.charCodeAt(i));
                }

                return bytesFormatted;
            }

            function generateConnectionResponse(data) {
                if (!data || data.indexOf('GET') != 0) {
                    return;
                }

                var key = getWebSocketKey(data);
                if (!key) {
                    return;
                }

                var response = connectionResponseHeader + key + newLine + newLine;
                return response;
            }

            function getWebSocketKey(data) {
                var result;
                var match = data.match(/Sec-WebSocket-Key: (.*)/g);
                if (match.length > 0 && match[0].indexOf(': ') > 0) {
                    var i;
                    var key = match[0].split(': ')[1] + webSocketMagicString;
                    var keyBytes = [];
                    for (i = 0; i < key.length; i++) {
                        keyBytes.push(key.charCodeAt(i));
                    }

                    sha1.reset();
                    sha1.update(keyBytes);
                    var sha1Bytes = sha1.digest();
                    var sha1Hash = '';
                    for (i = 0; i < sha1Bytes.length; i++) {
                        sha1Hash += String.fromCharCode(sha1Bytes[i]);
                    }

                    result = btoa(sha1Hash);
                }

                return result;
            }

            function stopServer() {
                logDebug('Stopping websocket server');
                if (listener) {
                    listener.removeEventListener('connectionreceived', onConnectionReceived);
                    listener.close();
                    listener = null;
                }

                for (var i = 0; i < connectedSockets.length; i++) {
                    var socket = connectedSockets[i];
                    socket.close();
                    socket = null;
                }

                connectedSockets = null;
                connectedSockets = [];
                logDebug('Websocket server stopped');
            }

            function removeSocket(socket) {
                for (var i = 0; i < connectedSockets.length; i++) {
                    if (connectedSockets[i] == socket) {
                        connectedSockets.splice(i, 1);
                        socket.close();
                        socket = null;
                        return;
                    }
                }
            }

            this.start = function(port) {
                stopServer();
                logDebug('Starting websocket server');
                if (!Windows ||
                    !Windows.Networking ||
                    !Windows.Networking.Sockets ||
                    !Windows.Networking.Sockets.StreamSocketListener) {
                    return;
                }

                listener = new Windows.Networking.Sockets.StreamSocketListener(port);
                listener.addEventListener('connectionreceived', onConnectionReceived);
                listener.control.keepAlive = false;
                listener.bindServiceNameAsync(port).done(function() {
                    logDebug('Websocket server started');
                }, function() {
                    logDebug('Websocket server failed to start');
                    stopServer();
                });
            };

            this.stop = function() {
                stopServer();
            };

            this.subscribeToUpdates = function(callback) {
                subscribers.push(callback);
            };

            this.unsubscribeFromUpdates = function(callback) {
                for (var i = 0; i < subscribers.length; i++) {
                    if (subscribers[i] == callback) {
                        subscribers.splice(i, 1);
                        return;
                    }
                }
            };
        }

        return (UwpSocketServer);
    }]);
})(window.angular);

(function ($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('network');

    app.factory('ChromeSocketServer', function () {
        function ChromeSocketServer() {
            var wsServer;
            var server;
            // A list of connected websockets.
            var connectedSockets = [];
            var subscribers = [];

            this.start = function (port) {
                // console.log("chromeSocketServer : start");
                if (http.Server && http.WebSocketServer) {
                    // Listen for HTTP connections.
                    server = new http.Server();
                    wsServer = new http.WebSocketServer(server);
                    server.listen(port);
                    wsServer.addEventListener('request', wsServerRequestListener);
                }
            }

            this.stop = function () {
                // console.log("chromeSocketServer : stop");
                // clear all connected sockets
                for (var i = 0; i < connectedSockets.length; i++) {
                    var socket = connectedSockets[i];
                    socket.close();
                    socket.removeEventListener('message', socketMessageListener);
                    socket.removeEventListener('close', socketCloseListener);
                }

                connectedSockets = [];
                if (wsServer) {
                    wsServer.removeEventListener('request', wsServerRequestListener);
                }
                if (!server) {
                    return;
                }
                // Close all open sockets on the web socket server so that only one socket will be open at a time later on
                server.close();
                server = undefined;
            }

            function wsServerRequestListener(req) {
                // console.log('Client connected');
                var socket = req.accept();

                //check if URL contains payload, if yes
                if (req.headers && req.headers.url != '/'){
                    var d = new Date();
                    //1. return response
                    socket.send('success_' + d.getTime());
                    //2. update subscribers
                    notifySubscribers(req.headers.url.substring(1));
                    //3. close socket
                    socket.close();
                    return true;
                }

                connectedSockets.push(socket);

                // When a message is received on one socket, rebroadcast it on all
                // connected sockets.
                socket.addEventListener('message', function (e) {
                    socketMessageListener(e,socket);
                });

                // When a socket is closed, remove it from the list of connected sockets.
                socket.addEventListener('close', function (e) {
                    socketCloseListener(e, socket);
                });
                return true;
            }

            function socketMessageListener(e, socket) {
                // console.log('message : ' + e.data);
                for (var i = 0; i < connectedSockets.length; i++) {
                    //notify client
                    if (connectedSockets[i] == socket) {
                        var d= new Date();
                        connectedSockets[i].send('success_' + d.getTime());
                        break;
                    }
                }

                notifySubscribers(e.data);
            }

            function socketCloseListener(e, socket) {
                // console.log('Client disconnected');
                for (var i = 0; i < connectedSockets.length; i++) {
                    if (connectedSockets[i] == socket) {
                        connectedSockets.splice(i, 1);
                        break;
                    }
                }
            }

            this.subscribeToUpdates = function (callback) {
                subscribers.push(callback);
            }

            this.unsubscribeFromUpdates = function (callback) {
                for (var i = 0; i < subscribers.length; i++) {
                    if (subscribers[i] == callback) {
                        subscribers.splice(i, 1);
                        return;
                    }
                }
            }

            function notifySubscribers(message) {
                //console.log(subscribers.length)
                for (var i = 0; i < subscribers.length; i++) {
                    var sub = subscribers[i];
                    // console.log(sub);
                    sub(message);
                }
            }
        }

        return (ChromeSocketServer);
    })
})(window.angular);

/**
 * Copyright (c) 2013 The Chromium Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 **/

var http = function() {

if (!window.chrome || !window.chrome.sockets || !window.chrome.sockets.tcpServer)
    return {};

// Wrap chrome.sockets.tcp socketId with a Promise API.
var PSocket = (function() {
  // chrome.sockets.tcp uses a global listener for incoming data so
  // use a map to dispatch to the proper instance.
  var socketMap = {};
  chrome.sockets.tcp.onReceive.addListener(function(info) {
    var pSocket = socketMap[info.socketId];
    if (pSocket) {
      if (pSocket.handlers) {
        // Fulfil the pending read.
        pSocket.handlers.resolve(info.data);
        delete pSocket.handlers;
      }
      else {
        // No pending read so put data on the queue.
        pSocket.readQueue.push(info);
      }
    }
  });

  // Read errors also use a global listener.
  chrome.sockets.tcp.onReceiveError.addListener(function(info) {
    var pSocket = socketMap[info.socketId];
    if (pSocket) {
      if (pSocket.handlers) {
        // Reject the pending read.
        pSocket.handlers.reject(new Error('chrome.sockets.tcp error ' + info.resultCode));
        delete pSocket.handlers;
      }
      else {
        // No pending read so put data on the queue.
        pSocket.readQueue.push(info);
      }
    }
  });

  // PSocket constructor.
  return function(socketId) {
    this.socketId = socketId;
    this.readQueue = [];

    // Register this instance for incoming data processing.
    socketMap[socketId] = this;
    chrome.sockets.tcp.setPaused(socketId, false);
  };
})();

// Returns a Promise<ArrayBuffer> with read data.
PSocket.prototype.read = function() {
  var that = this;
  if (this.readQueue.length) {
    // Return data from the queue.
    var info = this.readQueue.shift();
    if (!info.resultCode)
      return Promise.resolve(info.data);
    else
      return Promise.reject(new Error('chrome.sockets.tcp error ' + info.resultCode));
  }
  else {
    // The queue is empty so install handlers.
    return new Promise(function(resolve, reject) {
      that.handlers = { resolve: resolve, reject: reject };
    });
  }
};

// Returns a Promise<integer> with the number of bytes written.
PSocket.prototype.write = function(data) {
  var that = this;
  return new Promise(function(resolve, reject) {
    chrome.sockets.tcp.send(that.socketId, data, function(info) {
      if (info && info.resultCode >= 0)
        resolve(info.bytesSent);
      else
        reject(new Error('chrome sockets.tcp error ' + (info && info.resultCode)));
    });
  });
};

// Returns a Promise.
PSocket.prototype.close = function() {
  var that = this;
  return new Promise(function(resolve, reject) {
    chrome.sockets.tcp.disconnect(that.socketId, function() {
      chrome.sockets.tcp.close(that.socketId, resolve);
    });
  });
};
  
// Http response code strings.
var responseMap = {
  200: 'OK',
  301: 'Moved Permanently',
  304: 'Not Modified',
  400: 'Bad Request',
  401: 'Unauthorized',
  403: 'Forbidden',
  404: 'Not Found',
  413: 'Request Entity Too Large',
  414: 'Request-URI Too Long',
  500: 'Internal Server Error'};

/**
 * Convert from an ArrayBuffer to a string.
 * @param {ArrayBuffer} buffer The array buffer to convert.
 * @return {string} The textual representation of the array.
 */
var arrayBufferToString = function(buffer) {
  var array = new Uint8Array(buffer);
  var str = '';
  for (var i = 0; i < array.length; ++i) {
    str += String.fromCharCode(array[i]);
  }
  return str;
};

/**
 * Convert from an UTF-8 array to UTF-8 string.
 * @param {array} UTF-8 array
 * @return {string} UTF-8 string
 */
var ary2utf8 = (function() {

  var patterns = [
    {pattern: '0xxxxxxx', bytes: 1},
    {pattern: '110xxxxx', bytes: 2},
    {pattern: '1110xxxx', bytes: 3},
    {pattern: '11110xxx', bytes: 4},
    {pattern: '111110xx', bytes: 5},
    {pattern: '1111110x', bytes: 6}
  ];
  patterns.forEach(function(item) {
    item.header = item.pattern.replace(/[^10]/g, '');
    item.pattern01 = item.pattern.replace(/[^10]/g, '0');
    item.pattern01 = parseInt(item.pattern01, 2);
    item.mask_length = item.header.length;
    item.data_length = 8 - item.header.length;
    var mask = '';
    for (var i = 0, len = item.mask_length; i < len; i++) {
      mask += '1';
    }
    for (var i = 0, len = item.data_length; i < len; i++) {
      mask += '0';
    }
    item.mask = mask;
    item.mask = parseInt(item.mask, 2);
  });

  return function(ary) {
    var codes = [];
    var cur = 0;
    while(cur < ary.length) {
      var first = ary[cur];
      var pattern = null;
      for (var i = 0, len = patterns.length; i < len; i++) {
        if ((first & patterns[i].mask) == patterns[i].pattern01) {
          pattern = patterns[i];
          break;
        }
      }
      if (pattern == null) {
        throw 'utf-8 decode error';
      }
      var rest = ary.slice(cur + 1, cur + pattern.bytes);
      cur += pattern.bytes;
      var code = '';
      code += ('00000000' + (first & (255 ^ pattern.mask)).toString(2)).slice(-pattern.data_length);
      for (var i = 0, len = rest.length; i < len; i++) {
        code += ('00000000' + (rest[i] & parseInt('111111', 2)).toString(2)).slice(-6);
      }
      codes.push(parseInt(code, 2));
    }
    return String.fromCharCode.apply(null, codes);
  };

})();

/**
 * Convert from an UTF-8 string to UTF-8 array.
 * @param {string} UTF-8 string
 * @return {array} UTF-8 array
 */
var utf82ary = (function() {

  var patterns = [
    {pattern: '0xxxxxxx', bytes: 1},
    {pattern: '110xxxxx', bytes: 2},
    {pattern: '1110xxxx', bytes: 3},
    {pattern: '11110xxx', bytes: 4},
    {pattern: '111110xx', bytes: 5},
    {pattern: '1111110x', bytes: 6}
  ];
  patterns.forEach(function(item) {
    item.header = item.pattern.replace(/[^10]/g, '');
    item.mask_length = item.header.length;
    item.data_length = 8 - item.header.length;
    item.max_bit_length = (item.bytes - 1) * 6 + item.data_length;
  });

  var code2utf8array = function(code) {
    var pattern = null;
    var code01 = code.toString(2);
    for (var i = 0, len = patterns.length; i < len; i++) {
      if (code01.length <= patterns[i].max_bit_length) {
        pattern = patterns[i];
        break;
      }
    }
    if (pattern == null) {
      throw 'utf-8 encode error';
    }
    var ary = [];
    for (var i = 0, len = pattern.bytes - 1; i < len; i++) {
      ary.unshift(parseInt('10' + ('000000' + code01.slice(-6)).slice(-6), 2));
      code01 = code01.slice(0, -6);
    }
    ary.unshift(parseInt(pattern.header + ('00000000' + code01).slice(-pattern.data_length), 2));
    return ary;
  };

  return function(str) {
    var codes = [];
    for (var i = 0, len = str.length; i < len; i++) {
      var code = str.charCodeAt(i);
      Array.prototype.push.apply(codes, code2utf8array(code));
    }
    return codes;
  };

})();

/**
 * Convert a string to an ArrayBuffer.
 * @param {string} string The string to convert.
 * @return {ArrayBuffer} An array buffer whose bytes correspond to the string.
 */
var stringToArrayBuffer = function(string) {
  var buffer = new ArrayBuffer(string.length);
  var bufferView = new Uint8Array(buffer);
  for (var i = 0; i < string.length; i++) {
    bufferView[i] = string.charCodeAt(i);
  }
  return buffer;
};

/**
 * An event source can dispatch events. These are dispatched to all of the
 * functions listening for that event type with arguments.
 * @constructor
 */
function EventSource() {
  this.listeners_ = {};
};

EventSource.prototype = {
  /**
   * Add |callback| as a listener for |type| events.
   * @param {string} type The type of the event.
   * @param {function(Object|undefined): boolean} callback The function to call
   *     when this event type is dispatched. Arguments depend on the event
   *     source and type. The function returns whether the event was "handled"
   *     which will prevent delivery to the rest of the listeners.
   */
  addEventListener: function(type, callback) {
    if (!this.listeners_[type])
      this.listeners_[type] = [];
    this.listeners_[type].push(callback);
  },

  /**
   * Remove |callback| as a listener for |type| events.
   * @param {string} type The type of the event.
   * @param {function(Object|undefined): boolean} callback The callback
   *     function to remove from the event listeners for events having type
   *     |type|.
   */
  removeEventListener: function(type, callback) {
    if (!this.listeners_[type])
      return;
    for (var i = this.listeners_[type].length - 1; i >= 0; i--) {
      if (this.listeners_[type][i] == callback) {
        this.listeners_[type].splice(i, 1);
      }
    }
  },

  /**
   * Dispatch an event to all listeners for events of type |type|.
   * @param {type} type The type of the event being dispatched.
   * @param {...Object} var_args The arguments to pass when calling the
   *     callback function.
   * @return {boolean} Returns true if the event was handled.
   */
  dispatchEvent: function(type, var_args) {
    if (!this.listeners_[type])
      return false;
    for (var i = 0; i < this.listeners_[type].length; i++) {
      if (this.listeners_[type][i].apply(
              /* this */ null,
              /* var_args */ Array.prototype.slice.call(arguments, 1))) {
        return true;
      }
    }
  }
};

/**
 * HttpServer provides a lightweight Http web server. Currently it only
 * supports GET requests and upgrading to other protocols (i.e. WebSockets).
 * @constructor
 */
function HttpServer() {
  EventSource.apply(this);
  this.readyState_ = 0;
}

HttpServer.prototype = {
  __proto__: EventSource.prototype,

  /**
   * Listen for connections on |port| using the interface |host|.
   * @param {number} port The port to listen for incoming connections on.
   * @param {string=} opt_host The host interface to listen for connections on.
   *     This will default to 0.0.0.0 if not specified which will listen on
   *     all interfaces.
   */
  close: function() {
    var closeSocket= function(socketId) {
      chrome.sockets.tcpServer.close(socketId);
    };
    chrome.sockets.tcpServer.getSockets(
      function(openSockets) {
        if (!openSockets.length) {
          return;
        }
        for (var i = 0; i < openSockets.length; i++) {
          closeSocket(openSockets[i].socketId);
        }
      }
    );
  },

  listen: function(port, opt_host) {
    var t = this;
    chrome.sockets.tcpServer.create(function(socketInfo) {
      chrome.sockets.tcpServer.onAccept.addListener(function(acceptInfo) {
        if (acceptInfo.socketId === socketInfo.socketId)
          t.readRequestFromSocket_(new PSocket(acceptInfo.clientSocketId));
      });
      
      chrome.sockets.tcpServer.listen(
        socketInfo.socketId,
        opt_host || '0.0.0.0',
        port,
        50,
        function(result) {
          if (!result) {
            t.readyState_ = 1;
          }
          else {
            var errorMessage = chrome.runtime && chrome.runtime.lastError && chrome.runtime.lastError.message ?
              chrome.runtime.lastError.message : '';

            console.log(
              'listen error ' +
              errorMessage +
                ' (normal if another instance is already serving requests)');
          }
        });
    });
  },

  readRequestFromSocket_: function(pSocket) {
    var t = this;
    var requestData = '';
    var endIndex = 0;
    var onDataRead = function(data) {
      requestData += arrayBufferToString(data).replace(/\r\n/g, '\n');
      // Check for end of request.
      endIndex = requestData.indexOf('\n\n', endIndex);
      if (endIndex == -1) {
        endIndex = requestData.length - 1;
        return pSocket.read().then(onDataRead);
      }

      var headers = requestData.substring(0, endIndex).split('\n');
      var headerMap = {};
      // headers[0] should be the Request-Line
      var requestLine = headers[0].split(' ');
      headerMap['method'] = requestLine[0];
      headerMap['url'] = requestLine[1];
      headerMap['Http-Version'] = requestLine[2];
      for (var i = 1; i < headers.length; i++) {
        requestLine = headers[i].split(':', 2);
        if (requestLine.length == 2)
          headerMap[requestLine[0]] = requestLine[1].trim();
      }
      var request = new HttpRequest(headerMap, pSocket);
      t.onRequest_(request);
    };

    pSocket.read().then(onDataRead).catch(function(e) {
      pSocket.close();
    });
  },

  onRequest_: function(request) {
    var type = request.headers['Upgrade'] ? 'upgrade' : 'request';
    var keepAlive = request.headers['Connection'] == 'keep-alive';
    if (!this.dispatchEvent(type, request))
      request.close();
    else if (keepAlive)
      this.readRequestFromSocket_(request.pSocket_);
  },
};

// MIME types for common extensions.
var extensionTypes = {
  'css': 'text/css',
  'html': 'text/html',
  'htm': 'text/html',
  'jpg': 'image/jpeg',
  'jpeg': 'image/jpeg',
  'js': 'text/javascript',
  'png': 'image/png',
  'svg': 'image/svg+xml',
  'txt': 'text/plain'};

/**
 * Constructs an HttpRequest object which tracks all of the request headers and
 * socket for an active Http request.
 * @param {Object} headers The HTTP request headers.
 * @param {Object} pSocket The socket to use for the response.
 * @constructor
 */
function HttpRequest(headers, pSocket) {
  this.version = 'HTTP/1.1';
  this.headers = headers;
  this.responseHeaders_ = {};
  this.headersSent = false;
  this.pSocket_ = pSocket;
  this.writes_ = 0;
  this.bytesRemaining = 0;
  this.finished_ = false;
  this.readyState = 1;
}

HttpRequest.prototype = {
  __proto__: EventSource.prototype,

  /**
   * Closes the Http request.
   */
  close: function() {
    // The socket for keep alive connections will be re-used by the server.
    // Just stop referencing or using the socket in this HttpRequest.
    if (this.headers['Connection'] != 'keep-alive')
      pSocket.close();

    this.pSocket_ = null;
    this.readyState = 3;
  },

  /**
   * Write the provided headers as a response to the request.
   * @param {int} responseCode The HTTP status code to respond with.
   * @param {Object} responseHeaders The response headers describing the
   *     response.
   */
  writeHead: function(responseCode, responseHeaders) {
    var headerString = this.version + ' ' + responseCode + ' ' +
        (responseMap[responseCode] || 'Unknown');
    this.responseHeaders_ = responseHeaders;
    if (this.headers['Connection'] == 'keep-alive')
      responseHeaders['Connection'] = 'keep-alive';
    if (!responseHeaders['Content-Length'] && responseHeaders['Connection'] == 'keep-alive')
      responseHeaders['Transfer-Encoding'] = 'chunked';
    for (var i in responseHeaders) {
      headerString += '\r\n' + i + ': ' + responseHeaders[i];
    }
    headerString += '\r\n\r\n';
    this.write_(stringToArrayBuffer(headerString));
  },

  /**
   * Writes data to the response stream.
   * @param {string|ArrayBuffer} data The data to write to the stream.
   */
  write: function(data) {
    if (this.responseHeaders_['Transfer-Encoding'] == 'chunked') {
      var newline = '\r\n';
      var byteLength = (data instanceof ArrayBuffer) ? data.byteLength : data.length;
      var chunkLength = byteLength.toString(16).toUpperCase() + newline;
      var buffer = new ArrayBuffer(chunkLength.length + byteLength + newline.length);
      var bufferView = new Uint8Array(buffer);
      for (var i = 0; i < chunkLength.length; i++)
        bufferView[i] = chunkLength.charCodeAt(i);
      if (data instanceof ArrayBuffer) {
        bufferView.set(new Uint8Array(data), chunkLength.length);
      } else {
        for (var i = 0; i < data.length; i++)
          bufferView[chunkLength.length + i] = data.charCodeAt(i);
      }
      for (var i = 0; i < newline.length; i++)
        bufferView[chunkLength.length + byteLength + i] = newline.charCodeAt(i);
      data = buffer;
    } else if (!(data instanceof ArrayBuffer)) {
      data = stringToArrayBuffer(data);
    }
    this.write_(data);
  },

  /**
   * Finishes the HTTP response writing |data| before closing.
   * @param {string|ArrayBuffer=} opt_data Optional data to write to the stream
   *     before closing it.
   */
  end: function(opt_data) {
    if (opt_data)
      this.write(opt_data);
    if (this.responseHeaders_['Transfer-Encoding'] == 'chunked')
      this.write('');
    this.finished_ = true;
    this.checkFinished_();
  },

  /**
   * Automatically serve the given |url| request.
   * @param {string} url The URL to fetch the file to be served from. This is
   *     retrieved via an XmlHttpRequest and served as the response to the
   *     request.
   */
  serveUrl: function(url) {
    var t = this;
    var xhr = new XMLHttpRequest();
    xhr.onloadend = function() {
      var type = 'text/plain';
      if (this.getResponseHeader('Content-Type')) {
        type = this.getResponseHeader('Content-Type');
      } else if (url.indexOf('.') != -1) {
        var extension = url.substr(url.indexOf('.') + 1);
        type = extensionTypes[extension] || type;
      }
      console.log('Served ' + url);
      var contentLength = this.getResponseHeader('Content-Length');
      if (xhr.status == 200)
        contentLength = (this.response && this.response.byteLength) || 0;
      t.writeHead(this.status, {
        'Content-Type': type,
        'Content-Length': contentLength});
      t.end(this.response);
    };
    xhr.open('GET', url, true);
    xhr.responseType = 'arraybuffer';
    xhr.send();
  },

  write_: function(array) {
    var t = this;
    this.bytesRemaining += array.byteLength;
    this.pSocket_.write(array).then(function(bytesWritten) {
      t.bytesRemaining -= bytesWritten;
      t.checkFinished_();
    }).catch(function(e) {
      console.error(e.message);
      return;
    });
  },

  checkFinished_: function() {
    if (!this.finished_ || this.bytesRemaining > 0)
      return;
    this.close();
  }
};

/**
 * Constructs a server which is capable of accepting WebSocket connections.
 * @param {HttpServer} httpServer The Http Server to listen and handle
 *     WebSocket upgrade requests on.
 * @constructor
 */
function WebSocketServer(httpServer) {
  EventSource.apply(this);
  httpServer.addEventListener('upgrade', this.upgradeToWebSocket_.bind(this));
}

WebSocketServer.prototype = {
  __proto__: EventSource.prototype,

  upgradeToWebSocket_: function(request) {
    if (request.headers['Upgrade'] != 'websocket' ||
        !request.headers['Sec-WebSocket-Key']) {
      return false;
    }

    if (this.dispatchEvent('request', new WebSocketRequest(request))) {
      if (request.pSocket_)
        request.reject();
      return true;
    }

    return false;
  }
};

/**
 * Constructs a WebSocket request object from an Http request. This invalidates
 * the Http request's socket and offers accept and reject methods for accepting
 * and rejecting the WebSocket upgrade request.
 * @param {HttpRequest} httpRequest The HTTP request to upgrade.
 */
function WebSocketRequest(httpRequest) {
  // We'll assume control of the socket for this request.
  HttpRequest.apply(this, [httpRequest.headers, httpRequest.pSocket_]);
  httpRequest.pSocket_ = null;
}

WebSocketRequest.prototype = {
  __proto__: HttpRequest.prototype,

  /**
   * Accepts the WebSocket request.
   * @return {WebSocketServerSocket} The websocket for the accepted request.
   */
  accept: function() {
    // Construct WebSocket response key.
    var clientKey = this.headers['Sec-WebSocket-Key'];
    var toArray = function(str) {
      var a = [];
      for (var i = 0; i < str.length; i++) {
        a.push(str.charCodeAt(i));
      }
      return a;
    }
    var toString = function(a) {
      var str = '';
      for (var i = 0; i < a.length; i++) {
        str += String.fromCharCode(a[i]);
      }
      return str;
    }

    // Magic string used for websocket connection key hashing:
    // http://en.wikipedia.org/wiki/WebSocket
    var magicStr = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';

    // clientKey is base64 encoded key.
    clientKey += magicStr;
    var sha1 = new Sha1();
    sha1.reset();
    sha1.update(toArray(clientKey));
    var responseKey = btoa(toString(sha1.digest()));
    var responseHeader = {
      'Upgrade': 'websocket',
      'Connection': 'Upgrade',
      'Sec-WebSocket-Accept': responseKey};
    if (this.headers['Sec-WebSocket-Protocol'])
      responseHeader['Sec-WebSocket-Protocol'] = this.headers['Sec-WebSocket-Protocol'];
    this.writeHead(101, responseHeader);
    var socket = new WebSocketServerSocket(this.pSocket_);
    // Detach the socket so that we don't use it anymore.
    this.pSocket_ = 0;
    return socket;
  },

  /**
   * Rejects the WebSocket request, closing the connection.
   */
  reject: function() {
    this.close();
  }
}

/**
 * Constructs a WebSocketServerSocket using the given socketId. This should be
 * a socket which has already been upgraded from an Http request.
 * @param {number} socketId The socket id with an active websocket connection.
 */
function WebSocketServerSocket(pSocket) {
  this.pSocket_ = pSocket;
  this.readyState = 1;
  EventSource.apply(this);
  this.readFromSocket_();
}

WebSocketServerSocket.prototype = {
  __proto__: EventSource.prototype,

  /**
   * Send |data| on the WebSocket.
   * @param {string|Array.<number>|ArrayBuffer} data The data to send over the WebSocket.
   */
  send: function(data) {
    // WebSocket must specify opcode when send frame.
    // The opcode for data frame is 1(text) or 2(binary).
    if (typeof data == 'string' || data instanceof String) {
      this.sendFrame_(1, data);
    } else {
      this.sendFrame_(2, data);
    }
  },

  /**
   * Begin closing the WebSocket. Note that the WebSocket protocol uses a
   * handshake to close the connection, so this call will begin the closing
   * process.
   */
  close: function() {
    if (this.readyState === 1) {
      this.sendFrame_(8);
      this.readyState = 2;
    }
  },

  readFromSocket_: function() {
    var t = this;
    var data = [];
    var message = '';
    var fragmentedOp = 0;
    var fragmentedMessages = [];

    var onDataRead = function(dataBuffer) {
      var a = new Uint8Array(dataBuffer);
      for (var i = 0; i < a.length; i++)
        data.push(a[i]);

      while (data.length) {
        var length_code = -1;
        var data_start = 6;
        var mask;
        var fin = (data[0] & 128) >> 7;
        var op = data[0] & 15;

        if (data.length > 1)
          length_code = data[1] & 127;
        if (length_code > 125) {
          if ((length_code == 126 && data.length > 7) ||
              (length_code == 127 && data.length > 14)) {
            if (length_code == 126) {
              length_code = data[2] * 256 + data[3];
              mask = data.slice(4, 8);
              data_start = 8;
            } else if (length_code == 127) {
              length_code = 0;
              for (var i = 0; i < 8; i++) {
                length_code = length_code * 256 + data[2 + i];
              }
              mask = data.slice(10, 14);
              data_start = 14;
            }
          } else {
            length_code = -1; // Insufficient data to compute length
          }
        } else {
          if (data.length > 5)
            mask = data.slice(2, 6);
        }

        if (length_code > -1 && data.length >= data_start + length_code) {
          var decoded = data.slice(data_start, data_start + length_code).map(function(byte, index) {
            return byte ^ mask[index % 4];
          });
          if (op == 1) {
            decoded = ary2utf8(decoded);
          }
          data = data.slice(data_start + length_code);
          if (fin && op > 0) {
            // Unfragmented message.
            if (!t.onFrame_(op, decoded))
              return;
          } else {
            // Fragmented message.
            fragmentedOp = fragmentedOp || op;
            fragmentedMessages.push(decoded);
            if (fin) {
              var joinMessage = null;
              if (op == 1) {
                joinMessage = fragmentedMessagess.join('');
              } else {
                joinMessage = fragmentedMessages.reduce(function(pre, cur) {
                  return Array.prototype.push.apply(pre, cur);
                }, []);
              }
              if (!t.onFrame_(fragmentedOp, joinMessage))
                return;
              fragmentedOp = 0;
              fragmentedMessages = [];
            }
          }
        } else {
          break; // Insufficient data, wait for more.
        }
      }

      return t.pSocket_.read().then(onDataRead);
    };

    this.pSocket_.read().then(function(data) {
      return onDataRead(data);
    }).catch(function(e) {
      t.close_();
    });
  },

  onFrame_: function(op, data) {
    if (op == 1 || op == 2) {
      if (typeof data == 'string' || data instanceof String) {
        // Don't do anything.
      } else if (Array.isArray(data)) {
        data = new Uint8Array(data).buffer;
      } else if (data instanceof ArrayBuffer) {
        // Don't do anything.
      } else {
        data = data.buffer;
      }
      this.dispatchEvent('message', {'data': data});
    } else if (op == 8) {
      // A close message must be confirmed before the websocket is closed.
      if (this.readyState === 1) {
        this.sendFrame_(8);
        this.readyState = 2;
      } else {
        this.close_();
        return false;
      }
    }
    return true;
  },

  sendFrame_: function(op, data) {
    var t = this;
    var WebsocketFrameData = function(op, data) {
      var ary = data;
      if (typeof data == 'string' || data instanceof String) {
        ary = utf82ary(data);
      }
      if (Array.isArray(ary)) {
        ary = new Uint8Array(ary);
      }
      if (ary instanceof ArrayBuffer) {
        ary = new Uint8Array(ary);
      }
      ary = new Uint8Array(ary.buffer);
      var length = ary.length;
      if (ary.length > 65535)
        length += 10;
      else if (ary.length > 125)
        length += 4;
      else
        length += 2;
      var lengthBytes = 0;
      var buffer = new ArrayBuffer(length);
      var bv = new Uint8Array(buffer);
      bv[0] = 128 | (op & 15); // Fin and type text.
      bv[1] = ary.length > 65535 ? 127 :
              (ary.length > 125 ? 126 : ary.length);
      if (ary.length > 65535)
        lengthBytes = 8;
      else if (ary.length > 125)
        lengthBytes = 2;
      var len = ary.length;
      for (var i = lengthBytes - 1; i >= 0; i--) {
        bv[2 + i] = len & 255;
        len = len >> 8;
      }
      var dataStart = lengthBytes + 2;
      for (var i = 0; i < ary.length; i++) {
        bv[dataStart + i] = ary[i];
      }
      return buffer;
    }
    var array = WebsocketFrameData(op, data || '');
    this.pSocket_.write(array).then(function(bytesWritten) {
      if (bytesWritten !== array.byteLength)
        throw new Error('insufficient write');
    }).catch(function(e) {
      t.close_();
    });
  },

  close_: function() {
    if (this.readyState !== 3) {
      this.pSocket_.close();
      this.readyState = 3;
      this.dispatchEvent('close');
    }
  }
};

return {
  'Server': HttpServer,
  'WebSocketServer': WebSocketServer,
};
}();

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Copyright 2005 Google Inc. All Rights Reserved.

/**
 * @fileoverview SHA-1 cryptographic hash.
 * Variable names follow the notation in FIPS PUB 180-3:
 * http://csrc.nist.gov/publications/fips/fips180-3/fips180-3_final.pdf.
 *
 * Usage:
 *   var sha1 = new goog.crypt.sha1();
 *   sha1.update(bytes);
 *   var hash = sha1.digest();
 *
 */

/**
 * SHA-1 cryptographic hash constructor.
 *
 * The properties declared here are discussed in the above algorithm document.
 * @constructor
 */
var Sha1 = function() {
  /**
   * Holds the previous values of accumulated variables a-e in the compress_
   * function.
   * @type {Array.<number>}
   * @private
   */
  this.chain_ = [];

  /**
   * A buffer holding the partially computed hash result.
   * @type {Array.<number>}
   * @private
   */
  this.buf_ = [];

  /**
   * An array of 80 bytes, each a part of the message to be hashed.  Referred to
   * as the message schedule in the docs.
   * @type {Array.<number>}
   * @private
   */
  this.W_ = [];

  /**
   * Contains data needed to pad messages less than 64 bytes.
   * @type {Array.<number>}
   * @private
   */
  this.pad_ = [];

  this.pad_[0] = 128;
  for (var i = 1; i < 64; ++i) {
    this.pad_[i] = 0;
  }

  this.reset();
};


/**
 * Resets the internal accumulator.
 */
Sha1.prototype.reset = function() {
  this.chain_[0] = 0x67452301;
  this.chain_[1] = 0xefcdab89;
  this.chain_[2] = 0x98badcfe;
  this.chain_[3] = 0x10325476;
  this.chain_[4] = 0xc3d2e1f0;

  this.inbuf_ = 0;
  this.total_ = 0;
};


/**
 * Internal helper performing 32 bit left rotate.
 * @return {number} w rotated left by r bits.
 * @private
 */
Sha1.prototype.rotl_ = function(w, r) {
  return ((w << r) | (w >>> (32 - r))) & 0xffffffff;
};


/**
 * Internal compress helper function.
 * @param {Array} buf containing block to compress.
 * @private
 */
Sha1.prototype.compress_ = function(buf) {
  var W = this.W_;

  // get 16 big endian words
  for (var i = 0; i < 64; i += 4) {
    var w = (buf[i] << 24) |
            (buf[i + 1] << 16) |
            (buf[i + 2] << 8) |
            (buf[i + 3]);
    W[i / 4] = w;
  }

  // expand to 80 words
  for (var i = 16; i < 80; i++) {
    W[i] = this.rotl_(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1);
  }

  var a = this.chain_[0];
  var b = this.chain_[1];
  var c = this.chain_[2];
  var d = this.chain_[3];
  var e = this.chain_[4];
  var f, k;

  for (var i = 0; i < 80; i++) {
    if (i < 40) {
      if (i < 20) {
        f = d ^ (b & (c ^ d));
        k = 0x5a827999;
      } else {
        f = b ^ c ^ d;
        k = 0x6ed9eba1;
      }
    } else {
      if (i < 60) {
        f = (b & c) | (d & (b | c));
        k = 0x8f1bbcdc;
      } else {
        f = b ^ c ^ d;
        k = 0xca62c1d6;
      }
    }

    var t = (this.rotl_(a, 5) + f + e + k + W[i]) & 0xffffffff;
    e = d;
    d = c;
    c = this.rotl_(b, 30);
    b = a;
    a = t;
  }

  this.chain_[0] = (this.chain_[0] + a) & 0xffffffff;
  this.chain_[1] = (this.chain_[1] + b) & 0xffffffff;
  this.chain_[2] = (this.chain_[2] + c) & 0xffffffff;
  this.chain_[3] = (this.chain_[3] + d) & 0xffffffff;
  this.chain_[4] = (this.chain_[4] + e) & 0xffffffff;
};


/**
 * Adds a byte array to internal accumulator.
 * @param {Array.<number>} bytes to add to digest.
 * @param {number} opt_length is # of bytes to compress.
 */
Sha1.prototype.update = function(bytes, opt_length) {
  if (!opt_length) {
    opt_length = bytes.length;
  }

  var n = 0;

  // Optimize for 64 byte chunks at 64 byte boundaries.
  if (this.inbuf_ == 0) {
    while (n + 64 < opt_length) {
      this.compress_(bytes.slice(n, n + 64));
      n += 64;
      this.total_ += 64;
    }
  }

  while (n < opt_length) {
    this.buf_[this.inbuf_++] = bytes[n++];
    this.total_++;

    if (this.inbuf_ == 64) {
      this.inbuf_ = 0;
      this.compress_(this.buf_);

      // Pick up 64 byte chunks.
      while (n + 64 < opt_length) {
        this.compress_(bytes.slice(n, n + 64));
        n += 64;
        this.total_ += 64;
      }
    }
  }
};


/**
 * @return {Array} byte[20] containing finalized hash.
 */
Sha1.prototype.digest = function() {
  var digest = [];
  var totalBits = this.total_ * 8;

  // Add pad 0x80 0x00*.
  if (this.inbuf_ < 56) {
    this.update(this.pad_, 56 - this.inbuf_);
  } else {
    this.update(this.pad_, 64 - (this.inbuf_ - 56));
  }

  // Add # bits.
  for (var i = 63; i >= 56; i--) {
    this.buf_[i] = totalBits & 255;
    totalBits >>>= 8;
    }

  this.compress_(this.buf_);

  var n = 0;
  for (var i = 0; i < 5; i++) {
    for (var j = 24; j >= 0; j -= 8) {
      digest[n++] = (this.chain_[i] >> j) & 255;
    }
  }

  return digest;
};

(function($angular) {
    'use strict';

    var app = $angular.module('network');
    app.service('cardContentCacheService', ["networkContentCacheService", "mediaUtil", "FileUtil", "dataUtil", "$q", "coreConfigurationService", "storageFileService", function(
        networkContentCacheService,
        mediaUtil,
        FileUtil,
        dataUtil,
        $q,
        coreConfigurationService,
        storageFileService) {
        var MANIFEST_COLLECTION_KEY = 'device.sync';
        var MANIFEST_KEY = 'cardcontentmanifest';
        var MANIFEST_KEY_DELIMITER = '|';
        var FILENAME_SEPARATOR = '.';
        var CONTENT_CACHE_REPOSITORY = 'signs.content';
        var PROMISE_STATE_FULFILLED = 'fulfilled';
        var checkLocalFileResultCache = {};
        function initialize() {
            networkContentCacheService.subscribeToPurgeDirectoryCheck(onPurgeDirectoryCheck);
        }

        function cacheByManifest(cardPath, contentNumericId, manifest) {
            var baseCardPath = getBaseCardPath(cardPath);
            if (isCardStreaming(baseCardPath) || !contentNumericId || !manifest || !manifest.items) {
                return $q.resolve();
            }

            var promise = $q.defer();
            var cachePromises = [];
            baseCardPath = FileUtil.join(baseCardPath, contentNumericId);
            var options = manifest.options || {};
            var headers = options.headers || {};
            for (var i = 0; i < manifest.items.length; i++) {
                var item = manifest.items[i];
                item.options = item.options || {};
                var mergedHeaders = dataUtil.mergeObject(headers, item.options.headers || {});
                var mergedOptions = dataUtil.mergeObject(options, item.options);
                mergedOptions.headers = mergedHeaders;
                var fileName = md5(item.contentUrl) + FILENAME_SEPARATOR + (!dataUtil.isNullOrUndefined(item.fileName) ? item.fileName : FileUtil.getBaseName(item.contentUrl));
                var fullLocalTargetPath = FileUtil.join(baseCardPath, fileName);
                var cachePromise = networkContentCacheService.cacheFile(CONTENT_CACHE_REPOSITORY, item.contentUrl, false, fullLocalTargetPath, mergedOptions).then(function(manifestItem, result) {
                    return {
                        localPath: result,
                        manifestItem: manifestItem
                    };
                }.bind(undefined, item));
                cachePromises.push(cachePromise);
            }

            // if 1 or more promises got rejected, reject the main promise
            $q.allSettled(cachePromises).then(function(results) {
                var containsError = false;
                var newContentManifestKeys = [];
                var contentManifest = getManifestFromCache();
                for (var k = 0; k < results.length; k++) {
                    var promiseResult = results[k];
                    if (promiseResult.state !== PROMISE_STATE_FULFILLED) {
                        containsError = true;
                        continue;
                    }

                    var result = promiseResult.value;
                    var contentUrl = result.manifestItem.contentUrl;
                    var manifestKey = getManifestItemKey(contentUrl, contentNumericId);
                    contentManifest[manifestKey] = {
                        localPath: result.localPath,
                        remotePath: contentUrl,
                        contentNumericId: contentNumericId,
                        key: manifestKey,
                        baseCardPath: baseCardPath
                    };
                    newContentManifestKeys.push(manifestKey);
                }

                // delete unused content
                var keysToBeDeleted = [];
                for (var key in contentManifest) {
                    var item = contentManifest[key];
                    // skip for other cards OR the item exists in the new manifest
                    if (item.contentNumericId !== contentNumericId || newContentManifestKeys.indexOf(item.key) !== -1) {
                        continue;
                    }

                    // flag for clean up
                    keysToBeDeleted.push(item.key);
                }

                var cleanPromises = [];
                for (var i = 0; i < keysToBeDeleted.length; i++) {
                    var cleanUpItem = contentManifest[keysToBeDeleted[i]];
                    cleanPromises.push(networkContentCacheService.removeFile(CONTENT_CACHE_REPOSITORY, cleanUpItem.localPath));
                    cleanUpManifestByKey(cleanUpItem.key, contentManifest);
                }

                $q.allSettled(cleanPromises).then(function() {
                    saveManifestToCache(contentManifest);
                    promise[containsError ? 'reject' : 'resolve']();
                });
            }, promise.reject);

            return promise.promise;
        }

        function resolveContentUrl(cardPath, contentNumericId, contentUrl) {
            var baseCardPath = getBaseCardPath(cardPath);
            if (isCardStreaming(baseCardPath)) {
                return $q.reject('Card is streaming');
            }

            var promise = $q.defer();
            var contentManifest = getManifestFromCache();
            var manifestKey = getManifestItemKey(contentUrl, contentNumericId);
            var manifestItem = contentManifest[manifestKey];
            if (!manifestItem) {
                return $q.reject('Unable to find file in manifest');
            }

            var cachedResult = checkLocalFileResultCache[manifestKey];
            if (cachedResult) {
                return $q.resolve(cachedResult);
            }

            storageFileService.checkFile(manifestItem.localPath).then(function(item, result) {
                if (!result.exists) {
                    promise.reject('Unable to find local file in storage');
                    return;
                }

                var relativePath = item.localPath.replace(baseCardPath, '');
                checkLocalFileResultCache[item.key] = relativePath;
                promise.resolve(relativePath);
            }.bind(undefined, manifestItem), promise.reject);
            return promise.promise;
        }

        function getManifestItemKey(contentPath, cardId) {
            return cardId + MANIFEST_KEY_DELIMITER + contentPath;
        }

        function isCardStreaming(cardPath) {
            return !cardPath || mediaUtil.isAppspaceStreamingContentUrl(cardPath);
        }

        function getBaseCardPath(cardPath) {
            return cardPath ? FileUtil.getContentPath(cardPath) : undefined;
        }

        function getManifestFromCache() {
            var manifestString = coreConfigurationService.get(MANIFEST_COLLECTION_KEY, MANIFEST_KEY);
            var defaultValue = {};
            if (!manifestString) {
                return defaultValue;
            }

            return dataUtil.tryParseJSON(manifestString) || defaultValue;
        }

        function saveManifestToCache(manifest) {
            coreConfigurationService.set(MANIFEST_COLLECTION_KEY, MANIFEST_KEY, JSON.stringify(manifest));
        }

        function onPurgeDirectoryCheck(directory) {
            if (!directory) {
                return false;
            }

            var directoryToCheck = dataUtil.toLowerCase(directory);
            var manifest = getManifestFromCache();
            // check if cache service is trying to delete our downloaded card content
            var keysToBeDeleted = [];
            for (var key in manifest) {
                var item = manifest[key];
                var basePath = dataUtil.toLowerCase(item.baseCardPath);
                // we're still using this folder, return true to flag cache service to not clean
                if (basePath === directoryToCheck) {
                    return true;
                }

                if (basePath.indexOf(directoryToCheck) !== 0) {
                    continue;
                }

                // if cache service is trying to delete the card itself, mark for clean up
                keysToBeDeleted.push(item.key);
            }

            if (keysToBeDeleted.length) {
                for (var i = 0; i < keysToBeDeleted.length; i++) {
                    var keyToBeDeleted = keysToBeDeleted[i];
                    cleanUpManifestByKey(keyToBeDeleted, manifest);
                }

                saveManifestToCache(manifest);
            }

            // we're not using this directory, return false
            return false;
        }

        function cleanUpManifestByKey(key, manifest) {
            delete manifest[key];
            delete checkLocalFileResultCache[key];
        }

        return {
            cacheByManifest: cacheByManifest,
            resolveContentUrl: resolveContentUrl,
            initialize: initialize,
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('network');

    app.service('cdnService', ["$q", "$timeout", "CDN", "CDN_EVENT", "HttpWrapper", "logService", "halPlatformService", "coreConfigurationService", "dataUtil", "TimeoutUtil", "AppspaceUrlUtil", function(
        $q,
        $timeout,
        CDN,
        CDN_EVENT,
        HttpWrapper,
        logService,
        halPlatformService,
        coreConfigurationService,
        dataUtil,
        TimeoutUtil,
        AppspaceUrlUtil) {
        var subscribers = [];
        var SMART_HUB_PATH = '/smarthubservice/ConfigScriptHandler.ashx?operation=smarthub&id={{device-id}}';
        var PROPERTY_KEY = {
            SYSTEM_CDN_SERVICE: 'player.system.cdn.service.',
            SYSTEM_CDN_PING_FILE: 'player.system.cdn.pingfile.',
            CDN_SERVICE: 'player.cdn.service',
            CDN_PING_FILE: 'player.cdn.pingfile'
        };
        var CATEGORY = {
            SYSTEM: 'system',
            PROPERTY: 'property',
            BLOCK: 'block',
            SMART_HUB: 'smarthub'
        };
        var FORCE_CDN_KEY = 'feature.device.cdn.force';
        var CDN_CONFIG = {
            SYSTEM: {
                name: 'cdn-system',
                category: CATEGORY.SYSTEM
            },
            PROPERTY: {
                name: 'cdn-property',
                category: CATEGORY.PROPERTY
            },
            BLOCK: {
                category: CATEGORY.BLOCK
            },
            SMART_HUB: {
                category: CATEGORY.SMART_HUB
            }
        };

        function resolveUrl(url, appspaceUrl, cdnRoute, cdnType) {
            if (!cdnRoute || cdnRoute.length == 0) {
                return;
            }

            var result;
            var serviceUrl = cdnRoute.charAt(cdnRoute.length - 1) == '/' ? cdnRoute.substr(0, cdnRoute.length - 1) : cdnRoute;
            var serviceType = cdnType || 'cdn';

            // if http and https are not present in the service url, use appspace's protocol
            if (serviceUrl.toLowerCase().indexOf('http:') == -1 && serviceUrl.toLowerCase().indexOf('https:') == -1) {
                var protocol = (appspaceUrl.toLowerCase().indexOf('http:') > -1) ? 'http://' : 'https://';
                serviceUrl = protocol + serviceUrl;
            }

            // smarthub only support appspace local content (not local content = imagepreview)
            if (serviceType == 'smarthub' && url.toLowerCase().indexOf('/fastnetdata/') > -1) {
                result = url.split(appspaceUrl + '/app/').join(serviceUrl + '/medianet/');
            } else if (serviceType == 'cdn') {// cdn support both appspace local contents & image preview
                result = url.split(appspaceUrl).join(serviceUrl);
            }

            return result;
        }

        // public method
        function setConfiguration(config) {
            var interval = 30;
            if (config.interval && !isNaN(config.interval)) {
                interval = config.interval * 1000;
            }

            if (monitorInterval && interval != monitorInterval) {
                monitorInterval = interval;
                startTimer(1);
            }

            var timeout = 15000;
            if (config.timeout && !isNaN(config.timeout)) {
                timeout = config.timeout * 1000;
            }

            requestTimeoutDuration = timeout;
        }

        function getCDNs() {
            return cdns;
        }

        function getActiveCDN() {
            var res;
            for (var i = 0; i < cdns.length; i++) {
                var cdn = cdns[i];
                if (cdn.isOnline == true) {
                    res = angular.copy(cdn);
                    res.url = AppspaceUrlUtil.replaceAppspaceUrl(res.url) || res.url;
                    res.url = AppspaceUrlUtil.replaceAppspaceContentHost(res.url) || res.url;
                    break;
                }
            }

            return res;
        }

        function start() {
            cdns = [];
            if (!monitorInterval) {
                monitorInterval = 30000;
            }

            if (!requestTimeoutDuration) {
                requestTimeoutDuration = 15000;
            }

            logKey = 'appspace.cdn.cdnservice';
            monitorTimeoutHandler = undefined;
            wrapper = new HttpWrapper();
            startTimer();
        }

        function addCDN(cdn) {
            removeCDN(cdn);

            if (cdns[cdn.priority]) {
                cdn.priority++;
            }

            cdns.splice(cdn.priority, 0, cdn);
        }

        function removeCDN(cdn) {
            var indexToReplace = -1;
            for (var i = 0; i < cdns.length; i++) {
                var cdnObject = cdns[i];
                if (cdnObject.pingUrl == cdn.pingUrl) {
                    indexToReplace = i;
                    break;
                }
            }

            if (indexToReplace != -1) {
                cdns.splice(indexToReplace, 1);
            }
        }

        function removeAllCDN() {
            cdns = [];
        }

        function forceCheckCDN(requestTimeout) {
            var deferred = $q.defer();

            if (!requestTimeout) {
                requestTimeout = requestTimeoutDuration;
            }

            // if currently checking, resolve when everything is done
            if (isChecking == true) {
                pendingForcePromise = deferred;
            } else {
                pendingForcePromise = null;

                doStartCheckCDN(requestTimeout).then(function() {
                    deferred.resolve(getActiveCDN());
                }, function(err) {
                    deferred.reject(err);
                });
            }

            return deferred.promise;
        }

        // private vars
        var cdns = [];
        var monitorInterval;
        var monitorTimeoutHandler;
        var wrapper;
        var currentCheck = 0;
        var requestTimeoutDuration;
        var useTimeoutDuration;
        var isChecking = false;
        var pendingForcePromise = null;
        var logKey;
        // private method
        function startTimer(interval) {
            clearTimer();
            if (!interval) {
                interval = monitorInterval;
            }

            monitorTimeoutHandler = $timeout(checkCDN, interval);
        }

        function clearTimer() {
            TimeoutUtil.cancel(monitorTimeoutHandler);
            monitorTimeoutHandler = undefined;
        }

        function checkCDN() {
            clearTimer();

            if (isChecking == true) {
                startTimer();
                return;
            }

            doStartCheckCDN(requestTimeoutDuration).then(function() {
                startTimer();
            }, function(err) {
                startTimer();
            });
        }

        function doStartCheckCDN(timeoutDuration) {
            var deferred = $q.defer();
            currentCheck = 0;
            isChecking = true;
            useTimeoutDuration = timeoutDuration;
            doCheckCDN(cdns[currentCheck], deferred);
            return deferred.promise;
        }

        function doCheckCDN(cdn, promise) {
            if (!cdn || !cdn.pingUrl) {
                isChecking = false;
                if (pendingForcePromise) {
                    pendingForcePromise.resolve(getActiveCDN());
                    pendingForcePromise = null;
                }

                promise.resolve();
                return;
            }

            var onCheckCompleted = function(isOnline) {
                cdns[currentCheck].isOnline = isOnline;
                logService.debug(logKey, 'Is CDN "' + cdn.name + '" online: ' + cdns[currentCheck].isOnline);
                doCheckCDN(cdns[++currentCheck], promise);
                if (isOnline || cdn.category !== CATEGORY.SYSTEM) {
                    return;
                }

                notifySubscribers(CDN_EVENT.SYSTEM_CDN_FAILURE);
            };

            wrapper.handlerGet(cdn.pingUrl, useTimeoutDuration).then(function(res) {
                logService.debug(logKey, 'Pinging "' + cdn.name + '" (' + res.config.url + ')');
                var isOnline = false;
                if (cdn.type == 'smarthub') {
                    isOnline = (res.data && (res.data.toLowerCase() == 'ready'));
                } else {
                    isOnline = (res.status == 200);
                }

                onCheckCompleted(isOnline);
            }, function() {
                onCheckCompleted(false);
            });
        }

        function createCDNObject(config) {
            var cdn = new CDN();
            cdn.type = 'cdn';
            cdn.category = config.category;
            cdn.name = config.name || 'cdn-' + new Date().getTime();
            cdn.priority = config.priority;
            cdn.url = convertCDNDTOUrl(config.url);
            cdn.pingUrl = config.pingFile ? joinUrl(cdn.url, config.pingFile) : cdn.pingUrl;
            return cdn;
        }

        function joinUrl(url1, url2) {
            // remove trailing / from url1
            if (url1.charAt(url1.length - 1) == '/') {
                url1 = url1.substring(0, url1.length - 1);
            }

            // remove leading / from url2
            if (url2.charAt(0) == '/') {
                url2 = url2.substring(1);
            }

            return url1 + '/' + url2;
        }

        function convertCDNDTOUrl(ip) {
            var res = '';
            // if IP doesn't contain http or https or not using appspace URL replacement, use protocol from appspace
            if (ip.indexOf('http://') == -1 && ip.indexOf('https://') == -1 && ip.indexOf('{{appspace-url}}') == -1) {
                var appspace = coreConfigurationService.get('appspace', 'appspaceURL').toLowerCase();
                res = appspace.indexOf('http:') > -1 ? 'http://' : 'https://';
            }

            res += ip;

            return res;
        }

        function notifySubscribers(event) {
            for (var i = 0; i < subscribers.length; i++) {
                subscribers[i](event);
            }
        }

        function subscribeToEvents(callback) {
            if (!callback || subscribers.indexOf(callback) > -1) {
                return;
            }

            subscribers.push(callback);
        }

        function unsubscribeToEvents(callback) {
            if (!callback) {
                return;
            }

            for (var i = 0; i < subscribers.length; i++) {
                if (subscribers[i] !== callback) {
                    continue;
                }

                subscribers.splice(i, 1);
                break;
            }
        }

        function convertSmarthubDTOUrl(ip, port) {
            var res = '';

            if (ip.indexOf('http://') == -1 && ip.indexOf('https://') == -1) {
                res = 'http://';
            }

            res += ip;

            if (port) {
                res += ':' + port;
            }

            return res;
        }

        function configureCDNs(appProperties, licenses, cdnBlock) {
            var add = function(name, url, pingFile, category, pingUrl) {
                var cdnObject = createCDNObject({
                    name: name,
                    priority: configuredCDNs.length,
                    url: url,
                    pingFile: pingFile,
                    category: category,
                    pingUrl: pingUrl
                });
                addCDN(cdnObject);
            };

            var configurePropertyCdn = function(cdnKey, failureCallback) {
                var cdnConfig = CDN_CONFIG[cdnKey];
                if (!cdnPropertyUrl || !cdnPropertyPingFile) {
                    if (failureCallback) {
                        failureCallback();
                    }

                    return;
                }

                add(cdnConfig.name, cdnPropertyUrl, cdnPropertyPingFile, cdnConfig.category);
            };

            var configuredCDNs = [];
            var cdnPropertyUrl;
            var cdnPropertyPingFile;
            // Retrieve CDNs from properties
            if (appProperties) {
                var integrationType = halPlatformService.getIntegrationType();
                if (dataUtil.isString(integrationType)) {
                    integrationType = integrationType.toLowerCase();
                }

                // Retrieve system CDN
                if (licenses && licenses[FORCE_CDN_KEY] && integrationType) {
                    cdnPropertyUrl = appProperties[PROPERTY_KEY.SYSTEM_CDN_SERVICE + integrationType];
                    cdnPropertyPingFile = appProperties[PROPERTY_KEY.SYSTEM_CDN_PING_FILE + integrationType];
                    configurePropertyCdn('SYSTEM', function() {
                        notifySubscribers(CDN_EVENT.SYSTEM_CDN_UNCONFIGURED);
                    });
                }

                // Retrieve property CDN
                cdnPropertyUrl = appProperties[PROPERTY_KEY.CDN_SERVICE];
                cdnPropertyPingFile = appProperties[PROPERTY_KEY.CDN_PING_FILE];
                configurePropertyCdn('PROPERTY');
            }

            if (cdnBlock) {
                // Retrieve block CDNs
                var cdnBlockConfig = CDN_CONFIG.BLOCK;
                for (var i = 0; i < cdnBlock.cdns.length; i++) {
                    var dto = cdnBlock.cdns[i];
                    add(dto.name, dto.ip, dto.pingFile, cdnBlockConfig.category);
                }

                // Retrieve smart hub CDNs
                var smartHubConfig = CDN_CONFIG.SMART_HUB;
                for (var j = 0; j < cdnBlock.smartHubs.length; j++) {
                    var dtoSmarthub = cdnBlock.smartHubs[j];
                    var cdnObjectSmarthub = new CDN();
                    add(dtoSmarthub.name,
                        convertSmarthubDTOUrl(dtoSmarthub.ip, dtoSmarthub.port),
                        undefined,
                        smartHubConfig.category,
                        cdnObjectSmarthub.url + SMART_HUB_PATH);
                }
            }

            return $q.resolve();
        }

        // public interface
        return {
            setConfiguration: setConfiguration,
            getCDNs: getCDNs,
            getActiveCDN: getActiveCDN,
            addCDN: addCDN,
            removeCDN: removeCDN,
            removeAllCDN: removeAllCDN,
            forceCheckCDN: forceCheckCDN,
            start: start,
            resolveUrl: resolveUrl,
            createCDNObject: createCDNObject,
            configureCDNs: configureCDNs,
            subscribeToEvents: subscribeToEvents,
            unsubscribeToEvents: unsubscribeToEvents
        };
    }]);
})(window.angular);

(function ($angular) {
  var angular = $angular;
  'user strict';

  var app = angular.module('network');

  app.factory('CDN', function () {

    function CDN() {
      this.name;
      this.type;
      this.priority;
      this.url = '';
      this.pingUrl;
      this.port;

      //private vars
      this.hash;
      this.isOnline = false;
    };

    return (CDN);
  });
})(window.angular);
(function($angular) {
    'use strict';

    var app = $angular.module('network');

    app.factory('networkContentRouterService', ["$q", "$window", function($q, $window) {
        function init() {
        }

        var services = {};

        // Initialise the service
        init();

        // Resolves the url
        function resolve(url, serviceName, opt) {
            if (url) {
                url = url.trim();
            }

            if (!url) {
                return $q.reject('invalid url');
            }
            // Find the service for this resolve
            var service = services[serviceName];
            if (service === undefined) {
                // return original url as there is no service found
                return $q.resolve(url);
            }

            var defer = $q.defer();
            // We have processed the maps and have a mapped url
            var onMapsProcessed = function(mappedUrl) {
                processRoutes(mappedUrl, service, opt).then(onRoutesProcessed);
            };

            // We have routed the url
            var onRoutesProcessed = function(routedUrl) {
                // Send back the url
                defer.resolve(routedUrl);
            };

            // Start processing the url
            processMaps(url, service, opt).then(onMapsProcessed);
            return defer.promise;
        }

        function processMaps(url, service, opt) {
            var defer = $q.defer();
            // Go through service maps and apply their transforms
            if (service.maps && service.maps.length > 0) {
                // As we process each map sequentially we maintain am index into which one we are processing
                var index = 0;
                var currentUrl = url;

                // On map fail
                var mapUrlError = function(err) {
                    // Map the next one using the last known url
                    mapUrlAsync(currentUrl);
                };

                // Takes a url, executes a map, and passes to the next map function
                var mapUrlAsync = function(url) {
                    // Update current url
                    currentUrl = url;
                    // Check have we executed all maps
                    if (index >= service.maps.length) {
                        // No more maps, resolve the final url
                        defer.resolve(url);
                        return;
                    }
                    // Get the next map to execute
                    var map = service.maps[index++];
                    // Execute the map and on success or failure goto the next map to process
                    map.map(url, opt).then(mapUrlAsync, mapUrlError);
                };

                // Start the mapping process
                mapUrlAsync(currentUrl);
            } else {
                defer.resolve(url);
            }
            return defer.promise;
        }

        function processRoutes(url, service, opt) {
            var defer = $q.defer();

            // Go through service routes and find one which matches
            if (service.routes && service.routes.length > 0) {
                // As we process each route sequentially in order of priority we maintain am index into which one we are processing
                var index = 0;

                // On route pass
                var routeUrlPass = function(url) {
                    // This route has been selected
                    defer.resolve(url);
                };

                var routeUrlFail = function(url) {
                    // Check next route
                    checkRoute();
                };

                // Takes a url, executes a route, and passes to the next map function
                var checkRoute = function() {
                    // Check have we executed all routes
                    if (index >= service.routes.length) {
                        // No more routes, resolve the final url
                        defer.resolve(url);
                        return;
                    }
                    // Get the next route to execute
                    var route = service.routes[index++];
                    // Execute the route and on success resolve, on failure goto the next route to process
                    route.route(url, opt).then(routeUrlPass, routeUrlFail);
                };

                // Start the mapping process
                checkRoute();
            } else {
                defer.resolve(url);
            }
            return defer.promise;
        }

        // Adds a service that will be used by the resolve process
        function addService(service) {
            // Check its a valid service
            if (service.name === undefined) {
                return false;
            }

            // Ensure the service has the required properties intialised
            if (service.maps === undefined || service.maps === null) {
                service.maps = [];
            }
            if (service.routes === undefined || service.routes === null) {
                service.routes = [];
            }
            if (service.policies === undefined || service.policies === null) {
                service.policies = [];
            }

            // Add or overwrite the existing service
            services[service.name] = service;

            // Resort routes incase priority changed
            sortServiceRoutes(service);
            return true;
        }

        // Removes a service
        function removeService(serviceName) {
            if (services[serviceName] === undefined) {
                return false;
            }
            services[serviceName] = undefined;
        }

        // Find a service based on name
        function findService(serviceName) {
            return services[serviceName];
        }

        // Add a map to a service
        function addMap(serviceName, map) {
            var service = services[serviceName];
            if (service === undefined) {
                return false;
            }

            // Check the map does not already exist, if so update
            for (var i = 0; i < service.maps.length; i++) {
                var m = service.maps[i];
                if (m.name == map.name) {
                    service.maps[i] = map;
                    return true;
                }
            }
            // Just add the map to the collection as we didnt find it
            service.maps.push(map);
        }

        // Remove an existing map from a service
        function removeMap(serviceName, map) {
            var service = services[serviceName];
            if (service === undefined) {
                return false;
            }
            // Check the map does not already exist, if so update
            for (var i = 0; i < service.maps.length; i++) {
                var m = service.maps[i];
                if (m.name == map.name) {
                    service.maps.splice(i, 1);
                    return true;
                }
            }
            // Could not be found
            return false;
        }

        // Add a route to a service
        function addRoute(serviceName, route) {
            var service = services[serviceName];
            if (service === undefined) {
                return false;
            }

            // Check the route does not already exist, if so update
            for (var i = 0; i < service.routes.length; i++) {
                var r = service.routes[i];
                if (r.name == route.name) {
                    service.routes[i] = route;
                    // Resort routes incase priority changed
                    sortServiceRoutes(service);
                    return true;
                }
            }
            // Just add the maprouteto the collection as we didnt find it
            service.routes.push(route);
            // Resort routes incase priority changed
            sortServiceRoutes(service);
        }

        // Remove an existing route from a service
        function removeRoute(serviceName, route) {
            var service = services[serviceName];
            if (service === undefined) {
                return false;
            }
            // Check the route does not already exist, if so update
            for (var i = 0; i < service.routes.length; i++) {
                var r = service.routes[i];
                if (r.name == route.name) {
                    service.routes.splice(i, 1);
                    return true;
                }
            }
            // Could not be found
            return false;
        }

        function sortServiceRoutes(service) {
            service.routes.sort(function(a, b) {
                return a.priority - b.priority;
            });
        }

        return {
            // Public interface
            resolve: resolve,
            addService: addService,
            removeService: removeService,
            findService: findService,
            addMap: addMap,
            removeMap: removeMap,
            addRoute: addRoute,
            removeRoute: removeRoute,
        };
    }]);
})(window.angular);

(function ($angular) {

    "use strict";

  var app = $angular.module('network');

  app.factory("networkFileTransferService", ["$q", "$window", "halNetworkService", "logService", function($q, $window, halNetworkService, logService) {

    function init() {
    }

    //Initialise the service
    init();


    function downloadHttp(srcUrl, destPath, downloadItem) {
      var defer = $q.defer();
      var p = halNetworkService.downloadHttp(srcUrl, destPath, downloadItem);
      if(p === null) {
        //Not implemented by driver
        defer.reject("Not implemented");
      }
      else {
        p.then(
          function(res) {
            logService.trace("as-core.network", "Downloaded file: " + srcUrl + " to " + destPath);
            defer.resolve(res);
          }
          ,function (err) {
            logService.warn("as-core.network", "Failed to download file: " + srcUrl + " to " + destPath);
            defer.reject(err);
          }
        );
      }
      return defer.promise;
    }

    return {
      //Public interface
      downloadHttp: downloadHttp
    };

  }]);
})(window.angular);
(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('network');

    app.service('httpService', ["$q", "HttpWrapper", function($q, HttpWrapper) {
        var requireRandomParam = {};
        var httpWrapper = new HttpWrapper();

        function generateNoCacheId(url) {
            if (!url || !requireRandomParam[url]) {
                return '';
            }

            var operator = url.indexOf('?') !== -1 ? '&' : '?';
            return operator + '_noCacheId=' + new Date().getTime();
        }

        function isNoCacheHeaderExists(headers) {
            if (!headers || !headers['cache-control']) {
                return false;
            }

            return headers['cache-control'].indexOf('no-cache, no-store') !== -1;
        }

        function get(url) {
            var promise = $q.defer();
            httpWrapper.handlerGet(url + generateNoCacheId(url)).then(function(res) {
                requireRandomParam[url] = !isNoCacheHeaderExists(res.headers());
                promise.resolve(res);
            }, promise.reject);
            return promise.promise;
        }

        return {
            get: get
        };
    }]);
})(window.angular);

(function ($angular) {
    'use strict';

    var app = $angular.module('network');
    app.factory('networkContentCacheService', ["$q", "$filter", "$interval", "storageFileService", "networkFileTransferService", "coreConfigurationService", "DownloadItem", "OrderedHash", "logService", "dataUtil", "UrlUtil", "FileUtil", "pathService", "halDriverService", "networkContentRouterService", "cdnService", "storageLocalStorageService", "halMediaService", "halNetworkService", "halPlatformService", "CONTENT_ACCESS_CONTEXT", function ($q, $filter, $interval, storageFileService, networkFileTransferService,
        coreConfigurationService, DownloadItem, OrderedHash, logService, dataUtil, UrlUtil, FileUtil, pathService, halDriverService
        , networkContentRouterService, cdnService, storageLocalStorageService, halMediaService, halNetworkService,
        halPlatformService, CONTENT_ACCESS_CONTEXT) {
        var queue = new OrderedHash();
        var repositories = undefined;
        var _maxConcurrentDownload = 1;
        var cachedFileValidationResults = {};
        var cacheManifestPaths = {};
        var currentDownloadQueue = [];
        var purgeDirectorySubscribers = [];
        var logkey = 'appspace.network.contentcache';
        var supportLocalStorage;

        function init() {
            // set default path
            var defaultTmpCachePath = '/tmp';
            var defaultCachePath = '/cache';
            var tmpCachePath = coreConfigurationService.get('appspace', 'tmpCachePath');
            var cachePath = coreConfigurationService.get('appspace', 'cachePath');
            supportLocalStorage = halPlatformService.getFeatureSupport('file.localstorage') === 'yes';

            // get default path
            var sysUrl = storageFileService.resolveUrl(defaultCachePath);

            // todo - shouldn't override it, but cordova ios app on emulator have different folder each time install
            // check why it change folder
            if (!cachePath || cachePath !== sysUrl || !tmpCachePath) {
                // tmp cache path
                var cacheUrl = storageFileService.resolveUrl(defaultTmpCachePath);

                coreConfigurationService.set('appspace', 'tmpCachePath', cacheUrl);
                coreConfigurationService.set('appspace', 'cachePath', sysUrl);
            }

            // init routing
            var cdnRouting = {
                name: 'appspace-content-cdn-routing',
                priority: 10,
                route: function (url, opt) {
                    var deffered = $q.defer();
                    // Only route content from appspace as the origin
                    var appspaceUrl = coreConfigurationService.get('appspace', 'appspaceURL').toLowerCase();
                    if (url.toLowerCase().indexOf('c:\\fastnetv2') == -1 && url.toLowerCase().indexOf(appspaceUrl) == -1) {
                        deffered.reject();
                        return deffered.promise;
                    }

                    var cdn = cdnService.getActiveCDN();
                    if (cdn) {
                        var result = cdnService.resolveUrl(url, appspaceUrl, cdn.url, cdn.type);
                        if (result) {
                            deffered.resolve(result);
                        } else {
                            deffered.reject('cdn type (' + cdn.type + ') doesn\'t support url:' + url);
                        }
                    } else {
                        deffered.reject('no active cdn');
                    }

                    return deffered.promise;
                }
            };

            var router = {
                name: 'content-cache-routing',
                maps: [],
                routes: [cdnRouting]
            };
            networkContentRouterService.addService(router);

            $interval(processQueue, 10);
        }

        // intialise the service
        halDriverService.isReady().then(function () {
            storageLocalStorageService.isReady().then(init);
        });

        // public interface
        return {
            preFetch: preFetch,
            abortPrefetch: abortPrefetch,
            abortCache: abortCache,
            purgeRepository: purgeRepository,
            purgeCache: purgeCache,
            createRepository: createRepository,
            removeRepository: removeRepository,
            clear: clear,
            clearRepository: clearRepository,
            setCachePolicies: setCachePolicies,
            getFiles: getFiles,
            getRepositories: getRepositories,
            getCacheFilePath: getCacheFilePath,
            cacheFile: cacheFile,
            removeFile: removeFile,
            clearCache: clearCache,
            clearTmp: clearTmp,
            subscribeToPurgeDirectoryCheck: subscribeToPurgeDirectoryCheck,
        };

        function clearTmp() {
            var defer = $q.defer();
            var tmpPath = coreConfigurationService.get('appspace', 'tmpCachePath');

            if (!supportLocalStorage) {
                defer.resolve();
                return defer.promise;
            }

            storageFileService.removeDirectory(tmpPath).then(function (res) {
                defer.resolve();
            }, function (err) {
                defer.reject();
            });

            return defer.promise;
        }

        function clearCache(repository) {
            // currently explicit file manifest
            var defer = $q.defer();
            var cachePath = getRepoPath(repository);

            var cleanCacheByDirectory = function (path) {
                var defer = $q.defer();
                var promises = [];

                // read directory
                storageFileService.enumerateDirectory(path)
                    .then(function (dirEntries) {
                        for (var i in dirEntries) {
                            var entry = dirEntries[i];

                            if (entry.isDirectory) {
                                // check if the directory exists on cacheManifest
                                if (!cacheManifestPaths[repository]['directories'][entry.path]) {
                                    invalidateFileCheckResultCacheByPath(repository, entry.path);
                                    promises.push(storageFileService.removeDirectory(entry.path).then($angular.noop, $angular.noop));
                                } else {
                                    var newPath = path;
                                    if (path[path.length - 1] === '/') {
                                        newPath = newPath + entry.name + '/';
                                    } else {
                                        newPath = newPath + '/' + entry.name + '/';
                                    }

                                    var scan = cleanCacheByDirectory(newPath);
                                    promises.push(scan);
                                }
                            } else {
                                if (!cacheManifestPaths[repository]['files'][entry.path]) {
                                    invalidateFileCheckResultCache(repository, entry.path);
                                    promises.push(storageFileService.removeFile(entry.path).then($angular.noop, $angular.noop));
                                }
                            }
                        }

                        // wait until traversing and cleaning process complete
                        $q.all(promises).then(
                            function (res) {
                                defer.resolve();
                            },
                            function (err) {
                                defer.reject(err);
                            }
                        );
                    }, function (err) {
                        defer.reject(err);
                    });

                return defer.promise;
            };

            // todo: delete repo, if not exists
            if (!cacheManifestPaths[repository]) {
                invalidateFileCheckResultCacheByRepo(repository);
                storageFileService.removeDirectory(cachePath)
                    .then(function (res) {
                        defer.resolve(res);
                    }, function (err) {
                        defer.reject(err);
                    });
            } else {
                // start cleaning process
                cleanCacheByDirectory(cachePath).then(
                    function (res) {
                        defer.resolve(res);
                    },
                    function (err) {
                        defer.reject(err);
                    });
            }

            return defer.promise;
        }

        // store cache manifest
        function getCacheManifestPaths(repo, manifest) {
            var cache = {
                'directories': {},
                'files': {}
            };
            var files = manifest.files;

            for (var i = 0; i < files.length; i++) {
                var file = files[i];
                var dirPath = getDestPath(repo, file.url, true);
                var filePath = getDestPath(repo, file.url);
                // cache it
                cache['directories'][dirPath] = true;
                cache['files'][filePath] = true;
            }

            return cache;
        }

        function addFileCheckResultCache(repository, file, exists, opt) {
            if (opt && opt.disableFileValidationCache) {
                return;
            }

            cachedFileValidationResults[repository] = cachedFileValidationResults[repository] || {};
            cachedFileValidationResults[repository][file] = exists;
        }

        function getFileCheckResultFromCache(repository, file, opt) {
            if (!cachedFileValidationResults[repository] || (opt && opt.disableFileValidationCache)) {
                return false;
            }

            return cachedFileValidationResults[repository][file];
        }

        function invalidateFileCheckResultCacheByPath(repository, path) {
            logService.debug(logkey, 'Invalidating file path result for folder ' + path);
            for (var file in cachedFileValidationResults[repository]) {
                if (file.indexOf(path) == -1) {
                    continue;
                }

                invalidateFileCheckResultCache(repository, file);
            }
        }

        function invalidateFileCheckResultCache(repository, file) {
            if (!cachedFileValidationResults[repository] || !cachedFileValidationResults[repository][file]) {
                return;
            }

            logService.debug(logkey, 'Invalidating file path result for ' + file);
            cachedFileValidationResults[repository][file] = false;
        }

        function invalidateFileCheckResultCacheByRepo(repository) {
            logService.debug(logkey, 'Invalidating file path result for repo ' + repository);
            cachedFileValidationResults[repository] = undefined;
        }

        function invalidateAllFileCheckResultCache() {
            logService.debug(logkey, 'Invalidating file path result');
            cachedFileValidationResults = {};
        }

        // Remove content in the repo that is not in the manifest
        function purgeRepository(repository, manifest) {
            var clearPromise = halMediaService.clearContentCache(manifest, repository);
            if (clearPromise) {
                return clearPromise;
            }

            // currently explicit file manifest
            var defer = $q.defer();
            var cachePath = getRepoPath(repository);
            cacheManifestPaths[repository] = getCacheManifestPaths(repository, manifest);

            var cleanCacheByDirectory = function (path) {
                var defer = $q.defer();
                var promises = [];
                var purgedPaths = [];
                var updatePurgedPaths = function (path) {
                    purgedPaths.push(path);
                };

                // read directory
                storageFileService.enumerateDirectory(path).then(function (dirEntries) {
                    for (var i in dirEntries) {
                        var entry = dirEntries[i];
                        // if this entry is a directory
                        if (entry.isDirectory) {
                            var currentDirectoryPath = storageFileService.resolveUrl(entry.path);
                            // if directory doesn't exists in repo, check if we use it in our manifest
                            if (!cacheManifestPaths[repository]['directories'][currentDirectoryPath]) {
                                var isCurrentDirectoryUsed = false;
                                for (var manifestDirectoryPath in cacheManifestPaths[repository]['directories']) {
                                    // if the manifest directory path contains a part of current directory that we're checking,
                                    // flag the current directory path as used
                                    if (manifestDirectoryPath.toLowerCase().indexOf(currentDirectoryPath.toLowerCase()) >= 0) {
                                        isCurrentDirectoryUsed = true;
                                        break;
                                    }
                                }

                                // if we don't use the directory,
                                // and no one is using this directory,
                                // then push it to the list of directories to be removed
                                if (!isCurrentDirectoryUsed && !isUsedByPurgeSubscribers(currentDirectoryPath)) {
                                    logService.debug(logkey, 'Purging folder ' + currentDirectoryPath);
                                    promises.push(storageFileService.removeDirectory(currentDirectoryPath));
                                    invalidateFileCheckResultCacheByPath(repository, currentDirectoryPath);
                                    updatePurgedPaths(currentDirectoryPath);
                                }
                            } else {
                                // if directory exists in our manifest, go deeper
                                var newPath = pathService.join(path, entry.name);
                                var scan = cleanCacheByDirectory(newPath);
                                promises.push(scan);
                            }
                        } else {
                            // if this is entry is a file
                            var currentFilePath = storageFileService.resolveUrl(entry.path);
                            // if the current file is not inside our manifest, push it to the list of files to be removed
                            if (!cacheManifestPaths[repository]['files'][currentFilePath]) {
                                logService.debug(logkey, 'Purging file ' + currentFilePath);
                                invalidateFileCheckResultCache(repository, currentFilePath);
                                promises.push(storageFileService.removeFile(currentFilePath));
                                updatePurgedPaths(currentFilePath);
                            }
                        }
                    }

                    // wait until traversing and cleaning process complete
                    $q.all(promises).then(
                        function (res) {
                            defer.resolve(purgedPaths);
                        },
                        function (err) {
                            defer.reject(err);
                        }
                    );
                }, function (err) {
                    defer.reject(err);
                });

                return defer.promise;
            };

            // if all information is there, clean cache by directory
            if (cacheManifestPaths[repository]) {
                if (cacheManifestPaths[repository]['directories']) {
                    cleanCacheByDirectory(cachePath).then(defer.resolve, defer.reject);
                } else {
                    defer.reject(repository + ' doesn\'t contained any directories');
                }
            } else {
                // if repo doesn't exists delete the repo
                invalidateFileCheckResultCacheByRepo(repository);
                storageFileService.removeDirectory(cachePath).then(defer.resolve, defer.reject);
            }

            return defer.promise;
        }

        function isUsedByPurgeSubscribers(currentDirectory) {
            for (var i = 0; i < purgeDirectorySubscribers.length; i++) {
                var subscriber = purgeDirectorySubscribers[i];
                if (subscriber && subscriber(currentDirectory)) {
                    return true;
                }
            }

            return false;
        }

        function subscribeToPurgeDirectoryCheck(callback) {
            if (purgeDirectorySubscribers.indexOf(callback) >= 0) {
                return;
            }

            purgeDirectorySubscribers.push(callback);
        }

        function purgeCache() {
            var defer = $q.defer();

            var tmpPath = coreConfigurationService.get('appspace', 'tmpCachePath');
            storageFileService.removeDirectory(tmpPath).then(function (res) {
                defer.resolve();
            }, function (err) {
                defer.reject(err);
            });

            return defer.promise;
        }

        // Aborts and existing prefetch
        function abortPrefetch(prefetchPromise) {
            for (var x = 0; x < currentDownloadQueue.length; x++) {
                removeDownloadItemFromQueue(currentDownloadQueue[x]);
            }

            var promises = prefetchPromise._cachePromises;
            if (!promises) {
                return;
            }

            for (var i = 0; i < promises.length; i++) {
                abortCache(promises[i]);
            }
        }

        // Aborts and existing prefetch
        function abortCache(cachePromise) {
            var downloadQueue = queue.getArray();
            for (var queueKey in downloadQueue) {
                var downloadItem = downloadQueue[queueKey];
                var downloadPromises = downloadItem.promises;
                for (var promiseKey in downloadPromises) {
                    var downloadPromise = downloadPromises[promiseKey];
                    if (downloadPromise.promise !== cachePromise) {
                        continue;
                    }

                    // delete promise from array
                    downloadPromise.reject({
                        url: downloadItem.url,
                        message: 'Download cancelled'
                    });
                    downloadPromises.splice(promiseKey, 1);
                }

                if (downloadPromises.length != 0) {
                    continue;
                }

                queue.remove(downloadItem._id);

                if (!downloadItem.cancelDownload) {
                    continue;
                }

                downloadItem.cancelDownload();
            }
        }

        // ask - cache multiple files or single only
        // ask - only through prefetch, it set manifest, when does cache cleaning process happens?
        // Prefetches content based on a manifest
        function preFetch(repo, manifest, maxConcurrentDownload) {
            var defer = $q.defer();
            var startPrefetch = new Date().getTime();
            var cachePromises = [];
            _maxConcurrentDownload = (maxConcurrentDownload && maxConcurrentDownload > 0) ? maxConcurrentDownload : 1;
            // cache manifest
            // cacheManifest[repo] = manifest;
            var files = manifest.files;

            // tony - mask cache manifest
            // populate cache manifest paths for cache policy usage
            cacheManifestPaths[repo] = getCacheManifestPaths(repo, manifest);
            // start downloading
            for (var i = 0; i < files.length; i++) {
                // start caching files
                cachePromises.push(cacheFile(repo, files[i].url));
            }

            defer.promise.append('_cachePromises', cachePromises);

            // Wait for all promises to be completed
            $q.allSettled(cachePromises).then(function (promises) {
                var containedRejected = false;
                for (var i = 0; i < promises.length; i++) {
                    var promise = promises[i];
                    // if our promises contains reject, we need to reject our main promise
                    if (promise.state == 'rejected') {
                        containedRejected = true;
                        break;
                    }
                }

                var endPrefetch = new Date().getTime();
                logService.info(logkey, 'Prefetch completed in ' + (endPrefetch - startPrefetch) + 'ms');
                if (!containedRejected) {
                    defer.resolve(promises);
                } else {
                    defer.reject(promises);
                }
            });

            return defer.promise;
        }

        // Shorthand for generating prefix-key
        function createKey(repository, url, targetPath) {
            return repository + '-' + url +
                (targetPath ? '-' + targetPath : '');
        }

        function checkCurrentDownloadQueuePriority() {
            // if we're well within our limit, don't check
            if (currentDownloadQueue.length < _maxConcurrentDownload) {
                return;
            }

            var currentQueue = queue.getArray();
            currentQueue = $filter('orderBy')(currentQueue, 'priority', true);
            for (var i = 0; i < currentQueue.length; i++) {
                var downloadItem = currentQueue[i];
                var downloadQueue = $filter('orderBy')(currentDownloadQueue, 'priority');
                for (var j = 0; j < downloadQueue.length; j++) {
                    var currentDownloadItem = downloadQueue[j];
                    if (!currentDownloadItem.cancelDownload ||
                        downloadItem.priority <= currentDownloadItem.priority) {
                        continue;
                    }

                    currentDownloadItem.cancelDownload();
                    removeDownloadItemFromQueue(currentDownloadItem);
                    // add back the file to the queue
                    queue.add(currentDownloadItem._id, currentDownloadItem);
                    break;
                }
            }
        }

        // Downloads the content from the URL and stores it in the repository
        function cacheFile(repository, url, highPriority, targetPath, options) {

            url = UrlUtil.appendQueryString(url, { 
                context: CONTENT_ACCESS_CONTEXT.CONTENT_CACHING
            });

            logService.debug(logkey, 'Caching ' + url + ' to ' + targetPath);
            var defer = $q.defer();

            var fileNotFound = function () {
                // add to queue
                var key = createKey(repository, url, targetPath);

                // check if download item exists
                var di = queue.get(key);
                // if queue exists, append previous one
                if (di) {
                    if (!highPriority || highPriority && di.priority == 100) {
                        di.addPromise(defer);
                        return;
                    }

                    // if item is high priority but the queue is not set to high, remove it from queue and add it back again
                    queue.remove(key);
                } else {
                    // check if we are currently downloading the file
                    for (var i = 0; i < currentDownloadQueue.length; i++) {
                        var currentFileToDownload = currentDownloadQueue[i];
                        if (key == currentFileToDownload._id) {
                            logService.debug(logkey, 'The file: ' + url + ' is already downloading. Adding it to the existing item download list.');
                            currentFileToDownload.addPromise(defer);
                            return;
                        }
                    }
                }

                di = new DownloadItem(repository, url, 0, defer, targetPath, options);
                di.priority = highPriority ? 100 : FileUtil.getFilePriority(url);
                queue.add(key, di);
                checkCurrentDownloadQueuePriority();
            };

            // Check does file exist
            getCacheFilePath(repository, url, { targetPath: targetPath })
                .then(function (cacheLocation) {
                    // File already cached, just return its location
                    defer.resolve(cacheLocation);
                }, fileNotFound);

            return defer.promise;
        }

        function removeFile(repository, filePath) {
            var defer = $q.defer();

            storageFileService.removeFile(filePath).then(function (res) {
                invalidateFileCheckResultCache(repository, filePath);
                defer.resolve(res);
            }, function (err) {
                defer.reject(err);
            });

            return defer.promise;
        }

        // Creates a repository
        function createRepository(name) {
            var defer = $q.defer();
            var repoPath = getRepoPath(name);

            // Check does it already exist
            getRepositories().then(function (repos) {
                // Does it exist?
                for (var i = 0; i < repos.length; i++) {
                    var repo = repositories[i];
                    if (repo.name === name) {
                        defer.resolve(repo);
                        return;
                    }
                }

                // if not exists, creates it
                storageFileService.createDirectory(repoPath)
                    .then(function (res) {
                        repositories.push(res.entry);
                        defer.resolve(res);
                    }, function (err) {
                        defer.reject(err);
                    });
            }, function (err) {
                defer.reject(err);
            });

            return defer.promise;
        }

        // Creates a repository
        function removeRepository(name) {
            var defer = $q.defer();
            var repoPath = getRepoPath(name);

            // Check does it already exist
            getRepositories().then(function (repos) {
                // Does it exist?
                for (var i = 0; i < repos.length; i++) {
                    var repo = repositories[i];
                    if (repo.name === name) {
                        invalidateFileCheckResultCacheByRepo(name);
                        storageFileService.removeDirectory(repoPath)
                            .then(function (res) {
                                // remove cache repositories
                                repositories.splice(i, 1);
                                defer.resolve(repo);
                                return;
                            }, function (err) {
                                defer.reject(err);
                                return;
                            });

                        return;
                    }
                }

                // not found
                defer.reject();
            }, defer.reject);

            return defer.promise;
        }

        // Removes all content in the cache and tmp folder
        function clear() {
            var defer = $q.defer();
            var repoPath = getRepoPath();

            // clear /tmp
            clearTmp().then(function () {
                // clear /cache
                storageFileService.removeDirectory(repoPath)
                    .then(function (res) {
                        // reset cache
                        logService.debug(logkey, 'Invalidating all file path result');
                        invalidateAllFileCheckResultCache();
                        cacheManifestPaths = {};
                        repositories = [];
                        defer.resolve();
                    }, function (err) {
                        defer.resolve();
                    });
            }, function (err) {
                // todo - to address empty folder throws error issue
                defer.resolve();
                // defer.reject(err);
            });

            return defer.promise;
        }

        // Removes all content in a repository cache
        function clearRepository(repository) {
            var localCachingSupported = halPlatformService.getDeviceCapabilities().supportFileCaching;
            var clearRepositoryImplementation = localCachingSupported ?
                removeRepository :
                halNetworkService.clearAllResources;
            var defer = $q.defer();
            var finalize = function(result) {
                // clear cache
                logService.debug(logkey, 'Invalidating all file path result for repo ' + repository);
                invalidateFileCheckResultCacheByRepo(repository);
                cacheManifestPaths[repository] = {};
                defer.resolve(result);
            };

            var clearRepositoryResult = clearRepositoryImplementation(repository);
            if (dataUtil.isPromise(clearRepositoryResult)) {
                clearRepositoryResult.then(finalize, defer.reject);
            } else {
                finalize(clearRepositoryResult);
            }

            return defer.promise;
        }

        // Sets the caching policies for a specific repository
        function setCachePolicies(repository, config) {

        }

        // Returns all cached files in a repository
        function getFiles(repository) {
            var defer = $q.defer();

            var cachePath = getRepoPath(repository);
            var fileEntries = [];

            // scanning each directory
            var scanDirectory = function (path) {
                var defer = $q.defer();
                var promises = [];

                storageFileService.enumerateDirectory(path)
                    .then(function (dirEntries) {
                        for (var i in dirEntries) {
                            var entry = dirEntries[i];
                            if (entry.isDirectory) {
                                var newPath = path;
                                if (path[path.length - 1] === '/') {
                                    newPath = newPath + entry.name;
                                } else {
                                    newPath = newPath + '/' + entry.name;
                                }

                                var scan = scanDirectory(newPath);
                                promises.push(scan);
                            } else {
                                fileEntries.push(entry);
                            }
                        }

                        // wait until traversing complete
                        $q.all(promises).then(
                            function (res) {
                                defer.resolve(fileEntries);
                            },
                            function (err) {
                                defer.reject(err);
                            }
                        );
                    },
                        function (err) {
                            defer.reject(err);
                        });

                return defer.promise;
            };

            scanDirectory(cachePath).then(function () {
                defer.resolve(fileEntries);
            }, function (err) {
                defer.reject(err);
            });

            return defer.promise;
        }

        // Returns all cache repositories
        function getRepositories() {
            var defer = $q.defer();
            var cachePath = getRepoPath();

            // Check have already cached the repositories
            if (repositories !== undefined) {
                defer.resolve(repositories);
            } else {
                storageFileService.enumerateDirectory(cachePath)
                    .then(function (dirEntries) {
                        var fileEntries = [];
                        for (var i in dirEntries) {
                            if (dirEntries[i].isDirectory === true) {
                                fileEntries.push(dirEntries[i]);
                            }
                        }

                        repositories = fileEntries;

                        defer.resolve(fileEntries);
                    },
                        function (err) {
                            // create cache path if not exists
                            storageFileService.createDirectory(cachePath)
                                .then(function (res) {
                                    // returns blank repo
                                    repositories = [];
                                    defer.resolve(repositories);
                                }, function (err) {
                                    defer.reject(err);
                                });
                        });
            }

            return defer.promise;
        }

        // Returns the location for a cached url
        function getCacheFilePath(repository, url, opt) {
            var destPath = opt && opt.targetPath ? opt.targetPath : getDestPath(repository, url);
            if (getFileCheckResultFromCache(repository, destPath, opt)) {
                return $q.resolve(destPath);
            }

            var defer = $q.defer();
            // todo: create transform url
            storageFileService.checkFile(destPath).then(function (res) {
                addFileCheckResultCache(repository, destPath, res.exists, opt);
                if (res.exists) {
                    logService.debug(logkey, 'Local file for ' + url + ' found at ' + destPath);
                    defer.resolve(destPath);
                } else {
                    logService.debug(logkey, 'Unable to find local file for ' + url + ' at ' + destPath);
                    defer.reject();
                }
            }, defer.reject);

            return defer.promise;
        }

        // Return cache path from config or default it
        function getRepoPath(repository) {
            // cachepath
            var cachePath = coreConfigurationService.get('appspace', 'cachePath');
            if (repository !== undefined) {
                cachePath = pathService.join(cachePath, repository);
            }

            return cachePath;
        }

        function getDestPath(repository, url, onlyDir) {
            var destPath = getRepoPath(repository);
            var transformPath = '';
            // if url contains ug media, hash up until the media id, and continue appending the folder
            // https://db9.dev.appspace.com/app/fastnetdata/ug%20media/5/51454/a/b/c/d/e/_thumbnail.jpg becomes
            // file://contentcache/reponame/dfdfAdfdf434/a/b/c/d/e/_thumbnail.jpg
            if (url.toLowerCase().indexOf('/ug%20media/') >= 0) {
                var splits = url.split('/');
                var path = '';
                for (var i = 0; i < splits.length; i++) {
                    // ignore file name, will be appended at the bottom
                    if (i == (splits.length - 1)) {
                        break;
                    }

                    path += splits[i] + '/';
                    if (splits[i].match(/ug%20media/i)) {
                        // grab the network ID & media ID and progress the counter
                        path += splits[i + 1] + '/' + splits[i + 2];
                        i += 2;
                        // hash up until the media ID
                        path = md5(path) + '/';
                    }
                }

                transformPath = path;
                splits = [];
                splits = null;
            } else {
                // md5 url location to folder name
                // /http://appspae.com/media/files/stan.jpg
                // file://contentcache/reponame/dfdfAdfdf434/stan.jpg
                var urlPath = url.substring(0, url.lastIndexOf('/'));
                transformPath = md5(urlPath) + '/';
            }

            // we omitting deviceid, context, channelid for cache path calculation
            url = UrlUtil.removeQueryString(url, ['deviceid','context','channelid']);

            var filePath = url.substring(url.lastIndexOf('/') + 1);
            destPath = pathService.join(destPath, transformPath);

            if (onlyDir !== true) {
                // extract only filename
                if (filePath.indexOf('?') > -1) {
                    var segs = filePath.split('?');
                    filePath = segs[0];
                    var queryString = segs.length > 1 ? segs[1] : '';
                    if (queryString) {
                        var fileName =  filePath.substring(0, filePath.lastIndexOf('.'));
                        var fileExtension = filePath.substring(filePath.lastIndexOf('.') + 1);
                        filePath = fileName + '-' + md5(queryString);
                        if (fileExtension.length > 0) {
                            filePath += '.' + fileExtension;
                        }
                    }

                    segs = [];
                    segs = null;
                }

                filePath = pathService.sanitize(filePath);
                destPath = pathService.join(destPath, filePath);
            }

            return destPath;
        }

        // change dest path to tmp
        function convertTmpPath(url) {
            var tmpPath = coreConfigurationService.get('appspace', 'tmpCachePath');
            var baseName = FileUtil.getBaseName(url);
            var filePath = '_' + new Date().getTime() + '/' + baseName;

            return pathService.join(tmpPath, filePath);
        }

        function postDownloadCleanUp(path, log) {

            storageFileService.removeDirectory(path).then(function () {
                if (!log) {
                    return;
                }

                logService.debug(logkey, 'Successfully cleaned up ' + path);
            }, function (error) {
                if (!log) {
                    return;
                }

                logService.debug(logkey, 'Failed to clean up ' + path + ' due to: ' + JSON.stringify(error));
            });
        }

        function download(di) {
            var defer = $q.defer();
            var url = di.url;
            var destPath = di.targetPath || getDestPath(di.repository, di.url);
            var destDirPath = FileUtil.getDirPath(destPath);

            // convert this to tmp path
            var tmpPath = convertTmpPath(destPath);
            var srcDirPath = FileUtil.getDirPath(tmpPath);
            logService.debug(logkey, 'Downloading ' + url + ' to ' + destPath);
            createRepository(di.repository).then(function (res) {
                // create sub path
                storageFileService.createDirectory(srcDirPath).
                    then(function (res) {
                        // remove tmp file in case it exist
                        storageFileService.removeFile(tmpPath)
                            .then($angular.noop, $angular.noop)
                            .finally(function () {
                                var doDownload = function (url) {
                                    // if the resolved URL is not the same as the original URL, mention it in the log
                                    if (di.url != url) {
                                        logService.debug(logkey, 'Downloading ' + di.url + ' from ' + url);
                                    }

                                    var downloadSuccess = false;
                                    networkFileTransferService.downloadHttp(url, tmpPath, di)
                                        .then(function (res) {
                                            onItemDownloaded(di);
                                            storageFileService.moveDirectory(srcDirPath, destDirPath).then(
                                                function (res) {
                                                    downloadSuccess = true;
                                                    defer.resolve(res[0].path);
                                                }, defer.reject).finally(function () {
                                                    postDownloadCleanUp(srcDirPath, !downloadSuccess);
                                                });
                                        },
                                            function (err) {
                                                onItemDownloadFail(di, srcDirPath);
                                                logService.warn(logkey, 'Failed to download ', di.url);
                                                defer.reject(err);
                                            }
                                        );
                                };

                                networkContentRouterService.resolve(di.url, 'content-cache-routing').then(doDownload,
                                    function (err) {
                                        doDownload(di.url);
                                    });
                            });
                    }, function (err) {
                        // Failed to create directory
                        logService.warn(logkey, 'Failed to create directory for ', srcDirPath, err);
                        defer.reject(err);
                    });
            }, function (err) {
                defer.reject(err);
            });

            return defer.promise;
        }

        function removeDownloadItemFromQueue(di) {
            var index = currentDownloadQueue.indexOf(di);
            if (index > -1) {
                currentDownloadQueue.splice(index, 1);
            }
        }

        function getNextDownloadQueueItem() {
            if (queue.length() == 0) {
                return;
            }

            var currentQueue = $filter('orderBy')(queue.getArray(), 'priority', true);
            var firstItem = currentQueue.shift();
            queue.remove(firstItem._id);
            return firstItem;
        }

        function processQueue() {
            if (currentDownloadQueue.length >= _maxConcurrentDownload) {
                return;
            }

            // find priority item first
            var di = getNextDownloadQueueItem();
            if (!di || di.promises.length < 1) {
                return;
            }

            // perform download
            download(di).then(function (res) {
                logService.debug(logkey, 'Download complete : ' + res);
                for (var i in di.promises) {
                    di.promises[i].resolve(res);
                }

                removeDownloadItemFromQueue(di);
            }, function (err) {
                var error = err || {};
                if (err && !dataUtil.isObject(err)) {
                    error = {
                        errorMessage: err
                    };
                }

                error.url = di.url;
                logService.warn(logkey, 'Download failed : ', error);
                for (var i in di.promises) {
                    di.promises[i].reject(error);
                }

                removeDownloadItemFromQueue(di);
            });
            currentDownloadQueue.push(di);
        }

        // remove possible duplicate task entry
        function onItemDownloaded(di) {
            var key = createKey(di.repository, di.url);
            queue.remove(key);
        }

        function onItemDownloadFail(di, path) {
            postDownloadCleanUp(path, true);
        }
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('network');

    app.factory('WebRTC', ["$window", "$timeout", "$q", "logService", "TimeoutUtil", function($window, $timeout, $q, logService, TimeoutUtil) {
        var ipv4ipv6Regex = /[0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}:([a-f0-9]{1,4}:|:)+([a-f0-9]{1,4})?/i;
        function WebRTC() {
            this.getIpAddress = function(defaultIpAddress) {
                var processError = function(errorMessage, errorType) {
                    if (defaultIpAddress) {
                        logService.debug('appspace.core.hal', errorMessage);
                        return $q.resolve({address: defaultIpAddress});
                    }

                    logService.debug('appspace.core.hal', errorMessage);
                    return $q.reject({type: errorType, message: errorMessage});
                };
                // Browser specific connection object
                $window.RTCPeerConnection = $window.RTCPeerConnection || $window.mozRTCPeerConnection || $window.webkitRTCPeerConnection; // compatibility for firefox and chrome
                if (!$window.RTCPeerConnection || !('createDataChannel' in $window.RTCPeerConnection.prototype)) {
                    return processError(
                        'WebRTC not supported for get IP Address, using loopback ip instead',
                        'notSupported');
                }

                // Create an RTC Peer connection
                var peerConnection = new RTCPeerConnection({iceServers: []});
                var noop = function() { };
                var defer = $q.defer();
                logService.trace('appspace.core.hal', 'Attempting to get IP using WebRTC');

                // Start an error timeout
                var errorTimeout = $timeout(function() {
                    handleGetIpError(
                        'WebRTC get IP address timeout, fallback to loop back ip',
                        'errorTimeout');
                }, 1000);

                var cleanUp = function() {
                    if (errorTimeout) {
                        TimeoutUtil.cancel(errorTimeout);
                        errorTimeout = undefined;
                    }

                    if (!peerConnection) {
                        return;
                    }

                    peerConnection.onicecandidate = noop;
                    peerConnection.oniceconnectionstatechange = noop;
                    peerConnection.close();
                    peerConnection = undefined;
                };

                var handleGetIpError = function(errorMessage, errorType) {
                    cleanUp();
                    processError(
                        errorMessage,
                        errorType).then(defer.resolve, defer.reject);
                };

                // create a bogus data channel
                peerConnection.createDataChannel('');

                // Listen to connection state events
                peerConnection.oniceconnectionstatechange = function() {
                    logService.trace('appspace.core.hal', 'RTC connection state update: ' + peerConnection.iceConnectionState);
                };

                // listen for candidate connections
                peerConnection.onicecandidate = function(ice) {
                    if (!ice || !ice.candidate || !ice.candidate.candidate) {
                        handleGetIpError('Invalid ICE.Candidate in RTC', 'invalidICE');
                        return;
                    }

                    var execResult = ipv4ipv6Regex.exec(ice.candidate.candidate);
                    if (!execResult || !execResult[0]) {
                        handleGetIpError('No match for ipv4/ipv6 address', 'ipv4ipv6NoMatch');
                        return;
                    }

                    var ipAddress = execResult[0];
                    logService.debug('appspace.core.hal', 'WebRTC IP: ' + ipAddress);
                    cleanUp();
                    defer.resolve({address: ipAddress});
                };

                // Create an offer and bind
                peerConnection.createOffer(peerConnection.setLocalDescription.bind(peerConnection), noop);
                return defer.promise;
            };
        }

        // Return the constructor for the class
        return (WebRTC);
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var app = $angular.module('network');
    app.factory('requestUtil', ["$http", function($http) {
        function httpHead(url, config) {
            config = config || {};
            config.method = 'HEAD';
            config.url = url;
            return $http(config);
        }

        return {
            httpHead: httpHead
        };
    }]);
})(window.angular);

(function($angular) {

    "use strict";

	//declare storage module and its dependencies 
    var app = $angular.module('storage', ['hal','core', 'angular-md5']);

})(window.angular);
(function($angular) {
    'use strict';

    var app = $angular.module('storage');

    app.factory('ApplicationStorage', ["$q", "$timeout", function($q, $timeout) {
        function ApplicationStorage(runtimeStorage, persistentStorage) {
            var DEBOUNCE_TIME_MILLISECONDS = 100;
            var IS_DATA_MIGRATED_KEY = 'isDataMigrated';
            var debounceTimeout;

            if (!runtimeStorage) {
                throw new Error('ApplicationStorage requires runtimeStorage parameter');
            }

            this.length = 0;

            this.initialize = function(originalStorage, configuration) {
                var defer = $q.defer();
                persistentStorage.getCollection().then(function(result) {
                    runtimeStorage.saveCollection(result);
                    // Migration
                    migrateData(originalStorage, configuration);
                    defer.resolve();
                }, defer.reject);
                return defer.promise;
            };

            function migrateData(originalStorage, configuration) {
                var isDataMigrated = runtimeStorage.getItem(IS_DATA_MIGRATED_KEY);
                if (isDataMigrated || !originalStorage) {
                    return;
                }

                var migratedItems = [];
                for (var i = 0; i < originalStorage.length; i++) {
                    var key = originalStorage.key(i);
                    runtimeStorage.setItem(key, originalStorage.getItem(key));
                    migratedItems.push(key);
                }

                runtimeStorage.setItem(IS_DATA_MIGRATED_KEY, true);
                assignLengthAndPersistCollection();
                if (!configuration || !configuration.clearAfterMigration) {
                    return;
                }

                for (var j = 0; j < migratedItems.length; j++) {
                    originalStorage.removeItem(migratedItems[j]);
                }
            }

            this.clear = function() {
                runtimeStorage.clear();
                assignLengthAndPersistCollection();
            };

            this.getItem = function(keyName) {
                return runtimeStorage.getItem(keyName);
            };

            this.key = function(index) {
                return runtimeStorage.key(index);
            };

            this.removeItem = function(keyName) {
                runtimeStorage.removeItem(keyName);
                assignLengthAndPersistCollection();
            };

            this.setItem = function(keyName, keyValue) {
                runtimeStorage.setItem(keyName, keyValue);
                assignLengthAndPersistCollection();
            };

            var assignLengthAndPersistCollection = function() {
                this.length = runtimeStorage.length;
                clearDebounceTimeout();
                if (!persistentStorage) {
                    return;
                }

                debounceTimeout = $timeout(function() {
                    persistentStorage.saveCollection(runtimeStorage.getCollection());
                    clearDebounceTimeout();
                }, DEBOUNCE_TIME_MILLISECONDS);
            }.bind(this);

            var clearDebounceTimeout = function() {
                if (!debounceTimeout) {
                    return;
                }

                $timeout.cancel(debounceTimeout);
                debounceTimeout = undefined;
            };
        }

        return (ApplicationStorage);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('storage');

    app.factory('CordovaStorage', ["$window", "logService", "$q", "dataUtil", function($window, logService, $q, dataUtil) {
        var LOCAL_STORAGE_FILE_NAME = 'localStorage.data';
        var DATA_DIRECTORY;
        var LOG_KEY = 'appspace.core.storage';
        var isSaving = false;
        var passphrase;
        var nextJob;

        function CordovaStorage(key) {
            DATA_DIRECTORY = $window.cordova.file.dataDirectory;
            passphrase = key;
        }

        function getCollection() {
            var defer = $q.defer();
            var onError = function(error) {
                logService.warn(LOG_KEY, JSON.stringify(error));
                defer.resolve({});
            };

            var onLoadEnd = function() {
                try {
                    // eslint-disable-next-line no-invalid-this
                    defer.resolve(JSON.parse(dataUtil.aesDecrypt(this.result, passphrase)));
                } catch (error) {
                    logService.warn(LOG_KEY, 'Error while parsing local storage file');
                    defer.resolve({});
                }
            };

            var onGetFile = function(fileEntry) {
                fileEntry.file(function(file) {
                    var reader = new $window.FileReader();
                    reader.onloadend = onLoadEnd;
                    reader.readAsText(file);
                }, onError);
            };

            var onLocalFileSystemResolved = function(dir) {
                dir.getFile(LOCAL_STORAGE_FILE_NAME, {create: true}, onGetFile, onError);
            };

            $window.resolveLocalFileSystemURL(DATA_DIRECTORY, onLocalFileSystemResolved, onError);
            return defer.promise;
        }

        function saveCollection(collection) {
            if (isSaving) {
                nextJob = collection;
                return;
            }

            isSaving = true;
            var finallyCallback = function() {
                isSaving = false;
                if (!nextJob) {
                    return;
                }

                var clonedJob = $angular.copy(nextJob);
                nextJob = undefined;
                saveCollection(clonedJob);
            };

            var onError = function(error) {
                logService.error(LOG_KEY, JSON.stringify(error));
                finallyCallback();
            };

            var onGetFile = function(fileEntry) {
                fileEntry.createWriter(function(fileWriter) {
                    var blob = new Blob([dataUtil.aesEncrypt(JSON.stringify(collection), passphrase).toString()], {type: 'text/plain'});
                    fileWriter.onwriteend = finallyCallback;
                    fileWriter.write(blob);
                }, onError);
            };

            var onLocalFileSystemResolved = function(dir) {
                dir.getFile(LOCAL_STORAGE_FILE_NAME, {create: true, exclusive: false}, onGetFile, onError);
            };

            $window.resolveLocalFileSystemURL(DATA_DIRECTORY, onLocalFileSystemResolved, onError);
        }

        CordovaStorage.prototype = {
            getCollection: getCollection,
            saveCollection: saveCollection
        };

        return (CordovaStorage);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    $angular.module('storage').factory('MemoryStorage',
        function() {
            function MemoryStorage() {
                var collectionLookup = {};
                var updateCollectionsLength = function() {
                    this.length = Object.keys(collectionLookup).length;
                }.bind(this);

                // public interface
                this.length = 0;

                this.setItem = function(key, value) {
                    if (!key) {
                        return;
                    }

                    collectionLookup[key] = value;
                    updateCollectionsLength();
                };

                this.getItem = function(key) {
                    return collectionLookup[key];
                };

                this.key = function(index) {
                    if (index == undefined || index == null || isNaN(index)) {
                        return;
                    }

                    return Object.keys(collectionLookup)[index];
                };

                this.removeItem = function(key) {
                    if (!key) {
                        return;
                    }

                    delete collectionLookup[key];
                    updateCollectionsLength();
                };

                this.clear = function() {
                    collectionLookup = {};
                    updateCollectionsLength();
                };

                this.getCollection = function() {
                    return collectionLookup;
                };

                this.saveCollection = function(collection) {
                    collectionLookup = collection;
                    updateCollectionsLength();
                };
            }

            return (MemoryStorage);
        });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('storage');

    app.factory('PersistentFileStorage', ["$q", "$injector", "logService", function(
        $q,
        $injector,
        logService) {
        var LOCAL_STORAGE_FILE_PATH = {
            FILE_NAME: 'localStorageData.txt',
            DIRECTORY: 'appspace'
        };
        var directory;
        var LOG_KEY = 'appspace.core.storage';
        var isSaving = false;
        var nextJob;
        var halFileService;

        function PersistentFileStorage(configuration) {
            if (!configuration) {
                throw new Error('Missing PersistentFileStorage configuration');
            }

            directory = configuration.directory;
        }

        function getHalFileService() {
            if (!halFileService) {
                halFileService = $injector.get('halFileService');
            }

            return halFileService;
        }

        function getFilePath() {
            return [directory, LOCAL_STORAGE_FILE_PATH.DIRECTORY, LOCAL_STORAGE_FILE_PATH.FILE_NAME].join('/');
        }

        function getCollection() {
            var deferred = $q.defer();
            var onFailed = function(error) {
                var reason = 'Failed to get persistent file storage collection due to: ' + error;
                logService.warn(LOG_KEY, reason);
                deferred.resolve({});
            };

            getHalFileService().readFile(getFilePath()).then(function(data) {
                if (!data) {
                    onFailed('Empty file content');
                    return;
                }

                deferred.resolve(data);
            }, function(error) {
                onFailed('Error when reading file: ' + error);
            });
            return deferred.promise;
        }

        function saveCollection(collection) {
            if (isSaving) {
                nextJob = collection;
                return;
            }

            isSaving = true;
            var finalize = function() {
                isSaving = false;
                if (!nextJob) {
                    return;
                }

                var clonedJob = $angular.copy(nextJob);
                nextJob = undefined;
                saveCollection(clonedJob);
            };

            var onFailed = function(reason) {
                logService.warn(LOG_KEY, 'Failed to save data to persistent file storage due to: ' + reason);
                finalize();
            };

            getHalFileService().createDirectory(directory).then(function() {
                getHalFileService().writeFile(getFilePath(), JSON.stringify(collection)).then(finalize, function(error) {
                    onFailed('Error when writing file: ' + error);
                });
            }, function(error) {
                onFailed('Error when creating directory: ' + error);
            });
        }

        PersistentFileStorage.prototype = {
            getCollection: getCollection,
            saveCollection: saveCollection
        };

        return (PersistentFileStorage);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('storage');

    app.factory('fakeDbService', ["$window", function($window) {
        var indexedDB;

        function init() {
            if (!$window.mockIndexedDB) {
                return;
            }

            indexedDB = $window.mockIndexedDB;
        }

        init();

        return {
            indexDB: indexedDB
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('storage');

    app.factory('storageFileService', ["$q", "$window", "halFileService", "logService", "FileUtil", "pathService", "FileError", function($q, $window, halFileService, logService, FileUtil, pathService, FileError) {
        function init() {
        }

        // Initialise the service
        init();

        var systemPath = {
            data: halFileService.getSystemPath('data'),
            internalData: halFileService.getSystemPath('internalData'),
            externalData: halFileService.getSystemPath('externalData'),
            cache: halFileService.getSystemPath('cache'),
        };

        // Creates a directory on the local storage
        function createDirectory(path) {
            var defer = $q.defer();
            var p = halFileService.createDirectory(path);
            if (p === null) {
                // Not implemented by driver
                defer.reject('Not implemented');
            } else {
                p.then(
                    function(res) {
                        logService.trace('appspace.core.storage', 'Created directory: ' + path);
                        defer.resolve(res);
                    }
                    , function(err) {
                        logService.trace('appspace.core.storage', 'Failed to create directory: ' + path);
                        defer.reject(err);
                    }
                );
            }

            return defer.promise;
        }

        // Creates a files on the local storage
        function createFile(path) {
            var defer = $q.defer();
            var p = halFileService.createFile(path);
            if (p === null) {
                // Not implemented by driver
                defer.reject('Not implemented');
            } else {
                p.then(
                    function(res) {
                        logService.trace('appspace.core.storage', 'Created file: ' + path);
                        defer.resolve(res);
                    }
                    , function(err) {
                        logService.trace('appspace.core.storage', 'Failed to create file: ' + path);
                        defer.reject(err);
                    }
                );
            }

            return defer.promise;
        }

        // Checks if a file exists in local storage
        function checkFile(path) {
            logService.debug('appspace.core.storage', 'Checking file: ' + path);
            var defer = $q.defer();
            var p = halFileService.checkFile(path);
            if (p === null) {
                // Not implemented by driver
                defer.reject('Not implemented');
            } else {
                p.then(
                    function(res) {
                        logService.debug('appspace.core.storage', 'Check file: ' + path + ' returned ' + JSON.stringify(res));
                        defer.resolve(res);
                    }, function(err) {
                        logService.debug('appspace.core.storage', 'Failed to check file: ' + path);
                        defer.reject(err);
                    }
                );
            }

            return defer.promise;
        }

        // Write content to file in local storage
        function writeFile(path, content) {
            var defer = $q.defer();
            var p = halFileService.writeFile(path, content);
            if (p === null) {
                // Not implemented by driver
                defer.reject('Not implemented');
            } else {
                p.then(
                    function(res) {
                        logService.trace('appspace.core.storage', 'Write file: ' + path + ' returned ' + JSON.stringify(res));
                        defer.resolve(res);
                    }
                    , function(err) {
                        logService.trace('appspace.core.storage', 'Failed to write file: ' + path);
                        defer.reject(err);
                    }
                );
            }

            return defer.promise;
        }

        // Read content from file in local storage
        function readFile(path) {
            var defer = $q.defer();
            var p = halFileService.readFile(path);
            if (p === null) {
                // Not implemented by driver
                defer.reject('Not implemented');
            } else {
                p.then(
                    function(res) {
                        logService.trace('appspace.core.storage', 'Read file: ' + path + ' returned ' + res);
                        defer.resolve(res);
                    }
                    , function(err) {
                        logService.trace('appspace.core.storage', 'Failed to read file: ' + path);
                        defer.reject(err);
                    }
                );
            }

            return defer.promise;
        }

        // Moves or renames a file on a local storage.
        function moveFile(sourcePath, destinationPath) {
            var defer = $q.defer();
            var p = halFileService.moveFile(sourcePath, destinationPath);
            if (p === null) {
                // Not implemented by driver
                defer.reject('Not implemented');
            } else {
                p.then(
                    function(res) {
                        logService.trace('appspace.core.storage', 'Moved file: ' + sourcePath + ' to ' + destinationPath);
                        defer.resolve(res);
                    }
                    , function(err) {
                        logService.trace('appspace.core.storage', 'Failed to move file: ' + sourcePath + ' to ' + destinationPath);
                        defer.reject(err);
                    }
                );
            }

            return defer.promise;
        }

        // Deletes a file on local storage
        function removeFile(path) {
            var defer = $q.defer();
            var p = halFileService.removeFile(path);
            if (p === null) {
                // Not implemented by driver
                defer.reject('Not implemented');
            } else {
                p.then(
                    function(res) {
                        logService.trace('appspace.core.storage', 'Removed file: ' + path);
                        defer.resolve(res);
                    }
                    , function(err) {
                        logService.trace('appspace.core.storage', 'Failed to remove file: ' + path);
                        defer.reject(err);
                    }
                );
            }

            return defer.promise;
        }

        // Checks if a directory exists in local storage
        function checkDirectory(path) {
            var defer = $q.defer();
            var p = halFileService.checkDirectory(path);
            if (p === null) {
                // Not implemented by driver
                defer.reject('Not implemented');
            } else {
                p.then(
                    function(res) {
                        logService.trace('appspace.core.storage', 'Check directory: ' + path + ' returned ' + res);
                        defer.resolve(res);
                    }
                    , function(err) {
                        logService.trace('appspace.core.storage', 'Failed to check directory: ' + path);
                        defer.reject(err);
                    }
                );
            }

            return defer.promise;
        }

        // append and overwrite file
        function moveDirectory(sourcePath, destinationPath) {
            var defer = $q.defer();
            var sourcePath = resolveRelative(sourcePath);
            var destinationPath = resolveRelative(destinationPath);

            // iterate through each element and copy it
            var copyItemDir = function() {
                var promises = [];

                var overwriteFile = function(entry) {
                    var defer = $q.defer();

                    var fileName = FileUtil.getBaseName(entry.path);
                    var path = pathService.join(destinationPath, fileName);

                    removeFile(path).then($angular.noop, $angular.noop).finally(function() {
                        // console.log('move from', entry.path, 'to>>', path);
                        moveFile(entry.path, path).then(function(res) {
                            defer.resolve(res);
                        }, function(err) {
                            defer.reject(err);
                        });
                    });
                    return defer.promise;
                };

                // loop through cache directory
                enumerateDirectory(sourcePath).then(function(item) {
                    // fixed file
                    for (var i in item) {
                        var p = overwriteFile(item[i]);
                        promises.push(p);
                    }

                    // when it's done
                    $q.all(promises).then(function(res) {
                        removeDirectory(sourcePath).finally(function() {
                            defer.resolve(res);
                        });
                    }, function(err) {
                        defer.reject(err);
                    });
                }, function(err) {
                    var fileError = new FileError({
                        message: err,
                        path: sourcePath
                    });

                    defer.reject(fileError);
                });
            };

            // ensure directory exists
            createDirectory(destinationPath).finally(function() {
                copyItemDir();
            });
            return defer.promise;
        }

        // Deletes a directory and its content on local storage
        function removeDirectory(path) {
            var defer = $q.defer();
            var p = halFileService.removeDirectory(path);
            if (p === null) {
                // Not implemented by driver
                defer.reject('Not implemented');
            } else {
                p.then(
                    function(res) {
                        logService.trace('appspace.core.storage', 'Removed directory: ' + path);
                        defer.resolve(res);
                    }
                    , function(err) {
                        logService.trace('appspace.core.storage', 'Failed to remove directory: ' + path);
                        defer.reject(err);
                    }
                );
            }

            return defer.promise;
        }

        // Read a directory and its content on local storage
        function enumerateDirectory(path) {
            var defer = $q.defer();
            var p = halFileService.enumerateDirectory(path);
            if (p === null) {
                // Not implemented by driver
                defer.reject('Not implemented');
            } else {
                p.then(
                    function(res) {
                        logService.trace('appspace.core.storage', 'Read directory: ' + path);
                        defer.resolve(res);
                    }
                    , function(err) {
                        logService.trace('appspace.core.storage', 'Failed to read directory: ' + path);
                        defer.reject(err);
                    }
                );
            }

            return defer.promise;
        }

        function resolveUrl(path) {
            var p = halFileService.resolveUrl(path);

            if (p !== null || p !== undefined) {
                return p;
            }

            return '';
        }

        function resolveRelative(path) {
            var p = halFileService.resolveRelative(path);

            if (p !== null || p !== undefined) {
                return p;
            }

            return '';
        }

        return {
            // Public interface

            SystemPath: systemPath,
            resolveUrl: resolveUrl,
            resolveRelative: resolveRelative,
            createDirectory: createDirectory,
            createFile: createFile,
            writeFile: writeFile,
            readFile: readFile,
            checkFile: checkFile,
            moveFile: moveFile,
            removeFile: removeFile,
            checkDirectory: checkDirectory,
            removeDirectory: removeDirectory,
            moveDirectory: moveDirectory,
            enumerateDirectory: enumerateDirectory
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('storage');

    app.factory('storageLocalStorageService', ["$q", "halDriverService", "halPlatformService", "logService", "dataUtil", "FEATURE_CODE", function(
        $q,
        halDriverService,
        halPlatformService,
        logService,
        dataUtil,
        FEATURE_CODE
    ) {
        var memCache = {};
        var storage = null;
        var isInit = false;
        var globalName = null;
        var contentPrefix = 'content$';
        function init() {
            var defer = $q.defer();
            if (isInit) {
                defer.resolve();
                return defer.promise;
            }

            halDriverService.isReady().then(function() {
                isInit = true;
                storage = halPlatformService.getLocalStorage();
                defer.resolve();
            });
            return defer.promise;
        }

        // Initialise the service
        init();

        function isReady() {
            return init();
        }

        function setGlobalIdentifier(id) {
            globalName = id;
        }

        // Stores a serialisable object based on a key and optionally a collection name
        function set(collection, key, value) {
            var uniqueKey = '*' + collection + '>>' + key;
            if (globalName) {
                uniqueKey = '^' + globalName + uniqueKey;
            } else {
                globalName = '';
            }

            if (memCache[globalName + collection] === undefined) {
                // Add collection
                memCache[globalName + collection] = {};
            }

            if (memCache[globalName + collection][key] == undefined) {
                // Add Key
            }

            // Set mem cache
            memCache[globalName + collection][key] = value;
            try {
                // Persist
                storage.setItem(uniqueKey, value);
            } catch (e) {
                logService.error('appspace.core.storage', 'Unable to setItem', {valueLength: value.length, collection: collection, key: key, exception: e});
                // throw back the exception so that it can be handled by the consumer
                throw e;
            }
        }

        function generateCollectionKey(collection) {
            return contentPrefix + collection;
        }

        function contentSet(collection, key, value) {
            var contentCollection = generateCollectionKey(collection);
            set(contentCollection, key, value);
        }

        // Retrieves an object based on a key and optionally a collection name
        function get(collection, key) {
            var uniqueKey = '*' + collection + '>>' + key;
            globalName = globalName || '';
            if (globalName) {
                uniqueKey = '^' + globalName + uniqueKey;
            }

            var collectionKey = globalName + collection;
            var isMemoryCacheSupported = dataUtil.isValueBooleanTrue(halPlatformService.getFeatureSupport(FEATURE_CODE.STORAGE_MEMORY_CACHE_SUPPORT));
            if (memCache[collectionKey] && memCache[collectionKey][key] && isMemoryCacheSupported) {
                // Return from mem cache
                return memCache[collectionKey][key];
            }

            // Return from storage
            var item = storage.getItem(uniqueKey);
            if (item && isMemoryCacheSupported) {
                // Ensure its in memcache
                memCache[collectionKey] = memCache[collectionKey] || {};
                memCache[collectionKey][key] = item;
            }

            return item;
        }

        function contentGet(collection, key) {
            var contentCollection = generateCollectionKey(collection);
            return get(contentCollection, key);
        }

        // Retrieves all object in a collection
        function getKeys(collection, includeValue) {
            var keyIndex = '*' + collection + '>>';
            if (globalName) {
                keyIndex = '^' + globalName + keyIndex;
            }

            var keys = [];
            for (var i = 0; i < storage.length; i++) {
                var key = storage.key(i);
                if (key.indexOf(keyIndex) == 0) {
                    var leanKey = key.split('>>')[1];
                    if (includeValue === undefined || includeValue == true) {
                        keys.push({key: leanKey, value: storage.getItem(key)});
                    } else {
                        keys.push({key: leanKey});
                    }
                }
            }

            return keys;
        }

        function contentGetKeys(collection, includeValue) {
            var contentCollection = generateCollectionKey(collection);
            return getKeys(contentCollection, includeValue);
        }

        // Retrieves all collections
        function getCollections() {
            var collections = [];
            var added = {};
            for (var i = 0; i < storage.length; i++) {
                var key = storage.key(i);
                if (key.indexOf('*') >= 0 && key.indexOf('>>') >= 0) {
                    var leanCollection = key.split('>>')[0].substring(key.indexOf('*') + 1);
                    if (added[leanCollection] === undefined) {
                        added[leanCollection] = true;
                        collections.push(leanCollection);
                    }
                }
            }

            return collections;
        }

        // Removes a Key from a collection
        function removeKey(collection, key) {
            var uniqueKey = '*' + collection + '>>' + key;
            if (globalName) {
                uniqueKey = '^' + globalName + uniqueKey;
            } else {
                globalName = '';
            }

            if (memCache[globalName + collection] === undefined) {
                // Add collection
                memCache[globalName + collection] = {};
            }

            // Remove from mem cache
            memCache[globalName + collection][key] = undefined;

            // Persist remove
            storage.removeItem(uniqueKey);
        }

        function contentRemoveKey(collection, key) {
            var contentCollection = generateCollectionKey(collection);
            removeKey(contentCollection, key);
        }

        // Removes all items in a collection
        function removeCollection(collection) {
            var keys = getKeys(collection, false);
            for (var i = 0; i < keys.length; i++) {
                var item = keys[i];
                removeKey(collection, item.key);
            }

            return keys;
        }

        function contentRemoveCollection(collection) {
            var collections = getCollections();
            for (var i = 0; i < collections.length; i++) {
                var collectionName = collections[i];
                if (collectionName.indexOf(contentPrefix) == -1) {
                    continue;
                }

                var secondCheck = collectionName.indexOf(collection);
                if (secondCheck >= contentPrefix.length &&
                    (secondCheck + collection.length) == collectionName.length) {
                    return removeCollection(collectionName);
                }
            }
        }

        function contentRemoveAll() {
            var collections = getCollections();
            for (var i = 0; i < collections.length; i++) {
                var collectionName = collections[i];
                if (collectionName.indexOf(contentPrefix) == 0) {
                    removeCollection(collectionName);
                }
            }
        }

        return {
            // Public interface
            isReady: isReady,
            content: {
                set: contentSet,
                get: contentGet,
                getKeys: contentGetKeys,
                removeKey: contentRemoveKey,
                removeCollection: contentRemoveCollection,
                removeAll: contentRemoveAll
            },
            set: set,
            get: get,
            getKeys: getKeys,
            getCollections: getCollections,
            removeKey: removeKey,
            removeCollection: removeCollection,
            setGlobalIdentifier: setGlobalIdentifier // to indicate the collections belongs to a global namespace
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('storage');

    app.factory('storageObjectDbService', ["$window", "platformUtil", "VersionCompare", "UrlUtil", "fakeDbService", "embedProfileService", function($window, platformUtil, VersionCompare, UrlUtil, fakeDbService, embedProfileService) {
        var isNative = false;
        var indexedDB;

        function initFakeDb() {
            if (embedProfileService.isPreviewMode()) {
                indexedDB = fakeDbService.indexDB;
                return true;
            }
        }

        function initIEC260() {
            if (checkIEC260()) {
                initShim();
                return true;
            }
        }

        function initIndexedDb() {
            if ($window.indexedDB) {
                indexedDB = $window.indexedDB;
                isNative = true;
                return true;
            }
        }

        function initMozIndexedDb() {
            if ($window.mozIndexedDB) {
                indexedDB = $window.mozIndexedDB;
                return true;
            }
        }

        function initWebKitIndexedDb() {
            if ($window.webkitIndexedDB) {
                indexedDB = $window.webkitIndexedDB;
                return true;
            }
        }

        function initMsIndexedDb() {
            if ($window.msIndexedDB) {
                indexedDB = $window.msIndexedDB;
                return true;
            }
        }

        function initShimIndexedDB() {
            if ($window.shimIndexedDB) {
                initShim();
                return true;
            }
        }

        var dbConfigurations = [
            {tryInit: initFakeDb},
            {tryInit: initIEC260},
            {tryInit: initIndexedDb},
            {tryInit: initMozIndexedDb},
            {tryInit: initWebKitIndexedDb},
            {tryInit: initMsIndexedDb},
            {tryInit: initShimIndexedDB},
        ];

        function initShim() {
            $window.shimIndexedDB.__useShim();
            indexedDB = $window.shimIndexedDB;
        }

        function checkIEC260() {
            var platform = platformUtil.detectPlatform($window.navigator.userAgent);
            // This is a workaround to check IEC FW 2.6.0. There's a bug with their IndexedDB which Cisco did'nt fix
            if (platform.toLowerCase() != 'iec' || !global || !global.device || !global.device.version) {
                return false;
            }
            var vc = new VersionCompare();
            return vc.compare(global.device.version, '2.6.0', {lexicographical: true});
        }

        function init() {
            // Check all prefixes for implementations we will support, shim is the last option
            for (var i = 0; i < dbConfigurations.length; i++) {
                if (dbConfigurations[i].tryInit()) {
                    break;
                }
            }
        }

        init();

        return {
            // Public interface
            isNative: isNative,
            indexDB: indexedDB
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('storage');

    app.service('sessionService', ["dataUtil", "UrlUtil", "halPlatformService", function(dataUtil, UrlUtil, halPlatformService) {
        var sessionStorage;
        function init() {
            if (UrlUtil.processNativeQueryString()) {
                return;
            }

            sessionStorage = halPlatformService.getSessionStorage();
            var urlParameters = UrlUtil.retrieveParams();
            if (Object.keys(urlParameters).length > 0) {
                if (urlParameters.clearparams == 'true') {
                    clearSession();
                    delete urlParameters.clearparams;
                }

                setSessionValue('urlParameters', urlParameters);
                if (urlParameters.preview != 'true') {
                    UrlUtil.clearParams();
                }
            }
        }

        function setSessionValue(key, value) {
            if (!sessionStorage || !key || dataUtil.isNullOrUndefined(value) || typeof (key) != 'string') {
                return;
            }

            sessionStorage.setItem(key.toLowerCase(), JSON.stringify(value));
        }

        function getSessionValue(key) {
            if (!sessionStorage || !key || dataUtil.isNullOrUndefined(key) || typeof (key) != 'string') {
                return;
            }

            var value = sessionStorage.getItem(key.toLowerCase());
            return value ? JSON.parse(value) : value;
        }

        function clearSession(skippedKeys) {
            if (!sessionStorage) {
                return;
            }

            skippedKeys = skippedKeys || [];
            var skippedKeysCount = skippedKeys.length;
            var temporaryStorage = {};
            if (skippedKeysCount) {
                for (var i = 0; i < skippedKeysCount; i++) {
                    var key = skippedKeys[i];
                    if (!key) {
                        continue;
                    }

                    temporaryStorage[key] = getSessionValue(key);
                }
            }

            halPlatformService.clearSessionStorage();
            if (!skippedKeysCount || dataUtil.isObjectEmpty(temporaryStorage)) {
                return;
            }

            // After clearing storage, make sure that the skipped keys are returned to storage
            for (var storageKey in temporaryStorage) {
                setSessionValue(storageKey, temporaryStorage[storageKey]);
            }
        }

        function getUrlParameters() {
            return getSessionValue('urlParameters') || {};
        }

        function getSessionStorage() {
            return sessionStorage;
        }

        this.set = setSessionValue;
        this.get = getSessionValue;
        this.clear = clearSession;
        this.remove = function(key) {
            if (!sessionStorage) {
                return;
            }

            if (getSessionValue(key)) {
                sessionStorage.removeItem(key.toLowerCase());
            }
        };

        this.getUrlParameters = getUrlParameters;
        this.getSessionStorage = getSessionStorage;
        init();
    }]);
})(window.angular);

(function ($angular) {

    "use strict";

  var app = $angular.module('storage');
  
  app.factory("storageSqlDbService", ["$q", "$window", function($q, $window) {
  
    var isNative = true;
    
    function init() {
    }
    
    //Initialise the service
    init();
    
    //Returns the implementation for WebSQL
    function getDb(name, version, description, size) {
      return openDatabase(name, version, description, size);
    }
    
    return {
      //Public interface
      getDb: getDb,
      IsNative: isNative
    };
  }]);
})(window.angular);
(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('storage');

    app.factory('dataUtil', ["md5", "$window", "$sanitize", function(md5, $window, $sanitize) {
        var SI_MULTIPLIER = 1000;
        var SI_SIZES = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
        var IEC_MULTIPLIER = 1024;
        var IEC_SIZES = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
        var ARRAY_ITEM_LENGTH_TO_REMOVE = 1;
        var OBJECT_PATH_SEPARATOR = '.';
        var NEW_LINE_REGEX = /\r\n|\r|\n/g;
        var REPLACEMENTS = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '\\\'', "`": '&#96;' };
        var WHITESPACE_REGEX = /\s/;
        var KEY_VALUE_PAIR_SEPARATOR = '=';
        var INVALID_SEPARATOR_POSITION = -1;
        var MEGABYTE_TO_BYTE_MULTIPLIER = 1048576;
        var BASE64_PROTOCOL = 'base64://';
        // GUID regex pattern: 8-4-4-4-12 hexadecimal characters
        var GUID_REGEX_PATTERN = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;

        function removeKeysFromObject(keys, currentCollection, getCallback, setCallback) {
            if (!keys) {
                return;
            }

            if (typeof (keys) != 'object') {
                keys = [keys];
            }

            var newCollection = {};
            var removedKeys = [];
            for (var i = 0; i < keys.length; i++) {
                var currentKey = keys[i];
                var parameter = getCallback ? getCallback(currentKey) : currentCollection[currentKey];
                if (!parameter) {
                    continue;
                }

                removedKeys.push(currentKey);
            }

            for (var key in currentCollection) {
                if (removedKeys.indexOf(key) >= 0) {
                    continue;
                }

                var currentParameter = currentCollection[key];
                newCollection[key] = setCallback ? setCallback(key, currentParameter) : currentParameter;
            }

            return newCollection;
        }

        function duplicateObject(keys, currentCollection) {
            if (!keys) {
                return {};
            }

            if (typeof (keys) != 'object') {
                keys = [keys];
            }

            var newCollection = {};

            for (var i = 0; i < keys.length; i++) {
                var key = keys[i];
                var item = currentCollection[key];
                if (!item) {
                    continue;
                }

                newCollection[key] = item;
            }

            return newCollection;
        }

        function overwriteObject(targetObject, sourceObject) {
            if (!isObject(targetObject) || !isObject(sourceObject)) {
                return;
            }

            for (var key in sourceObject) {
                if (targetObject[key] === undefined) {
                    continue;
                }

                targetObject[key] = sourceObject[key];
            }

            return targetObject;
        }

        function mergeObject(targetObject, sourceObject) {
            if (!isObject(targetObject) || !isObject(sourceObject)) {
                return;
            }

            var result = angular.copy(targetObject);
            for (var key in sourceObject) {
                result[key] = sourceObject[key];
            }

            return result;
        }

        function isNullOrUndefined(value) {
            return value == undefined || value == null;
        }

        function isMoreThanZero(value) {
            // parseInt a null, undefined, empty or alphabet will always be false when checking against a number
            return parseInt(value, 10) > 0;
        }

        function httpParamSerializer(object) {
            if (!isObject(object)) {
                return '';
            }

            var res = [];
            for (var key in object) {
                if (!object[key]) {
                    continue;
                }

                res.push(key + '=' + object[key]);
            }

            return res.join('&');
        }

        function isObject(obj) {
            return obj && typeof obj === 'object';
        }

        function isObjectEmpty(obj) {
            return isObject(obj) && Object.keys(obj).length == 0;
        }

        function isObjectEmptyOrUndefined(value) {
            return isNullOrUndefined(value) || isObjectEmpty(value);
        }

        function toLowerCase(value) {
            value = value || '';
            return isString(value) ? value.toLowerCase() : value;
        }

        function isStringFalse(value) {
            return toLowerCase(value) == 'false';
        }

        function isStringTrue(value) {
            return toLowerCase(value) == 'true';
        }

        function isBooleanTrue(value) {
            return value === true;
        }

        function isBooleanFalse(value) {
            return value === false;
        }

        function isBoolean(value) {
            return typeof value === 'boolean';
        }

        function isArray(value) {
            return Boolean(value && Array.isArray(value));
        }

        function isArrayEqual(source, target) {
            return (!isArray(source) || !isArray(target)) ? false : _.isEqual(source, target);
        }

        function parseBoolean(value) {
            return isBooleanTrue(value) || (!isBooleanFalse(value) && !isNumber(value) && isStringTrue(value));
        }

        function objectToCssStyling(obj) {
            if (isObjectEmpty(obj)) {
                return '';
            }

            var res = '';
            for (var key in obj) {
                res += key + ':' + obj[key] + ';';
            }

            return res;
        }

        function isObjectEqual(object1, object2) {
            return JSON.stringify(object1) == JSON.stringify(object2);
        }

        function isObjectPropertiesDifferent(source, target, exclusions) {
            if (!isObject(source) || !isObject(target)) {
                return true;
            }

            var differences = [];
            exclusions = exclusions && isArray(exclusions) ?
                exclusions : [];
            for (var key in source) {
                if (exclusions.indexOf(key) >= 0 || isObjectEqual(source[key], target[key])) {
                    continue;
                }

                differences.push(key);
            }

            return differences.length > 0;
        }

        function stripNewLines(text) {
            return replaceNewLines(text, '');
        }

        function replaceNewLines(source, target) {
            return source.replace(NEW_LINE_REGEX, target);
        }

        function stripSpaces(text) {
            return text.replace(/ +/g, '');
        }

        function containsWhitespace(text) {
            return isString(text) && WHITESPACE_REGEX.test(text);
        }

        function encodeHtml(string) {
            string = string || '';
            string = $('<div>').text(string).html();
            string = replaceNewLines(string, '<br/>');
            return string;
        }

        function decodeHtml(value) {
            var result = $('<div>').html(value || '').text();
            // convert non-breaking-space to normal space
            return result.replace(String.fromCharCode(160), ' ');
        }

        function toHash(value) {
            if (!isString(value)) {
                value = JSON.stringify(value);
            }

            return md5.createHash(value);
        }

        function mergeCommonPlacements(convertedPlacement, placement) {
            convertedPlacement.width = placement.width;
            convertedPlacement.height = placement.height;
            if (!isNullOrUndefined(placement.zIndex)) {
                convertedPlacement.zIndex = placement.zIndex;
            }

            return convertedPlacement;
        }

        function toAppPlacement(placement) {
            if (!placement) {
                return;
            }

            return mergeCommonPlacements({x: placement.left, y: placement.top}, placement);
        }

        function toCssPlacement(placement) {
            if (!placement) {
                return;
            }

            return mergeCommonPlacements({left: placement.x, top: placement.y}, placement);
        }

        function getValidValue(checkValue, fallbackValue) {
            return isNullOrUndefined(checkValue) ? fallbackValue : checkValue;
        }

        function pushIfNotExist(arrayToPush, value) {
            if (!Array.isArray(arrayToPush) || arrayToPush.indexOf(value) !== -1) {
                return;
            }

            arrayToPush.push(value);
        }

        function toPlacementInPixels(canvasSize, placement) {
            if (!canvasSize || !placement || isNullOrUndefined(canvasSize.width) || isNullOrUndefined(canvasSize.height) ||
                isNullOrUndefined(placement.top) || isNullOrUndefined(placement.left) ||
                isNullOrUndefined(placement.width) || isNullOrUndefined(placement.height)) {
                return;
            }

            return {
                left: parseInt(placement.left * canvasSize.width, 10),
                top: parseInt(placement.top * canvasSize.height, 10),
                width: parseInt(placement.width * canvasSize.width, 10),
                height: parseInt(placement.height * canvasSize.height, 10)
            };
        }

        function sortNumericArray(array, descending) {
            if (!array || !Array.isArray(array)) {
                return;
            }

            return array.sort(function(a, b) {
                if (!descending) {
                    return a - b;
                }

                return b - a;
            });
        }

        function isNumber(value) {
            return !isNullOrUndefined(value) &&
                value !== '' &&
                !isArray(value) &&
                !isNaN(value);
        }

        function isString(value) {
            return typeof value === 'string';
        }

        function formatBytes(bytes, decimals) {
            return formatBytesInternal(bytes, decimals, SI_MULTIPLIER,
                SI_SIZES);
        }

        function formatBytesIEC(bytes, decimals) {
            // https://en.wikipedia.org/wiki/Binary_prefix
            return formatBytesInternal(bytes, decimals, IEC_MULTIPLIER,
                IEC_SIZES);
        }

        function formatBytesInternal(bytes, decimals, multiplier, sizes) {
            if (isNullOrUndefined(bytes) || !isNumber(bytes)) {
                return bytes;
            }

            if (bytes == 0) {
                return '0 Byte';
            }

            var decimalPoints = decimals + 1 || 3;
            var i = Math.floor(Math.log(bytes) / Math.log(multiplier));
            return parseFloat((bytes / Math.pow(multiplier, i)).toFixed(decimalPoints)) + ' ' + sizes[i];
        }

        function includesValue(value, validValues) {
            return validValues.indexOf(value) >= 0;
        }

        function includesString(keyword, searchTarget) {
            return searchTarget.indexOf(keyword) >= 0;
        }

        function includesArrayValue(array, validValues) {
            for (var i = 0; i < array.length; i++) {
                if (includesValue(array[i], validValues)) {
                    return true;
                }
            }

            return false;
        }

        function toMinutes(milliseconds) {
            return milliseconds / 60000;
        }

        function tryParseJSON(jsonString) {
            try {
                var result = JSON.parse(jsonString);
                if (isObject(result)) {
                    return result;
                }
            } catch (error) {
                return false;
            }

            return false;
        }

        function removeArrayItemAtIndex(array, index) {
            array.splice(index, ARRAY_ITEM_LENGTH_TO_REMOVE);
        }

        function getObjectValueAtPath(object, path) {
            if (!isObject(object) || !isString(path)) {
                return undefined;
            }

            var segments = path.split(OBJECT_PATH_SEPARATOR);
            var value = object;
            for (var i = 0; i < segments.length; i++) {
                value = value[segments[i]];
                // stop traversing once we found undefined property
                if (!value) {
                    return undefined;
                }
            }

            return value;
        }

        function splitByNewLines(string) {
            return isString(string) ? string.split(NEW_LINE_REGEX) : undefined;
        }

        function getKeyValuePairSeparatorIndex(string) {
            if (!isString(string)) {
                return undefined;
            }

            var index = string.indexOf(KEY_VALUE_PAIR_SEPARATOR);
            if (index === 0 || index === INVALID_SEPARATOR_POSITION) {
                return undefined;
            }

            return index;
        }

        function getKeyValuePairFromString(string) {
            var separatorIndex = getKeyValuePairSeparatorIndex(string);
            if (!separatorIndex) {
                return undefined;
            }

            var key = string.substring(0, separatorIndex).toLowerCase();
            var value = decodeHtml(string.substring(separatorIndex + 1));
            return {
                key: key,
                value: value
            };
        }

        function convertBase64ToBlob(base64Source, mimeType) {
            if (!mimeType || !isString(base64Source) || !base64Source.startsWith(BASE64_PROTOCOL)) {
                return;
            }

            try {
                var base64Data = base64Source.replace(BASE64_PROTOCOL, '');
                var base64String = 'data:' + mimeType + ';base64,'+ base64Data;
                var byteString = atob(base64String.split(',')[1]);
                var blobParts = new Uint8Array(byteString.length);
                for (let i = 0; i < byteString.length; i++) {
                  blobParts[i] = byteString.charCodeAt(i);
                }

                return new Blob([blobParts], {type: mimeType});
            } catch (error) {
                return undefined;
            }
        }

        function setObjectValueAtPath(object, path, value) {
            if (!isObject(object)) {
                throw new Error('Invalid object parameter');
            }

            if (!isString(path)) {
                throw new Error('Invalid path parameter');
            }

            var pathParts = path.split(OBJECT_PATH_SEPARATOR);
            var current = object;

            for (var i = 0; i < pathParts.length - 1; i++) {
                var key = pathParts[i];

                // Ensure the key exists and is an object
                if (!current[key] || !isObject(current[key])) {
                    current[key] = {};
                }

                // Prepare to traverse deeper by setting current with the sub-object
                current = current[key];
            }

            // Assign the value to the final key
            current[pathParts[pathParts.length - 1]] = value;

            return object;
        }

        function trim(text) {
            return text ? text.trim() : text;
        }

        function parseInlineStyle(value) {
            var result;
            try {
                // eslint-disable-next-line new-cap
                result = $window.InlineStyleParser(value);
            } catch (error) {
                result = [];
            }

            return result;
        }

        function cssToJson(value) {
            var inlineStyles = parseInlineStyle(value);
            var result = {};
            if (!inlineStyles.length) {
                return result;
            }

            for (var i = 0; i < inlineStyles.length; i++) {
                var style = inlineStyles[i] || {};
                style.property = trim(style.property || '').toLowerCase();
                result[style.property] = style.value || '';
            }

            return result;
        }

        function convertMegabyteToByte(value) {
            return value * MEGABYTE_TO_BYTE_MULTIPLIER;
        }

        function isPromise(value) {
            return value && value.then instanceof Function;
        }

        function aesEncrypt(value, passphrase) {
            return $window.CryptoJS.AES.encrypt(value, passphrase).toString();
        }

        function aesDecrypt(value, passphrase) {
            return $window.CryptoJS.AES.decrypt(value, passphrase).toString($window.CryptoJS.enc.Utf8);
        }

        function getArrayMinValue(array) {
            var minValue = array[0];
            for (var i = 1; i < array.length; i++) {
                minValue = Math.min(minValue, array[i]);
            }

            return minValue;
        }

        function sanitizeObjectProperties(object, properties) {
            return _.pick(object, properties);
        }

        function generateRandomGuid() {
            return uuid.v1();
        }

        function generateRandomNumber(min, max) {
            return isNumber(min) && isNumber(max) && max > min ?
                Math.floor(Math.random() * (max - min + 1)) + min : 0;
        }

        function formatNumber(number) {
            return number >= 1000 ? numeral(number).format('0.0a') : number.toString();
        }

        function serializeObjectCamelCase(o) {
            var newO;
            var origKey;
            var newKey;
            var value;
            if (o instanceof Array) {
                return o.map(function(value) {
                    if (typeof value === 'object') {
                        value = serializeObjectCamelCase(value);
                    }

                    return value;
                });
            } else {
                newO = {};
                for (origKey in o) {
                    if (o.hasOwnProperty(origKey)) {
                        newKey = (origKey.charAt(0).toLowerCase() + origKey.slice(1) || origKey).toString();
                        value = o[origKey];
                        if (value instanceof Array || (value !== null && value.constructor === Object)) {
                            value = serializeObjectCamelCase(value);
                        }

                        newO[newKey] = value;
                    }
                }
            }

            return newO;
        }

        function convertUnicodeString(value) {
            return $sanitize(value.replace(/\\u([\dA-F]{4})/gi, function(match, matchedGroup) {
                 return String.fromCharCode(parseInt(matchedGroup, 16));
             }));
         }

         function isArrayIdentical(arr1, arr2) {
            if (!isArray(arr1) || !isArray(arr2)) {
                return false;
            }

            if (arr1.length !== arr2.length) {
                return false;
            }

            arr1 = arr1.sort();
            arr2 = arr2.sort();

            for (var i = 0; i < arr1.length; i++) {
                if (arr1[i] !== arr2[i]) {
                    return false;
                }
            }

            return true;
         }

        function convertCSVToArray(csvString) {
            if (!isString(csvString)) {
                return [];
            }

            // Split the CSV string by commas
            return csvString.split(',');
        }

        function isValidGuid(value) {
            if (isNullOrUndefined(value) || value === '' || !isString(value)) {
                return false;
            }

            return GUID_REGEX_PATTERN.test(value);
        }

        function replaceStringPlaceholder(string, searchValue, replaceValue) {
            if (!isString(string) || !isString(searchValue) || !isString(replaceValue)) {
                return string;
            }

            var processedString = $angular.copy(string);
            return processedString.replace(searchValue, replaceValue);
        }

        function sanitizeString(string) {
            return String(string).replace(/[&<>"'`]/g, function(match) {
                return REPLACEMENTS[match];
            });
        }

        return {
            removeKeysFromObject: removeKeysFromObject,
            duplicateObject: duplicateObject,
            overwriteObject: overwriteObject,
            isNullOrUndefined: isNullOrUndefined,
            isMoreThanZero: isMoreThanZero,
            mergeObject: mergeObject,
            httpParamSerializer: httpParamSerializer,
            isObjectEmpty: isObjectEmpty,
            isObject: isObject,
            isValueFalse: isStringFalse,
            parseBoolean: parseBoolean,
            isValueTrue: isStringTrue,
            isValueBooleanTrue: isBooleanTrue,
            isValueBooleanFalse: isBooleanFalse,
            objectToCssStyling: objectToCssStyling,
            isObjectEqual: isObjectEqual,
            isObjectPropertiesDifferent: isObjectPropertiesDifferent,
            stripNewLines: stripNewLines,
            stripSpaces: stripSpaces,
            containsWhitespace: containsWhitespace,
            encodeHtml: encodeHtml,
            decodeHtml: decodeHtml,
            toHash: toHash,
            toLowerCase: toLowerCase,
            toAppPlacement: toAppPlacement,
            toCssPlacement: toCssPlacement,
            getValidValue: getValidValue,
            pushIfNotExist: pushIfNotExist,
            toPlacementInPixels: toPlacementInPixels,
            sortNumericArray: sortNumericArray,
            isNumber: isNumber,
            isString: isString,
            isBoolean: isBoolean,
            isArray: isArray,
            isArrayEqual: isArrayEqual,
            formatBytes: formatBytes,
            formatBytesIEC: formatBytesIEC,
            includesValue: includesValue,
            includesArrayValue: includesArrayValue,
            toMinutes: toMinutes,
            tryParseJSON: tryParseJSON,
            removeArrayItemAtIndex: removeArrayItemAtIndex,
            getObjectValueAtPath: getObjectValueAtPath,
            splitByNewLines: splitByNewLines,
            getKeyValuePairFromString: getKeyValuePairFromString,
            trim: trim,
            parseInlineStyle: parseInlineStyle,
            convertMegabyteToByte: convertMegabyteToByte,
            isPromise: isPromise,
            aesEncrypt: aesEncrypt,
            aesDecrypt: aesDecrypt,
            cssToJson: cssToJson,
            isObjectEmptyOrUndefined: isObjectEmptyOrUndefined,
            getArrayMinValue: getArrayMinValue,
            sanitizeObjectProperties: sanitizeObjectProperties,
            generateRandomGuid: generateRandomGuid,
            generateRandomNumber: generateRandomNumber,
            includesString: includesString,
            formatNumber: formatNumber,
            serializeObjectCamelCase: serializeObjectCamelCase,
            convertUnicodeString: convertUnicodeString,
            setObjectValueAtPath: setObjectValueAtPath,
            convertBase64ToBlob: convertBase64ToBlob,
            convertCSVToArray: convertCSVToArray,
            isArrayIdentical: isArrayIdentical,
            isValidGuid: isValidGuid,
            replaceStringPlaceholder: replaceStringPlaceholder,
            sanitizeString: sanitizeString
        };
    }]);
})(window.angular);

(function($angular) {

    "use strict";

	//declare media module and its dependencies
    var app = $angular.module('ui', ['core', 'hal', 'ui.bootstrap' , 'AppCommon']);

	app.filter('trusted', ['$sce', function ($sce) {
    return function(url) {
        return $sce.trustAsResourceUrl(url);
    };

}]);

})(window.angular);
(function ($angular) {

    "use strict";

    var app = $angular.module('ui');

    app.service("asClickService", ["$window", function ($window) {

        function bind(element, callback) {
            if (element) {
                element[0].clickState = {
                    startTime: null,
                    callback: callback
                };

                if('ontouchstart' in $window){
                    element.on('touchstart mousedown', onStart);
                    element.on('touchend mouseup', onEnd);
                    element.on('touchmove mousemove', onMove);
                }
                else{
                    element.on('click', onClick);
                }
            }
        }

        function unbind(element) {
            if(element){
                element.off('touchstart mousedown', onStart);
                element.off('touchend mouseup', onEnd);
                element.off('touchmove mousemove', onMove);
                element.off('click', onClick);
                element[0].clickState = null;
            }
        }

        function onClick(e){
            if(e && e.currentTarget && e.currentTarget.clickState && e.currentTarget.clickState.callback){
                e.currentTarget.clickState.callback(e);
                e.stopPropagation();
                e.preventDefault();
            }
        }

        function onStart(e) {
            var focused = $(':focus');
            if(focused){
                for(var i=0; i<focused.length; i++){
                    focused[i].blur();
                }
            }
            
            if (e && e.currentTarget.clickState && !e.currentTarget.clickState.startTime) {
                e.currentTarget.clickState.startTime = new Date().getTime();
                var clientX = e.clientX;
                var clientY = e.clientY;

                var useTouch = (!clientX || !clientY) && e.originalEvent && e.originalEvent.touches && e.originalEvent.touches.length > 0;
                e.currentTarget.clickState.clientX = !clientX && useTouch ? e.originalEvent.touches[0].clientX : clientX;
                e.currentTarget.clickState.clientY = !clientY && useTouch ? e.originalEvent.touches[0].clientY : clientY;
            }
        }

        function onEnd(e) {
            if (e && e.currentTarget.clickState && e.currentTarget.clickState.startTime) {
                var time = new Date().getTime() - e.currentTarget.clickState.startTime;
                e.currentTarget.clickState.startTime = null;

                if (e.currentTarget.clickState.callback && time < 500) {
                    if(!e.clientX) {
                        e.clientX = e.currentTarget.clickState.clientX;
                    }

                    if(!e.clientY) {
                        e.clientY = e.currentTarget.clickState.clientY;
                    }
                    
                    e.currentTarget.clickState.callback(e);
                    e.stopPropagation();
                    e.preventDefault();
                }
            }
        }

        function onMove(e) {
            // cancel by nullify the start time
            if (e && e.currentTarget.clickState) {
                e.currentTarget.clickState.startTime = null;

            }
        }

        return {
            bind: bind,
            unbind: unbind,
        };

    }])

})(window.angular);
(function($angular) {
    'use strict';

    var app = $angular.module('ui');

    app.service('playlistSchedulerService', function() {
        var playoutRecord = {};
        var exactPlayouts = {};

        // Retrieve an item that is using exact timing and is scheduled to playout next
        function getNextItemWithExactTiming(playlistId, specificDate) {
            if (!playlistId) {
                return null;
            }
            // Check if there are any exact items for this playlist
            var exactItems = exactPlayouts[playlistId];
            if (!exactItems) {
                return null;
            }
            var cdate = specificDate;
            if (!cdate) {
                cdate = new Date();
            }

            var itemsReadToPlay = [];
            var i;
            // Find the next item ready to be played
            for (i = 0; i < exactItems.length; i++) {
                var exactItem = exactItems[i];
                if (isItemValid(exactItem.item, cdate, {checkFrequency: true})) {
                    // console.log("Found an item that we should play")
                    itemsReadToPlay.push(exactItem);
                }
            }
            var next;
            var nextRecord;
            for (i = 0; i < itemsReadToPlay.length; i++) {
                var item = itemsReadToPlay[i];
                var currentRecord = getItemPlayout(item.item);
                if (!next) {
                    next = item;
                    nextRecord = currentRecord;
                } else if (nextRecord.lastPlayTime > currentRecord.lastPlayTime) {
                    next = item;
                }
            }
            if (next) {
                return next.item;
            }
            return null;
        }

        function registerExactItems(playlistId, items) {
            if (!playlistId || !items) {
                return;
            }
            // Go through each item and create a record
            var records = [];
            for (var i = 0; i < items.length; i++) {
                var item = items[i];
                if (!item.schedule || !item.schedule.frequency) {
                    // No schedule
                    continue;
                }
                var freq = item.schedule.frequency;
                if (!freq || !freq.type || !freq.condition || freq.condition.toLowerCase() != 'exactly' || freq.type.toLowerCase() == 'loop') {
                    // No exact frequency restriction
                    continue;
                }
                var record = {
                    item: item,
                    maxIntervalBetweenPlayout: getMaxIntervalBetweenPlayout(freq.type, freq.value)
                };
                records.push(record);
            }
            // Did we find any records?
            if (records.length) {
                // Create a new record for this playlist
                exactPlayouts[playlistId] = records;
            } else {
                // Remove existing records
                exactPlayouts[playlistId] = undefined;
            }
        }

        // Retrive the playout record for an item
        function getItemPlayout(item) {
            if (!item || !item.uniqueId) {
                return null;
            }

            var record = playoutRecord[item.uniqueId];
            if (!record) {
                record = {
                    playCount: 0,
                    lastPlayTime: 0
                };
                playoutRecord[item.uniqueId] = record;
            }
            return record;
        }

        // Store the last time this item played
        function recordItemPlayout(item, specificDate) {
            if (!item || !item.uniqueId) {
                return;
            }
            var cdate;
            if (specificDate) {
                cdate = specificDate;
            } else {
                cdate = new Date();
            }
            // Retrieve the existing record for this item
            var record = getItemPlayout(item);

            // Update last playout time
            record.lastPlayTime = cdate.getTime();
        }

        function getMaxIntervalBetweenPlayout(unit, value) {
            // Caclulate the maxTime between playouts based on unit
            var minDurationBetweenPlayout = 0;
            switch (unit) {
                case 'minute':
                    var maxPerMinute = parseFloat(value);
                    minDurationBetweenPlayout = ((60 * 1000) / maxPerMinute); // Number of ms per playout
                    break;
                case 'hour':
                    var maxPerHour = parseFloat(value);
                    minDurationBetweenPlayout = ((60 * 60 * 1000) / maxPerHour); // Number of ms per playout
                    break;
                case 'day':
                    var maxPerDay = parseFloat(value);
                    minDurationBetweenPlayout = ((24 * 60 * 60 * 1000) / maxPerDay); // Number of ms per playout
                    break;
            }
            return minDurationBetweenPlayout;
        }

        // Check if there is a frequency restriction
        function isFrequencyValid(item, cdate) {
            var freq = item.schedule.frequency;

            if (!freq) {
                // No frequency restriction
                return true;
            }

            var playoutRecord = getItemPlayout(item);
            if (!playoutRecord) {
                return true;
            }

            if (!cdate) {
                cdate = new Date();
            }

            var ctime = cdate.getTime();
            var durationSinceLastPlayout = ctime - playoutRecord.lastPlayTime; // ms since last playout
            playoutRecord.playCount++;

            switch (freq.type.toLowerCase()) {
                case 'hour':
                case 'day':
                case 'minute':
                    var minDurationBetweenPlayout = getMaxIntervalBetweenPlayout(freq.type, freq.value);
                    if (durationSinceLastPlayout < minDurationBetweenPlayout) {
                        return false;
                    }
                    break;
                case 'loop':
                    var loopsPerPlay = parseFloat(freq.value) + 1;
                    if (playoutRecord.playCount < loopsPerPlay) {
                        return false;
                    }
                    playoutRecord.playCount = 0;
                    break;
            }

            return true;
        }

        // Check if there is a date schedule restriction
        function isDateValid(item, cdate) {
            if (!item.schedule) {
                // No date restriction
                return true;
            }

            var ctime = cdate.getTime();

            // Check are we passed the start date
            if (item.schedule.startDate) {
                var sdate = new Date(item.schedule.startDate);
                if (ctime < sdate.getTime()) {
                    return false;
                }
            }
            // Check are we before the end date
            if (item.schedule.endDate && item.schedule.playForever != true) {
                var edate = new Date(item.schedule.endDate);
                if (ctime > edate.getTime()) {
                    return false;
                }
            }


            // Check is there a recur rule
            if (item.schedule.recur) {
                var days = item.schedule.recur.days;
                // Should we run on specific days
                if (days && days.length > 0) {
                    // Itterate through each day and see if we have a match
                    var shouldPlay = false;
                    var currentDay = cdate.getDay();
                    var currentTime = (cdate.getHours() * 60) + cdate.getMinutes();
                    for (var i = 0; i < days.length; i++) {
                        var d = days[i];
                        // Is it same as current day or is every day specified
                        if (d.day == currentDay || d.day == -1) {
                            shouldPlay = true;
                            // Are specific timespans
                            if (d.timespans && d.timespans.length) {
                                shouldPlay = false;
                                // Check each timespan, if we find one we are valid in, should play is true
                                for (var k = 0; k < d.timespans.length; k++) {
                                    var span = d.timespans[k];
                                    var sTime = new Date(span.startTime);
                                    var eTime = new Date(span.endTime);
                                    sTime = (sTime.getHours() * 60) + sTime.getMinutes();
                                    eTime = (eTime.getHours() * 60) + eTime.getMinutes();
                                    // Cheat to catch situation where playlist is asked to play until 23:59 or 0:00
                                    // when it is intended to play until midnight
                                    if (eTime == 1439 || eTime == 0) {
                                        eTime = 1440; // set to midnight
                                    }
                                    if (currentTime >= sTime && currentTime < eTime) {
                                        shouldPlay = true;
                                        break;
                                    }
                                }
                            }
                            // If we passed the rule, just break
                            if (shouldPlay) {
                                break;
                            }
                        }
                    }
                    if (shouldPlay == false) {
                        return false;
                    }
                }
            }
            return true;
        }

        // Checks if the item is valid based on its schedule
        function isItemValid(item, specificDate, options) {
            if (!item || item.enabled === false) {
                return false;
            }

            // First check if the parent is valid
            if (item._parent) {
                if (!isItemValid(item._parent, specificDate)) {
                    return false;
                }
            }

            if (!item.schedule) {
                return true;
            }

            var cdate;
            if (specificDate) {
                cdate = specificDate;
            } else {
                cdate = new Date();
            }

            // Check do we have a frequency restriction
            if (item.schedule.frequency && options && options.checkFrequency) {
                if (!isFrequencyValid(item, cdate)) {
                    return false;
                }
            }
            // Check if the date is valid
            return isDateValid(item, cdate);
        }

        function clearRecordItem(itemIds) {
            if (itemIds) {
                for (var i = 0; i < itemIds.length; i++) {
                    var id = itemIds[i];
                    playoutRecord[id] = {
                        playCount: 0,
                        lastPlayTime: 0
                    };
                }
            } else {
                playoutRecord = {};
                exactPlayouts = {};
            }
        }

        return {
            // Public interface
            registerExactItems: registerExactItems,
            getNextItemWithExactTiming: getNextItemWithExactTiming,
            isItemValid: isItemValid,
            recordItemPlayout: recordItemPlayout,
            clearRecordItem: clearRecordItem
        };
    });
})(window.angular);

(function($angular) {

    "use strict";

    var app = $angular.module('ui');

    /**
     * @controller 
     * @type {Function}
     */
    app.controller('LinearPlaylistController', ['$scope', '$http', '$q', '$attrs', 'LinearPlaylist', function($scope, $http, $q, $attrs, LinearPlaylist) {

        var playlist = new LinearPlaylist();
        var getItemsPromise = null;
        $scope.currentItem = null;
        
        $scope.next = function() {
            $scope.currentItem = playlist.next();
            return $scope.currentItem;
        };
        
        $scope.previous = function() {
            $scope.currentItem = playlist.previous();
            return $scope.currentItem;
        };
        
        $scope.seek = function(index) {
            $scope.currentItem = playlist.seek(index);
            return $scope.currentItem;
        };
        
        $scope.setItems = function(items) {
            playlist.setItems(items);
            
            $scope.currentItem = null;
            if(playlist.random) {
                playlist.randomise();
            }
            if($scope.autoStart == true) {
                $scope.next();
            }
        };
        
        $scope.loadItemsFromUrl = function(url) {
            if(getItemsPromise != null) {
                getItemsPromise.abort();
            }
            var request = $http({
                method: "get",
                url: url 
            });
            
             // Intercept request resolve to load the items
            var promise = request.then(
                function handleResolve( response ) {
                    //console.log("loaded items from url");
                    if(response.data.items) {
                        $scope.setItems(response.data.items);     
                    }     
                    return( response.data );

                },
                function handleReject( response ) {
                    console.log("failed to load items from url");
                    return( $q.reject( response.status ) );
                }
            );
            // Wire in the underlying abort method.
            promise.abort = request.abort;
            promise.finally(
                function handleFinally() {
                    request = promise = null;
                }
            );
            return( promise );
        };
        
        if($attrs.itemsUrl) {
            $scope.loadItemsFromUrl($attrs.itemsUrl);
        }
        if($attrs.autoStart) {
            $scope.autoStart = ($attrs.autoStart === "true");
        }
        if($attrs.loop) {
            playlist.loop =  ($attrs.loop === "true");
        }
        if($attrs.random) {
            playlist.random =  ($attrs.random === "true");
        }
    }]);
})(window.angular);
(function ($angular) {

    "use strict";

    var app = $angular.module('ui');

    /**
     * @controller 
     * @type {Function}
     */
    app.controller('ScheduablePlaylistController', ['$scope', '$http', '$q', '$attrs', 'ScheduablePlaylist', 'NestedScheduablePlaylist', function ($scope, $http, $q, $attrs, ScheduablePlaylist, NestedScheduablePlaylist) {

        var playlist = new ScheduablePlaylist();
        // var nestedplaylist = new NestedScheduablePlaylist();
        var getItemsPromise = null;
        $scope.currentItem = null;

        function itemCompare(nestedItem, item) {
            if (item == null && nestedItem != null) {
                console.log(item, nestedItem);
                return;
            }
            else if (item != null && nestedItem == null) {
                console.log(item, nestedItem);
                return;
            }
            else if (item == null && nestedItem == null) {
                return;
            }
            else if (item.mediaUrl != nestedItem.mediaUrl) {
                console.log(item, nestedItem);
                return;
            }
            //console.log(item, nestedItem);
            //console.log("Match");
        }

        $scope.next = function () {
            //playlist.interleave = true;
            var item = playlist.next();
            $scope.currentItem = item;
            //var ni = nestedplaylist.next();
            //itemCompare(ni, $scope.currentItem);
            return $scope.currentItem;
        };

        $scope.previous = function () {
            $scope.currentItem = playlist.previous();
            //var ni = nestedplaylist.previous();
            //itemCompare(ni, $scope.currentItem);
            return $scope.currentItem;
        };

        $scope.seek = function (index) {
            $scope.currentItem = playlist.seek(index);
            //var ni = nestedplaylist.seek(index);
            //itemCompare(ni, $scope.currentItem);
            return $scope.currentItem;
        };

        $scope.setItems = function (items) {
            playlist.setItems(items);
            // nestedplaylist.setItems(items);

            $scope.currentItem = null;
            if (playlist.random) {
                playlist.randomise();
                // nestedplaylist.randomise(items);
            }
            if ($scope.autoStart == true) {
                $scope.next();
            }
        };

        $scope.loadItemsFromUrl = function (url) {
            if (getItemsPromise != null) {
                getItemsPromise.abort();
            }
            var request = $http({
                method: "get",
                url: url
            });

            // Intercept request resolve to load the items
            var promise = request.then(
                function handleResolve(response) {
                    //console.log("loaded items from url");
                    if (response.data.items) {
                        $scope.setItems(response.data.items);
                    }
                    return (response.data);

                },
                function handleReject(response) {
                    console.log("failed to load items from url");
                    return ($q.reject(response.status));
                }
            );
            // Wire in the underlying abort method.
            promise.abort = request.abort;
            promise.finally(
                function handleFinally() {
                    request = promise = null;
                }
            );
            return (promise);
        };

        if ($attrs.itemsUrl) {
            $scope.loadItemsFromUrl($attrs.itemsUrl);
        }
        if ($attrs.autoStart) {
            $scope.autoStart = ($attrs.autoStart === "true");
        }
        if ($attrs.loop) {
            playlist.loop = ($attrs.loop === "true");
            // nestedplaylist.loop = playlist.loop;
        }
        if ($attrs.random) {
            playlist.random = ($attrs.random === "true");
            // nestedplaylist.random = playlist.random;
        }
    }]);
})(window.angular);
(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('ui');
    app.factory('CpCssHistory', function() {
        function CpCssHistory() {
            this.cssChanges = [];
        }

        CpCssHistory.prototype.applyCss = function(element, css) {
            var cssState = null;
            for (var i = 0; i < this.cssChanges.length; i++) {
                if (this.cssChanges[i].element !== element) {
                    continue;
                }

                cssState = this.cssChanges[i];
            }

            if (!cssState) {
                cssState = {
                    element: element,
                    css: {}
                };
                this.cssChanges.push(cssState);
            }

            // Go through each css key and add to the history collection
            for (var key in css) {
                // Check have we already stored the original value
                if (cssState.css[key]) {
                    continue;
                }

                // Capture original value
                cssState.css[key] = element.css(key);
            }

            // Apply new css
            element.css(css);
        };

        // Restore original css to stage
        CpCssHistory.prototype.restoreCss = function() {
            for (var i = 0; i < this.cssChanges.length; i++) {
                var cssState = this.cssChanges[i];
                cssState.element.css(cssState.css);
            }
        };

        return (CpCssHistory);
    });
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('ui');
    app.factory('CpCssTransition', ["$q", "$log", "CpCssHistory", "StopWatch", function($q, $log, CpCssHistory, StopWatch) {
        var DEFAULT_TRANSITION = ' animated';
        var CONTENT_ANIMATION_CLASS = ' cp-item-animation';
        function CpCssTransition(config) {
            this.config = config;
            this.activeDefer = null;
            this.cssHistory = null;
        }

        function validateConfig(config) {
            // Check we have a config, stage and scope
            // AND we have atleast one element to transition
            // AND we have atleast one transition defined
            if ((!config || !config.stage || !config.$scope) ||
                (!config.exitElement && !config.enterElement) ||
                (!config.enterTransition && !config.exitTransition)) {
                return false;
            }

            // Convert elements to jQuery elements
            config.stage = $(config.stage);
            config.enterElement = $(config.enterElement);
            config.exitElement = $(config.exitElement);
            return true;
        }

        CpCssTransition.prototype.applyElementTransitionCss = function(element, transitionDuration, transitionClass, isAnimatingIn) {
            if (!element) {
                return;
            }

            this.cssHistory.applyCss(element, {
                'z-index': isAnimatingIn ? '1' : '0',
                '-webkit-animation-duration': transitionDuration + 's',
                'animation-duration': transitionDuration + 's',
            });
            element.addClass(transitionClass + DEFAULT_TRANSITION + CONTENT_ANIMATION_CLASS);

            if (!isAnimatingIn) {
                return;
            }

            element.removeClass('cp-item-loading');
        };

        // Based on transition requirements, apply custom stage css
        CpCssTransition.prototype.prepareCss = function() {
            this.cssHistory.applyCss(this.config.stage, {
                overflow: 'hidden'
            });

            this.applyElementTransitionCss(this.config.enterElement, this.config.enterDuration, this.config.enterTransition, true);
            this.applyElementTransitionCss(this.config.exitElement, this.config.exitDuration, this.config.exitTransition);
        };

        // Restore original css to stage
        CpCssTransition.prototype.restoreCss = function() {
            this.cssHistory.restoreCss();
            this.removeTransitionClass(this.config.enterElement, this.config.enterTransition);
            this.removeTransitionClass(this.config.exitElement, this.config.exitTransition);
        };

        CpCssTransition.prototype.removeTransitionClass = function(element, transitionClass) {
            if (!element) {
                return;
            }

            element.removeClass(transitionClass + DEFAULT_TRANSITION +
                (this.config.persistAnimationClass ? '' : CONTENT_ANIMATION_CLASS));
        };

        CpCssTransition.prototype.complete = function() {
            if (!this.activeDefer) {
                $log.warn('Trying to complete a transition that is not iniatilized');
                return;
            }

            // Restore original css
            this.restoreCss();
            // Notify consumer the transition has compelte
            this.activeDefer.resolve();
            // Reset transition values
            this.config = null;
            this.activeDefer = null;
            this.cssHistory = null;
            // Clear the top watch
            this.transitionStopWatch.reset();
            this.transitionStopWatch.stop();
            this.transitionStopWatch.clearTriggers();
            this.transitionStopWatch = null;
        };

        CpCssTransition.prototype.start = function() {
            // If a transition is already running, return existing promise
            if (this.activeDefer) {
                return this.activeDefer.promise;
            }

            this.activeDefer = $q.defer();
            // Validate config
            if (!validateConfig(this.config)) {
                this.activeDefer.reject();
                $log.warn('Trying to start a transition that has an invalid config', this.config);
                return this.activeDefer.promise;
            }

            if (this.config.initialZIndex) {
                this.config.enterElement.css({
                    'z-index': this.config.initialZIndex
                });
            }

            // create the transition promises
            this.transitionStopWatch = new StopWatch();
            this.cssHistory = new CpCssHistory();

            // prepareStage
            this.prepareCss();

            // Set stop watch to the longest duration
            var duration = this.config.enterDuration > this.config.exitDuration ? this.config.enterDuration : this.config.exitDuration;
            if (duration > 0) {
                this.transitionStopWatch = new StopWatch({supportAnimationFrame: this.config.supportAnimationFrame});
                this.transitionStopWatch.addTrigger({
                    callback: this.complete.bind(this),
                    scope: this.config.$scope,
                    elapse: duration * 1000
                });
                this.transitionStopWatch.start();
            } else {
                this.config.$scope.$evalAsync(this.complete.bind(this));
            }

            return this.activeDefer.promise;
        };

        return (CpCssTransition);
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('ui');
    app.factory('CpCubeTransition', ["$q", "$log", "CpCssHistory", "StopWatch", function($q, $log, CpCssHistory, StopWatch) {
        function CpCubeTransition(config) {
            this.config = config;
            this.activeDefer = null,
            this.cssHistory = null;
        }

        var ROTATE_LEFT = 'rotateY(-90deg)';
        var ROTATE_RIGHT = 'rotateY(90deg)';
        var ROTATE_UP = 'rotateX(-90deg)';
        var ROTATE_DOWN = 'rotateX(90deg)';
        var CUBE_LEFT = 'cube-left';
        var CUBE_RIGHT = 'cube-right';
        var CUBE_UP = 'cube-up';
        var CUBE_DOWN = 'cube-down';
        var CUBE_RANDOM = 'cube-random';
        var TRANSITIONS = [CUBE_LEFT, CUBE_RIGHT, CUBE_UP, CUBE_DOWN];
        var DEFAULT_EXIT_FACE_ROTATE = 'rotateY(0deg)';
        var MAXIMUM_BRIGHTNESS = '100%';
        var NO_BRIGHTNESS = '0%';
        // Define the instance methods and properties
        CpCubeTransition.prototype = {
            zoomDuration: .4,
            zoomAmount: .85,
            translateAmount: 0,
            cubeRotate: ROTATE_UP,
            faceRotate: ROTATE_DOWN
        };

        function validateConfig(config) {
            // Check we have a config, stage and scope
            // AND Check we have atleast one element to transition
            // AND Check we have atleast one transition defined
            if ((!config || !config.stage || !config.$scope) ||
                (!config.exitElement && !config.enterElement) ||
                (!config.enterTransition && !config.exitTransition)) {
                return false;
            }

            // Convert elements to jQuery elements
            config.stage = $(config.stage);
            config.enterTransition = config.enterTransition ? config.enterTransition : '';
            config.exitTransition = config.enterTransition ? config.exitTransition : '';
            config.enterElement = $(config.enterElement);
            config.exitElement = $(config.exitElement);

            // If random, select a transition randomly
            if (config.enterTransition == CUBE_RANDOM) {
                config.enterTransition = TRANSITIONS[Math.floor((Math.random() * TRANSITIONS.length))];
            }

            return true;
        }

        function getCubeTransitionCss(faceRotate, zTranslateAmount, brightnessLevel) {
            return {
                'position': 'absolute',
                'z-index': '1',
                'top': '0px',
                'left': '0px',
                'width': '100%',
                'height': '100%',
                'filter': 'brightness(' + brightnessLevel + ')',
                'transform': faceRotate + ' translateZ(' + zTranslateAmount + 'px)'
            };
        }

        function getBrightnessTransitionCss(brightnessLevel, brightnessDuration) {
            return {
                'filter': 'brightness(' + brightnessLevel + ')',
                'transition': 'filter ' + brightnessDuration + 's linear',
                'transition-delay': '.5s'
            };
        }

        function createStopWatch(config) {
            return new StopWatch({supportAnimationFrame: config.supportAnimationFrame});
        }

        CpCubeTransition.prototype.applyElementTransitionCss = function(element, faceRotate, invertBrightnessValues, brightnessDuration, removeLoadingClass) {
            if (!element) {
                return;
            }

            var originalBrightnessValue = NO_BRIGHTNESS;
            var targetBrightnessValue = MAXIMUM_BRIGHTNESS;

            if (invertBrightnessValues) {
                originalBrightnessValue = MAXIMUM_BRIGHTNESS;
                targetBrightnessValue = NO_BRIGHTNESS;
            }

            this.cssHistory.applyCss(element,
                getCubeTransitionCss(faceRotate, this.translateAmount, originalBrightnessValue));
            // Apply a simple brightness effect to look like lighting
            this.cssHistory.applyCss(element,
                getBrightnessTransitionCss(targetBrightnessValue, brightnessDuration));

            if (!removeLoadingClass) {
                return;
            }

            element.removeClass('cp-item-loading');
        };

        // Based on transition requirements, apply custom stage css
        CpCubeTransition.prototype.prepareCss = function() {
            var duration = this.config.enterDuration > this.config.exitDuration ? this.config.enterDuration : this.config.exitDuration;
            var getWidthCenter = function() {
                return this.config.stage.width() / 2;
            }.bind(this);

            var getHeightCenter = function() {
                return this.config.stage.height() / 2;
            }.bind(this);

            // Setup rotation and translate
            switch (this.config.enterTransition) {
                case CUBE_RIGHT:
                    this.translateAmount = getWidthCenter();
                    this.cubeRotate = ROTATE_LEFT;
                    this.faceRotate = ROTATE_RIGHT;
                    break;
                case CUBE_UP:
                    this.translateAmount = getHeightCenter();
                    this.cubeRotate = ROTATE_DOWN;
                    this.faceRotate = ROTATE_UP;
                    break;
                case CUBE_DOWN:
                    this.translateAmount = getHeightCenter();
                    this.cubeRotate = ROTATE_UP;
                    this.faceRotate = ROTATE_DOWN;
                    break;
                case CUBE_LEFT:
                default:
                    this.cubeRotate = ROTATE_RIGHT;
                    this.faceRotate = ROTATE_LEFT;
                    this.translateAmount = getWidthCenter();
                    break;
            }

            this.cssHistory.applyCss(this.config.stage, {
                'position': 'relative',
                'transform-style': 'preserve-3d',
                'transform': 'perspective(100vw) translateZ(-' + this.translateAmount + 'px)'
            });

            this.applyElementTransitionCss(this.config.enterElement, this.faceRotate, false, duration, true);
            this.applyElementTransitionCss(this.config.exitElement, DEFAULT_EXIT_FACE_ROTATE, true, duration);
        };

        // Restore original css to stage
        CpCubeTransition.prototype.restoreCss = function() {
            this.cssHistory.restoreCss();
        };

        CpCubeTransition.prototype.getZoom = function() {
            return this.config.isZoomSupported ? ' scale(' + this.zoomAmount + ')' : '';
        };

        CpCubeTransition.prototype.zoomOut = function() {
            this.cssHistory.applyCss(this.config.stage, {
                'transition-delay': '.1s',
                'transition': 'transform ' + this.zoomDuration + 's ease-in',
                'transform': 'perspective(100vw) translateZ(-' + this.translateAmount + 'px)' + this.getZoom()
            });
            this.transitionStopWatch.addTrigger({
                callback: this.beginRotate.bind(this),
                scope: this.config.$scope,
                elapse: (this.zoomDuration * 1000) + 100
            });
            this.transitionStopWatch.start();
        };

        CpCubeTransition.prototype.beginRotate = function() {
            var duration = this.config.enterDuration > this.config.exitDuration ? this.config.enterDuration : this.config.exitDuration;
            this.cssHistory.applyCss(this.config.stage, {
                'transition-delay': '0s',
                'transition': 'transform ' + duration + 's linear',
                'transform': 'perspective(100vw) translateZ(-' + this.translateAmount + 'px) ' + this.cubeRotate + this.getZoom()
            });
            this.transitionStopWatch.clearTriggers();
            this.transitionStopWatch.reset();
            this.transitionStopWatch.addTrigger({
                callback: this.zoomIn.bind(this),
                scope: this.config.$scope,
                elapse: duration * 1000
            });
        };

        CpCubeTransition.prototype.zoomIn = function() {
            this.cssHistory.applyCss(this.config.stage, {
                'transition': 'transform ' + this.zoomDuration + 's ease-out',
                'transform': 'perspective(100vw) translateZ(-' + this.translateAmount + 'px) ' + this.cubeRotate
            });
            this.transitionStopWatch.clearTriggers();
            this.transitionStopWatch.reset();
            this.transitionStopWatch.addTrigger({
                callback: this.complete.bind(this),
                scope: this.config.$scope,
                elapse: this.zoomDuration * 1000
            });
        };

        CpCubeTransition.prototype.complete = function() {
            if (!this.activeDefer) {
                $log.warn('Trying to complete a transition that is not running');
                return;
            }

            // Restore original css
            this.restoreCss();
            // Notify consumer the transition has compelte
            this.activeDefer.resolve();
            // Reset transition values
            this.config = null;
            this.activeDefer = null;
            this.cssHistory = null;
            // Clear the top watch
            this.transitionStopWatch.reset();
            this.transitionStopWatch.stop();
            this.transitionStopWatch.clearTriggers();
            this.transitionStopWatch = null;
        };

        CpCubeTransition.prototype.start = function() {
            // If a transition is already running, return existing promise
            if (this.activeDefer) {
                return this.activeDefer.promise;
            }

            this.activeDefer = $q.defer();
            // Validate config
            if (!validateConfig(this.config)) {
                this.activeDefer.reject();
                $log.warn('Trying to start a transition that has an invalid config', this.config);
                return this.activeDefer.promise;
            }

            // create the transition promises
            this.transitionStopWatch = createStopWatch(this.config);
            this.cssHistory = new CpCssHistory();

            // prepareStage
            this.prepareCss();

            // Set stop watch to the longest duration
            var duration = this.config.enterDuration > this.config.exitDuration ? this.config.enterDuration : this.config.exitDuration;
            if (duration > 0) {
                this.transitionStopWatch = createStopWatch(this.config);
                this.zoomOut();
            } else {
                this.config.$scope.$evalAsync(this.complete.bind(this));
            }

            return this.activeDefer.promise;
        };

        return (CpCubeTransition);
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;

    angular.module('ui').factory('cpItemHelper', ["logService", "dataUtil", "CONTENT_TYPE", "StopWatch", function(logService, dataUtil, CONTENT_TYPE, StopWatch) {
            // Remove item from dom
            var removeItem = function(item) {
                if (!item) {
                    return;
                }

                logService.debug('appspace.core.ui', 'unloading ' + item.content.mediaUrl);
                if (item.api) {
                    if (item.api.unload) {
                        item.api.unload();
                    }

                    if (item.api.destroy) {
                        item.api.destroy();
                    }

                    item.api = null;
                }

                if (item.scope) {
                    item.scope.$destroy();
                    item.scope = null;
                }

                resetStopWatch(item.stopWatch);
                resetStopWatch(item.loadByStopWatch);

                // Remove the item from the DOM
                if (item.element) {
                    if (item.element.empty) {
                        item.element.empty();
                    }

                    item.element.remove();
                    item.element = null;
                }

                logService.debug('appspace.core.ui', 'unloaded ' + item.content.mediaUrl);
            };

            var preRemoveItem = function(item) {
                if (!item || !item.api || !item.api.preUnload) {
                    return;
                }

                item.api.preUnload();
                logService.debug('appspace.core.ui', 'pre-unloaded ' + item.content.mediaUrl);
            };

            // Set volume of the item
            var setItemVolume = function(item, volume) {
                if (!item || !item.api || !item.api.setVolume) {
                    return;
                }

                item.api.setVolume(volume);
            };

            // Set mute for the item
            var setItemMute = function(item, shouldMute, isTemporary) {
                if (!item || !item.api || !item.api.mute) {
                    return;
                }

                item.api.mute(shouldMute, isTemporary);
            };

            var addTriggerToStopWatch = function(item) {
                var customDuration;
                var itemContent = item.content || {};
                if (itemContent.endByTime) {
                    resetStopWatch(item.stopWatch);
                    customDuration = itemContent.endByTime - new Date().getTime();
                }

                var elapseDuration = customDuration;
                if (!elapseDuration) {
                    var contentDuration = dataUtil.isNullOrUndefined(itemContent.duration) ? item.duration : itemContent.duration;
                    // don't do anything if no duration is set
                    if (!contentDuration) {
                        return;
                    }

                    elapseDuration = contentDuration * 1000;
                }

                item.stopWatch.addTrigger({
                    callback: item.onComplete,
                    item: item,
                    scope: item.scope,
                    elapse: elapseDuration
                });
            };

            var restart = function(item) {
                if (!item) {
                    return;
                }

                if (item.stopWatch) {
                    resetStopWatch(item.stopWatch);
                } else {
                    item.stopWatch = createStopWatch(item);
                }

                addTriggerToStopWatch(item);
            };

            // Play the item
            var playItem = function(item) {
                if (!item) {
                    return;
                }

                // Update the start time
                item.startTime = new Date().getTime();
                if (item.api && item.api.play) {
                    item.api.play();
                }

                // if previously we don't have stop watch, create a new trigger for it
                if (!item.stopWatch) {
                    item.stopWatch = createStopWatch(item);
                    addTriggerToStopWatch(item);
                }

                // If we have an end by time, we need to recreate our triggers
                if (item.content && item.content.endByTime) {
                    addTriggerToStopWatch(item);
                }

                item.stopWatch.start();
            };

            // Pause the item
            var pauseItem = function(item) {
                if (!item) {
                    return;
                }

                if (item.stopWatch) {
                    item.stopWatch.stop();
                }

                // If item doesn't support pause, ignore
                if (!item.api || !item.api.pause) {
                    return;
                }

                // Call pause on item
                item.api.pause();
            };

            // Play the item
            var seekItem = function(item, currentTime) {
                if (!item) {
                    return;
                }

                // set seek
                if (item.api && item.api.seek) {
                    item.api.seek(currentTime);
                }
            };

            var setSource = function(item, source) {
                if (!item) {
                    return;
                }

                // set source
                if (item.api && item.api.setSource) {
                    item.api.setSource(source);
                }
            };

            var setStretch = function(item, stretch) {
                if (!item) {
                    return;
                }

                // set stretch
                if (item.api && item.api.setStretch) {
                    item.api.setStretch(stretch);
                }
            };

            var canReuse = function(item, newItem) {
                // Check do we have an item, and does it support allow reuse check
                if (!item || !item.api || !item.api.allowReuse || !newItem || !newItem.content) {
                    return false;
                }

                // New content must be video
                if (newItem.content.mediaType != CONTENT_TYPE.VIDEO) {
                    return false;
                }

                // we do not allow for content directive audio
                if (item.api.mediaDirective === CONTENT_TYPE.AUDIO) {
                    return false;
                }

                // does it allow reuse?
                return item.api.allowReuse(newItem);
            };

            var reuse = function(item, newItem) {
                // Check do we have an item, and does it support allow reuse check
                if (!canReuse(item, newItem)) {
                    return false;
                }

                item.duration = newItem.duration;
                item.startTime = newItem.startTime;
                item.wasComplete = false;
                item.content = newItem.content;
                resetStopWatch(item.stopWatch);
                resetStopWatch(item.loadByStopWatch);
                setSource(item, newItem.content.mediaUrl);
                setStretch(item, newItem.content.stretch);
                addTriggerToStopWatch(item);
                return true;
            };

            function resetStopWatch(stopWatch) {
                if (!stopWatch) {
                    return;
                }

                stopWatch.reset();
                stopWatch.stop();
                stopWatch.clearTriggers();
            }

            function isContainedVideoOrAudio(item) {
                return item && item.content && (
                    item.content.mediaType === CONTENT_TYPE.VIDEO ||
                    item.content.mediaType === CONTENT_TYPE.AUDIO ||
                    (item.runtimeConfiguration && (
                    item.runtimeConfiguration.containsVideo ||
                    item.runtimeConfiguration.containsAudio)));
            }

            function supportSwapWithVideo(newItem, existingItem) {
                return Boolean(existingItem && existingItem.supportSwapWithVideo &&
                    newItem && newItem.supportSwapWithVideo);
            }

            function isReuseSupported(newItem, existingItem) {
                return Boolean(existingItem && existingItem.isReuseSupported &&
                    newItem && newItem.isReuseSupported);
            }

            function createStopWatch(item) {
                return new StopWatch({supportAnimationFrame: item.supportAnimationFrame});
            }

            function preloadContentItem(item, source) {
                if (!item || !item.api || !item.api.preloadContentItem) {
                    return;
                }

                item.api.preloadContentItem(source);
            }

            return {
                canReuse: canReuse,
                reuse: reuse,
                restart: restart,
                playItem: playItem,
                pauseItem: pauseItem,
                setSource: setSource,
                setStretch: setStretch,
                seekItem: seekItem,
                preRemoveItem: preRemoveItem,
                removeItem: removeItem,
                setItemVolume: setItemVolume,
                setItemMute: setItemMute,
                isContainedVideoOrAudio: isContainedVideoOrAudio,
                supportSwapWithVideo: supportSwapWithVideo,
                isReuseSupported: isReuseSupported,
                preloadContentItem: preloadContentItem,
            };
        }]
    );
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('ui');
    app.factory('CpNullTransition', ["$q", "$log", function($q, $log) {
        function CpNullTransition(config) {
            this.config = config;
            this.activeDefer = null;
        }

        function validateConfig(config) {
            // Check we have a config, stage and scope
            // AND we have atleast one element to transition
            if ((!config || !config.stage || !config.$scope) ||
                (!config.exitElement && !config.enterElement)) {
                return false;
            }

            // Convert elements to jQuery elements
            config.stage = $(config.stage);
            config.enterElement = $(config.enterElement);
            config.exitElement = $(config.exitElement);
            return true;
        }

        CpNullTransition.prototype.complete = function() {
            if (!this.activeDefer) {
                $log.warn('Trying to complete a transition that is not iniatilized');
                return;
            }

            // Notify consumer the transition has compelte
            this.activeDefer.resolve();
            // Reset transition values
            this.config = null;
            this.activeDefer = null;
        };

        CpNullTransition.prototype.start = function() {
            // If a transition is already running, return existing promise
            if (this.activeDefer) {
                return this.activeDefer.promise;
            }

            this.activeDefer = $q.defer();
            // Validate config
            if (!validateConfig(this.config)) {
                this.activeDefer.reject();
                $log.warn('Trying to start a transition that has an invalid config', this.config);
                return this.activeDefer.promise;
            }

            if (this.config.initialAnimationPosition) {
                this.config.enterElement.css({
                    position: this.config.initialAnimationPosition
                });
            }

            this.config.enterElement.removeClass('cp-item-loading');
            this.config.$scope.$evalAsync(this.complete.bind(this));
            return this.activeDefer.promise;
        };

        return (CpNullTransition);
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;

    angular.module('ui').factory(
        'cpTemplateHelper',
        ["halPlatformService", "halMediaService", "CONTENT_TYPE", "MEDIA_DIRECTIVE", function(halPlatformService, halMediaService, CONTENT_TYPE, MEDIA_DIRECTIVE) {
            var DEFAULT_TEMPLATE_CSS_CLASS = 'cp-item-loading cp-item-container';
            var createTemplate = function(item, currentItem) {
                var options = '';
                if (item.content.options) {
                    halMediaService.processContentDirectiveOptions(item.content);
                    for (var prop in item.content.options) {
                        options += (prop.toLowerCase() + '="' + item.content.options[prop] + '" ');
                    }
                }

                switch (item.content.mediaType) {
                    case CONTENT_TYPE.IMAGE:
                        return createImageTemplate(item, options);
                    case CONTENT_TYPE.VIDEO:
                        return createVideoTemplate(item, options, null, currentItem);
                    case CONTENT_TYPE.AUDIO:
                        return createVideoTemplate(item, options, 'isaudio="true" ');
                    case CONTENT_TYPE.TEXT:
                        return createTextTemplate(item, options);
                    case CONTENT_TYPE.CARD:
                        return createCardTemplate(item, options);
                    case CONTENT_TYPE.WEB_SHORTEN:
                    case CONTENT_TYPE.WEB:
                        return createWebTemplate(item, options);
                    case CONTENT_TYPE.SIGNS:
                        var listenToCompleteEvent = item && item.content && item.content.options && item.content.options.listenToCompleteEvent;
                        var listenToPlaybackErrorEvent = item && item.content && item.content.options && item.content.options.listenToPlaybackErrorEvent;
                        return createSigns(item, options, listenToCompleteEvent, listenToPlaybackErrorEvent);
                    case CONTENT_TYPE.HTLM_SHORTEN:
                    case CONTENT_TYPE.HTML:
                        return createHTMLCodeTemplate(item, options);
                    case CONTENT_TYPE.FLASH:
                        return item.content.options && item.content.options.forceNativeFlashPlayback ?
                            createFlashTemplate(item, options) :
                            createWebTemplate(item, options);
                    case CONTENT_TYPE.LOW_BANDWIDTH_THUMBNAIL:
                        return createLowBandwidthThumbnailTemplate(item, options);
                    default:
                        return null;
                }
            };

            var createVideoTemplate = function(item, options, extraAttribute, currentItem) {
                currentItem = currentItem || {};
                currentItem.content = currentItem.content || {};
                var videoOptions = {
                    previousContentType: currentItem.content.mediaType,
                    mediaUrl: item.content.mediaUrl
                };
                var mediaDirective = halMediaService.getMediaDirective('as-media-video', videoOptions);
                mediaDirective.options = mediaDirective.options || {};
                var template = '<div ' + mediaDirective.directive + ' ' + mediaDirective.attribute + ' class="' + DEFAULT_TEMPLATE_CSS_CLASS + '" source="' + item.content.mediaUrl + '" stretch="' + item.content.stretch + '"';
                template += ' on-api-ready="onItemApiReady(api, ' + item.instanceId + ')" on-video-load="onMediaLoad(metadata,' + item.instanceId + ')" ';
                template += 'on-video-load-error="onMediaLoadError(' + item.instanceId + ')" ';
                template += 'on-video-playback-error="onMediaPlaybackError(' + item.instanceId + ')" ';
                template += 'on-video-complete="onMediaCompleteEvent(' + item.instanceId + ')" ';
                template += 'on-playback-progress="onPlaybackProgress(' + item.instanceId + ', progress)" ';
                if (extraAttribute) {
                    template += extraAttribute;
                }

                var mediaAttributes = mediaDirective.attributes;
                if (mediaAttributes) {
                    template += mediaAttributes;
                }

                // if device doesn't support decode on pause & content presenter is not paused, set autoplay
                if (halPlatformService.getFeatureSupport('media.video.decodeonpause.supported') == 'no') {
                    template += 'autoplay ';
                }

                template += ' ' + options + '></div>';
                return {
                    template: template,
                    supportOnLoad: true,
                    isReuseSupported: mediaDirective.options.isReuseSupported
                };
            };

            var createImageTemplate = function(item, options) {
                var template = '<div as-media-image class="' + DEFAULT_TEMPLATE_CSS_CLASS + '" url="' + item.content.mediaUrl + '" stretch="' + item.content.stretch + '"' +
                    ' on-api-ready="onItemApiReady(api, ' + item.instanceId + ')" on-image-load="onMediaLoad(metadata,' + item.instanceId + ')" on-image-load-error="onMediaLoadError(' + item.instanceId + ', message)" ' + options + '></div>';

                return {
                    template: template,
                    supportOnLoad: true
                };
            };

            var createCardTemplate = function(item, options) {
                var mediaDirective = halMediaService.getMediaDirective(MEDIA_DIRECTIVE.CARD);
                mediaDirective.options = mediaDirective.options || {};
                var template = '<div class="' + DEFAULT_TEMPLATE_CSS_CLASS + '"> ';
                template += '<as-media-card style="width:100%; height:100%; position:absolute;" source="' + item.content.mediaUrl + '" ';
                template += 'content-id="' + item.content.contentId + '" ';
                template += 'content-numeric-id="' + item.content.id + '" ';
                template += 'on-api-ready="onItemApiReady(api, ' + item.instanceId + ')" on-load="onMediaLoad(metadata,' + item.instanceId + ')" ';
                template += 'on-load-error="onMediaLoadError(' + item.instanceId + ')" ';
                template += 'on-playback-error="onMediaPlaybackError(' + item.instanceId + ')" ';
                template += 'on-metadata-loaded="onMetadataLoaded(metadata, ' + item.instanceId + ')" ';

                if (!item.content.duration) {
                    template += 'on-complete="onMediaCompleteEvent(' + item.instanceId + ')" ';
                }

                if (item.content.originalMediaUrl) {
                    template += 'original-media-url="' + item.content.originalMediaUrl + '" ';
                }

                template += 'on-action-received="onActionReceived(action,' + item.instanceId + ')" ';
                template += (options + '></as-media-card>');
                template += '</div>';

                return {
                    template: template,
                    supportOnLoad: true,
                    supportSwapWithVideo: mediaDirective.options.supportSwapWithVideo
                };
            };

            var createTextTemplate = function(item, options) {
                var template = '<div class="' + DEFAULT_TEMPLATE_CSS_CLASS + '"> ' +
                    '<span style="' + item.content.style + ' height:100%; position:absolute;" class="' + item.content.class + '">' + item.content.data + '</span>' +
                    '</div>';

                return {
                    template: template,
                    supportOnLoad: false
                };
            };

            var createWebTemplate = function(item, options) {
                var mediaDirective = halMediaService.getMediaDirective('as-media-web', item.content.options);
                var template = '<div class="' + DEFAULT_TEMPLATE_CSS_CLASS + '"> ' +
                    '<' + mediaDirective.directive + ' style="width:100%; height:100%; position:absolute;" url="' + item.content.mediaUrl + '" scroll="' + item.content.scroll + '"' +
                    ' on-api-ready="onItemApiReady(api, ' + item.instanceId + ')" on-web-load="onMediaLoad(metadata,' + item.instanceId + ')" on-web-load-error="onMediaLoadError(' + item.instanceId + ')" ' + options + '></' + mediaDirective.directive + '>' +
                    '</div>';

                mediaDirective.options = mediaDirective.options || {};
                return {
                    supportAnimationFrame: mediaDirective.options.supportAnimationFrame,
                    supportSwapWithVideo: mediaDirective.options.supportSwapWithVideo,
                    delayLoadNextItem: mediaDirective.options.delayLoadNextItem,
                    template: template,
                    supportOnLoad: true
                };
            };

            var createSigns = function(item, options, listenToCompleteEvent, listenToPlaybackErrorEvent) {
                var template = '<div class="' + DEFAULT_TEMPLATE_CSS_CLASS + '">' +
                    '<as-media-web ' +
                    'style="width:100%; height:100%; position:absolute;" ' +
                    'url="' + item.content.mediaUrl + '" ' +
                    'scroll="' + item.content.scroll + '" ' +
                    'subject-id="' + item.content.contentId + '" ' +
                    'subject-type="Channel" ' +
                    'on-api-ready="onItemApiReady(api, ' + item.instanceId + ')" ' +
                    'on-web-load="onMediaLoad(metadata,' + item.instanceId + ')" ';
                if (listenToCompleteEvent) {
                    template += 'on-end-of-playlist="onMediaCompleteEvent(' + item.instanceId + ', true)" ';
                }

                if (listenToPlaybackErrorEvent) {
                    template += 'on-web-playback-error="onMediaPlaybackError(' + item.instanceId + ', error)" ';
                }

                template += 'on-web-load-error="onMediaLoadError(' + item.instanceId + ', error)" ' +
                    options + ' ></as-media-web>' +
                    '</div>';

                return {
                    template: template,
                    supportOnLoad: true
                };
            };

            var createHTMLCodeTemplate = function(item, options) {
                var template = '<div class="' + DEFAULT_TEMPLATE_CSS_CLASS + ' html-text-content"> ' +
                    '<as-media-html style="width:100%; height:100%; position:absolute;" url="' + item.content.mediaUrl + '"' +
                    ' on-api-ready="onItemApiReady(api, ' + item.instanceId + ')" on-web-load="onMediaLoad(metadata,' + item.instanceId + ')" on-web-load-error="onMediaLoadError(' + item.instanceId + ')" ' + options + '></as-media-html>' +
                    '</div>';

                return {
                    template: template,
                    supportOnLoad: true
                };
            };

            var createFlashTemplate = function(item, options) {
                var template = '<div class="' + DEFAULT_TEMPLATE_CSS_CLASS + '"> ' +
                    '<as-media-flash style="width:100%; height:100%; position:absolute;" url="' + item.content.mediaUrl + '"' +
                    ' on-api-ready="onItemApiReady(api, ' + item.instanceId + ')" on-flash-load="onMediaLoad(metadata,' + item.instanceId + ')" on-flash-load-error="onMediaLoadError(' + item.instanceId + ')" ' + options + '></as-media-flash>' +
                    '</div>';

                return {
                    template: template,
                    supportOnLoad: true
                };
            };

            var createLowBandwidthThumbnailTemplate = function(item, options) {
                return {
                    template: '<div as-media-low-bandwidth-thumbnail'
                        + ' class="' + DEFAULT_TEMPLATE_CSS_CLASS + '"'
                        + ' url="' + item.content.mediaUrl + '"'
                        + ' on-load="onMediaLoad(metadata,' + item.instanceId + ')"'
                        + ' on-error="onMediaLoadError(' + item.instanceId + ', message)"'
                        + ' on-api-ready="onItemApiReady(api,' + item.instanceId + ')"'
                        + ' ' + options
                        + '>'
                        + '</div>',
                    supportOnLoad: true
                };
            };

            return {
                createTemplate: createTemplate
            };
        }]
    );
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;

    angular.module('ui').factory(
        'cpTransitionHelper',
        ["CpCssTransition", "CpNullTransition", "CpCubeTransition", function(CpCssTransition, CpNullTransition, CpCubeTransition) {
            function createTransition(config) {
                var enterTransition = config.enterTransition ? config.enterTransition : '';
                var exitTransition = config.exitTransition ? config.exitTransition : '';

                if ((!enterTransition && !exitTransition) ||
                    (!config.enterDuration && !config.exitDuration)) {
                    return new CpNullTransition(config);
                }

                if (enterTransition.indexOf('cube-') === 0) {
                    return new CpCubeTransition(config);
                }

                return new CpCssTransition(config);
            }

            return {
                createTransition: createTransition
            };
        }]
    );
})(window.angular);

(function ($angular) {

    "use strict";

    var app = $angular.module('ui');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('asUiAutoScrollY', function () {

        return {
            restrict: "A",
            
             scope: {
                scrollTimeout: '@',
                scrollAnimation: '@'
            },

            link: function (scope, element, attrs) {
                
                if(scope.scrollTimeout == undefined) {
                    scope.scrollTimeout = "15";
                }
                if(scope.scrollAnimation == undefined) {
                    scope.scrollAnimation = "1";
                }
                scope.startTimer();
            },

            controller: ['$scope', '$element', '$interval', function ($scope, $element, $interval) {

                var timer = null;
                var expectedScroll = null;
                var userInteractTime = null;
                var defaultTimeout;
                var animationDuration;
                
                var scrollHeight = null;
                
                $scope.destroy = function() {
                    $interval.cancel(timer);
                }
                
                function scrollToBottom() {
                    
                    var container = $($element)[0];
                    var cPos = container.scrollTop;
                    var cHeight = container.scrollHeight;
                    
                    //Has the scroll height changed
                    if(scrollHeight == cHeight) {
                        //container height has not changed
                        return;
                    }
                    
                    //If user has interacted, has the timeout passed
                    if(userInteractTime != null) {
                        //Check has the user interacted again
                        if(expectedScroll != cPos) {
                            expectedScroll = cPos;
                            //Record time when user interacted
                            userInteractTime = new Date().getTime();
                            return;
                        }
                        var elapsed = new Date().getTime() - userInteractTime;
                        if(elapsed > defaultTimeout) {
                            //Re-enable auto scroll
                            userInteractTime = null;
                            expectedScroll = cPos;
                        }
                        else {
                            //Auto scroll disabled for now
                            return;
                        }
                    }
                    //Check has the user scrolled
                    else if(expectedScroll != null && expectedScroll != cPos) {
                        expectedScroll = cPos;
                        //Record time when user interacted
                        userInteractTime = new Date().getTime();
                        return;
                    }
                    
                    scrollHeight = cHeight; //Update the container height
                    expectedScroll = cHeight; //Update the container scroll Position
                    $(container).animate({scrollTop:cHeight},animationDuration, "swing");
                }
                
                $scope.startTimer = function() {
                    defaultTimeout = parseFloat($scope.scrollTimeout) * 1000;
                    animationDuration = parseFloat($scope.scrollAnimation) * 1000;
                    timer = $interval(scrollToBottom, 800);
                }
                
                
                 
                
               
            }]
        };

    });


})(window.angular);
(function ($angular) {

    "use strict";

    var app = $angular.module('ui');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('asUiCarousel', ["$compile", function ($compile) {

        return {
            restrict: "EA",
            scope: {
                items: '=',
                config: '=',
                autoHighlight: '@',
                itemsPerPage: '@',
                onApiReady: '&',
                onItemFocus: '&'
            },

            template: '<div style ="position: relative;" class="ui-carousel owl-carousel owl-theme"></div>',

            link: function (scope, element, attrs) {

                if(attrs.itemsPerPage) {
                    scope.itemsPerPage = attrs.itemsPerPage;
                }

                if(!attrs.autoHighlight) {
                    scope.autoHighlight = true;
                }

               // scope.initCarousel();

                scope.$on('$destroy', function() {
                    scope.destroy();
                });

                element.on('$destroy', function() {
                    //console.log("asUiCarousel element destroy");
                    scope.destroy();
                });

                scope.$watch('items', function(newVal) {
                    if(!newVal) {
                        return;
                    }
                    return scope.addItems(newVal);
                });

                scope.$watch('config', function(newVal) {
                    if(!newVal) {
                        return;
                    }
                    return scope.setConfig(newVal);
                });

                //Construct the api interface
                scope.api = {
                    addItem: scope.addItem,
                    addItems: scope.addItems,
                    nextItem: scope.nextItem,
                    previousItem: scope.previousItem,
                    removeHighlight: scope.removeHighlight,
                    moveToItem: scope.moveToItem,
                    getItems: scope.getItems,
                    getCurrentItemOffset: scope.getCurrentItemOffset,
                    getCurrentItemColumnOffset: scope.getCurrentItemColumnOffset,
                    getItemsInView: scope.getItemsInView
                };

                if( scope.onApiReady) {
                    scope.onApiReady({api:scope.api});
                }
            },

            controller: ['$scope', '$element', '$q', '$interval',  function ($scope, $element, $q, $interval) {

                var carousel = $($element.children()[0]);
                var carouselItems = [];
                var currentPos = -1;
                var carouselInfo = {
                    itemsPerPage: 0,
                    pageIndex: 0,
                    pageCount: 0
                };

                var carouselConfig = {
                        loop:false,
                        margin:10,
                        nav:false,
                        items: $scope.itemsPerPage
                    };

                $scope.destroy = function() {
                    $element.empty();
                }

                $scope.initCarousel = function() {
                    //Initialise carousel with config
                    carousel.owlCarousel(carouselConfig);
                    //Register click event
                    $(carousel).unbind('click').click(onItemClick);
                    //Track changes
                    carousel.on('changed.owl.carousel', function(event) {
                        carouselInfo.pageSize = event.page.size;
                        carouselInfo.pageIndex = event.page.index;
                        carouselInfo.pageCount = event.page.count;
                        //console.log(event);
                    })
                }

                $scope.setConfig = function (config) {
                    carouselConfig = config;
                    //If an items per page is given, override the config
                    if($scope.itemsPerPage) {
                        carouselConfig.items = $scope.itemsPerPage
                    }
                    carousel.trigger('destroy.owl');
                    carousel.empty();
                    for(var i = 0; i < carouselItems.length; i++) {
                        //Add content item based on type
                        addContentItem(carouselItems[i]);
                    }
                    $scope.initCarousel();
                }

                function onItemClick(event) {
                    var element = event.toElement;
                    while(element) {
                        var classList = element.className.split(/\s+/);
                        for (var i = 0; i < classList.length; i++) {
                            var classParts = classList[i].toLowerCase().split('carousel_offset_');
                            if (classParts.length == 2) {
                                var offset = parseInt(classParts[1]);
                                $scope.removeHighlight(currentPos);
                                currentPos = offset;
                                $scope.updateHighlight(offset);
                                return;
                            }
                        }
                        element = element.parentElement;
                    }
                }

                function addImageItem(contentItem) {
                    var markup = "<div style='height:100%; background:url(\"" + contentItem.url + "\"); background-size: cover; background-position: center; background-repeat: no-repeat; '></div>"
                    var element = $(markup)
                    element.addClass( "carousel_offset_" + carousel.children().length);
                    carousel.append(element);
                }
                function addMarkupItem(contentItem) {
                    var element = $(contentItem.markup);
                    element.addClass( "carousel_offset_" + carousel.children().length);
                    carousel.append(element);
                }
                function addElementItem(contentItem) {
                    $(contentItem.element).addClass( "carousel_offset_" + carousel.children().length);
                    carousel.append($(contentItem.element));
                }

                function addContentItem(contentItem) {
                    switch(contentItem.type.toLowerCase()) {
                        case "image":
                        addImageItem(contentItem);
                        break;
                        case "markup":
                        addMarkupItem(contentItem);
                        break;
                        case "element":
                        addElementItem(contentItem);
                        break;
                    }
                }

                function getItemElements(offset) {
                    var elements = [];
                    var items = carousel.find( ".owl-item" );
                    var offsetClass = ".carousel_offset_" + offset;
                    for(var i = 0; i < items.length; i++) {
                        var item = items[i];
                        if($(item).find(offsetClass).length > 0) {
                            elements.push(items[i]);
                        }
                    }
                    return elements;
                }

                function getItemElement(offset) {
                    var items = carousel.find( ".owl-item" );
                    var offsetClass = ".carousel_offset_" + offset;
                    for(var i = 0; i < items.length; i++) {
                        var item = items[i];
                        if($(item).find(offsetClass).length > 0) {
                            //console.log(i);
                            return items[i];
                        }
                    }
                    return null;
                }

                $scope.removeHighlight = function(offset) {
                    if(offset == undefined) {
                        offset = currentPos;
                    }
                    var elements = getItemElements(offset);
                    for(var i = 0; i < elements.length; i++) {
                        $(elements[i]).removeClass("ui-carousel-active");
                    }
                }

                $scope.updateHighlight = function(offset) {
                    var elements = getItemElements(offset);
                    for(var i = 0; i < elements.length; i++) {
                        $(elements[i]).addClass("ui-carousel-active");
                    }
                    if($scope.onItemFocus) {
                        $scope.onItemFocus({api:$scope.api, offset:offset});
                    }
                }

                $scope.getItemsInView = function() {
                    var itemsInView = [];
                    //Determine which items are in view by looking for the active class
                    var items = carousel.find( ".owl-item.active" );
                    for(var i = 0; i < items.length; i++) {
                        var item = items[i];
                        var child = $(item).children(0);
                        var classList = $(child).attr("class").split(/\s+/);
                        for (var k = 0; k < classList.length; k++) {
                            var classParts = classList[k].toLowerCase().split('carousel_offset_');
                            if (classParts.length == 2) {
                                var itemOffset = parseInt(classParts[1]);
                                itemsInView.push(itemOffset);
                            }
                        }
                    }
                    return itemsInView;
                }

                function moveToPage(offset) {
                    if(!carouselInfo.pageSize) {
                        return;
                    }

                    var itemsInView = $scope.getItemsInView();
                    //Determine if the requested offset is in view
                    for(var i = 0; i < itemsInView.length; i++) {
                        var itemOffset = itemsInView[i];
                        if(offset == itemOffset) {
                            //No need to move, just return
                            return;
                        }
                    }

                    //Move to the item in view
                    var page = Math.floor(offset / carouselInfo.pageSize);
                    carousel.trigger('to.owl.carousel', [page]);
                }


                $scope.getItems = function() {
                    return carouselItems;
                }

                $scope.getCurrentItemOffset = function() {
                    return currentPos;
                }

                $scope.getCurrentItemColumnOffset = function() {
                    var itemsInView = $scope.getItemsInView();
                    //Determine if the requested offset is in view
                    for(var i = 0; i < itemsInView.length; i++) {
                        var itemOffset = itemsInView[i];
                        if(currentPos == itemOffset) {
                            //No need to move, just return
                            return i ;
                        }
                    }
                    return 0;
                }

                //Adds a single item to the carousel
                $scope.addItem =function (content) {
                    $scope.addItems([content]);
                }

                //Adds a collection of items to the carousel
                $scope.addItems =function (contentItems) {
                    carousel.trigger('destroy.owl');
                    carousel.empty();

                    //Add items to our internal collection
                    carouselItems = carouselItems.concat(contentItems);
                    for(var i = 0; i < carouselItems.length; i++) {
                        //Add content item based on type
                        addContentItem(carouselItems[i]);
                    }
                    $scope.initCarousel();
                }

                $scope.nextItem = function() {
                    var newPos = currentPos + 1;
                    if(currentPos >= carouselItems.length - 1) {
                        if(carouselConfig.loop == true) {
                            newPos = 0;
                        }
                        else {
                            return;
                        }
                    }
                    $scope.removeHighlight(currentPos);
                    currentPos = newPos;
                    if($scope.autoHighlight) {
                        $scope.updateHighlight(newPos);
                    }
                    moveToPage(newPos);
                }

                $scope.previousItem = function() {
                    var newPos = currentPos-1;
                    if(currentPos <= 0) {
                        if(carouselConfig.loop == true) {
                            newPos = carouselItems.length-1;
                        }
                        else {
                            return;
                        }
                    }
                    $scope.removeHighlight(currentPos);
                    currentPos = newPos;
                    if($scope.autoHighlight) {
                        $scope.updateHighlight(newPos);
                    }

                    moveToPage(newPos);
                }

                $scope.moveToItem = function(offset) {

                    $scope.removeHighlight(currentPos);
                    currentPos = offset;
                    if($scope.autoHighlight) {
                        $scope.updateHighlight(currentPos);
                    }
                    moveToPage(currentPos);
                }

            }]
        };

    }]);


})(window.angular);
(function ($angular) {

    "use strict";

    var app = $angular.module('ui');
    
    /**
     * @directive
     * @type {Function}
     */
    app.directive('asClick', ["$parse", "asClickService", function ($parse, asClickService) {

        return {
            link: function ($scope, $element, $attrs) {
                var mainCallback =  $parse($attrs.asClick);

                var callback = function(e){
                    $scope.$apply(function () {
                        mainCallback($scope, {
                            $event: e
                        });
                    });
                }

                asClickService.bind($element, callback);
            }
        };
    }]);
})(window.angular);
(function($angular) {
    'use strict';

    var app = $angular.module('ui');
    var angular = $angular;
    /**
     * @directive
     * @type {Function}
     */
    app.directive('asUiContentPresenter', ["$log", "$q", "cpTemplateHelper", "cpItemHelper", "cpTransitionHelper", "halPlatformService", "CONTENT_TYPE", "dataUtil", "lowBandwidthService", "halMediaService", function($log, $q, cpTemplateHelper, cpItemHelper, cpTransitionHelper,
        halPlatformService, CONTENT_TYPE, dataUtil, lowBandwidthService, halMediaService) {
        return {
            restrict: 'EA',
            scope: {
                defaultStretch: '@', // determines the default stretch for content based on its aspect ratio
                defaultDuration: '@', // default duration for content

                defaultTransitionDuration: '@',
                defaultEnterTransition: '@',
                defaultExitTransition: '@',
                mute: '@',
                volume: '@',

                contentItem: '=',

                onApiReady: '&',
                onItemReady: '&',
                onItemLoad: '&',
                onItemBeginTransition: '&',
                onItemLoadError: '&',
                onItemPlaybackError: '&',
                onItemComplete: '&',
                onItemPlaybackProgress: '&',

                customContainerStyle: '=?',

                onItemActionReceived: '&'
            },

            template: function(element, attrs) {
                var suppressPositionRelative = halPlatformService.getFeatureSupport('contentpresenter.suppress.position');
                var position = suppressPositionRelative === 'yes' ? '' : 'position: relative;';
                return '<div class="cp-stage" style="' + position + ';width:100%; height:100%;" ng-style="customStyle"></div>';
            },

            link: function(scope, element, attrs) {
                if (!attrs.defaultStretch) {
                    scope.defaultStretch = 'uniform';
                }

                if (!attrs.defaultDuration) {
                    scope.defaultDuration = '12';
                }

                if (!attrs.defaultEnterTransition) {
                    scope.defaultEnterTransition = 'fadeIn';
                }

                if (!attrs.defaultExitTransition) {
                    scope.defaultExitTransition = 'fadeOut';
                }

                if (!attrs.customContainerStyle) {
                    scope.customContainerStyle = {};
                }

                scope.setDefaultTransitionDuration(attrs.defaultTransitionDuration);

                scope.$on('$destroy', function() {
                    scope.destroy();
                });

                element.on('$destroy', function() {
                    scope.destroy();
                });

                scope.$watch('defaultTransitionDuration', function(newVal) {
                    if (!newVal) {
                        return;
                    }

                    scope.setDefaultTransitionDuration(newVal);
                });

                scope.$watch('defaultStretch', function(newVal) {
                    if (!newVal) {
                        return;
                    }

                    scope.setDefaultStretch(newVal);
                });

                scope.$watch('defaultDuration', function(newVal) {
                    if (!newVal) {
                        return;
                    }

                    return scope.setDefaultDuration(newVal);
                });

                scope.$watch('mute', function(newVal) {
                    if (!newVal) {
                        return;
                    }

                    return scope.setMute(
                        dataUtil.isValueTrue(newVal) ||
                        dataUtil.isValueBooleanTrue(newVal));
                });

                scope.$watch('volume', function(newVal) {
                    if (!newVal) {
                        return;
                    }

                    return scope.setVolume(newVal);
                });

                scope.$watch('contentItem', function(newVal) {
                    if (!newVal) {
                        if (scope.contentItem) {
                            scope.unload();
                        }

                        return;
                    }

                    return scope.loadContentItem(newVal);
                });

                scope.$watch('customContainerStyle', function(newVal) {
                    if (!newVal) {
                        return;
                    }

                    if (!scope.customStyle) {
                        scope.customStyle = {};
                    }

                    for (var key in newVal) {
                        scope.customStyle[key] = newVal[key];
                    }
                });

                // Construct the api interface
                scope.api = {
                    unload: scope.unload,
                    pause: scope.pause,
                    play: scope.play,
                    togglePause: scope.togglePause,
                    restart: scope.restart,
                    seek: scope.seek,
                    setVolume: scope.setVolume,
                    getVolume: scope.getVolume,
                    mute: scope.setMute,
                    setDefaultDuration: scope.setDefaultDuration,
                    setDefaultStretch: scope.setDefaultStretch,
                    setDefaultEnterTransition: scope.setDefaultEnterTransition,
                    setDefaultExitTransition: scope.setDefaultExitTransition,
                    setContentLoadTimeout: scope.setContentLoadTimeout,
                    setContentItem: scope.loadContentItem,
                    isLoading: scope.isLoading,
                    executeContentAction: scope.executeContentAction,
                    updateDeviceProperties: scope.updateDeviceProperties,
                    updateResourceManifest: scope.updateResourceManifest,
                    setStretch: scope.setStretch,
                    resetPause: scope.resetPause,
                    getCurrentItemRuntimeConfiguration: scope.getCurrentItemRuntimeConfiguration,
                    preloadContentItem: scope.preloadContentItem,
                    reload: scope.reload,
                    freeze: scope.freeze
                };

                if (scope.onApiReady) {
                    scope.onApiReady({
                        api: scope.api
                    });
                }
            },

            controller: ["$scope", "$element", "$timeout", "$compile", "FEATURE_CODE", "HttpWrapper", "CSVParser", "logService", "halPlatformService", "StopWatch", "cardUtil", function(
                $scope,
                $element,
                $timeout,
                $compile,
                FEATURE_CODE,
                HttpWrapper,
                CSVParser,
                logService,
                halPlatformService,
                StopWatch,
                cardUtil) {
                var getFeatureSupport = function(key) {
                    return halPlatformService.getFeatureSupport(FEATURE_CODE[key]);
                };

                var loadTimeoutTimer = null;
                var loadTimeoutDuration = getFeatureSupport('MEDIA_TIMEOUT_DURATION'); // 10000;
                var cardTimeoutDuration = cardUtil.getCardTimeoutDuration();
                var loadingItem = null;
                var activeTransition = null;
                var currentItem = null;
                var paused = false;
                var pendingItem = null;
                var instanceCount = 0;
                var audioLevel = 1; // 1 is full, 0 is silent
                var audioMute = false;
                var mediaCompleted = false;
                var reuseVideoLayer = halPlatformService.getFeatureSupport('media.reusevideolayer');
                var supportVideoTransition = halPlatformService.getFeatureSupport('media.video.transition.supported') === true;
                var isSingleVideoLayerOnly = halPlatformService.getFeatureSupport('media.maxvideolayers') === '1';
                var persistAnimationClass = halPlatformService.getFeatureSupport('media.animation.persistclass') === true;
                var initialAnimationPosition = halPlatformService.getFeatureSupport('media.animation.initialposition');
                var initialZIndex = halPlatformService.getFeatureSupport('media.animation.initialzindex');
                var isZoomSupported = halPlatformService.getFeatureSupport('media.animation.zoom.supported');
                var startLoadTime;
                var loadByStopWatch;

                $scope.defaultDuration = 12;
                $scope.defaultStretch = 'fill';

                $scope.destroy = function() {
                    logService.debug('appspace.core.ui', 'Destroying content presenter');
                    $scope.unload();
                    logService.debug('appspace.core.ui', 'Content presenter destroyed');
                };

                $scope.setContentLoadTimeout = function(duration) {
                    loadTimeoutDuration = duration;
                };

                $scope.unload = function() {
                    mediaCompleted = false;

                    killLoadTimeout();

                    if (activeTransition) {
                        activeTransition.complete();
                        activeTransition = null;
                    }

                    if (currentItem) {
                        cpItemHelper.removeItem(currentItem);
                        currentItem = null;
                    }

                    if (loadingItem) {
                        cpItemHelper.removeItem(loadingItem);
                        loadingItem = null;
                    }

                    pendingItem = null;
                };

                $scope.togglePause = function() {
                    if (paused) {
                        $scope.play();
                        return;
                    }

                    $scope.pause();
                };

                $scope.pause = function() {
                    paused = true;
                    if (currentItem) {
                        pauseItem(currentItem);
                    }
                };

                $scope.play = function() {
                    paused = false;
                    if (currentItem) {
                        playItem(currentItem);
                    }
                };

                $scope.seek = function(item, offsetDuration) {
                    if (!item || !offsetDuration) {
                        return;
                    }

                    cpItemHelper.seekItem(item, offsetDuration);
                };

                $scope.isLoading = function() {
                    return !loadingItem ? false : true;
                };

                $scope.executeContentAction = function(action) {
                    if (currentItem && currentItem.api && currentItem.api.executeAction) {
                        currentItem.api.executeAction(action);
                    }
                };

                $scope.onActionReceived = function(action) {
                    if (!$scope.onItemActionReceived) {
                        $scope.onItemActionReceived({action: action});
                    }
                };

                $scope.updateDeviceProperties = function(properties) {
                    if (!currentItem || !currentItem.api || !currentItem.api.updateDeviceProperties) {
                        return;
                    }

                    currentItem.api.updateDeviceProperties(properties);
                };

                $scope.updateResourceManifest = function(manifest) {
                    if (!currentItem || !currentItem.api || !currentItem.api.updateResourceManifest) {
                        return;
                    }

                    currentItem.api.updateResourceManifest(manifest);
                };

                $scope.setDefaultDuration = function(duration) {
                    $scope.defaultDuration = duration;
                };

                $scope.setDefaultEnterTransition = function(transition) {
                    $scope.defaultEnterTransition = transition;
                };

                $scope.setDefaultExitTransition = function(transition) {
                    $scope.defaultExitTransition = transition;
                };

                $scope.setDefaultStretch = function(stretch) {
                    $scope.defaultStretch = stretch;
                };

                $scope.setStretch = function(stretch) {
                    if (!currentItem || !currentItem.api.setStretch) {
                        return;
                    }

                    currentItem.api.setStretch(stretch);
                };

                $scope.setDefaultTransitionDuration = function(transitionDuration) {
                    transitionDuration = parseFloat(transitionDuration);
                    $scope.defaultTransitionDuration = isNaN(transitionDuration) ? 0.5 : transitionDuration;
                };

                $scope.restart = function() {
                    if (!currentItem) {
                        return;
                    }

                    cpItemHelper.restart(currentItem);
                    mediaCompleted = false;
                    // Start playing
                    playItem(currentItem);
                };

                $scope.setVolume = function(volume) {
                    audioLevel = volume;
                    cpItemHelper.setItemVolume(currentItem, volume);
                    cpItemHelper.setItemVolume(loadingItem, loadingItem);
                };

                $scope.getVolume = function(volume) {
                    return audioLevel;
                };

                $scope.setMute = function(shouldMute, isTemporary) {
                    audioMute = shouldMute;
                    cpItemHelper.setItemMute(currentItem, shouldMute, isTemporary);
                    cpItemHelper.setItemMute(loadingItem, shouldMute, isTemporary);
                };

                $scope.loadContentItem = function(contentItem, force) {
                    startLoadTime = new Date().getTime();
                    // Clone the content item so we can make changes
                    lowBandwidthService.processContentItem(angular.copy(contentItem)).then(function(newContentItem) {
                        resolveContentUrl(newContentItem, force);
                    });
                };

                function resolveContentUrl(contentItem, force) {
                    resolveContentPath(contentItem).then(function(newContentPath) {
                        contentItem.originalMediaUrl = contentItem.mediaUrl;
                        contentItem.mediaUrl = newContentPath;
                        onLowBandwidthProcessed(contentItem, force);
                    });
                }

                function resolveContentPath(contentItem) {
                    return isHtmlSnippet(contentItem) ?
                        $q.resolve(contentItem.mediaUrl) :
                        halMediaService.resolveContentPath(contentItem.mediaUrl);
                }

                function isHtmlSnippet(contentItem) {
                    return contentItem.mediaUrl.indexOf('data:text/html;') > -1;
                }

                function isContentType(item, type) {
                    return item && item.content && item.content.mediaType === type;
                }

                $scope.reload = function(playerProperties) {
                    // Do not reload if item is already loading
                    if (loadingItem || !currentItem || !currentItem.api || !currentItem.api.reload) {
                        return;
                    }

                    currentItem.api.reload(playerProperties);
                };

                $scope.freeze = function() {
                    if (!currentItem || !currentItem.api || !currentItem.api.freeze) {
                        return;
                    }

                    currentItem.api.freeze();
                };

                function onLowBandwidthProcessed(contentItem, force) {
                    contentItem.mediaType = contentItem.mediaType.toLowerCase();
                    var content = {
                        content: contentItem
                    };

                    // Set default values if not set
                    if (dataUtil.isNullOrUndefined(contentItem.duration)) {
                        // for video or audio, set the duration to 0 to use the video/audio natural duration
                        contentItem.duration = (isContentType(content, CONTENT_TYPE.VIDEO) ||
                            isContentType(content, CONTENT_TYPE.AUDIO)) ?
                            0 : $scope.defaultDuration;
                    }

                    contentItem.stretch = contentItem.stretch || $scope.defaultStretch;
                    // If something is loading, we delay load until the current item has finished
                    if (loadingItem && !force) {
                        pendingItem = contentItem;
                        return;
                    }

                    // If nothing is currently loading, we add it it to the displayList to be loaded
                    pendingItem = null;
                    // Prevent any on complete events
                    mediaCompleted = true;
                    var pauseItemBeforeRemove = (halPlatformService.getFeatureSupport('media.video.decodeonpause.supported') == 'no') ? false : true;
                    // Pause current item
                    if (currentItem && pauseItemBeforeRemove) {
                        pauseItem(currentItem);
                    }

                    // Create the loading item reference
                    loadingItem = {
                        startTime: new Date().getTime(),
                        instanceId: ++instanceCount,
                        onComplete: itemContentComleteTrigger,
                        content: contentItem,
                        duration: dataUtil.isNullOrUndefined(contentItem.duration) ? $scope.defaultDuration : contentItem.duration,
                        wasCompleted: false
                    };

                    if (isContentType(content, CONTENT_TYPE.EXTERNAL_CONTENT)) {
                        loadExternalContentConfiguration(loadingItem);
                        return;
                    }

                    loadTimeoutDuration = isContentType(content, CONTENT_TYPE.CARD) && cardTimeoutDuration ?
                        cardTimeoutDuration : loadTimeoutDuration;
                    // dynamically compile the template for the content item
                    var itemTemplate = cpTemplateHelper.createTemplate(loadingItem, currentItem);
                    // Do we have a directive for this type of content
                    if (!itemTemplate) {
                        logService.debug('appspace.core.ui', 'Failed to find directive for current content type: ' + loadingItem.content.mediaType);
                        $scope.onMediaLoadError(loadingItem.instanceId);
                        return;
                    }

                    loadingItem.supportAnimationFrame = dataUtil.isNullOrUndefined(itemTemplate.supportAnimationFrame) ?
                        true : itemTemplate.supportAnimationFrame;
                    loadingItem.supportSwapWithVideo = dataUtil.isNullOrUndefined(itemTemplate.supportSwapWithVideo) ?
                        true : itemTemplate.supportSwapWithVideo;
                    loadingItem.delayLoadNextItem = itemTemplate.delayLoadNextItem;
                    loadingItem.isReuseSupported = itemTemplate.isReuseSupported;

                    if (!loadTimeoutDuration) {
                        loadTimeoutDuration = 10 * 1000;
                    }

                    killLoadTimeout();

                    // Start our load timer
                    loadTimeoutTimer = $timeout(onLoadTimeout, loadTimeoutDuration, false);

                    if ((reuseVideoLayer == 'yes' || cpItemHelper.isReuseSupported(loadingItem, currentItem)) && cpItemHelper.reuse(currentItem, loadingItem)) {
                        // Update the loading item id to be the same as current
                        loadingItem.instanceId = currentItem.instanceId;
                        loadingItem.api = currentItem.api;
                        logService.debug('appspace.core.ui', 'ContentPresenter: Reusing video object - ' + currentItem.content.mediaUrl);
                        return;
                    }

                    var processLoadItem = function() {
                        logService.debug('appspace.core.ui', 'Loading ' + loadingItem.content.mediaUrl);
                        loadingItem.scope = $scope.$new();
                        var el = $compile(itemTemplate.template)(loadingItem.scope);

                        // Add the item directive to the element
                        $element.find('.cp-stage').append(el);

                        // store the element reference
                        loadingItem.element = el;
                        if (itemTemplate.supportOnLoad) {
                            return;
                        }

                        // Fire the onload event manually if the item doesn't support on load event
                        $timeout(function() {
                            $scope.onMediaLoad(loadingItem.instanceId);
                        });
                    };

                    if (isCurrentContentRemoved(loadingItem, currentItem)) {
                        var delayLoadNextItem = currentItem.delayLoadNextItem;
                        currentItem = undefined;
                        if (delayLoadNextItem) {
                            setTimeout(processLoadItem, delayLoadNextItem);
                            return;
                        }

                        $scope.$evalAsync(processLoadItem);
                        return;
                    }

                    processLoadItem();
                }

                $scope.resetPause = function() {
                    // To make sure that the current item will play regardless of whether the previous item was paused
                    paused = false;
                };

                $scope.getCurrentItemRuntimeConfiguration = function() {
                    return currentItem ? currentItem.runtimeConfiguration : {};
                };

                var loadExternalContentConfiguration = function(lItemParam) {
                    var lItem = lItemParam;
                    var item = lItem.content;
                    var wrapper = new HttpWrapper();
                    wrapper.handlerGet(item.mediaUrl).then(function(res) {
                        if (res && res.data) {
                            var parser = new CSVParser();
                            res.data = 'table=1\r\n' + res.data;
                            parser.parse(res.data).then(function(parsed) {
                                // data invalid, reject
                                if (parsed.length == 0 || (parsed[0] && parsed[0].items.length == 0)) {
                                    $scope.onMediaLoadError(lItem.instanceId);
                                    return;
                                }

                                var data = parsed[0].items[0];
                                // copy the current item and modify the type & url
                                var newItem = $angular.copy(item);
                                newItem.mediaUrl = data.MediaUrl;
                                newItem.mediaType = data.MediaType.toLowerCase();

                                // Check if its an image
                                if (newItem.mediaType == CONTENT_TYPE.IMAGE) {
                                    // If so we need to ensure it doesnt get cached.
                                    var url = newItem.mediaUrl.toLowerCase();
                                    // Confirm its for a remote web server
                                    if (url.indexOf('http://') != -1 || url.indexOf('https://') != -1) {
                                        // Check does it already have a query string
                                        if (url.indexOf('?') > 0) {
                                            // Append the no cache tick
                                            newItem.mediaUrl = newItem.mediaUrl + '&noCacheTick=' + new Date().getTime();
                                        } else {
                                            // Append the no cache tick
                                            newItem.mediaUrl = newItem.mediaUrl + '?noCacheTick=' + new Date().getTime();
                                        }
                                    }
                                }

                                // decrease the instance count so that we're using the same id when we load external content
                                instanceCount--;
                                // reset loading item to null
                                loadingItem = null;
                                $scope.loadContentItem(newItem);
                            }, function(err) {
                                $scope.onMediaLoadError(lItem.instanceId);
                            });
                        }
                    }, function(err) {
                        $scope.onMediaLoadError(lItem.instanceId);
                    });
                };

                // Play the item
                var playItem = function(item) {
                    if (!item) {
                        return;
                    }

                    cpItemHelper.playItem(item);
                };

                // Pause the item
                var pauseItem = function(item) {
                    if (!item) {
                        return;
                    }

                    cpItemHelper.pauseItem(item);
                };

                var loadPendingItem = function() {
                    // Check if there is an item pending load, if so we start the load
                    // else we clear the loadingItem
                    if (pendingItem) {
                        $scope.loadContentItem(pendingItem, true);
                        return;
                    }

                    loadingItem = null;
                };

                // Kill the load timeout error
                var killLoadTimeout = function() {
                    if (!loadTimeoutTimer) {
                        return;
                    }

                    $timeout.cancel(loadTimeoutTimer);
                    loadTimeoutTimer = null;
                };

                var itemContentComleteTrigger = function(trigger) {
                    $scope.onMediaComplete(trigger.item.instanceId);
                };

                var replaceCurrentItem = function() {
                    if (!loadingItem || !currentItem) {
                        return;
                    }

                    currentItem.wasCompleted = loadingItem.wasCompleted;
                    currentItem.content = loadingItem.content;
                    // Reset media completed
                    mediaCompleted = false;

                    // Set volume
                    cpItemHelper.setItemVolume(currentItem, audioLevel);
                    cpItemHelper.setItemMute(currentItem, audioMute);

                    // Start playback
                    if (!paused) {
                        playItem(currentItem);
                    }

                    // Signal item has loaded
                    if ($scope.onItemLoad) {
                        $scope.onItemLoad({
                            item: currentItem
                        });
                    }

                    $($element.children()[0]).css('overflow', '');

                    // Check if there is a pending item to load
                    loadPendingItem();
                };

                $scope.preloadContentItem = function(url) {
                    if (!currentItem) {
                        return;
                    }

                    cpItemHelper.preloadContentItem(currentItem, url);
                };

                function removeItem(item) {
                    if (!item) {
                        return;
                    }

                    item.element.css('display', 'none');
                    cpItemHelper.removeItem(item);
                }

                function preRemoveItem(item) {
                    if (!item) {
                        return;
                    }

                    // Process to run before current item is replaced
                    cpItemHelper.preRemoveItem(item);
                }

                function toString(message) {
                    return dataUtil.isString(message) ? message : JSON.stringify(message);
                }

                // Once an item is loaded, we make it active and remove the previous
                var processItemLoaded = function() {
                    killLoadTimeout();

                    // Load must have been aborted by an unload
                    if (!loadingItem) {
                        exitElement = null;
                        return;
                    }

                    // Clear the loadby stopwatch
                    if (loadByStopWatch) {
                        loadByStopWatch.stop();
                        loadByStopWatch.reset();
                        loadByStopWatch.clearTriggers();
                        loadByStopWatch = undefined;
                    }

                    if (pendingItem) {
                        cpItemHelper.removeItem(loadingItem);
                        loadingItem = null;
                        // Check if there is a pending item to load
                        loadPendingItem();
                        return;
                    }

                    var exitElement = currentItem;
                    if (exitElement) {
                        preRemoveItem(exitElement);
                    }

                    // Have we simply updated the source?
                    if (currentItem && currentItem.instanceId == loadingItem.instanceId) {
                        replaceCurrentItem();
                        return;
                    }

                    // Set current item to the one loaded
                    currentItem = loadingItem;
                    // if device doesn't support switching content animation, directly intro new item & destroy old item if the current or loading item is a video
                    if (halPlatformService.getFeatureSupport('media.switchcontent.animate.supported') == 'no' &&
                        ((currentItem && currentItem.content.mediaType == CONTENT_TYPE.VIDEO) || (loadingItem && loadingItem.content.mediaType == CONTENT_TYPE.VIDEO))) {
                        var removeVideo = function() {
                            cpItemHelper.removeItem(exitElement);
                            exitElement = null;
                        };

                        currentItem.element.removeClass('cp-item-loading');
                        replaceCurrentItem();
                        // only delay the removal of the video if the next item is not a video
                        if (exitElement && (currentItem.content.mediaType != CONTENT_TYPE.VIDEO && exitElement.content.mediaType == CONTENT_TYPE.VIDEO)) {
                            $timeout(removeVideo, 1000);
                            return;
                        }

                        removeVideo();
                        return;
                    }

                    // Determine animation duration & type
                    var animationDuration = $scope.defaultTransitionDuration;
                    var enterTransition = getItemTransition(currentItem, 'enterTransition', $scope.defaultEnterTransition);
                    var exitTransition = '';
                    if (exitElement) {
                        exitTransition = getItemTransition(exitElement, 'exitTransition', $scope.defaultExitTransition);
                    }

                    // If no exit transition, set no animation duration for exit to 0
                    var exitDuration = exitTransition ? animationDuration : 0;
                    // if no enter transition, set animation duration to 0
                    var enterDuration = enterTransition ? animationDuration : 0;
                    var enterElement = null;
                    var exitElementItem = null;
                    var supportEnterAnimationFrame = true;
                    var supportExitAnimationFrame = true;
                    if (currentItem) {
                        enterElement = currentItem.element;
                        supportEnterAnimationFrame = currentItem.supportAnimationFrame;
                    }

                    if (exitElement) {
                        exitElementItem = exitElement.element;
                        supportExitAnimationFrame = exitElement.supportAnimationFrame;
                    }

                    activeTransition = cpTransitionHelper.createTransition({
                        stage: $element.find('.cp-stage'),
                        $scope: $scope,
                        enterElement: enterElement,
                        exitElement: exitElementItem,
                        enterTransition: enterTransition,
                        enterDuration: enterDuration,
                        exitDuration: exitDuration,
                        exitTransition: exitTransition,
                        persistAnimationClass: persistAnimationClass,
                        initialAnimationPosition: initialAnimationPosition,
                        initialZIndex: initialZIndex,
                        isZoomSupported: isZoomSupported,
                        supportAnimationFrame: supportEnterAnimationFrame && supportExitAnimationFrame
                    });

                    activeTransition.start().finally(function() {
                        replaceCurrentItem();
                        if (exitElement) {
                            removeItem(exitElement);
                        }

                        activeTransition = null;
                    });

                    if (!$scope.onItemBeginTransition) {
                        return;
                    }

                    $scope.onItemBeginTransition({
                        item: currentItem
                    });
                };

                // Fired by the item directive during initialization
                $scope.onItemApiReady = function(api, id) {
                    if (!loadingItem || id != loadingItem.instanceId) {
                        return;
                    }

                    // Capture the item directives api
                    loadingItem.api = api;

                    if (!loadingItem.api.setData || !loadingItem.content.data) {
                        return;
                    }

                    loadingItem.api.setData(loadingItem.content.data);
                };

                // Fired when the item directive loads its content and is ready to display
                $scope.onMediaLoad = function(metadata, id) {
                    // Signal item is ready
                    if ($scope.onItemReady) {
                        $scope.onItemReady();
                    }

                    if (!loadingItem || id != loadingItem.instanceId) {
                        return;
                    }

                    if (pendingItem) {
                        cpItemHelper.removeItem(loadingItem);
                        loadingItem = null;
                        // Check if there is a pending item to load
                        loadPendingItem();
                        return;
                    }

                    var ctime = new Date().getTime();
                    loadingItem.content = loadingItem.content || {};
                    logService.debug('appspace.core.ui', 'ContentPresenter: Media loaded - ' + loadingItem.content.mediaUrl + ' in ' + (ctime - startLoadTime) + 'ms');

                    var loadDelayTime = loadingItem.content.loadByTime > 0 ? loadingItem.content.loadByTime - ctime : 0;
                    if (loadDelayTime < 0) {
                        loadingItem.content.seekOffset = loadDelayTime * -1 / 1000;
                        $log.warn('Missed the load time by ', loadDelayTime * -1, 'ms for', loadingItem.content.mediaUrl);
                    }

                    // Delay the item load event if device doesn't support switch content animation
                    // and if the current item is not a video & the loading item is a video - so that we can prevent the black video when switching from non-video to video
                    if (halPlatformService.getFeatureSupport('media.switchcontent.animate.supported') == 'no' &&
                        (isContentType(loadingItem, CONTENT_TYPE.VIDEO) && isContentType(currentItem, CONTENT_TYPE.VIDEO))) {
                        loadDelayTime = + 800;
                    }

                    metadata = metadata || {};
                    // If we have natural duration and there's no content specific duration, set the content duration to 0 to play until on complete
                    if (isContentType(loadingItem, CONTENT_TYPE.CARD) && metadata.hasNaturalDuration) {
                        var playback = loadingItem.content.playback || {};
                        var message = 'Card has natural duration. Waiting for card\'s complete event';
                        loadingItem.content.duration = 0;
                        if (playback.contentDuration) {
                            message = 'Ignoring card natural duration. Card has user specified duration of ' + playback.contentDuration + ' seconds';
                            loadingItem.content.duration = playback.contentDuration;
                        }

                        logService.debug('appspace.core.ui', 'ContentPresenter: ' + message);
                    }

                    loadingItem.metadata = metadata;
                    if (loadingItem.content.seekOffset > 0) {
                        $scope.seek(loadingItem, loadingItem.content.seekOffset);
                    }

                    if (loadDelayTime > 0) {
                        // media already loaded but we want to delay the load event, so we need to kill the load timeout timer
                        killLoadTimeout();
                        loadByStopWatch = new StopWatch();
                        loadByStopWatch.addTrigger({
                            callback: processItemLoaded,
                            scope: $scope,
                            elapse: loadDelayTime
                        });
                        loadByStopWatch.start();
                        return;
                    }

                    processItemLoaded();
                };

                // Fired when the item directive has an error during playback
                $scope.onMediaPlaybackError = function(id, error) {
                    // If we are loading an item, ignore
                    if (loadingItem) {
                        logService.debug('appspace.core.ui', 'Ignore playback error as we\'re loading an item');
                        return;
                    }

                    // if we don't have current item, ignore
                    if (!currentItem) {
                        logService.debug('appspace.core.ui', 'Ignore playback error as there is no current item');
                        return;
                    }

                    // If the current item id doesnt match, ignore
                    if (id != currentItem.instanceId) {
                        logService.debug('appspace.core.ui', 'Ignore playback error as the instance id doesn\'t match');
                        return;
                    }

                    // if the current item has completed before, ignore
                    if (currentItem && currentItem.wasCompleted) {
                        logService.debug('appspace.core.ui', 'Ignore playback error as item already send complete');
                        return;
                    }

                    var logMessage = 'ContentPresenter: Playback error of content - ' + currentItem.content.mediaUrl;
                    error = error ? toString(error) : '';
                    if (error) {
                        logMessage += ' due to: ' + error;
                    }

                    logService.error('appspace.core.ui', logMessage);

                    // Notify that we had a media playback error
                    var item = $angular.copy(currentItem.content);
                    if (!$scope.onItemLoadError) {
                        return;
                    }

                    $scope.onItemLoadError({
                        item: item,
                        error: error
                    });
                };

                // Fired when the item directive has failed to load in time
                function onLoadTimeout() {
                    if (!loadingItem) {
                        return;
                    }

                    logService.debug('appspace.core.ui', 'Failed to load current content in time (' + loadTimeoutDuration + 'ms) - ' + loadingItem.content.mediaUrl);
                    $scope.onMediaLoadError(loadingItem.instanceId);
                }

                // Fired when the item directive has an error during load
                $scope.onMediaLoadError = function(id, message) {
                    if (!loadingItem ||
                        id != loadingItem.instanceId) {
                        return;
                    }

                    killLoadTimeout();
                    var logMessage = 'ContentPresenter: Failed to load content - ' + loadingItem.content.mediaUrl;
                    message = message ? toString(message) : '';
                    if (message) {
                        logMessage += ' Message: ' + message;
                    }

                    logService.error('appspace.core.ui', logMessage);
                    var item = $angular.copy(loadingItem.content);
                    if (currentItem && currentItem.instanceId == loadingItem.instanceId) {
                        cpItemHelper.removeItem(currentItem);
                        currentItem = null;
                    }

                    cpItemHelper.removeItem(loadingItem);
                    loadingItem = null;

                    if (!$scope.onItemLoadError) {
                        return;
                    }

                    $scope.onItemLoadError({
                        item: item,
                        error: message
                    });
                };

                function isItemIdValid(id) {
                    return (loadingItem && id === loadingItem.instanceId) ||
                        (currentItem && id === currentItem.instanceId);
                }

                function getItemTransition(item, keyTransition, defaultTransition) {
                    // if a content contains video and device doesn't support video transition, returns empty
                    if (cpItemHelper.isContainedVideoOrAudio(item) && !supportVideoTransition) {
                        return '';
                    }

                    var itemTransition = item.content[keyTransition];
                    return dataUtil.isNullOrUndefined(itemTransition) ?
                        defaultTransition : itemTransition;
                }

                function shouldRemoveContentFirst(newItem, existingItem) {
                    // for devices that only support 1 video/audio object at a time
                    return (isSingleVideoLayerOnly
                        && cpItemHelper.isContainedVideoOrAudio(newItem)
                        && cpItemHelper.isContainedVideoOrAudio(existingItem)) ||
                        // or current content or next content doesn't support swapping with video
                        (!cpItemHelper.supportSwapWithVideo(newItem, existingItem) &&
                            cpItemHelper.isContainedVideoOrAudio(newItem));
                }

                function isCurrentContentRemoved(newItem, existingItem) {
                    if (dataUtil.isNullOrUndefined(existingItem) || !shouldRemoveContentFirst(newItem, existingItem)) {
                        return false;
                    }

                    logService.debug('appspace.core.ui', 'Removing the current content before loading the next content');
                    removeItem(existingItem);
                    return true;
                }

                $scope.onMetadataLoaded = function(metadata, id) {
                    metadata = metadata || {};
                    loadingItem.runtimeConfiguration = {
                        containsVideo: metadata.containsVideo,
                        containsAudio: metadata.containsVideo,
                        allowVideoScrubbing: metadata.allowVideoScrubbing
                    };
                    if (!isCurrentContentRemoved(loadingItem, currentItem)) {
                        return;
                    }

                    currentItem = undefined;
                };

                // Fired when the item directive completes playback or the content timeout timer expires
                $scope.onMediaCompleteEvent = function(id, isEndOfPlaylist) {
                    if (!isItemIdValid(id)) {
                        return;
                    }

                    if (isEndOfPlaylist) {
                        logService.debug('appspace.core.ui', 'ContentPresenter: Media complete due to end of playlist');
                        $scope.onMediaComplete(id, isEndOfPlaylist);
                        return;
                    }

                    var retrieveError = function() {
                        if (!currentItem) {
                            return;
                        }

                        if (currentItem.content.endByTime) {
                            return 'Media Complete event skipped as endtime specified';
                        }

                        if (currentItem.content.duration) {
                            return 'Media Complete event skipped as content has custom duration';
                        }
                    };

                    var message = retrieveError();
                    if (message) {
                        logService.debug('appspace.core.ui', 'ContentPresenter: ' + message);
                        return;
                    }

                    $scope.onMediaComplete(id);
                };

                // Processes media complete event
                $scope.onMediaComplete = function(id, isEndOfPlaylist) {
                    if (!isItemIdValid(id)) {
                        return;
                    }

                    if (currentItem) {
                        pauseItem(currentItem);
                        currentItem.wasCompleted = true;
                        if (currentItem.content) {
                            logService.debug('appspace.core.ui', 'ContentPresenter: Media complete - ' + currentItem.content.mediaUrl);
                        }
                    }

                    if (!$scope.onItemComplete || mediaCompleted) {
                        return;
                    }

                    $scope.onItemComplete({
                        item: currentItem,
                        isEndOfPlaylist: isEndOfPlaylist
                    });
                };

                $scope.onPlaybackProgress = function(id, progress) {
                    if (!currentItem || !isItemIdValid(id) || !$scope.onItemPlaybackProgress) {
                        return;
                    }

                    $scope.onItemPlaybackProgress({
                        progress: progress,
                        item: currentItem
                    });
                };
            }]
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('ui');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('asUiDateTime', function() {
        return {
            restrict: 'EA',
            scope: {
                timeZone: '@', // specific timezone
                format: '@', // how to format the date time
                autoUpdate: '@', // if false the date time will not automatically update
                date: '=', // custom date time value
                fillHeight: '@', // if true, height is 100%. if not, no height
                onApiReady: '&',
                caps: '@' // capitalise all words
            },

            template: '<div ng-attr-style="width:100%; {{heightStyle}};">{{dateTimeText}}</div>',

            link: function(scope, element, attrs) {
                if (!attrs.fillHeight) {
                    scope.fillHeight = 'true';
                }

                if (!attrs.caps) {
                    scope.caps = 'false';
                }

                scope.$on('$destroy', function() {
                    scope.destroy();
                });

                element.on('$destroy', function() {
                    scope.destroy();
                });

                scope.$watch('fillHeight', function(newval) {
                    if (!newval) {
                        return;
                    }
                    scope.setFillHeight(newval);
                });

                scope.$watch('date', function(newVal) {
                    if (!newVal) {
                        return;
                    }
                    scope.setDate(newVal);
                });

                scope.$watch('autoUpdate', function(newVal) {
                    if (!newVal) {
                        scope.resumeUpdates();
                        return;
                    }
                    if (newVal == 'true') {
                        scope.resumeUpdates();
                        return;
                    }
                    scope.stopUpdates();
                });

                scope.$watch('format', function(newVal) {
                    if (!newVal) {
                        return;
                    }
                    scope.refresh();
                });

                scope.api = {
                    setDate: scope.setDate,
                    resumeUpdates: scope.resumeUpdates,
                    stopUpdates: scope.stopUpdates,
                    setFillHeight: scope.setFillHeight,
                    destroy: scope.destroy,
                    refresh: scope.refresh
                };

                if (scope.onApiReady) {
                    scope.onApiReady({api: scope.api});
                }

                scope.refresh();
            },

            controller: ['$scope', '$element', '$q', '$interval', function($scope, $element, $q, $interval) {
                var updatePromise;
                var customDate;
                $scope.destroy = function() {
                    $scope.stopUpdates();
                    customDate = undefined;
                    if (!$element) {
                        return;
                    }
                    $element.empty();
                    $element = undefined;
                };

                $scope.setFillHeight = function(newval) {
                    newval = (newval.toLowerCase() == 'false') ? false : true;
                    $scope.heightStyle = (newval == true) ? 'height:100%' : '';
                };

                $scope.refresh = function() {
                    render();
                };

                $scope.setDate = function(date) {
                    customDate = date;
                    render();
                };

                $scope.resumeUpdates = function() {
                    $scope.stopUpdates();
                    $scope.refresh();
                    updatePromise = $interval($scope.refresh, 1000);
                };

                $scope.stopUpdates = function() {
                    if (!updatePromise) {
                        return;
                    }
                    $interval.cancel(updatePromise);
                    updatePromise = undefined;
                };

                function render() {
                    var date = customDate ? new Date(customDate) : new Date();
                    var m = moment(date);
                    var text = '';
                    if ($scope.timeZone && $scope.timeZone.length > 0) {
                        m = m.tz($scope.timeZone);
                    }
                    if ($scope.format) {
                        text = m.format($scope.format);
                    }
                    if ($scope.caps && $scope.caps == 'true') {
                        text = text.toUpperCase();
                    }
                    $scope.dateTimeText = text;
                }
            }]
        };
    });
})(window.angular);

(function ($angular) {

    "use strict";

    var app = $angular.module('ui');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('asUiGrid', ["$compile", function ($compile) {

        return {
            restrict: "EA",
            transclude: true,
            scope: {
                items: '=',
                config: '=',
                itemsPerPage: '@',
                onApiReady: '&',
                onItemFocus: '&',
                onItemClick: '&'
            },

            template: '<div class="ui-grid-outer"><div class="ui-grid"><div class="ui-grid-container"><div class="ui-grid-transcluded-content-container"><ng-transclude></ng-transclude></div></div></div></div>',

            link: function (scope, element, attrs) {

                if (attrs.itemsPerPage) {
                    scope.itemsPerPage = attrs.itemsPerPage;
                }

                scope.$on('$destroy', function () {
                    scope.destroy();
                });

                element.on('$destroy', function () {
                    //console.log("asUiGrid element destroy");
                    scope.destroy();
                });

                scope.$watch('items', function (newVal) {
                    if (!newVal) {
                        return;
                    }

                    scope.clearItems();

                    return scope.addItems(newVal);
                });

                scope.$watch('config', function (newVal) {
                    if (!newVal) {
                        return;
                    }
                    return scope.setConfig(newVal);
                }, true);

                //Construct the api interface
                scope.api = {
                    addItem: scope.addItem,
                    addItems: scope.addItems,
                    nextItem: scope.nextItem,
                    setConfig: scope.setConfig,
                    clearItems: scope.clearItems,
                    previousItem: scope.previousItem,
                    upItem: scope.upItem,
                    downItem: scope.downItem,
                    leftItem: scope.leftItem,
                    rightItem: scope.rightItem,
                    removeHighlight: scope.removeHighlight,
                    moveToItem: scope.moveToItem,
                    getItems: scope.getItems,
                    getFocusedItem: scope.getFocusedItem,
                    resetFocusedItem: scope.resetFocusedItem,
                    getCurrentItemOffset: scope.getCurrentItemOffset,
                    getCurrentItemColumnOffset: scope.getCurrentItemColumnOffset,
                    getCurrentItemRowOffset: scope.getCurrentItemRowOffset,
                    getItemsInView: scope.getItemsInView,
                    gotoPage: scope.gotoPage,
                    getTotalPages: scope.getTotalPages,
                    getCurrentPage: scope.getCurrentPage,
                    gotoNextPage: scope.gotoNextPage,
                    gotoPreviousPage: scope.gotoPreviousPage,
                    destroy: scope.destroy
                };

                scope.init();

                if (scope.onApiReady) {
                    scope.onApiReady({ api: scope.api });
                }
            },

            controller: ['$scope', '$element', '$q', '$interval', '$timeout', 'halPlatformService', 'halRemoteService', 'asClickService', function ($scope, $element, $q, $interval, $timeout, halPlatformService, halRemoteService, asClickService) {

                var carousel = $($element).children().find(".ui-grid-container");

                var lastTouchPosition;
                var requestAnimationId;
                var scrollMomentum = 0;
                var useBrowserScroll = false;
                var highlightTimeoutHandler = 0;

                var gridItems = [];
                var gridInfo = {
                    position: {
                        rowOffset: 0,
                        colOffset: 0,
                        itemOffset: -1,
                        scrollOffset: 0,
                        aligmentOffset: 0
                    },
                    viewport: {
                        width: 1,
                        height: 1,
                        rowCount: 1,
                        maxYOffset: 1,
                        maxXOffset: 1
                    },
                    items: {
                        itemsPerRow: 1,
                        totalRows: 1,
                        spacingX: 0,
                        spacingY: 0
                    }
                };

                var gridConfig = {
                    padding: { top: 0, left: 0, right: 0, bottom: 0 },
                    itemSpacing: { horizontal: 10, vertical: 10, autoJustify: false },
                    aspectRatio: 1,
                    autoHighlight: true,
                    items: $scope.itemsPerPage,
                    transcludeSticky: false, //Should the transcluded content remain sticky even when scrolling
                    scrollAlignment: "item", //"page" or "item" - default is item, causes the scroll offset to always be aligned to page boundaries
                    scrollDirection: "vertical", //horizontal or vertical" - default vertical
                    //itemSizeCalculation: "predefine or auto" - default auto, determines the size of the items
                    //itemWidth: xxx
                    //itemHeight: xxx the size of the item in pixels. only use if itemsizecalculation is predefine
                    itemAlignment: "start" //"start" or "middle" or "end"
                };

                var singleIntervalId;

                $scope.destroy = function () {
                    if (highlightTimeoutHandler) {
                        $timeout.cancel(highlightTimeoutHandler);
                        highlightTimeoutHandler = 0;
                    }
                    $scope.$destroy();
                    // kill all interval
                    $element.empty();
                    if (singleIntervalId) {
                        $interval.cancel(singleIntervalId);
                    }
                }

                $scope.init = function () {
                    useBrowserScroll = true;
                }

                $scope.initGrid = function () {

                    var bottomMarker = carousel.find(".as-grid-bottom-marker");
                    if (bottomMarker.length > 0) {
                        bottomMarker.remove();
                    }

                    carousel.append($("<div class='as-grid-bottom-marker'></div>"));

                    //Move the trancluded content to either the carousel or root depending on whether its sticky or not
                    var transcludedContainer = $($element).find(".ui-grid-transcluded-content-container");
                    transcludedContainer.remove();
                    if (gridConfig.transcludeSticky) {
                        var outerGrid = $($element).find(".ui-grid-outer");
                        outerGrid.append(transcludedContainer);
                    }
                    else {
                        carousel.append(transcludedContainer);
                    }

                    calculateViewportInfo();
                    updateItemLayout();

                    if (!singleIntervalId) {
                        singleIntervalId = $interval(checkContainerSize, 1000);
                    }
                }

                function checkContainerSize() {
                    var gridContainer = $($element.children()[0]);
                    var width = gridContainer.width();
                    var height = gridContainer.height();
                    if (gridConfig.padding) {
                        width -= (gridConfig.padding.left + gridConfig.padding.right);
                        height -= (gridConfig.padding.top + gridConfig.padding.bottom);
                    }

                    //Remove the transcluded content height
                    var transcludedContainer = $($element).find(".ui-grid-transcluded-content-container");
                    height -= transcludedContainer.height();

                    if (width != gridInfo.viewport.width || height != gridInfo.viewport.height) {
                        calculateViewportInfo();
                        updateItemLayout();
                        scrollToItem(gridInfo.position.itemOffset);
                    }
                }

                $scope.setConfig = function (config) {
                    gridConfig = config;
                    //If an items per page is given, override the config
                    if ($scope.itemsPerPage) {
                        gridConfig.items = $scope.itemsPerPage
                    }

                    if (gridConfig.uiGridItemClass) {
                        gridInfo.uiGridItemClass = gridConfig.uiGridItemClass;
                    } else {
                        gridInfo.uiGridItemClass = "ui-grid-item";
                    }

                    if (gridConfig.uiGridItemActiveClass) {
                        gridInfo.uiGridItemActiveClass = gridConfig.uiGridItemActiveClass;
                    } else {
                        gridInfo.uiGridItemActiveClass = "ui-grid-item-active";
                    }

                    if (!gridConfig.aspectRatio) {
                        gridConfig.aspectRatio = 1;
                    }

                    if (!gridConfig.padding) {
                        gridConfig.padding = { top: 0, left: 0, right: 0, bottom: 0 };
                    }
                    if (!gridConfig.itemSpacing) {
                        gridConfig.itemSpacing = { horizontal: 10, vertical: 10, autoJustify: false };
                    }

                    if (!gridConfig.scrollAlignment) {
                        gridConfig.scrollAlignment = "page";
                    }

                    if (gridConfig.autoHighlight == undefined) {
                        gridConfig.autoHighlight = true;;
                    }

                    if (gridConfig.highlightTimeout == undefined) {
                        var res = halPlatformService.getFeatureSupport("input.touchscreen");
                        if (gridConfig.highlightTimeout === null || gridConfig.highlightTimeout === undefined) {
                            if (res && res == "yes") {
                                gridConfig.highlightTimeout = 200;
                            }
                            else {
                                gridConfig.highlightTimeout = 0;
                            }
                        }
                    }

                    var cEl = carousel[0];
                    cEl.style.overflow = "hidden";
                    if (gridConfig.scrollDirection == "horizontal") {
                        cEl.style.overflowX = "scroll";
                    }
                    else {
                        cEl.style.overflowY = "scroll";
                    }

                    $scope.initGrid();
                    $scope.resetFocusedItem();
                    updateScrollPosition(0, 0);
                    updateColRowOffset();
                }

                function onItemClick(event) {
                    // console.log("onItemClick");
                    // This is fix for UWP where toElement is always null, need to use target instead
                    var element = event.toElement ? event.toElement : event.target;
                    while (element) {
                        var classList = element.className.split(/\s+/);
                        for (var i = 0; i < classList.length; i++) {
                            var classParts = classList[i].toLowerCase().split('grid_offset_');
                            if (classParts.length == 2) {
                                var offset = parseInt(classParts[1]);
                                $scope.removeHighlight(gridInfo.position.itemOffset);

                                gridInfo.position.itemOffset = offset;
                                gridInfo.position.rowOffset = Math.ceil((offset + 1) / gridInfo.items.itemsPerRow) - 1;

                                //auto scroll on click if we're using remote control
                                if (halRemoteService.getLastEventTime()) {
                                    scrollToItem(offset);
                                }

                                $scope.updateHighlight(offset, true);

                                if ($scope.onItemClick) {
                                    $scope.onItemClick({ api: $scope.api, offset: offset });
                                }
                                return;
                            }
                        }
                        element = element.parentElement;
                    }
                    return false;
                }

                function calculateViewportInfo() {
                    //Reset grid info
                    //Update the viewport info
                    gridInfo.viewport.width = carousel.width();
                    gridInfo.viewport.height = carousel.height();

                    //Remove the paddings from the viewport width
                    if (gridConfig.padding) {
                        gridInfo.viewport.width -= (gridConfig.padding.left + gridConfig.padding.right);
                        gridInfo.viewport.height -= (gridConfig.padding.top + gridConfig.padding.bottom);
                    }

                    //Remove the transcluded content height
                    var transcludedContainer = $($element).find(".ui-grid-transcluded-content-container");
                    gridInfo.viewport.height -= transcludedContainer.height();

                    //Update grid spacing
                    gridInfo.items.spacingX = gridConfig.itemSpacing.horizontal;
                    gridInfo.items.spacingY = gridConfig.itemSpacing.vertical;

                    // itemSizeCalculation - itemSize
                    gridInfo.itemSizeCalculation = gridConfig.itemSizeCalculation;

                    // scroll direction
                    if (gridConfig.scrollDirection) {
                        gridInfo.scrollDirection = gridConfig.scrollDirection.toLowerCase();
                    }

                    //Set item count per Row
                    if (gridConfig.items) {
                        gridInfo.items.itemsPerRow = gridConfig.items;
                    }
                    else {
                        gridInfo.items.itemsPerRow = 5;
                    }

                    //Check if there is a responsive config
                    if (gridConfig.responsive) {
                        var responsive = null;
                        for (var key in gridConfig.responsive) {
                            var res = parseInt(key);
                            if (gridInfo.viewport.width > res) {
                                //This responsive config is accepted
                                responsive = gridConfig.responsive[key];
                            }
                            else {
                                break;
                            }
                        }
                        if (responsive) {
                            gridInfo.items.itemsPerRow = responsive.items;
                        }
                    }

                    if (gridInfo.items.itemsPerRow < 1) {
                        gridInfo.items.itemsPerRow = 1;
                    }

                    // calculate item size
                    if (gridConfig.itemWidth != undefined || gridConfig.itemHeight != undefined) {
                        //Config supplied an item size
                        if (gridConfig.itemWidth != undefined) { //If item width supplied, use it, else determine based on ratio
                            if (gridConfig.itemWidth == 0) {
                                gridInfo.items.width = gridInfo.viewport.width;
                            }
                            else {
                                gridInfo.items.width = gridConfig.itemWidth;
                            }
                        }
                        else {
                            gridInfo.items.width = gridConfig.itemHeight * gridConfig.aspectRatio;
                        }
                        if (gridConfig.itemHeight != undefined) { //If item height supplied, use it, else determine based on ratio
                            if (gridConfig.itemHeight == 0) {
                                gridInfo.items.height = gridInfo.viewport.height;
                                //s console.log(gridInfo.items.height);
                            }
                            else {
                                gridInfo.items.height = gridConfig.itemHeight;
                            }
                        }
                        else {
                            gridInfo.items.height = gridConfig.itemWidth / gridConfig.aspectRatio;
                        }

                        //Update items per row
                        if (gridInfo.scrollDirection == "horizontal") {
                            gridInfo.items.itemsPerRow = Math.floor((gridInfo.viewport.height + gridInfo.items.spacingY) / (gridInfo.items.height + gridInfo.items.spacingY));
                        } else {
                            gridInfo.items.itemsPerRow = Math.floor((gridInfo.viewport.width + gridInfo.items.spacingX) / (gridInfo.items.width + gridInfo.items.spacingX));
                        }

                        if (gridInfo.items.itemsPerRow < 1) {
                            gridInfo.items.itemsPerRow = 1;
                        }
                    } else {
                        //Calculate item size based on count per row
                        gridInfo.items.width = (gridInfo.viewport.width - (gridInfo.items.spacingX * (gridInfo.items.itemsPerRow - 1))) / gridInfo.items.itemsPerRow;
                        gridInfo.items.height = gridInfo.items.width / gridConfig.aspectRatio;
                    }

                    //Should we auto adjust item spacing to fit viewport
                    if (gridConfig.itemSpacing.autoJustify) {

                        if (gridInfo.scrollDirection == "horizontal") {
                            var usedArea = (gridInfo.items.itemsPerRow * (gridInfo.items.height + gridInfo.items.spacingY)) - gridInfo.items.spacingY;
                            var remainingSpace = gridInfo.viewport.height - usedArea;
                            var adjustment = remainingSpace / gridInfo.items.itemsPerRow;
                            gridInfo.items.spacingY += adjustment;
                        }
                        else {
                            var usedArea = (gridInfo.items.itemsPerRow * (gridInfo.items.width + gridInfo.items.spacingX)) - gridInfo.items.spacingX;
                            var remainingSpace = gridInfo.viewport.width - usedArea;
                            var adjustment = remainingSpace / gridInfo.items.itemsPerRow;
                            gridInfo.items.spacingX += adjustment;
                        }
                    }

                    //Calculate item size based on count per row
                    gridInfo.items.totalRows = Math.ceil(gridItems.length / gridInfo.items.itemsPerRow);

                    //Update rowCount for viewport
                    if (gridInfo.scrollDirection == "horizontal") {
                        gridInfo.viewport.rowCount = Math.floor((gridInfo.viewport.width + gridInfo.items.spacingX) / (gridInfo.items.width + gridInfo.items.spacingX));
                    }
                    else {
                        gridInfo.viewport.rowCount = Math.floor((gridInfo.viewport.height + gridInfo.items.spacingY) / (gridInfo.items.height + gridInfo.items.spacingY));
                    }

                    gridInfo.viewport.rowCount = gridInfo.viewport.rowCount || 1;

                    if (gridConfig.scrollDirection == "horizontal") {
                        if (gridConfig.itemAlignment == "middle") {
                            var usedHeight = ((gridInfo.items.height + gridInfo.items.spacingY) * gridInfo.items.itemsPerRow) - (gridInfo.items.spacingY);
                            gridInfo.position.aligmentOffset = (gridInfo.viewport.height - usedHeight) / 2;
                        }
                        else if (gridConfig.itemAlignment == "end") {
                            var usedHeight = ((gridInfo.items.height + gridInfo.items.spacingY) * gridInfo.items.itemsPerRow) - (gridInfo.items.spacingY);
                            gridInfo.position.aligmentOffset = gridInfo.viewport.height - usedHeight;
                        }
                        else {
                            gridInfo.position.aligmentOffset = 0;
                        }
                    }
                    else {
                        if (gridConfig.itemAlignment == "middle") {
                            var usedWidth = ((gridInfo.items.width + gridInfo.items.spacingX) * gridInfo.items.itemsPerRow) - (gridInfo.items.spacingX);
                            gridInfo.position.aligmentOffset = (gridInfo.viewport.width - usedWidth) / 2;
                        }
                        else if (gridConfig.itemAlignment == "end") {
                            var usedWidth = ((gridInfo.items.width + gridInfo.items.spacingX) * gridInfo.items.itemsPerRow) - (gridInfo.items.spacingX);
                            gridInfo.position.aligmentOffset = gridInfo.viewport.width - usedWidth;
                        }
                        else {
                            gridInfo.position.aligmentOffset = 0;
                        }
                    }
                }

                //positions the items in the grid
                function updateItemLayout() {
                    var elements = carousel.find("> .ui-grid-item");
                    var xOffset = 0;
                    var yOffset = 0;

                    if (gridConfig.scrollDirection == "horizontal") {
                        yOffset = gridInfo.position.aligmentOffset;
                    }
                    else {
                        xOffset = gridInfo.position.aligmentOffset;
                    }

                    gridInfo.viewport.maxXOffset = 1;
                    gridInfo.viewport.maxYOffset = 1;

                    if (gridConfig.padding) {
                        xOffset += gridConfig.padding.left;
                        yOffset += gridConfig.padding.top;
                    }

                    var transcludedContainer = $($element).find(".ui-grid-transcluded-content-container");

                    //Position the transcluded content based on the padding
                    transcludedContainer.css({
                        left: xOffset,
                        top: yOffset,
                        position: "absolute"
                    });

                    //Increment the y offset by the transcluded content inner height
                    yOffset += transcludedContainer.height();

                    for (var i = 0; i < elements.length; i++) {
                        var element = $(elements[i]);

                        var columnOffset;
                        var rowOffset;
                        if (gridInfo.scrollDirection == "horizontal") {
                            columnOffset = Math.floor(i / gridInfo.items.itemsPerRow);
                            rowOffset = i % gridInfo.items.itemsPerRow;
                        } else {
                            columnOffset = i % gridInfo.items.itemsPerRow;
                            rowOffset = Math.floor(i / gridInfo.items.itemsPerRow);
                        }

                        var positionRule = {
                            width: gridInfo.items.width + 'px',
                            height: gridInfo.items.height + 'px',
                            left: (columnOffset * (gridInfo.items.width + gridInfo.items.spacingX)) + xOffset,
                            top: (rowOffset * (gridInfo.items.height + gridInfo.items.spacingY)) + yOffset,
                        };

                        element.css(positionRule);

                        //update view port scroll offset maximums
                        var right = element[0].offsetLeft + gridInfo.items.width;
                        var bottom = element[0].offsetTop + gridInfo.items.height;
                        if (right > gridInfo.viewport.maxXOffset) {
                            gridInfo.viewport.maxXOffset = right;
                        }
                        if (bottom > gridInfo.viewport.maxYOffset) {
                            gridInfo.viewport.maxYOffset = bottom;
                        }
                    }

                    //Move the grid marker to furthers right and bottom, so the padding is correctly applied
                    var bottomMarker = carousel.find(".as-grid-bottom-marker");
                    if (bottomMarker.length > 0 && gridConfig.padding) {
                        var positionRule = {
                            width: 1,
                            height: 1,
                            left: gridInfo.viewport.maxXOffset + gridConfig.padding.right,
                            top: gridInfo.viewport.maxYOffset + gridConfig.padding.bottom,
                        };
                        gridInfo.viewport.maxXOffset = positionRule.right;
                        gridInfo.viewport.maxYOffset = positionRule.top;
                        bottomMarker.css(positionRule);
                    }

                    //Check if our offsets are less than viewport size
                    if (gridInfo.viewport.maxXOffset < gridInfo.viewport.width) {
                        gridInfo.viewport.maxXOffset = gridInfo.viewport.width;
                    }

                    if (gridInfo.viewport.maxYOffset < gridInfo.viewport.height) {
                        gridInfo.viewport.maxYOffset = gridInfo.viewport.height;
                    }
                }

                function addImageItem(contentItem) {
                    var markup = "<div style='height:100%; background:url(\"" + contentItem.url + "\"); background-size: cover; background-position: center; background-repeat: no-repeat; '></div>"
                    var element = $(markup)
                    addElementItem({ type: 'element', element: element });

                }

                function addMarkupItem(contentItem) {
                    var element = $(contentItem.markup);
                    addElementItem({ type: 'element', element: element });
                }

                function addElementItem(contentItem) {
                    // console.log("addElementItem");
                    // console.log($(contentItem.element));
                    var elem = $(contentItem.element);

                    var elements = carousel.find("> .ui-grid-item");

                    elem.addClass("grid_offset_" + elements.length);

                    if (!gridInfo.uiGridItemClass) {
                        gridInfo.uiGridItemClass = "ui-grid-item";
                    }

                    elem.addClass(gridInfo.uiGridItemClass);
                    asClickService.unbind(elem);
                    asClickService.bind(elem, onItemClick);

                    carousel.append(elem);
                }

                function addContentItem(contentItem) {
                    switch (contentItem.type.toLowerCase()) {
                        case "image":
                            addImageItem(contentItem);
                            break;
                        case "markup":
                            addMarkupItem(contentItem);
                            break;
                        case "element":
                            addElementItem(contentItem);
                            break;
                    }
                }

                function getItemElements(offset) {
                    var elements = carousel.find("> .ui-grid-item");
                    if (offset < 0 || offset >= elements.length) {
                        return [];
                    }
                    return [elements[offset]];
                }

                function getItemElement(offset) {
                    var elements = carousel.find("> .ui-grid-item");
                    if (offset < 0 || offset >= elements.length) {
                        return [];
                    }
                    return elements[offset];
                }

                $scope.removeHighlight = function (offset) {
                    if (offset == undefined) {
                        offset = gridInfo.position.itemOffset;
                    }
                    var elements = getItemElements(offset);
                    for (var i = 0; i < elements.length; i++) {
                        $(elements[i]).removeClass(gridInfo.uiGridItemActiveClass);
                    }
                }

                $scope.updateHighlight = function (offset, fromClick) {
                    var elements = getItemElements(offset);
                    for (var i = 0; i < elements.length; i++) {
                        // $(elements[i]).addClass("ui-grid-item-active");
                        $(elements[i]).addClass(gridInfo.uiGridItemActiveClass);
                    }

                    if (highlightTimeoutHandler) {
                        $timeout.cancel(highlightTimeoutHandler);
                        highlightTimeoutHandler = 0;
                    }

                    var lastRemoteEvent = halRemoteService.getLastEventTime();
                    //only remove highlight if there's a timeout AND no one uses remote control
                    if (gridConfig.highlightTimeout > 0 && lastRemoteEvent == null) {
                        highlightTimeoutHandler = $timeout(function () {
                            $scope.removeHighlight(offset);
                        }, gridConfig.highlightTimeout);
                    }

                    if ($scope.onItemFocus) {
                        $scope.onItemFocus({ api: $scope.api, offset: offset, fromClick: fromClick });
                    }
                }

                $scope.getFocusedItem = function () {
                    if (gridInfo.position.itemOffset >= 0 && gridInfo.position.itemOffset < gridItems.length) {
                        return gridItems[gridInfo.position.itemOffset];
                    }
                    return null;
                }

                $scope.resetFocusedItem = function () {
                    $scope.removeHighlight();
                    gridInfo.position.itemOffset = -1;
                }

                $scope.getItemsInView = function (scrollOffset) {
                    var visibleItems = getItemsInScrollView(scrollOffset);
                    var itemsInView = [];
                    if (visibleItems.startOffset >= 0) {
                        for (var i = visibleItems.startOffset; i <= visibleItems.endOffset; i++) {
                            itemsInView.push(i);
                        }
                    }
                    return itemsInView;
                }



                $scope.gotoPage = function (page) {
                    // get total page
                    var totalPages = $scope.getTotalPages();
                    if (!page || page < totalPages || totalPages <= 1) {
                        return;
                    }

                    // get first rowOffset of the page
                    var newOffset = getFirstOffsetOfThePage(page);
                    if (newOffset > gridItems.length - 1 || newOffset < 0) {
                        return;
                    }

                    // select and scroll
                    selectItemByOffset(newOffset);
                }

                $scope.gotoNextPage = function () {
                    // get total page
                    var totalPages = $scope.getTotalPages();

                    // get current page
                    var currentPage = $scope.getCurrentPage();

                    // get next page
                    var nextPage = currentPage + 1;

                    if (nextPage > totalPages) {
                        return;
                    }

                    // get first rowOffset of the page
                    var newOffset = getFirstOffsetOfThePage(nextPage);

                    if (newOffset >= gridItems.length || newOffset < 0) {
                        return;
                    }

                    // select and scroll
                    selectItemByOffset(newOffset);
                }

                $scope.gotoPreviousPage = function () {
                    // get current page
                    var currentPage = $scope.getCurrentPage();

                    // get next page
                    var nextPage = currentPage - 1;
                    if (nextPage < 1) {
                        return;
                    }

                    // get first rowOffset of the page
                    var newOffset = getFirstOffsetOfThePage(nextPage);
                    if (newOffset >= gridItems.length || newOffset < 0) {
                        return;
                    }

                    // select and scroll
                    selectItemByOffset(newOffset);
                }

                $scope.getTotalPages = function () {
                    if (gridItems.length == 0) {
                        return 1;
                    }
                    var rows = Math.floor(gridItems.length / gridInfo.items.itemsPerRow);
                    var page = Math.floor(rows / gridInfo.viewport.rowCount);
                    return page + 1
                }

                $scope.getCurrentPage = function () {
                    if (gridInfo.position.itemOffset < 0) {
                        return 1;
                    }
                    var row = Math.floor(gridInfo.position.itemOffset / gridInfo.items.itemsPerRow);
                    var page = Math.floor(row / gridInfo.viewport.rowCount);
                    return page + 1;
                }

                function getFirstOffsetOfThePage(pageNumber) {
                    return (pageNumber - 1) * (gridInfo.items.itemsPerRow * gridInfo.viewport.rowCount);
                }

                function selectItemByOffset(offSet) {
                    $scope.removeHighlight(gridInfo.position.itemOffset);
                    gridInfo.position.itemOffset = offSet;
                    gridInfo.position.rowOffset = Math.ceil((offSet + 1) / gridInfo.items.itemsPerRow) - 1;
                    if (gridConfig.autoHighlight) {
                        $scope.updateHighlight(offSet);
                    }

                    // scroll to page
                    scrollToItem(offSet);
                }

                //Calculate which items are in the current scroll view
                function getItemsInScrollView(scrollOffset) {

                    //If not items, just return 0,0
                    if (!gridItems || gridItems.length === 0) {
                        return {
                            startOffset: -1,
                            endOffset: -1
                        }
                    }
                    var scrollTop = 0;
                    var scrollLeft = 0;
                    //Has a custom scroll offset been provided?
                    if (scrollOffset !== null && scrollOffset !== undefined) {
                        scrollLeft = scrollTop = scrollOffset | 0;
                    }
                    else {
                        scrollTop = carousel.scrollTop();
                        scrollLeft = carousel.scrollLeft();
                    }

                    var sItem = null;
                    var eItem = null;
                    var transcludedContainer = $($element).find(".ui-grid-transcluded-content-container");
                    var startY = transcludedContainer[0].offsetTop + transcludedContainer.height();
                    var startX = transcludedContainer[0].offsetLeft;

                    //Go through each item, and check if its in view.
                    for (var i = 0; i < gridItems.length; i++) {
                        var itemElement = carousel.find(".grid_offset_" + i);
                        var itemInView = false;
                        //Check if item is in view
                        if (gridInfo.scrollDirection == "horizontal") {
                            var elementLeft = (itemElement[0].offsetLeft - startX - scrollLeft) | 0;
                            if (elementLeft >= 0 && elementLeft + gridInfo.items.width <= gridInfo.viewport.width + 1) {
                                itemInView = true;
                            }
                        }
                        else {
                            var elementTop = itemElement[0].offsetTop - startY - scrollTop;
                            if (elementTop >= 0 && elementTop + gridInfo.items.height <= gridInfo.viewport.height + 1) {
                                itemInView = true;
                            }
                        }
                        if (itemInView) {
                            if (sItem === null) {
                                sItem = i;
                            }
                            else {
                                eItem = i;
                            }
                        }
                        else {
                            //If we have found both the start and enditem
                            if (sItem !== null && eItem !== null) {
                                break;
                            }
                        }
                    }
                    return {
                        startOffset: sItem,
                        endOffset: eItem
                    };
                }

                //Based on the current scroll offset, this method updates the selected items column and
                //row offset relative to the viewport
                function updateColRowOffset() {

                    if (!gridItems || gridItems.length === 0) {
                        gridInfo.position.rowOffset = 0;
                        gridInfo.position.colOffset = 0;
                        return;
                    }

                    var itemOffset = gridInfo.position.itemOffset;
                    if (itemOffset < 0) {
                        itemOffset = 0;
                    }
                    else if (itemOffset >= gridItems.length) {
                        itemOffset = gridItems.length - 1;
                    }
                    //Determine the current item Offset
                    var itemsInView = $scope.getItemsInView(gridInfo.position.scrollOffset);
                    var rowOffset = 0;
                    var colOffset = 0;
                    //Determine if the requested offset is in view
                    for (var i = 0; i < itemsInView.length; i++) {
                        var itemOffset = itemsInView[i];
                        if (gridInfo.position.itemOffset == itemOffset) {
                            //No need to move, just return
                            gridInfo.position.rowOffset = rowOffset;
                            gridInfo.position.colOffset = colOffset;
                            break;
                        }
                        if (gridInfo.scrollDirection == "horizontal") {
                            rowOffset++;
                            if (rowOffset >= gridInfo.items.itemsPerRow) {
                                colOffset++;
                                rowOffset = 0;
                            }
                        }
                        else {
                            colOffset++;
                            if (colOffset >= gridInfo.items.itemsPerRow) {
                                colOffset = 0;
                                rowOffset++;
                            }
                        }
                    }
                }

                //Scroll the view so that the item is visible
                function scrollToItem(itemOffset) {

                    if (!gridItems || gridItems.length === 0) {
                        return;
                    }

                    if (itemOffset < 0) {
                        itemOffset = 0;
                    }
                    else if (itemOffset >= gridItems.length) {
                        itemOffset = gridItems.length - 1;
                    }

                    gridInfo.position.itemOffset = itemOffset;

                    //Check is item already in view
                    var range = getItemsInScrollView();

                    //Check if item is already in view
                    if (gridConfig.scrollAlignment != "item" && itemOffset >= range.startOffset && itemOffset <= range.endOffset) {
                        //Item is in view
                        updateColRowOffset();
                        return;
                    }
                    else {
                        //Item not in view
                        if (gridConfig.scrollAlignment == "page") { //If we align to page, need to scroll to item at begining of each page
                            //Determine which page the item is on and scroll to it
                            var page = Math.floor((itemOffset / gridInfo.items.itemsPerRow) / gridInfo.viewport.rowCount);
                            itemOffset = page * (gridInfo.items.itemsPerRow * gridInfo.viewport.rowCount);
                        }
                    }

                    updateScrollPosition(itemOffset);
                    updateColRowOffset();
                }
                function updateScrollPosition(itemOffset, duration) {
                    if (duration == null) {
                        duration = 400;
                    }
                    if (itemOffset < 0) {
                        itemOffset = 0;
                    }

                    if (!gridItems || gridItems.length === 0) {
                        return;
                    }

                    //Determine which row the item is on, and then is scroll offset
                    var itemRow = Math.floor(itemOffset / gridInfo.items.itemsPerRow);

                    //Adjust vertical scroll offset by transcluded content height
                    var scrollOffset = 0;
                    if (itemRow == 0) {
                        scrollOffset += $($element).find(".ui-grid-transcluded-content-container").height();
                    }
                    //Else simply scroll to the item
                    var elementToScrollTo = carousel.find(".grid_offset_" + itemOffset);

                    //Cancel previous scroll animation
                    elementToScrollTo.velocity("stop"); // Normal stop

                    //Scroll based on direction
                    if (gridInfo.scrollDirection == "horizontal") {
                        //Offset by left padding
                        scrollOffset = gridConfig.padding.left;
                        //Update scroll Offset
                        gridInfo.position.scrollOffset = elementToScrollTo[0].offsetLeft - scrollOffset;
                        var maxScroll = carousel[0].scrollWidth - carousel.outerWidth();
                        if (gridInfo.position.scrollOffset > maxScroll) {
                            gridInfo.position.scrollOffset = maxScroll;
                        }
                        //Use velocity to scroll to the element if there is a duration
                        if (duration > 0) {
                            elementToScrollTo.velocity("scroll", { axis: "x", container: $(carousel), offset: -scrollOffset });
                        }
                        else {
                            carousel.scrollLeft(gridInfo.position.scrollOffset - scrollOffset);
                        }
                    } else {
                        //Offset needs to include top padding
                        scrollOffset += gridConfig.padding.top;
                        //Update scroll Offset
                        gridInfo.position.scrollOffset = elementToScrollTo[0].offsetTop - scrollOffset;
                        var maxScroll = carousel[0].scrollHeight - carousel.outerHeight();
                        if (gridInfo.position.scrollOffset > maxScroll) {
                            gridInfo.position.scrollOffset = maxScroll;
                        }
                        //Use velocity to scroll to the element if there is a duration
                        if (duration > 0) {
                            elementToScrollTo.velocity("scroll", { container: $(carousel), offset: -scrollOffset });
                        }
                        else {
                            carousel.scrollTop(gridInfo.position.scrollOffset - scrollOffset);
                        }
                    }
                }

                $scope.getItems = function () {
                    return gridItems;
                }

                $scope.getCurrentItemOffset = function () {
                    return gridInfo.position.itemOffset;
                }

                $scope.getCurrentItemColumnOffset = function (absoluteOffset) {
                    if (gridInfo.scrollDirection == "horizontal") {
                        if (absoluteOffset) {
                            return Math.floor(gridInfo.position.itemOffset / gridInfo.items.itemsPerRow);
                        }
                    }
                    return gridInfo.position.colOffset;
                }

                $scope.getCurrentItemRowOffset = function (absoluteOffset) {
                    if (gridInfo.scrollDirection == "vertical") {
                        if (absoluteOffset) {
                            return Math.floor(gridInfo.position.itemOffset / gridInfo.items.itemsPerRow);
                        }
                    }
                    return gridInfo.position.rowOffset;
                }

                //Adds a single item to the carousel
                $scope.addItem = function (content) {
                    $scope.addItems([content]);
                }

                //Adds a collection of items to the carousel
                $scope.addItems = function (contentItems) {
                    //Add items to our internal collection
                    gridItems = gridItems.concat(contentItems);
                    for (var i = 0; i < gridItems.length; i++) {
                        addContentItem(gridItems[i]);
                    }
                    $scope.initGrid();
                }

                //Clears items in the grid
                $scope.clearItems = function (clearItems) {
                    gridItems = [];
                    var elements = carousel.find("> .ui-grid-item");
                    for (var i = 0; i < elements.length; i++) {
                        elements[i].remove();
                    }
                    $scope.initGrid();
                }

                //Move to the next item
                $scope.nextItem = function () {
                    var newPos = gridInfo.position.itemOffset;
                    if (newPos == -1) {
                        newPos = 0;
                    }
                    else {
                        newPos++;
                        if (newPos > gridItems.length - 1) {
                            return;
                        }
                    }
                    console.log(newPos);
                    $scope.removeHighlight(gridInfo.position.itemOffset);
                    gridInfo.position.itemOffset = newPos;
                    gridInfo.position.rowOffset = Math.ceil((newPos + 1) / gridInfo.items.itemsPerRow) - 1;
                    if (gridConfig.autoHighlight) {
                        $scope.updateHighlight(newPos);
                    }
                    scrollToItem(newPos);
                }

                //Move to the previous item
                $scope.previousItem = function () {
                    var newPos = gridInfo.position.itemOffset;
                    if (newPos == -1) {
                        newPos = 0;
                    }
                    else {
                        newPos--;
                        if (newPos < 0) {
                            return;
                        }
                    }
                    $scope.removeHighlight(gridInfo.position.itemOffset);
                    gridInfo.position.itemOffset = newPos;
                    gridInfo.position.rowOffset = Math.ceil((newPos + 1) / gridInfo.items.itemsPerRow) - 1;
                    if (gridConfig.autoHighlight) {
                        $scope.updateHighlight(newPos);
                    }

                    scrollToItem(newPos);
                }

                //Move to the item above the currenlty highlighted one
                $scope.upItem = function (isRedirect) {
                    var newPos = gridInfo.position.itemOffset;
                    if (newPos == -1) {
                        newPos = 0;
                    }
                    else {
                        //Move to the item below the current
                        if (gridInfo.scrollDirection == "horizontal") {
                            //Check is there a row below us
                            if (gridInfo.position.rowOffset > 0) {
                                newPos--;
                            }
                            else {
                                return;
                            }
                        }
                        else {
                            //Check is there a row below us
                            newPos -= gridInfo.items.itemsPerRow;
                        }
                    }
                    if (newPos < 0) {
                        return;
                    }

                    $scope.removeHighlight(gridInfo.position.itemOffset);
                    gridInfo.position.itemOffset = newPos;
                    gridInfo.position.rowOffset = Math.ceil((newPos + 1) / gridInfo.items.itemsPerRow) - 1;
                    if (gridConfig.autoHighlight) {
                        $scope.updateHighlight(newPos);
                    }
                    scrollToItem(newPos);
                }

                //Move to the item below the currenlty highlighted one
                $scope.downItem = function (isRedirect) {
                    var newPos = gridInfo.position.itemOffset;
                    if (newPos == -1) {
                        newPos = 0;
                    }
                    else {
                        //Move to the item below the current
                        if (gridInfo.scrollDirection == "horizontal") {
                            //Check is there a row below us
                            if (gridInfo.position.rowOffset + 1 < gridInfo.items.itemsPerRow) {
                                newPos++;
                            }
                            else {
                                return;
                            }
                        }
                        else {
                            //Check is there a row below us
                            newPos += gridInfo.items.itemsPerRow;
                        }
                    }

                    if (newPos >= gridItems.length) {
                        newPos = gridItems.length - 1;
                        if (Math.floor(gridInfo.position.itemOffset / gridInfo.items.itemsPerRow) == Math.floor(newPos / gridInfo.items.itemsPerRow)) {
                            //On last row already
                            return;
                        }
                        newPos = gridItems.length - 1;
                    }

                    $scope.removeHighlight(gridInfo.position.itemOffset);
                    gridInfo.position.itemOffset = newPos;
                    gridInfo.position.rowOffset = Math.ceil((newPos + 1) / gridInfo.items.itemsPerRow) - 1;
                    if (gridConfig.autoHighlight) {
                        $scope.updateHighlight(newPos);
                    }
                    scrollToItem(newPos);
                }

                //Move to the item to the right of the currenlty highlighted one
                $scope.rightItem = function (isRedirect) {
                    var newPos = gridInfo.position.itemOffset;
                    if (newPos == -1) {
                        newPos = 0;
                    }
                    else {
                        //Move to the item below the current
                        if (gridInfo.scrollDirection == "horizontal") {
                            newPos += gridInfo.items.itemsPerRow;
                        }
                        else {
                            newPos++;

                            if (gridInfo.position.colOffset >= gridInfo.items.itemsPerRow - 1) {
                                return;
                            }
                        }
                    }
                    if (newPos >= gridItems.length) {
                        newPos = gridItems.length - 1;
                        if (Math.floor(gridInfo.position.itemOffset / gridInfo.items.itemsPerRow) == Math.floor(newPos / gridInfo.items.itemsPerRow)) {
                            //On last row already
                            return;
                        }
                    }

                    $scope.removeHighlight(gridInfo.position.itemOffset);
                    gridInfo.position.itemOffset = newPos;
                    gridInfo.position.rowOffset = Math.ceil((newPos + 1) / gridInfo.items.itemsPerRow) - 1;
                    if (gridConfig.autoHighlight) {
                        $scope.updateHighlight(newPos);
                    }
                    scrollToItem(newPos);
                }

                //Move to the item to the left of the currenlty highlighted one
                $scope.leftItem = function (isRedirect) {
                    var newPos = gridInfo.position.itemOffset;
                    if (newPos == -1) {
                        newPos = 0;
                    }
                    else {
                        //Move to the item below the current
                        if (gridInfo.scrollDirection == "horizontal") {
                            newPos -= gridInfo.items.itemsPerRow;
                        }
                        else {
                            newPos--;
                            if (gridInfo.position.colOffset == 0) {
                                return;
                            }
                        }
                    }
                    if (newPos < 0) {
                        return;
                    }
                    $scope.removeHighlight(gridInfo.position.itemOffset);
                    gridInfo.position.itemOffset = newPos;
                    gridInfo.position.rowOffset = Math.ceil((newPos + 1) / gridInfo.items.itemsPerRow) - 1;
                    if (gridConfig.autoHighlight) {
                        $scope.updateHighlight(newPos);
                    }
                    scrollToItem(newPos);
                }



                $scope.moveToItem = function (offset, moveWithoutScroll) {
                    if (offset < 0 || offset >= gridItems.length) {
                        return;
                    }
                    //If we are already on the item, return
                    if (offset == gridInfo.position.itemOffset) {
                        return;
                    }
                    $scope.removeHighlight(gridInfo.position.itemOffset);
                    gridInfo.position.itemOffset = offset;
                    gridInfo.position.rowOffset = Math.ceil((offset + 1) / gridInfo.items.itemsPerRow) - 1;
                    if (gridConfig.autoHighlight) {
                        $scope.updateHighlight(gridInfo.position.itemOffset);
                    }
                    if (!moveWithoutScroll) {
                        scrollToItem(gridInfo.position.itemOffset);
                    }
                }

            }]
        };

    }]);

})(window.angular);
(function ($angular) {

    "use strict";

    var app = $angular.module('ui');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('asUiKeyboard', ["halPlatformService", function (halPlatformService) {

        return {
            restrict: "A",
            scope: {
                autoShowBasedOnDevice: '@',
                maxInput: '@',
                layout: '@',
                onKeyboardShow: '&',
                onKeyboardHide: '&',
                onKeyboardAccept: '&',
            },

            link: function (scope, element, attrs) {

                //console.log("keyboard directive link");
                if (!attrs.layout) {
                    scope.layout = "qwerty";
                }

                scope.$on('$destroy', function() {
                    scope.destroy();
                });
                element.on('$destroy', function() {
                    //console.log("asUiKeyboard element destroy");
                    scope.destroy();
                });

                //Check if should show only if the device requires it
                if('autoShowBasedOnDevice' in attrs) {
                    var ks = halPlatformService.getFeatureSupport("input.keyboard");
                    if(ks == null || ks.toLowerCase() == "none") {
                        scope.initKeypad();
                    }
                }
                else {
                    //Just show
                    scope.initKeypad();
                }
            },

            controller: ['$scope', '$element', '$q', '$interval', function ($scope, $element, $q, $interval) {

                $scope.destroy = function() {
                    $element.empty();
                }

                function getIPadLayout() {
                    return {
                        'default': [
                            'q w e r t y u i o p {bksp}',
                            'a s d f g h j k l {enter}',
                            '{s} z x c v b n m , . {s}',
                            '{meta1} {space} {meta1} {accept}'
                        ],
                        'shift': [
                            'Q W E R T Y U I O P {bksp}',
                            'A S D F G H J K L {enter}',
                            '{s} Z X C V B N M ! ? {s}',
                            '{meta1} {space} {meta1} {accept}'
                        ],
                        'meta1': [
                            '1 2 3 4 5 6 7 8 9 0 {bksp}',
                            '- / : ; ( ) \u20ac & @ {enter}',
                            '{meta2} . , ? ! \' " {meta2}',
                            '{default} {space} {default} {accept}'
                        ],
                        'meta2': [
                            '[ ] { } # % ^ * + = {bksp}',
                            '_ \\ | ~ < > $ \u00a3 \u00a5 {enter}',
                            '{meta1} . , ? ! \' " {meta1}',
                            '{default} {space} {default} {accept}'
                        ]
                    };
                }

                if($scope.layout == "ipad") {
                   $scope.layout = "custom";
                   $scope.customLayout =  getIPadLayout();
                   $scope.display = {
                            'bksp'   :  "\u2190",
                            'accept' : 'return',
                            'default': 'ABC',
                            'meta1'  : '.?123',
                            'meta2'  : '#+='
                        }
                }

                $scope.initKeypad = function () {
                    $($element[0]).keyboard({
                        // set this to ISO 639-1 language code to override language set by the layout
                        // http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
                        // language defaults to "en" if not found
                        language     : null, // string or array

                        // *** choose layout ***
                        layout       : $scope.layout,
                        customLayout : $scope.customLayout,

                        display: $scope.display,

                        position : {
                            of : null,        // when null, element will default to kb.$keyboard
                            my : 'right top', // 'center top', (position under keyboard)
                            at : 'left top',  // 'center bottom',
                            collision: 'flip'
                        },

                        css: {
                            // input & preview
                            // "label-default" for a darker background
                            // "light" for white text
                            input: 'form-control',
                            // keyboard container
                            container: 'center-block well',
                            // default state
                            buttonDefault: 'btn btn-default',
                            // hovered button
                            buttonHover: 'btn-primary',
                            // Action keys (e.g. Accept, Cancel, Tab, etc);
                            // this replaces "actionClass" option
                            buttonAction: 'active',
                            // used when disabling the decimal button {dec}
                            // when a decimal exists in the input area
                            buttonDisabled: 'disabled'
                        },
                        initialized   : function(e, keyboard, el) { },
                        beforeVisible : function(e, keyboard, el) {},
                        visible       : function(e, keyboard, el) {
                            if($scope.onKeyboardShow) {
                                $scope.onKeyboardShow();
                            }
                        },
                        change        : function(e, keyboard, el) {},
                        beforeClose   : function(e, keyboard, el, accepted) {},
                        accepted      : function(e, keyboard, el)  {
                            if($scope.onKeyboardAccept) {
                                $scope.onKeyboardAccept();
                            }
                        },
                        canceled      : function(e, keyboard, el) {},
                        restricted    : function(e, keyboard, el) {},
                        hidden        : function(e, keyboard, el)  {
                            if($scope.onKeyboardHide) {
                                $scope.onKeyboardHide();
                            }
                        }
                    });
                }
            }]
        };

    }]);


})(window.angular);
(function($angular) {

    "use strict";

    var app = $angular.module('ui');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('asUiLogOverlay', function () {

    return {
        restrict: "EA",
        scope: {
            enableUpdates: '@'
        },

        templateUrl: '/modules/ui/directives/asLogOverlayTemplate.html',

        link: function (scope, element, attrs) {

            scope.$on('$destroy', function() {
                scope.destroy();
            });


           scope.$watch('enableUpdates', function(newVal) {
                if(!newVal) {
                    return;
                }
                scope.toggleUpdates(newVal);
            });

            //Construct the api interface
            scope.api = {
                enableUpdates: scope.toggleUpdates,

            };

            if( scope.onApiReady) {
                scope.onApiReady({api:scope.api});
            }
        },

        controller: ["$q", "$scope", "$element", "$document", "logService", function($q, $scope, $element, $document, logService) {
            $scope.LOGLEVEL = [{text: 'Fatal', value: 'fatal'},
                               {text: 'Error', value: 'error'},
                               {text: 'Warn', value: 'warn'},
                               {text: 'Info', value: 'info'},
                               {text: 'Debug', value: 'debug'},
                               {text: 'Trace', value: 'trace'},
                               {text: 'Default', value: null}];

            $scope.DEFAULTLOGLEVEL = [{text: 'Fatal', value: 'fatal'},
                               {text: 'Error', value: 'error'},
                               {text: 'Warn', value: 'warn'},
                               {text: 'Info', value: 'info'},
                               {text: 'Debug', value: 'debug'},
                               {text: 'Trace', value: 'trace'}];

            $scope.selectedLoggers = '';

            $scope.viewFatal = '';
            $scope.viewError = '';
            $scope.viewWarn = '';
            $scope.viewInfo = '';
            $scope.viewDebug = '';
            $scope.viewTrace = '';
            $scope.defaultLogLevel = {text: 'Info', value: 'info'};
            $scope.selectedLevels = [];

            var leRunningCount = 0;

            //Init log
            init();

            //show or hide directive
            $scope.toggleUpdates = function (newVal) {
                if(newVal == "true" || newVal == true) {
                    //get loggers from the logservice
                    $scope.loggers = getLoggers();

                    logService.unsubscribe(onLogEvent);
                    //Subscribe to log event
                    logService.subscribe(onLogEvent);

                    //get logs from the log service
                    getLogs().then(function(logs){
                        $scope.logs = logs;
                    });

                    logService.trace("appspace.core.ui", "LogOverlay updates enabled");
                }
                else {
                    //Unsubscribe to log event
                    logService.unsubscribe(onLogEvent);
                    //Clear UI of logs
                    $scope.logs = [];
                    $scope.loggers = [];
                    logService.trace("appspace.core.ui", "LogOverlay updates disabled");
                }
            };

            $scope.destroy = function(){
                $scope.logs = [];
                $scope.loggers = [];
            }

            $scope.levelFilter = function (item) {
                if ($scope.selectedLevels.length == 0) {
                    return true;
                }
                return ($scope.selectedLevels.indexOf(item.logLevel) != -1);
            };

            $scope.reloadLogs = function() {
                $scope.logs = getLogs();
            };

            $scope.enableLogger = function(selectedLogger) {
                //Turn logger on of off
                selectedLogger.enabled = !selectedLogger.enabled;
                selectedLogger.logLevel = selectedLogger.enabled ? 'info' : 'none';
                $scope.updateLoggerConfiguration(selectedLogger);
            }

            $scope.updateLoggerLevel = function(selectedLogger) {
                //update logger level
                console.log(selectedLogger);
                $scope.updateLoggerConfiguration(selectedLogger);
            }

            $scope.updateDefaultLoggerLevel = function(logLevel) {
                $scope.defaultLogLevel = logLevel;
                //console.log($scope.defaultLogLevel);
                $scope.updateLoggerConfiguration();
            }

            $scope.updateLoggerConfiguration = function(selectedLogger) {
                var config = logService.getConfiguration();
                if (config !== undefined && config.loggers !== undefined) {
                    if(selectedLogger) {
                        for (var i = 0; i < config.loggers.length; i++) {
                            var l = config.loggers[i];
                            if(l.name == selectedLogger.name) {
                                l.logLevel = selectedLogger.logLevel;
                                l.enabled = selectedLogger.enabled;
                                break;
                            }
                        }
                    }
                    config.logLevel = $scope.defaultLogLevel.value;
                }

                //console.log(config);
                logService.setConfiguration(config);
                config = logService.getConfiguration();
                //console.log(config);
            }

            $scope.clearLog = function() {
                logService.clearHistory();
                $scope.logs = [];
            }

            function init() {
                logService.trace("appspace.signs.app", "LogOverlay directive initialized");
            }

            function getLogLevelObject(level) {
                for(var i = 0; i < $scope.DEFAULTLOGLEVEL.length; i++) {
                    var l = $scope.DEFAULTLOGLEVEL[i];
                    if(level == l.value) {
                        return l;
                    }
                }
                return null;
            }

            function getLoggers() {
                var loggers = []
                var config = logService.getConfiguration();

                if (config !== undefined && config.loggers !== undefined) {
                    $scope.defaultLogLevel = getLogLevelObject(config.logLevel);
                    for (var i = 0; i < config.loggers.length; i++) {
                        var l = config.loggers[i];
                        //create logger
                        var logger = {
                            name: l.name,
                            logLevel: 'none',
                            enabled: true
                        };
                        //set logLevel to the root value if not specified
                        if (l.config === undefined || l.logLevel === undefined) {
                            logger.logLevel = null;
                        }
                        //if logLevel is 'none', logger is disabled
                        else if (l.logLevel === 'none') {
                            logger.enabled = false;
                        }
                        else {
                            logger.logLevel = l.logLevel;
                        }

                        //add to loggers
                        loggers.push(logger);
                    }
                }

                return loggers;
            }

            function getLogs() {
                var defer = $q.defer();
                // var history = logService.getHistory();

                logService.getHistory().then(function(history){
                    var logs = [];
                    for (var i = 0; i < history.length; i++) {
                        var le = history[i];

                        var log = convertLogEntry(le);
                        if (log){
                            logs.push(log);
                        }
                    }
                    defer.resolve(logs);
                });

                return defer.promise;
            }

            function convertLogEntry(le) {

                if (le == undefined) {
                    return;
                }

                if (le.messages === undefined) {
                    le.messages = [];
                }

                //Convert message into an array if only one item
				if(!(le.messages instanceof Array)) {
					le.messages = [le.messages];
				}

                //create log entry
                var item = {
                    id: leRunningCount++,
                    createdAt: moment(le.createdAt).format("YYYY-MM-DD hh:mm:ss a"),
                    logger: le.logger,
                    logLevel: le.logLevel,
                    message: le.messages.join(' ')
                };


                //return item only if logger is enabled
                var res = $scope.loggers.filter( function(logger){return (logger.name===item.logger.name);} );
                if (res.length > 0 && res[0].enabled){
                    return item;
                }
                return null;
            }

            function onLogEvent(le) {
                var log = convertLogEntry(le);
                if (log) {
                    $scope.logs.push(log);
                }
            }
        }],
    };

});


})(window.angular);
(function($angular) {
    'use strict';
    var app = $angular.module('ui');
    app.component('asMessageBox', {
        template: '<div style="background-color:{{$ctrl.backgroundColor}}">{{$ctrl.message}}</div>',
        bindings: {
            backgroundColor: '=',
            message: '='
        },
        controller: function() {
            var $ctrl = this;
        }
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('ui');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('asUiRssItem', function() {
        return {
            restrict: 'EA',
            scope: {
                contentItem: '=',
                defaultLayout: '@',
                titleStyle: '@',
                descriptionStyle: '@',
                dateStyle: '@',
                mediaStretch: '@',
                defaultDateFormat: '@',
                mediaSize: '@',
                mediaPosition: '@',
                mediaBorder: '@'
            },
            template: '<div style="width:100%;height:100%;overflow:hidden;"></div>',
            link: function(scope, element, attrs) {
                if (!attrs.mediaBorder) {
                    scope.mediaBorder = '0px solid red';
                }

                if (!attrs.mediaStretch) {
                    scope.mediaStretch = 'uniform';
                }

                if (!attrs.defaultLayout) {
                    scope.defaultLayout = 'date,title,text';
                }

                if (!attrs.defaultDateFormat) {
                    scope.defaultDateFormat = 'MM/DD/YYYY HH:mm';
                }

                if (!attrs.mediaPosition) {
                    scope.mediaPosition = 'center';
                }

                scope.$on('$destroy', function() {
                    scope.destroy();
                });

                element.on('$destroy', function() {
                    scope.destroy();
                });

                scope.$watchGroup(['defaultLayout', 'mediaPosition', 'mediaStretch', 'mediaSize', 'mediaBorder', 'dateStyle', 'titleStyle', 'descriptionStyle'], function() {
                    scope.onConfigurationChanges();
                });

                scope.$watch('contentItem', function(newVal) {
                    if (!newVal) {
                        return;
                    }
                    return scope.setContentItem(newVal);
                });

                scope.api = {
                    setContentItem: scope.setContentItem
                };


                if (scope.onApiReady) {
                    scope.onApiReady({api: scope.api});
                }
            },
            controller: ["$scope", "$compile", "$element", function($scope, $compile, $element) {
                    $scope.setContentItem = setContentItem;
                    $scope.onConfigurationChanges = onConfigurationChanges;
                    $scope.currentItem;
                    $scope.dateFormat;
                    var dateAPI;
                    var mediaAPI;
                    var titleAPI;
                    var descAPI;
                    var currentElement;
                    var current = {
                        defaultLayout: undefined,
                        mediaPosition: undefined,
                        mediaStretch: undefined,
                        mediaSize: undefined,
                        mediaBorder: undefined,
                        dateStyle: undefined,
                        titleStyle: undefined,
                        descriptionStyle: undefined
                    };

                    function setContentItem(contentItem) {
                        $scope.currentItem = contentItem;
                        render();
                    }

                    $scope.destroy = function() {
                        removePreviousDisplay();
                    };

                    function onConfigurationChanges() {
                        var isRenderRequired;
                        for (var key in current) {
                            if (current[key] === $scope[key]) {
                                continue;
                            }
                            isRenderRequired = true;
                            current[key] = $scope[key];
                        }
                        if (!isRenderRequired) {
                            return;
                        }
                        render();
                    }

                    function render() {
                        removePreviousDisplay();
                        var template = createTemplate();
                        var isolatedScope = $scope.$new();
                        currentElement = $compile(template)(isolatedScope);
                        currentElement.scope = isolatedScope;
                        $($element.children()[0]).append(currentElement);
                    }

                    function removePreviousDisplay() {
                        if (dateAPI && dateAPI.destroy) {
                            dateAPI.destroy();
                            dateAPI = null;
                        }
                        if (mediaAPI && mediaAPI.destroy) {
                            mediaAPI.destroy();
                            mediaAPI = null;
                        }
                        if (titleAPI && titleAPI.destroy) {
                            titleAPI.destroy();
                            titleAPI = null;
                        }
                        if (descAPI && descAPI.destroy) {
                            descAPI.destroy();
                            descAPI = null;
                        }
                        if (currentElement) {
                            if (currentElement.scope) {
                                currentElement.scope.$destroy();
                                currentElement.scope = null;
                            }
                            currentElement.remove();
                        }
                        dateAPI = null;
                        mediaAPI = null;
                        titleAPI = null;
                        descAPI = null;
                        currentElement = null;
                    }

                    $scope.onItemAPIReady = function(api, type) {
                        switch (type) {
                            case 'date':
                                dateAPI = api;
                                break;
                            case 'media':
                                mediaAPI = api;
                                break;
                            case 'title':
                                titleAPI = api;
                                break;
                            case 'description':
                                descAPI = api;
                                break;
                        }
                    };

                    function createTemplate() {
                        if (!$scope.currentItem) {
                            return;
                        }
                        var template = '';
                        var mediaSize = parseInt($scope.mediaSize, 10);
                        var hasMedia = ((current.defaultLayout.toLowerCase().indexOf('media') >= 0) && ($scope.currentItem.media && $scope.currentItem.media != ''));
                        var baseTextStyle = '';
                        var layoutOrder = current.defaultLayout.split(',');
                        var mediaIndex = layoutOrder.indexOf('media');
                        if (hasMedia && $scope.mediaPosition != 'center' && mediaIndex > -1) {
                            baseTextStyle += ';width:' + (100 - mediaSize) + '%';
                            // remove media from the layout order
                            layoutOrder.splice(mediaIndex, 1);
                            // insert media back to 0 (first) in the layout order so that the media displays on the side on its own only
                            layoutOrder.splice(0, 0, 'media');
                            baseTextStyle += $scope.mediaPosition == 'left' ? ';float:right' : ';float:left';
                        }
                        var l = layoutOrder.length;
                        for (var i = 0; i < l; i++) {
                            var currentLayout = layoutOrder[i];
                            if (!currentLayout && currentLayout == '') {
                                continue;
                            }
                            switch (currentLayout.toLowerCase()) {
                                case 'date':
                                    var dateStyle = $scope.dateStyle;
                                    if ($scope.currentItem.dateStyle) {
                                        dateStyle = $scope.currentItem.dateStyle;
                                    }
                                    if (!dateStyle) {
                                        dateStyle = '';
                                    }
                                    var dateFormat = $scope.defaultDateFormat;
                                    if ($scope.currentItem.dateFormat) {
                                        dateFormat = $scope.currentItem.dateFormat;
                                    }
                                    dateStyle += baseTextStyle;
                                    template += '<div style="' + dateStyle + '">';
                                    template += '<as-ui-date-time date="currentItem.date" auto-update="false" format="' + dateFormat + '" fill-height="false" on-api-ready="onItemAPIReady(api,\'date\')"></as-ui-date-time>';
                                    template += '</div>';
                                    break;
                                case 'title':
                                    var titleStyle = $scope.titleStyle;
                                    if ($scope.currentItem.dateStyle) {
                                        titleStyle = $scope.currentItem.titleStyle;
                                    }
                                    if (!titleStyle) {
                                        titleStyle = '';
                                    }
                                    textStyle += 'word-wrap: break-word;';
                                    titleStyle += baseTextStyle;
                                    template += '<div style="' + titleStyle + '">';
                                    template += '<as-media-text encoded="true" use-non-breaking-space="true" text="{{currentItem.title}}" fill-height="false" on-api-ready="onItemAPIReady(api,\'title\')"></as-media-text>';
                                    template += '</div>';
                                    break;
                                case 'text':
                                    var textStyle = $scope.descriptionStyle;
                                    if ($scope.currentItem.dateStyle) {
                                        textStyle = $scope.currentItem.descriptionStyle;
                                    }
                                    if (!textStyle) {
                                        textStyle = '';
                                    }
                                    textStyle += 'word-wrap: break-word;';
                                    textStyle += baseTextStyle;
                                    template += '<div style="' + textStyle + '">';
                                    template += '<as-media-text encoded="true" use-non-breaking-space="true" text="{{currentItem.description}}" fill-height="false" on-api-ready="onItemAPIReady(api,\'description\')"></as-media-text>';
                                    template += '</div>';
                                    break;
                                case 'media':
                                    if (hasMedia) {
                                        var mediaStyle = '';

                                        if ($scope.mediaPosition == 'left') {
                                            mediaStyle = 'width:' + mediaSize + '%;height:100%;float:left;';
                                        } else if ($scope.mediaPosition == 'right') {
                                            mediaStyle = 'width:' + mediaSize + '%;height:100%;float:right;';
                                        } else {
                                            mediaStyle = 'height:' + mediaSize + '%;';
                                        }
                                        template += '<div style="' + mediaStyle + 'box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;border:' + $scope.mediaBorder + '">';
                                        template += '<div as-media-image style="height:100%;width:100%" url="' + $scope.currentItem.media + '" stretch="' + $scope.mediaStretch + '" on-api-ready="onItemAPIReady(api,\'media\')"></div>';
                                        template += '</div>';
                                    }
                                    break;
                            }
                        }

                        return template;
                    }
                }]
        };
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('ui');

    app.directive('asUiTicker', ["TimeoutUtil", function(TimeoutUtil) {
        return {
            restrict: 'EA',
            scope: {
                contentItem: '=',
                direction: '@', // 1 = right to left; 2 = left to right; 3 = bottom to top; 4 = top to bottom
                onApiReady: '&',
                onTickerCompleted: '&',
                autoStart: '@',
                spacing: '@',
                speed: '@' // 25 = slow; 100 = normal; 150 = fast
            },
            template: '<div style="width:100%;height:100%;overflow:hidden;">'
                + '</div>',
            link: function(scope, element, attrs) {
                if (!attrs.autoStart) {
                    scope.autoStart = 'true';
                    scope.autoPlay = true;
                }

                scope.direction = !attrs.direction ? '1' : scope.direction;
                scope.speed = !attrs.speed ? '100' : scope.speed;
                scope.spacing = !attrs.spacing ? 100 : scope.spacing;

                scope.$on('$destroy', function() {
                    scope.destroy();
                });
                element.on('$destroy', function() {
                    scope.destroy();
                });

                scope.$watch('autoStart', function(newVal) {
                    scope.autoPlay = newVal == 'true' || newVal == true;
                });

                scope.$watch('spacing', function(newVal) {
                    var intVal = parseInt(newVal, 10);
                    scope.spacing = isNaN(intVal) ? 0 : intVal;
                });

                scope.$watch('direction', function() {
                    scope.reRender();
                });

                scope.$watch('speed', function() {
                    scope.reRender();
                });

                scope.$watch('contentItem', function(newVal) {
                    scope.setContentItem(newVal);
                });

                scope.api = {
                    setContentItem: scope.setContentItem,
                    play: scope.play,
                    pause: scope.pause,
                    destroy: scope.destroy
                };

                if (scope.onApiReady) {
                    scope.onApiReady({api: scope.api});
                }
            },
            controller: ['$scope', '$compile', '$element', '$interval', '$timeout', 'LinearPlaylist',
                function($scope, $compile, $element, $interval, $timeout, LinearPlaylist) {
                    var playlist = new LinearPlaylist(false, false, false);
                    var isStarted = false;
                    var isPlaying = false;
                    var tickerStage = $element.children()[0];
                    var isCurrentElementFadingOut = false;
                    var currentFadeInFadeOutItemIndex = -1;
                    var displayItems = {};
                    var currentElement;
                    var loadingTemplate;
                    var lastTranslateX;
                    var lastTranslateY;
                    var lastTranslateZ;
                    var previousDirection;
                    var resetInitialPosition;
                    var renderNextItemTimeoutHandler;
                    var animateElementVelocityTimer;
                    var previousItems;
                    $scope.destroy = function() {
                        clearStage();
                        $element.empty();
                    };

                    $scope.setContentItem = function(items) {
                        if (!previousItems && !items) {
                            return;
                        }
                        previousItems = items || [];
                        // clear next render timeout
                        if (renderNextItemTimeoutHandler) {
                            $timeout.cancel(renderNextItemTimeoutHandler);
                            renderNextItemTimeoutHandler = undefined;
                        }

                        playlist.setItems(items);
                        resetInitialPosition = true;
                        if (!isStarted && !$scope.autoPlay) {
                            return;
                        }
                        isPlaying = true;
                        doRender();
                    };

                    $scope.play = function() {
                        isStarted = true;
                        isPlaying = true;
                        if (currentElement) {
                            animateElementVelocity(currentElement);
                            return;
                        }
                        doRender();
                    };

                    $scope.pause = function() {
                        isPlaying = false;
                        if (!currentElement) {
                            return;
                        }
                        undefineLastTranslateXYZ();
                        if (isDirectionTheSameAsPrevious()) {
                            lastTranslateX = $.Velocity.hook(currentElement, 'translateX');
                            lastTranslateY = $.Velocity.hook(currentElement, 'translateY');
                            lastTranslateZ = $.Velocity.hook(currentElement, 'translateZ');
                        }
                        $(currentElement[0]).velocity('stop');
                    };

                    $scope.onItemApiReady = function(api, id) {
                        var item = displayItems[id];
                        item.api = api;
                    };

                    $scope.reRender = function() {
                        if (!isPlaying) {
                            return;
                        }
                        doRender();
                    };

                    function doRender() {
                        loadingTemplate = createTemplate($scope.direction);
                        addLoadingElementToStage();
                    }

                    function undefineLastTranslateXYZ() {
                        lastTranslateX = undefined;
                        lastTranslateY = undefined;
                        lastTranslateZ = undefined;
                    }

                    function isDirectionTheSameAsPrevious() {
                        return previousDirection === $scope.direction;
                    }

                    function getPositionStyling(direction) {
                        var res = 'left:100%;height:100%;';
                        switch (direction) {
                            case '2': // left-to-right
                                res = 'left:-100%;height:100%';
                                break;
                            case '3': // bottom-to-top
                                res = 'top:100%;text-align:center;width:100%';
                                break;
                            case '4': // top-to-bottom
                                // top position of this direction will be modified by the animation
                                res = 'top:100%;text-align:center;width:100%';
                                break;
                            case '5':
                                res = '';
                                break;
                        }
                        return res;
                    }

                    function getItems(direction) {
                        var res = [];
                        var item;
                        playlist.currentOffset = null;
                        switch (direction) {
                            case '2': // left-to-right
                            case '4': // top-to-bottom
                                item = playlist.previous();
                                while (item) {
                                    res.push(item);
                                    item = playlist.previous();
                                }
                                break;
                            case '1': // right-to-left
                            case '3': // bottom-to-top
                            default:
                                item = playlist.next();
                                while (item) {
                                    res.push(item);
                                    item = playlist.next();
                                }
                                break;
                        }
                        return res;
                    }

                    function addLoadingElementToStage() {
                        if (isCurrentElementFadingOut) {
                            return;
                        }
                        if (!currentElement) {
                            switchElement();
                            return;
                        }
                        isCurrentElementFadingOut = true;
                        $(currentElement[0]).stop().velocity(
                            {
                                opacity: 0
                            },
                            {
                                duration: 200,
                                easing: 'linear',
                                complete: function() {
                                    undefineLastTranslateXYZ();
                                    if (isDirectionTheSameAsPrevious()) {
                                        lastTranslateX = $.Velocity.hook(currentElement, 'translateX');
                                        lastTranslateY = $.Velocity.hook(currentElement, 'translateY');
                                        lastTranslateZ = $.Velocity.hook(currentElement, 'translateZ');
                                    }
                                    isCurrentElementFadingOut = false;
                                    // Dont switch during a velocity handler
                                    $timeout(switchElement, 0);
                                }
                            }
                        );
                    }

                    function clearStage() {
                        if (currentElement) {
                            $(currentElement[0]).velocity('stop');
                            if (currentElement.scope) {
                                currentElement.scope.$destroy();
                                currentElement.scope = null;
                            }
                            currentElement.empty();
                            currentElement.remove();
                            currentElement = null;
                        }
                        $(tickerStage).empty();
                        isCurrentElementFadingOut = false;
                    }

                    function switchElement() {
                        // Clear the stage, so we can add the new element
                        clearStage();
                        // Create a new isolated scope, which we can destroy on unload
                        var newScope = $scope.$new();
                        currentElement = $compile(loadingTemplate)(newScope);
                        currentElement.scope = newScope;
                        loadingTemplate = null;
                        $(tickerStage).append(currentElement);
                        animateElementVelocity(currentElement);
                    }

                    function onAnimateCompleted(element) {
                        $scope.onTickerCompleted(element);
                        renderNextItemTimeoutHandler = $timeout(function() {
                            animateElementVelocity(element);
                        }, 10);
                    }

                    function animateElementVelocity(element) {
                        // don't animate if the element is not current
                        if (element != currentElement) {
                            return;
                        }

                        TimeoutUtil.cancel(animateElementVelocityTimer);
                        animateElementVelocityTimer = null;
                        // delay so that we will get the width of the appended element
                        animateElementVelocityTimer = $timeout(function() {
                            if (!element || !element[0]) {
                                return;
                            }
                            var width = element[0].offsetWidth + 10;
                            var containerWidth = tickerStage.offsetWidth;
                            width += containerWidth;
                            // inverse the percentage
                            var percentage = parseInt($scope.speed, 10) - 100;
                            // get the duration based on the width of the ticker
                            // assuming we need to move 100px/s
                            var duration = (width / 100) * 1;
                            duration *= 1000;
                            duration = Math.round(duration);
                            var maxDuration = duration * 10;
                            var minDuration = duration / 10;
                            // get the duration based on percentage
                            duration = duration - ((percentage / 100) * duration);
                            // failsafe - to ensure the ticker are not too slow or too fast
                            if (duration < minDuration) {
                                duration = minDuration;
                            } else if (duration > maxDuration) {
                                duration = maxDuration;
                            }
                            // lower duration means faster
                            if (resetInitialPosition) {
                                resetInitialPosition = false;
                                undefineLastTranslateXYZ();
                            }
                            previousDirection = $scope.direction;
                            switch ($scope.direction) {
                                case '2': // left-to-right
                                    animateLeftToRight(element, duration);
                                    break;
                                case '3': // bottom-to-top
                                    animateBottomToTop(element, duration);
                                    break;
                                case '4': // top-to-bottom
                                    animateTopToBottom(element, duration);
                                    break;
                                case '5': // fade-in-fade-out
                                    animateFadeInFadeOut(element, $scope.speed);
                                    break;
                                default: // right-to-left
                                    animateRightToLeft(element, duration);
                                    break;
                            }
                        }, 500);
                    }

                    function animateRightToLeft(element, duration) {
                        var width = element[0].offsetWidth + 10;
                        var containerWidth = tickerStage.offsetWidth;
                        element[0].style.left = containerWidth + 'px';
                        width += containerWidth;
                        var startPos = 0;
                        if (lastTranslateX) {
                            startPos = parseFloat(lastTranslateX);
                            var perc = 1 - (startPos / (-1 * width));
                            duration = duration * perc;
                        } else {
                            element[0].style.opacity = 1;
                        }
                        $(element[0]).velocity(
                            {
                                translateX: [width * -1, startPos],
                                translateZ: [0]
                            },
                            {
                                duration: duration,
                                easing: 'linear',
                                complete: function() {
                                    onAnimateCompleted(element);
                                },
                            }
                        );
                        if (lastTranslateX) {
                            $(element[0]).stop().velocity(
                                {
                                    opacity: [1, 0]
                                },
                                {
                                    duration: 500,
                                    easing: 'linear'
                                }
                            );
                        }
                        lastTranslateX = undefined;
                        lastTranslateZ = undefined;
                    }

                    function animateLeftToRight(element, duration) {
                        var width = element[0].offsetWidth + 10;
                        element[0].style.left = (width * -1) + 'px';
                        var containerWidth = tickerStage.offsetWidth;
                        width += containerWidth;
                        var startPos = 0;
                        if (lastTranslateX) {
                            startPos = parseFloat(lastTranslateX);
                            var perc = 1 - (startPos / width);
                            duration = duration * perc;
                        } else {
                            element[0].style.opacity = 1;
                        }
                        $(element[0]).velocity(
                            {
                                translateX: [width, startPos],
                                translateZ: [0]
                            },
                            {
                                duration: duration,
                                easing: 'linear',
                                complete: function() {
                                    onAnimateCompleted(element);
                                }
                            }
                        );
                        if (lastTranslateX) {
                            $(element[0]).stop().velocity(
                                {
                                    opacity: [1, 0]
                                },
                                {
                                    duration: 500,
                                    easing: 'linear'
                                }
                            );
                        }
                        lastTranslateX = undefined;
                        lastTranslateZ = undefined;
                    }

                    function animateBottomToTop(element, duration) {
                        var height = element[0].offsetHeight + 10;
                        var containerHeight = tickerStage.offsetHeight;
                        height += containerHeight;
                        var startPos = 0;
                        if (lastTranslateY) {
                            startPos = parseFloat(lastTranslateY);
                            var perc = 1 - (startPos / (-1 * height));
                            duration = duration * perc;
                        } else {
                            element[0].style.opacity = 1;
                        }
                        // speed adjusment
                        duration -= (duration * 0.7);
                        $(element[0]).velocity(
                            {
                                translateY: [height * -1, startPos],
                                translateZ: [0]
                            },
                            {
                                duration: duration,
                                easing: 'linear',
                                complete: function() {
                                    onAnimateCompleted(element);
                                }
                            }
                        );
                        if (lastTranslateY) {
                            $(element[0]).stop().velocity(
                                {
                                    opacity: [1, 0]
                                },
                                {
                                    duration: 500,
                                    easing: 'linear'
                                }
                            );
                        }
                        lastTranslateY = undefined;
                        lastTranslateZ = undefined;
                    }

                    function animateTopToBottom(element, duration) {
                        var height = element[0].offsetHeight + 10;
                        var containerHeight = tickerStage.offsetHeight;
                        height += containerHeight;
                        var startPos = height * -1;
                        if (lastTranslateY) {
                            startPos = parseFloat(lastTranslateY);
                            var perc = (-1 * startPos / height);
                            duration = duration * perc;
                        } else {
                            element[0].style.opacity = 1;
                        }
                        // speed adjusment
                        duration -= (duration * 0.7);
                        $(element[0]).velocity(
                            {
                                translateY: [0, startPos],
                                translateZ: [0]
                            },
                            {
                                duration: duration,
                                easing: 'linear',
                                complete: function() {
                                    onAnimateCompleted(element);
                                }
                            }
                        );
                        if (lastTranslateY) {
                            $(element[0]).stop().velocity(
                                {
                                    opacity: [1, 0]
                                },
                                {
                                    duration: 500,
                                    easing: 'linear'
                                }
                            );
                        }
                        lastTranslateY = undefined;
                        lastTranslateZ = undefined;
                    }

                    function animateFadeInFadeOut(element, speed) {
                        // stop
                        $(element[0]).velocity('stop');
                        var childElement;
                        // set previous item
                        if (currentFadeInFadeOutItemIndex >= 0) {
                            childElement = $(element.children()[currentFadeInFadeOutItemIndex])[0];
                            if (childElement && childElement.style) {
                                childElement.style.opacity = 0;
                                childElement.style.display = 'none';
                            }
                        }
                        // set next index
                        currentFadeInFadeOutItemIndex++;
                        currentFadeInFadeOutItemIndex = currentFadeInFadeOutItemIndex >= element.children().length ?
                            0 :
                            currentFadeInFadeOutItemIndex;
                        // set next item
                        childElement = $(element.children()[currentFadeInFadeOutItemIndex])[0];
                        if (childElement && childElement.style) {
                            childElement.style.opacity = 1;
                            childElement.style.display = 'inherit';
                        }
                        // set properties
                        speed = speed < 45 ? 45 : speed;
                        var delay = (20 / speed) * 1000 * 6;
                        // do transition
                        $(element[0])
                            // fade in
                            .velocity({opacity: 1}, {duration: 500, easing: 'linear'})
                            // fade out
                            .velocity({opacity: 0}, {
                                delay: delay, duration: 500, easing: 'linear', complete: function() {
                                    onAnimateCompleted(element);
                                }
                            });
                    }

                    function createTemplate(direction) {
                        return direction === '5' ? createTemplateFade(direction) : createTemplateScrolling(direction);
                    }

                    function createTemplateScrolling(direction) {
                        var positionStyling = getPositionStyling(direction);
                        var template = '<div style="white-space:nowrap;display:table;position:relative;' + positionStyling + ';background-color:;opacity:0;height:100%;width:100%;">';
                        var items = getItems(direction);
                        for (var i = 0; i < items.length; i++) {
                            var item = items[i];
                            var last = false;
                            if (i == (items.length - 1)) {
                                last = true;
                            }
                            template += createItemTemplateScrolling(item, direction, last, i);
                            item = playlist.next();
                        }
                        template += '</div>';
                        return template;
                    }

                    function createTemplateFade(direction) {
                        var template = '<div style="width:100%;height:100%;opacity:0;">';
                        var items = getItems(direction);
                        for (var i = 0; i < items.length; i++) {
                            var item = items[i];
                            var last = false;
                            if (i == (items.length - 1)) {
                                last = true;
                            }
                            template += createItemTemplateFade(item, direction, last, i);
                            item = playlist.next();
                        }
                        template += '</div>';
                        return template;
                    }

                    function createItemTemplateFade(item, direction, ignoreSpacing, index) {
                        // just to get the text center with 100% width
                        var template = '<div style="width:100%;height:100%;opacity:0;">';
                        template += '<div style="display:table;width:100%;height:100%">';
                        template += '<div style="display:table-row;width:100%;height:100%">';
                        template += '<div style="display:table-cell;vertical-align:middle;width:100%;height:100%;">';
                        template += item.data;
                        template += '</div>';
                        template += '</div>';
                        template += '</div>';
                        template += '</div>';
                        return template;
                    }

                    function createItemTemplateScrolling(item, direction, ignoreSpacing, index) {
                        var style = direction == '3' || direction == '4' ? // vertical ticker
                            'display:table-row;' :
                            'display:table-cell;'; // horizontal ticker
                        var template = '<div style="' + style + 'vertical-align:middle">';
                        switch (item.type.toLowerCase()) {
                            case 'video': // not supported yet
                            case 'image': // not supported yet
                            case 'markup':
                            default:
                                template += item.data;
                                break;
                        }
                        template += '</div>';
                        template = generateSpacing(template, direction, ignoreSpacing);
                        return template;
                    }

                    function generateSpacing(template, direction, ignoreSpacing) {
                        if (ignoreSpacing) {
                            return template;
                        }
                        switch (direction) {
                            case '5':
                                // do nothing, fadeinfadeout not need spacing between ticker item
                                break;
                            case '3': // bottom-to-top
                            case '4': // top-to-bottom
                                template += '<div style="display:table-row;height:' + $scope.spacing + 'px">&nbsp;</div>';
                                break;
                            case '1': // right-to-left
                            case '2': // left-to-right
                            default:
                                template += '<div style="display:inline-block;width:' + $scope.spacing + 'px">&nbsp;</div>';
                                break;
                        }
                        return template;
                    }
                }
            ]
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('ui');

    app.factory('InterleaveControl', ["INTERLEAVE_TYPE", "dataUtil", function(INTERLEAVE_TYPE, dataUtil) {
        function InterleaveControl(config) {
            this.contentCount = 1;
            this.playlistItemConfiguredContentLimit = [];
            this.currentIteration = [];
            this.maxIteration = 0;
            this.endedPlaylists = [];
            this.remainPlaylistslength = [];
            this.DEFAULT_CONTENT_COUNT = 1;
            this.interleaveConfiguration = config || {};
            this.configuredContentCount = this.interleaveConfiguration.options && this.interleaveConfiguration.options.contentCount ?
                this.interleaveConfiguration.options.contentCount : this.DEFAULT_CONTENT_COUNT;
            this.isEndPlaylistReset = false; // flag to reset each playlist
            this.isEndPlaylistRestart = false; // flag to restart interleave from 1st playlist
        }

        InterleaveControl.prototype.addEndedPlaylist = function(playlistOffset) {
            if (this.endedPlaylists.indexOf(playlistOffset) >= 0) {
                return;
            }

            this.endedPlaylists.push(playlistOffset);
        };

        InterleaveControl.prototype.removeEndedPlaylist = function(playlistOffset) {
            var playlistIndex = this.endedPlaylists.indexOf(playlistOffset);
            if (playlistIndex < 0) {
                return;
            }

            this.endedPlaylists.splice(playlistIndex, 1);
        };

        InterleaveControl.prototype.setMaxPlaylistIteration = function(playlists) {
            var playlistsItemsCount = playlists.map(function(playlist) {
                return playlist.items.length;
            }).filter(function(val) {
                return val !== 0;
            });
            
            // add empty array into ended playlist by default
            this.maxIteration = dataUtil.getArrayMinValue(playlistsItemsCount);
        };

        InterleaveControl.prototype.validateInterleaveSkipEnded = function(playlistOffset, playlistsLength) {
            if (!this.isEndPlaylistReset) {
                this.addEndedPlaylist(playlistOffset);
                if (this.endedPlaylists.length >= playlistsLength) {
                    this.isEndPlaylistReset = true;
                    this.isEndPlaylistRestart = true;
                }

                return false;
            }

            this.removeEndedPlaylist(playlistOffset);
            // check default empty playlist instead of empty endedPlaylist
            // reset playlist if all playlist are reset (excluding empty array)
            if (this.endedPlaylists.length <= 0) {
                this.isEndPlaylistReset = false;
            }

            return true;
        };

        InterleaveControl.prototype.allowResetForEndPlaylist = function() {
            return this.interleaveConfiguration.type != INTERLEAVE_TYPE.SKIP_ENDED || this.isEndPlaylistReset;
        };

        InterleaveControl.prototype.restartInterleaveSkipEnded = function() {
            var restartInterleave = this.isEndPlaylistReset && this.isEndPlaylistRestart;
            if (restartInterleave) {
                this.isEndPlaylistRestart = false;
            }

            return restartInterleave;
        };

        InterleaveControl.prototype.validateInterleaveSpaceEven = function(offsetInfo) {
            if (!offsetInfo || dataUtil.isNullOrUndefined(offsetInfo.playlistOffset) || dataUtil.isNullOrUndefined(offsetInfo.playlist.items)) {
                return true;
            }

            var playlistOffset = offsetInfo.playlistOffset;
            var remainingItemsLength = offsetInfo.playlist.items.length - (this.remainPlaylistslength[playlistOffset] || 0);

            if (!this.currentIteration[playlistOffset] || this.currentIteration[playlistOffset] >= this.maxIteration) {
                this.currentIteration[playlistOffset] = 0;
            }

            // always recount limit when playlistContentLimit is undefined
            if (!this.playlistItemConfiguredContentLimit[playlistOffset] || this.contentCount >= this.playlistItemConfiguredContentLimit[playlistOffset]) {
                this.playlistItemConfiguredContentLimit[playlistOffset] = Math.floor((remainingItemsLength || 1) / (this.maxIteration - this.currentIteration[playlistOffset]));
            }

            if (!this.remainPlaylistslength[playlistOffset] || this.remainPlaylistslength[playlistOffset] > offsetInfo.playlist.items.length) {
                this.remainPlaylistslength[playlistOffset] = 0;
            }

            this.remainPlaylistslength[playlistOffset]++;

            if (remainingItemsLength <= this.DEFAULT_CONTENT_COUNT) {
                this.remainPlaylistslength[playlistOffset] = 0;
            }

            if (this.contentCount < this.playlistItemConfiguredContentLimit[playlistOffset]) {
                this.contentCount++;
                return false;
            }

            this.playlistItemConfiguredContentLimit[playlistOffset] = undefined;
            this.contentCount = this.DEFAULT_CONTENT_COUNT;
            this.currentIteration[playlistOffset]++;
            return true;
        };

        InterleaveControl.prototype.validateDefaultInterleave = function() {
            if (this.contentCount < this.configuredContentCount) {
                this.contentCount++;
                return false;
            }

            this.contentCount = this.DEFAULT_CONTENT_COUNT;
            return true;
        };

        InterleaveControl.prototype.validateInterleave = function(offsetInfo) {
            switch (this.interleaveConfiguration.type) {
                case INTERLEAVE_TYPE.DEFAULT:
                    return this.validateDefaultInterleave();
                case INTERLEAVE_TYPE.SPACE_EVEN:
                    return this.validateInterleaveSpaceEven(offsetInfo);
                default: // any number other than 1 and 3 will be defaulted to this
                    return true;
            }
        };

        return (InterleaveControl);
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('ui');
    app.factory('LinearPlaylist', ["$http", "$q", "playlistSchedulerService", "TIME", function($http, $q, playlistSchedulerService, TIME) {
        var DEFAULT_OFFSET_INDEX = 0;
        function LinearPlaylist(paramAutoStart, paramLoop, paramRandom) {
            this.autoStart = (paramAutoStart) ? true : false;
            this.loop = (paramLoop) ? true : false;
            this.random = (paramRandom) ? true : false;
        }

        // Define the instance methods and properties
        LinearPlaylist.prototype = {
            getItemsPromise: null,
            currentItem: null,
            currentOffset: null,
            autoStart: false,
            loop: true,
            playlistEnd: false,
            random: true,
            items: [],
            playlistId: null,
            activeItems: [],
            lastActiveCheck: null,
            deterministicSchedule: {},
            deterministicScheduleAvgContentLoadTime: 0.5
        };

        LinearPlaylist.prototype.updateActiveItems = function(force) {
            var ctime = new Date().getMinutes();
            if (force == true) {
                // we always update
            } else {
                // Check have we updated this minute, if so dont update again
                if (ctime == this.lastActiveCheck) {
                    return;
                }
            }
            this.lastActiveCheck = ctime;
            this.activeItems = this.generateActiveItems(this.items);
            if (this.currentItem) {
                this.currentOffset = this.findCurrentOffesetByContentId(this.currentItem.contentId);
            }
        };

        LinearPlaylist.prototype.findCurrentOffesetByContentId = function(contentId) {
            if (!contentId) {
                return DEFAULT_OFFSET_INDEX;
            }

            for (var i = 0; i < this.activeItems.length; i++) {
                if (this.activeItems[i].contentId === contentId) {
                    return i;
                }
            }

            return DEFAULT_OFFSET_INDEX;
        }

        LinearPlaylist.prototype.generateDeterministicSchedule = function(date, defaultDuration, useExactDate, lookAheadTime) {
            // What date should we generate the schedule for
            var scheduleTime = date ? new Date(date) : new Date();
            // If undefined, default the limit look ahead to 24hours
            lookAheadTime = lookAheadTime || TIME.TWENTY_FOUR_HOURS_IN_MILLISECONDS;
            var endTime = new Date(scheduleTime.getTime() + lookAheadTime);

            // Assume start of the day if not using exact date
            if (!useExactDate) {
                scheduleTime.setHours(0, 0, 0, 0);
            }

            this.deterministicSchedule = {
                date: scheduleTime.getDate()
            };
            var day = scheduleTime.getDay();

            // Set the default duration for items that don't have a duration
            defaultDuration = defaultDuration || 12;

            // Reset current offset
            this.currentOffset = null;

            var playlistItem = null;
            var slotsCreated = 0;
            var lastMin = -1;
            var usedContents = [];

            // Generate the playlist items for the day, make sure we dont create more than 10k
            while (scheduleTime.getDay() == day && slotsCreated < 100000 && scheduleTime.getTime() < endTime.getTime()) {
                var scheduleTimestamp = scheduleTime.getTime();
                var duration = 12;
                var hour = scheduleTime.getHours();
                var min = scheduleTime.getMinutes();
                var sec = scheduleTime.getSeconds();

                // Force update the active items
                if (lastMin != min) {
                    lastMin = min;
                    this.updateActiveItems(true);
                }

                // Get the next item for this specific time, dont peek as we want to update the head
                playlistItem = this.next(false, new Date(scheduleTimestamp));
                playlistItem = angular.copy(playlistItem);
                // No more items
                if (playlistItem) {
                    // if an item have a uniqueId & we never record it before, record it so that we can clear it off after generating the markings
                    if (playlistItem.uniqueId && usedContents.indexOf(playlistItem.uniqueId) == -1) {
                        usedContents.push(playlistItem.uniqueId);
                    }
                    var playoutPropertiesDuration = playlistItem.playoutProperties ? (playlistItem.playoutProperties.duration || playlistItem.playoutProperties.fixedVideoDuration) : 0;
                    duration = playoutPropertiesDuration || playlistItem.duration || playlistItem.defaultDuration || duration;
                    duration = Math.floor(duration);
                    // If the item duration is less than 1 second, defaulted it to 12
                    duration = duration < 1 ? 12 : duration;
                }

                // Add some time for the average load time
                duration += this.deterministicScheduleAvgContentLoadTime;

                // If there is a playlist item, create an entry for it
                if (playlistItem) {
                    // Set the playlist item start and end time
                    playlistItem.$_startTime = new Date(scheduleTimestamp);
                    playlistItem.$_endTime = new Date(scheduleTimestamp);
                    playlistItem.$_endTime.setTime(scheduleTimestamp + (duration * 1000));

                    // Insert the current playlist item into our record for this timeslot
                    if (!this.deterministicSchedule[hour]) {
                        this.deterministicSchedule[hour] = {};
                    }
                    if (!this.deterministicSchedule[hour][min]) {
                        this.deterministicSchedule[hour][min] = {};
                    }
                    this.deterministicSchedule[hour][min][sec] = playlistItem;
                }
                // Increment our schedule by duration
                scheduleTime.setTime(scheduleTimestamp + (duration * 1000));
                slotsCreated++;
            }
            // we're done generating the markers, clear up the records on playlist scheduler service
            playlistSchedulerService.clearRecordItem(usedContents);
        };


        LinearPlaylist.prototype.getPlaylistItemByTime = function(specificDate) {
            if (!specificDate) {
                return null;
            }
            // Check do we have a schedule for this date
            if (!this.deterministicSchedule || this.deterministicSchedule.date != specificDate.getDate()) {
                // If not generate
                this.generateDeterministicSchedule(specificDate);
            }

            var hour = specificDate.getHours();
            var min = specificDate.getMinutes();
            var sec = specificDate.getSeconds();

            while (hour >= 0 && min >= 0 && sec >= 0) {
                var retry = false;
                // Scan for the closest hour slot that contains an entry
                while (!this.deterministicSchedule[hour]) {
                    // Check the previous hour
                    hour--;
                    min = 59;
                    sec = 59;
                    if (hour < 0) {
                        // Cant find any lookup entry
                        return null;
                    }
                }

                // Scan for the closest min slot that contains an entry
                while (!this.deterministicSchedule[hour][min]) {
                    // Check previous min
                    min--;
                    sec = 59;
                    if (min < 0) {
                        hour--;
                        min = 59;
                        sec = 59;
                        retry = true;
                        break;
                    }
                }

                if (retry) {
                    continue;
                }

                // Scan for the closest second slot that contains an entry
                while (!this.deterministicSchedule[hour][min][sec] && sec >= 0) {
                    // Check previous sec
                    sec--;
                    if (sec < 0) {
                        min--;
                        if (min < 0) { // If this is the first min of the hour, decrement the hour
                            min = 59;
                            hour--;
                        }
                        sec = 59;
                        retry = true;
                        break;
                    }
                }
                if (!retry) {
                    // Found an entry
                    break;
                }
            }

            var playlistItem = this.deterministicSchedule[hour][min][sec];

            if (specificDate.getTime() > playlistItem.$_endTime.getTime()) {
                return;
            }
            this.currentOffset = playlistItem.position;
            return playlistItem;
        };

        LinearPlaylist.prototype.updateItemLastPlayTime = function(item, specificDate) {
            playlistSchedulerService.recordItemPlayout(item, specificDate);
        };

        LinearPlaylist.prototype.reset = function() {
            this.currentOffset = null;
            this.currentItem = null;
        };

        LinearPlaylist.prototype.next = function(peek, specificDate) {
            // Check do we need to update active item collection
            this.updateActiveItems(specificDate);

            var nextItem = null;
            var attempts = 0;

            // Get current playlist
            if (this.activeItems && this.activeItems.length) {
                nextItem = playlistSchedulerService.getNextItemWithExactTiming(this.playlistId, specificDate);
                if (nextItem) {
                    nextItem = angular.copy(nextItem);
                }
            }

            while (!nextItem && attempts < this.activeItems.length) {
                if (!this.activeItems || this.activeItems.length == 0) {
                    return null; // Empty playlist
                }

                if (this.currentOffset == null) {
                    this.currentOffset = 0;
                } else {
                    this.currentOffset++;
                }

                if (this.currentOffset >= this.activeItems.length) {
                    if (this.loop == false) {
                        this.currentOffset = this.activeItems.length - 1;
                        return null; // Reach end of playlist and no looping enabled
                    }
                    this.currentOffset = 0;
                    // Check do we need to randomise
                    if (this.random) {
                        this.randomise();
                    }
                }

                // Clone the  item so we can add a unique ID to ensure it detected as being different
                var item = angular.copy(this.activeItems[this.currentOffset]);
                item.$_position = this.currentOffset;

                // Check does this item meet the frequency restriction
                if (playlistSchedulerService.isItemValid(item, specificDate, {checkFrequency: true})) {
                    nextItem = item;
                } else {
                    // Check next item
                    attempts++;
                }
            }

            if (nextItem) {
                this.currentItem = nextItem;
                // Record that we played this item
                playlistSchedulerService.recordItemPlayout(this.currentItem, specificDate);
                return this.currentItem;
            } else {
                return null;
            }
        };

        LinearPlaylist.prototype.isLastItem = function() {
            return this.currentOffset >= (this.activeItems.length - 1);
        };


        LinearPlaylist.prototype.previous = function(specificDate) {
            // Check do we need to update active item collection
            this.updateActiveItems();

            if (!this.activeItems || this.activeItems.length == 0) {
                return null; // Empty playlist
            }

            if (this.currentOffset == null) {
                this.currentOffset = this.activeItems.length - 1;
            } else {
                this.currentOffset--;
            }

            if (this.currentOffset < 0) {
                if (this.loop == false) {
                    this.currentOffset = 0;
                    return null; // Reach end of playlist and no looping enabled
                }
                this.currentOffset = this.activeItems.length - 1;
                // Check do we need to randomise
                if (this.random) {
                    this.randomise();
                }
            }

            // Clone the  item so we can add a unique ID to ensure it detected as being different
            var item = angular.copy(this.activeItems[this.currentOffset]);
            item.$_position = this.currentOffset;
            this.currentItem = item;
            // Record that we played this item
            playlistSchedulerService.recordItemPlayout(this.currentItem, specificDate);
            return this.currentItem;
        };

        LinearPlaylist.prototype.seek = function(index, specificDate) {
            // Check do we need to update active item collection
            this.updateActiveItems();

            if (!this.activeItems || this.activeItems.length == 0) {
                return null; // Empty playlist
            }

            if (index == null) {
                return null; // invalid index
            } else if (index < 0 || index >= this.activeItems.length) {
                return null; // index out of bounds
            }
            this.currentOffset = index;

            // Clone the  item so we can add a unique ID to ensure it detected as being different
            var item = angular.copy(this.activeItems[this.currentOffset]);
            item.$_position = this.currentOffset;
            this.currentItem = item;

            // Record that we played this item
            playlistSchedulerService.recordItemPlayout(this.currentItem, specificDate);
            return this.currentItem;
        };

        // Checks if the item is valid based on its schedule
        LinearPlaylist.prototype.isValid = function(item, specificDate) {
            return playlistSchedulerService.isItemValid(item, specificDate);
        };

        LinearPlaylist.prototype.generateActiveItems = function(items, specificDate) {
            var active = [];
            for (var i = 0; i < items.length; i++) {
                var item = items[i];
                if (this.isValid(item, specificDate)) {
                    active.push(item);
                }
            }
            return active;
        };

        LinearPlaylist.prototype.setItems = function(paramItems, playlistId) {
            this.items = paramItems;
            this.lastActiveCheck = null;
            this.playlistId = playlistId;
            if (!this.playlistId) {
                this.playlistId = new Date().getTime();
            }
            playlistSchedulerService.registerExactItems(this.playlistId, this.items);

            this.deterministicSchedule = {};
            // Check do we need to update active item collection
            this.updateActiveItems();

            this.currentItem = null;
            this.currentOffset = null;
            if (this.random) {
                this.randomise();
            }
            if (this.autoStart == true) {
                this.next();
            }
        };

        LinearPlaylist.prototype.randomise = function() {
            this.items = shuffle(this.items);
            this.lastActiveCheck = null;
        };

        function shuffle(array) {
            var currentIndex = array.length;
            var temporaryValue;
            var randomIndex;
            // While there remain elements to shuffle...
            while (0 !== currentIndex) {
                // Pick a remaining element...
                randomIndex = Math.floor(Math.random() * currentIndex);
                currentIndex -= 1;
                // And swap it with the current element.
                temporaryValue = array[currentIndex];
                array[currentIndex] = array[randomIndex];
                array[randomIndex] = temporaryValue;
            }
            return array;
        }

        LinearPlaylist.prototype.loadItemsFromUrl = function(url) {
            if (this.getItemsPromise != null) {
                this.getItemsPromise.abort();
            }
            var request = $http({
                method: 'get',
                url: url
            });
            var owner = this;

            function handleResolve(response) {
                // Loaded items from url
                if (response.data.items) {
                    owner.setItems(response.data.items);
                }
                return (response.data);
            }

            function handleReject(response) {
                // Failed to load items from url
                return ($q.reject(response.status));
            }

            function handleFinally() {
                request = promise = null;
            }
            // Intercept request resolve to load the items
            var promise = request.then(handleResolve, handleReject);
            // Wire in the underlying abort method.
            promise.abort = request.abort;
            promise.finally(handleFinally);
            return (promise);
        };

        LinearPlaylist.prototype.getItemAtIndex = function(index) {
            var res = null;
            if (index >= 0 && this.activeItems.length < index) {
                this.currentOffset = index;
                res = this.activeItems[index];
            }
            return res;
        };

        LinearPlaylist.prototype.getTotalItems = function() {
            return this.activeItems.length;
        };

        return (LinearPlaylist);
    }]);
})(window.angular);

(function ($angular) {

    "use strict";
    var angular = $angular;
    var app = angular.module('ui');
    /**
     * @type {Function}
     */
    app.factory('NestedScheduablePlaylist', ['$http', '$q', 'playlistSchedulerService', function ($http, $q, playlistSchedulerService) {

        function NestedScheduablePlaylist(paramAutoStart, paramLoop, paramRandom) {
            this.autoStart = (paramAutoStart) ? true : false;
            this.loop = (paramLoop) ? true : false;
            this.random = (paramRandom) ? true : false;
            this.linkedItemList = new Array();
            this.nodeCount = 0;
        }

        //Define the instance methods and properties
        NestedScheduablePlaylist.prototype = {
            getItemsPromise: null,
            currentItem: null,
            currentOffset: null,
            autoStart: false,
            loop: true,
            playlistEnd: false,
            random: false,
            items: [],
            deterministicSchedule: {},
            deterministicScheduleAvgContentLoadTime: 0.5
        };

        NestedScheduablePlaylist.prototype.isLastItem = function () {
            return this.playlistEnd;
        }

        NestedScheduablePlaylist.prototype.findLastItem = function () {

            var item = null;
            var lastItem = null;
            //Capture state and reset
            var cOffset = this.currentOffset;
            var loop = this.loop;
            var pend = this.playlistEnd;
            var rand = this.random;
            this.currentOffset = null;
            this.loop = false;
            this.playlistEnd = false;
            this.random = false;

            while ((item = this.next())) {
                lastItem = item;
            }
            //restore state
            this.loop = loop;
            this.random = rand;
            this.playlistEnd = pend;
            this.currentOffset = cOffset;
            return lastItem;
        }

        NestedScheduablePlaylist.prototype.generateDeterministicSchedule = function (date, defaultDuration) {

            //Profile time to create the deterministic schedule
            var ctime = new Date();

            //What date should we generate the schedule for
            var scheduleTime;
            if (date) {
                scheduleTime = new Date(date);
            }
            else {
                scheduleTime = new Date();
            }
            scheduleTime.setHours(0, 0, 0, 0);


            this.deterministicSchedule = {
                date: scheduleTime.getDate()
            }

            var day = scheduleTime.getDay();

            //Set the default duration for items that dont have a duration
            if (!defaultDuration) {
                defaultDuration = 12;
            }

            var playlistItem = null;
            var slotsCreated = 0;

            //Generate the playlist items for the day, make sure we dont create more than 10k
            while (scheduleTime.getDay() == day && slotsCreated < 100000) {
                var scheduleTimestamp = scheduleTime.getTime();
                var duration = 12;
                var hour = scheduleTime.getHours();
                var min = scheduleTime.getMinutes();
                var sec = scheduleTime.getSeconds();

                //Get the next item for this specific time, dont peek as we want to update the head
                playlistItem = this.next(false, new Date(scheduleTimestamp));
                playlistItem = angular.copy(playlistItem);
                //No more items
                if (playlistItem) {
                    duration = playlistItem.duration ? playlistItem.duration : playlistItem.playoutProperties.duration;
                    //If the item has not duration or its less than 1 second
                    if (!duration || duration < 1) {
                        duration = playlistItem.defaultDuration ? playlistItem.defaultDuration : 12;
                    }
                }

                //We only support round numbers for deterministic scheduling
                duration = duration | 0;

                //Add some time for the average load time
                duration += this.deterministicScheduleAvgContentLoadTime;

                //If there is a playlist item, create an entry for it
                if (playlistItem) {
                    //Set the playlist item start and end time
                    playlistItem.$_startTime = new Date(scheduleTimestamp);
                    playlistItem.$_endTime = new Date(scheduleTimestamp);
                    playlistItem.$_endTime.setTime(scheduleTimestamp + (duration * 1000));

                    //Insert the current playlist item into our record for this timeslot
                    if (!this.deterministicSchedule[hour])
                        this.deterministicSchedule[hour] = {};
                    if (!this.deterministicSchedule[hour][min])
                        this.deterministicSchedule[hour][min] = {};
                    this.deterministicSchedule[hour][min][sec] = playlistItem;
                }
                //Increment our schedule by duration
                scheduleTime.setTime(scheduleTimestamp + (duration * 1000));
                slotsCreated++;
            }
            var elapsed = new Date().getTime() - ctime.getTime();
            //console.log("Deterministic schedule stats: elapsed " + elapsed + "ms, slots " + slotsCreated);
            //console.log(this.deterministicSchedule);
        }

        NestedScheduablePlaylist.prototype.getPlaylistItemByTime = function (specificDate) {
            if (!specificDate) {
                specificDate = new Date();
            }
            //Check do we have a schedule for this date
            if (!this.deterministicSchedule || this.deterministicSchedule.date != specificDate.getDate()) {
                //If not generate
                this.generateDeterministicSchedule(specificDate);
            }

            var hour = specificDate.getHours();
            var min = specificDate.getMinutes();
            var sec = specificDate.getSeconds();

            while (hour >= 0 && min >= 0 && sec >= 0) {
                var retry = false;
                //Scan for the closest hour slot that contains an entry
                while (!this.deterministicSchedule[hour]) {
                    //Check the previous hour
                    hour--;
                    min = 59;
                    sec = 59;
                    if (hour < 0) {
                        //Cant find any lookup entry
                        console.log("Could not find a schedule entry for the requested time");
                        return null;
                    }
                }

                //Scan for the closest min slot that contains an entry
                while (!this.deterministicSchedule[hour][min]) {
                    //Check previous min
                    min--;
                    sec = 59;
                    if (min < 0) {
                        hour--;
                        min = 59;
                        sec = 59;
                        retry = true;
                        break;
                    }
                }

                if (retry) {
                    continue;
                }

                //Scan for the closest second slot that contains an entry
                while (!this.deterministicSchedule[hour][min][sec] && sec >= 0) {
                    //Check previous sec
                    sec--;
                    if (sec < 0) {
                        min--;
                        if (min < 0) { //If this is the first min of the hour, decrement the hour
                            min = 59;
                            hour--;
                        }
                        sec = 59;
                        retry = true;
                        break;
                    }
                }
                if (!retry) {
                    //Found an entry
                    break;
                }
            }

            var playlistItem = this.deterministicSchedule[hour][min][sec];

            if (specificDate.getTime() > playlistItem.$_endTime.getTime()) {
                console.log("No content active during this time")
                return;
            }

            //console.log(playlistItem);
            return playlistItem;
        }

        NestedScheduablePlaylist.prototype.next = function (peek, specificDate) {
            if (this.linkedItemList.length == 0) {
                return null; //Empty playlist
            }
            this.playlistEnd = false;
            //If this is the first time run we pick the first item in the list
            var skipCheck = false;
            if (this.currentOffset == null) {
                this.currentOffset = this.linkedItemList[0];
            }
            else {
                //Dont check the current item, progress to the next
                skipCheck = true;
            }
            //Enumerate through the linked list to find the next item
            var nextItem = null
            var enumOffset = this.currentOffset;
            var checkCount = 0;
            while (!nextItem && checkCount < this.nodeCount + 1) {
                checkCount++;
                var valid = this.isValid(enumOffset, specificDate);
                //Check is this item valid
                if (!skipCheck && (!enumOffset.items) && valid) {
                    nextItem = enumOffset;
                    if (!nextItem._next) {
                        this.playlistEnd = true;
                    }
                    break;
                }
                skipCheck = false;
                //Check do we have child items to enumerate through
                if (valid && enumOffset.items && enumOffset.items.length > 0) {
                    enumOffset = enumOffset.items[0];
                    continue;
                }
                else if (valid && enumOffset.blocker == true) {
                    //This item is a blocker and there are no child items
                    //Check do we need to loop
                    if (this.loop == false) {
                        //console.log("End of playlist");
                        return null; //Reach end of playlist and no looping enabled
                    }
                    else {
                        //We need to loop from the begining
                        //Check do we need to randomise
                        if (this.random) {
                            this.randomise();
                        }
                        //Reset the offset to first item
                        enumOffset = this.linkedItemList[0];
                    }
                }
                //Check do we have another item to enumerate to
                else if (enumOffset._next == null) {
                    //Scan the parents to find one that has a next item
                    var foundNextItem = false;
                    while (enumOffset._parent != null && enumOffset._parent.blocker != true) {
                        enumOffset = enumOffset._parent;
                        if (enumOffset._next) {
                            enumOffset = enumOffset._next;
                            foundNextItem = true;
                            break;
                        }
                    }
                    //Did we find another item?
                    if (foundNextItem) {
                        continue;
                    }

                    //Cant find a parent with a next item

                    //Check do we need to loop
                    if (this.loop == false) {
                        //console.log("End of playlist");
                        return null; //Reach end of playlist and no looping enabled
                    }
                    else {
                        //We need to loop from the begining
                        //Check do we need to randomise
                        if (this.random) {
                            this.randomise();
                        }
                        //Reset the offset to first item
                        enumOffset = this.linkedItemList[0];
                    }
                }
                //Get the next item in the list
                else {
                    enumOffset = enumOffset._next;
                }
                //Check have we looped once through all items
                if (enumOffset == this.currentOffset) {
                    //We have scanned this list completely
                    if (this.isValid(this.currentOffset, specificDate)) {
                        //Clone the  item so we can add a unique ID to ensure it detected as being different
                        this.currentItem = angular.copy(this.currentOffset);
                        //Record that we played this item
                        playlistSchedulerService.recordItemPlayout(this.currentItem, specificDate);
                        return this.currentItem;
                    }
                    //No valid items
                    break;
                }
            }
            //Couldnt find the next item
            if (nextItem == null) {
                return null;
            }
            //Update our offset
            this.currentOffset = nextItem;
            //Clone the  item so we can add a unique ID to ensure it detected as being different
            this.currentItem = angular.copy(nextItem);
            //Record that we played this item
            playlistSchedulerService.recordItemPlayout(this.currentItem, specificDate);
            return this.currentItem;
        };

        NestedScheduablePlaylist.prototype.previous = function (peek, specificDate) {
            if (this.linkedItemList.length == 0) {
                return null; //Empty playlist
            }
            this.playlistEnd = false;
            //If this is the first time run we pick the first item in the list
            var skipCheck = false;
            if (this.currentOffset == null) {
                var lastItem = this.linkedItemList[this.linkedItemList.length - 1];
                while (lastItem.items && lastItem.items.length) {
                    lastItem = lastItem.items[lastItem.items.length - 1];
                }
                this.currentOffset = lastItem;
            }
            else {
                //Dont check the current item, progress to the next
                skipCheck = true;
            }

            //Enumerate through the linked list to find the next item
            var nextItem = null
            var enumOffset = this.currentOffset;
            var checkCount = 0;
            while (!nextItem && checkCount < this.nodeCount) {
                checkCount++;
                var valid = this.isValid(enumOffset, specificDate);
                //Check is this item valid
                if (!skipCheck && (!enumOffset.items || enumOffset.items.length == 0) && valid) {
                    nextItem = enumOffset;
                    break;
                }
                skipCheck = false;

                //Check do we have another item to enumerate to
                if (enumOffset._previous == null) {
                    //Check the parent & get its previous item
                    if (enumOffset._parent) {
                        enumOffset = enumOffset._parent;
                        continue;
                    }
                    this.playlistEnd = true;
                    //Check do we need to loop
                    if (this.loop == false) {
                        // console.log("End of playlist");
                        return null; //Reach end of playlist and no looping enabled
                    }
                    else {
                        //We need to loop from the begining
                        //Check do we need to randomise
                        if (this.random) {
                            this.randomise();
                        }
                        //Reset the offset to last item
                        enumOffset = this.findLastItem();
                    }
                }
                //Get the previous item in the list
                else {
                    enumOffset = enumOffset._previous;
                    //Find the last item in the link
                    while (enumOffset.items && enumOffset.items.length) {
                        enumOffset = enumOffset.items[enumOffset.items.length - 1];
                    }
                }
                //Check have we looped once through all items
                if (enumOffset == this.currentOffset) {
                    //We have scanned this list completely
                    break;
                }
            }
            //Couldnt find the next item
            if (nextItem == null) {
                return null;
            }
            //Update our offset
            this.currentOffset = nextItem;
            //Clone the  item so we can add a unique ID to ensure it detected as being different
            this.currentItem = angular.copy(nextItem);
            //Record that we played this item
            playlistSchedulerService.recordItemPlayout(this.currentItem, specificDate);
            return this.currentItem;
        };

        NestedScheduablePlaylist.prototype.seek = function (index) {
            if (this.linkedItemList.length == 0) {
                return null; //Empty playlist
            }
            if (index == null || index == undefined || index < 0) {
                return null; //invalid index
            }
            //Reset to the start of the playlist
            this.currentItem = null,
                this.currentOffset = null,
                this.playlistEnd = false;

            //Store loop and disable so we dont wrap
            var loop = this.loop;
            this.loop = false;

            for (var i = 0; i <= index; i++) {
                //Increment
                this.next();
                if (this.playlistEnd) {
                    //Dont wrap to the first
                    break;
                }
            }
            //restore previous loop value
            this.loop = loop;
            //Return the current item after itterating through the list
            //Record that we played this item
            playlistSchedulerService.recordItemPlayout(this.currentItem, specificDate);
            return this.currentItem;
        };

        NestedScheduablePlaylist.prototype.setItems = function (paramItems) {
            this.items = paramItems;
            this.playlistEnd = false;
            this.deterministicSchedule = {};
            var res = buildLinkedList(this.items);
            if (res) {
                this.linkedItemList = res.items;
                this.nodeCount = res.count;
            }
            this.currentItem = null;
            this.currentOffset = null;
            if (this.random) {
                this.randomise();
            }
            if (this.autoStart == true) {
                this.next();
            }
        };

        NestedScheduablePlaylist.prototype.randomise = function () {
            if (this.linkedItemList == 0) {
                return;
            }
            this.playlistEnd = false;
            this.linkedItemList = shuffle(this.linkedItemList);
        };

        //Checks if the item is valid based on its schedule
        NestedScheduablePlaylist.prototype.isValid = function (item, specificDate) {
            return playlistSchedulerService.isItemValid(item, specificDate);
        };

        //Constructs a linear connected list of all items
        function linkItems(parent, pos) {
            if (!parent.items) {
                return;
            }
            //Enumerate through child items and link
            for (var i = 0; i < parent.items.length; i++) {
                var item = parent.items[i];
                item._previous = parent.items[i - 1];
                item._next = parent.items[i + 1];
                item._parent = parent;
                if (!item.items || item.items.length == 0) {
                    item.$_position = pos.leafs++;
                }
                item._nodeOffset = pos.nodes++;
                linkItems(item, pos);
            }
            return;
        }

        //Constructs a linear connected list of all items
        function buildLinkedList(items) {
            if (!items) {
                return null;
            }
            var clonedItems = angular.copy(items);
            //Keep a marker to track position of leaf nodes
            var pos = { leafs: 0, nodes: 0 };
            for (var i = 0; i < clonedItems.length; i++) {
                var item = clonedItems[i];
                //Link the item to its previous and next
                item._next = clonedItems[i + 1];
                item._previous = clonedItems[i - 1];
                item._parent = null;
                if (!item.items || item.items.length == 0) {
                    item.$_position = pos.leafs++;
                }
                item._nodeOffset = pos.nodes++;
                linkItems(item, pos);
            }
            return { items: clonedItems, count: pos.nodes };
        }

        function shuffle(array) {
            var currentIndex = array.length, temporaryValue, randomIndex;
            // While there remain elements to shuffle...
            while (0 !== currentIndex) {
                // Pick a remaining element...
                randomIndex = Math.floor(Math.random() * currentIndex);
                currentIndex -= 1;
                // And swap it with the current element.
                temporaryValue = array[currentIndex];
                array[currentIndex] = array[randomIndex];
                array[randomIndex] = temporaryValue;
            };
            return array;
        }

        NestedScheduablePlaylist.prototype.loadItemsFromUrl = function (url) {
            if (this.getItemsPromise != null) {
                this.getItemsPromise.abort();
            }
            var request = $http({
                method: "get",
                url: url
            });

            // Intercept request resolve to load the items
            var promise = request.then(
                function handleResolve(response) {
                    //console.log("loaded items from url");
                    if (response.data.items) {
                        this.setItems(response.data.items);
                    }
                    return (response.data);

                },
                function handleReject(response) {
                    //console.log("failed to load items from url");
                    return ($q.reject(response.status));
                }
            );
            // Wire in the underlying abort method.
            promise.abort = request.abort;
            promise.finally(
                function handleFinally() {
                    request = promise = null;
                }
            );
            return (promise);
        };

        return (NestedScheduablePlaylist);
    }]);
})(window.angular);
(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('ui');
    app.factory('ScheduablePlaylist', ["$http", "$q", "dataUtil", "logService", "playlistSchedulerService", "TIME", "INTERLEAVE_TYPE", "InterleaveControl", function($http, $q, dataUtil, logService, playlistSchedulerService, TIME, INTERLEAVE_TYPE, InterleaveControl) {
        function ScheduablePlaylist(paramAutoStart, paramLoop, paramRandom, paramInterleave, interleaveConfiguration) {
            this.autoStart = (paramAutoStart) ? true : false;
            this.loop = (paramLoop) ? true : false;
            this.random = (paramRandom) ? true : false;
            this.playlists = [];
            this.offsetInfo = null;
            this.interleave = (paramInterleave) ? true : false;
            this.itemCount = 0;
            this.interleaveConfiguration = interleaveConfiguration;
            this.interleaveControl = new InterleaveControl(this.interleaveConfiguration);
        }

        // Define the instance methods and properties
        ScheduablePlaylist.prototype = {
            getItemsPromise: null,
            autoStart: false,
            loop: true,
            interleave: false,
            random: false,
            items: [],
            playlistItemOffset: {},
            deterministicSchedule: {},
            interleaveConfiguration: {},
            interleaveControl: null,
            deterministicScheduleAvgContentLoadTime: 0.5
        };

        ScheduablePlaylist.prototype.generateDeterministicSchedule = function(date, defaultDuration, useExactDate, lookAheadTime) {
            // Reset out current offset info as we will generate from begining
            this.offsetInfo = null;

            // What date should we generate the schedule for
            var scheduleTime = date ? new Date(date) : new Date();

            // Assume start of the day if not using exact date
            if (!useExactDate) {
                scheduleTime.setHours(0, 0, 0, 0);
            }

            // By default, limit look ahead to 24hours
            lookAheadTime = lookAheadTime || TIME.TWENTY_FOUR_HOURS_IN_MILLISECONDS;
            var endTime = new Date(scheduleTime.getTime() + lookAheadTime);
            this.deterministicSchedule = {
                date: scheduleTime.getDate()
            };
            var day = scheduleTime.getDay();

            // Set the default duration for items that dont have a duration
            if (!defaultDuration) {
                defaultDuration = 12;
            }

            var playlistItem = null;
            var slotsCreated = 0;
            var usedContents = [];
            // Generate the playlist items for the day, make sure we dont create more than 10k
            while (scheduleTime.getDay() == day && slotsCreated < 100000 && scheduleTime.getTime() < endTime.getTime()) {
                var scheduleTimestamp = scheduleTime.getTime();
                var duration = 12;
                var hour = scheduleTime.getHours();
                var min = scheduleTime.getMinutes();
                var sec = scheduleTime.getSeconds();

                // Get the next item for this specific time, dont peek as we want to update the head
                playlistItem = this.next(false, new Date(scheduleTimestamp));
                playlistItem = angular.copy(playlistItem);
                // No more items
                if (playlistItem) {
                    // if an item have a uniqueId & we never record it before, record it so that we can clear it off after generating the markings
                    if (playlistItem.uniqueId && usedContents.indexOf(playlistItem.uniqueId) == -1) {
                        usedContents.push(playlistItem.uniqueId);
                    }

                    var playoutPropertiesDuration = playlistItem.playoutProperties ? (playlistItem.playoutProperties.duration || playlistItem.playoutProperties.fixedVideoDuration) : 0;
                    duration = playoutPropertiesDuration || playlistItem.duration || playlistItem.defaultDuration || duration;
                    duration = Math.floor(duration);
                    // If the item duration is less than 1 second, defaulted it to 12
                    duration = duration < 1 ? 12 : duration;
                }

                // Add some time for the average load time
                duration += this.deterministicScheduleAvgContentLoadTime;

                // If there is a playlist item, create an entry for it
                if (playlistItem) {
                    // Set the playlist item start and end time
                    playlistItem.$_startTime = new Date(scheduleTimestamp);
                    playlistItem.$_endTime = new Date(scheduleTimestamp);
                    playlistItem.$_endTime.setTime(scheduleTimestamp + (duration * 1000));

                    // Insert the current playlist item into our record for this timeslot
                    if (!this.deterministicSchedule[hour]) {
                        this.deterministicSchedule[hour] = {};
                    }

                    if (!this.deterministicSchedule[hour][min]) {
                        this.deterministicSchedule[hour][min] = {};
                    }

                    this.deterministicSchedule[hour][min][sec] = playlistItem;
                }

                // Increment our schedule by duration
                scheduleTime.setTime(scheduleTimestamp + (duration * 1000));
                slotsCreated++;
            }

            // we're done generating the markers, clear up the records on playlist scheduler service
            playlistSchedulerService.clearRecordItem(usedContents);
        };

        ScheduablePlaylist.prototype.getPlaylistItemByTime = function(specificDate) {
            if (!specificDate) {
                specificDate = new Date();
            }

            // Check do we have a schedule for this date
            if (!this.deterministicSchedule || this.deterministicSchedule.date != specificDate.getDate()) {
                // If not generate
                this.generateDeterministicSchedule(specificDate);
            }

            var hour = specificDate.getHours();
            var min = specificDate.getMinutes();
            var sec = specificDate.getSeconds();

            while (hour >= 0 && min >= 0 && sec >= 0) {
                var retry = false;
                // Scan for the closest hour slot that contains an entry
                while (!this.deterministicSchedule[hour]) {
                    // Check the previous hour
                    hour--;
                    min = 59;
                    sec = 59;
                    if (hour < 0) {
                        return null;
                    }
                }

                // Scan for the closest min slot that contains an entry
                while (!this.deterministicSchedule[hour][min]) {
                    // Check previous min
                    min--;
                    sec = 59;
                    if (min < 0) {
                        hour--;
                        min = 59;
                        sec = 59;
                        retry = true;
                        break;
                    }
                }

                if (retry) {
                    continue;
                }

                // Scan for the closest second slot that contains an entry
                while (!this.deterministicSchedule[hour][min][sec] && sec >= 0) {
                    // Check previous sec
                    sec--;
                    if (sec < 0) {
                        min--;
                        if (min < 0) { // If this is the first min of the hour, decrement the hour
                            min = 59;
                            hour--;
                        }

                        sec = 59;
                        retry = true;
                        break;
                    }
                }

                if (!retry) {
                    // Found an entry
                    break;
                }
            }

            var playlistItem = this.deterministicSchedule[hour][min][sec];

            if (specificDate.getTime() > playlistItem.$_endTime.getTime()) {
                return;
            }

            return angular.copy(playlistItem);
        };

        ScheduablePlaylist.prototype.isLastItem = function() {
            if (!this.offsetInfo) {
                return true;
            }

            var lastItem = this.getLastItem();
            if (!lastItem) {
                return true;
            }

            if (lastItem.item == this.offsetInfo.item) {
                return true;
            } else {
                return false;
            }
        };

        ScheduablePlaylist.prototype.getInterleavePlaylistItemOffset = function(playlistOffset) {
            // If we are not in interleave mode we skip
            if (!this.interleave) {
                return null;
            }

            // Make sure the playlist offset is in bounds
            if (playlistOffset >= this.playlists.length) {
                return null;
            }

            // Get the playlist and last known item offset
            var p = this.playlists[playlistOffset];
            var itemOffset = this.playlistItemOffset[playlistOffset];
            // If there is an item offset, make sure its in bounb
            if (itemOffset != null && itemOffset >= 0 && itemOffset < p.items.length) {
                return itemOffset;
            }

            // no valid item offset
            return null;
        };

        ScheduablePlaylist.prototype.getValidItemFromRange = function(fromOffset, toOffset, items, specificDate) {
            if (toOffset > items.length) {
                toOffset = items.length;
            }

            for (var i = fromOffset; i < toOffset; i++) {
                var item = items[i];
                if (this.isValid(item, specificDate)) {
                    return {
                        item: item,
                        offset: i
                    };
                }
            }
        };

        ScheduablePlaylist.prototype.validateSkipEndedInterleaveConfiguration = function() {
            return this.interleaveConfiguration && this.interleaveConfiguration.enable && this.interleaveConfiguration.type === INTERLEAVE_TYPE.SKIP_ENDED;
        };

        ScheduablePlaylist.prototype.getItemOffsetInfoFromPlaylist = function(playlist, playlistOffset, nextItemOffset, specificDate) {
            // try retrieving exact-based items first
            var item = this.retrieveExactItemForPlaylist(playlist, specificDate);
            if (item) {
                return {
                    playlist: playlist,
                    playlistOffset: playlistOffset,
                    item: item,
                    itemOffset: -1
                };
            }

            var offset = this.getInterleavePlaylistItemOffset(playlistOffset);
            if (offset == null) {
                offset = nextItemOffset || 0;
                // if interleave is option 2, store the ended playlist so that it will skip
                var currentPlaylist = this.playlists[playlistOffset];
                var itemOffset = this.playlistItemOffset[playlistOffset];
                if (itemOffset && itemOffset >= currentPlaylist.items.length &&
                    this.validateSkipEndedInterleaveConfiguration()) {
                    // when all playlist has ended, restart from first playlist
                    if (this.interleaveControl.restartInterleaveSkipEnded()) {
                        return this.getFirstItem(specificDate);
                    }

                    // each ended playlist is added into interleave control
                    // if all playlist has ended, each playlist will be restart accordingly from the first content
                    var hasPlaylistSkipped = !this.interleaveControl.validateInterleaveSkipEnded(playlistOffset, this.playlists.length);
                    if (hasPlaylistSkipped) {
                        return undefined;
                    }
                }
            }

            var items = playlist.items;
            var itemInfo = this.getValidItemFromRange(offset, items.length, items, specificDate);
            // if there is an item found while traversing item list starting from offset, then return the item
            if (itemInfo) {
                return {
                    playlist: playlist,
                    playlistOffset: playlistOffset,
                    item: itemInfo.item,
                    itemOffset: itemInfo.offset
                };
            }

            // if no item found from offset till last item, mark playlist as ended
            // empty playlist will also be marked as ended here
            if (this.validateSkipEndedInterleaveConfiguration() &&
            !this.interleaveControl.validateInterleaveSkipEnded(playlistOffset, this.playlists.length)) {
                return undefined;
            }

            // if not interleaving, then return nothing
            // if traverse from the first offset (all items has been searched through) and nothing found, then return nothing
            if (!this.interleave || offset === 0) {
                return undefined;
            }

            // if not traverse from the first offset, then traverse from the first item until offset
            itemInfo = this.getValidItemFromRange(0, offset, items, specificDate);

            // if found, then return the item
            if (itemInfo) {
                return {
                    playlist: playlist,
                    playlistOffset: playlistOffset,
                    item: itemInfo.item,
                    itemOffset: itemInfo.offset
                };
            }

            // nothing found
            return undefined;
        };

        ScheduablePlaylist.prototype.getFirstItem = function(specificDate) {
            var firstItem;
            for (var i = 0; i < this.playlists.length; i++) {
                var p = this.playlists[i];
                var valid = this.isValid(p, specificDate);
                if (valid) {
                    firstItem = this.getItemOffsetInfoFromPlaylist(p, i, 0, specificDate);
                    // if we manage to get the item, stop looking
                    if (firstItem) {
                        break;
                    }
                }
            }

            return firstItem;
        };

        ScheduablePlaylist.prototype.getLastItem = function(specificDate) {
            var lastItem = null;
            for (var i = this.playlists.length - 1; i >= 0; i--) {
                var p = this.playlists[i];
                var valid = this.isValid(p, specificDate);

                // If we have the last item but found an exclisive playist before us then we reset
                if (lastItem && p.blocker && valid) {
                    lastItem = null;
                }

                if (valid && !lastItem) {
                    var offset = this.getInterleavePlaylistItemOffset(i);
                    if (offset == null) {
                        offset = p.items.length - 1;
                    }

                    for (var k = offset; k >= 0; k--) {
                        var item = p.items[k];
                        valid = this.isValid(item, specificDate);
                        if (valid) {
                            lastItem = {
                                playlist: p,
                                playlistOffset: i,
                                item: item,
                                itemOffset: k
                            };
                            break;
                        }
                    }
                }
            }

            return lastItem;
        };

        ScheduablePlaylist.prototype.isPlaylistBlocked = function(playlist, specificDate) {
            for (var i = 0; i < this.playlists.length; i++) {
                var p = this.playlists[i];
                if (p.__uniqueId == playlist.__uniqueId) {
                    // We found the current playlist, so its not blocked
                    return false;
                } else if (p.blocker && this.isValid(p, specificDate)) {// Is this playlist flagged as exclusive and is valid
                    // This playlist blocks the current one
                    return true;
                }
            }

            return false;
        };

        ScheduablePlaylist.prototype.updateItemLastPlayTime = function(item, specificDate) {
            playlistSchedulerService.recordItemPlayout(item, specificDate);
        };

        ScheduablePlaylist.prototype.retrieveExactItemForPlaylist = function(playlist, specificDate) {
            var item;
            if (playlist && this.isValid(playlist, specificDate) && !this.isPlaylistBlocked(playlist, specificDate)) {
                item = playlistSchedulerService.getNextItemWithExactTiming(playlist.__uniqueId, specificDate);
            }

            return item;
        };

        ScheduablePlaylist.prototype.next = function(peek, specificDate) {
            if (!this.playlists || this.playlists.length == 0) {
                return null;
            }

            var nextItem = null;
            var attempts = 0;

            // Always check if we have anything from exact to play if we're playing in a playlist
            if (this.offsetInfo && this.offsetInfo.playlistOffset >= 0 && this.offsetInfo.playlistOffset < this.playlists.length) {
                var currentPlaylist = this.playlists[this.offsetInfo.playlistOffset];
                nextItem = this.retrieveExactItemForPlaylist(currentPlaylist, specificDate);
                if (nextItem) {
                    nextItem = angular.copy(nextItem);
                }
            }

            // Find the next item that is valid
            while (!nextItem && attempts < this.itemCount) {
                var item = this.doNext(peek, specificDate);
                // Check does this item meet the frequency restriction
                if (item && playlistSchedulerService.isItemValid(item, specificDate, {checkFrequency: true})) {
                    nextItem = item;
                } else {
                    // Check next item
                    attempts++;
                }
            }

            if (nextItem) {
                // Record that we played this item
                playlistSchedulerService.recordItemPlayout(nextItem, specificDate);
                return nextItem;
            } else {
                return null;
            }
        };

        ScheduablePlaylist.prototype.processDefaultInterleave = function(specificDate) {
            // If we are interleaved the first thing we do is increment the playlist
            if (!this.interleave || this.playlists.length <= 1) {
                return;
            }

            // Get current playlist
            var currentPlaylist = this.playlists[this.offsetInfo.playlistOffset];
            // If its a blocker we cant get next, instead we go back to the start
            if (currentPlaylist.blocker && this.isValid(currentPlaylist, specificDate)) {
                this.offsetInfo = this.getFirstItem(specificDate);
                if (this.offsetInfo == null) {
                    return null;
                }

                this.playlistItemOffset[this.offsetInfo.playlistOffset] = this.offsetInfo.itemOffset + 1;

                // return the first item
                return angular.copy(this.offsetInfo.item);
            }

            this.offsetInfo.playlistOffset++;
            this.offsetInfo.itemOffset = null;
        };

        ScheduablePlaylist.prototype.processInterleave = function(specificDate) {
            if ((!this.interleaveConfiguration || !this.interleaveConfiguration.type)) {
                this.processDefaultInterleave(specificDate);
                return;
            }

            // validate interleave to see if we should proceed to next playlist
            if (!this.interleaveControl.validateInterleave(this.offsetInfo)) {
                return;
            }

            this.processDefaultInterleave(specificDate);
        };

        ScheduablePlaylist.prototype.doNext = function(peek, specificDate) {
            if (this.playlists.length == 0) {
                return null; // Empty playlist
            }

            // If this is the first time run we pick the first item in the list
            if (this.offsetInfo == null) {
                this.offsetInfo = this.getFirstItem(specificDate);
                if (this.offsetInfo == null) {
                    return null;
                }

                this.playlistItemOffset[this.offsetInfo.playlistOffset] = this.offsetInfo.itemOffset + 1;

                // return the first item
                return angular.copy(this.offsetInfo.item);
            }

            // Check if an exclusive playlists is blocking the current offset
            if (this.isPlaylistBlocked(this.offsetInfo.playlist)) {
                // Reset our offset info, and find the first valid item
                this.offsetInfo = this.getFirstItem(specificDate);
                if (this.offsetInfo == null) {
                    return null;
                }

                // Return the first valid item
                this.playlistItemOffset[this.offsetInfo.playlistOffset] = this.offsetInfo.itemOffset + 1;

                return angular.copy(this.offsetInfo.item);
            }

            this.processInterleave(specificDate);

            // Find the next item based on current playlist and item offset
            for (var i = this.offsetInfo.playlistOffset; i < this.playlists.length; i++) {
                var p = this.playlists[i];
                var playlistValid = this.isValid(p, specificDate);
                // Check is the playlist valid?
                if (playlistValid) {
                    // grab the next item offset. if we don't have current item offset, set it to undefined so that it will always grab the first item
                    // note : null is a number. null = 0
                    var nextItemOffset = !isNaN(this.offsetInfo.itemOffset) && this.offsetInfo.itemOffset != null ? this.offsetInfo.itemOffset + 1 : 0;
                    var offsetInfo = this.getItemOffsetInfoFromPlaylist(p, i, nextItemOffset, specificDate);
                    if (offsetInfo) {
                        this.offsetInfo = offsetInfo;
                        this.playlistItemOffset[this.offsetInfo.playlistOffset] = this.offsetInfo.itemOffset + 1;
                        return angular.copy(this.offsetInfo.item);
                    }
                }

                // if interleave not type SKIP_ENDED, allow offset to reset
                // if interleave is type SKIP_ENDED, must wait for isEndPlaylistReset flag to be TRUE to reset offet
                if (this.interleaveControl.allowResetForEndPlaylist()) {
                    this.offsetInfo.itemOffset = null;
                    this.playlistItemOffset[this.offsetInfo.playlistOffset] = null;
                }

                // If this playlist is a blocker we dont check the next playlist
                if (p.blocker && playlistValid) {
                    break;
                }
            }

            // If we reach here then we are at the end of the playlist
            // Check do we need to loop
            if (this.interleave == false && this.loop == false) {
                this.offsetInfo = null;
                return null; // Reach end of playlist and no looping enabled
            } else {
                // We need to loop from the begining
                // Check do we need to randomise
                if (this.random) {
                    this.randomise();
                }

                this.offsetInfo = this.getFirstItem(specificDate);
                if (this.offsetInfo == null) {
                    return null;
                }

                // Return the first valid item
                this.playlistItemOffset[this.offsetInfo.playlistOffset] = this.offsetInfo.itemOffset + 1;

                return angular.copy(this.offsetInfo.item);
            }
        };

        ScheduablePlaylist.prototype.previous = function(peek, specificDate) {
            if (this.playlists.length == 0) {
                return null; // Empty playlist
            }

            // If this is the first time run we pick the first item in the list
            if (this.offsetInfo == null) {
                this.offsetInfo = this.getLastItem(specificDate);
                if (this.offsetInfo == null) {
                    return;
                }

                // return the first item
                this.playlistItemOffset[this.offsetInfo.playlistOffset] = this.offsetInfo.itemOffset - 1;
                return angular.copy(this.offsetInfo.item);
            }

            // Check if an exclusive playlists is blocking the current offset
            if (this.isPlaylistBlocked(this.offsetInfo.playlist)) {
                // Reset our offset info, and find the first valid item
                this.offsetInfo = this.getLastItem(specificDate);
                if (this.offsetInfo == null) {
                    return null;
                }

                // Return the first valid item
                this.playlistItemOffset[this.offsetInfo.playlistOffset] = this.offsetInfo.itemOffset - 1;
                return angular.copy(this.offsetInfo.item);
            }

            // If we are interleaved the first thing we do is decrement the playlist offset
            if (this.interleave && this.playlists.length > 1) {
                this.offsetInfo.playlistOffset--;
                this.offsetInfo.itemOffset = null;
            }

            // Find the previous item based on current playlist and item offset
            for (var i = this.offsetInfo.playlistOffset; i >= 0; i--) {
                var p = this.playlists[i];
                var valid = this.isValid(p, specificDate);
                // Check is the playlist valid?
                if (valid) {
                    var itemOffset = this.getInterleavePlaylistItemOffset(i);
                    if (itemOffset == null && this.offsetInfo.itemOffset != null) {
                        itemOffset = this.offsetInfo.itemOffset - 1;
                    }

                    if (itemOffset == null) {
                        itemOffset = p.items.length - 1;
                    }

                    // Get the previous item based on current offset
                    for (var k = itemOffset; k >= 0; k--) {
                        var item = p.items[k];
                        valid = this.isValid(item, specificDate);
                        // Chek is this item is valid
                        if (valid) {
                            this.offsetInfo = {
                                playlist: p,
                                playlistOffset: i,
                                item: item,
                                itemOffset: k
                            };
                            // Return the next playlist item
                            this.playlistItemOffset[this.offsetInfo.playlistOffset] = this.offsetInfo.itemOffset - 1;
                            return angular.copy(item);
                        }
                        // Check previous item in the playlist
                    }
                }

                // Check previous playlist, reset the offsets
                this.offsetInfo.itemOffset = null;
                this.playlistItemOffset[this.offsetInfo.playlistOffset] = null;
            }

            // If we reach here then we are at the end of the playlist
            // Check do we need to loop, if we are interleaved we always loop
            if (this.interleave == false && this.loop == false) {
                this.offsetInfo = null;
                return null; // Reach end of playlist and no looping enabled
            } else {
                // We need to loop from the begining
                // Check do we need to randomise
                if (this.random) {
                    this.randomise();
                }

                this.offsetInfo = this.getLastItem(specificDate);
                if (this.offsetInfo == null) {
                    return null;
                }

                // Return the first valid item
                this.playlistItemOffset[this.offsetInfo.playlistOffset] = this.offsetInfo.itemOffset - 1;

                // Record that we played this item
                playlistSchedulerService.recordItemPlayout(this.offsetInfo.item, specificDate);

                return angular.copy(this.offsetInfo.item);
            }
        };

        ScheduablePlaylist.prototype.seek = function(index, specificDate) {
            if (this.playlists.length == 0) {
                return null; // Empty playlist
            }

            if (index == null || index == undefined || index < 0) {
                return null; // invalid index
            }

            // Reset to the start of the playlist
            this.offsetInfo = null;

            for (var i = 0; i <= index; i++) {
                // Increment
                this.next();
                if (!this.offsetInfo) { // Only happens if looping is disabled
                    // index is out of bound
                    return null;
                }
            }

            // Record that we played this item
            playlistSchedulerService.recordItemPlayout(this.offsetInfo.item, specificDate);

            // Return the current item after itterating through the list
            return angular.copy(this.offsetInfo.item);
        };

        ScheduablePlaylist.prototype.reset = function() {
            this.offsetInfo = null;
        };

        ScheduablePlaylist.prototype.setItems = function(paramItems) {
            // prevent layout reset if the items are exactly the same
            if (dataUtil.isArrayEqual(this.items, paramItems)) {
                // eslint-disable-next-line no-console
                logService.info('Skipping playlist set as items are the same');
                return this.playlists;
            }

            // Reset playlist item offsets
            this.playlistItemOffset = {};
            this.deterministicSchedule = {};
            this.itemCount = 0;

            this.items = paramItems;
            this.playlists = angular.copy(this.items);
            this.interleaveControl.setMaxPlaylistIteration(this.playlists);

            for (var i = 0; i < this.playlists.length; i++) {
                var p = this.playlists[i];
                // add unique id to each playlist for easier reference later
                p.__uniqueId = uuid.v1();
                this.itemCount += p.items.length;
                playlistSchedulerService.registerExactItems(p.__uniqueId, p.items);
            }

            this.offsetInfo = null;
            if (this.random) {
                this.randomise();
            }

            if (this.autoStart == true) {
                this.next();
            }

            return this.playlists;
        };

        ScheduablePlaylist.prototype.randomise = function() {
            // Create a new copy of the items
            this.playlists = angular.copy(this.items);
            if (this.playlists == 0) {
                return;
            }

            for (var i = 0; i < this.playlists.length; i++) {
                var p = this.playlists[i];
                p.items = shuffle(p.items);
            }
        };

        // Checks if the item is valid based on its schedule
        ScheduablePlaylist.prototype.isValid = function(item, specificDate) {
            // if we have parent & parent is blocked, consider item not valid
            if (item._parent && this.isPlaylistBlocked(item._parent, specificDate)) {
                return false;
            } else {// if we have no parent or our parent is not blocked, then we check if item valid or not
                return playlistSchedulerService.isItemValid(item, specificDate);
            }
        };

        function shuffle(array) {
            var currentIndex = array.length;
            var temporaryValue;
            var randomIndex;
            // While there remain elements to shuffle...
            while (0 !== currentIndex) {
                // Pick a remaining element...
                randomIndex = Math.floor(Math.random() * currentIndex);
                currentIndex -= 1;
                // And swap it with the current element.
                temporaryValue = array[currentIndex];
                array[currentIndex] = array[randomIndex];
                array[randomIndex] = temporaryValue;
            }

            return array;
        }

        ScheduablePlaylist.prototype.loadItemsFromUrl = function(url) {
            if (this.getItemsPromise != null) {
                this.getItemsPromise.abort();
            }

            var request = $http({
                method: 'get',
                url: url
            });
            var owner = this;
            // Intercept request resolve to load the items
            function handleResolve(response) {
                if (response.data.items) {
                    owner.setItems(response.data.items);
                }

                return (response.data);
            }

            function handleReject(response) {
                return ($q.reject(response.status));
            }

            function handleFinally() {
                request = promise = null;
            }

            var promise = request.then(handleResolve, handleReject);
            // Wire in the underlying abort method.
            promise.abort = request.abort;
            promise.finally(handleFinally);
            return (promise);
        };

        ScheduablePlaylist.prototype.getTotalItems = function() {
            return this.itemCount;
        };

        return (ScheduablePlaylist);
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('ui');
    app.factory('StopWatch', ["dataUtil", function(dataUtil) {
        function StopWatch(configuration) {
            this.startTime = 0;
            this.elapsedTime = 0;
            this.triggers = [];
            this.rafHandler = 0;
            this.setTimeoutHandler = 0;
            this.running = false;
            this.supportAnimationFrame = (dataUtil.isNullOrUndefined(configuration) || dataUtil.isNullOrUndefined(configuration.supportAnimationFrame)) ||
                dataUtil.isValueBooleanTrue(configuration.supportAnimationFrame);
        }

        function getTime() {
            if (performance) {
                return performance.now();
            }

            return new Date().getTime();
        }

        StopWatch.prototype.startListener = function() {
            // If we support request animation frame, use it
            if (this.supportAnimationFrame && !this.rafHandler && window.requestAnimationFrame) {
                this.rafHandler = window.requestAnimationFrame(this.checkTriggers.bind(this));
                return;
            }

            // If we dont support animation frame, fallback to timeout
            if (!this.setTimeoutHandler) {
                this.setTimeoutHandler = setTimeout(this.checkTriggers.bind(this), 0);
            }
        };

        StopWatch.prototype.stopListener = function() {
            if (this.rafHandler) {
                window.cancelAnimationFrame(this.rafHandler);
                this.rafHandler = 0;
            }

            if (this.setTimeoutHandler) {
                clearTimeout(this.setTimeoutHandler);
                this.setTimeoutHandler = 0;
            }
        };

        StopWatch.prototype.checkTriggers = function() {
            // stop all listeners
            this.stopListener();
            var areTriggersValid = function() {
                return this.triggers.length > 0 && this.running;
            }.bind(this);
            // Enumerate through triggers while running
            if (areTriggersValid()) {
                var elapsed = this.elapsed();
                for (var i = 0; i < this.triggers.length; i++) {
                    var trigger = this.triggers[i];
                    // We add 8, as check trigger has a 16ms resolution, so we split the difference
                    if (elapsed + 8 >= trigger.elapse) {
                        this.triggers.splice(i, 1);
                        i--;
                        trigger.callback(trigger);
                        if (trigger.scope) {
                            trigger.scope.$apply();
                        }

                        // update elapsed
                        elapsed = this.elapsed();
                    }
                }
            }

            // Check do we still have triggers, and if so are we running
            if (areTriggersValid()) {
                this.startListener();
            }
        };

        StopWatch.prototype.start = function() {
            if (this.running) {
                return;
            }

            this.startTime = getTime();
            this.running = true;
            if (this.triggers.length > 0) {
                this.startListener();
            }
        };

        StopWatch.prototype.stop = function() {
            if (!this.running) {
                return;
            }

            this.elapsedTime = this.elapsedTime + (getTime() - this.startTime);
            this.startTime = getTime();
            this.running = false;
            this.stopListener();
        };

        StopWatch.prototype.reset = function() {
            this.elapsedTime = 0;
            this.startTime = getTime();
        };

        StopWatch.prototype.elapsed = function() {
            if (!this.running) {
                return this.elapsedTime;
            }

            return this.elapsedTime + (getTime() - this.startTime);
        };

        StopWatch.prototype.addTrigger = function(trigger) {
            if (!trigger.callback || !trigger.elapse) {
                return;
            }

            this.triggers.push(trigger);
        };

        StopWatch.prototype.clearTriggers = function() {
            this.triggers = [];
            this.stopListener();
        };

        return (StopWatch);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('ui');
    app.constant('INTERLEAVE_TYPE', {
        DEFAULT: 1,
        SKIP_ENDED: 2, // skip playlist if ended
        SPACE_EVEN: 3, // spread even playlist
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('ui');
    app.constant('TIME', {
        TWENTY_FOUR_HOURS_IN_MILLISECONDS: 86400000
    });
})(window.angular);

angular.module("ui").run(["$templateCache", function($templateCache) {$templateCache.put("/modules/ui/directives/asLogOverlayTemplate.html","<div ng-attr-style=\"display:{{display}}\">\n  \n  <tabset>\n    <tab heading=\"Log View\" active=\"true\">\n        <div class=\"form-inline log-view-filters\">\n          <div class=\"input-group\">\n            <select class=\"form-control log-select\" ng-model=\"selectedLoggers\"> \n                <option value=\"\">All loggers</option>\n                <option ng-repeat=\'logger in loggers | filter:{enabled: true}\' ng-attr-value=\"{{logger.name}}\" ng-selected=\"{{logger.name === selectedLoggers}}\">{{logger.name}}</option>\n            </select>\n          </div>\n          <div ng-repeat=\"level in LOGLEVEL\" ng-model=\"selectedLevels\" class=\"btn-group\">\n            <button class=\"btn btn-md {{selectedLevels.indexOf(level.value) === -1 ? \'btn-default\' : \'btn-primary\'}}\" ng-click=\"index = selectedLevels.indexOf(level.value); index === -1 ? selectedLevels.push(level.value) : selectedLevels.splice(index, 1);\">{{level.text}}</button>\n          </div>\n          <input type=\"text\" class=\"form-control log-search\" placeholder=\"Search for...\" ng-model=\"search.message\">\n          <button class=\"btn btn-md btn-default log-clear\" ng-click=\"clearLog()\">Clear</button>\n        </div>\n    \n        <div as-ui-auto-scroll-y scroll-timeout=\"15\" scroll-animation=\"0.5\"  class=\"log-view-content\" name=\"logItems\">\n          <table class=\"table borderless\">\n            <tbody>\n              <tr ng-repeat=\"log in logs | filter:search:strict | filter:(!!selectedLoggers || undefined) && selectedLoggers:true | filter:levelFilter\">\n                <td class=\"log-view-date-col\">\n                  {{log.createdAt}}\n                </td>\n                <td class=\"log-view-logger-col\">\n                  {{log.logger.name}}\n                </td>\n                <td class=\"log-view-level-col\">\n                  {{log.logLevel}}\n                </td>\n                <td>\n                  {{log.message}}\n                </td>\n              </tr>\n            </tbody>\n          </table>\n        </div>\n    </tab>\n    <tab heading=\"Settings\">\n      <div class=\"tab-pane log-settings-content\" id=\"settings\">\n        <form>\n          <p class=\"log-settings-info\">You can configure the default log level for all loggers or set a custom level for a specific logger</p>\n          <table class=\"table borderless\">\n            <tbody>\n            <tr>\n                <td style=\"border: none !important;\">\n                  <b> Default log level for all loggers</b>\n                </td>\n                <td ng-form=\"cellForm\" style=\"border: none !important;\">\n                  <div class=\"input-group\">\n                   <select class=\"form-control\" style=\"width: 200px; margin-right: 10px;\" ng-model=\'defaultLogLevel\' ng-options=\'item as item.text for item in DEFAULTLOGLEVEL track by item.value\' ng-change=\"updateDefaultLoggerLevel(defaultLogLevel)\"></select>\n                  </div>\n                </td>\n              </tr>\n              <tr ng-repeat=\"logger in loggers\">\n                <td style=\"border: none !important;\">\n                  {{logger.name}}\n                </td>\n                <td ng-form=\"cellForm\" style=\"border: none !important;\">\n                  <div class=\"input-group\">\n                    <select ng-disabled=\"!logger.enabled\" class=\"form-control\" ng-model=\"logger.logLevel\" style=\"width: 200px; margin-right: 10px;\" ng-change=\"updateLoggerLevel(logger)\"> \n                       <option ng-repeat=\'level in LOGLEVEL\' ng-attr-value=\"{{level.value}}\" ng-selected=\"{{level.value == logger.logLevel}}\">{{level.text}}</option>\n                    </select>\n                    <button class=\"btn btn-default btn-md\" ng-click=\"enableLogger(logger)\" ng-attr-style=\"width: 50px; color: white; background-color: {{ logger.enabled ? \'green\' : \'black\'}}\">{{ logger.enabled ? \'ON\' : \'OFF\'}}</button>\n                  </div>\n                </td>\n              </tr>\n            </tbody>\n          </table>\n        </form>\n      </div>\n    </tab>\n  </tabset>\n  \n</div>\n");}]);
(function($angular) {
  var angular = $angular;
  'use strict';

  // Declare app level module which depends on views, and components
  angular.module('signsHTML5', [
    'ngRoute',

    // app common
    'AppCommon',

    // appspace core modules
    'storage',
    'hal',
    'core',
    'network',
    'media',
    'ui',
    'appspaceAPI',
    'deviceManager',
    'alerts',
    'job',

    'SignalR',

    // misc modules
    'cb.x2js',
    'angular-md5',
    'ngPromiseExtras',
    'monospaced.qrcode'
  ]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';
    var app = angular.module('signsHTML5');

    app.$inject = ['$rootScope', '$location', 'deviceRegistrationService', 'storageLocalStorageService', 'coreFrameworkService', 'BrowserGenericDriver', 'deviceHealthService'];

    app.config(['$routeProvider', '$locationProvider', '$compileProvider', function($routeProvider, $locationProvider, $compileProvider) {
        $locationProvider.hashPrefix('');
        // production optimization
        $compileProvider.debugInfoEnabled(false);
        $compileProvider.commentDirectivesEnabled(false);
        $compileProvider.cssClassDirectivesEnabled(false);
        $routeProvider.
            when('/tv/splash', {
                templateUrl: 'tv/splash/view.html',
                css: 'assets/css/splash.css'
            }).
            when('/tv/unload', {
                templateUrl: 'tv/unload/view.html',
                css: 'assets/css/unload.css'
            }).
            when('/tv/appinit', {
                templateUrl: 'tv/applicationinit/view.html',
                css: ['assets/css/registration.css', 'assets/css/appinit.css']
            }).
            when('/tv/watchdog', {
                templateUrl: 'tv/watchdog/view.html',
                css: ['assets/css/registration.css', 'assets/css/appinit.css']
            }).
            when('/tv/appdisplay', {
                templateUrl: 'tv/applicationdisplay/view.html'
            }).
            when('/tv/previewappdisplay', {
                templateUrl: 'tv/applicationdisplay/view.html'
            }).
            when('/tv/invalid', {
                templateUrl: 'tv/invalid/view.html'
            }).
            when('/tv/invalid/:error', {
                templateUrl: 'tv/invalid/view.html'
            }).
            when('/tv/registration', {
                templateUrl: 'tv/registration/index.html',
                css: 'assets/css/registration.css'
            }).
            when('/tv/registrationlanguage', {
                templateUrl: 'tv/registration/language.html',
                css: 'assets/css/registration.css'
            }).
            when('/tv/registrationaccount', {
                templateUrl: 'tv/registration/account.html',
                css: 'assets/css/registration.css'
            }).
            when('/tv/registrationdevicename', {
                templateUrl: 'tv/registration/devicename.html',
                css: 'assets/css/registration.css'
            }).
            when('/tv/registrationchannel', {
                templateUrl: 'tv/registration/channel.html',
                css: 'assets/css/registration.css'
            }).
            when('/tv/registrationprogress', {
                templateUrl: 'tv/registration/progress.html',
                css: 'assets/css/registration.css'
            }).
            when('/tv/registrationsuccess', {
                templateUrl: 'tv/registration/success.html',
                css: 'assets/css/registration.css'
            }).
            when('/tv/registrationrestoresuccess', {
                templateUrl: 'tv/registration/successrestore.html',
                css: 'assets/css/registration.css'
            }).
            when('/tv/registrationfail', {
                templateUrl: 'tv/registration/fail.html',
                css: 'assets/css/registration.css'
            }).
            when('/tv/silentregistration', {
                templateUrl: 'tv/registration/silentreg.html',
                css: 'assets/css/registration.css'
            }).
            when('/tv/unregister', {
                templateUrl: 'tv/registration/unregister.html',
                css: 'assets/css/registration.css'
            }).
            when('/tv/registrationinstance', {
                templateUrl: 'tv/registration/instance.html',
                css: 'assets/css/registration.css'
            }).
            when('/tv/alerts', {
                templateUrl: 'tv/alerts/view.html'
            }).
            when('/tv/broadcasts', {
                templateUrl: 'tv/broadcasts/view.html'
            }).
            when('/tv/deviceoperation/:op', {
                templateUrl: 'tv/devicemenu/deviceoperation.html'
            }).
            when('/tv/channelbrowser', {
                templateUrl: 'tv/channelbrowser/channelbrowser.html'
            }).
            when('/tv/channel/:id', {
                templateUrl: 'tv/channelbrowser/channel-viewer/channel-viewer.html',
                reloadOnSearch: false
            }).
            when('/tv/channel/:id/autoplay/:autoplay', {
                templateUrl: 'tv/channelbrowser/channel-viewer/channel-viewer.html',
                reloadOnSearch: false
            }).
            when('/tv/channel/:id/exclusive/:exclusive', {
                templateUrl: 'tv/channelbrowser/channel-viewer/channel-viewer.html',
                reloadOnSearch: false
            }).
            when('/tv/channel', {
                templateUrl: 'tv/channelbrowser/channel-viewer/channel-viewer.html',
                reloadOnSearch: false
            }).
            when('/tv/epg', {
                templateUrl: 'tv/channelbrowser/epg.html'
            }).
            when('/tv/livechannel/channelnumber/:number', {
                templateUrl: 'tv/channelbrowser/livechannel.html'
            }).
            when('/tv/livechannel/channelnumber/:number/autoplay/:autoplay', {
                templateUrl: 'tv/channelbrowser/livechannel.html'
            }).
            when('/tv/livechannel/:id', {
                templateUrl: 'tv/channelbrowser/livechannel.html'
            }).
            when('/tv/livechannel/:id/autoplay/:autoplay', {
                templateUrl: 'tv/channelbrowser/livechannel.html'
            }).
            when('/tv/datetimewidgetexample', {
                templateUrl: 'example/dateTimeWidget/view.html'
            }).
            when('/tv/plaintextwidgetexample', {
                templateUrl: 'example/plainTextWidget/view.html'
            }).
            when('/tv/rssviewerwidgetexample', {
                templateUrl: 'example/rssViewerWidget/view.html'
            }).
            when('/tv/devicemenuexample', {
                templateUrl: 'example/deviceMenu/view.html'
            }).
            when('/tv/test', {
                templateUrl: 'commondirectives/test.html'
            }).
            when('/tv/sleep', {
                templateUrl: 'tv/sleep/view.html'
            }).
            when('/tv/standby', {
                templateUrl: 'tv/standby/view.html'
            }).
            otherwise({
                redirectTo: '/tv/splash'
            });
    }]).run(["$rootScope", "$q", "$location", "$timeout", "deviceRegistrationService", "storageLocalStorageService", "embedProfileService", function($rootScope, $q, $location, $timeout, deviceRegistrationService, storageLocalStorageService,
        embedProfileService) {
        // this is to intercept login request, if callback is defined, load callback instead of login page
        // this is a temporary pattern
        var registrationService = deviceRegistrationService;
        var storage = storageLocalStorageService;
        var applicationReady = false;
        var applicationReadyPromise = null;
        // Confirm framework is ready
        var publicPaths = [
            '/tv/splash',
            '/tv/registration',
            '/tv/unregister',
            '/tv/silentregistration',
            '/tv/deviceoperation',
            '/tv/test',
            '/tv/previewappdisplay',
            '/tv/unload',
            '/tv/invalid'
        ];

        function init() {
            var params = window.location.toString().split('/');
            var serverUrl = '';
            for (var i = 0; i < 3; i++) {
                if (i == 0) {
                    serverUrl = params[i] + '//';
                } else {
                    serverUrl += params[i];
                }
            }

            var currentUrl = storage.get('appspace', 'appspaceURL');
            if (currentUrl) {
                return;
            }

            storage.set('appspace', 'appspaceURL', serverUrl);
        }

        function isAuthorized(route) {
            // if we're in editor or preview mode and we have the necessary parameters for injection
            if ((embedProfileService.isEditorMode() || embedProfileService.isPreviewMode()) && embedProfileService.isValid()) {
                return true;
            }

            // Check if this url is allow without player ID
            for (var i = 0; i < publicPaths.length; i++) {
                var publicPath = publicPaths[i];
                if (route.originalPath && route.originalPath.indexOf(publicPath) >= 0) {
                    return true;
                }
            }

            // If not public it needs a playerID
            return registrationService.getPlayerId();
        }

        $rootScope.$on('$routeChangeStart', function(event, next, current) {
            // Check is application ready?
            if (!applicationReady) {
                // Are we already waiting for a load
                if (!applicationReadyPromise) {
                    var defer = $q.defer();
                    applicationReadyPromise = $rootScope.applicationReadyPromise;
                    applicationReadyPromise.then(function() {
                        $timeout(function() {
                            applicationReady = true;
                            // init
                            init();
                            $rootScope.$evalAsync(function() {
                                if (!isAuthorized(next)) {
                                    defer.reject();
                                    $location.path('/tv/registration');
                                } else {
                                    defer.resolve();
                                }
                            });
                        });
                    });
                    next.resolve = next.resolve || {};
                    // Inject a resolve into the state change so we wait until the framework is ready
                    next.resolve.coreFrameworkService = function() {
                        return defer.promise;
                    };
                }

                return;
            }

            // Check if this url is allow without player ID
            if (isAuthorized(next)) {
                return;
            }

            event.preventDefault();
            $location.path('/tv/registration');
        });
    }]);

    // link css dynamically
    app.directive(function($rootScope, $compile) {
        return {
            restrict: 'E',
            link: function(scope, elem) {
                var html = '<link rel="stylesheet" ng-repeat="(routeCtrl, cssUrl) in routeStyles" ng-href="{{cssUrl}}" />';
                elem.append($compile(html)(scope));
                scope.routeStyles = {};
                $rootScope.$on('$routeChangeStart', function(e, next, current) {
                    // Unload current page CSS
                    if (current && current.$$route && current.$$route.css) {
                        if (!Array.isArray(current.$$route.css)) {
                            current.$$route.css = [current.$$route.css];
                        }

                        angular.forEach(current.$$route.css, function(sheet) {
                            delete scope.routeStyles[sheet];
                        });
                    }

                    // Load new CSS
                    if (!next || !next.$$route || !next.$$route.css) {
                        return;
                    }

                    if (!Array.isArray(next.$$route.css)) {
                        next.$$route.css = [next.$$route.css];
                    }

                    angular.forEach(next.$$route.css, function(sheet) {
                        scope.routeStyles[sheet] = sheet;
                    });
                });
            }
        };
    });
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('signsHTML5');
    var getElement = angular.element;
    app.controller('AppController', ["$rootScope", "$scope", "$timeout", "$document", "$q", "$location", "$window", "cronService", "commandService", "telemetryService", "deviceMetricsService", "contentFallbackService", "deviceHealthService", "broadcastAlertService", "coreConfigurationService", "networkContentRouterService", "halPlatformService", "logService", "networkContentCacheService", "coreFrameworkService", "cdnService", "admService", "HttpWrapper", "CSVParser", "scriptingService", "syncService", "alertsService", "FileReader", "halRemoteService", "ROUTE", "CDN_EVENT", "ADM_EVENT", "REMOTEVCODE", "REMOTEEVENTTYPE", "INVALID_PAGE_ERROR", "BROADCAST_ALERT_EVENT", "PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT", "CONTENT_ACCESS_CONTEXT", "halMediaService", "pushNotificationClientService", "storageLocalStorageService", "applicationCommandService", "NetworkUtil", "coreFontManagerService", "deviceStateService", "ASERRORCODE", "RegistrationUtil", "halNetworkService", "watchdogService", "sessionService", "remoteServiceManager", "postMessagingService", "SYNC_EVENT", "interactivityManager", "CRESTRON_MODE", "CRESTRON_INTENT_MESSAGE", "FEATURE_CODE", "postMessagingUtil", "publicService", "clockSyncService", "PLAYER_PROPERTY_KEYS", "TimeoutUtil", "broadcastService", "lowBandwidthService", "deviceIdentityService", "softwareUpdateService", "UrlUtil", "deviceTokenService", "authenticationLibraryService", "registrationRestorationService", "dataUtil", "CURSOR_VISIBILITY", "deviceAlertService", "deviceRegistrationService", "cardContentCacheService", "embedProfileService", "singleAppService", "occupancyService", "pwaCacheService", "webViewManager", "cardUtil", function(
        $rootScope,
        $scope,
        $timeout,
        $document,
        $q,
        $location,
        $window,
        cronService,
        commandService,
        telemetryService,
        deviceMetricsService,
        contentFallbackService,
        deviceHealthService,
        broadcastAlertService,
        coreConfigurationService,
        networkContentRouterService,
        halPlatformService,
        logService,
        networkContentCacheService,
        coreFrameworkService,
        cdnService,
        admService,
        HttpWrapper,
        CSVParser,
        scriptingService,
        syncService,
        alertsService,
        FileReader,
        halRemoteService,
        ROUTE,
        CDN_EVENT,
        ADM_EVENT,
        REMOTEVCODE,
        REMOTEEVENTTYPE,
        INVALID_PAGE_ERROR,
        BROADCAST_ALERT_EVENT,
        PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT,
        CONTENT_ACCESS_CONTEXT,
        halMediaService,
        pushNotificationClientService,
        storageLocalStorageService,
        applicationCommandService,
        NetworkUtil,
        coreFontManagerService,
        deviceStateService,
        ASERRORCODE,
        RegistrationUtil,
        halNetworkService,
        watchdogService,
        sessionService,
        remoteServiceManager,
        postMessagingService,
        SYNC_EVENT,
        interactivityManager,
        CRESTRON_MODE,
        CRESTRON_INTENT_MESSAGE,
        FEATURE_CODE,
        postMessagingUtil,
        publicService,
        clockSyncService,
        PLAYER_PROPERTY_KEYS,
        TimeoutUtil,
        broadcastService,
        lowBandwidthService,
        deviceIdentityService,
        softwareUpdateService,
        UrlUtil,
        deviceTokenService,
        authenticationLibraryService,
        registrationRestorationService,
        dataUtil,
        CURSOR_VISIBILITY,
        deviceAlertService,
        deviceRegistrationService,
        cardContentCacheService,
        embedProfileService,
        singleAppService,
        occupancyService,
        pwaCacheService,
        webViewManager,
        cardUtil
    ) {
        var configService = coreConfigurationService;
        var router = networkContentRouterService;
        var cache = networkContentCacheService;
        var hidePromise;
        var hideMouseTimeoutHandler;
        var previousMousePosition;
        var alertsAPI;
        var previousAppProperties;
        var cursorVisibilility;
        var cameraFramingMode;
        var crestronModes = [CRESTRON_MODE.OCCUPIED, CRESTRON_MODE.UNOCCUPIED, CRESTRON_MODE.STANDBY];
        var applicationReadyDefer = $q.defer();
        var backmostVideoLayerOffMethod;
        var isDeviceRecreating = false;
        var isIdentityReady = false;
        var activeBroadcasts = [];
        var isDeviceServicesStarted = false;
        var TRANSPARENT_BACKGROUND_CLASS = 'transparent-background';
        var WPG_REGEX_CHECK = /(\.wpg)(:?\?|$)/gi;
        var DYNAMIC_HTML = 'dynamic';
        var STATIC_HTML = 'static';
        var LOG_KEY = {
            DEFAULT: 'appspace.signs',
            SYNC: 'appspace.signs.sync'
        };
        var SYSTEM_COMMAND_TYPE = 'system';
        var SYSTEM_COMMAND = {
            RESYNC: 'resync',
            RESTART: 'restart'
        };
        var CACHE_REPOSITORY = {
            CONTENT: 'signs.content',
            FALLBACK_CONTENT: 'signs.fallbackcontent',
        };
        var ERROR_PAGE_PATH = ROUTE.INVALID_INJECTION;
        var CDN_EVENT_ERROR_PAGE_PATH = {};
        CDN_EVENT_ERROR_PAGE_PATH[CDN_EVENT.SYSTEM_CDN_FAILURE] = INVALID_PAGE_ERROR.CDN_FAILURE;
        CDN_EVENT_ERROR_PAGE_PATH[CDN_EVENT.SYSTEM_CDN_UNCONFIGURED] = INVALID_PAGE_ERROR.CDN_UNCONFIGURED;

        $scope.isForcedTransparentBackground = false;
        $scope.currentAlert;
        $scope.currentBroadcast;
        $scope.notifyCurrentBroadcastComplete = false;
        $scope.showMenu = false;
        $scope.appShow = {'visibility': 'visible'};
        $rootScope.applicationReadyPromise = applicationReadyDefer.promise;
        $scope.onCurrentBroadcastComplete = broadcastAlertService.notifyBroadcastComplete;
        $scope.onPwaCacheApiReady = pwaCacheService.setApi;

        $scope.$on('$destroy', function() {
            $document.off('mousemove', onMouseMove);
            if ($window.deviceMenuInvokeInfo) {
                $window.deviceMenuInvokeInfo = null;
            }

            // remove the resize/orientation event
            $window.removeEventListener('resize', onOrientationChanged);
            $window.removeEventListener('orientationchange', onOrientationChanged);

            stopPostMessageService();
            occupancyService.stop();
            lowBandwidthService.stop();
            deviceStateService.stop();
            interactivityManager.stop();
            telemetryService.stop(true);
            cronService.stop();
            deviceTokenService.stop();
            commandService.stop();
            softwareUpdateService.stop();
            cdnService.unsubscribeToEvents(onCDNEvent);
            deviceIdentityService.unsubscribeToIdentityCheck(identityCheckCallback);
            isIdentityReady = false;
            isDeviceServicesStarted = false;
            admService.unsubscribe(onAdmEvent);
            authenticationLibraryService.unsubscribeToConstellationTokensUpdate(onConstellationTokensRefreshed);
            authenticationLibraryService.unsubscribeToConstellationTokensRefreshFailed(onConstellationTokensFailedToRefresh);
            removeVirtualKeyboard();
            if (!backmostVideoLayerOffMethod) {
                return;
            }

            backmostVideoLayerOffMethod();
        });

        function executeSystemCommand(action) {
            applicationCommandService.execute({type: SYSTEM_COMMAND_TYPE, action: action});
        }

        function recreateDevice(policy) {
            isDeviceRecreating = true;
            logService.debug(LOG_KEY.DEFAULT, 'Policy ID changed. Recreating device...');
            deviceRegistrationService.recreate(policy).then(function() {
                logService.debug(LOG_KEY.DEFAULT, 'Device re-registration initiated');
                executeSystemCommand(SYSTEM_COMMAND.RESTART);
            }, function(error) {
                logService.debug(LOG_KEY.DEFAULT, 'Skipping device re-registration: ' + error);
            }).finally(function() {
                isDeviceRecreating = false;
            });
        }

        function processUrlPolicyMode() {
            if (isDeviceRecreating) {
                logService.warn(LOG_KEY.DEFAULT, 'Device recreation is already in progress. Skipping current attempt...');
                return;
            }

            // Device is not registered or
            // device does not have reference ID or
            // URL policy mode is not enforced
            if (!deviceStateService.isRegistered() ||
                dataUtil.isNullOrUndefined(coreConfigurationService.getReferenceDeviceId()) ||
                !halPlatformService.isUrlPolicyModeEnforced()) {
                return;
            }

            var policy = RegistrationUtil.getUrlPolicy().registration;
            // No URL policy
            if (!policy) {
                return;
            }

            var urlPolicyId = policy.policy_id;
            var savedPolicyId = coreConfigurationService.getRegistrationPolicyId();
            // No policy ID in URL or policy ID is the same as the one saved in local storage
            if (!urlPolicyId || savedPolicyId === urlPolicyId) {
                return;
            }

            // Policy ID changed and reference device ID exists, initiate device re-registration
            recreateDevice(policy);
        }

        function onAdmEvent(event) {
            switch ((event || '').toLowerCase()) {
                case ADM_EVENT.QUEUE_MARKER_UPDATED:
                    deviceIdentityService.generateDeviceIdentity().then(updateRegistrationInfo);
                    break;
            }
        }

        function updateRegistrationInfo(identity) {
            if (embedProfileService.isPreviewMode() ||
                !identity ||
                !identity.hardware ||
                !identity.hardware.fingerprint ||
                !identity.hardware.fingerprint.fingerprint) {
                return;
            }

            registrationRestorationService.updateRegistrationInfo({
                accountId: identity.appspace.accountId,
                deviceId: identity.appspace.deviceGuid,
                apiUrl: identity.appspace.instanceUrl,
                deviceState: RegistrationUtil.getDeviceState()
            }).then($angular.noop, $angular.noop);
        }

        function identityCheckCallback(identity) {
            isIdentityReady = true;
            logService.info(LOG_KEY.DEFAULT, 'Device identity check completed');
            deviceTokenService.processConstellationTokens(identity);
            RegistrationUtil.persistRegistrationInfoOnBoot();
            processUrlPolicyMode();
            updateRegistrationInfo(identity);
            startDeviceServices();
        }

        function isRegistered() {
            return deviceStateService.isRegistered();
        }

        function onFallbackContentUpdated(content) {
            $scope.fallbackContent = $angular.copy(content);
            $scope.isUnregistered = !isRegistered();
        }

        function onPnsClientEvent(event, data) {
            if (!event) {
                return;
            }

            data = data || {};
            switch (event.toLowerCase()) {
                case PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.CLIENT_CLOSED:
                    logService.debug(LOG_KEY.DEFAULT, 'Push notification service client connection closed due to code ' + data.code);
                    broadcastAlertService.stop();
                    break;
                case PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.CLIENT_CONNECTED:
                    logService.info(LOG_KEY.DEFAULT, 'Push notification client service started');
                    broadcastAlertService.subscribe(onBroadcastAlertEvent);
                    logService.info(LOG_KEY.DEFAULT, 'Starting broadcast alert service');
                    broadcastAlertService.start(data).then(function() {
                        logService.info(LOG_KEY.DEFAULT, 'Broadcast alert service started');
                    }, function(error) {
                        logService.error(LOG_KEY.DEFAULT, 'Failed to start broadcast alert service due to: ' + JSON.stringify(error));
                    });
                    break;
                case PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.SERVICE_STOPPED:
                    logService.debug(LOG_KEY.DEFAULT, 'Push notification client service stopped');
                    broadcastAlertService.unsubscribe(onBroadcastAlertEvent);
                    broadcastAlertService.stop();
                    // Clear current broadcast when stopping broadcast alert service
                    processCurrentBroadcast(undefined, true);
                    break;
                case PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.CLIENT_DISCONNECTED:
                    logService.error(LOG_KEY.DEFAULT, 'Push notification client connection disrupted: ' + JSON.stringify(data));
                    break;
                case PUSH_NOTIFICATION_CLIENT_SERVICE_EVENT.CLIENT_RECONNECTED:
                    logService.debug(LOG_KEY.DEFAULT, 'Push notification client connection resumed');
                    break;
            }
        }

        function startDeviceServices() {
            if (isDeviceServicesStarted || !isRegistered()) {
                // Skip starting device services if already started or device is not registered
                return;
            }

            // Device is now in registered state, start device services
            isDeviceServicesStarted = true;
            telemetryService.start();
            deviceMetricsService.start();
            commandService.start();
            cronService.start();
            softwareUpdateService.start();
        }

        function doInitialize() {
            halPlatformService.updateReferenceDeviceId(halPlatformService.getReferenceDeviceIdKey());
            halPlatformService.updateIntegrationType();
            var urlParameters = sessionService.getUrlParameters();
            deviceIdentityService.updateDeviceName(urlParameters);
            urlParameters.registrationmode = urlParameters.registrationmode || '';
            if (!urlParameters.registrationmode) {
                delete urlParameters.registrationmode;
            }

            RegistrationUtil.setPassiveMode(urlParameters);
            sessionService.set(
                'urlParameters',
                processCrestronMode(urlParameters.crestronmode, urlParameters));
            backmostVideoLayerOffMethod = broadcastService.onBackmostVideoLayerEvent(onBackmostVideoLayerEventFired);
            storageLocalStorageService.set('device.info', 'startuptime', new Date());
            initLogging();
            halPlatformService.subscribeToPlatformMessages(onPlatformMessages);
            deviceIdentityService.subscribeToIdentityCheck(identityCheckCallback);
            admService.subscribe(onAdmEvent);
            authenticationLibraryService.subscribeToConstellationTokensUpdate(onConstellationTokensRefreshed);
            authenticationLibraryService.subscribeToConstellationTokensRefreshFailed(onConstellationTokensFailedToRefresh);
            deviceStateService.start();
            deviceHealthService.isReady().then(deviceIdentityService.start);
            startDeviceServices();
            coreFontManagerService.init();
            contentFallbackService.subscribe(onFallbackContentUpdated);
            contentFallbackService.initialize();

            $scope.showMenu = false;
            clockSyncService.start();
            deviceTokenService.start();

            initSystemHotKeys();
            // Register for remote input
            halRemoteService.registerEvents(onRemoteEvent);
            // start watchdog service
            watchdogService.start();
            // Confirm framework is ready
            // Create a cache repo for signs content
            cache.createRepository(CACHE_REPOSITORY.CONTENT).then($angular.noop, $angular.noop);
            cache.createRepository(CACHE_REPOSITORY.FALLBACK_CONTENT).then($angular.noop, $angular.noop);
            cache.createRepository('font.cache').then($angular.noop, $angular.noop);

            // If this is the first sync, we clear the framework repository
            cache.clearCache('framework.cache').then($angular.noop, $angular.noop);

            initOfflineServices();
            initOnlineServices();

            hideMouseTimeoutHandler = 5000;
            $document.on('mousemove', onMouseMove);

            halMediaService.subscribeAppDisplayUpdate(appDisplayUpdateEvent);
            pushNotificationClientService.subscribe(onPnsClientEvent);
            syncService.subscribe(onApplicationUpdated);
            alertsService.subscribe(alertReceived);
            scriptingService.start();

            // Hide the cordova splash screen
            halPlatformService.hideSplashscreen();

            // Hide the cordova status bar
            halPlatformService.hideStatusBar();

            halPlatformService.keepAwake();
            halPlatformService.subscribeToStandby(onStandbyChanged);

            // handle the resize/orientation event
            $window.addEventListener('resize', onOrientationChanged);
            $window.addEventListener('orientationchange', onOrientationChanged);

            startPostMessageService();
            occupancyService.start();
            interactivityManager.start();
            applicationReadyDefer.resolve();
            lowBandwidthService.start();
            webViewManager.init();
            if (!embedProfileService.isChannelPreviewMode()) {
                singleAppService.start().then($angular.noop, $angular.noop);
            }

            $scope.isTransparentCacheSupported = pwaCacheService.isTransparentCacheSupported();
        }

        // Initialise web app
        function init() {
            halPlatformService.initialize().then(function() {
                logService.debug(LOG_KEY.DEFAULT, 'Platform initialized');
            }, function(error) {
                logService.warn(LOG_KEY.DEFAULT, 'Failed to initialize platform: ' + JSON.stringify(error));
            }).finally(doInitialize);
        }

        // refactor to make it driver based operations
        function processCrestronMessage(mode) {
            var urlParameters = sessionService.getUrlParameters();
            sessionService.set('urlParameters',
                processCrestronMode(mode, urlParameters));
            if (postMessagingUtil.isCrestronModeApplicable(mode)) {
                deviceStateService.startAllServices();
            }

            // send back response
            postMessagingService.raiseCrestronProcessedMessage();
            $location.path('/tv/appinit');
        }

        function startPostMessageService() {
            postMessagingService.subscribe(onReceivedPostMessage);
            postMessagingService.start();
            publicService.registerService('contentPostMessaging', {
                sendPostMessageToContent: postMessagingService.sendPostMessageToContent,
                subscribeToContentMessages: postMessagingService.subscribeToContentMessages,
                unsubscribeFromContentMessages: postMessagingService.unsubscribeFromContentMessages
            });
        }

        function stopPostMessageService() {
            postMessagingService.unsubscribe(onReceivedPostMessage);
            postMessagingService.stop();
        }

        function onReceivedPostMessage(data) {
            if (!data.message) {
                return;
            }

            switch (data.message.toLowerCase()) {
                case 'appspaceapp.hardware':
                    var serialNumber = (data.data || {}).serialnumber;
                    if (serialNumber) {
                        halPlatformService.updateSerialNumber(serialNumber);
                        deviceIdentityService.restart();
                    }

                    break;
                case 'appspaceapp.unload':
                    $location.path('/tv/unload');
                    break;
                case 'appspaceapp.crestron':
                    var mode = data.mode || '';
                    processCrestronMessage(mode);
                    break;
                case 'appspaceapp.command':
                    remoteServiceManager.raiseCommand(data);
                    break;
                case 'appspaceapp.script':
                    var script = data.script;
                    if (!halPlatformService.getFeatureSupport('playback.mode.editor') || !script) {
                        return;
                    }

                    syncService.processAppScript(script);
                    break;
                case 'setproperties':
                    var params = sessionService.getUrlParameters();
                    var path = $location.path();
                    scriptingService.appendAppProperties(data.properties);
                    // refresh the page
                    $location.path(path).search(params);
                    break;
            }
        }

        function processCrestronMode(crestronMode, urlParameters) {
            crestronMode = (crestronMode || '').toLowerCase();
            /*
            * if we're not loading in crestron mode,
            * remove all the information
            */
            if (crestronModes.indexOf(crestronMode) == -1) {
                sessionService.remove('crestronMode');
                delete urlParameters.crestronmode;
                return urlParameters;
            }

            sessionService.set('crestronMode', crestronMode);
            urlParameters.embedprofile = 'ondemand';
            urlParameters.crestronmode = crestronMode;
            /*
            * if we're in crestron mode but not running occupied or unoccupied,
            * remove the actions
            */
            if (!postMessagingUtil.isCrestronModeApplicable(crestronMode)) {
                delete urlParameters.channel;
                delete urlParameters.channelname;
                return urlParameters;
            }

            urlParameters.channel = 'loadchannel';
            urlParameters.channelname = 'crestron-' + crestronMode;
            return urlParameters;
        }

        function onOrientationChanged() {
            $timeout(function() {
                halMediaService.syncDisplayResolutionToOrientation();
            }, 1000);
        }

        function onStandbyChanged(isStandby) {
            logService.info(LOG_KEY.DEFAULT, 'Device standby mode: ' + isStandby);
            $location.path(isStandby ? ROUTE.STANDBY : ROUTE.APP_INIT);
        }

        function getCdnErrorPagePath(event) {
            return ERROR_PAGE_PATH + '/' + CDN_EVENT_ERROR_PAGE_PATH[event];
        }

        function onCDNEvent(event) {
            var path = getCdnErrorPagePath(event);
            if (!path) {
                return;
            }

            $location.path(path);
        }

        function initOfflineServices() {
            cdnService.start();
            cdnService.subscribeToEvents(onCDNEvent);
            deviceAlertService.start();
            cardContentCacheService.initialize();
            initiateRemoteServiceManager();
        }

        function initiateRemoteServiceManager() {
            syncService.getAppProperties().then(function(properties) {
                if ((properties['remote.enable'] || '').toLowerCase() == 'true') {
                    remoteServiceManager.start({
                        arc: properties['remote.arc.address'],
                        forceUdp: (properties['remote.brightsign.forceudp'] || '').toLowerCase() === 'true'
                    });
                    return;
                }

                remoteServiceManager.stop();
            }, $angular.noop);
        }

        function waitForAppspaceOnline() {
            if (halPlatformService.getFeatureSupport('bypass.check.appspace.online')) {
                return $q.resolve();
            }

            return NetworkUtil.waitForAppspaceOnline();
        }

        function initOnlineServices() {
            waitForAppspaceOnline().then(function() {
                logService.debug(LOG_KEY.DEFAULT, 'Connection to Appspace established.');
                if (halPlatformService.getFeatureSupport('services.disable.sync')) {
                    logService.info(LOG_KEY.DEFAULT, 'Driver does not support sync service.');
                    return;
                }

                syncService.startSync();
                deviceHealthService.isReady().then(function() {
                    // To kickstart device identity service when recovering from
                    // boot without network connection
                    if (!isIdentityReady) {
                        logService.info(LOG_KEY.DEFAULT, 'Device identity service is not ready. Kickstarting it now');
                        deviceIdentityService.restart();
                        return;
                    }

                    deviceIdentityService.start();
                });
            }, function(err) {
                // retry
                initOnlineServices();
            });
        }

        function getProperty(key) {
            return syncService.getCurrentAppProperty(key);
        }

        function getProperties() {
            return syncService.getCurrentAppProperties() || {};
        }

        function savePropertiesToLocalStorage(properties) {
            storageLocalStorageService.set('appspace', 'instanceGuid', properties['player.instance.guid']);
            storageLocalStorageService.set('appspace', 'accountId', properties['player.account.guid']);
            saveServiceApiUrl(properties['player.services.api']);
        }

        function saveServiceApiUrl(value) {
            storageLocalStorageService.set('appspace', 'serviceApiUrl', value);
        }

        function onApplicationUpdated(event) {
            setCursorVisibility();
            setVirtualKeyboard();
            setCameraOptions();
            var eventName = event.event;
            if (eventName == SYNC_EVENT.APP_UNASSIGNED) {
                executeSystemCommand(SYSTEM_COMMAND.RESYNC);
                return;
            }

            // only listen if we have new script
            if (eventName != SYNC_EVENT.SCRIPT_SAVED) {
                return;
            }

            cleanUpUnusedContentTokens();
            // process properties
            var properties = getProperties();
            var propertiesString = JSON.stringify(properties);
            if (previousAppProperties == propertiesString) {
                return;
            }

            previousAppProperties = propertiesString;
            savePropertiesToLocalStorage(properties);
            // update alerts (if we broadcast)
            updateAlertsProperties(properties);
            initiateRemoteServiceManager();
            updateMaxVideoLayersProperties(properties);
        }

        function setVirtualKeyboard() {
            enableVirtualKeyboard(dataUtil.isValueTrue(
                getProperty(PLAYER_PROPERTY_KEYS.VIRTUAL_KEYBOARD_ENABLED))
            );
        }

        function removeVirtualKeyboard() {
            enableVirtualKeyboard(false);
        }

        function enableVirtualKeyboard(state) {
            halPlatformService.enableVirtualKeyboard(state);
        }

        function cleanUpUnusedContentTokens() {
            var manifest = storageLocalStorageService.get('device.sync', 'manifest');
            if (!manifest) {
                return;
            }

            manifest = JSON.parse(manifest);
            deviceTokenService.clearContentTokensFromManifest(manifest.files);
        }

        function alertReceived(alert) {
            logService.debug(LOG_KEY.DEFAULT, 'Received Alerts broadcast from Appspace');
            var setAlertMedia = function(url) {
                alert.media = url;
                // append current properties to alert
                var properties = getProperties();
                previousAppProperties = JSON.stringify(properties);
                alert.playerProperties = properties;
                savePropertiesToLocalStorage(properties);
                $scope.currentAlert = alert;
                $location.path('/tv/alerts');
            };

            var onMediaResolved = function(url) {
                if (url.toLowerCase().indexOf('.wpg') >= 0) {
                    networkContentRouterService.resolve(url, 'signs-wpg-routing-service').then(setAlertMedia);
                } else {
                    setAlertMedia(url);
                }
            };

            if (alert) {
                networkContentRouterService.resolve(alert.media, 'signs-content-routing-service', {
                    type: alert.mediaType ? alert.mediaType.toLowerCase() : undefined
                }).then(onMediaResolved);
            } else {
                $scope.currentAlert = alert;
            }
        }

        function processCurrentBroadcast(broadcast, clear) {
            if (!$scope.currentBroadcast && !broadcast) {
                // No valid current broadcast to process after initiating broadcast alert service
                return;
            }

            var previousBroadcast = $angular.copy($scope.currentBroadcast || {});
            $scope.currentBroadcast = $angular.copy(broadcast);
            if (clear) {
                logService.debug(LOG_KEY.DEFAULT, 'Clearing broadcasts');
                activeBroadcasts = [];
                $scope.notifyCurrentBroadcastComplete = false;
                if (!previousBroadcast || previousBroadcast.isNotification) {
                    return;
                }

                // Redirect if we were previously showing a fullscreen broadcast
                $location.path(ROUTE.APP_INIT);
                return;
            }

            if (!$scope.currentBroadcast) {
                logService.debug(LOG_KEY.DEFAULT, 'No active broadcast');
                $location.path(ROUTE.APP_INIT);
                return;
            }

            $scope.notifyCurrentBroadcastComplete = activeBroadcasts.length > 1;
            logService.debug(LOG_KEY.DEFAULT, 'Current active broadcast: ' + JSON.stringify(broadcast));
            // Check if previous and current display mode are the same to determine if redirection is required
            if (previousBroadcast.isNotification === $scope.currentBroadcast.isNotification) {
                return;
            }

            $location.path($scope.currentBroadcast.isNotification ?
                ROUTE.APP_INIT : ROUTE.BROADCASTS);
        }

        function onBroadcastAlertEvent(event, data) {
            if (!event) {
                return;
            }

            switch (event.toLowerCase()) {
                case BROADCAST_ALERT_EVENT.CURRENT_BROADCAST_UPDATE:
                    processCurrentBroadcast(data);
                    break;
                case BROADCAST_ALERT_EVENT.BROADCAST_UPDATE:
                    activeBroadcasts = data || [];
                    break;
            }
        }

        $scope.onAlertsViewApiReady = function(api) {
            alertsAPI = api;
        };

        $scope.alertsLoaded = function(alert) {
        };

        $scope.alertsRemoved = function() {
            $scope.currentAlert = undefined;
            $location.path('/tv/appinit');
        };

        function updateAlertsProperties(properties) {
            if (!alertsAPI || !$scope.currentAlert) {
                return;
            }

            $scope.currentAlert.playerProperties = properties;
            alertsAPI.setAlert($scope.currentAlert);
        }

        function updateMaxVideoLayersProperties(properties) {
            var requireRestart = halMediaService.setMaxVideoLayers(properties[PLAYER_PROPERTY_KEYS.MAX_VIDEO_LAYERS]);
            if (!requireRestart) {
                return;
            }

            executeSystemCommand(SYSTEM_COMMAND.RESTART);
        }

        function setCameraOptions() {
            var updatedFramingMode = getProperty(PLAYER_PROPERTY_KEYS.CAMERA_FRAMING_MODE) || 'default';
            if (cameraFramingMode === updatedFramingMode) {
                return;
            }

            cameraFramingMode = $angular.copy(updatedFramingMode);
            var setCameraOptionsImplementation = halMediaService.setCameraOptions({
                framing: cameraFramingMode,
            });
            if (!setCameraOptionsImplementation) {
                logService.debug(LOG_KEY.DEFAULT, 'Unable to set camera framing mode - setCameraOptions implementation not found');
                return;
            }

            setCameraOptionsImplementation.then(function(result) {
                logService.debug(LOG_KEY.DEFAULT, 'Set camera framing mode result: ' + JSON.stringify(result));
            }, function(error) {
                logService.warn(LOG_KEY.DEFAULT, 'Failed to set camera framing mode due to: ' + JSON.stringify(error));
            });
        }

        function getLogLevel(logger) {
            var level = storageLocalStorageService.get('profile.loggerconfig', logger.name);
            return level || null;
        }

        function initLogging() {
            var defaultAppenders = storageLocalStorageService.get('device.settings', 'default.appenders');
            if (!defaultAppenders) {
                defaultAppenders = ['ConsoleAppender', 'DBAppender'];
                storageLocalStorageService.set('device.settings', 'default.appenders', JSON.stringify(defaultAppenders));
            } else {
                defaultAppenders = JSON.parse(defaultAppenders);
            }

            var externalAppendersConfig = storageLocalStorageService.get('device.settings', 'external.appenders.config');
            if (!externalAppendersConfig) {
                externalAppendersConfig = [];
                storageLocalStorageService.set('device.settings', 'external.appenders.config', JSON.stringify(externalAppendersConfig));
            } else {
                externalAppendersConfig = JSON.parse(externalAppendersConfig);
            }

            var defaultLevel = getLogLevel({name: 'default'});
            if (!defaultLevel) {
                defaultLevel = 'info';
            }

            var logConfig = {
                logLevel: defaultLevel,
                defaultAppenders: defaultAppenders,
                externalAppenders: externalAppendersConfig,
                internalAppenders: [ // Determines what appender instances to create
                    {
                        type: 'ConsoleAppender'
                    },
                    {
                        type: 'DBAppender'
                    }
                ],
                loggers: [
                    {name: 'appspace.signs', config: {}},
                    {name: 'appspace.play', config: {}},
                    {name: 'appspace.signs.app', config: {}},
                    {name: 'appspace.signs.sync', config: {}},
                    {name: 'appspace.signs.registration', config: {}},
                    {name: 'appspace.signs.scripting', config: {}},
                    {name: 'appspace.signs.widgets', config: {}},
                    {name: 'appspace.signs.metric.service', config: {}},
                    {name: 'appspace.signs.rendezvous', config: {}},
                    {name: 'appspace.signs.editor', config: {}},
                    {name: 'appspace.core', config: {}},
                    {name: 'appspace.core.hal', config: {}},
                    {name: 'appspace.core.network', config: {}},
                    {name: 'appspace.network.contentcache', config: {}},
                    {name: 'appspace.core.media', config: {}},
                    {name: 'appspace.core.ui', config: {}},
                    {name: 'appspace.core.storage', config: {}},
                    {name: 'appspace.core.platform', config: {}},
                    {name: 'appspace.core.devicemanager', config: {}},
                    {name: 'appspace.core.api', config: {}},
                    {name: 'appspace.core.driver.chrome', config: {}},
                    {name: 'appspace.alerts.alertsservice', config: {}},
                    {name: 'appspace.job.admservice', config: {}},
                    {name: 'appspace.network.comm', config: {}},
                    {name: 'appspace.public.service', config: {}},
                    {name: 'appspace.content.cards', config: {}},
                    {name: 'appspace.cdn.cdnservice', config: {}},
                    {name: 'appspace.device.state', config: {}},
                    {name: 'appspace.core.media.webviewmanager', config: {}},
                    {name: 'appspace.core.deviceidentity', config: {}},
                    {name: 'appspace.core.devicetoken', config: {}},
                    {name: 'appspace.core.softwareupdate', config: {}},
                    {name: 'appspace.core.occupancy', config: {}},
                    {name: 'appspace.core.broadcastalert', config: {}},
                    {name: 'appspace.core.pushnotificationclient', config: {}},
                    {name: 'appspace.core.pushupdate', config: {}},
                    {name: 'appspace.core.registrationrestoration', config: {}},
                    {name: 'appspace.alerts.devicealertservice', config: {}},
                    {name: 'appspace.core.socialservice', config: {}},
                    {name: 'appspace.core.webbroadcastservice', config: {}},
                    {name: 'appspace.core.telemetry', config: {}},
                    {name: 'appspace.core.contentfallback', config: {}},
                    {name: 'appspace.core.pwacacheservice', config: {}},
                    {name: 'appspace.core.devicemetrics', config: {}},
                    {name: 'appspace.core.cron', config: {}},
                    {name: 'appspace.core.command', config: {}},
                ]
            };

            for (var i = 0; i < logConfig.loggers.length; i++) {
                var logger = logConfig.loggers[i];
                logger.logLevel = getLogLevel(logger);
            }

            logConfig.retainHistoryWindow = halPlatformService.getFeatureSupport('log.retain.history.window');
            // Logger configuration
            logService.setConfiguration(logConfig);
        }

        function getCacheFilePath(repo, file) {
            var opt = {
                disableFileValidationCache: getProperties()['player.local.file.validation.cache'] == 'disable'
            };
            return cache.getCacheFilePath(repo, file, opt);
        }

        function initSignsContentRouter() {
            function findAndReplace(actualString, stringToSearch, stringToReplace) {
                var res = actualString;
                if (actualString.toLowerCase().indexOf(stringToSearch.toLowerCase()) >= 0) {
                    // handle backslash when creating regex
                    var regexToSearch = new RegExp(stringToSearch, 'ig');
                    res = actualString.split(regexToSearch).join(stringToReplace);
                }

                return res;
            }

            // Set up custom routers
            var map = {
                name: 'signs-content-mapping',
                map: function(url, opt) {
                    var appspaceUrl = getAppspaceUrl();
                    if (url.toLowerCase().indexOf('c:\\fastnetv2') == -1 || !appspaceUrl) {
                        var isEncoded = new RegExp(/%\d[\dA-F]/g).test(url);
                        // Check is the url encoded? If not we encode
                        url = !isEncoded ? encodeURI(url) : url;
                    } else {
                        url = url.replace(/\\/g, '/');
                        // for datasource streaming
                        url = findAndReplace(url, 'c:/fastnetv2/datasource', appspaceUrl + '/medianet/fastnetdata/datasource');
                        // for media streaming
                        url = findAndReplace(url, 'c:/fastnetv2', appspaceUrl + '/app/fastnetdata');
                        // Encode the url to replace spaces and other illegal file characters
                        url = encodeURI(url);
                    }

                    // Standardize lowercased UG Media URL
                    url = url.replace('UG%20Media', 'ug%20media');

                    if (embedProfileService.isWebPublishingMode()) {
                        url = UrlUtil.appendQueryString(url, {
                            context: CONTENT_ACCESS_CONTEXT.WEB_PUBLISHING,
                            channelId: embedProfileService.getUrlParameter('appid')
                        });
                    } else if (!UrlUtil.hasQueryString(url, 'context')) {
                        // inherit the context from the parent
                        url = UrlUtil.appendQueryString(url, {
                            context: embedProfileService.isChannelPreviewMode() ?
                                embedProfileService.getContext()
                                : CONTENT_ACCESS_CONTEXT.CHANNEL
                        });
                    }

                    return $q.resolve(UrlUtil.appendDeviceInformation(url));
                }
            };

            var propertyResolveMap = {
                name: 'appspace-content-property-mapping',
                map: function(url) {
                    var deferred = $q.defer();
                    url = scriptingService.resolvePropertyExpression(url);
                    deferred.resolve(url);
                    return deferred.promise;
                }
            };

            var isUrlEndsWithWpg = function(url) {
                return url && url.match(WPG_REGEX_CHECK);
            };

            var wpgMap = {
                name: 'appspace-content-wpg-mapping',
                map: function(url) {
                    if (!isUrlEndsWithWpg(url)) {
                        return $q.reject('not a wpg format url');
                    }

                    var deferred = $q.defer();
                    var onPathResolved = function(resolvedUrl) {
                        var wrapper = new HttpWrapper();
                        wrapper.handlerGet(resolvedUrl).then(function(result) {
                            var finalUrl = resolvedUrl;
                            if (!result || !result.data) {
                                deferred.reject('wpg returns invalid data');
                                return;
                            }

                            var csvParser = new CSVParser();
                            result.data = 'table=1\n' + result.data;
                            csvParser.parse(result.data).then(function(wpgObject) {
                                if (wpgObject[0] && wpgObject[0].items && wpgObject[0].items[0]) {
                                    wpgObject = wpgObject[0].items[0];
                                }

                                if (!wpgObject.mode || !wpgObject.url) {
                                    deferred.reject('invalid wpg content format');
                                    return;
                                }

                                switch (wpgObject.mode.toLowerCase()) {
                                    case DYNAMIC_HTML:
                                        var authenticationString = '';
                                        if (wpgObject.username && wpgObject.password) {
                                            if (wpgObject.password.indexOf('{BAE}') >= 0 && atob) {
                                                wpgObject.password = atob(wpgObject.password.split('{BAE}').join(''));
                                                var slices = wpgObject.password.split(',');
                                                wpgObject.password = slices.slice(1, slices.length).join(',');
                                            }

                                            authenticationString = wpgObject.username + ':' + wpgObject.password + '@';
                                        }

                                        wpgObject.url = wpgObject.url.split('://').join('://' + authenticationString);
                                        finalUrl = wpgObject.url;
                                        deferred.resolve(finalUrl);
                                        break;
                                    case STATIC_HTML:
                                        // Resolve the place of the local file
                                        var onHtmlPlatformContentResolved = function(htmlResolvedUrl) {
                                            if (halPlatformService.getFeatureSupport('media.supportdataprotocol') === 'no') {
                                                deferred.resolve(htmlResolvedUrl);
                                                return;
                                            }

                                            // Read the data into memory and create a data url
                                            var fr = new FileReader();
                                            fr.read(htmlResolvedUrl).then(function(htmlString) {
                                                deferred.resolve('data:text/html;charset=utf-8,' + encodeURIComponent(htmlString));
                                            });
                                        };

                                        var onHtmlContentResolved = function(resolvedUrl) {
                                            halMediaService.resolveContentPath(resolvedUrl).then(onHtmlPlatformContentResolved);
                                        };

                                        router.resolve(wpgObject.url, 'signs-content-routing-service').then(onHtmlContentResolved,
                                            function() {
                                                deferred.reject('unable to resolve static html url');
                                            });
                                        break;
                                    default:
                                        deferred.reject('invalid html mode');
                                        break;
                                }
                            }, function() {
                                deferred.reject('unable to parse wpg');
                            });
                        });
                    };

                    halMediaService.resolveContentPath(url).then(onPathResolved);
                    return deferred.promise;
                }
            };

            // This router checks if the file is local
            var localrouting = {
                name: 'appspace-content-local-routing',
                priority: 1,
                route: function(url, opt) {
                    opt = opt || {};
                    var deferred = $q.defer();
                    var ignoreTypes;
                    // if someone has provided options to force stream certain type of contents, use that instead player properties
                    if (opt && opt.forceStreamType) {
                        ignoreTypes = opt.forceStreamType;
                    } else {// check from player properties if we need to ignore certain content type
                        ignoreTypes = scriptingService.retrieveAppProperty('player.appspaceapp.stream.contenttype');
                    }

                    var ignore = false;
                    if (ignoreTypes && opt && opt.type) {
                        ignoreTypes = ignoreTypes.toLowerCase().split(',');
                        ignore = (ignoreTypes.indexOf(opt.type.toLowerCase()) == -1) ? false : true;
                    }

                    // if we're supposed to load it locally,
                    // check if device support local flash playback, if not, don't resolve it
                    if (!ignore && opt && opt.type && opt.type.toLowerCase() == 'flash') {
                        var supportFlashLocalPlayback = halPlatformService.getFeatureSupport('media.flash.local.playback');
                        if (supportFlashLocalPlayback == 'no') {
                            ignore = true;
                        }

                        // if we are forcing it to ignore, check if the user set to bypass restriction directly
                        if (ignore) {
                            // check from widget property
                            if (opt && opt.bypassFlashLocalPlayback == true) {
                                ignore = false;
                            } else {
                                // check from player property
                                var prop = scriptingService.retrieveAppProperty('player.bypass.flash.local.playback.restriction');
                                ignore = (prop && prop.toLowerCase() == 'true') ? false : true;
                            }
                        }
                    }

                    if (ignore) {
                        deferred.reject();
                        return deferred.promise;
                    }

                    // Only route content from appspace as the origin
                    var appspaceUrl = configService.get('appspace', 'appspaceURL').toLowerCase();
                    if (url.toLowerCase().indexOf('c:\\fastnetv2') == -1 && url.toLowerCase().indexOf(appspaceUrl) == -1) {
                        deferred.reject();
                        return deferred.promise;
                    }

                    // All urls locally should be lower case
                    url = url.toLowerCase();

                    var fileFound = function(url) {
                        // For cards, if the device doesn't support local playback for online cards, stream it directly
                        if (opt && opt.type && opt.type.toLowerCase() == 'card') {
                            // Extract path from the url
                            var segs = url.split('/');
                            var cardPath = '';
                            for (var i = 0; i < segs.length - 1; i++) {
                                cardPath += (segs[i] + '/');
                            }

                            // Load the card manifest to check if the card requires a connection
                            var onManifestFileResolved = function(resolvedManifestUrl) {
                                halNetworkService.getJSON(resolvedManifestUrl).then(function(cardInfo) {
                                    if (cardUtil.isStreamingCard(cardInfo)) {
                                        logService.debug(LOG_KEY.SYNC, 'Force streaming card ' + cardInfo.Id);
                                        deferred.reject();
                                        return;
                                    }

                                    deferred.resolve(url);
                                }, deferred.reject);
                            };

                            halMediaService.resolveContentPath(cardPath + 'manifest.json').then(onManifestFileResolved);
                        } else {
                            // Resolve to local file system
                            deferred.resolve(url);
                        }
                    };

                    var repository = (opt || {}).repository || CACHE_REPOSITORY.CONTENT;
                    // Check do we have a local cache for the url
                    getCacheFilePath(repository, url).then(fileFound, deferred.reject);
                    return deferred.promise;
                }
            };

            // This router checks if the file is local
            var fontLocalrouting = {
                name: 'font-local-routing',
                priority: 1,
                route: function(url, opt) {
                    var deferred = $q.defer();
                    // Check do we have a local cache for the url
                    getCacheFilePath('font.cache', url).then(deferred.resolve, function() {
                        // No local cache found
                        logService.debug(LOG_KEY.SYNC, 'Font not found in cache: ' + url);
                        // Attempt to download to cache and resolve with the new local path url
                        cache.cacheFile('font.cache', url).then(function(localUrl) {
                            logService.info(LOG_KEY.SYNC, 'Dynamically cached font: ' + url);
                            // Attempt to retrieve the local path now that its cached
                            getCacheFilePath('font.cache', url).then(deferred.resolve, deferred.reject);
                        }, function() {
                            logService.warn(LOG_KEY.SYNC, 'Failed to cache font: ' + url);
                            deferred.reject();
                        });
                    });
                    return deferred.promise;
                }
            };

            var cdnRouting = {
                name: 'appspace-content-cdn-routing',
                priority: 10,
                route: function(url, opt) {
                    var deferred = $q.defer();
                    // Only route content from appspace as the origin
                    var appspaceUrl = getAppspaceUrl();
                    if (url.toLowerCase().indexOf('c:\\fastnetv2') == -1 && url.toLowerCase().indexOf(appspaceUrl) == -1) {
                        deferred.reject();
                        return deferred.promise;
                    }

                    if (url.toLowerCase().indexOf('/fastnetdata/') == -1 && url.toLowerCase().indexOf('imagepreview.axd') == -1) {
                        deferred.reject();
                        return deferred.promise;
                    }

                    var cdn = cdnService.getActiveCDN();
                    if (cdn) {
                        var result = cdnService.resolveUrl(url, appspaceUrl, cdn.url, cdn.type);
                        if (result) {
                            deferred.resolve(result);
                        } else {
                            deferred.reject('cdn type (' + cdn.type + ') doesn\'t support url:' + url);
                        }
                    } else {
                        deferred.reject('no active cdn');
                    }

                    return deferred.promise;
                }
            };

            // This router checks if its an external image, and if so if we need to cache
            var frameworkRouting = {
                name: 'framework-content-local-routing',
                priority: 5,
                route: function(url, opt) {
                    opt = opt || {};
                    opt.type = (opt.type || '').toLowerCase();
                    // We only route to local content if no external images are supported (e.g chrome)
                    if (halPlatformService.getFeatureSupport('network.allowexternalresources') == 'yes' ||
                        // Only route content from external web URLS
                        (url.toLowerCase().indexOf('http://') == -1 && url.toLowerCase().indexOf('https://') == -1) ||
                        // If content is not an image, don't process it
                        opt.type != 'image') {
                        return $q.reject();
                    }

                    var promise = $q.defer();
                    logService.debug(LOG_KEY.SYNC, 'Looking for dynamically cached image: ' + url);
                    // Check do we have a local cache for the url
                    getCacheFilePath('framework.cache', url).then(promise.resolve, function() {
                        // No local cache found
                        logService.debug(LOG_KEY.SYNC, 'Dynamically cached image not found. Downloading: ' + url);
                        cache.cacheFile('framework.cache', url, true).then(promise.resolve, function() {
                            logService.warn(LOG_KEY.SYNC, 'Failed to dynamically cache image: ' + url);
                            promise.reject();
                        });
                    });
                    return promise.promise;
                }
            };

            // Used by framework media object for a final resolve
            var frameworkRouterService = {
                name: 'framework.media.image',
                maps: [],
                routes: [frameworkRouting]
            };

            var signsRouterService = {
                name: 'signs-content-routing-service',
                maps: [map, propertyResolveMap],
                routes: [cdnRouting]
            };
            var wpgRouterService = {
                name: 'signs-wpg-routing-service',
                maps: [wpgMap, propertyResolveMap]
            };

            var playRouterService = {
                name: 'play-content-routing-service',
                maps: [map, propertyResolveMap],
                routes: [frameworkRouting, cdnRouting]
            };

            var fontRouterService = {
                name: 'font-routing-service',
                maps: [propertyResolveMap, map],
                routes: [cdnRouting]
            };

            // Check does this device support local storage, if so we add the local router
            var lsSupport = halPlatformService.getFeatureSupport('file.localstorage');
            if (lsSupport == 'yes') {
                logService.info(LOG_KEY.SYNC, 'Platform supports local storage, content caching is enabled ');
                signsRouterService.routes.push(localrouting);
                playRouterService.routes.push(localrouting);
                fontRouterService.routes.push(fontLocalrouting);
            } else {
                logService.info(LOG_KEY.SYNC, 'Platform does not support local storage, content caching is not enabled ');
            }

            router.addService(signsRouterService);
            router.addService(wpgRouterService);
            router.addService(playRouterService);
            router.addService(frameworkRouterService);
            router.addService(fontRouterService);
        }

        function initSystemHotKeys() {

        }

        var remoteBehaviour = {};
        remoteBehaviour[REMOTEVCODE.volume_up] = {
            callback: function(event) {
                event.preventDefault();
                halMediaService.volumeUp();
            },
            supportedEventType: [REMOTEEVENTTYPE.buttonRepeat, REMOTEEVENTTYPE.buttonDown]
        };

        remoteBehaviour[REMOTEVCODE.volume_down] = {
            callback: function(event) {
                event.preventDefault();
                halMediaService.volumeDown();
            },
            supportedEventType: [REMOTEEVENTTYPE.buttonRepeat, REMOTEEVENTTYPE.buttonDown]
        };

        remoteBehaviour[REMOTEVCODE.mute] = {
            callback: function(event) {
                event.preventDefault();
                halMediaService.toggleVolumeMute();
            },
            supportedEventType: [REMOTEEVENTTYPE.buttonDown]
        };

        remoteBehaviour[REMOTEVCODE.system] = {
            callback: function(event) {
                if ($location.url().toLowerCase().indexOf('/tv/deviceoperation') != -1) {
                    return;
                }

                event.preventDefault();
                $scope.showMenu = !$scope.showMenu;
            },
            supportedEventType: [REMOTEEVENTTYPE.buttonDown]
        };

        remoteBehaviour[REMOTEVCODE.back] = {
            callback: function(event) {
                if ($scope.showMenu) {
                    $scope.showMenu = false;
                    event.preventDefault();
                }
            },
            supportedEventType: [REMOTEEVENTTYPE.buttonDown]
        };

        // Event handler for remote events
        var onRemoteEvent = function(remoteEvent) {
            var re = remoteEvent;
            var behaviour = remoteBehaviour[re.vcode] || {};
            behaviour.supportedEventType = behaviour.supportedEventType || [];
            if (!behaviour.callback || behaviour.supportedEventType.indexOf(re.eventType) < 0) {
                return;
            }

            behaviour.callback(re);
        };

        $scope.showOverlayMask = function() {
            getElement('#overlayMask').show();
        };

        $scope.hideOverlayMask = function() {
            getElement('#overlayMask').hide();
        };

        function setCursorVisibility() {
            cursorVisibilility = (getProperty(PLAYER_PROPERTY_KEYS.CURSOR_VISIBILITY) || (dataUtil.isValueTrue(getProperty(PLAYER_PROPERTY_KEYS.CURSOR_VISIBILITY_ENABLED) || 'true') ? CURSOR_VISIBILITY.AUTO : CURSOR_VISIBILITY.HIDDEN)).toLowerCase();
            if (cursorVisibilility === CURSOR_VISIBILITY.VISIBLE) {
                showMouse();
                return;
            }

            hideMouse();
        }

        function onMouseMove(e) {
            // if we receive mouse move event but it is the same as previous position, ignore it
            // this is an issue in brightsign when changing layouts once a mouse has been plugged out
            if (previousMousePosition &&
                previousMousePosition.x == e.screenX &&
                previousMousePosition.y == e.screenY) {
                return;
            }

            previousMousePosition = {x: e.screenX, y: e.screenY};
            if (cursorVisibilility === CURSOR_VISIBILITY.VISIBLE || cursorVisibilility === CURSOR_VISIBILITY.HIDDEN) {
                return;
            }

            showMouse();
            TimeoutUtil.cancel(hidePromise);
            hidePromise = $timeout(hideMouse, hideMouseTimeoutHandler);
        }

        function showMouse() {
            halMediaService.setCursorVisible(true);
        }

        function hideMouse() {
            halMediaService.setCursorVisible(false);
        }

        function appDisplayUpdateEvent(val) {
            $scope.appShow = val ? {'visibility': 'visible'} : {'visibility': 'hidden'};
        }

        function onBackmostVideoLayerEventFired(event, state) {
            $scope.isForcedTransparentBackground = state.enable;
            // manual class for body
            var classMethod = state.enable ? 'addClass' : 'removeClass';
            $angular.element('body')[classMethod](TRANSPARENT_BACKGROUND_CLASS);
        }

        function processQueryParameters() {
            var params = sessionService.getUrlParameters();
            if (halPlatformService.isUrlPolicyModeEnforced()) {
                init();
                return;
            }

            NetworkUtil.validateIdInjection(params).then(function() {
                RegistrationUtil.processQueryInjection();
            }, function(err) {
                logService.debug(LOG_KEY.DEFAULT, err.errormessage);
                if (err.errorCode != ASERRORCODE.INCOMPLETE_INJECTION_PARAMETERS && err.errorCode != ASERRORCODE.NO_INJECTION_PARAMETERS) {
                    $location.path(ERROR_PAGE_PATH);
                }
            }).finally(init);
        }

        // refactor to make it driver based operations
        function onPlatformMessages(payload) {
            payload = payload || {};
            var type = (payload.type || '').toLowerCase();
            switch (type) {
                case CRESTRON_INTENT_MESSAGE.PAUSE_APP:
                    processCrestronMessage(CRESTRON_MODE.STANDBY);
                    break;
                case CRESTRON_INTENT_MESSAGE.RESUME_APP:
                    processCrestronMessage(CRESTRON_MODE.UNOCCUPIED);
                    halPlatformService.sendPlatformMessage({
                        type: CRESTRON_INTENT_MESSAGE.RESUMED_APP
                    });
                    break;
            }
        }

        coreFrameworkService.isReady().then(function() {
            initSignsContentRouter();
            processQueryParameters();
        });

        function onConstellationTokensRefreshed(constellationTokens) {
            logService.debug(LOG_KEY.DEFAULT, 'Device constellation tokens updated');
            deviceTokenService.save(constellationTokens);
        }

        function onConstellationTokensFailedToRefresh(data) {
            if (!data) {
                return;
            }

            logService.warn(LOG_KEY.DEFAULT, 'Constellation tokens failed to refresh - ', JSON.stringify(data));
            telemetryService.addToQueue({
                module: 'device-authorization',
                type: 'jwt-refresh-failure',
                description: 'JWT failed to refresh',
                metadata: {
                    'request_payload': JSON.stringify(data.payload),
                    'response': JSON.stringify(data.error),
                },
            });
        }

        function getAppspaceUrl() {
            return UrlUtil.removeTrailingSlash((configService.get('appspace', 'appspaceURL') || '').toLowerCase());
        }
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    app.directive('asBroadcastAlert', function() {
        return {
            templateUrl: 'commondirectives/asBroadcastAlert.html',
            scope: {
                currentBroadcast: '=',
                notifyCurrentBroadcastComplete: '=',
                onCurrentBroadcastComplete: '&'
            },
            link: function(scope) {
                scope.$watch('currentBroadcast', scope.processBroadcast);
            },
            controller: ["$scope", "$timeout", "syncService", "dataUtil", "CONTENT_TYPE", function(
                $scope,
                $timeout,
                syncService,
                dataUtil,
                CONTENT_TYPE
            ) {
                var DEFAULT_MEDIA_ITEMS = [];
                var INITIAL_PLAYBACK_INDEX = 0;
                var DEFAULT_TEXT_TIMEOUT_DURATION = 30000;
                var DEFAULT_CHANNEL_TIMEOUT_DURATION = 60000;
                var currentPlaybackIndex = INITIAL_PLAYBACK_INDEX;
                var notifyCompleteTimeout;
                var presenterApi;

                $scope.DEFAULT_PLAYBACK_DURATION = 8;
                $scope.mediaItems = DEFAULT_MEDIA_ITEMS;
                $scope.onItemComplete = processNextPlaylistItem;
                $scope.onItemLoadError = onItemLoadError;
                $scope.customStyle = {
                    position: 'relative'
                };
                $scope.$on('$destroy', function() {
                    cancelNotifyCompleteTimeout();
                    unloadPresenter();
                });

                $scope.onPresenterApiReady = function(api) {
                    unloadPresenter();
                    presenterApi = api;
                };

                $scope.processBroadcast = function() {
                    $scope.currentItem = undefined;
                    $scope.mediaItems = DEFAULT_MEDIA_ITEMS;
                    cancelNotifyCompleteTimeout();
                    if (!$scope.currentBroadcast) {
                        return;
                    }

                    if ($scope.currentBroadcast.isText) {
                        if ($scope.notifyCurrentBroadcastComplete) {
                            notifyCompleteTimeout = $timeout(notifyComplete, DEFAULT_TEXT_TIMEOUT_DURATION);
                        }

                        return;
                    }

                    if (!$scope.currentBroadcast.isFullscreenContent && !$scope.currentBroadcast.isMedia) {
                        return;
                    }

                    if ($scope.currentBroadcast.isChannel) {
                        $scope.currentItem = createChannelPreviewItem();
                        if ($scope.notifyCurrentBroadcastComplete && $scope.currentBroadcast.isLiveChannel) {
                            notifyCompleteTimeout = $timeout(notifyComplete, DEFAULT_CHANNEL_TIMEOUT_DURATION);
                        }

                        return;
                    }

                    $scope.mediaItems = createMediaPlaylist();
                    currentPlaybackIndex = INITIAL_PLAYBACK_INDEX;
                    $scope.currentItem = $scope.mediaItems[currentPlaybackIndex];
                };

                function unloadPresenter() {
                    if (!presenterApi) {
                        return;
                    }

                    presenterApi.pause();
                    presenterApi.unload();
                    presenterApi = undefined;
                }

                function notifyComplete() {
                    cancelNotifyCompleteTimeout();
                    if (!$scope.onCurrentBroadcastComplete) {
                        return;
                    }

                    $scope.onCurrentBroadcastComplete();
                }

                function onItemLoadError() {
                    processNextPlaylistItem(undefined, true);
                }

                function processNextPlaylistItem(item, isEndOfPlaylist) {
                    if (notifyCompleteTimeout) {
                        // Ignore if timer to notify complete has already been set
                        return;
                    }

                    if ($scope.currentBroadcast.isChannel) {
                        if (isEndOfPlaylist && $scope.notifyCurrentBroadcastComplete) {
                            notifyComplete();
                        }

                        return;
                    }

                    var mediaItemsCount = $scope.mediaItems.length;
                    if (mediaItemsCount === 0) {
                        $scope.currentItem = undefined;
                        return;
                    }

                    var endOfPlaylist = currentPlaybackIndex >= mediaItemsCount - 1;
                    if ($scope.notifyCurrentBroadcastComplete && endOfPlaylist) {
                        $scope.currentItem = undefined;
                        currentPlaybackIndex = INITIAL_PLAYBACK_INDEX;
                        notifyComplete();
                        return;
                    }

                    currentPlaybackIndex = endOfPlaylist ?
                        INITIAL_PLAYBACK_INDEX : currentPlaybackIndex + 1;
                    $scope.currentItem = $angular.copy($scope.mediaItems[currentPlaybackIndex]);
                }

                function createMediaItem(mediaContent) {
                    var mediaDisplayType = (mediaContent.contentType || '').toLowerCase();
                    if (!mediaDisplayType) {
                        return;
                    }

                    return {
                        options: {
                            listenToCompleteEvent: true
                        },
                        data: {
                            properties: syncService.getCurrentAppProperties()
                        },
                        contentId: mediaContent.contentId,
                        mediaType: mediaDisplayType,
                        mediaUrl: mediaContent.mediaUrl,
                        playback: {
                            contentDuration: $scope.DEFAULT_PLAYBACK_DURATION
                        }
                    };
                }

                function createMediaPlaylist() {
                    var mediaContentItems = $angular.copy($scope.currentBroadcast.content.mediaContentItems);
                    if (!dataUtil.isArray(mediaContentItems)) {
                        return DEFAULT_MEDIA_ITEMS;
                    }

                    var mediaPlaylist = [];
                    for (var i = 0; i < mediaContentItems.length; i++) {
                        var mediaContent = mediaContentItems[i];
                        if (!mediaContent ||
                            !mediaContent.contentId) {
                            continue;
                        }

                        var mediaItem = createMediaItem(mediaContent);
                        if (!mediaItem) {
                            continue;
                        }

                        mediaPlaylist.push(mediaItem);
                    }

                    return mediaPlaylist;
                }

                function createChannelPreviewItem() {
                    return {
                        options: {
                            listenToCompleteEvent: true,
                            listenToPlaybackErrorEvent: true
                        },
                        data: {
                            properties: syncService.getCurrentAppProperties()
                        },
                        contentId: $scope.currentBroadcast.id,
                        mediaType: CONTENT_TYPE.SIGNS,
                        mediaUrl: $scope.currentBroadcast.mediaUrl
                    };
                }

                function cancelNotifyCompleteTimeout() {
                    if (!notifyCompleteTimeout) {
                        return;
                    }

                    $timeout.cancel(notifyCompleteTimeout);
                    notifyCompleteTimeout = undefined;
                }
            }]
        };
    });
})(window.angular);

(function ($angular) {

    "use strict";

    var app = $angular.module('signsHTML5');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('asDeviceMenuIcon', function () {
        return {
            restrict: 'EA',
            replace: 'true',
            templateUrl: 'commondirectives/asdevicemenuicon.html',
            scope: {
            },
            link: function (scope, element, attrs) {
            },
            controller: ["$scope", "halRemoteService", "RemoteEvent", "REMOTEEVENTTYPE", "REMOTEVCODE", "sessionService", "coreFrameworkService", "halPlatformService", function ($scope, halRemoteService,RemoteEvent,REMOTEEVENTTYPE,REMOTEVCODE, sessionService, coreFrameworkService, halPlatformService) {
                $scope.isDeviceMenuHidden = false;
                function init(){
                    $scope.isDeviceMenuHidden = sessionService.get('passiveRegistration') || halPlatformService.getFeatureSupport('support.device.menu') === 'no';
                }
                $scope.onDeviceMenuClick = function () {
                    if ($scope.isDeviceMenuHidden) {
                        return;
                    }
                    var event = {eventtype: REMOTEEVENTTYPE.buttonDown, vcode: REMOTEVCODE.system};
                    halRemoteService.raiseRemoteEvent(new RemoteEvent(event));
                }
                coreFrameworkService.isReady().then(init);
            }]
        }
    });
})(window.angular);
(function(angular) {
    'use strict';
    var app = angular.module('signsHTML5');

    app.directive('asEditorGrid', ["$q", "EDITOR_SETTINGS", function($q, EDITOR_SETTINGS) {
        return {
            restrict: 'E',
            replace: 'true',
            template: '<div ng-show="show" ng-style=gridStyle></div>',
            scope: {
                onApiReady: '='
            },
            link: function(scope) {
                scope.$watch('configuration', function(newVal) {
                    scope.setGrid(newVal);
                });

                scope.onApiReady({
                    setVisualScale: scope.setVisualScale,
                    setGridConfiguration: scope.setGridConfiguration
                });
            },
            controller: ["$scope", function($scope) {
                var visualScale = EDITOR_SETTINGS.DEFAULT_VISUAL_SCALE;
                var currentConfig;
                var size;
                var defaultColor = '#000';
                var defaultBackgroundColor = 'transparent';
                $scope.show = false;
                $scope.$on('$destroy', function() {
                    $scope.gridStyle = undefined;
                });

                $scope.setGrid = function(config) {
                    if (!config || !config.show) {
                        size = undefined;
                        $scope.gridStyle = {};
                        return;
                    }
                    size = isNaN(config.size) ? EDITOR_SETTINGS.MIN_GRID_SIZE : Math.max(config.size, EDITOR_SETTINGS.MIN_GRID_SIZE);
                    size = Math.min(size, EDITOR_SETTINGS.MAX_GRID_SIZE);
                    var color = config.color || defaultColor;
                    var backgroundColor = config.backgroundColor || defaultBackgroundColor;
                    visualScale = config.visualScale || visualScale;
                    var styleConfig = {
                        size: size,
                        color: color,
                        backgroundColor: backgroundColor
                    };
                    render(styleConfig);
                    return styleConfig;
                };

                $scope.setGridConfiguration = function(config) {
                    $scope.show = config.show;
                    return $q.resolve($scope.setGrid(config));
                };

                function render(config) {
                    currentConfig = config;
                    if (!currentConfig) {
                        $scope.gridStyle = {};
                        return;
                    }
                    var gridBorder = Math.max(1, Math.round(1/visualScale)) + 'px';
                    $scope.gridStyle = {
                        'width': '100%',
                        'height': '100%',
                        'background-image': 'linear-gradient(0deg, ' + config.color + ' ' + gridBorder + ', transparent 1px), linear-gradient(90deg, ' + config.color + ' ' + gridBorder + ', ' + config.backgroundColor + ' 1px)',
                        'background-size': config.size + 'px ' + config.size + 'px',
                    };
                }

                $scope.setVisualScale = function(scale) {
                    visualScale = scale || EDITOR_SETTINGS.DEFAULT_VISUAL_SCALE;
                    render(currentConfig);
                };
            }]
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    app.directive('asFallbackContent', function() {
        return {
            templateUrl: 'commondirectives/asFallbackContent.html',
            scope: {
                content: '=',
                isUnregistered: '=',
            },
            link: function(scope) {
                scope.$watch('content', scope.processContent);
            },
            controller: ["$q", "$scope", "syncService", "networkContentRouterService", "dataUtil", function(
                $q,
                $scope,
                syncService,
                networkContentRouterService,
                dataUtil
            ) {
                var DEFAULT_MEDIA_ITEMS = [];
                var DEFAULT_STRETCH = 'uniformfill';
                var INITIAL_PLAYBACK_INDEX = 0;
                var DEFAULT_PLAYBACK_DURATION = 8;
                var CONTENT_ROUTING = {
                    SERVICE: 'signs-content-routing-service',
                    REPOSITORY: 'signs.fallbackcontent'
                };
                var currentPlaybackIndex = INITIAL_PLAYBACK_INDEX;
                var presenterApi;

                $scope.mediaItems = DEFAULT_MEDIA_ITEMS;
                $scope.onItemComplete = processNextPlaylistItem;
                $scope.onItemLoadError = onItemLoadError;
                $scope.defaultStretch = DEFAULT_STRETCH;

                $scope.$on('$destroy', function() {
                    unloadPresenter();
                });

                $scope.onPresenterApiReady = function(api) {
                    unloadPresenter();
                    presenterApi = api;
                };

                $scope.processContent = function() {
                    $scope.currentItem = undefined;
                    $scope.mediaItems = DEFAULT_MEDIA_ITEMS;
                    if (!$scope.content) {
                        return;
                    }

                    createMediaPlaylist().then(function(playlist) {
                        $scope.mediaItems = playlist;
                        currentPlaybackIndex = INITIAL_PLAYBACK_INDEX;
                        $scope.currentItem = $scope.mediaItems[currentPlaybackIndex];
                    });
                };

                function unloadPresenter() {
                    if (!presenterApi) {
                        return;
                    }

                    presenterApi.pause();
                    presenterApi.unload();
                    presenterApi = undefined;
                }

                function onItemLoadError() {
                    processNextPlaylistItem(undefined, true);
                }

                function processNextPlaylistItem() {
                    var mediaItemsCount = $scope.mediaItems.length;
                    if (mediaItemsCount === 0) {
                        $scope.currentItem = undefined;
                        return;
                    }

                    var endOfPlaylist = currentPlaybackIndex >= mediaItemsCount - 1;
                    if ($scope.notifyCurrentBroadcastComplete && endOfPlaylist) {
                        $scope.currentItem = undefined;
                        currentPlaybackIndex = INITIAL_PLAYBACK_INDEX;
                        return;
                    }

                    currentPlaybackIndex = endOfPlaylist ?
                        INITIAL_PLAYBACK_INDEX : currentPlaybackIndex + 1;
                    $scope.currentItem = $angular.copy($scope.mediaItems[currentPlaybackIndex]);
                }

                function getContentDuration() {
                    return $scope.content && dataUtil.isNumber($scope.content.contentDuration) && dataUtil.isMoreThanZero($scope.content.contentDuration) ?
                        $scope.content.contentDuration : DEFAULT_PLAYBACK_DURATION;
                }

                function resolveMediaUrl(url, mediaType) {
                    var deferred = $q.defer();
                    var mediaUrl = url;
                    var onUrlResolved = function() {
                        deferred.resolve(mediaUrl);
                    };

                    var onMediaResolved = function(resolvedMediaUrl) {
                        mediaUrl = resolvedMediaUrl;
                        onUrlResolved();
                    };

                    networkContentRouterService.resolve(mediaUrl, CONTENT_ROUTING.SERVICE, {
                        type: mediaType,
                        repository: CONTENT_ROUTING.REPOSITORY,
                    }).then(onMediaResolved);
                    return deferred.promise;
                }

                function createMediaItem(mediaContent) {
                    var deferred = $q.defer();
                    var mediaDisplayType = (mediaContent.contentType || '').toLowerCase();
                    resolveMediaUrl(mediaContent.mediaUrl, mediaDisplayType).then(function(mediaUrl) {
                        deferred.resolve({
                            options: {
                                listenToCompleteEvent: true
                            },
                            data: {
                                properties: syncService.getSavedProperties()
                            },
                            contentId: mediaContent.contentId,
                            mediaType: mediaDisplayType,
                            mediaUrl: mediaUrl,
                            playback: {
                                contentDuration: getContentDuration()
                            }
                        });
                    });
                    return deferred.promise;
                }

                function createMediaPlaylist() {
                    var mediaContentItems = $angular.copy($scope.content.mediaContentItems);
                    if (!dataUtil.isArray(mediaContentItems)) {
                        return $q.resolve(DEFAULT_MEDIA_ITEMS);
                    }

                    var deferred = $q.defer();
                    var mediaItemCreations = [];
                    var mediaPlaylist = [];
                    for (var i = 0; i < mediaContentItems.length; i++) {
                        var mediaContent = mediaContentItems[i];
                        if (!mediaContent ||
                            !mediaContent.contentId) {
                            continue;
                        }

                        mediaItemCreations.push(createMediaItem(mediaContent).then(function(mediaItem) {
                            if (!mediaItem) {
                                return;
                            }

                            mediaPlaylist.push(mediaItem);
                        }));
                    }

                    $q.allSettled(mediaItemCreations).then(function() {
                        deferred.resolve(mediaPlaylist);
                    });

                    return deferred.promise;
                }
            }]
        };
    });
})(window.angular);

(function ($angular) {

    "use strict";

    var app = $angular.module('signsHTML5');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('asFullscreenSpinner', function () {

        return {
            restrict: 'EA',
            replace: 'true',
            templateUrl: 'commondirectives/asfullscreenspinnertemplate.html',
            scope: {
                show: '@',
                message: '@',
                animateduration: '='
            },
            link: function (scope, element, attrs) {
                scope.$watch('animateduration', function(newVal) {
                    scope.setAnimateDuration(newVal);
                });

                scope.$watch('show', function(newVal) {
                    scope.toggleSpinner(newVal);
                });

                scope.$watch('message', function(newVal) {
                    scope.updateMessage(newVal);
                });
                
            },
            controller: ["$scope", "$location", "$element", function ($scope, $location, $element) {
                $scope.message = "";
                $scope.displaySpinner = "none";
                var duration = 0;
                
                function init() {
                    //Handle view destroy
                    $scope.$on('$destroy', function() {
                        // clean up stuff
                        
                    });
                }

                $scope.setAnimateDuration = function(val){
                    if(val !== undefined){
                        duration = val;
                    }
                }

                $scope.toggleSpinner = function(show) {
                    var spinner = $("#spinner");
                    spinner.velocity("stop");
                    if(show == true || show == "true") {
                        $scope.displaySpinner = "";
                        spinner.velocity({opacity:1}, {duration: duration});
                    }
                    else {
                        $scope.displaySpinner = "none";
                        spinner.velocity({opacity:0}, {duration: duration, display:"none"});
                    }
                };
                
                $scope.updateMessage = function(msg) {
                    $scope.message = msg;
                };

                init();
            }]
        };
    });


})(window.angular);
(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('asKeyboard', ["$q", "FileReader", "TimeoutUtil", function(
        $q,
        FileReader,
        TimeoutUtil) {
        return {
            restrict: 'EA',
            replace: 'true',
            templateUrl: 'commondirectives/askeyboard.html',
            scope: {
                show: '@',
                layout: '@',
                avoidPreventDefault: '@',
                inputValue: '=',
                onApiReady: '&',
                onFocusChange: '&',
                onKeySelected: '&',
                onValueChange: '&',
                onActionSelected: '&',
                onKeyboardEdge: '&'
            },
            link: function(scope, element, attrs) {
                scope.$watch('show', function(newVal) {
                    scope.toggleKeyboard(newVal);
                });

                scope.$watch('layout', function(newVal) {
                    if (newVal) {
                        scope.loadLayout(newVal);
                    }
                });

                scope.$watch('inputValue', function(newVal) {
                    scope.updateInputValue(newVal);
                });

                scope.$on('$destroy', function() {
                    scope.destroy();
                });

                element.on('$destroy', function() {
                    scope.destroy();
                });

                // set avoidPreventDefault
                if (attrs.avoidPreventDefault == 'false') {
                    scope.avoidPreventDefault = false;
                }

                // Construct the api interface
                scope.api = {
                    upKey: scope.upKey,
                    downKey: scope.downKey,
                    leftKey: scope.leftKey,
                    rightKey: scope.rightKey,
                    focusKey: scope.focusKey,
                    selectKey: scope.selectKey,
                    clearInputValue: scope.clearInputValue,
                    backKey: scope.backKey,
                    getInputValue: scope.getInputValue,
                    setInputValue: scope.setInputValue,
                    getLayout: scope.getLayout,
                    addLayout: scope.addLayout,
                    importLayouts: scope.importLayouts,
                    setLayout: scope.setLayout,
                    enableRemoteNavigation: scope.enableRemoteNavigation,
                    removeHighlight: scope.removeHighlight,
                    registerKey: scope.registerKey,
                    unRegisterKey: scope.unRegisterKey,
                    unfocusKey: scope.unfocusKey
                };

                if (scope.onApiReady) {
                    scope.onApiReady({api: scope.api});
                }
            },
            controller: ["$scope", "$location", "$element", "$timeout", "$compile", "halRemoteService", "REMOTEVCODE", "REMOTEEVENTTYPE", "halPlatformService", function($scope, $location, $element, $timeout, $compile,
                halRemoteService, REMOTEVCODE, REMOTEEVENTTYPE, halPlatformService) {
                $scope.displayKeyboard = 'none';
                var layouts = [];
                var currentLayout = null;
                var currentRowIndex = null;
                var currentColumnPos = null;
                var focusKey = null;
                var focusEl = null;
                var inputValue = '';
                var remoteNavEnabled = false;
                var layoutScope = null;
                var runningKeyId = 1;
                var firstKey = '';
                var highlightTimeoutHandler;
                var removeSelectedKeyClassPromise;
                $scope.avoidPreventDefault = true;

                function init() {
                    // init default layouts
                    initDefaultLayouts();

                    // load alphanumeric layout by default
                    $scope.loadLayout('alphanumeric');
                }

                function initDefaultLayouts() {
                    $scope.addLayout(initCapitalAlphanumericLayout());
                    $scope.addLayout(initLowerCaseAlphanumericLayout());
                }

                function cancelHighlightTimeoutHandler() {
                    TimeoutUtil.cancel(highlightTimeoutHandler);
                    highlightTimeoutHandler = undefined;
                }

                $scope.destroy = function() {
                    if (layoutScope) {
                        layoutScope.$destroy();
                        layoutScope = null;
                    }

                    cancelHighlightTimeoutHandler();
                    // unregister remote event
                    if (remoteNavEnabled) {
                        halRemoteService.unRegisterEvents(onRemoteEvent);
                    }
                };

                $scope.getLayout = function(layoutName) {
                    if (layoutName) {
                        for (var i = 0; i < layouts.length; i++) {
                            var l = layouts[i];
                            if (l.name == layoutName) {
                                return angular.copy(l);
                            }
                        }
                    }

                    return null;
                };

                $scope.addLayout = function(layout) {
                    if (layout) {
                        // assign auto id to all keys
                        for (var r = 0; r < layout.rows.length; r++) {
                            var row = layout.rows[r];
                            for (var k = 0; k < row.length; k++) {
                                var key = row[k];
                                key.id = runningKeyId++;
                            }
                        }

                        layouts.push(layout);
                    }

                    return layouts;
                };

                $scope.importLayouts = function(file) {
                    var deferred = $q.defer();
                    var onFileSuccess = function(res) {
                        if (!res) {
                            deferred.resolve(null);
                            return;
                        }

                        // clear default layouts
                        layouts = [];
                        for (var i = 0; i < res.layouts.length; i++) {
                            $scope.addLayout(res.layouts[i]);
                        }

                        // load first layout by default
                        loadLayoutObj(layouts[0]);

                        // return promise
                        deferred.resolve(layouts);
                    };

                    var onFileError = function(err) {
                        deferred.resolve(null);
                    };

                    if (file) {
                        // read the file
                        var fileReader = new FileReader();
                        fileReader.read(file).then(onFileSuccess, onFileError);
                    } else {
                        deferred.resolve(null);
                    }

                    return deferred.promise;
                };

                $scope.setLayout = function(layout) {
                    if (layout) {
                        $scope.loadLayout(layout.name);
                    }
                };

                $scope.enableRemoteNavigation = function(enable) {
                    remoteNavEnabled = enable;
                    if (enable) {
                        // Register for remote input
                        halRemoteService.registerEvents(onRemoteEvent);
                    } else {
                        halRemoteService.unRegisterEvents(onRemoteEvent);
                    }
                };

                $scope.toggleKeyboard = function(show) {
                    var keyboardEl = $element.find('#keyboardContainer');
                    keyboardEl.velocity('stop');
                    if (show == true || show == 'true') {
                        $scope.displayKeyboard = '';
                        keyboardEl.velocity({opacity: 1}, {duration: 300});
                    } else {
                        $scope.displayKeyboard = 'none';
                        keyboardEl.velocity({opacity: 0}, {duration: 300, display: 'none'});
                    }
                };

                $scope.loadLayout = function(layoutName) {
                    var layout = $scope.getLayout(layoutName);
                    loadLayoutObj(layout);
                };

                $scope.registerKey = function() {
                    window.document.addEventListener('keydown', keyDownHandler);
                };

                $scope.unRegisterKey = function() {
                    window.document.removeEventListener('keydown', keyDownHandler);
                };

                function keyDownHandler(e) {
                    for (var r = 0; r < currentLayout.rows.length; r++) {
                        var row = currentLayout.rows[r];
                        for (var k = 0; k < row.length; k++) {
                            var key = row[k];
                            key.shift = key.shift ? key.shift : false;
                            if (key.keyCode == e.which && key.shift == e.shiftKey) {
                                $scope.onSelectKey(key.id);
                                return;
                            }
                        }
                    }
                }

                function loadLayoutObj(layout) {
                    // if no valid layout
                    if (layout == null) {
                        // load alphanumeric
                        layout = initLowerCaseAlphanumericLayout();
                    }

                    // draw keyboard layout
                    drawLayout(layout);

                    // set current layout
                    currentLayout = layout;

                    // focus on first key
                    firstKey = layout.rows[0][0];
                    if (firstKey) {
                        $scope.focusKey(firstKey.value);
                    }
                }

                function drawLayout(layout) {
                    if (layout) {
                        // empty current elements
                        if (layoutScope) {
                            layoutScope.$destroy();
                            layoutScope = null;
                        }

                        $element.empty();
                        var html = [];
                        for (var r = 0; r < layout.rows.length; r++) {
                            var row = layout.rows[r];
                            html.push('<div class="row">');
                            for (var k = 0; k < row.length; k++) {
                                var key = row[k];
                                html.push('<div id="'); html.push(key.id); html.push('"');
                                html.push(' as-click="onSelectKey('); html.push(key.id); html.push(')"');
                                html.push(' class="key noselect');
                                switch (key.width) {
                                    case 2:
                                        html.push(' key-2x');
                                        break;
                                    case 3:
                                        html.push(' key-3x');
                                        break;
                                    case 4:
                                        html.push(' key-4x');
                                        break;
                                    case 5:
                                        html.push(' key-5x');
                                        break;
                                    case 6:
                                        html.push(' key-6x');
                                        break;
                                }

                                if (key.cssClass) {
                                    html.push(' '); html.push(key.cssClass);
                                }

                                html.push('">'); html.push(key.label); html.push('</div>');
                            }

                            html.push('</div>');
                        }

                        var template = html.join('');

                        // Compile
                        layoutScope = $scope.$new();
                        var el = $compile(template)(layoutScope);
                        // append row to container
                        $element.append(el);
                    }
                }

                // Event handler for remote events
                var onRemoteEvent = function(remoteEvent) {
                    // Only listen to down button events
                    if (remoteEvent.eventType != REMOTEEVENTTYPE.buttonDown) {
                        return;
                    }

                    // Perform action based on Virtual code
                    switch (remoteEvent.vcode) {
                        case REMOTEVCODE.up: // Up Key
                            $scope.upKey();
                            break;
                        case REMOTEVCODE.down: // Down Key
                            $scope.downKey();
                            break;
                        case REMOTEVCODE.left: // Left Key
                            $scope.leftKey();
                            break;
                        case REMOTEVCODE.right: // Right Key
                            $scope.rightKey();
                            break;
                        case REMOTEVCODE.select: // option/alt Key
                            $scope.selectKey();
                            break;
                        case REMOTEVCODE.back: // command/windows Key
                            $scope.backKey();
                            break;
                    }

                    if ($scope.avoidPreventDefault == true) {
                        remoteEvent.preventDefault();
                    }
                };

                // On Up clicked
                $scope.upKey = function() {
                    // get the correct key
                    var key = getUpKey();

                    if (key) {
                        // fire onfocuschange event
                        if ($scope.onFocusChange) {
                            $scope.onFocusChange({previousKey: focusKey, currentKey: key});
                        }

                        // set selected key
                        focusKey = key;
                        // hightlight the key
                        highlightKey(key.id);
                    } else {
                        // fire onKeyboardEdge event
                        if ($scope.onKeyboardEdge) {
                            $scope.onKeyboardEdge({edge: 'top'});
                        }
                    }
                };

                // On Down clicked
                $scope.downKey = function() {
                    // get the correct key
                    var key = getDownKey();

                    if (key) {
                        // fire onfocuschange event
                        if ($scope.onFocusChange) {
                            $scope.onFocusChange({previousKey: focusKey, currentKey: key});
                        }

                        // set selected key
                        focusKey = key;
                        // hightlight the key
                        highlightKey(key.id);
                    } else {
                        // fire onKeyboardEdge event
                        if ($scope.onKeyboardEdge) {
                            $scope.onKeyboardEdge({edge: 'down'});
                        }
                    }
                };

                // On Left clicked
                $scope.leftKey = function() {
                    // get the correct key
                    var key = getLeftKey();

                    if (key) {
                        // fire onfocuschange event
                        if ($scope.onFocusChange) {
                            $scope.onFocusChange({previousKey: focusKey, currentKey: key});
                        }

                        // set selected key
                        focusKey = key;
                        // hightlight the key
                        highlightKey(key.id);
                    } else {
                        // fire onKeyboardEdge event
                        if ($scope.onKeyboardEdge) {
                            $scope.onKeyboardEdge({edge: 'left'});
                        }
                    }
                };

                // On Right clicked
                $scope.rightKey = function() {
                    // get the correct key
                    var key = getRightKey();

                    if (key) {
                        // fire onfocuschange event
                        if ($scope.onFocusChange) {
                            $scope.onFocusChange({previousKey: focusKey, currentKey: key});
                        }

                        // set selected key
                        focusKey = key;
                        // hightlight the key
                        highlightKey(key.id);
                    } else {
                        // fire onKeyboardEdge event
                        if ($scope.onKeyboardEdge) {
                            $scope.onKeyboardEdge({edge: 'right'});
                        }
                    }
                };

                // On back clicked
                $scope.backKey = function() {
                    // send back action
                    var back = {id: -1, label: 'BACK', value: 'back', type: 'action', width: 2, cssClass: ''};
                    // fire onActionSelected event
                    if ($scope.onActionSelected) {
                        $scope.onActionSelected({key: back});
                    }
                };

                $scope.onFocusKey = function(id) {
                    for (var r = 0; r < currentLayout.rows.length; r++) {
                        var row = currentLayout.rows[r];
                        for (var k = 0; k < row.length; k++) {
                            var key = row[k];
                            if (key.id == id) {
                                $scope.focusKey(key.value);
                                return;
                            }
                        }
                    }
                };

                // Focus on specific key
                $scope.focusKey = function(value) {
                    // get the correct key
                    var key = value ? getKey(value) : firstKey;

                    if (key) {
                        // fire onfocuschange event
                        if ($scope.onFocusChange) {
                            $scope.onFocusChange({previousKey: focusKey, currentKey: key});
                        }

                        // set selected key
                        focusKey = key;
                        // hightlight the key
                        highlightKey(key.id);
                    }
                };

                $scope.unfocusKey = function() {
                    focusKey = null;
                    $scope.removeHighlight();
                };

                // select a key
                $scope.onSelectKey = function(id) {
                    for (var r = 0; r < currentLayout.rows.length; r++) {
                        var row = currentLayout.rows[r];
                        for (var k = 0; k < row.length; k++) {
                            var key = row[k];
                            if (key.id == id) {
                                $scope.selectKey(key.value);
                                return;
                            }
                        }
                    }
                };

                // select a key
                $scope.selectKey = function(value) {
                    // if value is not null, focus on the key
                    if (value != null) {
                        $scope.focusKey(value);
                    }

                    // if there is a key currently in focus, select it
                    if (focusKey != null) {
                        // update css
                        if (focusEl) {
                            // simulate button click
                            focusEl.addClass('key-selected');
                            if (removeSelectedKeyClassPromise) {
                                $timeout.cancel(removeSelectedKeyClassPromise);
                            }

                            removeSelectedKeyClassPromise = $timeout(function() {
                                // remove key-selected class from all elements under this directive
                                $element.find('.key-selected').removeClass('key-selected');
                                removeSelectedKeyClassPromise = undefined;
                            }, 50);
                        }

                        var newVal = null;
                        switch (focusKey.type) {
                            case 'key':
                                // fire onKeySelected event
                                if ($scope.onKeySelected) {
                                    $scope.onKeySelected({key: focusKey});
                                }

                                if (inputValue) {
                                    newVal = inputValue + focusKey.value;
                                } else {
                                    newVal = focusKey.value;
                                }

                                $scope.updateInputValue(newVal);
                                break;
                            case 'action':
                                // fire onActionSelected event
                                if ($scope.onActionSelected) {
                                    $scope.onActionSelected({key: focusKey});
                                }

                                switch (focusKey.value) {
                                    // delete the last character
                                    case 'delete':
                                        if (inputValue != null && inputValue.length > 0) {
                                            newVal = inputValue.substring(0, inputValue.length - 1);
                                            $scope.updateInputValue(newVal);
                                        }

                                        break;
                                    // clear the whole input string
                                    case 'clear':
                                        newVal = '';
                                        $scope.updateInputValue(newVal);
                                        break;
                                    // return the whole input string
                                    case 'done':
                                        $scope.getInputValue();
                                        break;
                                    // shift between capital and lowercase layout
                                    /* case "shift":
                                        if (currentLayout.name == "ALPHANUMERIC") {
                                            $scope.loadLayout("alphanumeric");
                                            $scope.focusKey("shift");
                                        }
                                        else {
                                            $scope.loadLayout("ALPHANUMERIC");
                                            $scope.focusKey("shift");
                                        }
                                        break;*/
                                    default:
                                        if ($scope.onActionSelected) {
                                            $scope.onActionSelected(focusKey);
                                        }

                                        break;
                                }

                                        break;
                                }
                        }
                    };

                // return input value
                $scope.getInputValue = function() {
                    return inputValue;
                };

                // set input value
                $scope.setInputValue = function(newVal) {
                    if (newVal == null) {
                        newVal = '';
                    }

                    inputValue = newVal;
                };

                $scope.clearInputValue = function() {
                    inputValue = '';
                };

                // update input value
                $scope.updateInputValue = function(newValue) {
                    if (newValue == null) {
                        newValue = '';
                    }

                    var previousValue = inputValue;
                    inputValue = newValue;

                    // fire onValueChange event
                    if ($scope.onValueChange && (previousValue != newValue)) {
                        $scope.onValueChange({previousValue: previousValue, currentValue: newValue});
                    }
                };

                // Remove key highlight
                $scope.removeHighlight = function() {
                    if (focusEl) {
                        focusEl.removeClass('key-focus');
                    }
                };

                // get key from the row above
                function getUpKey() {
                    // if no key selected, select the first one
                    if (focusKey == null) {
                        currentRowIndex = 0;
                        currentColumnPos = 0;
                        return currentLayout.rows[0][0];
                    } else {
                        // check that currentRow is not the first one
                        if (currentRowIndex > 0) {
                            // select prev row
                            currentRowIndex -= 1;

                            // select a column directly above the current column based on width
                            var rowKeys = currentLayout.rows[currentRowIndex];
                            var startPos = 0;
                            for (var i = 0; i < rowKeys.length; i++) {
                                var k = rowKeys[i];
                                // if colPos is within the same boundary, select the key
                                var endPos = startPos + k.width;
                                if (startPos <= currentColumnPos && endPos > currentColumnPos) {
                                    currentColumnPos = startPos;
                                    return k;
                                } else {
                                    startPos = endPos;
                                }
                            }
                        }
                    }

                    return null;
                }

                // get key from the row below
                function getDownKey() {
                    // if no key selected, select the first one
                    if (focusKey == null) {
                        currentRowIndex = 0;
                        currentColumnPos = 0;
                        return currentLayout.rows[0][0];
                    } else {
                        // check that currentRow is not the last one
                        if (currentRowIndex < (currentLayout.rows.length - 1)) {
                            // select next row
                            currentRowIndex += 1;

                            // select a column directly below the current column based on width
                            var rowKeys = currentLayout.rows[currentRowIndex];
                            var startPos = 0;
                            for (var i = 0; i < rowKeys.length; i++) {
                                var k = rowKeys[i];
                                // if colPos is within the same boundary, select the key
                                var endPos = startPos + k.width;
                                if (startPos <= currentColumnPos && endPos > currentColumnPos) {
                                    currentColumnPos = startPos;
                                    return k;
                                } else {
                                    startPos = endPos;
                                }
                            }
                        }
                    }

                    return null;
                }

                // get next key to the left
                function getLeftKey() {
                    // if no key selected, select the first one
                    if (focusKey == null) {
                        currentRowIndex = 0;
                        currentColumnPos = 0;
                        return currentLayout.rows[0][0];
                    } else {
                        var rowKeys = currentLayout.rows[currentRowIndex];
                        var keyPos = 0;
                        for (var i = 0; i < rowKeys.length; i++) {
                            var k = rowKeys[i];
                            // find the current key
                            if (k == focusKey) {
                                // check that the current key is not the first one in the row
                                if (i == 0) {
                                    return null;
                                } else {
                                    // store current column position
                                    currentColumnPos = keyPos - k.width;
                                    // if the current key is not the first one, return the previous program
                                    return rowKeys[i - 1];
                                }
                            }

                            // store key position based on width
                            keyPos += k.width;
                        }
                    }

                    return null;
                }

                // get next key to the right
                function getRightKey() {
                    // if no key selected, select the first one
                    if (focusKey == null) {
                        currentRowIndex = 0;
                        currentColumnPos = 0;
                        return currentLayout.rows[0][0];
                    } else {
                        var rowKeys = currentLayout.rows[currentRowIndex];
                        var keyPos = 0;
                        for (var i = 0; i < rowKeys.length; i++) {
                            var k = rowKeys[i];
                            // store key position based on width
                            keyPos += k.width;
                            // find the current key
                            if (k == focusKey) {
                                // if the current key is not the last one, return the next program
                                if (i < (rowKeys.length - 1)) {
                                    // store current column position
                                    currentColumnPos = keyPos;
                                    return rowKeys[i + 1];
                                }
                            }
                        }
                    }

                    return null;
                }

                // get key from the row above
                function getKey(value) {
                    if (value != null) {
                        for (var r = 0; r < currentLayout.rows.length; r++) {
                            var rowKeys = currentLayout.rows[r];
                            var keyPos = 0;
                            for (var i = 0; i < rowKeys.length; i++) {
                                var k = rowKeys[i];
                                if (k.value == value) {
                                    currentRowIndex = r;
                                    currentColumnPos = keyPos;
                                    return k;
                                }

                                // store key position based on width
                                keyPos += k.width;
                            }
                        }
                    }

                    return null;
                }

                // Highlight key
                function highlightKey(keyId) {
                    $scope.removeHighlight();

                    cancelHighlightTimeoutHandler();
                    var lastRemoveEvent = halRemoteService.getLastEventTime();
                    // if there is no remote event, auto remove highlight
                    if (!lastRemoveEvent) {
                        highlightTimeoutHandler = $timeout(function() {
                            $scope.removeHighlight();
                        }, 100);
                    }

                    // find the key element
                    var keyEl = null;
                    var rows = $element.children();
                    var found = false;
                    if (rows) {
                        for (var i = 0; i < rows.length; i++) {
                            var cells = rows[i].children;
                            if (cells) {
                                for (var k = 0; k < cells.length; k++) {
                                    var c = $(cells[k]);
                                    if (c.attr('id') == keyId) {
                                        keyEl = c;
                                        found = true;
                                        break;
                                    }
                                }
                            }

                            if (found) {
                                break;
                            }
                        }
                    }

                    if (keyEl) {
                        // highlight the new key
                        keyEl.addClass('key-focus');
                        focusEl = keyEl;
                    }
                }

                function initCapitalAlphanumericLayout() {
                    var layout = {
                        name: 'ALPHANUMERIC',
                        rows: [
                            [
                                {label: 'A', value: 'A', type: 'key', width: 1},
                                {label: 'B', value: 'B', type: 'key', width: 1},
                                {label: 'C', value: 'C', type: 'key', width: 1},
                                {label: 'D', value: 'D', type: 'key', width: 1},
                                {label: 'E', value: 'E', type: 'key', width: 1},
                                {label: 'F', value: 'F', type: 'key', width: 1}
                            ],
                            [
                                {label: 'G', value: 'G', type: 'key', width: 1},
                                {label: 'H', value: 'H', type: 'key', width: 1},
                                {label: 'I', value: 'I', type: 'key', width: 1},
                                {label: 'J', value: 'J', type: 'key', width: 1},
                                {label: 'K', value: 'K', type: 'key', width: 1},
                                {label: 'L', value: 'L', type: 'key', width: 1}
                            ],
                            [
                                {label: 'M', value: 'M', type: 'key', width: 1},
                                {label: 'N', value: 'N', type: 'key', width: 1},
                                {label: 'O', value: 'O', type: 'key', width: 1},
                                {label: 'P', value: 'P', type: 'key', width: 1},
                                {label: 'Q', value: 'Q', type: 'key', width: 1},
                                {label: 'R', value: 'R', type: 'key', width: 1}
                            ],
                            [
                                {label: 'S', value: 'S', type: 'key', width: 1},
                                {label: 'T', value: 'T', type: 'key', width: 1},
                                {label: 'U', value: 'U', type: 'key', width: 1},
                                {label: 'V', value: 'V', type: 'key', width: 1},
                                {label: 'W', value: 'W', type: 'key', width: 1},
                                {label: 'X', value: 'X', type: 'key', width: 1}
                            ],
                            [
                                {label: 'Y', value: 'Y', type: 'key', width: 1},
                                {label: 'Z', value: 'Z', type: 'key', width: 1},
                                {label: '1', value: '1', type: 'key', width: 1},
                                {label: '2', value: '2', type: 'key', width: 1},
                                {label: '3', value: '3', type: 'key', width: 1},
                                {label: '4', value: '4', type: 'key', width: 1}
                            ],
                            [
                                {label: '5', value: '5', type: 'key', width: 1},
                                {label: '6', value: '6', type: 'key', width: 1},
                                {label: '7', value: '7', type: 'key', width: 1},
                                {label: '8', value: '8', type: 'key', width: 1},
                                {label: '9', value: '9', type: 'key', width: 1},
                                {label: '0', value: '0', type: 'key', width: 1}
                            ],
                            [
                                {label: '.', value: '.', type: 'key', width: 1},
                                {label: '_', value: '_', type: 'key', width: 1},
                                {label: '@', value: '@', type: 'key', width: 1},
                                {label: '.com', value: '.com', type: 'key', width: 1, cssClass: 'key-small'},
                                {label: '!', value: '!', type: 'key', width: 1},
                                {label: '\'', value: '\'', type: 'key', width: 1}
                            ],
                            [
                                {label: 'SPACE', value: ' ', type: 'key', width: 2, cssClass: 'key-small'},
                                {label: 'DELETE', value: 'delete', type: 'action', width: 2, cssClass: 'key-small'},
                                {label: 'CLEAR', value: 'clear', type: 'action', width: 2, cssClass: 'key-small'}
                            ],
                            [
                                {label: 'Next', value: 'next', type: 'action', width: 6, cssClass: 'key-small key-primary'}
                            ]
                        ]
                    };
                    return layout;
                }

                function initLowerCaseAlphanumericLayout() {
                    var layout = {
                        name: 'alphanumeric',
                        rows: [
                            [
                                {label: 'a', value: 'a', type: 'key', width: 1},
                                {label: 'b', value: 'b', type: 'key', width: 1},
                                {label: 'c', value: 'c', type: 'key', width: 1},
                                {label: 'd', value: 'd', type: 'key', width: 1},
                                {label: 'e', value: 'e', type: 'key', width: 1},
                                {label: 'f', value: 'f', type: 'key', width: 1}
                            ],
                            [
                                {label: 'g', value: 'g', type: 'key', width: 1},
                                {label: 'h', value: 'h', type: 'key', width: 1},
                                {label: 'i', value: 'i', type: 'key', width: 1},
                                {label: 'j', value: 'j', type: 'key', width: 1},
                                {label: 'k', value: 'k', type: 'key', width: 1},
                                {label: 'l', value: 'l', type: 'key', width: 1}
                            ],
                            [
                                {label: 'm', value: 'm', type: 'key', width: 1},
                                {label: 'n', value: 'n', type: 'key', width: 1},
                                {label: 'o', value: 'o', type: 'key', width: 1},
                                {label: 'p', value: 'p', type: 'key', width: 1},
                                {label: 'q', value: 'q', type: 'key', width: 1},
                                {label: 'r', value: 'r', type: 'key', width: 1}
                            ],
                            [
                                {label: 's', value: 's', type: 'key', width: 1},
                                {label: 't', value: 't', type: 'key', width: 1},
                                {label: 'u', value: 'u', type: 'key', width: 1},
                                {label: 'v', value: 'v', type: 'key', width: 1},
                                {label: 'w', value: 'w', type: 'key', width: 1},
                                {label: 'x', value: 'x', type: 'key', width: 1}
                            ],
                            [
                                {label: 'y', value: 'y', type: 'key', width: 1},
                                {label: 'z', value: 'z', type: 'key', width: 1},
                                {label: '1', value: '1', type: 'key', width: 1},
                                {label: '2', value: '2', type: 'key', width: 1},
                                {label: '3', value: '3', type: 'key', width: 1},
                                {label: '4', value: '4', type: 'key', width: 1}
                            ],
                            [
                                {label: '5', value: '5', type: 'key', width: 1},
                                {label: '6', value: '6', type: 'key', width: 1},
                                {label: '7', value: '7', type: 'key', width: 1},
                                {label: '8', value: '8', type: 'key', width: 1},
                                {label: '9', value: '9', type: 'key', width: 1},
                                {label: '0', value: '0', type: 'key', width: 1}
                            ],
                            [
                                {label: '.', value: '.', type: 'key', width: 1},
                                {label: '_', value: '_', type: 'key', width: 1},
                                {label: '@', value: '@', type: 'key', width: 1},
                                {label: '.com', value: '.com', type: 'key', width: 1, cssClass: 'key-small'},
                                {label: '!', value: '!', type: 'key', width: 1},
                                {label: '\'', value: '\'', type: 'key', width: 1}
                            ],
                            [
                                {label: 'SPACE', value: ' ', type: 'key', width: 2, cssClass: 'key-small'},
                                {label: 'DELETE', value: 'delete', type: 'action', width: 2, cssClass: 'key-small'},
                                {label: 'CLEAR', value: 'clear', type: 'action', width: 2, cssClass: 'key-small'}
                            ],
                            [
                                {label: 'Next', value: 'next', type: 'action', width: 6, cssClass: 'key-small key-primary'}
                            ]
                        ]
                    };
                    return layout;
                }

                init();
            }]
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var app = $angular.module('signsHTML5');

    app.directive('asPwaCacheService', function() {
        return {
            restrict: 'EA',
            scope: {
                onApiReady: '&'
            },
            link: function(scope, element) {
                scope.$on('$destroy', scope.destroy);

                element.on('$destroy', function() {
                    scope.destroy(true);
                });

                scope.api = {
                    postMessage: scope.postMessage,
                    createWebFrames: scope.createWebFrames,
                    clearWebFrames: scope.clearWebFrames
                };

                if (!scope.onApiReady) {
                    return;
                }

                scope.onApiReady({api: scope.api});
            },
            controller: ["$scope", "$element", "dataUtil", function(
                $scope,
                $element,
                dataUtil
            ) {
                var IFRAME_TEMPLATE = '<iframe '
                + 'id="{{ID}}" '
                + 'frameBorder="0" '
                + 'width="0px" '
                + 'height="0px" '
                + 'src="about:blank"></iframe>';
                var IFRAME_ID_POSTFIX = 'pwa-cache-service-webframe-{{ID}}';
                var IFRAME_ID = '{{ID}}';
                var webFrames = [];

                $scope.createWebFrames = function(urls) {
                    if (!urls || !urls.length) {
                        return;
                    }

                    for (var i = 0; i < urls.length; i++) {
                        var frame = $angular.element(IFRAME_TEMPLATE.replace(IFRAME_ID, IFRAME_ID_POSTFIX.replace(IFRAME_ID, i)))[0];
                        frame.src = urls[i];
                        webFrames.push(frame);
                        $element.append(frame);
                    }

                    return webFrames;
                };

                $scope.postMessage = function postMessageToContent(message) {
                    webFrames.forEach(function(webFrame) {
                        if (!webFrame || !webFrame.contentWindow || !webFrame.contentWindow.postMessage ||
                            (message.frameSrc && !dataUtil.includesString(webFrame.src, message.frameSrc))
                        ) {
                            return;
                        }

                        webFrame.contentWindow.postMessage(message, '*');
                    });
                };

                $scope.clearWebFrames = function() {
                    webFrames.forEach(function(webFrame) {
                        if (!webFrame) {
                            return;
                        }

                        webFrame.src = 'about:blank';
                        webFrame.remove();
                    });
                    $element.empty();
                    webFrames = [];
                };
            }]
        };
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    app.directive('asRegistrationBar', function() {
        return {
            templateUrl: 'commondirectives/asRegistrationBar.html',
            controller: ["$scope", "RegistrationUtil", "rendezvousService", function(
                $scope,
                RegistrationUtil,
                rendezvousService
            ) {
                var REGISTRATION_MESSAGE = {};
                var RENDEZVOUS_EVENT = {
                    ON_CODE: 'on-code',
                    OFFLINE: 'offline',
                    SERVICE_DOWN: 'service-down',
                };
                REGISTRATION_MESSAGE[RENDEZVOUS_EVENT.ON_CODE] = 'Register your device';
                REGISTRATION_MESSAGE[RENDEZVOUS_EVENT.OFFLINE] = 'Connect this device to the internet to retrieve your code and register online';
                REGISTRATION_MESSAGE[RENDEZVOUS_EVENT.SERVICE_DOWN] = 'Contact Appspace support for more information. Looks like we cannot retrieve your registration code';
                resetDisplay();
                rendezvousService.subscribe(onRendezvousEvent);

                $scope.$on('$destroy', function() {
                    resetDisplay();
                    rendezvousService.unsubscribe(onRendezvousEvent);
                });

                function resetDisplay() {
                    $scope.registrationQrLink = '';
                    $scope.registrationCode = '';
                    $scope.registrationMessage = '';
                }

                function updateCode(code) {
                    if ($scope.registrationCode === code) {
                        return;
                    }

                    $scope.registrationCode = code;
                    $scope.registrationQrLink = RegistrationUtil.getQrClaimCodeUrl(code);
                }

                function updateMessage(event) {
                    $scope.registrationMessage = REGISTRATION_MESSAGE[event] || '';
                }

                function onRendezvousEvent(event) {
                    if (!event) {
                        return;
                    }

                    updateMessage(event.event);
                    switch (event.event) {
                        case RENDEZVOUS_EVENT.ON_CODE:
                            updateCode(event.code);
                            break;
                        case RENDEZVOUS_EVENT.OFFLINE:
                        case RENDEZVOUS_EVENT.SERVICE_DOWN:
                            updateCode('');
                            break;
                    }
                }
            }]
        };
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('asSideMenu', function() {
        return {
            restrict: 'EA',
            replace: 'true',
            templateUrl: 'commondirectives/assidemenu.html',
            scope: {
                onApiReady: '&',
                onMenuItemSelected: '&'
            },
            link: function(scope, element, attrs) {
                scope.$on('$destroy', function() {
                    scope.destroy();
                });

                element.on('$destroy', function() {
                    scope.destroy();
                });

                // Construct the api interface
                scope.api = {
                    enableRemoteNavigation: scope.enableRemoteNavigation,
                    highlightMenuItem: scope.highlightMenuItem,
                    resetFocusedItem: scope.resetFocusedItem,
                    resizeSideMenu: scope.resizeSideMenu,
                    setItems: scope.setItems
                };

                scope.resizeSideMenu();

                if (scope.onApiReady) {
                    scope.onApiReady({api: scope.api});
                }
            },
            controller: ["$scope", "$location", "$element", "$timeout", "$compile", "halRemoteService", "REMOTEVCODE", "REMOTEEVENTTYPE", function($scope, $location, $element, $timeout, $compile,
                halRemoteService, REMOTEVCODE, REMOTEEVENTTYPE) {
                $scope.menuItems = [];
                $scope.selectedItem = null;
                var gridApi = null;
                var remoteNavEnabled = false;
                var remoteBounded = false;
                var previousSideMenuWidth = -1;
                var previousSideMenuHeight = -1;

                function init() {
                    remoteBounded = false;
                    $timeout(function() {
                        // highlight first item by default
                        if ($scope.menuItems != null && $scope.menuItems.length > 0) {
                            $scope.highlightMenuItem($scope.menuItems[0], 0);
                        }
                    }, 0);
                }
                // on api ready
                $scope.onGridApi = function(api) {
                    gridApi = api;
                };

                // on item selected
                $scope.onGridItem = function(api, offset, fromClick) {
                    if (fromClick || $scope.selectedItem == $scope.gridItems[offset]) {
                        $timeout(function() {
                            onActionSelect();
                        }, 100);
                    }
                    $scope.selectedItem = $scope.gridItems[offset];
                };

                $scope.setItems = function(items) {
                    var elements = [];
                    if (items.length > 0) {
                        for (var i = 0; i < items.length; i++) {
                            var e = createGridElement(items[i]);
                            elements.push(e);
                        }
                        $scope.gridItems = elements;
                    }
                };

                function createGridElement(item) {
                    if (item && item.text.length > 0 && item.text != ' ' && item.value) {
                        var markup = '<a class="list-group-item" id="side_' + item.value + '">' + item.text + '</a>';
                        var t = {type: 'markup', markup: markup, value: item.value};
                        return t;
                    }
                    return null;
                }

                $scope.destroy = function() {
                    // unregister remote event
                    if (remoteNavEnabled) {
                        halRemoteService.unRegisterEvents(onRemoteEventSideMenu);
                    }
                };

                $scope.enableRemoteNavigation = function(enable) {
                    remoteNavEnabled = enable;
                    if (enable && !remoteBounded) {
                        remoteBounded = true;
                        // Register for remote input
                        halRemoteService.registerEvents(onRemoteEventSideMenu);
                    } else if (!enable) {
                        remoteBounded = false;
                        halRemoteService.unRegisterEvents(onRemoteEventSideMenu);
                    }
                };

                $scope.highlightMenuItem = function(index) {
                    gridApi.moveToItem(index);
                };

                $scope.resetFocusedItem = function() {
                    $scope.selectedItem = null;
                    gridApi.resetFocusedItem();
                };

                // Event handler for remote events
                var onRemoteEventSideMenu = function(remoteEvent) {
                    // Only listen to down button events
                    if (remoteEvent.eventType != REMOTEEVENTTYPE.buttonDown) {
                        return;
                    }
                    // Perform action based on Virtual code
                    switch (remoteEvent.vcode) {
                        case REMOTEVCODE.up: // Up Key
                            onActionUp();
                            remoteEvent.preventDefault();
                            break;
                        case REMOTEVCODE.down: // Down Key
                            onActionDown();
                            remoteEvent.preventDefault();
                            break;
                        case REMOTEVCODE.select: // option/alt Key
                            onActionSelect();
                            remoteEvent.preventDefault();
                            break;
                    }
                };

                // On Up clicked
                function onActionUp() {
                    gridApi.upItem();
                }

                // On Down clicked
                function onActionDown() {
                    gridApi.downItem();
                }

                $scope.resizeSideMenu = function() {
                    var sideMenuWidth = $('#sideMenuContainer').width();
                    var sideMenuHeight = Math.floor($('.list-group').height() * 0.1);

                    if (previousSideMenuWidth != sideMenuWidth || previousSideMenuHeight != sideMenuHeight) {
                        previousSideMenuWidth = sideMenuWidth;
                        previousSideMenuHeight = sideMenuHeight;
                        $scope.gridConfig = {
                            padding: {top: 10, left: 0, right: 0, bottom: 10},
                            itemSpacing: {horizontal: 0, vertical: 0},
                            itemWidth: sideMenuWidth,
                            itemHeight: sideMenuHeight,
                            scrollDirection: 'vertical',
                            highlightTimeout: 0
                        };
                    }
                };

                // select a key
                function onActionSelect() {
                    // fire onMenuItemSelected event
                    if ($scope.onMenuItemSelected) {
                        $scope.onMenuItemSelected({item: $scope.selectedItem});
                    }
                }

                $scope.isSelected = function(item) {
                    return $scope.selectedItem === item;
                };

                init();
            }]
        };
    });
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');
    app.directive('asSocialBar', function() {
        var DEFAULT_ACTION_LINK_VALUE = undefined;
        var DEFAULT_QR_CODE_ENABLED_VALUE = true;
        var DEFAULT_EMPTY_AMOUNT = '';
        return {
            templateUrl: 'commondirectives/asSocialBar.html',
            restrict: 'EA',
            scope: {
                onApiReady: '&',
                currentPost: '=',
            },
            link: function(scope, element) {
                scope.reactionCount = DEFAULT_EMPTY_AMOUNT;
                scope.commentCount = DEFAULT_EMPTY_AMOUNT;
                scope.reactionIcons = [];
                scope.actionLink = DEFAULT_ACTION_LINK_VALUE;

                scope.$watch('currentPost', scope.setReactionContent);
                scope.$on('$destroy', scope.$destroy);
                element.on('$destroy', function() {
                    scope.$destroy(true);
                });
            },
            controller: ["$scope", "dataUtil", "REACTION_ICON", "socialAnimationService", "configurationService", "PLAYER_PROPERTY_KEYS", function($scope, dataUtil, REACTION_ICON, socialAnimationService, configurationService, PLAYER_PROPERTY_KEYS) {
                var currentReaction;
                var AVATAR_ANIMATION_DURATION = 2400; // 2.5s - 0.1s buffer
                var ENTER_EXIT_ANIMATION_DURATION = 400; // 0.5s - 0.1s buffer
                var REACTION_ANIMATION_DURATION = 1400; // 1.5s - 0.1s buffer
                var DEFAULT_REACTIONS_ANIMATIONS = [
                    {
                        animation: ' fadeIn',
                        duration: ENTER_EXIT_ANIMATION_DURATION,
                    },
                    {
                        animation: ' reaction-animation-user',
                        duration: AVATAR_ANIMATION_DURATION,
                        randomizeStopPosition: true,
                    },
                    {
                        animation: ' reaction-animation-emoji wobble infinite',
                        duration: REACTION_ANIMATION_DURATION,
                        hideAvatar: true,
                        showReaction: true,
                    },
                    {
                        animation: ' reaction-animation-exit bounceOut',
                        duration: ENTER_EXIT_ANIMATION_DURATION,
                    }
                ];

                function init() {
                    configurationService.subscribe(onConfigurationChanges);
                    setIsQrCodeEnabled(configurationService.getCurrentProperty(PLAYER_PROPERTY_KEYS.SOCIAL_QR_CODE_ENABLE));
                }

                $scope.setReactionContent = function(reactionContent) {
                    var newReactionContent = $angular.copy(reactionContent);
                    if (!newReactionContent || !newReactionContent.isReactionBarEnabled) {
                        $scope.actionLink = DEFAULT_ACTION_LINK_VALUE;
                        $scope.reactionIcons = [];
                        $scope.reactionCount = DEFAULT_EMPTY_AMOUNT;
                        $scope.commentCount = DEFAULT_EMPTY_AMOUNT;
                        socialAnimationService.clearReactions();
                        return;
                    }

                    if (currentReaction) {
                        // same post, process live reactions
                        if (currentReaction.id === newReactionContent.id) {
                            processLiveReactionsUpdate(newReactionContent);
                        } else {
                            // for different post, restart
                            socialAnimationService.clearReactions();
                        }
                    }

                    currentReaction = $angular.copy(newReactionContent);
                    $scope.reactionCount = dataUtil.formatNumber(currentReaction.reactionCount);
                    $scope.commentCount = dataUtil.formatNumber(currentReaction.commentCount) + ' comment' + (currentReaction.commentCount > 1 ? 's' : '');
                    $scope.actionLink = currentReaction.qrcodeLink || DEFAULT_ACTION_LINK_VALUE;
                    processInitialReactions(currentReaction);
                };

                function getAbbreviation(name) {
                    return name.split(' ').map(function(word) {
                        return word.charAt(0);
                    }).join('');
                }

                function processLiveReactionsUpdate(newReaction) {
                    // go through new each reaction
                    for (var i = 0; i < newReaction.reactionCounters.length; i++) {
                        var newCounter = newReaction.reactionCounters[i];
                        var newReactionType = newCounter.reactionType;
                        var newReactionCount = newCounter.count;
                        var currentCounter = {
                            reactionType: newReactionType,
                            count: 0
                        };

                        // get current reaction type
                        for (var j = 0; j < currentReaction.reactionCounters.length; j++) {
                            var counter = currentReaction.reactionCounters[j];
                            if (counter.reactionType === newReactionType) {
                                // update counter
                                currentCounter = counter;
                                break;
                            }
                        }

                        for (var k = 0; k < newReactionCount - currentCounter.count; k++) {
                            var animations = [].concat(DEFAULT_REACTIONS_ANIMATIONS);
                            var user = newCounter.users[k];
                            var reaction = REACTION_ICON[newReactionType.toUpperCase()];
                            socialAnimationService.addReaction({
                                type: newReactionType,
                                animations: animations,
                                avatarIcon: user && user.userAvatarUrl ? user.userAvatarUrl : undefined,
                                avatarAbbreviation: user && user.userName ? getAbbreviation(user.userName) : '',
                                reactionIcon: reaction.icon,
                                containerDomId: '#reaction-column',
                            });
                        }
                    }
                }

                function processInitialReactions(reactionContent) {
                    $scope.reactionIcons = [];
                    if (!reactionContent.reactionIconOrder) {
                        return;
                    }

                    for (var i = 0; i < reactionContent.reactionIconOrder.length; i++) {
                        var reaction = $angular.copy(REACTION_ICON[reactionContent.reactionIconOrder[i].toUpperCase()]);
                        if (!reaction) {
                            continue;
                        }

                        reaction.style.zIndex = reactionContent.reactionIconOrder.length - i;
                        $scope.reactionIcons.push(reaction);
                    }
                }

                function setIsQrCodeEnabled(value) {
                    $scope.isQrCodeEnabled = dataUtil.isNullOrUndefined(value) || dataUtil.parseBoolean(value) === true || dataUtil.trim(value) === '';
                }

                function onConfigurationChanges(properties) {
                    setIsQrCodeEnabled(properties ? properties[PLAYER_PROPERTY_KEYS.SOCIAL_QR_CODE_ENABLE] : DEFAULT_QR_CODE_ENABLED_VALUE);

                    $scope.$applyAsync();
                }

                init();
            }]
        };
    });
})(window.angular);

(function ($angular) {
	'use strict';
	var angular = $angular;
	var app = angular.module('signsHTML5');
	
	app.controller('TestCtrl', ["$scope", "$timeout", "halRemoteService", "REMOTEVCODE", "REMOTEEVENTTYPE", function ($scope, $timeout, halRemoteService, REMOTEVCODE, REMOTEEVENTTYPE) {
		var keyboardApi = null;
		$scope.inputValue = null;
		
		function init() {
			
			//Handle view destroy
			$scope.$on('$destroy', function() {
				// clean up stuff
				halRemoteService.unRegisterEvents(onRemoteEvent);
			});
			  
			//Register for remote input
    		halRemoteService.registerEvents(onRemoteEvent);
		}
		
		//Event handler for remote events
		var onRemoteEvent = function(remoteEvent) {
			//Only listen to down button events
			if(remoteEvent.eventType != REMOTEEVENTTYPE.buttonDown) {
				return;
			}
			//Perform action based on Virtual code
	    	switch(remoteEvent.vcode) {
				case REMOTEVCODE.up: //Up Key
					onActionUp();
					break;
				case REMOTEVCODE.down: //Down Key
					onActionDown();
					break;
				case REMOTEVCODE.left: //Left Key
					onActionLeft();
					break;
				case REMOTEVCODE.right: //Right Key
					onActionRight();
					break;
				case REMOTEVCODE.select: //option/alt Key
					onActionSelect();
					break;
				case REMOTEVCODE.back: //command/windows Key
					//onActionSelect();
					break;
			}
		}; 
		
		$scope.onKeyboardApi = function(api) {
			keyboardApi = api;
			keyboardApi.focusKey('Z');
		};
		
		$scope.onKeySelected = function(key) {
			console.log(key);
		}
		
		$scope.onFocusChange = function(previousKey, currentKey) {
			//console.log(previousKey);
			//console.log(currentKey);
		}
		
		$scope.onValueChange = function(previousValue, currentValue) {
			$scope.inputValue = currentValue;
		}
		
		//On Up clicked
		function onActionUp() {
			keyboardApi.upKey();
		}
		
		//On Down clicked
		function onActionDown(api) {
			keyboardApi.downKey();
		}
		
		//On Left clicked
		function onActionLeft() {
			keyboardApi.leftKey();
		}
		
		//On Right clicked
		function onActionRight() {
			keyboardApi.rightKey();
		}
		
		//On Right clicked
		function onActionSelect() {
			keyboardApi.selectKey();
		}
		
		init();
	}]);
})(window.angular);
(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');
    app.component('asContentNavigationButtons', {
        templateUrl: 'commondirectives/contentNavigationButtons/asContentNavigationButtons.html',
        bindings: {
            onPreviousClicked: '&',
            onNextClicked: '&'
        },
        controller: function() {
            var $ctrl = this;
            $ctrl.onContentPreviousClicked = function() {
                if (!$ctrl.onPreviousClicked) {
                    return;
                }

                $ctrl.onPreviousClicked();
            };

            $ctrl.onContentNextClicked = function() {
                if (!$ctrl.onNextClicked) {
                    return;
                }

                $ctrl.onNextClicked();
            };
        }
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');
    app.factory('ActionUtil', ["Utils", "x2js", function(Utils, x2js) {
        function convertStringToActions(actionString) {
            var o = x2js.xml_str2json(actionString);
            var actions = o.Actions.Action;

            if (!actions) {
                return [];
            }

            var res = [];
            // if not an array, turn it into an array of one item
            if (!(actions instanceof Array)) {
                actions = [actions];
            }
            var utils = new Utils();
            for (var i = 0; i < actions.length; i++) {
                var a = actions[i];
                var action = {};
                for (var k in a) {
                    action[utils.toCamelCase(k)] = a[k];
                }
                res.push(action);
            }
            return res;
        }

        function convertStringToChannelActions(actionString) {
            var res = {action: ''};

            var action = actionString.split('&');
            res.action = action[0];

            for (var i = 1; i < action.length; i++) {
                var q = action[i].split('=');
                if (q[0] && q[1]) {
                    res[q[0]] = decodeURIComponent(q[1]);
                }
            }

            return res;
        }

        // Define the instance methods and properties
        return {
            // Public interface
            convertStringToActions: convertStringToActions,
            convertStringToChannelActions: convertStringToChannelActions
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');

    app.factory('claimDeviceUtil', ["$q", "$location", "ROUTE", "deviceStateService", "deviceTokenService", "halNetworkService", "halPlatformService", "deviceAPIService", "logService", "jwtUtil", "RegistrationUtil", function(
        $q,
        $location,
        ROUTE,
        deviceStateService,
        deviceTokenService,
        halNetworkService,
        halPlatformService,
        deviceAPIService,
        logService,
        jwtUtil,
        RegistrationUtil
    ) {
        var LOG_KEY = 'appspace.core.devicemanager';
        var STATUS_CODE = {
            UNAUTHORIZED: 401
        };
        var REDIRECTION_DELAY = 0;

        function claimDeviceInformation(accessToken) {
            var defer = $q.defer();
            var handleFailure = function(err) {
                var errorMessage = 'Failed to claim device information: ' + JSON.stringify(err);
                logService.error(LOG_KEY, errorMessage);
                defer.reject({
                    errorMessage: errorMessage,
                    originalError: err
                });
            };

            halNetworkService.getIpAddress().then(function(ipAddressResponse) {
                if (!ipAddressResponse || !ipAddressResponse.address) {
                    handleFailure('Invalid ip address retrieved: ' + JSON.stringify(ipAddressResponse));
                    return;
                }

                var ipAddress = ipAddressResponse.address;
                halPlatformService.getDeviceInfo().then(function(deviceInfo) {
                    if (!deviceInfo) {
                        handleFailure('Invalid device info retrieved: ' + JSON.stringify(deviceInfo));
                        return;
                    }

                    var data = {
                        deviceType: deviceInfo.appspace.deviceType,
                        deviceName: deviceInfo.appspace.referenceDeviceName,
                        applicationVersion: deviceInfo.software.appVersion,
                        deviceip: ipAddress,
                        fingerprint: deviceInfo.hardware.fingerprint.fingerprint,
                        accessToken: accessToken
                    };

                    deviceAPIService.claimDevice(data).then(function(response) {
                        var successClaimMessage = 'Device claimed registration information.';
                        var registrationInformation = null;
                        if (!response || !response.data ||!response.data.deviceJwt) {
                            handleFailure('Invalid response: ' + JSON.stringify(response));
                            return;
                        }

                        var deviceIdentity = jwtUtil.decode(response.data.deviceJwt);
                        if (!deviceIdentity || !deviceIdentity.metadata) {
                            handleFailure('Invalid device identity jwt: ' + JSON.stringify(deviceIdentity));
                            return;
                        }

                        registrationInformation = deviceIdentity.metadata;
                        if (!registrationInformation.accountId
                            || !registrationInformation.deviceId || !registrationInformation.instanceUrl
                            || !registrationInformation.instanceId) {
                            handleFailure('Invalid registration information: ' + JSON.stringify(registrationInformation));
                            return;
                        }

                        RegistrationUtil.saveRegistrationInfo(deviceIdentity.metadata);
                        logService.info(LOG_KEY, successClaimMessage);
                        defer.resolve(successClaimMessage);
                    }, handleFailure);
                }, handleFailure);
            }, handleFailure);

            return defer.promise;
        }

        function claimDevice(configuration) {
            var errorMessage = 'Failed to refresh token: ';
            var invalidConfigurationMessage = errorMessage + 'Invalid configuration: ' + JSON.stringify(configuration);
            var skipClaimMessage = 'Skip claiming device information. Device stored access token is valid.';
            if (!configuration || !configuration.registrationToken || !configuration.deviceGuid) {
                logService.error(LOG_KEY, invalidConfigurationMessage);
                return $q.reject({
                    originalError: {
                        status: STATUS_CODE.UNAUTHORIZED,
                        isUnauthorized: true,
                    },
                    errorMessage: invalidConfigurationMessage
                });
            }

            // Get current token to compare
            var deviceConstellationTokens = deviceTokenService.getConstellationTokens();
            var isDeviceRegistered = deviceStateService.isRegistered();
            if (isDeviceRegistered) {
                if (deviceConstellationTokens && configuration.registrationToken === deviceConstellationTokens.refreshToken) {
                    logService.info(LOG_KEY, skipClaimMessage);
                    return $q.resolve(skipClaimMessage);
                }

                // Clear registration info if token is different
                var redirectToRegistrationPage = function() {
                    $location.path(ROUTE.REGISTRATION);
                };

                RegistrationUtil.cleanUpRegistrationInfo(redirectToRegistrationPage, REDIRECTION_DELAY);
            }

            var defer = $q.defer();
            var handleFailRefreshToken = function(error) {
                if (!(error && error.errorMessage)) {
                    error = {
                        originalError: {
                            status: STATUS_CODE.UNAUTHORIZED,
                            isUnauthorized: true,
                            response: error
                        },
                        errorMessage: errorMessage + JSON.stringify(error)
                    };
                }

                logService.error(LOG_KEY, error.errorMessage);
                defer.reject(error);
            };

            // Else clear data, refresh token and claim device
            deviceTokenService.clear();
            deviceTokenService.refreshConstellationTokensByRefreshToken(configuration).then(function handleTokenRefreshed(tokens) {
                if (!tokens || !tokens.accessToken) {
                    handleFailRefreshToken({
                        originalError: {
                            status: STATUS_CODE.UNAUTHORIZED,
                            isUnauthorized: true,
                            response: tokens
                        },
                        errorMessage: errorMessage + 'Invalid stored access token: ' + JSON.stringify(tokens)
                    });
                    return;
                }

                claimDeviceInformation(tokens.accessToken).then(defer.resolve, defer.reject);
            }, handleFailRefreshToken);

            return defer.promise;
        }

        return {
            claimDevice: claimDevice,
        };
    }]);
})(window.angular);

(function ($angular) {
    "use strict";

    var app = $angular.module('signsHTML5');
    app.factory('ColorUtil', function () {
        // Define the constructor function.
        function ColorUtil() {

        }

        //Define the instance methods and properties
        ColorUtil.prototype = {
            //Public interface
            parseHTMLColor: parseHTMLColor,
            convertHexToRGBA:convertHexToRGBA
        };


        function parseHTMLColor(color) {
            var res;

            var tIndex = color.indexOf("#", 0);
            if (tIndex >= 0) {
                color = color.substring(tIndex + 1, color.length);
            }
            if (color.length == 6) {
                color = "ff" + color;
            }
            var channels = [];
            while (color.length >= 2) {

                var c = color.substring(0, 2);
                color = color.substring(2, color.length);
                channels.push(parseInt(c, 16));
            }

            res = 'rgba(' + channels[1] + ',' + channels[2] + ',' + channels[3] + ',' + (channels[0] / 255) + ')';

            return res;
        }
        
        function convertHexToRGBA(hex, returnNullForTransparent){
            if( !hex || hex.length < 3 ) {
				return null;
			}
            //Default is to return null if a zero alpha is provided
            if(returnNullForTransparent == undefined) {
                returnNullForTransparent = true;
            }
			hex = hex.replace('#','');
			if(hex.length == 8) {
				var a = parseInt(hex.substring(0,2), 16) / 255;
				var color = '#' + hex.substring(2,8);
                //If no alpha, and return null is set for transparent
				if(a == 0 && returnNullForTransparent) {
					//Skip if there is no opacity
					return null;
				}
				return {c:color, a:a}; 
			}
			else {
				var color = '#' + hex.substring(0,8);
				return {c:color, a:1};
			}
        }
        
        //Return the constructor for the class
        return (ColorUtil);
    });
})(window.angular);
(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    app.factory('deviceInfoUtil', ["$q", "storageLocalStorageService", "halNetworkService", function($q, storageLocalStorageService, halNetworkService) {
        function getDeviceGuid() {
            return storageLocalStorageService.get('device', 'playerguid') || '-';
        }

        function getServerUrl() {
            return storageLocalStorageService.get('appspace', 'appspaceURL') || '-';
        }

        function getDeviceIp() {
            var promise = $q.defer();
            halNetworkService.getIpAddress().then(function(res) {
                promise.resolve(res.address);
            });

            return promise.promise;
        }

        function getDeviceIdentifier() {
            var promise = $q.defer();

            var info = {};
            info.deviceGuid = getDeviceGuid();
            info.serverUrl = getServerUrl();
            var promises = [getDeviceIp()];
            $q.all(promises).then(function(res) {
                info.ip = res[0];
                promise.resolve(info);
            }, promise.reject);

            return promise.promise;
        }

        return {
            getDeviceIdentifier: getDeviceIdentifier,
            getDeviceGuid: getDeviceGuid,
            getServerUrl: getServerUrl,
            getDeviceIp: getDeviceIp
        };
    }]);
})(window.angular);

(function ($angular) {
    "use strict";

    var app = $angular.module('signsHTML5');
    app.factory('FontUtil', ["ColorUtil", "coreFontManagerService", function (ColorUtil, coreFontManagerService) {

        function reformatStyleToCSS(style) {
            var res = '';
            var styles = style.split(';');
            for (var i = 0; i < styles.length; i++) {
                var key = styles[i].split('=')[0];
                var value = styles[i].split('=')[1];
                switch (key.toLowerCase()) {
                    case 'fontfamily':
                        res += 'font-family:' + value + ';';
                        break;
                    case 'fontsize':
                        var valueInt = parseFloat(value);
                        res += 'font-size:';
                        if (isNaN(valueInt)) {
                            res += value;
                        } else {
                            var fontSizeInEm = valueInt / 16;
                            res += fontSizeInEm + 'em';
                        }
                        res += ';';
                        break;
                    case 'align':
                        if (value == 'centered')
                            value = 'center';
                        res += 'text-align:' + value + ';';
                        break;
                    case 'fontstyle':
                        if (value == 'bold') {
                            res += 'font-weight:bold;';
                        }
                        break;
                    case 'color':
                        var colorUtil = new ColorUtil();
                        res += 'color:' + colorUtil.parseHTMLColor(value) + ';';
                        break;
                    case 'verticalalign':
                        res += 'vertical-align:' + value + ';';
                        break;
                }
            }

            style = style.split("=").join(":");
            style = style.split("fontfamily").join("font-family");
            style = style.split("fontsize").join("font-size");
            return res;
        }

        function convertPropertiesToFonts(properties) {
            var fonts = [];
            if (properties) {
                for (var key in properties) {
                    if (key.toLowerCase().indexOf("player.fonts.custom.") >= 0) {
                        var fontName = key.substring("player.fonts.custom.".length);
                        var font = {
                            family: fontName,
                            url: properties[key]
                        };
                        fonts.push(font);
                    }
                }
            }
            return fonts;
        }

        function installCustomFonts(fonts) {
            if (!fonts) {
                return;
            }
            for (var i = 0; i < fonts.length; i++) {
                coreFontManagerService.addFont(fonts[i]);
            }
        }

        return {
            //Public interfaces
            reformatStyleToCSS: reformatStyleToCSS,
            installCustomFonts: installCustomFonts,
            convertPropertiesToFonts: convertPropertiesToFonts
        };
    }]);
})(window.angular);
(function ($angular) {
    "use strict";

    var app = $angular.module('signsHTML5');

    app.factory('MacroCommands', ["$q", "scriptingService", function ($q, scriptingService) {

		// Define the constructor function.
        function MacroCommands() {
            // console.log("MacroCommands : constructor");
        }

        //Define the instance methods and properties
        MacroCommands.prototype = {
            updateTickerFromQuery: updateTickerFromQuery,
			updateTextFromQuery: updateTextFromQuery,
			loadPlaylistFromInfomenuItem: loadPlaylistFromInfomenuItem
        };

		function updateTickerFromQuery(tickerid, tablelistingid, query, rowstartoffset, rowendoffset, columnoffset, breakonblank, append) {
			getElementApi().then(
				function (returnApi) {
					var sheet = returnApi.tableListingApi.retrieveDataByQuery(query);

					if (!sheet) {
						return;
					}

					sheet = sheet[0];
					var items = [];
					var count = 0;

					for (var i = rowstartoffset; i <= rowendoffset; i++) {
						var row = sheet.rows[i];

						if (row) {
							var text = row['c' + (columnoffset + 1)];
							if (text) {
								items.push({ playoutProperties: { custom: "" }, position: count, title: text, type: "text" });
								count++;
							}
						}
					}

					returnApi.tickerApi.setDatasource({ result: { playlist: { items: items } } }, append);
				},
				function (err) { }
			);

			function getElementApi() {
				var deferred = $q.defer();
				var promises = [];

				promises.push(scriptingService.retrieveWidgetApi(tickerid));
				promises.push(scriptingService.retrieveWidgetApi(tablelistingid));

				//Wait for all app object promises to resolve
				$q.all(promises).then(function (results) {
					deferred.resolve({ tickerApi: results[0], tableListingApi: results[1] });
				}, deferred.reject);

				return deferred.promise;
			}
		}

		function updateTextFromQuery(actionName, plaintextid, tablelistingid, query, rowoffset, columnoffset, defaultvalue) {
			getElementApi().then(
				function (returnApi) {
					var sheet = returnApi.tableListingApi.retrieveDataByQuery(query);

					if (!sheet) {
						return;
					}

					var tempText = sheet[0].rows[rowoffset]["c" + (columnoffset + 1)];

					var newText = defaultvalue;
					if (tempText) {
						newText = tempText;
					}

					returnApi.textApi.executeAction({ name: actionName, text: newText });
				},
				function (err) { }
			);

			function getElementApi() {
				var deferred = $q.defer();
				var promises = [];

				promises.push(scriptingService.retrieveWidgetApi(plaintextid));
				promises.push(scriptingService.retrieveWidgetApi(tablelistingid));

				//Wait for all app object promises to resolve
				$q.all(promises).then(function (results) {
					deferred.resolve({ textApi: results[0], tableListingApi: results[1] });
				}, deferred.reject);

				return deferred.promise;
			}
		}

		function loadPlaylistFromInfomenuItem(infoMenuId, infoMenuItemId, mediaZoneId, assetType) {

			getElementApi().then(
				function (returnApi) {

					var data = returnApi.infoMenuApi.getDataById(infoMenuItemId);

					if (!data) {
						return;
					}

					var assets = data.assets;

					var items = [];

					for (var i = 0; i < assets.length; i++) {
						var tempdata = assets[i];
						if (assetType.toLowerCase() != tempdata.assetType.toLowerCase()) {
							continue;
						}
						var item = {
							position: (i + 1),
							type: tempdata.type,
							title: tempdata.text,
							description: "",
							configuration: null,
							mediaFile: tempdata.mediaFile,
							playoutProperties: {
								duration: tempdata.duration,
								aspectRatio: null,
								transition: null,
								playoutEffect: "0",
								custom: "",
								playoutTagsRule: "",
								frequencyCondition: "NotSet",
								frequencyPlayout: 1,
								frequencyUnit: "Hour",
								timespans: null
							}
						};
						items.push(item);
					}

					var dataSource = { result: { mediaZone: { id: "", name: "", playlist: { items: items } } } };
					returnApi.mediaZoneApi.setDatasource(dataSource).then(function () {
						returnApi.mediaZoneApi.executeAction({ name: "nextmedia" });
					});

				},
				function (err) { }
			);

			function getElementApi() {
				var deferred = $q.defer();
				var promises = [];

				promises.push(scriptingService.retrieveWidgetApiByDatasourceId(infoMenuId));
				promises.push(scriptingService.retrieveWidgetApi(mediaZoneId));

				//Wait for all app object promises to resolve
				$q.all(promises).then(function (results) {
					deferred.resolve({ infoMenuApi: results[0], mediaZoneApi: results[1] });
				}, deferred.reject);

				return deferred.promise;
			}
		}

		return (MacroCommands);

	}]);
})(window.angular);
(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');
    app.service('NetworkUtil', ["$q", "$window", "$timeout", "WebRTC", "SYSTEM_DATA", "HttpWrapper", "logService", "embedProfileService", "signsAPIService", "ASERRORCODE", function(
        $q,
        $window,
        $timeout,
        WebRTC,
        SYSTEM_DATA,
        HttpWrapper,
        logService,
        embedProfileService,
        signsAPIService,
        ASERRORCODE) {
        var MINUTES_TO_WAIT = 2;
        var LOG_KEY = 'appspace.signs';

        function pingAppspace(url) {
            return new HttpWrapper().handlerGet(url + '/api/v1/highavailability/nodes/ping', 30000);
        }

        function waitForAppspaceOnline(appspaceUrl) {
            var checkAppspaceOnlineTimerHandler;
            var promise = $q.defer();
            var startTime = moment();
            var checkAppspaceOnline = function() {
                if (checkAppspaceOnlineTimerHandler) {
                    $timeout.cancel(checkAppspaceOnlineTimerHandler);
                    checkAppspaceOnlineTimerHandler = null;
                }

                if (!appspaceUrl) {
                    appspaceUrl = '{{appspace-url}}';
                }

                pingAppspace(appspaceUrl).then(function(res) {
                    promise.resolve(res);
                }, function() {
                    // get elapse time
                    if (moment.duration(moment().diff(startTime)).asMinutes() > MINUTES_TO_WAIT) {
                        logService.error(LOG_KEY, 'Fail to establish communication with Appspace. Retry stop.');
                        promise.reject();
                    } else {
                        logService.debug(LOG_KEY, 'Unable to establish communication with Appspace. Retrying in 10 seconds.');
                        checkAppspaceOnlineTimerHandler =
                            $timeout(checkAppspaceOnline, 10000);
                    }
                });
            };

            checkAppspaceOnline();
            return promise.promise;
        }

        function validateIdInjection(injection) {
            if (Object.keys(injection).length < 1) {
                return $q.reject({errormessage: 'No parameters injected', errorCode: ASERRORCODE.NO_INJECTION_PARAMETERS, error: 'noparameters'});
            }

            if (!injection.deviceguid && !injection.appid) {
                return $q.reject({errormessage: 'Incomplete parameters injected', errorCode: ASERRORCODE.INCOMPLETE_INJECTION_PARAMETERS, error: 'incompleteparameters'});
            }

            injection.appspaceurl = injection.appspaceurl || window.location.origin;
            var promise = $q.defer();
            var onAppspacePingSuccess = function(res) {
                if (!res.data || res.data.Status != 1) {
                    promise.reject({errormessage: 'Appspace URL injected is invalid', errorCode: ASERRORCODE.INVALID_APPSPACE_URL, error: 'invalidappspaceurl'});
                    return;
                }

                var getTimestamp = signsAPIService.retrievePlayerTimestamp;
                var resolvedMessage = 'Appspace URL and device GUID injected are valid';
                if (injection.appid) {
                    getTimestamp = signsAPIService.retrieveApplicationTimestamp;
                    resolvedMessage = 'Appspace URL and app ID injected are valid';
                }

                getTimestamp(injection).then(function() {
                    promise.resolve(resolvedMessage);
                }, promise.reject);
            };

            pingAppspace(injection.appspaceurl).then(onAppspacePingSuccess, function(err) {
                promise.reject({errormessage: 'Appspace URL injected is invalid', errorCode: ASERRORCODE.INVALID_APPSPACE_URL, error: err});
            });
            return promise.promise;
        }

        function isIdInjectionValidationRequired() {
            // ID injection validation is only required in non-editor mode and
            // the injections required for preview mode are present
            return !embedProfileService.isEditorMode() && embedProfileService.isPreviewMode() && embedProfileService.isValid();
        }

        function getIpAddressFromWebRTC(logKey) {
            var defer = $q.defer();

            function onFailed(error) {
                logService.error(logKey, 'Failed to get IP address from WebRTC: ' + JSON.stringify(error));
                defer.reject(error);
            }

            if (!WebRTC) {
                onFailed('WebRTC not supported');
                return defer.promise;
            }

            var webRTC = new WebRTC();
            webRTC.getIpAddress().then(function(ipAddress) {
                logService.debug(logKey, 'WebRTC IP Address: ' + JSON.stringify(ipAddress));
                defer.resolve(ipAddress);
            }, onFailed);

            return defer.promise;
        }

        function getCordovaIpAddress(logKey) {
            if ($window.navigator &&
                $window.navigator.connection &&
                $window.navigator.connection.type === 'ethernet') {
                // Get IP address from WebRTC as Cordova plugin does not support ethernet connection
                return getIpAddressFromWebRTC();
            }

            var defer = $q.defer();
            $window.networkinterface.getIPAddress(function(address) {
                if (!address) {
                    logService.error(logKey, 'Invalid IP Address: ' + JSON.stringify(address));
                    defer.resolve({
                        originalAddress: address,
                        address: SYSTEM_DATA.DEFAULT_IP_ADDRESS
                    });
                    return;
                }

                logService.debug(logKey, 'Cordova IP Address: ' + address);
                defer.resolve({
                    address: address
                });
            }, function() {
                getIpAddressFromWebRTC().then(defer.resolve, defer.reject);
            });
            return defer.promise;
        }

        return {
            waitForAppspaceOnline: waitForAppspaceOnline,
            validateIdInjection: validateIdInjection,
            isIdInjectionValidationRequired: isIdInjectionValidationRequired,
            getCordovaIpAddress: getCordovaIpAddress,
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    app.factory('pageUtil', ["dataUtil", "domUtil", function(dataUtil, domUtil) {
        function setAppBackgroundColor(color) {
            if (dataUtil.isNullOrUndefined(color)) {
                return;
            }

            domUtil.getElement('.theme-dark').css('background', color);
            domUtil.getElement('body,html').css('background', color);
        }

        return {
            setAppBackgroundColor: setAppBackgroundColor
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    app.factory('QueryUtil', ["$filter", "halPlatformService", function($filter, halPlatformService) {
        // Define the constructor function.
        function QueryUtil() {
            // console.log("QueryUtil : constructor");
        }

        // Define the instance methods and properties
        QueryUtil.prototype = {
            runQuery: runQuery
        };

        function runQuery(oriData, sqlStatement) {
            if (!oriData) {
                return;
            }

            // clone object. to avoid reference issue/overide data
            var data = $angular.copy(oriData);
            // convert all to lower case for both statement and table name
            data = updateSheetName(data);

            sqlStatement = modifyStatement(sqlStatement);
            // modify sheet name to be sql compatible
            for (var i = 0; i < data.sheets.length; i++) {
                var sheet = data.sheets[i];
                sheet.name = sheet.name.toLowerCase();
                // replace dash with underscore
                sheet.name = sheet.name.split('-').join('_');
                if (!isNaN(parseInt(sheet.name.charAt(0), 10))) {
                    sheet.name = 'a' + sheet.name;
                }
                // modify the statement
                var regEx = new RegExp(sheet.name, 'ig');
                sqlStatement = sqlStatement.replace(regEx, sheet.name);
            }

            sqlStatement = sqlStatement.trim();
            // if sql statement ends with =, add empty string
            if (sqlStatement.charAt(sqlStatement.length - 1) == '=') {
                sqlStatement = sqlStatement + ' ""';
            }

            var finalRows = customExecuteQuery(sqlStatement, data.sheets);

            var res = [{rows: $angular.copy(finalRows)}];
            finalRows = [];
            return res;
        }

        function customExecuteQuery(sqlStatement, sheets) {
            // get column

            var columnString = getCharactersBetween(sqlStatement, /select(.*?)from/i);
            if (!columnString) {
                return [];
            }

            var tablesString = getCharactersBetween(sqlStatement, /from(.*?)($|where|order by)/i);
            if (!tablesString) {
                return [];
            }
            var finalRows = [];
            var columns = columnString.trim().split(',');
            var conditionsString = getCharactersBetween(sqlStatement, /where(.*?)($|order by)/i);
            var orderByString = getCharactersBetween(sqlStatement, /order by(.*?)$/i);

            var i;
            var table;
            var j;
            var row;
            var key;
            var column;
            var tableToSearch = (tablesString || '').trim().replace(/\s*,\s*/gm, '|');
            // get all relevant tables
            var usedTables = simpleObjectQuery.where(sheets, { //eslint-disable-line
                'name': new RegExp(tableToSearch)
            });
            // merge all tables together
            var mergedTable = [];
            // get max rows
            var maxRows = 0;
            for (i = 0; i < usedTables.length; i++) {
                table = usedTables[i];
                if (table.rows.length > maxRows) {
                    maxRows = table.rows.length;
                }
            }
            var columnsUsed = [];
            var tableColumnSeparator = '.';
            // add row by row across table
            // use ___ as table/column seperator so that we can use it for sorting
            if (orderByString) {
                tableColumnSeparator = '___';
            }
            for (i = 0; i < maxRows; i++) {
                for (j = 0; j < usedTables.length; j++) {
                    table = usedTables[j];
                    row = table.rows[i];
                    for (key in row) {
                        if (i == 0) {
                            columnsUsed.push(table.name + tableColumnSeparator + key);
                        }
                        if (!mergedTable[i]) {
                            mergedTable[i] = {};
                        }
                        mergedTable[i][table.name + tableColumnSeparator + key] = row[key];
                    }
                }

                // ensure for each rows we have all columns
                // 1st row will always have the complete set of columns
                if (i == 0) {
                    continue;
                }
                for (j = 0; j < columnsUsed.length; j++) {
                    var columnToCheck = columnsUsed[j];
                    if (!mergedTable[i][columnToCheck]) {
                        mergedTable[i][columnToCheck] = '';
                    }
                }
            }

            // sort data first
            if (orderByString) {
                var sorts = orderByString.match(/(.*?)(asc|desc|$)/i);
                if (sorts.length >= 3) {
                    var sortByColumns = sorts[1].trim().split(/,/g);
                    var isAscending = sorts[2].trim().match(/(desc)/i) ? false : true;
                    var sortOrder = [];
                    for (i = 0; i < sortByColumns.length; i++) {
                        var col = ensureColumnNameFormat(sortByColumns[i].trim(), usedTables[0].name);
                        sortOrder.push(isAscending ? '' : '-' + col.table + tableColumnSeparator + col.name);
                    }
                    mergedTable = $filter('orderBy')(mergedTable, sortOrder);
                }
            }

            // change back the table/column seperator if it's not the normal .
            if (tableColumnSeparator != '.') {
                for (i = 0; i < mergedTable.length; i++) {
                    row = mergedTable[i];
                    var newrow = {};
                    for (key in row) {
                        newrow[key.replace(tableColumnSeparator, '.')] = row[key];
                    }
                    mergedTable[i] = newrow;
                }
            }

            // filter by conditions
            var filteredRows = [];
            if (conditionsString) {
                var conditions = conditionsString.trim().split(/(?!\B"[^"]*)\b(and|or)\b(?![^"]*"\B)/gi);

                for (i = 0; i < conditions.length; i++) {
                    conditions[i] = ensureConditionFormat(conditions[i], usedTables[0].name);
                }
                // process each row
                for (i = 0; i < mergedTable.length; i++) {
                    row = mergedTable[i];
                    var totalSuccess = 0;
                    var previousOperator = null;
                    for (j = 0; j < conditions.length; j++) {
                        var condition = conditions[j];
                        if (condition.type == 'comparison') {
                            var conditionResult = isConditionValid(row[condition.left], condition.right, condition.operator);
                            // do we have previous operator? (AND or OR)
                            // if not, just set totalSuccess to 0/1 based on the result
                            if (previousOperator) {
                                if (previousOperator == 'and') {
                                    totalSuccess *= conditionResult ? 1 : 0;
                                } else {
                                    totalSuccess += conditionResult ? 1 : 0;
                                }
                            } else {
                                totalSuccess = conditionResult ? 1 : 0;
                            }
                        } else if (condition.type == 'operator') {
                            previousOperator = condition.operator;
                        }
                    }
                    if (totalSuccess > 0) {
                        filteredRows.push(row);
                    }
                }
                conditions = [];
            } else {// no conditions? use the mergedTable rows
                filteredRows = mergedTable;
            }
            // filter columns and add to final table
            for (i = 0; i < columns.length; i++) {
                column = columns[i].trim();
                columns[i] = ensureColumnNameFormat(column, usedTables[0].name);
            }

            for (i = 0; i < filteredRows.length; i++) {
                row = filteredRows[i];
                var columnHeader = 1;
                // loop through the specified columns on each row
                // e.g. SELECT table.c1,table.c2,table.c3 FROM table (the table.c1,table.c2,table.c3)
                for (j = 0; j < columns.length; j++) {
                    var targetColumn = columns[j];
                    for (var columnName in row) {
                        // only process the column if the selected column is * or matches the required columns
                        if (targetColumn.name != '*' && targetColumn.combined != columnName) {
                            continue;
                        }
                        finalRows[i] = finalRows[i] || {};
                        finalRows[i]['c' + columnHeader++] = row[columnName];
                    }
                }
            }

            filteredRows = [];
            mergedTable = [];

            return finalRows;
        }

        function isConditionValid(left, right, operator) {
            var valid = false;
            switch (operator) {
                case '=':
                    valid = left == right;
                    break;
                case '!=':
                case '<>':
                    valid = left != right;
                    break;
                case '>':
                    valid = parseFloat(left) > parseFloat(right);
                    break;
                case '>=':
                    valid = parseFloat(left) >= parseFloat(right);
                    break;
                case '<':
                    valid = parseFloat(left) < parseFloat(right);
                    break;
                case '<=':
                    valid = parseFloat(left) <= parseFloat(right);
                    break;
            }
            return valid;
        }

        function ensureColumnNameFormat(column, defaultTableName) {
            var res = {name: column, table: defaultTableName, combined: defaultTableName + '.' + column};
            var arr = column.split('.');
            if (arr.length > 1) {
                res.table = arr[0];
                res.name = arr[1];
                res.combined = res.table + '.' + res.name;
            }
            return res;
        }

        function ensureConditionFormat(condition, defaultTableName) {
            var res = {};
            var operatorRegex = new RegExp('(?!\\B"[^"]*)(=|>=|<=|>|<|!=|<>)(?![^"]*"\\B)', 'gi');
            if (condition.match(operatorRegex)) {
                res.type = 'comparison';
                var arr = condition.split(operatorRegex);
                if (arr.length == 3) {
                    res.left = arr[0].trim();
                    res.operator = arr[1].trim();
                    res.right = arr[2].trim().replace(/['"]+/g, '');
                }
            } else if (condition.match(/\b(and|or)\b/gi)) {
                res.type = 'operator';
                res.operator = condition.trim().toLowerCase();
            }

            return res;
        }

        function getCharactersBetween(text, regex) {
            var arr = text.match(regex);
            var res;
            if (arr && arr.length >= 2) {
                res = arr[1];
            }
            return res;
        }

        // convert sheet name/table name to valid format
        function updateSheetName(data) {
            for (var i = 0; i < data.sheets.length; i++) {
                var sheet = data.sheets[i];
                sheet.realName = sheet.name;
                sheet.name = sheet.name.replace(/\s+/g, '_');
                sheet.name = sheet.name.toLowerCase();
                data.sheets[i] = sheet;
            }
            return data;
        }


        // STEP 0 -
        function modifyStatement(s) {
            var res = s;
            // reformat table name
            var matches = res.match(/\[(.*?)\]/g);
            var i;
            if (matches) {
                for (i = 0; i < matches.length; i++) {
                    var newName = matches[i].replace(/\s+/g, '_');
                    newName = newName.replace('[', '');
                    newName = newName.replace(']', '');
                    res = res.replace(matches[i], newName);
                }
            }

            // replace - with _
            res = res.split('-').join('_');

            // replace double white space with single white space
            var spaceRegex = '\\s\\s+(?=([^"]*"[^"]*")*[^"]*$)';
            var regExDoubleSpace = new RegExp(spaceRegex, 'g');
            while (regExDoubleSpace.test(res)) {
                res = res.replace(regExDoubleSpace, ' ');
            }

            // enclosed values inside quotes
            var regexWhere = new RegExp('(where)', 'gi');
            if (regexWhere.test(res)) {
                var baseSplit = res.split(regexWhere);
                var condition = baseSplit[2].trim();
                var conditions = condition.replace(/(?!\B"[^"]*)\b(and|or)\b(?![^"]*"\B)/gi, '***').split('***');
                var encloseRegex = '(\'|")(.*?)(\'|")';
                var regexEnclosed = new RegExp(encloseRegex, 'g');
                for (i = 0; i < conditions.length; i++) {
                    var cond = conditions[i].trim();
                    if (cond.match(/=/g)) {
                        var value = cond.split('=')[1];
                        if (!value.match(regexEnclosed)) {
                            var newValue = cond.replace(value, '"' + value.trim() + '"');
                            condition = condition.replace(cond, newValue);
                        }
                    }
                }
                res = baseSplit[0] + 'WHERE ' + condition;
            }
            return res;
        }

        // Return the constructor for the class
        return (QueryUtil);
    }]);
})(window.angular);

(function ($angular) {
    "use strict";

    var app = $angular.module('signsHTML5');

    app.factory('ScheduleUtil', function () {
        function ScheduleUtil() {
        }
        //Define the instance methods and properties
        ScheduleUtil.prototype = {
            isEventValid: isEventValid
        };

        function isEventValid(event, validRange) {
            return (event.start >= validRange.start && event.start < validRange.end) ||//if event start within the range
                (event.end >= validRange.start && event.end < validRange.end) ||//if event ends within the range
                (event.start <= validRange.start && event.end >= validRange.end); //if event is within the the valid range (start before valid range and end after valid range)
        }
        return (ScheduleUtil);
    });
})(window.angular);
(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');
    app.constant('CLOCK_SYNC', {
        LOGGER: 'appspace.core'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');
    app.constant('DEVICE_MENU_COMMAND', {
        ENTER_SINGLE_APP_MODE: 'entersingleappmode',
        EXIT_SINGLE_APP_MODE: 'exitsingleappmode',
        RESYNC: 'resync'
    });
})(window.angular);

(function ($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');
    //1: Name
    //2: Last Updated
    //3: Created Date
    //4: Custom (respect ordering)
    app.constant('MAPPING_SEQUENCEYBY', {
        1: 'title',
        2: '-updateddate',
        3: '-date',
        4: ''
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');
    app.constant('TICKER_DIRECTION', {
        righttoleft: 1,
        lefttoright: 2,
        fadeinout: 5
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');
    app.constant('TICKER_SPEED', {
        veryslow: 25,
        slow: 62,
        medium: 100,
        fast: 137,
        veryfast: 175
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');
    app.constant('DEVICE_MENU_INVOKE_COMMAND', {
        DISABLE_HOT_AREA: 'disableHotArea',
        ENABLE_HOT_AREA: 'enableHotArea'
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');
    app.factory('DeviceMenuInvokeInfo', ["dataUtil", function(
        dataUtil
    ) {
        function DeviceMenuInvokeInfo() {
            var ACTION_DELIMITER = ',';
            var DEFAULT_ACTIONS = 'top-right, bottom-right, bottom-left, top-left, top-right, top-right, top-right';
            var MAX_ACTIONS_COUNT = 10;
            var PERCENTAGE = {
                CORNER: 20,
                CENTER: 20
            };
            var ACTIONS = {
                'top-left': getSizePercentage(PERCENTAGE.CORNER, PERCENTAGE.CORNER, 0, 0),
                'top-right': getSizePercentage(PERCENTAGE.CORNER, PERCENTAGE.CORNER, 100 - PERCENTAGE.CORNER, 0),
                'bottom-left': getSizePercentage(PERCENTAGE.CORNER, PERCENTAGE.CORNER, 0, 100 - PERCENTAGE.CORNER),
                'bottom-right': getSizePercentage(PERCENTAGE.CORNER, PERCENTAGE.CORNER, 100 - PERCENTAGE.CORNER, 100 - PERCENTAGE.CORNER),
                'center-page': getSizePercentage(PERCENTAGE.CENTER, PERCENTAGE.CENTER, 50 - (PERCENTAGE.CENTER / 2), 50 - (PERCENTAGE.CENTER / 2)),
            };
            var VALID_ACTIONS = Object.keys(ACTIONS);
            this.lastActionIndex = 0;
            this.currentActions;

            this.setCurrentActions = function(value) {
                if (!dataUtil.isString(value)) {
                    value = DEFAULT_ACTIONS;
                }

                var inputActions = value.split(ACTION_DELIMITER);
                var validActions = [];
                for (var i = 0; i < Math.min(inputActions.length, MAX_ACTIONS_COUNT); i++) {
                    var action = dataUtil.trim(inputActions[i]).toLowerCase();
                    if (!isActionValid(action)) {
                        continue;
                    }

                    validActions.push(action);
                }

                this.currentActions = validActions;
            };

            this.setCurrentActions(DEFAULT_ACTIONS);

            this.canInvoke = function(pointX, pointY, currentWidth, currentHeight) {
                if (isActionMatched(
                        this.getCurrentAction(),
                        pointX,
                        pointY,
                        currentWidth,
                        currentHeight)
                ) {
                    this.lastActionIndex++;
                } else {
                    this.lastActionIndex = 0;
                }

                if (this.lastActionIndex === this.currentActions.length) {
                    this.lastActionIndex = 0;
                    return true;
                }

                return false;
            };

            this.getCurrentAction = function() {
                return ACTIONS[this.currentActions[this.lastActionIndex]];
            };

            this.getActionByIndex = function(index) {
                if (index < 0 || index >= this.currentActions.length) {
                    return;
                }

                return ACTIONS[this.currentActions[index]];
            };

            this.isCurrentActionVisible = function() {
                return this.lastActionIndex > 0 && this.lastActionIndex < this.currentActions.length;
            };

            function isActionValid(action) {
                return dataUtil.includesValue(action, VALID_ACTIONS);
            }

            function getSizePercentage(width, height, left, top) {
                return {
                    widthPercentage: width,
                    heightPercentage: height,
                    leftPercentage: left,
                    topPercentage: top
                };
            }

            function isActionMatched(currentAction, pointX, pointY, currentWidth, currentHeight) {
                if (!currentAction) {
                    return false;
                }

                var widthPercentage = currentAction.leftPercentage + currentAction.widthPercentage;
                var heightPercentage = currentAction.topPercentage + currentAction.heightPercentage;
                return pointX >= currentWidth * currentAction.leftPercentage / 100 &&
                    pointX <= currentWidth * widthPercentage / 100 &&
                    pointY >= currentHeight * currentAction.topPercentage / 100 &&
                    pointY <= currentHeight * heightPercentage / 100;
            }
        }

        return DeviceMenuInvokeInfo;
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');
    var getElement = $angular.element;
    /**
     * @directive
     * @type {Function}
     */
    app.directive('asSignsDeviceMenu', function() {
        return {
            restrict: 'EA',
            replace: 'true',
            templateUrl: 'tv/devicemenu/asSignsDeviceMenuTemplate.html',
            scope: {
                enableOsd: '=?'
            },
            link: function(scope, element, attrs) {
                scope.$watch('enableOsd', function(newVal) {
                    scope.toggleOsd(newVal);
                });
            },
            controller: ["$q", "$scope", "$location", "$timeout", "$interval", "logService", "coreFrameworkService", "coreConfigurationService", "storageLocalStorageService", "halRemoteService", "REMOTEVCODE", "REMOTEEVENTTYPE", "halNetworkService", "halPlatformService", "syncService", "cdnService", "halMediaService", "publicService", "applicationCommandService", "SYNC_EVENT", "dataUtil", "DEVICE_MENU_COMMAND", "singleAppService", "STORAGE_KEY", "deviceMenuInvokerService", "$window", function($q, $scope, $location, $timeout, $interval, logService,
                coreFrameworkService, coreConfigurationService, storageLocalStorageService,
                halRemoteService, REMOTEVCODE, REMOTEEVENTTYPE, halNetworkService,
                halPlatformService, syncService, cdnService, halMediaService,
                publicService, applicationCommandService, SYNC_EVENT, dataUtil,
                DEVICE_MENU_COMMAND, singleAppService, STORAGE_KEY, deviceMenuInvokerService,
                $window) {
                $scope.title = 'Device Menu';
                $scope.items = [];
                $scope.selectedItem = null;
                var selectedIndex = -1;
                var logKey = 'appspace.signs';
                var localStorage;

                // Set by attributes
                $scope.displayOsd = 'none';
                $scope.displayMenu = 'none';
                $scope.ipAddress = '-';
                $scope.deviceModel = '-';

                // Set internally
                $scope.showLog = false;
                $scope.minimized = 'none';
                $scope.enableLogUpdates = false;
                $scope.showpasscode = 'none';

                // Single App mode related
                $scope.isSingleAppSupported = false;
                $scope.isSingleAppAllowed = false;
                $scope.showOperationError = false;

                // keyboard related
                var tabIndex = 1;
                var keyboardApi = null;
                var keyboardFocus = false;
                var lastKey = null;
                var idleTimer;
                var idleDuration = 10000;
                var passcode = null;
                var keyboardSupport = false;
                var dosavelog = false;
                var staticInfo;
                var SINGLE_APP_MODE_LABEL = {
                    ENABLED: 'Enabled',
                    DISABLED: 'Disabled',
                    ENABLE_FAILED: 'Enable Failed',
                    DISABLE_FAILED: 'Disable Failed'
                };
                var SINGLE_APP_MENU_LABEL = {
                    ENTER: 'Enter Single App Mode',
                    EXIT: 'Exit Single App Mode'
                };
                $scope.passcodeArr = new Array(4);
                $scope.showError = 'none';
                $scope.showKeyboard = true;

                // passcode timer related
                var passcodeExpiryDuration = 0;
                var lastPasscodeSucsessTime = 0;

                var deviceOperations = {
                    restart: executeRestart,
                    resync: executeResync,
                    resetContent: executeResetContent,
                    unregister: executeUnregister,
                    getDeviceInfo: getCurrentDeviceInfo,
                    executeAction: executeAction,
                    sleep: executeSleep,
                    resetdevice: executeResetDevice
                };

                var driverMenu = {};

                function executeRestart() {
                    executeAdvanceTask('restart');
                }

                function executeResync() {
                    executeAdvanceTask('resync');
                }

                function executeResetContent() {
                    executeAdvanceTask('reset');
                }

                function executeUnregister() {
                    executeAdvanceTask('unregister');
                }

                function executeResetDevice() {
                    executeAdvanceTask('resetdevice');
                }

                function executeAction(action) {
                    if (deviceOperations[action]) {
                        deviceOperations[action]();
                    }
                }

                function executeSleep(flag) {
                    var path = flag ? '/tv/sleep' : '/tv/appinit';
                    $location.path(path);
                }

                function toggleMenu() {
                    $scope.enableOsd = !$scope.enableOsd;
                }

                function init() {
                    localStorage = halPlatformService.getLocalStorage();

                    // Initialize version information early for non-interactive devices (e.g., Amazon Signage)
                    // This ensures buildVersion and softwareVer are available even if device menu is never opened
                    $scope.softwareVer = coreFrameworkService.getAppVersion();
                    $scope.buildVersion = coreFrameworkService.getBuildVersion();

                    // Handle view destroy
                    $scope.$on('$destroy', function() {
                        // clean up stuff
                        halRemoteService.unRegisterEvents(onRemoteEvent);
                    });

                    syncService.subscribe(onSyncServiceEvent);
                    publicService.registerService('deviceMenu', {
                        toggleMenu: toggleMenu,
                        isVisible: isVisible
                    });
                    publicService.registerService('deviceOperation', deviceOperations);

                    // Init Device menu items
                    initMenu();

                    applicationCommandService.subscribe(onCommandReceived);

                    syncService.isAppAvailable().then(function(available) {
                        if (available) {
                            initPasscode();
                        }
                    });

                    initSingleAppMode();
                }

                function initSingleAppMode() {
                    singleAppService.isSingleAppSupported().then(function(result) {
                        $scope.isSingleAppSupported = result;
                        if (!$scope.isSingleAppSupported) {
                            return;
                        }

                        singleAppService.subscribe(onSingleAppServiceUpdated);
                    });
                }

                function getSingleAppOperationResult() {
                    var item = localStorage.getItem(STORAGE_KEY.SINGLE_APP_STATUS);
                    localStorage.removeItem(STORAGE_KEY.SINGLE_APP_STATUS);
                    return item ? JSON.parse(item) : undefined;
                }

                function onSingleAppServiceUpdated(event) {
                    if (!event) {
                        return;
                    }

                    var allow = event.allow;
                    $scope.isSingleAppAllowed = allow;
                    initMenu({
                        showSingleAppMode: allow,
                        isSingleAppMode: event.enabled,
                        success: event.success
                    });
                    var previousResult = getSingleAppOperationResult();
                    if (previousResult) {
                        event = previousResult;
                    }

                    var isFailed = dataUtil.isValueBooleanFalse(event.success);
                    $scope.showOperationError = event.isMenuTriggered && isFailed;
                    if (event.enabled) {
                        $scope.singleAppModeStatus = isFailed ? SINGLE_APP_MODE_LABEL.ENABLE_FAILED : SINGLE_APP_MODE_LABEL.ENABLED;
                        return;
                    }

                    $scope.singleAppModeStatus = isFailed ? SINGLE_APP_MODE_LABEL.DISABLE_FAILED : SINGLE_APP_MODE_LABEL.DISABLED;
                }

                function onCommandReceived(event) {
                    if (!event || event.type !== 'system' || !event.action) {
                        return;
                    }

                    executeAction(event.action);
                }

                var onSyncServiceEvent = function(event) {
                    if (event.event == SYNC_EVENT.SCRIPT_SAVED) {
                        initPasscode();
                    }
                };

                // Event handler for remote events
                var onRemoteEvent = function(remoteEvent) {
                    // Only listen to down button events
                    if (remoteEvent.eventType != REMOTEEVENTTYPE.buttonDown) {
                        return;
                    }

                    if ($scope.enableOsd != true) {
                        return;
                    }

                    if ($scope.showpasscode == '') {
                        resetAndStartOverIdle();
                    }

                    // Perform action based on Virtual code
                    switch (remoteEvent.vcode) {
                        case REMOTEVCODE.up: // Up Key
                            onActionUp();
                            remoteEvent.preventDefault();
                            break;
                        case REMOTEVCODE.down: // Down Key
                            onActionDown();
                            remoteEvent.preventDefault();
                            break;
                        case REMOTEVCODE.left: // Left Key
                            onActionLeft();
                            remoteEvent.preventDefault();
                            break;
                        case REMOTEVCODE.right: // Right Key
                            onActionRight();
                            remoteEvent.preventDefault();
                            break;
                        case REMOTEVCODE.select: // option/alt Key
                            onActionSelect();
                            remoteEvent.preventDefault();
                            break;
                    }
                };

                function onActionUp() {
                    if (!keyboardFocus) {
                        if (selectedIndex > 0) {
                            selectedIndex -= 1;
                        }

                        if (selectedIndex == -1) {
                            selectedIndex = 0;
                        }

                        $timeout(function() {
                            $scope.selectedItem = $scope.items[selectedIndex];
                        }, 0);
                    } else {
                        if (tabIndex == 5) {
                            keyboardApi.focusKey(lastKey.value);
                            tabIndexFocus(4);
                        } else {
                            keyboardApi.upKey();
                        }
                    }
                }

                function onActionDown() {
                    if (!keyboardFocus) {
                        selectedIndex += 1;

                        // if over the array bound, select the last item
                        var length = $scope.items.length;
                        if (selectedIndex >= length) {
                            selectedIndex = length - 1;
                        }

                        $timeout(function() {
                            $scope.selectedItem = $scope.items[selectedIndex];
                        }, 0);
                    } else {
                        keyboardApi.downKey();
                    }
                }

                // On Left clicked
                function onActionLeft() {
                    // focus on keyboard if not in focus
                    if (keyboardFocus) {
                        keyboardApi.leftKey();
                    }
                }

                // On Right clicked
                function onActionRight() {
                    if (keyboardFocus) {
                        keyboardApi.rightKey();
                    }
                }

                function onActionSelect() {
                    if (keyboardFocus) {
                        if (tabIndex == 5) {
                            $scope.submitpasscode();
                        } else {
                            if ($scope.showKeyboard) {
                                keyboardApi.selectKey();
                            }
                        }
                    } else {
                        $scope.submit();
                    }
                }

                $scope.onDeviceMenuItemSelected = function(item, click) {
                    if ($scope.selectedItem == item || click) {
                        $scope.selectedItem = item;
                        $timeout($scope.submit, 100);
                    } else {
                        $scope.selectedItem = item;
                    }
                };

                $scope.submit = function() {
                    if (!$scope.selectedItem) {
                        return;
                    }

                    switch ($scope.selectedItem.value) {
                        case 'showlogger':
                            // show logger window
                            $scope.toggleMenu(false);
                            $scope.toggleLog(true);
                            break;
                        case 'deviceinfo':
                            $scope.toggleMenu(false);
                            $scope.toggleDeviceInfo(true);
                            break;
                        case 'unregister':
                            logService.info(logKey, 'Device has been unregistered by user');
                            resetPasscode();
                        default: // eslint-disable-line no-fallthrough
                            processDefaultMenuAction($scope.selectedItem.value);
                            break;
                    }
                };

                function processDefaultMenuAction(value) {
                    // check if value belongs to driver
                    var driverMenuCallback = driverMenu[value];
                    if (driverMenuCallback) {
                        driverMenuCallback();
                        // auto close menu
                        $scope.toggleMenu(false);
                        $scope.toggleOsd(false);
                        return;
                    }

                    $scope.toggleMenu(false);
                    // Unregister as we are browsing away
                    halRemoteService.unRegisterEvents(onRemoteEvent);
                    executeAdvanceTask(value);
                    $scope.toggleOsd(false);
                }

                function executeAdvanceTask(task) {
                    $location.url('/tv/deviceoperation/' + task);
                }

                function getStaticDeviceInfo(cacheStaticData) {
                    // if cache is enable & we have static info, return directly
                    if (staticInfo && cacheStaticData) {
                        return $q.resolve(staticInfo);
                    }

                    var promise = $q.defer();
                    staticInfo = {};
                    staticInfo.deviceName = storageLocalStorageService.get('signs.registration', 'devicename') || '-';
                    staticInfo.deviceGuid = storageLocalStorageService.get('device', 'playerguid') || '-';

                    // Safely handle buildVersion and softwareVer with fallbacks
                    var softwareVer = $scope.softwareVer || '';
                    var buildVersion = $scope.buildVersion || '';
                    var displayBuildVersion = buildVersion.replace(softwareVer + '-build', '');

                    staticInfo.appVersion = {
                        app: softwareVer,
                        appBuild: displayBuildVersion
                    };
                    staticInfo.serverUrl = storageLocalStorageService.get('appspace', 'appspaceURL') || '-';
                    staticInfo.deviceModel = $scope.deviceModel;
                    var getIpPromise = getDeviceIpAddress();
                    getIpPromise.then(function(resIP) {
                        staticInfo.deviceIpAddress = resIP;
                    });
                    var getFirmwarePromise = halPlatformService.getFirmware();
                    getFirmwarePromise.then(function(resFirmware) {
                        staticInfo.deviceFirmware = resFirmware;
                    });
                    var getFingerprintPromise = halPlatformService.getFingerprint();
                    getFingerprintPromise.then(function(resFingerprint) {
                        resFingerprint = resFingerprint || {};
                        staticInfo.deviceFingerprint = resFingerprint.fingerprint || '-';
                    });
                    staticInfo.activeCdn = getActiveCdn();
                    var getDisplayInfoPromise = getDisplayInfo();
                    getDisplayInfoPromise.then(function(resDisplayInfo) {
                        staticInfo.displayInfo = resDisplayInfo;
                    });
                    var promises = [
                        getIpPromise,
                        getFirmwarePromise,
                        getDisplayInfoPromise,
                        getFingerprintPromise
                    ];
                    var resolve = function() {
                        promise.resolve(staticInfo);
                    };

                    $q.allSettled(promises).then(resolve, resolve);
                    return promise.promise;
                }

                function getCurrentDeviceInfo(cacheStaticData) {
                    var promise = $q.defer();
                    getStaticDeviceInfo(cacheStaticData).then(function(res) {
                        res.deviceLocalTime = getDeviceLocalTime();
                        res.deviceUpTime = getDeviceUpTime();
                        res.serverConnection = !(getTimestampFailCount() >= 3);
                        res.syncStatus = getSyncStatus();
                        res.deviceTimeStamp = getDeviceTimestamp();

                        var getMemoryPromise = getMemoryUsage();
                        getMemoryPromise.then(function(resMemory) {
                            res.memoryUsage = resMemory;
                        });
                        var getCpuPromise = getCpuUsage();
                        getCpuPromise.then(function(resCPU) {
                            res.cpuUsage = resCPU;
                        });
                        var getCurrentChannelPromise = getCurrentChannel();
                        getCurrentChannelPromise.then(function(resCurrentChannel) {
                            res.currentChannel = resCurrentChannel;
                        });
                        var getStorageUsagePromise = getStorageUsage();
                        getStorageUsagePromise.then(function(resStorage) {
                            res.storageUsage = resStorage;
                        });
                        var promises = [getMemoryPromise, getCpuPromise,
                            getCurrentChannelPromise, getStorageUsagePromise];
                        var resolve = function() {
                            promise.resolve(res);
                        };

                        $q.allSettled(promises).then(resolve, resolve);
                    });
                    return promise.promise;
                }

                $scope.toggleOsd = function(show) {
                    if (halPlatformService.getFeatureSupport('support.device.menu') === 'no') {
                        logService.info(logKey, 'Device menu is not supported in preview mode');
                        return;
                    }

                    if (show == undefined) {
                        show = !$scope.enableOsd;
                    }

                    if (show) {
                        // Register for remote input
                        halRemoteService.unRegisterEvents(onRemoteEvent);
                        halRemoteService.registerEvents(onRemoteEvent);

                        $scope.displayOsd = '';
                        passwordPromptLogic();
                        $timeout(function() {
                            tabIndexFocus(1);
                        }, 0);
                    } else {
                        halRemoteService.unRegisterEvents(onRemoteEvent);
                        $scope.displayOsd = 'none';
                        $scope.toggleLog(false);
                        $scope.toggleMenu(false);
                        $scope.toggleDeviceInfo(false);
                        toggleKeyboardFocus(false);
                        resetKeyboard();
                        stopIdle();
                        if (dosavelog) {
                            saveCustomLogLevels();
                        }
                    }

                    $scope.enableOsd = show;
                    halMediaService.setAppDisplayState(!show);
                    return {
                        display: show
                    };
                };

                function updateMenuState(isMenuOpen) {
                    deviceMenuInvokerService[isMenuOpen ? 'disableHotArea' : 'enableHotArea']();
                }

                $scope.toggleMenu = function(show) {
                    logService.trace(logKey, 'Device menu toggle: ' + show);
                    updateMenuState(show);
                    if (show) {
                        // reset selected
                        selectedIndex = (halRemoteService.getLastEventTime()) ? 0 : -1;
                        $scope.selectedItem = $scope.items[selectedIndex];

                        $scope.displayMenu = '';
                        coreFrameworkService.isReady().then(function() {
                            $scope.softwareVer = coreFrameworkService.getAppVersion();
                            $scope.buildVersion = coreFrameworkService.getBuildVersion();

                            halNetworkService.getIpAddress().then(function(res) {
                                $scope.ipAddress = res.address;
                            }, function() {
                                $scope.ipAddress = '-';
                            });

                            var displayModel = function(model) {
                                $scope.deviceModel += model;
                            };

                            var retrieveModel = function(manufacturer) {
                                if (manufacturer) {
                                    manufacturer += ' - ';
                                }

                                $scope.deviceModel = manufacturer;

                                var model = halPlatformService.getModel();
                                if (dataUtil.isPromise(model)) {
                                    model.then(displayModel);
                                    return;
                                }

                                displayModel(model);
                            };

                            var man = halPlatformService.getManufacturer();
                            if (man && man.then) { // promised based
                                man.then(retrieveModel, function() {
                                    retrieveModel('');
                                });
                            } else if (man) { // return based
                                retrieveModel(man);
                            } else {
                                retrieveModel('');
                            }
                        });
                    } else {
                        $scope.displayMenu = 'none';
                    }
                };

                function saveCustomLogLevels() {
                    // Logger configuration
                    var config = logService.getConfiguration();
                    if (config) {
                        for (var i = 0; i < config.loggers.length; i++) {
                            var logger = config.loggers[i];
                            if (logger.logLevel) {
                                storageLocalStorageService.set('profile.loggerconfig', logger.name, logger.logLevel);
                            } else {
                                // Remove the key
                                storageLocalStorageService.removeKey('profile.loggerconfig', logger.name);
                            }
                        }

                        storageLocalStorageService.set('profile.loggerconfig', 'default', config.logLevel);
                    }
                }

                $scope.toggleLog = function(show) {
                    logService.trace(logKey, 'Device log toggle: ' + show);
                    $scope.toggleMenu(!show);
                    updateMenuState(show);
                    if (show) {
                        $scope.showLog = true;
                        $scope.enableLogUpdates = true;
                        $scope.minimized = 'none';
                        dosavelog = true;
                    } else {
                        $scope.enableLogUpdates = false;
                        $scope.showLog = false;
                        $scope.minimized = 'none';
                    }
                };

                var timerIntervalHandler;
                $scope.toggleDeviceInfo = function(show) {
                    logService.trace(logKey, 'Device Info toggle: ' + show);
                    $scope.showDeviceInfo = show;

                    updateMenuState(show);
                    if (show) {
                        // clear the static info so that we grab the new info when we open device info
                        staticInfo = null;
                        timerIntervalHandler = $interval(function() {
                            getCurrentDeviceInfo(true).then(function(res) {
                                // get device name
                                $scope.deviceName = res.deviceName;

                                // get device guid
                                $scope.deviceGuid = res.deviceGuid;

                                // device ipAddress
                                $scope.deviceIpAddress = res.deviceIpAddress;

                                // device Firmware
                                $scope.deviceFirmware = res.deviceFirmware;

                                // device appVersion
                                $scope.appVersion = 'Appspace App: ' + res.appVersion.app + ' | Build: ' + res.appVersion.appBuild;

                                // get server url
                                $scope.serverUrl = res.serverUrl;

                                // get active cdn or smarthub
                                $scope.activeCdn = res.activeCdn;

                                $scope.memoryUsage = res.memoryUsage;
                                $scope.deviceLocalTime = res.deviceLocalTime;
                                $scope.deviceUpTime = res.deviceUpTime;
                                $scope.cpuUsage = res.cpuUsage;
                                $scope.serverConnection = res.serverConnection ? 'True' : 'False';
                                $scope.syncStatus = res.syncStatus;
                                $scope.deviceTimeStamp = res.deviceTimeStamp;
                                $scope.currentChannel = res.currentChannel;
                                $scope.storageUsage = res.storageUsage;
                                $scope.displayInfo = res.displayInfo;
                                $scope.deviceFingerprint = res.deviceFingerprint;
                            });
                        }, 1000);
                    } else {
                        getElement('.device-info-view .info').animate({scrollTop: 0}, 0);
                        if (timerIntervalHandler) {
                            logService.trace(logKey, 'Timer interval canceled');
                            $interval.cancel(timerIntervalHandler);
                        }
                    }
                };

                function getDeviceIpAddress() {
                    var promise = $q.defer();
                    halNetworkService.getIpAddress().then(function(res) {
                        promise.resolve(res.address);
                    });

                    return promise.promise;
                }

                function getDeviceTimestamp() {
                    var timestamp = syncService.getCurrentTimestamp();
                    if (timestamp) {
                        return timestamp;
                    } else {
                        return '-';
                    }
                }

                function getTimestampFailCount() {
                    return syncService.getTimestampFailCount();
                }

                function getDeviceLocalTime() {
                    return formatDate(new Date());
                }

                function formatDate(date) {
                    var language;
                    if (navigator) {
                        language = navigator.language
                            || navigator.userLanguage
                            || navigator.browserLanguage;
                        if (!language && navigator.languages && navigator.languages.length > 0) {
                            language = navigator.languages[0];
                        }
                    }

                    return moment(date).locale(language || 'en').format('L LTS');
                }

                function getDeviceUpTime() {
                    var currentDateTime = new Date();
                    var startUpTime = storageLocalStorageService.get('device.info', 'startuptime');
                    var appRuntime = currentDateTime.getTime() - startUpTime.getTime();
                    var ticksInSecs = appRuntime / 1000;
                    var hh = Math.floor(ticksInSecs / 3600);
                    var mm = Math.floor((ticksInSecs % 3600) / 60);
                    var ss = Math.floor(ticksInSecs % 60);
                    return hh + ' hours ' + mm + ' minutes and ' + ss + ' seconds (since ' + formatDate(startUpTime) + ')';
                }

                function getCurrentChannel() {
                    var promise = $q.defer();

                    var id = storageLocalStorageService.get('device.sync', 'appid');
                    if (id) {
                        syncService.getStartupApp().then(function(startUp) {
                            if (startUp.type == 'play') {
                                promise.resolve('Interactive Mode');
                            } else {
                                promise.resolve(startUp.sign.name);
                            }
                        });
                    } else {
                        promise.resolve('No app available');
                    }

                    return promise.promise;
                }

                function getActiveCdn() {
                    var activecdn = cdnService.getActiveCDN();
                    return (activecdn && activecdn.url) ? activecdn.url : '-';
                }

                function getCpuUsage() {
                    var promise = $q.defer();

                    halPlatformService.getCpuUsage().then(function(res) {
                        var cpu = '-';
                        if (res && res.cpuUsage !== undefined) {
                            cpu = res.cpuUsage.toFixed(2) + '%';
                        }

                        promise.resolve(cpu);
                    }, function(err) {
                        promise.resolve('-');
                    });

                    return promise.promise;
                }

                function formatBytesOutput(response, firstValue, secondValue) {
                    var formatBytes = response.useIEC ? dataUtil.formatBytesIEC : dataUtil.formatBytes;
                    return formatBytes(firstValue) + '/' + formatBytes(secondValue);
                }

                function getStorageUsage() {
                    var promise = $q.defer();
                    halPlatformService.getAvailableStorage().then(function(res) {
                        var storage = '-';
                        if (res && res.used && res.total) {
                            storage = formatBytesOutput(res, res.used, res.total);
                        }

                        promise.resolve(storage);
                    }, function(err) {
                        promise.resolve('-');
                    });

                    return promise.promise;
                }

                function getMemoryUsage() {
                    var promise = $q.defer();
                    var memory = '-';

                    halPlatformService.getMemoryUsage().then(function(res) {
                        if (res && res.capacity && res.availableCapacity) {
                            memory = res.percentage ?
                                res.availableCapacity + '%' :
                                formatBytesOutput(res, res.availableCapacity, res.capacity);
                        }

                        promise.resolve(memory);
                    }, function(err) {
                        promise.resolve(memory);
                    });

                    return promise.promise;
                }

                function getDisplayInfo() {
                    var promise = $q.defer();

                    halPlatformService.getDisplayInfo().then(function(res) {
                        var display = '';
                        if (res && res.length > 0) {
                            for (var i = 0; i < res.length; i++) {
                                display += res[i].x + ' X ' + res[i].y + ' (' + (i + 1) + ') ';
                            }
                        } else {
                            display = '-';
                        }

                        promise.resolve(display);
                    }, function(err) {
                        promise.resolve('-');
                    });

                    return promise.promise;
                }

                $scope.submitpasscode = function() {
                    if (keyboardApi.getInputValue() == passcode + '') {
                        $scope.displayMenu = '';
                        toggleKeyboardFocus(false);
                        stopIdle();
                        lastPasscodeSucsessTime = (new Date()).getTime();
                        coreConfigurationService.set('appspace', 'lastPasscodeSucsessTime', lastPasscodeSucsessTime);
                    } else {
                        $scope.passcodeArr = new Array(4);
                        keyboardApi.clearInputValue();
                        tabIndexFocus(1);
                        $scope.showError = '';
                    }
                };

                function resetKeyboard() {
                    $scope.showpasscode = 'none';
                    // $scope.passcodeTxt = "";
                    lastKey = null;
                    if (keyboardApi) {
                        keyboardApi.clearInputValue();
                        keyboardApi.removeHighlight();
                    }

                    $scope.passcodeArr = new Array(4);
                    $scope.showError = 'none';
                }

                function resetAndStartOverIdle() {
                    stopIdle();
                    idleTimer = $timeout(function() {
                        $scope.toggleOsd(false);
                        stopIdle();
                    }, idleDuration);
                }

                function stopIdle() {
                    if (idleTimer) {
                        $timeout.cancel(idleTimer);
                        idleTimer = null;
                    }
                }

                $scope.onKeyboardApi = function(api) {
                    keyboardApi = api;

                    // import layouts into the keyboard
                    var file = 'assets/keyboard-layouts/device-menu-passcode.json';
                    var promise = keyboardApi.importLayouts(file);
                    if (promise) {
                        promise.then(function(layouts) {
                            if (layouts && layouts.length > 0) {
                                // load the layout
                                keyboardApi.setLayout(layouts[0]);
                            }
                        });
                    }
                };

                $scope.onKeyboardFocusChange = function(previousKey, currentKey) {
                    lastKey = currentKey;
                };

                $scope.onKeyboardValueChange = function(previousVal, currentVal) {
                    resetAndStartOverIdle();
                    if (currentVal.length > 4) {
                        keyboardApi.setInputValue(currentVal.substring(0, 4));
                        return;
                    }

                    var tempArray = currentVal.split('');

                    for (var i = 0; i < $scope.passcodeArr.length; i++) {
                        var temp = tempArray[i];
                        if (!temp) {
                            temp = '';
                        }

                        $scope.passcodeArr[i] = temp;
                    }

                    if (tempArray.length < 4) {
                        tabIndexFocus(tempArray.length + 1);
                    }
                };

                $scope.txtInputChange = function(index, val) {
                    resetAndStartOverIdle();
                    // remove empty space
                    val = val.replace(' ', '');

                    var isValidChar = true;

                    // limit to number only
                    if (isNaN(val) || val == '') {
                        isValidChar = false;
                    }

                    if (isValidChar) {
                        tabIndexFocus(index + 1);
                    } else {
                        $scope.passcodeArr[index - 1] = null;
                    }

                    keyboardApi.setInputValue($scope.passcodeArr.join(''));
                };

                $scope.onKeyboardActionSelected = function(key) {
                    resetAndStartOverIdle();
                    if (key) {
                        switch (key.value) {
                            case 'next':
                                // do nothing if on the register now link
                                if (tabIndex < 5) {
                                    tabIndexFocus(5);
                                    keyboardApi.removeHighlight();
                                }

                                break;
                        }
                    }
                };

                function toggleKeyboardFocus(focus) {
                    keyboardFocus = focus;
                    if (keyboardApi == null) {
                        return;
                    }

                    if (focus) {
                        // highlight last key on focus
                        if (lastKey) {
                            keyboardApi.focusKey(lastKey.value);
                        }
                    } else {
                        keyboardApi.removeHighlight();
                        resetKeyboard();
                    }
                }

                function getSyncStatus() {
                    var msg;
                    var state = syncService.getSyncState();
                    if (!state.started) {
                        msg = 'Sync service stopped';
                    } else {
                        if (state.syncing) {
                            msg = 'Device is syncing. ';
                            var p = state.syncProgress;
                            if (p && (p.total != p.cached)) {
                                msg += (p.total - p.cached - p.failed) + ' files remaining';
                            }
                        } else {
                            if (state.isInSynced) {
                                msg = 'In sync ';
                                var lastSync = storageLocalStorageService.get('device.sync', 'lastSyncTime');
                                if (lastSync) {
                                    msg += ' (Synced at ' + formatDate(lastSync) + ')';
                                }
                            } else {
                                msg = state.error || 'Out of sync';
                            }
                        }
                    }

                    return msg;
                }

                $scope.closeLog = function() {
                    saveCustomLogLevels();
                    $scope.enableLogUpdates = false;
                    $scope.toggleOsd(false);
                    $scope.toggleLog(false);
                };

                $scope.minimizeLog = function() {
                    $scope.showLog = false;
                    $scope.minimized = '';
                };

                $scope.maximizeLog = function() {
                    $scope.showLog = true;
                    $scope.minimized = 'none';
                };

                $scope.closeDeviceInfo = function() {
                    $scope.toggleOsd(false);
                    $scope.toggleDeviceInfo(false);
                };

                $scope.isSelected = function(item) {
                    return $scope.selectedItem === item;
                };

                function tabIndexFocus(ind) {
                    tabIndex = ind;
                    getElement('[tabindex=\'' + tabIndex + '\']').focus();
                }

                function passwordPromptLogic() {
                    var currentTime = (new Date()).getTime();
                    var timeDiff = (currentTime - lastPasscodeSucsessTime) / 1000;

                    if (passcode) {
                        if (passcodeExpiryDuration == 0 || (timeDiff > passcodeExpiryDuration)) {
                            $scope.showpasscode = '';
                            toggleKeyboardFocus(true);
                            resetAndStartOverIdle();
                            return;
                        }
                    }

                    $scope.toggleMenu(true);
                }

                function initMenu(config) {
                    config = config || {};
                    var menu = [{
                        text: 'Restart',
                        value: 'restart'
                    },
                    {
                        text: 'Resync',
                        value: 'resync'
                    },
                    {
                        text: 'Reset Content',
                        value: 'reset'
                    },
                    {
                        text: 'Unregister',
                        value: 'unregister'
                    },
                    {
                        text: 'Log View',
                        value: 'showlogger'
                    },
                    {
                        text: 'Device Info',
                        value: 'deviceinfo'
                    }
                    ];

                    if (halPlatformService.getFeatureSupport('devicemenu.allowclose')) {
                        menu.push({
                            text: 'Exit',
                            value: 'closeapplication'
                        });
                    }

                    addAdditionalMenuItems(menu);

                    if (!config.showSingleAppMode) {
                        $scope.items = menu;
                        return;
                    }

                    var text = SINGLE_APP_MENU_LABEL.ENTER;
                    var value = DEVICE_MENU_COMMAND.ENTER_SINGLE_APP_MODE;
                    if (config.isSingleAppMode && !dataUtil.isValueBooleanFalse(config.success)) {
                        text = SINGLE_APP_MENU_LABEL.EXIT;
                        value = DEVICE_MENU_COMMAND.EXIT_SINGLE_APP_MODE;
                    }

                    menu.push({text: text, value: value});
                    $scope.items = menu;
                }

                function initPasscode() {
                    resetPasscode();

                    var appId = syncService.retrieveAppId();

                    syncService.getAppProperties('menu.passcode', appId).then(function(res) {
                        passcode = res;
                    }, $angular.noop);

                    syncService.getAppProperties('menu.passcode.expiry', appId).then(function(res) {
                        var tempExpiry = res;
                        if (tempExpiry) {
                            passcodeExpiryDuration = tempExpiry * 60;
                        }
                    }, $angular.noop);

                    var temp = coreConfigurationService.get('appspace', 'lastPasscodeSucsessTime');
                    if (temp) {
                        lastPasscodeSucsessTime = temp;
                    } else {
                        lastPasscodeSucsessTime = 0;
                    }

                    keyboardSupport = halPlatformService.getFeatureSupport('input.keyboard');
                    if (keyboardSupport == 'hardware') {
                        $scope.showKeyboard = false;
                    }
                }

                function resetPasscode() {
                    passcode = null;
                    passcodeExpiryDuration = 0;
                    lastPasscodeSucsessTime = 0;
                }

                function isVisible() {
                    return $scope.enableOsd;
                }

                function addAdditionalMenuItems(menu) {
                    var additionalMenu = halPlatformService.getDeviceMenu();
                    if (dataUtil.isObjectEmptyOrUndefined(additionalMenu)) {
                        return;
                    }

                    for (var key in additionalMenu) {
                        var menuItem = additionalMenu[key];
                        menu.push({
                            text: menuItem.text,
                            value: key
                        });
                        driverMenu[key] = menuItem.callback;
                    }
                }

                coreFrameworkService.isReady().then(init);
            }]
        };
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');
    app.component('deviceMenuInvoker', {
        templateUrl: 'tv/devicemenu/deviceMenuInvoker.html',
        bindings: {
            showMenu: '='
        },
        controller: ["$window", "$timeout", "$scope", "SYNC_EVENT", "PLAYER_PROPERTY_KEYS", "STORAGE_COLLECTION_KEY", "STORAGE_KEY", "DEVICE_MENU_INVOKE_COMMAND", "coreConfigurationService", "syncService", "DeviceMenuInvokeInfo", "dataUtil", "deviceMenuInvokerService", function(
            $window,
            $timeout,
            $scope,
            SYNC_EVENT,
            PLAYER_PROPERTY_KEYS,
            STORAGE_COLLECTION_KEY,
            STORAGE_KEY,
            DEVICE_MENU_INVOKE_COMMAND,
            coreConfigurationService,
            syncService,
            DeviceMenuInvokeInfo,
            dataUtil,
            deviceMenuInvokerService
        ) {
            var $ctrl = this;
            var VALID_STYLES = ['background', 'border'];
            var WINDOW_PROPERTY = {
                ONTOUCHSTART: 'ontouchstart'
            };
            var WINDOW_EVENT = {
                TOUCHSTART: 'touchstart',
                MOUSEDOWN: 'mousedown',
                BLUR: 'blur'
            };
            var DEFAULT_STYLE = {
                display: 'block',
                opacity: 0
            };
            var OPACITY = {
                TRANSPARENT: '0',
                OPAQUE: '1'
            };
            var CSS_PERCENTAGE_UNIT = '%';
            var IDLE_TIMEOUT_IN_MILLISECONDS = 5000;
            var invokerTimer = undefined;
            var isTouched = false;
            var invokeInfo = new DeviceMenuInvokeInfo();
            var configHotAreaStyle = DEFAULT_STYLE;
            $ctrl.isMenuOpen = false;
            $ctrl.hotAreaStyle = configHotAreaStyle;
            $ctrl.isHotAreaEnabled = false;

            $scope.$on('$destroy', function() {
                syncService.unsubscribe(onSyncServiceUpdated);
                removeEventListeners();
            });

            function removeEventListeners() {
                if (WINDOW_PROPERTY.ONTOUCHSTART in $window) {
                    $window.removeEventListener(WINDOW_EVENT.TOUCHSTART, tryInvokeDeviceMenuFromTouch, $ctrl.isHotAreaEnabled);
                }

                $window.removeEventListener(WINDOW_EVENT.MOUSEDOWN, tryInvokeDeviceMenuFromMouse, $ctrl.isHotAreaEnabled);
                $window.removeEventListener(WINDOW_EVENT.BLUR, restartDeviceMenuInvokerTimer, $ctrl.isHotAreaEnabled);
            }

            function addEventListeners() {
                if (WINDOW_PROPERTY.ONTOUCHSTART in $window) {
                    $window.addEventListener(WINDOW_EVENT.TOUCHSTART, tryInvokeDeviceMenuFromTouch, $ctrl.isHotAreaEnabled);
                }

                $window.addEventListener(WINDOW_EVENT.MOUSEDOWN, tryInvokeDeviceMenuFromMouse, $ctrl.isHotAreaEnabled);
                $window.addEventListener(WINDOW_EVENT.BLUR, restartDeviceMenuInvokerTimer, $ctrl.isHotAreaEnabled);
            }

            function init() {
                addEventListeners();
                resetDeviceMenuInvoker();
                syncService.subscribe(onSyncServiceUpdated);
                deviceMenuInvokerService.subscribe(onDeviceMenuInvokeUpdated);
            }

            function onDeviceMenuInvokeUpdated(event) {
                switch (event) {
                    case DEVICE_MENU_INVOKE_COMMAND.DISABLE_HOT_AREA:
                        $ctrl.isMenuOpen = true;
                        break;
                    case DEVICE_MENU_INVOKE_COMMAND.ENABLE_HOT_AREA:
                        $ctrl.isMenuOpen = false;
                        break;
                }
            }

            function getProperties() {
                return syncService.getCurrentAppProperties() || {};
            }

            function onSyncServiceUpdated(event) {
                if (!event) {
                    return;
                }

                switch (event.event) {
                    case SYNC_EVENT.SERVICE_STOPPED:
                        configureCurrentActions();
                        configureHotArea();
                        break;
                    case SYNC_EVENT.SCRIPT_READY:
                    case SYNC_EVENT.SCRIPT_SAVED:
                        var properties = getProperties();
                        configureCurrentActions(properties);
                        configureHotArea(properties);
                        break;
                }
            }

            function configureCurrentActions(properties) {
                properties = properties || {};
                invokeInfo.setCurrentActions(properties[PLAYER_PROPERTY_KEYS.DEVICE_MENU_INVOKE_PATTERN]);
            }

            function configureHotArea(properties) {
                properties = properties || {};
                removeEventListeners();
                $ctrl.isHotAreaEnabled = dataUtil.parseBoolean(properties[PLAYER_PROPERTY_KEYS.DEVICE_MENU_HOT_AREA_ENABLED]);
                addEventListeners();
                parseHotAreaStyle(properties);
                updateCurrentHotArea();
            }

            function parseHotAreaStyle(properties) {
                configHotAreaStyle = DEFAULT_STYLE;
                var input = properties[PLAYER_PROPERTY_KEYS.DEVICE_MENU_HOT_AREA_STYLE];
                if (!dataUtil.isString(input)) {
                    return;
                }

                var inputStyle = dataUtil.parseInlineStyle(input) || [];
                var validStyle = {};
                for (var i = 0; i < inputStyle.length; i++) {
                    var style = inputStyle[i] || {};
                    style.property = dataUtil.trim(style.property || '').toLowerCase();
                    if (!dataUtil.includesValue(style.property, VALID_STYLES)) {
                        continue;
                    }

                    validStyle[style.property] = style.value || '';
                }

                configHotAreaStyle = dataUtil.mergeObject(configHotAreaStyle, validStyle);
            }

            function updateCurrentHotArea() {
                if (!$ctrl.isHotAreaEnabled) {
                    $ctrl.hotAreaStyle = DEFAULT_STYLE;
                    return;
                }

                var isVisible = invokeInfo.isCurrentActionVisible();
                var action = invokeInfo.getCurrentAction();
                if (!action) {
                    action = invokeInfo.getActionByIndex(0);
                }

                if (!action) {
                    return;
                }

                $ctrl.hotAreaStyle = dataUtil.mergeObject(configHotAreaStyle, {
                    width: action.widthPercentage + CSS_PERCENTAGE_UNIT,
                    height: action.heightPercentage + CSS_PERCENTAGE_UNIT,
                    top: action.topPercentage + CSS_PERCENTAGE_UNIT,
                    left: action.leftPercentage + CSS_PERCENTAGE_UNIT,
                    opacity: isVisible ? OPACITY.OPAQUE : OPACITY.TRANSPARENT
                });
            }

            function restartDeviceMenuInvokerTimer() {
                if (invokerTimer) {
                    $timeout.cancel(invokerTimer);
                }

                invokerTimer = $timeout(resetDeviceMenuInvoker, IDLE_TIMEOUT_IN_MILLISECONDS);
            }

            function resetDeviceMenuInvoker() {
                invokeInfo.lastActionIndex = 0;
                updateCurrentHotArea();
            }

            function tryInvokeDeviceMenuFromTouch(e) {
                preventDefault(e);
                if (e.touches && e.touches.length > 0) {
                    if (!e.pageX) {
                        e.pageX = e.touches[0].pageX;
                    }

                    if (!e.pageY) {
                        e.pageY = e.touches[0].pageY;
                    }
                }

                if (e.pageX && e.pageY) {
                    isTouched = true;
                    tryInvokeDeviceMenu(e);
                    return;
                }

                isTouched = false;
            }

            function tryInvokeDeviceMenuFromMouse(e) {
                preventDefault(e);
                if (isTouched) {
                    isTouched = false;
                    return;
                }

                tryInvokeDeviceMenu(e);
            }

            function preventDefault(event) {
                if (!$ctrl.isHotAreaEnabled || !event || $ctrl.isMenuOpen) {
                    return;
                }

                event.preventDefault();
            }

            function tryInvokeDeviceMenu(e) {
                var deviceGuid = coreConfigurationService.get(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.DEVICE_ID);
                if ($ctrl.showMenu || !dataUtil.isString(deviceGuid) || dataUtil.trim(deviceGuid).length <= 0) {
                    invokeInfo.lastActionIndex = 0;
                    return;
                }

                if (invokeInfo.canInvoke(e.pageX, e.pageY, $window.innerWidth, $window.innerHeight)) {
                    $ctrl.showMenu = true;
                }

                restartDeviceMenuInvokerTimer();
                updateCurrentHotArea();
            }

            init();
        }]
    });
})(window.angular);

(function($angular) {
    'use strict';
    var app = $angular.module('hal');

    app.service('deviceMenuInvokerService', ["DEVICE_MENU_INVOKE_COMMAND", function(DEVICE_MENU_INVOKE_COMMAND) {
        var subscribers = [];

        function subscribe(callback) {
            if (!callback || subscribers.indexOf(callback) > -1) {
                return false;
            }

            subscribers.push(callback);
            return true;
        }

        function unsubscribe(callback) {
            var index = subscribers.indexOf(callback);
            if (index === -1) {
                return false;
            }

            subscribers.splice(index, 1);
            return true;
        }

        function notifySubscribers(event) {
            for (var i = 0; i < subscribers.length; i++) {
                subscribers[i](event);
            }
        }

        function enableHotArea() {
            notifySubscribers(DEVICE_MENU_INVOKE_COMMAND.ENABLE_HOT_AREA);
        }

        function disableHotArea() {
            notifySubscribers(DEVICE_MENU_INVOKE_COMMAND.DISABLE_HOT_AREA);
        }

        return {
            subscribe: subscribe,
            unsubscribe: unsubscribe,
            enableHotArea: enableHotArea,
            disableHotArea: disableHotArea
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    var app = angular.module('signsHTML5');
    app.controller('DeviceOperationCtrl', ["$scope", "$element", "$location", "$timeout", "$window", "logService", "networkContentCacheService", "halPlatformService", "halNetworkService", "syncService", "halRemoteService", "navigationService", "REMOTEVCODE", "REMOTEEVENTTYPE", "deviceStateService", "DEVICE_MENU_COMMAND", "ROUTE", "remoteServiceManager", "singleAppService", "STORAGE_KEY", "dataUtil", function($scope, $element, $location, $timeout, $window, logService, networkContentCacheService,
        halPlatformService, halNetworkService, syncService, halRemoteService, navigationService,
        REMOTEVCODE, REMOTEEVENTTYPE, deviceStateService, DEVICE_MENU_COMMAND, ROUTE,
        remoteServiceManager, singleAppService, STORAGE_KEY, dataUtil) {
        var localStorage = halPlatformService.getLocalStorage();
        var SINGLE_APP_OPERATION = {
            ENTER_SINGLE_APP_MODE: 'enterSingleAppMode',
            EXIT_SINGLE_APP_MODE: 'exitSingleAppMode'
        };
        $scope.showLoader = true;
        $scope.operationMsg = '';

        function init() {
            // Handle view destroy
            $scope.$on('$destroy', function() {
                // clean up stuff
                halRemoteService.unRegisterEvents(onRemoteEvent);
            });

            // Register for remote input
            halRemoteService.registerEvents(onRemoteEvent);

            // get operation from the query string
            var op = getOperationFromQueryString();

            // Run operation
            runOperation(op);
        }

        function getOperationFromQueryString() {
            // get currentpath
            var url = $location.url();
            if (url) {
                // get operation
                var array = url.split('/');
                if (array.length > 0) {
                    var op = array[array.length - 1];
                    logService.trace('appspace.signs', 'Device operation type: ' + op);
                    return op;
                }
            }

            return null;
        }

        // Event handler for remote events
        var onRemoteEvent = function(remoteEvent) {
            // Only listen to down button events
            if (remoteEvent.eventType != REMOTEEVENTTYPE.buttonDown) {
                return;
            }

            $window.history.back();

            remoteEvent.preventDefault();
        };

        function stopServices() {
            remoteServiceManager.stop();
        }

        function runOperation(op) {
            var reloadToSplash = function() {
                $timeout(function() {
                    toggleLoading(false);
                    navigationService.reloadView(ROUTE.SPLASH);
                }, 2000);
            };

            switch (op) {
                case 'restart':
                    // restart the app
                    stopServices();
                    logService.info('appspace.signs', 'Device restarted by user');
                    $scope.operationMsg = 'Restarting the app...';
                    syncService.stopSync().then(reloadToSplash);
                    break;
                case DEVICE_MENU_COMMAND.RESYNC:
                    // Resync content and settings from server
                    stopServices();
                    $scope.operationMsg = 'Resyncing settings & content...';
                    logService.info('appspace.signs.sync', 'Content resync triggered on device');
                    syncService.stopSync().then(function() {
                        deviceStateService.clearCachedScripts();
                        reloadToSplash();
                    });
                    break;
                case 'reset':
                    // delete local content and redownload from server
                    stopServices();
                    $scope.operationMsg = 'Resetting content...';
                    syncService.stopSync().then(function() {
                        deviceStateService.resetContent().then(reloadToSplash);
                    });
                    break;
                case 'unregister':
                    unregister(false);
                    break;
                case 'resetdevice': // coming from sync service 410 when getting timestamp
                    unregister(true);
                    break;
                case 'closeapplication':
                    halPlatformService.closeApplication();
                    break;
                case DEVICE_MENU_COMMAND.ENTER_SINGLE_APP_MODE:
                    singleAppOperation(SINGLE_APP_OPERATION.ENTER_SINGLE_APP_MODE);
                    break;
                case DEVICE_MENU_COMMAND.EXIT_SINGLE_APP_MODE:
                    singleAppOperation(SINGLE_APP_OPERATION.EXIT_SINGLE_APP_MODE);
                    break;
            }
        }

        function singleAppOperation(command) {
            if (!command) {
                return;
            }

            var service = singleAppService[command];
            if (!service) {
                return;
            }

            service().then(storeSingleAppOperationResult, storeSingleAppOperationResult)
                .finally(function() {
                    runOperation(DEVICE_MENU_COMMAND.RESYNC);
                });
        }

        function storeSingleAppOperationResult(result) {
            result = dataUtil.mergeObject(result, {isMenuTriggered: true});
            localStorage.setItem(STORAGE_KEY.SINGLE_APP_STATUS, JSON.stringify(result));
        }

        function unregister(cleanOnly) {
            $scope.operationMsg = 'Unregistering device...';
            syncService.stopSync().then(function() {
                deviceStateService.clearCachedScripts();
                var path = '/tv/unregister';
                $location.path(path).search('clean', cleanOnly.toString());
            });
        }

        // Toggle the loading mask
        function toggleLoading(show) {
            $scope.showLoader = show;
        }

        init();
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    // Parses AZ into entity
    app.factory('DeviceLicensingParser', ["$q", "dataUtil", function($q, dataUtil) {
        function DeviceLicensingParser() {
            var EMPTY_SCRIPT_BLOCK_ERROR_MESSAGE = 'Script block is empty';
            var SEMICOLON = ';';

            this.type = 'signs.parser.device-licensing';
            this.parse = function(scriptBlock) {
                if (!scriptBlock || !scriptBlock.script) {
                    return $q.reject(EMPTY_SCRIPT_BLOCK_ERROR_MESSAGE);
                }

                var result = {
                    id: scriptBlock.id,
                    type: scriptBlock.type,
                    licenses: []
                };

                // e.g. script
                // <TABLE>
                // <ID>DeviceLicensing_799</ID>
                // feature.device.services.alert=false; (default true)
                // feature.device.services.adm=false; (default true)
                // feature.device.services.analytics=false; (default true)
                // feature.device.services.health=false; (default true)
                // </TABLE>
                var lines = dataUtil.splitByNewLines(scriptBlock.script);
                if (!lines) {
                    return $q.resolve(result);
                }

                var licenses = [];
                for (var i = 0; i < lines.length; i++) {
                    var line = lines[i];
                    var lastLinePosition = line.length - 1;
                    // remove the semicolon at the end of the value
                    if (line.charAt(lastLinePosition) === SEMICOLON) {
                        line = line.substring(0, lastLinePosition);
                    }

                    var keyValuePair = dataUtil.getKeyValuePairFromString(line);
                    if (!keyValuePair) {
                        continue;
                    }

                    licenses.push(keyValuePair);
                }

                result.licenses = licenses;
                return $q.resolve(result);
            };
        }

        return (DeviceLicensingParser);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    // Parses AZ into entity
    app.factory('LayoutTargetsParser', ["$q", "x2js", function($q, x2js) {
        function LayoutTargetsParser() {
            this.type = 'signs.parser.layout-targets';
            this.parse = function(scriptBlock) {
                var result = {
                    id: scriptBlock.id,
                    type: scriptBlock.type,
                    layoutTargets: []
                };

                var parsedXml = x2js.xml_str2json(scriptBlock.script);
                if (!parsedXml || !parsedXml.LayoutTargets || !parsedXml.LayoutTargets.LayoutTarget) {
                    return $q.resolve(result);
                }

                var layoutTargets = parsedXml.LayoutTargets.LayoutTarget;
                for (var i = 0; i < layoutTargets.length; i++) {
                    result.layoutTargets.push(parseLayout(layoutTargets[i]));
                }

                return $q.resolve(result);
            };
        }

        function parseLayout(layout) {
            layout.ElementPlacements = layout.ElementPlacements || {ElementPlacement: []};
            var elementPlacements = processElementPlacements(layout.ElementPlacements.ElementPlacement);
            var width = parseInt(layout.TargetWidth, 10);
            var height = parseInt(layout.TargetHeight, 10);
            return {
                aspectRatio: parseFloat(layout.AspectRatio),
                backgroundColor: layout.BackgroundColor,
                elementPlacements: elementPlacements,
                layoutId: parseInt(layout.LayoutID, 10),
                targetWidth: width,
                targetHeight: height,
                targetAspectRatio: width/height,
            };
        }

        function processElementPlacements(elementPlacements) {
            var result = [];
            for (var i = 0; i < elementPlacements.length; i++) {
                var elementPlacement = elementPlacements[i];
                var transitionProperties = processTransitionProperties(elementPlacement.TransitionProperties);
                result.push({
                    color: elementPlacement.Color,
                    elementId: parseInt(elementPlacement.ElementID, 10),
                    height: parseFloat(elementPlacement.Height),
                    opacity: parseFloat(elementPlacement.Opacity),
                    transitionProperties: transitionProperties,
                    width: parseFloat(elementPlacement.Width),
                    x: parseFloat(elementPlacement.X),
                    y: parseFloat(elementPlacement.Y),
                });
            }

            return result;
        }

        function processTransitionProperties(transitionProperties) {
            var keyValuePairs = transitionProperties.KeyValuePair;
            var result = {};
            if (!keyValuePairs) {
                return result;
            }

            if (!(keyValuePairs instanceof Array)) {
                keyValuePairs = [transitionProperties.KeyValuePair];
            }

            for (var i = 0; i < keyValuePairs.length; i++) {
                var keyValuePair = keyValuePairs[i];
                result[keyValuePair.Key] = keyValuePair.Value;
            }

            return result;
        }

        return (LayoutTargetsParser);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');
    // Parses the applications script into multiple script blocks
    app.factory('AppScriptParser', ["$q", "x2js", "md5", function($q, x2js, md5) {
        // Define the constructor function.
        function AppScriptParser() {

        }

        // Define the instance methods and properties
        AppScriptParser.prototype = {
            // Public interface
            type: 'signs.parser.app-script',
            parse: parse
        };

        // IMPORTANT: This version number should be updated whenever this parser is updated
        AppScriptParser.version = '1.0.0';

        // Schedule a deferred parsing of the object
        function parse(o) {
            // promise object for asyn response
            var deferred = $q.defer();
            doParse(o, deferred);
            return deferred.promise;
        }

        // Parse the object
        function doParse(o, promise) {
            if (!o || o.length == 0) {
                promise.reject('Empty script'); // Empty script
            }

            var scriptObjects = [];

            var array = o.split('</FOLDER LISTINGS>');

            for (var i = 0; i < array.length; i++) {
                var so = toScriptObject(array[i], promise);
                if (so) {
                    scriptObjects.push(so);
                }
            }

            promise.resolve(scriptObjects);
        }

        function toScriptObject(s) {
            if (!s || s.length == 0) {
                return null; // Empty script
            }

            var so = {
                id: null,
                type: null,
                hash: null,
                script: null,
                version: AppScriptParser.version,
                content: []
            };

            // get Id and type
            var startId = s.indexOf('<ID>');
            var endId = s.indexOf('</ID>');
            if (startId == -1 || endId == -1) {
                return null;
            }

            var id = s.substring(startId + 4, endId);
            var idArray = id.split('_');
            if (idArray.length < 2) {
                return null;
            }

            so.id = id;
            so.type = idArray[idArray.length - 2];

            // get script
            var startTable = s.indexOf('<TABLE>');
            var endTable = s.indexOf('</TABLE>');
            var startFl = s.indexOf('<FOLDER LISTINGS>');
            if (startTable == -1) {
                so.script = s.substring(endId + 6, startFl);
            } else {
                so.script = s.substring(endId + 6, endTable);
            }

            // create Hash
            if (so.script) {
                so.hash = md5.createHash(so.script + so.version);
            }

            // get FolderListings
            var flScript = s.substring(startFl + 17);
            so.content = parseFolderListings(flScript);

            return so;
        }

        function parseFolderListings(s) {
            var content = [];

            if (s && s.length > 0) {
                // treat xml
                s = s.split('<NUM FILES>').join('<NUMFILES>');
                s = s.split('</NUM FILES>').join('</NUMFILES>');
                s = s.split('<TOTAL SIZE>').join('<TOTALSIZE>');
                s = s.split('</TOTAL SIZE>').join('</TOTALSIZE>');
                // add root xml
                s = '<FOLDERS>' + s + '</FOLDERS>';

                // xml to json
                var o = x2js.xml_str2json(s);

                if (o) {
                    var folders = o.FOLDERS.FOLDER;
                    if (folders) {
                        if (!(folders instanceof Array)) {
                            folders = [o.FOLDERS.FOLDER];
                        }

                        for (var i = 0; i < folders.length; i++) {
                            var f = folders[i];

                            if (f.PATH) {
                                var files = f.FILES.FILE;
                                if (files) {
                                    if (!(files instanceof Array)) {
                                        files = [f.FILES.FILE];
                                    }

                                    for (var j = 0; j < files.length; j++) {
                                        var file = files[j];
                                        var c = {
                                            path: f.PATH,
                                            fileName: file.NAME,
                                            fileSize: parseInt(file.SIZE, 10),
                                            thumbnails: []
                                        };
                                        content.push(c);
                                    }
                                }
                            }
                        }
                    }
                }
            }

            return content;
        }

        // Return the constructor for the class
        return (AppScriptParser);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    // Parses AZ into entity
    app.factory('ApplicationAssetParser', ["$q", "CSVParser", function($q, CSVParser) {
        // Define the constructor function.
        function ApplicationAssetParser() {

        }

        // Define the instance methods and properties
        ApplicationAssetParser.prototype = {
            // Public interface
            type: 'signs.parser.application-asset',
            parse: parse
        };

        // Schedule a deferred parsing of the object
        function parse(o) {
            // promise object for async response
            var deferred = $q.defer();
            doParse(o, deferred);
            return deferred.promise;
        }

        // Actual parsing of object
        function doParse(scriptBlock, promise) {
            // Parser the csv string into an array of table objects - {type: [tableName], items: []}
            var csvParser = new CSVParser();
            // On parse we
            var onCSVParsed = function(o) {
                // Parse the CSV objects
                var entity = parseCsvObject(o);
                // Wrap the response in a standard format
                promise.resolve({id: scriptBlock.id, type: scriptBlock.type, widgets: entity});
            };

            csvParser.parse(scriptBlock.script).then(onCSVParsed, function(err) {
                promise.reject(err);
            });
        }

        // Process the CSV Obect and return a widget collection
        function parseCsvObject(o) {
            var widgets = [];

            var assetKeys = [];
            var assetBindings = [];

            // Split assetKeys and assetBindings into two separate arrays for processing
            for (var i = 0; i < o.length; i++) {
                if (o[i].type == 'assetkeys') {
                    assetKeys = o[i].items;
                } else if (o[i].type == 'assetbindings') {
                    assetBindings = o[i].items;
                }
            }

            var assetLookup = {};
            var widgetLookup = {};

            // Process asset keys
            for (var k = 0; k < assetKeys.length; k++) {
                var ak = assetKeys[k];
                // try to lookup the widget from the dictionary
                var w = widgetLookup[ak.AssetDataSourceId];
                // if not found, create an entry in the widgetLookup dictionary
                if (w === undefined) {
                    w = {
                        id: parseInt(ak.AssetDataSourceId),
                        assets: []
                    };
                    // Add widget to the dictionary
                    widgetLookup[w.id] = w;

                    widgets.push(w);
                }

                // Add asset to the dictionary
                assetLookup[ak.Id] = ak.Name;
            }

            // process asset bindings, which represents the widget assets
            for (var i = 0; i < assetBindings.length; i++) {
                var ab = assetBindings[i];
                // try to lookup the widget from the dictionary
                var w = widgetLookup[ab.AssetDataSourceId];
                if (w) {
                    w.assets.push({
                        name: assetLookup[ab.AssetKeyId],
                        position: parseInt(ab.Position),
                        mediaFile: ab.mediaFile
                    });
                }
            }

            return widgets;
        }

        // Return the constructor for the class
        return (ApplicationAssetParser);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    // Parses AZ into entity
    app.factory('ApplicationLinkingParser', ["$q", "CSVParser", function($q, CSVParser) {
        // Define the constructor function.
        function ApplicationLinkingParser() {

        }

        // Define the instance methods and properties
        ApplicationLinkingParser.prototype = {
            // Public interface
            type: 'signs.parser.application-linking',
            parse: parse
        };

        // Schedule a deferred parsing of the object
        function parse(o) {
            // promise object for async response
            var deferred = $q.defer();
            doParse(o, deferred);
            return deferred.promise;
        }

        // Actual parsing of object
        function doParse(scriptBlock, promise) {
            // Parser the csv string into an array of table objects - {type: [tableName], items: []}
            var csvParser = new CSVParser();
            // On parse we
            var onCSVParsed = function(o) {
                // Parse the CSV objects
                var entity = parseCsvObject(o);
                // Wrap the response in a standard format
                promise.resolve({id: scriptBlock.id, type: scriptBlock.type, applicationLinks: entity});
            };

            csvParser.parse(scriptBlock.script).then(onCSVParsed, function(err) {
                promise.reject(err);
            });
        }

        // Process the CSV Obect and return application links array
        function parseCsvObject(o) {
            var items = [];

            // Get the application links source
            var source = [];
            for (var i = 0; i < o.length; i++) {
                if (o[i].type == 'links') {
                    source = o[i].items;
                }
            }

            // Process application link items
            for (var k = 0; k < source.length; k++) {
                var s = source[k];

                // Create applicationLink object
                var item = {
                    linkedNetworkId: parseInt(s.LinkedNetworkID),
                    networkName: s.NetworkName,
                    linkDistance: parseInt(s.LinkDistance)
                };

                items.push(item);
            }

            return items;
        }

        // Return the constructor for the class
        return (ApplicationLinkingParser);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    app.factory('ApplicationPropertiesParser', ["dataUtil", "$q", function(dataUtil, $q) {
        // Define the constructor function.
        function ApplicationPropertiesParser() {

        }

        // Define the instance methods and properties
        ApplicationPropertiesParser.prototype = {
            // Public interface
            type: 'signs.parser.application-properties',
            parse: parse
        };

        // Schedule a deferred parsing of the object
        function parse(o) {
            // promise object for asyn response
            var deferred = $q.defer();
            doParse(o, deferred);
            return deferred.promise;
        }

        // Actual parsing of object
        function doParse(scriptBlock, promise) {
            var props = {};

            var script = scriptBlock.script;
            if (!script || !script.length) {
                return null; // Empty
            }

            // split each property into an array
            var splits = script.split(/\r\n|\r|\n/g);
            // iterate through each property and get the key and value from the key=value format
            for (var i = 0; i < splits.length; i++) {
                var split = splits[i];
                if (!split.length) {
                    continue;
                }
                // remove the semicolon at the end of the value
                if (split.charAt(split.length - 1) == ';') {
                    split = split.substring(0, split.length - 1);
                }
                var equalIndex = split.indexOf('=');
                if (!equalIndex || equalIndex == -1) {
                    continue;
                }
                var key = split.substring(0, equalIndex);
                props[key.toLowerCase()] = dataUtil.decodeHtml(split.substring(equalIndex + 1));
            }
            // wrap the response in a standard format
            promise.resolve({id: scriptBlock.id, type: scriptBlock.type, properties: props});
        }

        // Return the constructor for the class
        return (ApplicationPropertiesParser);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    // Parses AZ into entity
    app.factory('ApplicationXMLParser', ["$q", "x2js", "Utils", function($q, x2js, Utils) {
        // Instantiate the utils class
        var utils = new Utils();

        // Define the constructor function.
        function ApplicationXMLParser() {

        }

        // Define the instance methods and properties
        ApplicationXMLParser.prototype = {
            // Public interface
            type: 'signs.parser.application-xml',
            parse: parse
        };

        // Schedule a deferred parsing of the object
        function parse(o) {
            // promise object for asyn response
            var deferred = $q.defer();
            doParse(o, deferred);
            return deferred.promise;
        }

        // Actual parsing of object
        function doParse(scriptBlock, promise) {
            // Parse the compliant xml string
            var res = parseXml(scriptBlock.script, scriptBlock.id);
            // Wrap the response in a standard format
            promise.resolve({id: scriptBlock.id, type: scriptBlock.type, application: res});
        }

        // Parse XML and return an entity
        function parseXml(xmlText, id) {
            if (!xmlText || xmlText.length == 0) {
                return null; // Empty
            }

            // Check that its a valid application xml
            var startXml = xmlText.indexOf('<Application ');
            if (startXml == -1) {
                return null;
            }

            // Get xml string starting from the <Application> tag
            xmlText = xmlText.substring(startXml);

            // Convert xml to a json object so we can parse
            var o = x2js.xml_str2json(xmlText);

            // Check for valid application xml and id
            var appNode = o.Application;
            if (!appNode || !appNode.ApplicationID) {
                return null;
            }

            // Create an app object
            var app = {
                applicationId: parseInt(appNode.ApplicationID),
                name: appNode.Name,
                pixelWidth: parseInt(appNode.PixelWidth),
                pixelHeight: parseInt(appNode.PixelHeight),
                type: appNode.Type,
                properties: {},
                elements: [],
                layouts: [],
                layoutSchedules: [],
                triggers: []
            };

            if (appNode.Properties) {
                var props = appNode.Properties.KeyValuePair;
                if (props) {
                    // Convert element into an array if only one item
                    if (!(props instanceof Array)) {
                        props = [appNode.Properties.KeyValuePair];
                    }
                    // Iterate through the app properties keyvaluepair array and convert to dictionary
                    for (var i = 0; i < props.length; i++) {
                        var p = props[i];
                        app.properties[p.Key] = p.Value;
                    }
                }
            }

            if (appNode.Elements) {
                // Convert element into an array if only one item
                var elements = appNode.Elements.Element;
                if (elements) {
                    if (!(elements instanceof Array)) {
                        elements = [appNode.Elements.Element];
                    }
                    // Iterate through the app elements and parse into an element object and add to the array of elements
                    for (var i = 0; i < elements.length; i++) {
                        var e = parseElement(elements[i]);
                        if (e) {
                            app.elements.push(e);
                        }
                    }
                }
            }

            if (appNode.Layouts) {
                var layouts = appNode.Layouts.Layout;
                if (layouts) {
                    // Convert element into an array if only one item
                    if (!(layouts instanceof Array)) {
                        layouts = [appNode.Layouts.Layout];
                    }
                    // Iterate through the app layouts and parse into a layout object and add to the array of layouts
                    for (var i = 0; i < layouts.length; i++) {
                        var l = parseLayout(layouts[i]);
                        if (l) {
                            app.layouts.push(l);
                        }
                    }
                }
            }

            if (appNode.LayoutSchedules) {
                var layoutschedules = appNode.LayoutSchedules.LayoutSchedule;
                if (layoutschedules) {
                    // Convert element into an array if only one item
                    if (!(layoutschedules instanceof Array)) {
                        layoutschedules = [appNode.LayoutSchedules.LayoutSchedule];
                    }
                    // Iterate through the app layout schedules and parse into a layout schedule object and add to the array of layout schedules
                    for (var i = 0; i < layoutschedules.length; i++) {
                        var ls = parseLayoutSchedule(layoutschedules[i]);
                        if (ls) {
                            app.layoutSchedules.push(ls);
                        }
                    }
                }
            }

            if (appNode.Triggers) {
                var triggers = appNode.Triggers.Trigger;
                if (triggers) {
                    // Convert element into an array if only one item
                    if (!(triggers instanceof Array)) {
                        triggers = [appNode.Triggers.Trigger];
                    }
                    // Iterate through the triggers and parse into a trigger object and add to the array of triggers
                    for (var i = 0; i < triggers.length; i++) {
                        var t = parseTrigger(triggers[i]);
                        if (t) {
                            app.triggers.push(t);
                        }
                    }
                }
            }

            return app;
        }

        function parseElement(e) {
            var item = {
                elementId: parseInt(e.ElementID),
                name: e.Name,
                contentSourceId: parseInt(e.ContentSourceID),
                elementType: e.ElementType,
                assetSourceId: parseInt(e.AssetSourceID),
                properties: {}
            };

            if (e.Properties) {
                var props = e.Properties.KeyValuePair;
                if (props) {
                    if (!(props instanceof Array)) {
                        props = [e.Properties.KeyValuePair];
                    }
                    for (var i = 0; i < props.length; i++) {
                        var p = props[i];
                        item.properties[p.Key] = p.Value;
                    }
                }
            }

            if (!item.elementId) {
                return null; // incomplete element
            } else {
                return item;
            }
        }

        function parseLayout(e) {
            var item = {
                layoutId: parseInt(e.LayoutID),
                name: e.Name,
                aspectRatio: parseFloat(e.AspectRatio),
                backgroundColor: e.BackgroundColor,
                elementPlacements: []
            };

            if (e.ElementPlacements) {
                var elementPlacements = e.ElementPlacements.ElementPlacement;
                if (elementPlacements) {
                    if (!(elementPlacements instanceof Array)) {
                        elementPlacements = [e.ElementPlacements.ElementPlacement];
                    }
                    for (var i = 0; i < elementPlacements.length; i++) {
                        var p = parseElementPlacement(elementPlacements[i]);
                        if (p) {
                            item.elementPlacements.push(p);
                        }
                    }
                }
            }

            if (!item.layoutId) {
                return null; // incomplete layout
            } else {
                return item;
            }
        }

        function parseElementPlacement(e) {
            var item = {
                elementId: parseInt(e.ElementID),
                x: parseFloat(e.X),
                y: parseFloat(e.Y),
                width: parseFloat(e.Width),
                height: parseFloat(e.Height),
                opacity: parseFloat(e.Opacity),
                color: e.Color,
                transitionProperties: {}
            };

            if (e.TransitionProperties) {
                var tranProps = e.TransitionProperties.KeyValuePair;
                if (tranProps) {
                    if (!(tranProps instanceof Array)) {
                        tranProps = [e.TransitionProperties.KeyValuePair];
                    }
                    for (var i = 0; i < tranProps.length; i++) {
                        var p = tranProps[i];
                        item.transitionProperties[p.Key] = p.Value;
                    }
                }
            }

            if (!item.elementId) {
                return null; // incomplete elementplacement
            } else {
                return item;
            }
        }

        function parseLayoutSchedule(e) {
            var item = {
                layoutScheduleId: parseInt(e.LayoutScheduleID),
                name: e.Name,
                position: parseInt(e.Position),
                disable: e.Disable == 'true',
                tagExpression: e.TagExpression,
                layoutScheduleItems: [],
                playoutSchedule: {}
            };

            if (e.LayoutScheduleItems) {
                var lsItems = e.LayoutScheduleItems.LayoutScheduleItem;
                if (lsItems) {
                    if (!(lsItems instanceof Array)) {
                        lsItems = [e.LayoutScheduleItems.LayoutScheduleItem];
                    }
                    for (var i = 0; i < lsItems.length; i++) {
                        var p = lsItems[i];
                        var duration = parseFloat(p.Duration);
                        if (duration > 2147483647) {
                            duration = 2147483647 - 1;
                        }
                        item.layoutScheduleItems.push({
                            layoutScheduleItemId: parseInt(p.LayoutScheduleItemID),
                            layoutId: parseInt(p.LayoutID),
                            duration: duration,
                            disable: p.Disable == 'true'
                        });
                    }
                }
            }

            if (e.PlayoutSchedule) {
                var p = e.PlayoutSchedule;
                item.playoutSchedule = {
                    scheduleMode: p.ScheduleMode,
                    isExclusive: p.IsExclusive == 'true',
                    startDate: p.StartDate,
                    endDate: p.EndDate,
                    noEndDate: p.NoEndDate == 'true',
                    useRecurrencePattern: p.UseRecurrencePattern == 'true',
                    recurrencePattern: []
                };

                // if UseRecurrencePattern is true, parse the recurrence pattern
                if (item.playoutSchedule.useRecurrencePattern) {
                    if (p.RecurrencePatterns) {
                        var rps = p.RecurrencePatterns.PlayoutScheduleRecurrencePattern;
                        if (rps) {
                            // if not an array, turn it into an array of one item
                            if (!(rps instanceof Array)) {
                                rps = [p.RecurrencePatterns.PlayoutScheduleRecurrencePattern];
                            }

                            for (var i = 0; i < rps.length; i++) {
                                var rp = rps[i];
                                var rpObj = {
                                    allDayEvent: rp.AllDayEvent == 'true',
                                    endTime: rp.EndTime,
                                    playOnAllDays: rp.PlayOnAllDays == 'true',
                                    playOnFri: rp.PlayOnFri == 'true',
                                    playOnMon: rp.PlayOnMon == 'true',
                                    playOnSat: rp.PlayOnSat == 'true',
                                    playOnSun: rp.PlayOnSun == 'true',
                                    playOnThu: rp.PlayOnThu == 'true',
                                    playOnTue: rp.PlayOnTue == 'true',
                                    playOnWed: rp.PlayOnWed == 'true',
                                    startTime: rp.StartTime
                                };
                                // push into the recurrencePattern array
                                item.playoutSchedule.recurrencePattern.push(rpObj);
                            }
                        }
                    }
                }
            }

            if (!item.layoutScheduleId) {
                return null; // incomplete layoutSchedule
            } else {
                return item;
            }
        }

        function parseTrigger(e) {
            var item = {
                routedEvent: e._RoutedEvent,
                sourceName: e._SourceName,
                scope: e._Scope,
                actions: [],
                conditions: {}
            };

            if (e.Actions) {
                var actions = e.Actions.Action;
                if (actions) {
                    // if not an array, turn it into an array of one item
                    if (!(actions instanceof Array)) {
                        actions = [e.Actions.Action];
                    }
                    for (var i = 0; i < actions.length; i++) {
                        var a = actions[i];
                        var action = {};
                        for (var k in a) {
                            action[utils.toCamelCase(k)] = a[k];
                        }
                        item.actions.push(action);
                    }
                }
            }

            if (e.Conditions) {
                item.conditions = {
                    operator: e.Conditions._Operator,
                    conditions: []
                };

                var conditions = e.Conditions.Condition;
                if (conditions) {
                    // if not an array, turn it into an array of one item
                    if (!(conditions instanceof Array)) {
                        conditions = [e.Conditions.Condition];
                    }
                    for (var i = 0; i < conditions.length; i++) {
                        var conditionEl = conditions[i];

                        var conditionItem = {
                            operator: conditionEl._Operator,
                            expressions: []
                        };
                        // parse expressions
                        var expressions = conditionEl.Expression;
                        if (expressions) {
                            // if not an array, turn it into an array of one item
                            if (!(expressions instanceof Array)) {
                                expressions = [conditionEl.Expression];
                            }

                            // add expression to the array
                            for (var k = 0; k < expressions.length; k++) {
                                var expressionEl = expressions[k];
                                conditionItem.expressions.push({
                                    operation: expressionEl._Operation,
                                    arg1: expressionEl._Arg1,
                                    arg2: expressionEl._Arg2
                                });
                            }
                        }
                        // add condition to the array
                        item.conditions.conditions.push(conditionItem);
                    }
                }
            }

            if (!item.sourceName) {
                return null; // incomplete trigger
            } else {
                return item;
            }
        }

        // Return the constructor for the class
        return (ApplicationXMLParser);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    // Parses AZ into entity
    app.factory('AZParser', ["$q", "x2js", "Utils", function($q, x2js, Utils) {
        // Instantiate the utils class
        var utils = new Utils();

        // Define the constructor function.
        function AZParser() {

        }

        // Define the instance methods and properties
        AZParser.prototype = {
            // Public interface
            type: 'signs.parser.az',
            parse: parse
        };

        // Schedule a deferred parsing of the object
        function parse(o) {
            // promise object for asyn response
            var deferred = $q.defer();
            doParse(o, deferred);
            return deferred.promise;
        }

        // Actual parsing of object
        function doParse(scriptBlock, promise) {
            // Fix non compliant xml tags (e.g. <element data>)
            var xml = treatXml(scriptBlock.script);
            // Parse the compliant xml string
            var res = parseXml(xml, scriptBlock.id);
            // Wrap the response in a standard format
            promise.resolve({id: scriptBlock.id, type: scriptBlock.type, mediaZone: res});
        }

        // Fix non compliant xml tags (e.g. <element data>)
        function treatXml(source) {
            var xml = source.split('<ELEMENT DATA>').join('<ELEMENTDATA>');
            xml = xml.split('</ELEMENT DATA>').join('</ELEMENTDATA>');
            xml = '<ELEMENTS>' + xml + '</ELEMENTS>';
            xml = xml.split('&').join('&amp;');
            xml = xml.split('%').join('&#37;');
            return xml;
        }

        // Parse XML and return an entity
        function parseXml(xmlText, id) {
            var mediaZone = {
                id: id,
                name: null,
                playlist: {
                    items: []
                }
            };

            // Check its valid
            if (!xmlText || xmlText.length == 0) {
                return mediaZone; // Empty playlist
            }

            // Convert xml to a json object so we can parse]
            var o = x2js.xml_str2json(xmlText);

            var elements = o.ELEMENTS.ELEMENT;
            if (elements) {
                // Convert element into an array if only one item
                if (!(elements instanceof Array)) {
                    elements = [o.ELEMENTS.ELEMENT];
                }

                // itterate through the elements which represent playlist items
                for (var i = 0; i < elements.length; i++) {
                    var e = elements[i];
                    var item = parseElement(e, i);
                    if (item) {
                        // Add item to playlist
                        mediaZone.playlist.items.push(item);
                    }
                }
            }

            return mediaZone;
        }

        function parseElement(e, position) {
            if (!e) {
                return null;
            }

            // Reference playlist item object
            var item = {
                position: position,
                type: null,
                title: null,
                description: null,
                id: null,
                contentId: null,
                configuration: null,
                mediaFile: null,
                playoutProperties: {
                    duration: null,
                    aspectRatio: null,
                    transition: null,
                    playoutEffect: null,
                    custom: null,
                    playoutTagsRule: null,
                    frequencyCondition: null,
                    frequencyPlayout: null,
                    frequencyUnit: null,
                    timespans: null
                },
                schedule: {
                    startDate: null,
                    endDate: null,
                    playForever: null
                }
            };

            // Iterate through the element data to extract the properties of the media item
            for (var j = 0; j < e.ELEMENTDATA.length; j++) {
                var ed = e.ELEMENTDATA[j];
                // console.log(ed.TYPE + ': ' + ed.DATA);
                switch (ed.TYPE) {
                    case 'Media Type':
                        item.type = ed.DATA;
                        break;
                    case 'Media':
                        item.mediaFile = ed.DATA;
                        break;
                    case 'UGId':
                        break;
                    case 'Title':
                        item.title = ed.DATA;
                        break;
                    case 'Duration':
                        item.playoutProperties.duration = parseFloat(ed.DATA);
                        break;
                    case 'Playout':
                        item.playoutProperties.duration = parseFloat(ed.DATA);
                        break;
                    case 'StartDate':
                        item.schedule.startDate = ed.DATA;
                        break;
                    case 'EndDate':
                        item.schedule.endDate = ed.DATA;
                        break;
                    case 'PlayForever':
                        item.schedule.playForever = ed.DATA == 'True' ? true : false;
                        break;
                    case 'MediaZoneID':
                        item.mediaZoneId = ed.DATA;
                        break;
                    case 'MediaID':
                        item.id = parseInt(ed.DATA);
                        break;
                    case 'AspectRatio':
                        item.playoutProperties.aspectRatio = utils.toAspectRatio(ed.DATA, 'content');
                        break;
                    case 'Transition':
                        item.playoutProperties.transition = utils.toTransition(ed.DATA);
                        break;
                    case 'PlayoutEffect':
                        item.playoutProperties.playoutEffect = ed.DATA;
                        break;
                    case 'Custom':
                        item.playoutProperties.custom = ed.DATA;
                        break;
                    case 'PlayoutTagsRule':
                        item.playoutProperties.playoutTagsRule = ed.DATA;
                        break;
                    case 'Description':
                        item.description = ed.DATA;
                        break;
                    case 'FrequencyCondition':
                        item.playoutProperties.frequencyCondition = ed.DATA ? ed.DATA.toLowerCase() : undefined; // eslint-disable-line max-len
                        break;
                    case 'FrequencyUnit':
                        item.playoutProperties.frequencyUnit = ed.DATA ? ed.DATA.toLowerCase() : undefined; // eslint-disable-line max-len
                        break;
                    case 'FrequencyPlayout':
                        item.playoutProperties.frequencyPlayout = parseFloat(ed.DATA);
                        break;
                    case 'FixedVideoDuration':
                        item.playoutProperties.fixedVideoDuration = !isNaN(ed.DATA) ? parseFloat(ed.DATA) / 1000 : undefined; // eslint-disable-line max-len
                        break;
                    case 'NaturalDuration':
                        item.playoutProperties.hasNaturalDuration = utils.hasNaturalDuration(ed.DATA);
                        break;
                    case 'ContentId':
                        item.contentId = ed.DATA;
                        break;
                    default:
                        // console.log(ed.TYPE);
                        break;
                }
            }

            item.uniqueId = item.mediaZoneId + '-' + item.id + '-' + position;

            // Sanity check that its a valid item
            if (item.mediaType === null || item.mediaFile === null) {
                return null; // incomplete element
            } else {
                return item;
            }
        }

        // Return the constructor for the class
        return (AZParser);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    // Parses AZ into entity
    app.factory('CalendarParser', ["$q", "CSVParser", function($q, CSVParser) {
        // Define the constructor function.
        function CalendarParser() {

        }

        // Define the instance methods and properties
        CalendarParser.prototype = {
            // Public interface
            type: 'signs.parser.calendar',
            parse: parse
        };

        // Schedule a deferred parsing of the object
        function parse(o) {
            // promise object for async response
            var deferred = $q.defer();
            doParse(o, deferred);
            return deferred.promise;
        }

        // Actual parsing of object
        function doParse(scriptBlock, promise) {
            // Parser the csv string into an array of table objects - {type: [tableName], items: []}
            var csvParser = new CSVParser();
            // On parse we
            var onCSVParsed = function(o) {
                // Parse the CSV objects
                var entity = parseCsvObject(o);
                // Wrap the response in a standard format
                promise.resolve({id: scriptBlock.id, type: scriptBlock.type, events: entity});
            };

            csvParser.parse(scriptBlock.script).then(onCSVParsed, function(err) {
                promise.reject(err);
            });
        }

        // Local variables
        var _calendar = [];
        var _calAssets = [];
        var _calFields = [];
        var _calFieldData = [];

        // Process the CSV Obect returned into a meetingRoom collection
        function parseCsvObject(o) {
            var meetingRooms = [];

            // iterate through the table array and extract the tables
            for (var i = 0; i < o.length; i++) {
                switch (o[i].type) {
                    case 'calendar':
                        _calendar = o[i].items;
                        break;
                    case 'calasset':
                        _calAssets = o[i].items;
                        break;
                    case 'calendarfield':
                        _calFields = o[i].items;
                        break;
                    case 'calendarfielddata':
                        _calFieldData = o[i].items;
                        break;
                }
            }

            // create addtional fields lookup for room
            var roomFieldLookup = createRoomFieldLookup();

            var roomLookup = {};
            // iterate through the calendar object which represent the meetingroom
            for (var i = 0; i < _calendar.length; i++) {
                var c = _calendar[i];

                // try to lookup the meetingroom from the dictionary
                var room = roomLookup[c._compId];
                // if not found, create an entry in the roomLookup dictionary
                if (room === undefined) {
                    room = {
                        id: parseInt(c._compId),
                        events: []
                    };
                    roomLookup[room.id] = room;
                    // Add room to the dictionary
                    meetingRooms.push(room);
                }

                // Create event object
                var event = {
                    id: parseInt(c._id),
                    title: c.title,
                    startDate: c.date,
                    position: parseInt(c.position),
                    reminder: c.reminder,
                    minutes: parseInt(c.minutes),
                    endDate: c.enddate,
                    location: c.location,
                    description: c.description,
                    assets: getCalAssets(c._id),
                    additionalFields: getAdditionalFields(c._id, roomFieldLookup[room.id])
                };

                // Add event object into the meeting room event array
                room.events.push(event);
            }
            var res = [];
            if (meetingRooms.length > 0) {
                res = meetingRooms[0].events;
            }
            return res;
        }

        // get assets for the event
        function getCalAssets(calId) {
            var assets = [];
            // Iterate through the assets and find ones for the event
            for (var i = 0; i < _calAssets.length; i++) {
                var a = _calAssets[i];
                // find the asset from the _calAsset table and create an asset object
                if (a._calId === calId) {
                    var o = {
                        id: parseInt(a._id),
                        position: parseInt(a.position),
                        compAllocId: parseInt(a._compAllocId),
                        type: a.Type,
                        mediaFile: a.mediaFile
                    };
                    // insert the asset object into an array
                    assets.push(o);
                }
            }

            // return the asset array
            return assets;
        }

        // Create additional fields lookup for the meeting room
        function createRoomFieldLookup() {
            var roomFieldLookup = {};

            // iterate through the calendarfields object
            for (var i = 0; i < _calFields.length; i++) {
                var c = _calFields[i];

                // try to lookup the field from the dictionary
                var room = roomFieldLookup[c._compId];
                // if not found, create an entry in the roomFieldLookup dictionary
                if (room === undefined) {
                    room = {
                        roomId: parseInt(c._compId),
                        fields: []
                    };
                    // add to the dictionary
                    roomFieldLookup[room.roomId] = room;
                }

                // create field object
                var field = {};
                field[c._id] = c.fieldName;
                // Add field to the room
                // room.fields.push(field);
                room.fields[c._id] = c.fieldName;
            }

            return roomFieldLookup;
        }


        // Create additional fields lookup for the meeting room
        function getAdditionalFields(calId, roomFields) {
            if (roomFields == undefined) {
                return null;
            }

            var fieldDict = roomFields.fields;

            var additionalFields = {};

            // create additionalField object using the fieldDictionary
            for (var f in fieldDict) {
                additionalFields[fieldDict[f]] = null;
            }

            // iterate through the calendarfielddata object and find ones for the event
            for (var i = 0; i < _calFieldData.length; i++) {
                var c = _calFieldData[i];
                if (c._calendarId == calId) {
                    // check the _calendarFieldId and set the data for that field
                    additionalFields[fieldDict[c._calendarFieldId]] = c.fielddata;
                }
            }

            return additionalFields;
        }

        // Return the constructor for the class
        return (CalendarParser);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    // Parses AZ into entity
    app.factory('CampaignXMLParser', ["$q", "x2js", "Utils", function($q, x2js, Utils) {
        // Instantiate the utils class
        var utils = new Utils();

        // Define the constructor function.
        function CampaignXMLParser() {

        }

        // Define the instance methods and properties
        CampaignXMLParser.prototype = {
            // Public interface
            type: 'signs.parser.campaign-xml',
            parse: parse
        };

        // Schedule a deferred parsing of the object
        function parse(o) {
            // promise object for asyn response
            var deferred = $q.defer();
            doParse(o, deferred);
            return deferred.promise;
        }

        // Actual parsing of object
        function doParse(xmlText, promise) {
            // Parse the xml string
            var res = parseXml(xmlText);
            // Wrap the response in a standard format
            promise.resolve({campaign: res});
        }

        // Parse XML and return an entity
        function parseXml(xmlText) {
            if (!xmlText || xmlText.length == 0) {
                return null; // Empty
            }

            // Convert xml to a json object so we can parse
            var o = x2js.xml_str2json(xmlText);
            // Check for valid workbook xml
            if (o == null || o.CampaignXMLScript == null) {
                return null;
            }

            var campaign = {
                campaign: null,
                assets: [],
                bookingLoops: [],
                bookings: [],
                schedules: [],
                sites: [],
                templates: [],
                zones: []
            };

            var xmlObj = o.CampaignXMLScript;
            for (var prop in xmlObj) {
                var lc = prop.toLowerCase();
                if (xmlObj[prop] == '') {
                    continue;
                }

                if (lc.indexOf('assets') != -1) {
                    campaign.assets = parseAssets(xmlObj[prop]);
                } else if (lc.indexOf('bookingloops') != -1) {
                    campaign.bookingLoops = parseBookingLoops(xmlObj[prop]);
                } else if (lc.indexOf('bookings') != -1) {
                    campaign.bookings = parseBookings(xmlObj[prop]);
                } else if (lc.indexOf('campaign') != -1) {
                    campaign.campaign = parseCampaign(xmlObj[prop]);
                } else if (lc.indexOf('schedules') != -1) {
                    campaign.schedules = parseSchedules(xmlObj[prop]);
                } else if (lc.indexOf('sites') != -1) {
                    campaign.sites = parseSites(xmlObj[prop]);
                } else if (lc.indexOf('templates') != -1) {
                    campaign.templates = parseTemplates(xmlObj[prop]);
                } else if (lc.indexOf('zones') != -1) {
                    campaign.zones = parseZones(xmlObj[prop]);
                }
            }
            return campaign;
        }

        // Parse Assets
        function parseAssets(sourceObj) {
            var res = [];

            if (sourceObj) {
                var assets = sourceObj.Asset;
                if (assets) {
                    // Convert asset into an array if only one item
                    if (!(assets instanceof Array)) {
                        assets = [assets];
                    }

                    // parse each asset object
                    for (var i = 0; i < assets.length; i++) {
                        var a = assets[i];
                        if (a) {
                            var o = {};
                            o.entityTypeId = null;
                            for (var prop in a) {
                                switch (prop.toLowerCase()) {
                                    case '__prefix':
                                    case '__proto__':
                                    case '_xmlns:d3p1':
                                        break;
                                    case '_d3p1:type':
                                        o.type = a[prop];
                                        break;
                                    case 'metadata':
                                        o.metadata = parseMetadata(a[prop].KeyValuePair);
                                        break;
                                    default:
                                        o[utils.toCamelCase(prop)] = utils.convertStringVal(a[prop]);
                                        break;
                                }
                            }
                            if (!o.order) {
                                o.order = 0;
                            }
                            res.push(o);
                        }
                    }
                }
            }
            return res;
        }

        // Parse BookingLoops
        function parseBookingLoops(sourceObj) {
            var res = [];

            if (sourceObj) {
                var bookingLoops = sourceObj.BookingLoop;
                if (bookingLoops) {
                    // Convert bookingloops into an array if only one item
                    if (!(bookingLoops instanceof Array)) {
                        bookingLoops = [bookingLoops];
                    }

                    // parse each bookingloop object
                    for (var i = 0; i < bookingLoops.length; i++) {
                        var a = bookingLoops[i];
                        if (a) {
                            var o = {};
                            for (var prop in a) {
                                switch (prop.toLowerCase()) {
                                    case '__prefix':
                                    case '__proto__':
                                        break;
                                    case 'assetids':
                                        o.assetIds = [];
                                        if (a[prop]) {
                                            var assetIds = a[prop].int;
                                            if (assetIds) {
                                                // Convert bookingAssetIds into an array if only one item
                                                if (!(assetIds instanceof Array)) {
                                                    assetIds = [assetIds];
                                                }
                                                // parse each assetId object
                                                for (var k = 0; k < assetIds.length; k++) {
                                                    var id = assetIds[k];
                                                    o.assetIds.push(utils.convertStringVal(id));
                                                }
                                            }
                                        }
                                        break;
                                    default:
                                        var val = utils.convertStringVal(a[prop]);
                                        o[utils.toCamelCase(prop)] = val;
                                        break;
                                }
                            }
                            res.push(o);
                        }
                    }
                }
            }
            return res;
        }

        // Parse Bookings
        function parseBookings(sourceObj) {
            var res = [];

            if (sourceObj) {
                var bookings = sourceObj.Booking;
                if (bookings) {
                    // Convert bookings into an array if only one item
                    if (!(bookings instanceof Array)) {
                        bookings = [bookings];
                    }

                    // parse each booking object
                    for (var i = 0; i < bookings.length; i++) {
                        var b = bookings[i];
                        if (b) {
                            var o = {};
                            for (var prop in b) {
                                switch (prop.toLowerCase()) {
                                    case '__prefix':
                                    case '__proto__':
                                        break;
                                    case 'bookingloopids':
                                        o.bookingLoopIds = [];
                                        if (b[prop]) {
                                            var bookingLoopIds = b[prop].int;
                                            if (bookingLoopIds) {
                                                // Convert bookingLoopIds into an array if only one item
                                                if (!(bookingLoopIds instanceof Array)) {
                                                    bookingLoopIds = [bookingLoopIds];
                                                }

                                                // parse each bookingLoopId object
                                                for (var k = 0; k < bookingLoopIds.length; k++) {
                                                    var id = bookingLoopIds[k];
                                                    o.bookingLoopIds.push(utils.convertStringVal(id));
                                                }
                                            }
                                        }
                                        break;
                                    default:
                                        var val = utils.convertStringVal(b[prop]);
                                        o[utils.toCamelCase(prop)] = val;
                                        break;
                                }
                            }
                            res.push(o);
                        }
                    }
                }
            }
            return res;
        }

        // Parse Campaign
        function parseCampaign(sourceObj) {
            var res = null;

            if (sourceObj) {
                res = {};
                for (var prop in sourceObj) {
                    switch (prop.toLowerCase()) {
                        case '__prefix':
                        case '__proto__':
                        case '_xmlns:d2p1':
                            break;
                        case 'bookingids':
                            res.bookingIds = [];
                            if (sourceObj[prop]) {
                                var ids = sourceObj[prop].int;
                                if (ids) {
                                    // Convert bookingIds into an array if only one item
                                    if (!(ids instanceof Array)) {
                                        ids = [ids];
                                    }

                                    // parse each bookingId object
                                    for (var k = 0; k < ids.length; k++) {
                                        var id = ids[k];
                                        res.bookingIds.push(utils.convertStringVal(id));
                                    }
                                }
                            }
                            break;
                        case 'siteids':
                            res.siteIds = [];
                            if (sourceObj[prop]) {
                                var ids = sourceObj[prop].int;
                                if (ids) {
                                    // Convert bookingIds into an array if only one item
                                    if (!(ids instanceof Array)) {
                                        ids = [ids];
                                    }

                                    // parse each bookingId object
                                    for (var k = 0; k < ids.length; k++) {
                                        var id = ids[k];
                                        res.siteIds.push(utils.convertStringVal(id));
                                    }
                                }
                            }
                            break;
                        case 'zoneids':
                            res.zoneIds = [];
                            if (sourceObj[prop]) {
                                var ids = sourceObj[prop].int;
                                if (ids) {
                                    // Convert bookingIds into an array if only one item
                                    if (!(ids instanceof Array)) {
                                        ids = [ids];
                                    }

                                    // parse each bookingId object
                                    for (var k = 0; k < ids.length; k++) {
                                        var id = ids[k];
                                        res.zoneIds.push(utils.convertStringVal(id));
                                    }
                                }
                            }
                            break;
                        case 'metadata':
                            res.metadata = parseMetadata(sourceObj[prop].KeyValuePair);
                            break;
                        default:
                            var val = utils.convertStringVal(sourceObj[prop]);
                            res[utils.toCamelCase(prop)] = val;
                            break;
                    }
                }
            }
            return res;
        }

        // Parse Schedules
        function parseSchedules(sourceObj) {
            var res = [];

            if (sourceObj) {
                var scheds = sourceObj.Schedule;
                if (scheds) {
                    // Convert bookings into an array if only one item
                    if (!(scheds instanceof Array)) {
                        scheds = [scheds];
                    }

                    // parse each booking object
                    for (var i = 0; i < scheds.length; i++) {
                        var b = scheds[i];
                        if (b) {
                            var o = {};
                            for (var prop in b) {
                                switch (prop.toLowerCase()) {
                                    case '__prefix':
                                    case '__proto__':
                                        break;
                                    default:
                                        var val = utils.convertStringVal(b[prop]);
                                        o[utils.toCamelCase(prop)] = val;
                                        break;
                                }
                            }
                            res.push(o);
                        }
                    }
                }
            }
            return res;
        }

        // Parse Sites
        function parseSites(sourceObj, level) {
            var res = [];
            if (!level) {
                level = 1;
            }
            if (sourceObj) {
                var sites = sourceObj.Site;
                if (sites) {
                    // Convert sites into an array if only one item
                    if (!(sites instanceof Array)) {
                        sites = [sites];
                    }

                    // parse each site object
                    for (var i = 0; i < sites.length; i++) {
                        var a = sites[i];
                        if (a) {
                            var o = {};
                            o.level = level;
                            for (var prop in a) {
                                switch (prop.toLowerCase()) {
                                    case '__prefix':
                                    case '__proto__':
                                        break;
                                    case 'metadata':
                                        o.metadata = parseMetadata(a[prop].KeyValuePair);
                                        break;
                                    case 'filters':
                                        o[utils.toCamelCase(prop)] = parseSiteFilters(a[prop]);
                                        break;
                                    case 'sites':
                                        var arr = parseSites(a[prop], level + 1);
                                        if (!o.sites) {
                                            o.sites = [];
                                        }
                                        o.sites = o.sites.concat(arr);
                                        break;
                                    default:
                                        o[utils.toCamelCase(prop)] = utils.convertStringVal(a[prop]);
                                        break;
                                }
                            }
                            res.push(o);
                        }
                    }
                }
            }
            return res;
        }

        // Parse SiteFilters
        function parseSiteFilters(sourceObj) {
            var res = [];

            if (sourceObj) {
                var filters = sourceObj.Filter;
                if (filters) {
                    // Convert sites into an array if only one item
                    if (!(filters instanceof Array)) {
                        filters = [filters];
                    }

                    // parse each site object
                    for (var i = 0; i < filters.length; i++) {
                        var a = filters[i];
                        if (a) {
                            var o = {};
                            for (var prop in a) {
                                switch (prop.toLowerCase()) {
                                    case '__prefix':
                                    case '__proto__':
                                        break;
                                    case '_i:type':
                                        o.type = a[prop];
                                        break;
                                    case 'metadata':
                                        o.metadata = parseMetadata(a[prop].KeyValuePair);
                                        break;
                                    default:
                                        o[utils.toCamelCase(prop)] = utils.convertStringVal(a[prop]);
                                        break;
                                }
                            }
                            res.push(o);
                        }
                    }
                }
            }
            return res;
        }

        // Parse Templates
        function parseTemplates(sourceObj) {
            var res = [];

            if (sourceObj) {
            }

            return res;
        }

        // Parse Zones
        function parseZones(sourceObj) {
            var res = [];

            if (sourceObj) {
                var zones = sourceObj.Zone;
                if (zones) {
                    // Convert zones into an array if only one item
                    if (!(zones instanceof Array)) {
                        zones = [zones];
                    }

                    // parse each zone object
                    for (var i = 0; i < zones.length; i++) {
                        var a = zones[i];
                        if (a) {
                            var o = {};
                            for (var prop in a) {
                                switch (prop.toLowerCase()) {
                                    case '__prefix':
                                    case '__proto__':
                                        break;
                                    case 'metadata':
                                        o.metadata = parseMetadata(a[prop].KeyValuePair);
                                        break;
                                    default:
                                        o[utils.toCamelCase(prop)] = utils.convertStringVal(a[prop]);
                                        break;
                                }
                            }
                            res.push(o);
                        }
                    }
                }
            }
            return res;
        }

        function parseMetadata(kvps) {
            var metadata = null;
            if (kvps) {
                metadata = {};
                // Convert metadatas into an array if only one item
                if (!(kvps instanceof Array)) {
                    kvps = [kvps];
                }
                // parse the metadata keyvaluepair
                for (var k = 0; k < kvps.length; k++) {
                    var kvp = kvps[k];
                    if (kvp.Key) {
                        metadata[utils.toCamelCase(kvp.Key)] = utils.convertStringVal(kvp.Value);
                    }
                }
            }
            return metadata;
        }

        // Return the constructor for the class
        return (CampaignXMLParser);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    // Parses AZ into entity
    app.factory('CampaignParser', ["$q", "CSVParser", "Utils", function($q, CSVParser, Utils) {
        // Instantiate the utils class
        var utils = new Utils();

        // Define the constructor function.
        function CampaignParser() {

        }

        // Define the instance methods and properties
        CampaignParser.prototype = {
            // Public interface
            type: 'signs.parser.campaign',
            parse: parse
        };

        // Schedule a deferred parsing of the object
        function parse(o) {
            // promise object for async response
            var deferred = $q.defer();
            doParse(o, deferred);
            return deferred.promise;
        }

        // Actual parsing of object
        function doParse(scriptBlock, promise) {
            // Parser the csv string into an array of table objects - {type: [tableName], items: []}
            var csvParser = new CSVParser();
            // On parse we
            var onCSVParsed = function(o) {
                // Parse the CSV objects
                var campaign = parseCsvObject(o);

                // Wrap the response in a standard format
                promise.resolve({id: scriptBlock.id, type: scriptBlock.type, campaign: campaign});
            };

            csvParser.parse(scriptBlock.script).then(onCSVParsed, function(err) {
                promise.reject(err);
            });
        }

        // Local variables
        var identityItem = [];
        var campaignItem = [];

        // Process the CSV Obect returned into a campaign collection
        function parseCsvObject(o) {
            var campaign = {identities: [], campaigns: []};

            // iterate through the table array and extract the tables
            for (var i = 0; i < o.length; i++) {
                switch (o[i].type) {
                    case 'identity':
                        identityItem = o[i].items;
                        break;
                    case 'campaign':
                        campaignItem = o[i].items;
                        break;
                }
            }

            // iterate through the identity object
            for (var i = 0; i < identityItem.length; i++) {
                var s = identityItem[i];
                // create an identiy object and add it to the identity array
                if (s) {
                    var w = {};
                    for (var prop in s) {
                        var val = utils.convertStringVal(s[prop]);
                        w[prop] = val;
                    }
                    /* var w = {
                        siteid: parseInt(s.siteid),
                        sitename: s.sitename,
                        type: s.type
                    };*/
                    campaign.identities.push(w);
                }
            }

            // iterate through the campaign object
            for (var i = 0; i < campaignItem.length; i++) {
                var s = campaignItem[i];
                if (s) {
                    // create a campaign object and add it to the campaign array
                    var c = {};
                    for (var prop in s) {
                        var val = utils.convertStringVal(s[prop]);
                        c[prop] = val;
                    }

                    /* var c = {
                        id: parseInt(s.id),
                        lastpublished: s.lastpublished,
                        name: s.name,
                        path: s.path,
                        type: s.type,
                        filename: s.filename
                    };*/
                    campaign.campaigns.push(c);
                }
            }

            return campaign;
        }

        // Return the constructor for the class
        return (CampaignParser);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    // Parses AZ into entity
    app.factory('ContentDistributionParser', ["$q", "CSVParser", function($q, CSVParser) {
        // Define the constructor function.
        function ContentDistributionParser() {

        }

        // Define the instance methods and properties
        ContentDistributionParser.prototype = {
            // Public interface
            type: 'signs.parser.content-distribution',
            parse: parse
        };

        // Schedule a deferred parsing of the object
        function parse(o) {
            // promise object for async response
            var deferred = $q.defer();
            doParse(o, deferred);
            return deferred.promise;
        }

        // Actual parsing of object
        function doParse(scriptBlock, promise) {
            // Parser the csv string into an array of table objects - {type: [tableName], items: []}
            var csvParser = new CSVParser();
            // On parse we
            var onCSVParsed = function(o) {
                // Parse the CSV objects
                var entity = parseCsvObject(o);
                // Wrap the response in a standard format
                promise.resolve({id: scriptBlock.id, type: scriptBlock.type, contentDistribution: entity});
            };

            csvParser.parse(scriptBlock.script).then(onCSVParsed, function(err) {
                promise.reject(err);
            });
        }

        // Process the CSV Obect and return ContentDistribution object
        function parseCsvObject(o) {
            var contentDistr = {
                smartHubs: [],
                cdns: []
            };

            var smartHubArray = [];
            var cdnArray = [];
            // iterate through the table array and extract the tables
            for (var i = 0; i < o.length; i++) {
                switch (o[i].type) {
                    case 'SmartHub':
                        smartHubArray = o[i].items;
                        break;
                    case 'CDN':
                        cdnArray = o[i].items;
                        break;
                }
            }

            // iterate through the smarthub array
            for (var i = 0; i < smartHubArray.length; i++) {
                var s = smartHubArray[i];
                // Create the SmartHub object
                var smarthub = {
                    id: parseInt(s._id),
                    name: s.name,
                    order: s.order,
                    ip: s.ip,
                    port: s.port
                };

                // Add SmartHub object into the content distribution's SmartHub array
                contentDistr.smartHubs.push(smarthub);
            }

            // iterate through the cdn array
            for (var i = 0; i < cdnArray.length; i++) {
                var c = cdnArray[i];
                // Create the CDN object
                var cdn = {
                    id: parseInt(c._id),
                    name: c.name,
                    order: c.order,
                    ip: c.ip,
                    pingFile: c.pingfile
                };

                // Add SmartHub object into the content distribution's SmartHub array
                contentDistr.cdns.push(cdn);
            }

            return contentDistr;
        }

        // Return the constructor for the class
        return (ContentDistributionParser);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    // Parses AZ into entity
    app.factory('InfoMenuParser', ["$q", "CSVParser", function($q, CSVParser) {
        // Define the constructor function.
        function InfoMenuParser() {

        }

        // Define the instance methods and properties
        InfoMenuParser.prototype = {
            // Public interface
            type: 'signs.parser.infomenu',
            parse: parse
        };

        // Schedule a deferred parsing of the object
        function parse(o) {
            // promise object for async response
            var deferred = $q.defer();
            doParse(o, deferred);
            return deferred.promise;
        }

        // Actual parsing of object
        function doParse(scriptBlock, promise) {
            // Parser the csv string into an array of table objects - {type: [tableName], items: []}
            var csvParser = new CSVParser();
            // On parse we
            var onCSVParsed = function(o) {
                // Parse the CSV objects
                var entity = parseCsvObject(o);
                // Wrap the response in a standard format
                promise.resolve({id: scriptBlock.id, type: scriptBlock.type, items: entity});
            };

            csvParser.parse(scriptBlock.script).then(onCSVParsed, function(err) {
                promise.reject(err);
            });
        }

        // Process the CSV Obect and return a widget collection
        function parseCsvObject(o) {
            var items = [];

            var infoMenu = [];
            var assetTypes = [];
            var assets = [];

            // Split infomenu, assettype and infomenuasset into separate arrays for processing
            for (var i = 0; i < o.length; i++) {
                switch (o[i].type) {
                    case 'infomenu':
                        infoMenu = o[i].items;
                        break;
                    case 'assettype':
                        assetTypes = o[i].items;
                        break;
                    case 'infomenuasset':
                        assets = o[i].items;
                        break;
                }
            }

            // Process asset types and create lookup table
            var assetLookup = {};
            for (var k = 0; k < assetTypes.length; k++) {
                var im = assetTypes[k];
                // Add asset type to the dictionary
                assetLookup[im._id] = im.name;
            }

            // Process info menu as item
            var itemLookup = {};
            for (var k = 0; k < infoMenu.length; k++) {
                var im = infoMenu[k];
                // try to lookup the item from the dictionary
                var w = itemLookup[im._id];
                // if not found, create an entry in the itemLookup dictionary
                if (w === undefined) {
                    w = {
                        id: parseInt(im._id),
                        caption: im.caption,
                        parentId: im._parentId,
                        position: parseInt(im.position),
                        text: im.bodyText,
                        tag: im.tag,
                        children: [],
                        assets: []
                    };
                    // Add item to the dictionary
                    itemLookup[w.id] = w;

                    if (w.parentId === undefined || w.parentId === '') {
                        items.push(w);
                    }
                }
            }

            // iterate through root items and find children
            for (var id in itemLookup) {
                var item = itemLookup[id];
                // find item's parent, and if valid, add as a child
                var parent = itemLookup[item.parentId];
                if (parent) {
                    parent.children.push(item);
                }
            }

            // process item assets
            for (var i = 0; i < assets.length; i++) {
                var ab = assets[i];
                // try to lookup the item from the dictionary
                var w = itemLookup[ab._infoMenuId];
                if (w) {
                    w.assets.push({
                        id: parseInt(ab._id),
                        assetType: assetLookup[ab._assetId],
                        compAllocIc: parseInt(ab._compAllocId),
                        position: parseInt(ab.position),
                        duration: parseFloat(ab.duration),
                        tag: ab.tag,
                        mediaId: parseInt(ab.MediaID),
                        type: ab.Type,
                        mediaFile: ab.mediaFile
                    });
                }
            }

            return items;
        }

        // Return the constructor for the class
        return (InfoMenuParser);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    // Parses AZ into entity
    app.factory('InterfaceDescriptionParser', ["$q", "CSVParser", function($q, CSVParser) {
        // Define the constructor function.
        function InterfaceDescriptionParser() {

        }

        // Define the instance methods and properties
        InterfaceDescriptionParser.prototype = {
            // Public interface
            type: 'signs.parser.interface-description',
            parse: parse
        };

        // Schedule a deferred parsing of the object
        function parse(o) {
            // promise object for async response
            var deferred = $q.defer();
            doParse(o, deferred);
            return deferred.promise;
        }

        // Actual parsing of object
        function doParse(scriptBlock, promise) {
            // Parser the csv string into an array of table objects - {type: [tableName], items: []}
            var csvParser = new CSVParser();
            // On parse we
            var onCSVParsed = function(o) {
                // Parse the CSV objects
                var interfaceDesc = parseCsvObject(o);
                // Wrap the response in a standard format
                promise.resolve({id: scriptBlock.id, type: scriptBlock.type, interfaceDescription: interfaceDesc});
            };

            csvParser.parse(scriptBlock.script).then(onCSVParsed, function(err) {
                promise.reject(err);
            });
        }

        // Process the CSV Obect returned into a mediazone collection
        function parseCsvObject(o) {
            var entity = {
                mediaZones: [],
                components: []
            };

            for (var i = 0; i < o.length; i++) {
                // Create the MediaZone object
                if (o[i].type == 'media_zones') {
                    for (var k = 0; k < o[i].items.length; k++) {
                        var item = getMediaZone(o[i].items[k]);
                        entity.mediaZones.push(item);
                    }
                } else if (o[i].type == 'components') { // Create the Component object
                    for (var k = 0; k < o[i].items.length; k++) {
                        var c = getComponent(o[i].items[k]);
                        entity.components.push(c);
                    }
                }
            }

            return entity;
        }

        // Create the MediaZone object
        function getMediaZone(item) {
            var entity = {
                id: parseInt(item._id),
                name: item.Name,
                description: item.Description,
                interfaceId: parseInt(item._interfaceId),
                scheduleable: item.Schedulable != '0',
                typeId: parseInt(item.typeId),
                recordPlayoutMetrics: item.recordPlayoutMetrics != '0',
                guid: item.Guid
            };

            return entity;
        }

        // Create the component object
        function getComponent(item) {
            var entity = {
                id: parseInt(item._id),
                name: item.name,
                type: parseInt(item.type),
                friendlyId: item.friendlyId
            };

            return entity;
        }

        // Return the constructor for the class
        return (InterfaceDescriptionParser);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    // Parses AZ into entity
    app.factory('PMScheduleParser', ["$q", "CSVParser", function($q, CSVParser) {
        // Define the constructor function.
        function PMScheduleParser() {

        }

        // Define the instance methods and properties
        PMScheduleParser.prototype = {
            // Public interface
            type: 'signs.parser.pmschedule',
            parse: parse
        };

        // Schedule a deferred parsing of the object
        function parse(o) {
            // promise object for async response
            var deferred = $q.defer();
            doParse(o, deferred);
            return deferred.promise;
        }

        // Actual parsing of object
        function doParse(scriptBlock, promise) {
            // Parser the csv string into an array of table objects - {type: [tableName], items: []}
            var csvParser = new CSVParser();
            // On parse we
            var onCSVParsed = function(o) {
                // Parse the CSV objects
                var entity = parseCsvObject(o);
                // Wrap the response in a standard format
                promise.resolve({id: scriptBlock.id, type: scriptBlock.type, pmSchedules: entity});
            };

            csvParser.parse(scriptBlock.script).then(onCSVParsed, function(err) {
                promise.resolve(
                    {id: scriptBlock.id, type: scriptBlock.type, pmSchedules: []}
                );
            });
        }

        // Process the CSV Obect and return pmschedule item array
        function parseCsvObject(o) {
            var items = [];

            // Get the pmschedule source
            var source = [];
            for (var i = 0; i < o.length; i++) {
                if (o[i].type == 'pmschedule') {
                    source = o[i].items;
                }
            }

            // Process pmschedule items
            for (var k = 0; k < source.length; k++) {
                var s = source[k];

                // Create pmschedule object
                var item = {
                    id: parseInt(s.PMScheduleID),
                    zoneId: parseInt(s.ZoneID),
                    day: parseInt(s.Day),
                    startTime: s.StartTime,
                    endTime: s.EndTime
                };

                items.push(item);
            }

            return items;
        }

        // Return the constructor for the class
        return (PMScheduleParser);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    // Parses AZ into entity
    app.factory('RSSFileParser', ["$q", "x2js", function($q, x2js) {
        // Define the constructor function.
        function RSSFileParser() {

        }

        // Define the instance methods and properties
        RSSFileParser.prototype = {
            // Public interface
            type: 'signs.parser.rss-file',
            parse: parse
        };

        // Schedule a deferred parsing of the object
        function parse(o) {
            // promise object for asyn response
            var deferred = $q.defer();
            doParse(o.url, o.data, deferred);
            return deferred.promise;
        }

        // Actual parsing of object
        function doParse(url, scriptBlock, promise) {
            if (url == null || url.length == 0) {
                return promise.reject('Empty URL');
            }
            // parse the url to append to image
            var urlSplits = url.split('\\').join('/').split('/');
            var tempUrl = '';
            for (var i = urlSplits.length - 2; i >= 0; i--) {
                var d = urlSplits[i];

                tempUrl = d + '\\' + tempUrl;

                if (d.toLowerCase() == 'rssfeeds') {
                    tempUrl = 'c:\\fastnetv2\\ug media\\' + tempUrl;
                    break;
                }
            }
            url = tempUrl;

            // Parse the xml string
            var res = parseXml(url, scriptBlock);
            // Wrap the response in a standard format
            promise.resolve({channels: res});
        }

        // Parse XML and return an entity
        function parseXml(url, xmlText) {
            if (!xmlText || xmlText.length == 0) {
                return null; // Empty
            }

            // clean up urls that can cause parsing error
            var startPos = xmlText.toLowerCase().indexOf('<link>', 0);
            var count = 0;
            while (startPos > -1) {
                var end = xmlText.toLowerCase().indexOf('</link>', startPos);
                var substr = xmlText.substring(startPos + '<link>'.length, end);
                if (substr.indexOf('#') > -1 || substr.indexOf('-') > -1) {
                    var modsubstr = encodeURIComponent(substr);
                    xmlText = xmlText.split(substr).join(modsubstr);
                }
                startPos = xmlText.toLowerCase().indexOf('<link>', end);

                // fail safe check
                count++;
                if (count >= 10000) {
                    break;
                }
            }

            // symbol '&' break the xml format. convert it using x2js.xml_str2json make app crash. replace it with html entity name solve the issue.
            xmlText = xmlText.split('&').join('&amp;');

            // Convert xml to a json object so we can parse
            var o = x2js.xml_str2json(xmlText);

            if (!o) {
                return null;
            }

            // Check for valid rss node
            var rssNode = o.rss;
            if (!rssNode) {
                return null;
            }

            var channelObjects = [];

            if (rssNode.channel) {
                // Convert channel into an array if only one item
                var channels = rssNode.channel;
                if (channels) {
                    if (!(channels instanceof Array)) {
                        channels = [rssNode.channel];
                    }
                    // Iterate through the channels and parse into a channel object and add to the array of channels
                    for (var i = 0; i < channels.length; i++) {
                        var e = parseChannel(url, channels[i]);
                        if (e) {
                            channelObjects.push(e);
                        }
                    }
                }
            }
            return channelObjects;
        }

        function parseChannel(url, e) {
            // create channel object
            var channel = {
                ttl: parseInt(e.ttl),
                copyright: e.copyright,
                language: e.language,
                title: e.title,
                link: e.link,
                description: e.description,
                lastBuildDate: e.lastBuildDate,
                image: null,
                items: [],
                convertedFeed: url
            };

            // create channel image object
            if (e.image) {
                var img = e.image;
                channel.image = {
                    width: parseInt(img.width),
                    height: parseInt(img.height),
                    link: img.link,
                    title: img.title,
                    url: img.url
                };
            }

            // iterate through rss items and add it to channel items
            if (e.item) {
                // Convert item into an array if only one item
                var items = e.item;
                if (items) {
                    if (!(items instanceof Array)) {
                        items = [e.item];
                    }
                    // Iterate through the items and parse into an item object and add to the array of items
                    for (var i = 0; i < items.length; i++) {
                        var item = parseItem(url, items[i]);
                        if (item) {
                            channel.items.push(item);
                        }
                    }
                }
            }

            return channel;
        }

        function processXmlElement(element) {
            var res = element;
            if (Array.isArray(element)) {
                for (var i = 0; i < element.length; i++) {
                    var elem = element[i];
                    if (typeof elem == 'string') {
                        res = elem;
                        break;
                    }
                }
            }
            return res;
        }

        function processImage(url, e) {
            if (e.content && e.content.__prefix == 'media' && e.content._type && e.content._type.indexOf('image') >= 0) {
                return e.content._url;
            } else if (e.localImg) {
                return url + e.localImg;
            } else if (e.images && e.images.pic && e.images.pic._src) {
                return e.images.pic._src;
            } else if (e.thumbnail) {
                if (e.thumbnail.__prefix == 'media') {
                    return e.thumbnail._url;
                } else if (Array.isArray(e.thumbnail) && e.thumbnail[0] && e.thumbnail[0].__prefix == 'media') {
                    return e.thumbnail[0]._url;
                }
            }
        }

        function parseItem(url, e) {
            // create an item object
            var item = {
                category: processXmlElement(e.category),
                description: processXmlElement(e.description),
                link: processXmlElement(e.link),
                title: processXmlElement(e.title),
                pubDate: processXmlElement(e.pubDate),
                guid: (e.guid) ? e.guid.__text : '',
                media: processImage(url, e)
            };

            return item;
        }

        // Return the constructor for the class
        return (RSSFileParser);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    // Parses AZ into entity
    app.factory('RSSParser', ["$q", "CSVParser", function($q, CSVParser) {
        // Define the constructor function.
        function RSSParser() {

        }

        // Define the instance methods and properties
        RSSParser.prototype = {
            // Public interface
            type: 'signs.parser.rss',
            parse: parse
        };

        // Schedule a deferred parsing of the object
        function parse(o) {
            // promise object for async response
            var deferred = $q.defer();
            doParse(o, deferred);
            return deferred.promise;
        }

        // Actual parsing of object
        function doParse(scriptBlock, promise) {
            // Parser the csv string into an array of table objects - {type: [tableName], items: []}
            var csvParser = new CSVParser();
            // On parse we
            var onCSVParsed = function(o) {
                // Parse the CSV objects
                var entity = parseCsvObject(o);
                // Wrap the response in a standard format
                promise.resolve({id: scriptBlock.id, type: scriptBlock.type, feeds: entity});
            };

            csvParser.parse(scriptBlock.script).then(onCSVParsed, function(err) {
                promise.reject(err);
            });
        }

        // Process the CSV Obect and return rss item array
        function parseCsvObject(o) {
            var items = [];

            // Get the rss feed
            var source = [];
            for (var i = 0; i < o.length; i++) {
                if (o[i].type == 'rss') {
                    source = o[i].items;
                }
            }

            // Process rss items
            for (var k = 0; k < source.length; k++) {
                var s = source[k];

                // Create rss object
                var item = {
                    id: parseInt(s._id),
                    name: s.name,
                    url: s.url,
                    refreshRate: parseInt(s.refreshRate),
                    maxAge: parseInt(s.maxAge),
                    maxItems: parseInt(s.maxItems),
                    compId: parseInt(s._compId),
                    folderId: s._folderId,
                    lastDownload: s.lastDownload,
                    disable: s.disable != '0',
                    configuration: s.configuration,
                    currentSize: parseInt(s.currentSize),
                    lifetimeSize: parseInt(s.lifetimesize),
                    position: parseInt(s.position),
                    fileUrl: s.fileUrl
                };

                items.push(item);
            }

            return items;
        }

        // Return the constructor for the class
        return (RSSParser);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    // Parses AZ into entity
    app.factory('SchedulingParser', ["$q", "CSVParser", "Utils", "dataUtil", function($q, CSVParser, Utils, dataUtil) {
        // Instantiate Utils class
        var utils = new Utils();

        // Define the constructor function.
        function SchedulingParser() {

        }

        // Define the instance methods and properties
        SchedulingParser.prototype = {
            // Public interface
            type: 'signs.parser.scheduling',
            parse: parse
        };

        // Schedule a deferred parsing of the object
        function parse(o) {
            // promise object for async response
            var deferred = $q.defer();
            doParse(o, deferred);
            return deferred.promise;
        }

        // Actual parsing of object
        function doParse(scriptBlock, promise) {
            // Parser the csv string into an array of table objects - {type: [tableName], items: []}
            var csvParser = new CSVParser();
            // On parse we
            var onCSVParsed = function(o) {
                // Parse the CSV objects
                var mediaZones = parseCsvObject(o);
                // Wrap the response in a standard format
                promise.resolve({
                    id: scriptBlock.id,
                    type: scriptBlock.type,
                    mediaZones: mediaZones
                });
            };

            csvParser.parse(scriptBlock.script).then(onCSVParsed, function(err) {
                promise.reject(err);
            });
        }

        // Local variables
        var _schedules = [];
        var _timespans = [];
        var _scheduleMedias = [];
        var _scheduleMediaPlayouts = [];
        var _scheduleMediaDayparts = [];
        var DEFAULT_VOLUME_LEVEL = '100';

        var dayPartIndex = {
            sun: 0,
            mon: 1,
            tue: 2,
            wed: 3,
            thu: 4,
            fri: 5,
            sat: 6
        };

        // Process the CSV Obect returned into a mediazone collection
        function parseCsvObject(o) {
            var mediaZones = [];
            var i;
            // iterate through the table array and extract the tables
            for (i = 0; i < o.length; i++) {
                switch (o[i].type) {
                    case 'schedule':
                        _schedules = o[i].items;
                        break;
                    case 'timespan':
                        _timespans = o[i].items;
                        break;
                    case 'schedulemedia':
                        _scheduleMedias = o[i].items;
                        break;
                    case 'schedulemediaplayout':
                        _scheduleMediaPlayouts = o[i].items;
                        break;
                    case 'schedulemediadayparting':
                        _scheduleMediaDayparts = o[i].items || [];
                        break;
                }
            }

            var zoneLookup = {};
            // iterate through the schedule object which represent the playlist items
            for (i = 0; i < _schedules.length; i++) {
                var s = _schedules[i];

                // try to lookup the zone from the dictionary
                var z = zoneLookup[s._zoneId];
                // if not found, create an entry in the zoneLookup dictionary
                if (z === undefined) {
                    z = {
                        id: parseInt(s._zoneId),
                        name: s.zoneName,
                        playlists: []
                    };
                    zoneLookup[z.id] = z;
                    // Add zone to the dictionary
                    mediaZones.push(z);
                }

                // Create playlist object
                var playlist = {
                    id: parseInt(s._id),
                    guid: s.guid,
                    name: s.name,
                    description: s.description,
                    schedule: {
                        startDate: s.startDate,
                        endDate: s.endDate,
                        days: s.days,
                        isExclusive: s.isExclusive == '1' ? true : false,
                        timespans: getTimespans(s._id)
                    },
                    items: getPlaylistMedia(s._id)
                };

                if (playlist.guid) {
                    for (var j = 0; j < playlist.items.length; j++) {
                        playlist.items[j].channelGuid = playlist.guid;
                    }
                }

                // Add playlist object into the zone playlist array
                z.playlists.push(playlist);
            }

            return mediaZones;
        }

        // get media for the playlist
        function getPlaylistMedia(scheduleId) {
            var media = [];
            // Iterate through the media and find ones for the playlist
            for (var i = 0; i < _scheduleMedias.length; i++) {
                var m = _scheduleMedias[i];
                // find the media from the scheduleMedia table and create a media object
                if (m._scheduleId === scheduleId) {
                    var o = {
                        id: parseInt(m._id),
                        contentId: m.contentId,
                        position: parseInt(m.position),
                        type: m.Type,
                        title: m.Title,
                        description: m.Description,
                        mediaId: parseInt(m._mediaId),
                        configuration: m.configuration,
                        mediaFile: m.mediaFile,
                        playoutProperties: {
                            duration: null,
                            aspectRatio: null,
                            transition: null,
                            playoutEffect: null,
                            custom: null,
                            playoutTagsRule: null,
                            frequencyCondition: null,
                            frequencyPlayout: null,
                            frequencyUnit: null,
                            timespans: getTimespans(m._id),
                            volumeLevel: null,
                            isMuted: null
                        },
                        schedule: {
                            startDate: null,
                            endDate: null,
                            playForever: null
                        },
                        postId: m.postId,
                    };

                    o.uniqueId = o.id + '-' + o.mediaId + '-' + o.position;

                    // iterate through the mediaplayout table and find the media playout properties
                    for (var k = 0; k < _scheduleMediaPlayouts.length; k++) {
                        var p = _scheduleMediaPlayouts[k];
                        if (p._mediaAllocId == m._mediaAllocId) {
                            // console.log(p);
                            // initialize playoutproperties object for the media
                            o.playoutProperties.aspectRatio = utils.toAspectRatio(p.aspectRatio, 'content');
                            o.playoutProperties.duration =
                                o.playoutProperties.playoutduration =
                                parseFloat(p.playoutduration);
                            o.playoutProperties.transition = utils.toTransition(p.transition);
                            o.playoutProperties.playoutEffect = p.playoutEffect;
                            o.playoutProperties.custom = p.custom;
                            o.playoutProperties.playoutTagsRule = p.playoutTagsRule;
                            o.playoutProperties.frequencyPlayout = parseFloat(p.frequencyPlayout);
                            // set default value of frequency condition & unit to empty
                            o.playoutProperties.frequencyCondition = undefined;
                            o.playoutProperties.frequencyUnit = undefined;
                            o.playoutProperties.hasNaturalDuration = utils.hasNaturalDuration(p.naturalDuration);
                            o.playoutProperties.volumeLevel = p.volumeLevel ? parseInt(p.volumeLevel, 10) : undefined;
                            o.playoutProperties.isMuted = p.isMuted ? dataUtil.isValueTrue(p.isMuted) : undefined;
                            var frequencyCondition = (p.frequencyCondition || '').toLowerCase();
                            // if we have frequency condition and it is not 'notset' and we have frequency unit, populate the properties
                            if (frequencyCondition && frequencyCondition != 'notset' && p.frequencyUnit) {
                                o.playoutProperties.frequencyCondition = frequencyCondition;
                                o.playoutProperties.frequencyUnit = p.frequencyUnit.toLowerCase();
                            }

                            o.playoutProperties.fixedVideoDuration = !isNaN(p.fixedVideoDuration) ? parseFloat(p.fixedVideoDuration) / 1000 : undefined; // eslint-disable-line max-len

                            // initialize the schedule object for the media
                            o.schedule.startDate = p.startDate;
                            o.schedule.endDate = p.endDate;
                            o.schedule.playForever = p.playforever === 'True';
                        }
                    }

                    // iterate through the schedule dayparts table
                    for (var j = 0; j < _scheduleMediaDayparts.length; j++) {
                        var rawDayPart = _scheduleMediaDayparts[j];
                        if (rawDayPart._mediaAllocId != m._mediaAllocId) {
                            continue;
                        }

                        o.schedule.recur = o.schedule.recur || {days: []};
                        // Parse days
                        var days = rawDayPart.days.split(';');
                        for (var dayIndex = 0; dayIndex < days.length; dayIndex++) {
                            // Create a schedule daypart
                            // Add timespan to daypart
                            // Add this paypart to the recur collection
                            o.schedule.recur.days.push(utils.convertDaypartToSchedule(
                                rawDayPart.startTime,
                                rawDayPart.endTime,
                                dayPartIndex[days[dayIndex].toLowerCase()]
                            ));
                        }
                    }

                    // insert the media object into an array
                    media.push(o);
                }
            }

            // return the media array
            return media;
        }

        // Get timespans for the playlist
        function getTimespans(scheduleId) {
            var timespans = [];
            // iterate through the timespans array and look for ones for the playlist
            for (var i = 0; i < _timespans.length; i++) {
                var t = _timespans[i];
                if (t._scheduleId === scheduleId) {
                    // create timespan object and insert it into the array
                    timespans.push({
                        type: parseInt(t.type),
                        startTime: t.startTime,
                        endTime: t.endTime,
                        loopCount: parseInt(t.loopCount)
                    });
                }
            }

            return timespans;
        }

        // Return the constructor for the class
        return (SchedulingParser);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

  var app = $angular.module('signsHTML5');

  // This is a sample template parser. Do not use, simply copy and modify name and type
  app.factory('TemplateParser', ["$q", function($q) {
    // Define the constructor function.
    function TemplateParser() {

    }

    // Define the instance methods and properties
    TemplateParser.prototype = {
       // Public interface
      type: 'signs.parser.template',
      parse: parse
    };

    // Schedule a deferred parsing of the object
    function parse(o) {
      // promise object for asyn response
      var deferred = $q.defer();
            doParse(o, deferred);
      return deferred.promise;
    }

    // Actual parsing of object
    // promise.resolve(); //finished parsing
    // promise.reject(); //error during parse
    function doParse(o, promise) {
        var res = o.toUpperCase();
        promise.resolve(res);
    }

    // Return the constructor for the class
    return (TemplateParser);
  }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    // Parses AZ into entity
    app.factory('TextMsgParser', ["$q", "x2js", "dataUtil", function($q, x2js, dataUtil) {
        // Define the constructor function.
        function TextMsgParser() {

        }

        // Define the instance methods and properties
        TextMsgParser.prototype = {
            // Public interface
            type: 'signs.parser.textmsg',
            parse: parse
        };

        // Schedule a deferred parsing of the object
        function parse(o) {
            // promise object for asyn response
            var deferred = $q.defer();
            doParse(o, deferred);
            return deferred.promise;
        }

        // Actual parsing of object
        function doParse(scriptBlock, promise) {
            // Fix non compliant xml tags (e.g. <element data>)
            var xml = treatXml(scriptBlock.script);
            // Parse the compliant xml string
            var res = parseXml(xml);
            // Wrap the response in a standard format
            promise.resolve({id: scriptBlock.id, type: scriptBlock.type, playlist: res});
        }

        // Fix non compliant xml tags (e.g. <element data>)
        function treatXml(source) {
            var xml = source.split('<ELEMENT DATA>').join('<ELEMENTDATA>');
            xml = xml.split('</ELEMENT DATA>').join('</ELEMENTDATA>');
            xml = '<ELEMENTS>' + xml + '</ELEMENTS>';
            xml = xml.split('&').join('&amp;');
            xml = xml.split('%').join('&#37;');
            xml = encodeData(xml);
            return xml;
        }

        function encodeData(xmlText) {
            var startSection = '';
            var middleSection = '';
            var endSection = '';
            var dataOpenTag = '<DATA>';
            var dataCloseTag = '</DATA>';

            endSection = xmlText;

            while (endSection.indexOf(dataOpenTag) >= 0) {
                // set startSection(temp)
                var tempstartSection = endSection.substring(0, endSection.indexOf(dataOpenTag));

                // set middle section (the part that need to do special character swap)
                middleSection = endSection.substring(endSection.indexOf(dataOpenTag) + dataOpenTag.length, endSection.indexOf(dataCloseTag));

                // set end section
                endSection = endSection.substring(endSection.indexOf(dataCloseTag) + dataCloseTag.length, endSection.length);

                // do replace special characters on middle section
                middleSection = middleSection.split('<').join('&lt;');
                middleSection = middleSection.split('>').join('&gt;');
                middleSection = middleSection.split('"').join('&quot;');

                // combine with start section
                startSection += tempstartSection + dataOpenTag + middleSection + dataCloseTag;
            }
            return startSection + endSection;
        }

        // Parse XML and return an entity
        function parseXml(xmlText) {
            var playlist = {
                items: []
            };

            // Check its valid
            if (!xmlText || xmlText.length == 0) {
                return playlist; // Empty textmsg
            }
            // Convert xml to a json object so we can parse
            var o = x2js.xml_str2json(xmlText);

            var elements = o.ELEMENTS.ELEMENT;
            if (elements) {
                // Convert element into an array if only one item
                if (!(elements instanceof Array)) {
                    elements = [o.ELEMENTS.ELEMENT];
                }
                // iterate through the elements which represent textmsg items
                for (var i = 0; i < elements.length; i++) {
                    var e = elements[i];
                    var item = parseElement(e);
                    if (item) {
                        // set item position
                        item.position = i;
                        // Add item to textmsg items
                        playlist.items.push(item);
                    }
                }
            }
            return playlist;
        }

        function parseElement(e) {
            if (!e) {
                return null;
            }
            // Reference textmsg item object
            var item = {
                type: 'Text',
                title: null,
                position: 0,
                playoutProperties: {
                    custom: null
                }
            };

            // Iterate through the element data to extract the properties of the media item
            for (var j = 0; j < e.ELEMENTDATA.length; j++) {
                var ed = e.ELEMENTDATA[j];
                switch (ed.TYPE) {
                    case 'Caption':
                        item.title = dataUtil.convertUnicodeString(ed.DATA);
                        break;
                    case 'Properties':
                        item.playoutProperties.custom = ed.DATA;
                        break;
                }
            }
            // Sanity check that its a valid item
            return item.title === null ?
                null : // incomplete element
                item; // valid element
        }

        // Return the constructor for the class
        return (TextMsgParser);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    // Parses AZ into entity
    app.factory('VODAppParser', ["$q", "$filter", "channelUtil", "CSVParser", "Utils", "halMediaService", "FileReader", "FileUtil", "coreConfigurationService", "networkContentRouterService", "storageLocalStorageService", "MAPPING_SEQUENCEYBY", "dataUtil", "CONTENT_TYPE", "CHANNEL_TYPE", "mediaUtil", "AppspaceUrlUtil", "CONTENT_ACCESS_CONTEXT", function($q, $filter, channelUtil,
        CSVParser, Utils, halMediaService, FileReader, FileUtil, coreConfigurationService,
        networkContentRouterService, storageLocalStorageService, MAPPING_SEQUENCEYBY,
        dataUtil, CONTENT_TYPE, CHANNEL_TYPE, mediaUtil, AppspaceUrlUtil, CONTENT_ACCESS_CONTEXT) {
        var assetRunningId = 1;
        var rootPath;
        var supportedProtocols = {};
        var files;
        var thumbnails = {};
        var util = new Utils();
        var DAY_PART_INDEX = {
            sunday: 0,
            monday: 1,
            tuesday: 2,
            wednesday: 3,
            thursday: 4,
            friday: 5,
            saturday: 6
        };
        var mapping = {
            map: $q.resolve
        };
        var LOGO_PATH = {
            DEFAULT: '/play/DefaultItems/',
            PREVIEW: 'imagepreview'
        };
        // Get protocols from the driver
        halMediaService.getSupportedVideoProtocols().then(function(protocols) {
            for (var i = 0; i < protocols.length; i++) {
                supportedProtocols[protocols[i]] = true;
            }
        });

        // Define the constructor function.
        function VODAppParser() {

        }

        // Define the instance methods and properties
        VODAppParser.prototype = {
            // Public interface
            type: 'signs.parser.vod-app',
            parse: parse
        };

        function reset() {
            // clean thumbnails
            thumbnails = {};
            files = undefined;
            // reset asset running id
            assetRunningId = 1;
        }

        // Schedule a deferred parsing of the object
        function parse(o, options) {
            var promise = $q.defer();
            reset();
            var mappingService = networkContentRouterService.findService('signs-content-routing-service');
            for (var i = 0; i < mappingService.maps.length; i++) {
                var map = mappingService.maps[i];
                if (map.name.toLowerCase() != 'signs-content-mapping') {
                    continue;
                }

                if (map.map) {
                    mapping = map;
                }

                break;
            }
            
            rootPath = AppspaceUrlUtil.getAppspaceUrl() + '/app/';
            if (options && options.files) {
                files = options.files;
            }

            doParse(o, promise);
            return promise.promise;
        }

        // Actual parsing of object
        function doParse(scriptBlock, promise) {
            // Parser the csv string into an array of table objects - {type: [tableName], items: []}
            var csvParser = new CSVParser();

            var doLoadAndParseVODScript = function(url) {
                // parse VOD Script
                parseVODScript(url).then(function(entity) {
                    // Wrap the response in a standard format
                    promise.resolve({id: scriptBlock.id, type: scriptBlock.type, play: entity});
                }, promise.reject);
            };

            // On parse we
            var onCSVParsed = function(o) {
                // Parse the CSV objects
                var file = parseCsvObject(o);

                // Parse the content to get the full path
                var fullPath = retrieveVodScriptFileFullPath(file);
                networkContentRouterService.resolve(fullPath, 'play-content-routing-service').then(doLoadAndParseVODScript, function(err) {
                    doLoadAndParseVODScript(fullPath);
                });
            };

            csvParser.parse(scriptBlock.script).then(onCSVParsed, function(err) {
                promise.reject(err);
            });
        }

        // Process the CSV Obect and return the vod script file
        function parseCsvObject(o) {
            var scriptFile = null;

            // find vodscript file
            for (var i = 0; i < o.length; i++) {
                if (o[i].type.toLowerCase() == 'vodscript') {
                    var vodScripts = o[i].items;
                    // Process script
                    for (var k = 0; k < vodScripts.length; k++) {
                        scriptFile = vodScripts[k].file;
                    }
                }
            }

            return scriptFile;
        }

        // return the vod script file
        function retrieveVodScriptFileFullPath(file) {
            if (!file) {
                return '';
            }

            var scriptPath;
            var findPartialFileInCollection = function(filesCollection) {
                if (!filesCollection) {
                    return;
                }

                for (var i = 0; i < filesCollection.length; i++) {
                    var fileToCheck = filesCollection[i];
                    if (fileToCheck && fileToCheck.url.indexOf(file) >= 0) {
                        return fileToCheck.url;
                    }
                }
            };

            scriptPath = findPartialFileInCollection(files);
            if (!scriptPath) {
                var manifest = JSON.parse(storageLocalStorageService.get('device.sync', 'manifest')) || {};
                scriptPath = findPartialFileInCollection(manifest.files);
            }

            return scriptPath || file;
        }

        function parseVODScript(path) {
            // read the script and parse the channels
            var deferred = $q.defer();
            var fileReader = new FileReader();
            var onPathResolved = function(newPath) {
                fileReader.read(newPath)
                    .then(function(o) {
                        deferred.resolve(doParseAllChannels(o, path));
                    }, deferred.reject);
            };

            halMediaService.resolveContentPath(path).then(onPathResolved);
            return deferred.promise;
        }

        // parse all channels
        function doParseAllChannels(o, path) {
            var deferred = $q.defer();

            var play = {groups: [], channels: [], themeSettings: null, thumbnails: []};
            // parse live channels
            doParseLiveChannels(o, path).then(function(lc) {
                if (lc) {
                    if (lc.groups.length > 0) {
                        play.groups = play.groups.concat(lc.groups);
                    }

                    if (lc.channels.length > 0) {
                        play.channels = play.channels.concat(lc.channels);
                    }
                }

                // parse ondemand channels
                var odl = doParseOnDemandList(o);
                if (odl) {
                    if (odl.groups.length > 0) {
                        play.groups = play.groups.concat(odl.groups);
                    }

                    if (odl.channels.length > 0) {
                        play.channels = play.channels.concat(odl.channels);
                    }
                }

                // Sort channels based on name
                play.channels = $filter('orderBy')(play.channels, 'title');

                // Sort groups based on name
                for (var i = 0; i < play.groups.length; i++) {
                    var movement = false;
                    for (var k = 0; k < play.groups.length - 1; k++) {
                        var g1 = play.groups[k];
                        var g2 = play.groups[k + 1];
                        if (g1.id < 0) { // Skip virtual groups like Live channels
                            continue;
                        }

                        if (g1.name.toLowerCase() > g2.name.toLowerCase()) {
                            play.groups[k] = g2;
                            play.groups[k + 1] = g1;
                            movement = true;
                        }
                    }

                    if (!movement) {
                        break;
                    }
                }

                if (o.ThemeSettings) {
                    play.themeSettings = {};
                    play.themeSettings.themes = [];
                    if (!o.ThemeSettings.DefaultSelection) {
                        o.ThemeSettings.DefaultSelection = 'dark';
                    }

                    if (!o.ThemeSettings.Themes) {
                        o.ThemeSettings.Themes = [];
                    }

                    play.themeSettings.default = o.ThemeSettings.DefaultSelection.toLowerCase();
                    var isDefaultLogo = function(path) {
                        return path.indexOf(LOGO_PATH.DEFAULT) >= 0;
                    };

                    for (var j = 0; j < o.ThemeSettings.Themes.length; j++) {
                        var theme = o.ThemeSettings.Themes[j] || {};
                        var themeLogo = theme.ContentThumbnailRelativeUrl;
                        if (themeLogo) {
                            if (!isDefaultLogo(themeLogo)) {
                                themeLogo = generateThumbnailUrl(undefined, undefined, undefined, themeLogo);
                            }
                        } else {
                            themeLogo = theme.Logo;
                            // Use legacy numeric ID to generate thumbnail URL
                            // if content thumbnail relative URL is not defined
                            if (themeLogo && themeLogo.indexOf(LOGO_PATH.PREVIEW) >= 0) {
                                var start = themeLogo.indexOf('mid=') + 4;
                                var end = themeLogo.indexOf('&', start);
                                var mid = themeLogo.substring(start, end);
                                if (!isNaN(mid)) {
                                    themeLogo = generateThumbnailUrl(mid, null, 'high');
                                }
                            }
                        }

                        play.themeSettings.themes.push({
                            accent: theme.AccentColor,
                            logo: themeLogo,
                            name: theme.Selection.toLowerCase(),
                            isDefaultLogo: isDefaultLogo(themeLogo)
                        });
                    }
                }

                var resolveMappingPromises = [];
                for (var url in thumbnails) {
                    resolveMappingPromises.push(mapping.map(url.toLowerCase()));
                }

                $q.all(resolveMappingPromises).then(function(res) {
                    play.thumbnails = $angular.copy(res);
                    deferred.resolve(play);
                });
            }, function(err) {
                deferred.resolve(play);
            });

            return deferred.promise;
        }

        // Parse Live channels
        function doParseLiveChannels(o, path) {
            var deferred = $q.defer();

            var play = {groups: [], channels: []};

            // get vod data path
            var vodPath = '';
            var match = path.match(/\b(\d+).txt(?:\b$|\b\?)/i);
            if (match) {
                var fullpath = retrieveVodScriptFileFullPath(match[0]);
                var index = fullpath.toLowerCase().indexOf('vodclientscript');
                if (index > -1) {
                    vodPath = fullpath.substring(0, index) + 'VODEpg/';
                }
            }

            var epgPromises = [];
            var createProgramGuide = false;
            // create channels
            if (o.ChannelList && o.ChannelList.length > 0) {
                // create Live Channel group
                var g = {id: -1, name: 'Live Channels', position: 0, channels: []};
                for (var i = 0; i < o.ChannelList.length; i++) {
                    var cl = o.ChannelList[i];

                    // generate thumbnail url
                    var asset = cl.AssetInfo || {};
                    var thumbnailUrl = processSvgThumbnail(asset.ContentMediaPath) || generateThumbnailUrl(asset.ContentSourceID);
                    var logoUrl = generateMediaUrl(asset.ContentMediaPath);
                    // get media url
                    var mediaUrl = null;
                    if (cl.ContentSources.length > 0) {
                        var defaultUrl = '';
                        var hls = '';
                        var multicast = '';
                        for (var k = 0; k < cl.ContentSources.length; k++) {
                            var cs = cl.ContentSources[k];
                            if (!cs || cs.url.length == 0) {
                                continue;
                            }

                            // Use default if available
                            if (cs.DeviceType.toLowerCase() == 'default') {
                                defaultUrl = cs.url;
                            } else if (cs.DeviceType.toLowerCase() == 'hls') {// Accept HLS if available
                                hls = cs.url;
                            } else if (cs.DeviceType.toLowerCase() == 'multicast') {
                                multicast = cs.url;
                            }
                        }

                        if (multicast.length > 0 && supportedProtocols['multicast']) {
                            mediaUrl = multicast;
                            // console.log("Using multicast");
                        } else if (hls.length > 0 && supportedProtocols['hls']) {
                            mediaUrl = hls;
                            // console.log("Using HLS");
                        } else {
                            mediaUrl = defaultUrl;
                        }
                    }

                    // create channel
                    var channel = {
                        id: cl.ChannelID || cl.ChannelNo,
                        title: cl.ChannelName,
                        type: 'content',
                        channelType: 'livechannel',
                        channelNo: dataUtil.isNumber(cl.ChannelNo) ? parseInt(cl.ChannelNo) : '',
                        channelCode: cl.ChannelCode,
                        position: i,
                        parentChannelId: 0,
                        groupId: g.id,
                        description: cl.Description,
                        logoUrl: logoUrl,
                        epg: [],
                        assets: [
                            {id: assetRunningId++, type: 'thumbnail', position: 0, mediaType: 'image', mediaUrl: thumbnailUrl},
                            {id: assetRunningId++, type: 'poster', position: 0, mediaType: 'image', mediaUrl: thumbnailUrl},
                            {id: assetRunningId++, type: 'banner', position: 0, mediaType: 'image', mediaUrl: thumbnailUrl},
                            {id: assetRunningId++, type: 'content', position: 0, mediaType: 'video', mediaUrl: mediaUrl},
                        ],
                        parentName: g.name,
                        subChannels: [],
                        iconColor: asset.IconColor
                    };

                    // If no ID, we need to assign one
                    if (channel.id == null) {
                        channel.id = uuid.v1();
                    }

                    // add channel to group
                    g.channels.push(channel);

                    // add channel to play channel array
                    play.channels.push(channel);

                    // parse epg
                    if (cl.Schedules.length > 0) {
                        createProgramGuide = true;
                        // get the epg url
                        var sched = cl.Schedules[0];
                        var url = vodPath + cl.epgID + '/' + cl.ChannelID + '/' + sched.FileName;

                        // parse the epg script
                        epgPromises.push(parseEPG(channel.id, url));
                    }
                }

                if (g.channels.length > 0) {
                    g.channels = $filter('orderBy')(g.channels, 'title');
                    play.groups.push(g);
                }
            }

            // when all epg parsing is complete, push to channel epgs
            $q.all(epgPromises).then(function(results) {
                var foundEpg = false;
                for (var i = 0; i < results.length; i++) {
                    var epg = results[i];
                    if (epg && epg.length > 0) {
                        var cId = epg[0].channelId;
                        for (var k = 0; k < play.channels.length; k++) {
                            if (play.channels[k].id == cId) {
                                play.channels[k].epg = epg;
                                foundEpg = true;
                                break;
                            }
                        }
                    }
                }

                // create program guide channel if epg is available
                if (createProgramGuide && foundEpg) {
                    var epgChannel = {
                        id: -1, title: 'Program Guide', type: 'epg', position: 0, parentChannelId: 0, groupId: g.id, description: 'Program Guide',
                        assets: [
                            {id: assetRunningId++, type: 'thumbnail', position: 0, mediaType: 'image', mediaUrl: 'assets/images/program-guide.png'},
                            {id: assetRunningId++, type: 'content', position: 0, mediaType: 'image', mediaUrl: 'assets/images/program-guide.png'}
                        ],
                        subChannels: []
                    };
                    g.channels.unshift(epgChannel);
                    play.channels.unshift(epgChannel);
                }

                deferred.resolve(play);
            }, function(err) {
                deferred.resolve(play);
            });

            return deferred.promise;
        }

        // parse Live Channel EPG
        function parseEPG(channelId, epgUrl) {
            var deferred = $q.defer();
            var onContentPathResolved = function(path) {
                // read the script and parse the epg
                var fileReader = new FileReader();
                fileReader.read(path).then(function(o) {
                    deferred.resolve(doParseEPG(channelId, o));
                }, deferred.reject);
            };

            var onRouteResolved = function(url) {
                halMediaService.resolveContentPath(url).then(onContentPathResolved);
            };

            // resolve the route
            networkContentRouterService.resolve(epgUrl, 'play-content-routing-service')
                .then(onRouteResolved, deferred.reject);

            return deferred.promise;
        }

        function doParseEPG(channelId, program) {
            var epg = [];
            if (program) {
                for (var i = 0; i < program.length; i++) {
                    var s = program[i];
                    var p = {id: channelId + '_' + i, channelId: channelId, title: s.title, desc: s.desc, start: s.start, stop: s.stop};
                    epg.push(p);
                }
            }

            return epg;
        }

        // parse the OnDemandList
        function doParseOnDemandList(o) {
            var play = {groups: [], channels: []};
            if (o.OnDemandList) {
                // Parse Channel Group
                for (var i = 0; i < o.OnDemandList.length; i++) {
                    var odl = o.OnDemandList[i];

                    var id = (odl.Id) ? odl.Id : odl.OnDemandID;

                    // create group
                    var group = {id: id, name: odl.OnDemandName, position: i, channels: []};

                    if (odl.OnDemandFullPath) {
                        group.fullPath
                            = odl.OnDemandFullPath.substring(0, odl.OnDemandFullPath.length - 1);
                    }

                    var position = 0;
                    // Parse channels
                    if (odl.AssetScriptsItems) {
                        for (var k = 0; k < odl.AssetScriptsItems.length; k++) {
                            // create channel
                            var channel = createChannel(
                                odl.AssetScriptsItems[k],
                                position++,
                                0,
                                group.id,
                                group.fullPath,
                                odl.OnDemandName);
                            if (channel) {
                                // add channel to group
                                group.channels.push(channel);

                                // add channel to play channel array
                                play.channels.push(channel);
                            }
                        }
                    }

                    // Parse folders
                    if (odl.OnDemandItems) {
                        for (var l = 0; l < odl.OnDemandItems.length; l++) {
                            var odItem = odl.OnDemandItems[l];

                            // create folder
                            var f = createFolder(
                                odItem,
                                position++,
                                0,
                                group.id,
                                play.channels,
                                odl.OnDemandName);
                            if (f) {
                                if (odItem.OnDemandFullPath) {
                                    f.fullPath
                                        = odItem.OnDemandFullPath.substring(
                                            0,
                                            odItem.OnDemandFullPath.length - 1);
                                }

                                // Convert folder to playlist
                                if (odItem.IsPlaylist) {
                                    convertFolderToPlaylist(odItem, f);
                                }

                                // add channel to group
                                group.channels.push(f);

                                // add channel to play channel array
                                play.channels.push(f);
                            }
                        }
                    }

                    // sort group's channels alphabetically
                    group.channels = $filter('orderBy')(group.channels, 'title');

                    // validate that channels are not empty
                    if (group.channels.length > 0) {
                        play.groups.push(group);
                    }
                }
            }

            return play;
        }

        function convertFolderToPlaylist(channel, folder) {
            // Check if this channel is a playlist
            if (!channel.IsPlaylist) {
                return;
            }

            folder.type = 'playlist';
            channel.Playout = channel.Playout || {};
            folder.defaultContentDuration = channel.Playout.Duration || util.convertRawDurationToSeconds(channel.PlaylistDuration) || 12;
            var transition = util.toTransitionObject(channel.Playout.Transition);
            folder.enterTransition = transition.enter;
            folder.exitTransition = transition.exit;
            folder.aspectRatio = util.toAspectRatio(channel.Playout.AspectRatio);
        }

        function createChannel(o, position, parentId, groupId, fullPath, parentName) {
            // generate mediaurl
            var createDateStamp = o.ContentCreatedDate;
            // if script doesn't give any info about updated date, use the created date
            var updateDateStamp = o.ContentUpdatedDate || o.ContentCreatedDate || null;
            var updateDateStampTicks
                = (updateDateStamp) ? new Date(updateDateStamp).getTime() : null;
            var contentType = o.ContentType.toLowerCase();
            var mediaUrl;
            var thumbnailUrl;
            var type = CHANNEL_TYPE.CONTENT;
            if (contentType == CONTENT_TYPE.SIGNS) {
                type = CHANNEL_TYPE.SIGN;
                mediaUrl = channelUtil.generateSignsPreviewURL(o.ContentSourceID, CONTENT_ACCESS_CONTEXT.CHANNEL_BROWSING);
                thumbnailUrl = generateThumbnailUrl(o.ContentThumbnailMediaId);
            } else {
                mediaUrl = generateMediaUrl(o.ContentMediaPath, CONTENT_ACCESS_CONTEXT.CHANNEL_BROWSING);
                thumbnailUrl = generateThumbnailUrl(o.Id || o.ContentSourceID, updateDateStampTicks);
            }

            var playout = o.Playout || {};
            playout.Validity = playout.Validity || {};
            playout.Daypartings = playout.Daypartings || [];
            var transition = util.toTransitionObject(playout.Transition);
            // create channel
            var channel = {
                id: o.ContentSourceID,
                contentId: o.Id,
                title: o.ContentName,
                type: type,
                date: createDateStamp,
                updateddate: updateDateStamp,
                position: position,
                parentChannelId: parentId,
                groupId: groupId,
                description: o.ContentDescription,
                parentName: parentName,
                tags: o.ContentTags,
                assets: [
                    {
                        id: assetRunningId++,
                        type: 'thumbnail',
                        position: 0,
                        mediaType: 'image',
                        mediaUrl: thumbnailUrl
                    },
                    {
                        id: assetRunningId++,
                        type: 'poster',
                        position: 0,
                        mediaType: 'image',
                        mediaUrl: thumbnailUrl
                    },
                    {
                        id: assetRunningId++,
                        type: 'banner',
                        position: 0,
                        mediaType: 'image',
                        mediaUrl: thumbnailUrl
                    },
                    {
                        id: assetRunningId++,
                        type: 'content',
                        position: 0,
                        mediaType: contentType,
                        mediaUrl: mediaUrl
                    },
                ],
                subChannels: [],
                playout: {
                    start: util.toDate(playout.Validity.Start),
                    end: util.toDateCeiling(playout.Validity.End),
                    duration: playout.Duration,
                    aspectRatio: util.toAspectRatio(playout.AspectRatio),
                    enterTransition: transition.enter,
                    exitTransition: transition.exit,
                    daypartings: parseDaypartings(playout.Daypartings),
                },
                iconColor: o.IconColor
            };
            if (fullPath) {
                channel.fullPath = fullPath + '/' + o.ContentName;
            }

            return channel;
        }

        function parseDaypartings(daypartings) {
            var result = [];
            if (!daypartings || !daypartings.length) {
                return result;
            }

            // go through each dayparting and group it by days
            for (var i = 0; i < daypartings.length; i++) {
                var dayparting = daypartings[i];
                for (var j = 0; j < dayparting.Days.length; j++) {
                    var day = dayparting.Days[j];
                    var dayIndex = DAY_PART_INDEX[day.toLowerCase()];
                    result.push(util.convertDaypartToSchedule(
                        dayparting.StartTime,
                        dayparting.EndTime,
                        dayIndex
                    ));
                }
            }

            return result;
        }

        function processSvgThumbnail(mediaUrl) {
            if (!mediaUrl) {
                return;
            }

            var contentExtension = FileUtil.getFileExtension(mediaUrl.toLowerCase());
            return contentExtension == 'svg' ? generateMediaUrl(mediaUrl) : undefined;
        }

        function createFolder(o, folderPosition, parentId, groupId, channelList, parentName) {
            var position = 0;
            var defaultSequence = MAPPING_SEQUENCEYBY[o.SequenceBy] == undefined ?
                MAPPING_SEQUENCEYBY[1] : MAPPING_SEQUENCEYBY[o.SequenceBy];
            // Parse subchannels
            var subChannels = [];
            if (o.AssetScriptsItems) {
                for (var k = 0; k < o.AssetScriptsItems.length; k++) {
                    var fullpath = null;
                    if (o.OnDemandFullPath) {
                        fullpath = o.OnDemandFullPath.substring(0, o.OnDemandFullPath.length - 1);
                    }

                    // create channel
                    var c = createChannel(
                        o.AssetScriptsItems[k],
                        position++,
                        o.OnDemandID,
                        0,
                        fullpath,
                        o.OnDemandName);
                    c.defaultSequence = defaultSequence;
                    if (c) {
                        // add channel to channel array
                        channelList.push(c);

                        // add channel to folder subChannels
                        subChannels.push(c);
                    }
                }
            }

            // parse sub-folders
            if (o.OnDemandItems) {
                for (var l = 0; l < o.OnDemandItems.length; l++) {
                    var odItem = o.OnDemandItems[l];
                    // create folder
                    var f = createFolder(
                        odItem,
                        position++,
                        o.OnDemandID,
                        0,
                        channelList,
                        odItem.OnDemandName);

                    if (f) {
                        f.parentName = parentName;
                        // Convert folder to playlist
                        if (odItem.IsPlaylist) {
                            convertFolderToPlaylist(odItem, f);
                        }

                        // add folder to channel array
                        channelList.push(f);

                        // add folder to subChannels
                        subChannels.push(f);
                    }
                }
            }

            if (subChannels.length == 0) {
                return null;
            }

            // sort subChannels based on default sequence
            subChannels = $filter('orderBy')(subChannels, defaultSequence);

            var getFirstThumbnail = function(channels) {
                for (var i = 0; i < channels.length; i++) {
                    var channel = channels[i];
                    for (var k = 0; k < channel.assets.length; k++) {
                        var asset = channel.assets[k];
                        if (asset.type === 'thumbnail' && asset.mediaUrl) {
                            return processSvgThumbnail(asset.mediaUrl) || asset.mediaUrl;
                        }
                    }
                }
            };

            var getAssetThumbnail = function(assetInfo) {
                if (!assetInfo || !dataUtil.isMoreThanZero(assetInfo.ContentSourceID)) {
                    return;
                }

                var media = processSvgThumbnail(assetInfo.ContentMediaPath) ||
                    generateThumbnailUrl(assetInfo.ContentSourceID, undefined, undefined, assetInfo.ContentThumbnailRelativeUrl);
                thumbnails[media] = true;
                return media;
            };

            var assetInfo = o.AssetInfo || {};
            // check if channel have its own icon
            var thumbnailUrl = getAssetThumbnail(assetInfo) || getFirstThumbnail(subChannels);

            // Get date for the channel
            var date = new Date().toUTCString();
            if (o.AssetScriptsItems && o.AssetScriptsItems.length > 0) {
                date = o.AssetScriptsItems[o.AssetScriptsItems.length - 1].ContentCreatedDate;
            }

            var folder = {
                id: o.OnDemandID,
                channelGuid: o.ChannelId,
                title: o.OnDemandName,
                type: 'folder',
                date: date,
                position: folderPosition,
                parentChannelId: parentId,
                groupId: groupId,
                description: o.OnDemandDescription,
                parentName: parentName,
                defaultSequence: defaultSequence,
                assets: [
                    {id: assetRunningId++, type: 'thumbnail', position: 0, mediaType: 'image', mediaUrl: thumbnailUrl}
                ],
                subChannels: subChannels,
                iconColor: assetInfo.IconColor
            };

            return folder;
        }

        function generateMediaUrl(url, context) {
            if (!url) {
                return '';
            }

            var localRoot = 'C:\\FastNetV2\\';
            // remove C:\FastNetV2\
            var mediaUrl = null;
            var index = url.indexOf(localRoot);
            if (index > -1) {
                mediaUrl = url.substr(index + localRoot.length);
            }

            // replace \ with /
            mediaUrl = mediaUrl.split('\\').join('/');
            mediaUrl = rootPath + 'fastnetdata/' + mediaUrl;
            if (context){
                mediaUrl += '?context=' + context;
            }


            return mediaUrl;
        }

        function generateThumbnailUrl(id, timestamp, quality, relativeUrl) {
            if (!id && !relativeUrl) {
                return '';
            }

            var previewUrl = mediaUtil.getContentPreviewUrlFromRelativePath(relativeUrl);
            if (!previewUrl) {
                quality = quality || 'mid';
                previewUrl = mediaUtil.getContentPreviewUrlFromId(id, quality, timestamp);
            }

            thumbnails[previewUrl] = true;
            return previewUrl;
        }

        // Return the constructor for the class
        return (VODAppParser);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    // Parses AZ into entity
    app.factory('WeatherInfoParser', ["$q", "CSVParser", function($q, CSVParser) {
        // Define the constructor function.
        function WeatherInfoParser() {

        }

        // Define the instance methods and properties
        WeatherInfoParser.prototype = {
            // Public interface
            type: 'signs.parser.weatherinfo',
            parse: parse
        };

        // Schedule a deferred parsing of the object
        function parse(o) {
            // promise object for async response
            var deferred = $q.defer();
            doParse(o, deferred);
            return deferred.promise;
        }

        // Actual parsing of object
        function doParse(scriptBlock, promise) {
            // Parser the csv string into an array of table objects - {type: [tableName], items: []}
            var csvParser = new CSVParser();
            // On parse we
            var onCSVParsed = function(o) {
                // Parse the CSV objects
                var entity = parseCsvObject(o);
                // Wrap the response in a standard format
                promise.resolve({id: scriptBlock.id, type: scriptBlock.type, weather: entity});
            };

            csvParser.parse(scriptBlock.script).then(onCSVParsed, function(err) {
                promise.reject(err);
            });
        }

        // Local variables
        var _wSubscriptions = [];
        var _wEntries = [];
        var _wAssets = [];
        var _wGraphics = [];

        // Process the CSV Obect and return a weather object
        function parseCsvObject(o) {
            // iterate through the table array and extract the tables
            for (var i = 0; i < o.length; i++) {
                switch (o[i].type) {
                    case 'weathersubscriptions':
                        _wSubscriptions = o[i].items;
                        break;
                    case 'weatherentries':
                        _wEntries = o[i].items;
                        break;
                    case 'weatherassets':
                        _wAssets = o[i].items;
                        break;
                    case 'weathergraphics':
                        _wGraphics = o[i].items;
                        break;
                }
            }

            // create weather object
            var weather = {
                subscriptions: createSubscriptions(),
                assets: createWeatherAssets(),
                graphics: createWeatherGraphics()
            };

            return weather;
        }

        // create subscription objects
        function createSubscriptions() {
            var subscriptions = [];

            // iterate through the subscription array
            for (var i = 0; i < _wSubscriptions.length; i++) {
                var s = _wSubscriptions[i];
                // create a subscription object and add to the array
                var o = {
                    id: parseInt(s.Id),
                    location: convertIntoObject(s.Location),
                    forecast: getForecast(s.Id)
                };
                subscriptions.push(o);
            }
            // return the object
            return subscriptions;
        }

        // get forecast objects for subscription
        function getForecast(subscriptionId) {
            var forecast = [];

            // iterate through the weather entry array
            for (var i = 0; i < _wEntries.length; i++) {
                var e = _wEntries[i];

                // get forecast for the subscription
                if (e.WeatherSubscriptionId == subscriptionId) {
                    // create a forecast object and add it to the forecast array
                    var o = {
                        location: e.Location,
                        date: e.Date,
                        temperature: parseFloat(e.Temperature),
                        weatherCode: parseInt(e.WeatherCode),
                        weatherCondition: e.WeatherCondition,
                        weatherData: convertIntoObject(e.WeatherData)
                    };
                    forecast.push(o);
                }
            }
            return forecast;
        }

        // create weather asset objects
        function createWeatherAssets() {
            var assets = [];

            // iterate through the weather asset array
            for (var i = 0; i < _wAssets.length; i++) {
                var s = _wAssets[i];

                // create a weather asset object and add it to the array
                var o = {
                    id: parseInt(s.Id),
                    name: s.Name,
                    position: parseInt(s.Position),
                    configuration: s.Configuration,
                    type: s.Type,
                    mediaFile: s.mediaFile
                };
                assets.push(o);
            }
            // return the object
            return assets;
        }

        // create weather graphics objects
        function createWeatherGraphics() {
            var graphics = [];

            // iterate through the weather graphics array
            for (var i = 0; i < _wGraphics.length; i++) {
                var s = _wGraphics[i];

                // create a weather graphic object and add it to the array
                var o = {
                    id: parseInt(s.Id),
                    weatherCode: parseInt(s.WeatherCode),
                    type: s.Type,
                    mediaFile: s.mediaFile,
                };
                graphics.push(o);
            }
            // return the object
            return graphics;
        }

        // convert properties separated by semicolon into object
        function convertIntoObject(source) {
            var o = new Object();

            if (source && source.length > 0) {
                // first split by ';'
                var props = source.split(';');

                // iterate through each property and get the key and value
                for (var i = 0; i < props.length; i++) {
                    var p = props[i];
                    var kvp = p.split('=');
                    // check that it is a valid keyvalue pair
                    if (kvp.length > 1 && kvp[0].length > 0) {
                        // set the key and value as the object property
                        o[kvp[0]] = kvp[1];
                    }
                }
            }
            return o;
        }

        // Return the constructor for the class
        return (WeatherInfoParser);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    // Parses AZ into entity
    app.factory('WebWpgParser', ["$q", "CSVParser", function($q, CSVParser) {
        // Define the constructor function.
        function WebWpgParser() {

        }

        // Define the instance methods and properties
        WebWpgParser.prototype = {
            // Public interface
            type: 'signs.parser.web-wpg',
            parse: parse
        };

        // Schedule a deferred parsing of the object
        function parse(o) {
            // promise object for async response
            var deferred = $q.defer();
            doParse(o, deferred);
            return deferred.promise;
        }

        // Actual parsing of object
        function doParse(scriptBlock, promise) {
            // Parser the csv string into an array of table objects - {type: [tableName], items: []}
            var csvParser = new CSVParser();
            // On parse we
            var onCSVParsed = function(o) {
                // Parse the CSV objects
                var entity = parseCsvObject(o);
                // Wrap the response in a standard format
                promise.resolve({webItem: entity});
            };

            // insert table index into the first line of the script
            var script = 'web=1\n' + scriptBlock;

            // Parse CSV
            csvParser.parse(script).then(onCSVParsed, function(err) {
                promise.reject(err);
            });
        }

        // Process the CSV Obect and return web item array
        function parseCsvObject(o) {
            // Get the table
            var source = [];
            for (var i = 0; i < o.length; i++) {
                if (o[i].type == 'web') {
                    source = o[i].items;
                }
            }

            // Process web item - only one row ever
            for (var k = 0; k < source.length; k++) {
                var s = source[k];

                // Create web object
                var item = {
                    id: parseInt(s.id),
                    url: s.url,
                    useDuration: s.useduration == 'True',
                    duration: parseFloat(s.duration),
                    mode: s.mode
                };

                return item;
            }
        }

        // Return the constructor for the class
        return (WebWpgParser);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    // Parses AZ into entity
    app.factory('WorkbookDatatableParser', ["$q", "x2js", "Utils", function($q, x2js, Utils) {
        // Instantiate the utils class
        var utils = new Utils();

        // Define the constructor function.
        function WorkbookDatatableParser() {

        }

        // Define the instance methods and properties
        WorkbookDatatableParser.prototype = {
            // Public interface
            type: 'signs.parser.workbook-datatable',
            parse: parse
        };

        // Schedule a deferred parsing of the object
        function parse(o) {
            // promise object for asyn response
            var deferred = $q.defer();
            doParse(o, deferred);
            return deferred.promise;
        }

        // Actual parsing of object
        function doParse(scriptBlock, promise) {
            // Parse the xml string
            var res = parseXml(scriptBlock);
            // Wrap the response in a standard format
            promise.resolve({sheets: res});
        }

        // Parse XML and return an entity
        function parseXml(xmlText) {
            if (!xmlText || xmlText.length == 0) {
                return null; // Empty
            }

            // Convert xml to a json object so we can parse
            var o = x2js.xml_str2json(xmlText);

            // Check for valid workbook xml
            if (!o.sheets) {
                return null;
            }

            var sheets = o.sheets.sheet;

            // if number of sheet is 1, "sheest" value will be an object
            // change it to array
            if (!(sheets instanceof Array)) {
                sheets = [sheets];
            }

            var tempSheets = [];

            for (var i = 0; i < sheets.length; i++) {
                var sheet = sheets[i];
                // sheet.row

                var rows = sheet.row;
                // if length of rows is 1, "rows" value will be an object
                // change it to array
                if (!(rows instanceof Array)) {
                    rows = [rows];
                }

                var tempRows = [];
                if (!rows) {
                    continue;
                }
                for (var j = 0; j < rows.length; j++) {
                    var row = rows[j];
                    var tempRow = {};
                    if (!row || (row && !row.cell)) {
                        continue;
                    }
                    if (row == '') {
                        // empty
                    } else {
                        var c_counter = 0;

                        if (!row.cell.length) {
                            row.cell = [row.cell];
                        }

                        for (var k = 0; k < row.cell.length; k++) {
                            var column = row.cell[k];
                            var rpos = parseInt(column._rpos);

                            for (var l = 0; l < rpos - 1; l++) {
                                c_counter++;
                                tempRow['c' + c_counter] = '';
                            }

                            c_counter++;
                            tempRow['c' + c_counter] = column.__text;
                        }
                    }
                    tempRows.push(tempRow);
                }
                tempSheets.push({name: sheet._name, rows: tempRows});
            }

            return tempSheets;
        }

        function parseSheet(e) {
            var sheet = {name: e._name, rows: new Object()};

            var rows = e.row;
            if (rows) {
                // Convert row into an array if only one item
                if (!(rows instanceof Array)) {
                    rows = [e.row];
                }

                // set max number of columns
                var numOfCols = 0;
                for (var i = 0; i < rows.length; i++) {
                    var pos = getMaxColumnPosition(rows[i]) + 1; // add 1 to index to get length
                    if (pos > numOfCols) {
                        numOfCols = pos;
                    }
                }

                // Iterate through the rows and parse into a row object
                for (var i = 0; i < rows.length; i++) {
                    var rowObject = parseRow(rows[i], numOfCols);

                    // fill in the rest of the empty columns
                    for (var k = 0; k < numOfCols; k++) {
                        if (rowObject[k] === undefined) {
                            rowObject[k] = {type: null, value: null};
                        }
                    }

                    // use the row index as the object property to store the row object
                    sheet.rows[i + 1] = rowObject;
                }
            }

            return sheet;
        }

        function getMaxColumnPosition(r) {
            var maxPos = 0;
            var cells = r.cell;
            if (cells) {
                // Convert cell into an array if only one item
                if (!(cells instanceof Array)) {
                    cells = [r.cell];
                }
                for (var i = 0; i < cells.length; i++) {
                    var c = cells[i];
                    var colIndex = parseInt(c._rpos);
                    if (colIndex > maxPos) {
                        maxPos = colIndex;
                    }
                }
            }

            return maxPos;
        }

        function parseRow(r) {
            var row = new Object();

            var cells = r.cell;
            if (cells) {
                // Convert cell into an array if only one item
                if (!(cells instanceof Array)) {
                    cells = [r.cell];
                }
                // Iterate through the cells and use the cell index as the object property to store the cell value in
                for (var i = 0; i < cells.length; i++) {
                    var c = cells[i];
                    var colIndex = parseInt(c._rpos);

                    row[colIndex] = {type: c._type, value: c.__text};
                }
            }

            return row;
        }

        // Return the constructor for the class
        return (WorkbookDatatableParser);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    // Parses AZ into entity
    app.factory('WorkbookParser', ["$q", "CSVParser", function($q, CSVParser) {
        // Define the constructor function.
        function WorkbookParser() {

        }

        // Define the instance methods and properties
        WorkbookParser.prototype = {
            // Public interface
            type: 'signs.parser.workbook',
            parse: parse
        };

        // Schedule a deferred parsing of the object
        function parse(o) {
            // promise object for async response
            var deferred = $q.defer();
            doParse(o, deferred);
            return deferred.promise;
        }

        // Actual parsing of object
        function doParse(scriptBlock, promise) {
            // Parser the csv string into an array of table objects - {type: [tableName], items: []}
            var csvParser = new CSVParser();
            // On parse we
            var onCSVParsed = function(o) {
                // Parse the CSV objects
                var wb = parseCsvObject(o);
                // Wrap the response in a standard format
                promise.resolve({id: scriptBlock.id, type: scriptBlock.type, workbooks: wb});
            };

            csvParser.parse(scriptBlock.script).then(onCSVParsed, function(err) {
                promise.reject(err);
            });
        }

        // Local variables
        var _workbook = [];
        var _wbinstance = [];
        // var _wbschema = [];

        // Process the CSV Obect returned into a workbook collection
        function parseCsvObject(o) {
            var workbooks = [];

            // iterate through the table array and extract the tables
            for (var i = 0; i < o.length; i++) {
                switch (o[i].type) {
                    case 'workbook':
                        _workbook = o[i].items;
                        break;
                    case 'workbookinstance':
                        _wbinstance = o[i].items;
                        break;
                    case 'workbookschema':
                        // _wbschema = o[i].items;
                        break;
                }
            }

            // iterate through the workbook object
            for (var i = 0; i < _workbook.length; i++) {
                var s = _workbook[i];

                // create a workbook object and add it to the workbook array
                var w = {
                    id: parseInt(s.id),
                    name: s.name,
                    currentInstance: {},
                    workbookInstances: getWorkbookInstances(s.id)
                };

                // find the current instance and set it
                for (var j = 0; j < w.workbookInstances.length; j++) {
                    var wi = w.workbookInstances[j];
                    if (wi.id == s.currentinstanceid) {
                        w.currentInstance = wi;
                        break;
                    }
                }
                // add the workbook object to the array
                workbooks.push(w);
            }

            return workbooks;
        }

        // get workbookInstances for the workbook
        function getWorkbookInstances(workbookId) {
            var wbi = [];
            // Iterate through the workbookInstances and find ones for the workbook
            for (var i = 0; i < _wbinstance.length; i++) {
                var w = _wbinstance[i];
                // find the workbookInstance from the wbInstance table and create a workbookInstance object
                if (w.workbookdatasourceid === workbookId) {
                    var o = {
                        id: parseInt(w.id),
                        createdDate: w.createdat,
                        filename: w.filename
                    };
                    // insert the workbookinstance object into an array
                    wbi.push(o);
                }
            }

            // return the media array
            return wbi;
        }

        // Return the constructor for the class
        return (WorkbookParser);
    }]);
})(window.angular);

(function($angular) {
  'use strict';

  var app = $angular.module('signsHTML5');

  // Reads file into string
  app.factory('FileReader', ["$window", "$timeout", "$q", "$http", "halFileService", function($window, $timeout, $q, $http, halFileService) {
    // Define the constructor function.
    function FileReader() {

    }

    // Define the instance methods and properties
    FileReader.prototype = {
      // Public interface
      read: read
    };

    // Schedule a deferred reading of the file
    function read(url) {
      return halFileService.readFile(url);
    }

    // Return the constructor for the class
    return (FileReader);
  }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    // Reads file into string
    app.factory('Utils', ["coreConfigurationService", "dataUtil", "TRANSITION", "mediaUtil", function(coreConfigurationService, dataUtil, TRANSITION, mediaUtil) {
        // Define the constructor function.
        function Utils() {

        }

        // Define the instance methods and properties
        Utils.prototype = {
            // Public interface
            toCamelCase: toCamelCase,
            toAspectRatio: toAspectRatio,
            toTransition: toTransition,
            toTransitionObject: toTransitionObject,
            convertStringVal: convertStringVal,
            getContentPreviewUrl: getContentPreviewUrl,
            retrieveCardsContentIds: retrieveCardsContentIds,
            retrieveInfoFromRawContentPath: retrieveInfoFromRawContentPath,
            convertRawDurationToSeconds: convertRawDurationToSeconds,
            toDate: toDate,
            toDateCeiling: toDateCeiling,
            hasNaturalDuration: hasNaturalDuration,
            convertDaypartToSchedule: convertDaypartToSchedule
        };

        // Get the ugID and the Content Id from raw path
        function retrieveInfoFromRawContentPath(path) {
            var res = {
                ugId: 0,
                contentId: 0,
                rootFolder: ''
            };
            var checkPath = path.toLowerCase();
            // example : C:\FastNetV2\UG Media\229\768078\
            if (checkPath.indexOf('c:\\fastnetv2\\ug media\\') >= 0) {
                var paths = checkPath.split('\\');
                if (paths.length >= 5) {
                    res = {
                        ugId: parseInt(paths[3], 10),
                        contentId: parseInt(paths[4], 10),
                        rootFolder: (paths.length == 5)
                    };
                }
            }

            return res;
        }

        // Get the content Id for cards from file listing blocks
        function retrieveCardsContentIds(contents) {
            var res = {};
            for (var k = 0; k < contents.length; k++) {
                var content = contents[k];
                var pathInfo = retrieveInfoFromRawContentPath(content.path);
                // if we already process this content, skip
                if (res[pathInfo.contentId.toString()]) {
                    continue;
                }

                var filename = content.fileName.toLowerCase();
                // if manifest.json exists in the root folder, consider it as card
                if (filename.indexOf('manifest.json') >= 0 && pathInfo.rootFolder) {
                    res[pathInfo.contentId.toString()] = true;
                }
            }

            return res;
        }

        // Get the preview handler URL for a content item
        function getContentPreviewUrl(contentUrl, previewType, uniqueId) {
            // appspace.com\fastnet\ug media\UGID\CONTENTID\FILENAME.jpg
            var url = contentUrl.toLowerCase();
            var segs = url.split('\\ug media\\');
            if (segs.length != 2) {
                return contentUrl;
            }

            // ug media\UGID\CONTENTID\FILENAME.jpg
            segs = segs[1].split('\\');
            if (segs.length < 2) {
                return contentUrl;
            }

            // Get the content id
            var contentId = segs[1];
            // Get appspace url and create root path
            var rootPath = coreConfigurationService.get('appspace', 'appspaceURL') + '/app/';
            // Get preview type
            previewType = mediaUtil.convertPreviewTypeToHandlerValue(previewType);
            // create the URL
            url = rootPath + 'imagepreview.axd?mid=' + contentId + '&profileName=' + previewType;
            if (uniqueId) {
                url += '&_uniqueId=' + uniqueId;
            }

            // console.log(url);
            return url;
        }

        // Convert string to camelCase
        function toCamelCase(str) {
            if (str.charAt(0) == '_') {
                str = str.slice(1);
            }

            return str.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, function(match, index) {
                if (+match === 0) return ''; // or if (/\s+/.test(match)) for white spaces
                return index == 0 ? match.toLowerCase() : match.toUpperCase();
            });
        }

        // Convert string value to the correct datatype
        function convertStringVal(val) {
            var newVal = null;
            if (val) {
                // check for nil value
                for (var key in val) {
                    if (key.toLowerCase().indexOf(':nil') > 0 && val[key].toLowerCase() == 'true') {
                        return newVal;
                    }
                }

                // check if it  a number
                if (!isNaN(val)) {
                    newVal = 1 * val;
                } else if (val.toLowerCase() == 'true') {// check if it is a bool - true
                    newVal = true;
                } else if (val.toLowerCase() == 'false') { // check if it is a bool - false
                    newVal = false;
                } else { // default, return the original value
                    newVal = val;
                }
            }

            return newVal;
        }

        var aspectRatioMapping = {
            content: {
                '0': 'fill',
                '1': 'uniform',
                '2': 'fill',
                '3': 'uniformfill'
            },
            widget: {
                '0': 'fill',
                '1': 'fill',
                '2': 'uniform',
                '3': 'uniformfill'
            }
        };
        function toAspectRatio(ar, type) {
            if (dataUtil.isNullOrUndefined(ar)) {
                return null;
            }

            var mapping = (type || '').toLowerCase() == 'widget' ? aspectRatioMapping['widget'] : aspectRatioMapping['content'];
            return mapping[ar.toString()] || null;
        }

        var transitionMapping = {
            '0': TRANSITION.CROSS_FADE, // Fade
            '1': TRANSITION.LEFT_SWIPE, // Left Swipe
            '2': TRANSITION.RIGHT_SWIPE, // Right Swipe
            '3': TRANSITION.TOP_SWIPE, // Top Swipe
            '4': TRANSITION.BOTTOM_SWIPE, // Bottom Swipe
            '5': TRANSITION.CENTER_HORIZONTAL_EXPAND, // Center Horizontal Expand
            '6': TRANSITION.CENTER_VERTICAL_EXPAND, // Center Vertical Expand
            '7': TRANSITION.ROTATE_LEFT, // Center Rotate Fade
            '8': TRANSITION.CROSS_FADE, // Fade Equal
            '9': TRANSITION.FADE_IN, // Fade Fully
            '10': TRANSITION.LEFT_SWIPE_FADE, // LeftSwipeFade
            '11': TRANSITION.RIGHT_SWIPE_FADE, // Right Swipe Fade
            '12': TRANSITION.LEFT_SWITCH, // Left Switch
            '13': TRANSITION.RIGHT_SWITCH, // Right Switch
            '14': TRANSITION.TOP_SWITCH, // Top Switch
            '15': TRANSITION.BOTTOM_SWITCH, // Bottom Switch
            '16': TRANSITION.LEFT_PUSH, // Left Push
            '17': TRANSITION.RIGHT_PUSH, // Right Push
            '18': TRANSITION.ZOOM_IN_FROM_BACK, // Zoom In From Back
            '19': TRANSITION.ZOOM_IN_FROM_FRONT, // Zoom In From Front
            '20': TRANSITION.TOP_PUSH, // Top Push
            '21': TRANSITION.BOTTOM_PUSH, // Bottom Push
            '22': TRANSITION.CUBE_LEFT, // Cube Left
            '23': TRANSITION.CUBE_RIGHT, // Cube Right
            '24': TRANSITION.CUBE_UP, // Cube Up
            '25': TRANSITION.CUBE_DOWN, // Cube Down
            '26': TRANSITION.CUBE_RANDOM, // Cube Random
            '201': TRANSITION.ROTATE_LEFT, // Rotate Left
            '203': TRANSITION.ROTATE_RIGHT, // Rotate Right
            '404': TRANSITION.NONE, // No Playlist Transition
            '-1': TRANSITION.NOT_SET // No Playlist Transition
        };

        function toTransition(t) {
            if (dataUtil.isNullOrUndefined(t)) {
                return null;
            }

            var stringififiedTransition = t.toString();
            var mappedValue = transitionMapping[stringififiedTransition];
            return mappedValue === undefined ? stringififiedTransition : mappedValue;
        }

        function toTransitionObject(transitionRaw) {
            var transition = toTransition(transitionRaw);
            if (!transition) {
                return {};
            }

            var splits = transition.split(',');
            return {enter: splits[0].trim(), exit: splits[1].trim()};
        }

        function convertRawDurationToSeconds(raw) {
            if (!raw) {
                return 0;
            }

            var match = raw.match(/(\d+):(\d{1,2})/);
            if (!match) {
                return 0;
            }

            return (parseInt(match[1], 10) * 60) + parseInt(match[2], 10);
        }

        function toDate(value, ceil) {
            if (!value) {
                return;
            }

            return moment(value).seconds(ceil ? 59 : 0).toDate();
        }

        function toDateCeiling(value) {
            return toDate(value, true);
        }

        function hasNaturalDuration(data) {
            var naturalDuration = parseInt(data, 10);
            return !isNaN(naturalDuration) && naturalDuration >= 0;
        }

        function convertDaypartToSchedule(startTime, endTime, dayIndex) {
            var DAY_PART_TIME_FORMAT = 'hh:mm:ss';
            return {
                timespans: [{
                    startTime: moment(startTime, DAY_PART_TIME_FORMAT).toDate(),
                    endTime: moment(endTime, DAY_PART_TIME_FORMAT).toDate()
                }],
                day: dayIndex
            };
        }

        // Return the constructor for the class
        return (Utils);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('asSignsDateTimeWidget', ["FontUtil", "DateTimeUtil", function(FontUtil, DateTimeUtil) {
        return {
            restrict: 'EA',
            scope: {
                id: '@',
                datasource: '=',
                configuration: '=',

                onApiReady: '&',
                onFreeze: '&',
                onUnFreeze: '&',
            },

            template: '<div class="date-time-widget-container">'
                + '<div ng-attr-style="display:table;{{lineStyle}}{{firstTextStyle}}">'
                + '<as-ui-date-time caps="{{caps}}" auto-update="true" format="{{firstFormat}}" time-zone="{{timeZone}}" fill-height="false" style="display:table-cell;vertical-align:middle"></as-ui-date-time>'
                + '</div>'
                + '<div ng-attr-style="display:table;{{lineStyle}}{{secondTextStyle}}">'
                + '<as-ui-date-time caps="{{caps}}" auto-update="true" format="{{secondFormat}}" time-zone="{{timeZone}}" fill-height="false" style="display:table-cell;vertical-align:middle"></as-ui-date-time>'
                + '</div>'
                + '</div>',

            link: function(scope, element, attrs) {
                scope.$on('$destroy', function() {
                    scope.destroy();
                });

                element.on('$destroy', function() {
                    scope.destroy(true);
                });

                scope.$watch('datasource', function(newVal) {
                    if (!newVal) {
                        return;
                    }
                    scope.setDatasource(newVal);
                });

                scope.$watch('configuration', function(newVal) {
                    if (!newVal) {
                        return;
                    }
                    scope.setConfiguration(newVal);
                });

                // Construct the api interface
                scope.api = {
                    id: scope.id,
                    getConfiguration: scope.getConfiguration,
                    getDatasource: scope.getDatasource,
                    setConfiguration: scope.setConfiguration,
                    setDatasource: scope.setDatasource,
                    freeze: scope.freeze,
                    unFreeze: scope.unFreeze,
                    destroy: scope.destroy,
                    wakeup: scope.wakeup,
                    sleep: scope.sleep
                };

                if (scope.onApiReady) {
                    scope.onApiReady({api: scope.api});
                }
            },

            controller: ['$scope', '$timeout', '$q', '$element', function($scope, $timeout, $q, $element) {
                $scope.localDatasource;
                var caps = false;
                var datePattern = 'MMM dd yy';
                var timePattern = 'HH:mm';
                var dateShow = true;
                var timeShow = true;
                var dateFontFormat = '';
                var timeFontFormat = '';
                var order = 'datefirst'; // datefirst;timefirst
                var datetimeStyle = 'singleline';// twolines;singleline
                var timezone = 'default';// 12->default->-12


                $scope.destroy = function(ignoreElement) {
                    if (!$element) {
                        return;
                    }
                    $element.empty(); // Parent of the video element (optional);
                    if (ignoreElement) {
                        return;
                    }
                    if ($element.remove) {
                        $element.remove();
                    }
                    $element = null;
                };

                $scope.setConfiguration = function(config) {
                    $scope.config = config;

                    for (var key in config) {
                        switch (key.toLowerCase()) {
                            case 'custom.datetime.caps':
                                caps = config[key].toLowerCase() == 'true';
                                break;
                            case 'custom.datetime.date.font':
                                dateFontFormat = config[key];
                                break;
                            case 'custom.datetime.date.show':
                                dateShow = config[key].toLowerCase() == 'true';
                                break;
                            case 'custom.datetime.order':
                                order = config[key].toLowerCase();
                                break;
                            case 'custom.datetime.patterndate':
                                datePattern = config[key];
                                break;
                            case 'custom.datetime.patterntime':
                                timePattern = config[key];
                                break;
                            case 'custom.datetime.style':
                                datetimeStyle = config[key].toLowerCase();
                                break;
                            case 'custom.datetime.time.font':
                                timeFontFormat = config[key];
                                break;
                            case 'custom.datetime.time.show':
                                timeShow = config[key].toLowerCase() == 'true';
                                break;
                            case 'custom.datetime.timezone':
                                timezone = remapTimeZone(config[key]);
                                break;
                        }
                    }

                    if (datePattern == 'null' || datePattern == '') {
                        datePattern = 'MMM dd,yyyy';
                    }
                    if (timePattern == 'null' || timePattern == '') {
                        timePattern = 'h:mm a';
                    }
                    datePattern = reformatPattern(datePattern);
                    timePattern = reformatPattern(timePattern);
                    dateFontFormat = reformatStyle(dateFontFormat);
                    timeFontFormat = reformatStyle(timeFontFormat);
                    doRender();

                    return $q.resolve({api: $scope.api});
                };

                $scope.setDatasource = function(datasource) {
                    $scope.localDatasource = datasource;
                    return $q.resolve({api: $scope.api});
                };

                $scope.getConfiguration = function() {
                    return $scope.config;
                };

                $scope.getDatasource = function() {
                    return $scope.localDatasource;
                };

                $scope.freeze = function() {
                    if ($scope.onFreeze) {
                        $scope.onFreeze({api: $scope.api});
                    }
                    return $q.resolve();
                };

                $scope.unFreeze = function() {
                    if ($scope.onUnFreeze) {
                        $scope.onUnFreeze({api: $scope.api});
                    }
                    return $q.resolve();
                };

                $scope.wakeup = function() {
                    return $q.resolve({api: $scope.api});
                };

                $scope.sleep = function() {
                    return $q.resolve({api: $scope.api});
                };

                function doRender() {
                    // only render if unfreeze
                    if (datetimeStyle == 'singleline') {
                        $scope.lineStyle = 'width:50%; height:100%;float:left;';
                    } else {
                        $scope.lineStyle = 'width:100%; height:50%;';
                    }
                    $scope.caps = caps;
                    // if timezone has changed, update the timezone
                    if ($scope.timeZone != timezone) {
                        $scope.timeZone = timezone == 'default' ? '' : timezone;
                    }
                    var HIDE_STYLE = 'display:none;';
                    var SHOW_FULL_STYLE = 'width:100%; height:100%';
                    var dateStyle = HIDE_STYLE;
                    var timeStyle = HIDE_STYLE;
                    if (dateShow && !timeShow) {
                        dateStyle = dateFontFormat + SHOW_FULL_STYLE;
                        timeStyle = HIDE_STYLE;
                    } else if (!dateShow && timeShow) {
                        dateStyle = HIDE_STYLE;
                        timeStyle = timeFontFormat + SHOW_FULL_STYLE;
                    } else if (dateShow && timeShow) {
                        dateStyle = dateFontFormat;
                        timeStyle = timeFontFormat;
                    }
                    if (order == 'datefirst') {
                        $scope.firstFormat = datePattern;
                        $scope.secondFormat = timePattern;
                        $scope.firstTextStyle = dateStyle;
                        $scope.secondTextStyle = timeStyle;
                        return;
                    }
                    $scope.firstFormat = timePattern;
                    $scope.secondFormat = datePattern;
                    $scope.firstTextStyle = timeStyle;
                    $scope.secondTextStyle = dateStyle;
                }

                function reformatPattern(pattern) {
                    var dateTimeUtil = new DateTimeUtil();
                    return dateTimeUtil.convertWindowsFormatDateToMoment(pattern);
                }

                function reformatStyle(style) {
                    return FontUtil.reformatStyleToCSS(style);
                }

                function remapTimeZone(timezone) {
                    var res = timezone;
                    if (!isNaN(timezone)) {
                        var num = parseInt(timezone, 10);
                        // reverse the numbering
                        // moment's GMT is in reverse
                        // e.g. GMT+10 is GMT-10 in moment
                        num *= -1;
                        if (num >= 0) {
                            timezone = '+' + num;
                        } else {
                            timezone = num;
                        }
                        res = 'Etc/GMT' + timezone;
                    }
                    return res;
                }
            }]
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('asSignsEventCalendarWidget', ["FontUtil", "DateTimeUtil", "ScheduleUtil", function(FontUtil, DateTimeUtil, ScheduleUtil) {
        return {
            restrict: 'EA',
            scope: {
                id: '@',
                datasource: '=',
                configuration: '=',

                onApiReady: '&',
                onFreeze: '&',
                onUnFreeze: '&',
            },
            template: '<div style="width:100%;height:100%;overflow:hidden"></div>',
            link: function(scope, element, attrs) {
                scope.$on('$destroy', function() {
                    scope.destroy();
                });

                element.on('$destroy', function() {
                    scope.destroy();
                });

                scope.api = {
                    id: scope.id,
                    getConfiguration: scope.getConfiguration,
                    getDatasource: scope.getDatasource,
                    setConfiguration: scope.setConfiguration,
                    setDatasource: scope.setDatasource,
                    freeze: scope.freeze,
                    unFreeze: scope.unFreeze,
                    destroy: scope.destroy,
                    wakeup: scope.wakeup,
                    sleep: scope.sleep,
                    executeAction: scope.executeAction
                };

                if (scope.onApiReady) {
                    scope.onApiReady({api: scope.api});
                }
            },
            controller: ["$scope", "$q", "$compile", "$element", "$timeout", "$filter", "scriptingService", function($scope, $q, $compile, $element, $timeout, $filter, scriptingService) {
                var itemTemplate = '[div] {EVENTNAME}[br /]{EVENTSTART} {@@} {EVENTEND}[/div]';
                var noEventMessage = 'No Event Message';
                var numberofEvents = 1;
                var prerollTime = 30;
                var rotateDuration = 3000;
                var timeFont;
                var eventFont;
                var descFont;
                var locationFont;
                var itemRange;
                var defaultEventFont = 'fontfamily=arial;fontsize=36;align=left;fontstyle=;color=#ff666666';
                var itemWidth;
                var itemHeight;
                var filterLocation;
                var showPastEvent;
                var displayContext;
                var rawEvents;
                $scope.displayEvents;
                var startTimePattern;
                var endTimePattern;
                var startDatePattern;
                var endDatePattern;
                var currentElement;
                var checkEventIntervalHandler;
                var rotateIntervalHandler;
                var isRemoving;
                var isFrozen;
                var defaultTimeFormat = 'h:mm a';
                var defaultDateFormat = 'MMM D, YYYY';
                var datasourceid;
                var currentPage;
                var currentElementScope;
                var isDestroyed;
                function sanitizePixel(value) {
                    return (parseInt(value, 10) || 0) + 'px';
                }

                $scope.setConfiguration = function(config) {
                    resetProperties();
                    for (var configKey in config) {
                        var key = configKey;
                        var value = config[configKey];
                        switch (key.toLowerCase()) {
                            case 'custom.eventcalendar.displaytypecontext':
                                displayContext = value.toLowerCase();
                                break;
                            case 'custom.eventcalendar.eventitemformat':
                                itemTemplate = value;
                                break;
                            case 'custom.eventcalendar.noeventmessage':
                                noEventMessage = value;
                                break;
                            case 'custom.eventcalendar.numberofeventstodisplay':
                                numberofEvents = parseInt(value, 10);
                                break;
                            case 'custom.eventcalendar.prerolltime':
                                prerollTime = parseInt(value, 10);
                                break;
                            case 'custom.eventcalendar.rotateduration':
                                rotateDuration = parseInt(value, 10) * 1000;
                                break;
                            case 'custom.eventcalendar.time.text.font':
                                timeFont = value;
                                break;
                            case 'custom.eventcalendar.event.text.font':
                                eventFont = value;
                                break;
                            case 'custom.eventcalendar.description.text.font':
                                descFont = value;
                                break;
                            case 'custom.eventcalendar.location.text.font':
                                locationFont = value;
                                break;
                            case 'custom.eventcalendar.eventitemrange':
                                itemRange = value.split(',');
                                break;
                            case 'custom.eventcalendar.eventitem.width':
                                itemWidth = sanitizePixel(value);
                                break;
                            case 'custom.eventcalendar.eventitem.height':
                                itemHeight = sanitizePixel(value);
                                break;
                            case 'custom.eventcalendar.filterlocation':
                                filterLocation = value;
                                break;
                            case 'custom.eventcalendar.showpastevent':
                                showPastEvent = value.toLowerCase() == 'true';
                                break;
                            case 'custom.eventcalendar.time.start.format':
                                startTimePattern = value;
                                break;
                            case 'custom.eventcalendar.time.end.format':
                                endTimePattern = value;
                                break;
                            case 'custom.eventcalendar.date.start.format':
                                startDatePattern = value;
                                break;
                            case 'custom.eventcalendar.date.end.format':
                                endDatePattern = value;
                                break;
                        }
                    }
                    eventFont = FontUtil.reformatStyleToCSS(eventFont || defaultEventFont);
                    descFont = descFont ? FontUtil.reformatStyleToCSS(descFont) : eventFont;
                    locationFont = locationFont ? FontUtil.reformatStyleToCSS(locationFont) : eventFont;
                    timeFont = timeFont ? FontUtil.reformatStyleToCSS(timeFont) : eventFont;
                    rotateDuration = Math.max(1000, rotateDuration);
                    itemRange = itemRange.indexOf('*') > -1 ? ['*'] : itemRange;
                    var datetimeUtil = new DateTimeUtil();
                    var convert = datetimeUtil.convertWindowsFormatDateToMoment;
                    startTimePattern = convert(startTimePattern) || defaultTimeFormat;
                    endTimePattern = convert(endTimePattern) || defaultTimeFormat;
                    startDatePattern = convert(startDatePattern) || defaultDateFormat;
                    endDatePattern = convert(endDatePattern) || defaultDateFormat;

                    itemTemplate = itemTemplate.split('[').join('<').split(']').join('>');
                    // standardized br
                    itemTemplate = itemTemplate.split('<br>').join('<br/>');
                    itemTemplate = itemTemplate.split('<br >').join('<br/>');
                    itemTemplate = itemTemplate.split('<br />').join('<br/>');

                    if (datasourceid) {
                        startCheckEventTimer(1);
                        doRender();
                    }

                    return $q.resolve();
                };

                $scope.setDatasource = function(datasource) {
                    datasourceid = undefined;
                    if (!datasource || !datasource.result || !datasource.result.events) {
                        return $q.resolve();
                    }
                    datasourceid = parseInt(datasource.result.id.split('_')[1], 10);
                    rawEvents = $angular.copy(datasource.result.events);
                    return $q.resolve();
                };

                $scope.freeze = function() {
                    var deferred = $q.defer();
                    if ($scope.onFreeze) {
                        $scope.onFreeze({api: $scope.api});
                    }

                    var resetData = function() {
                        isFrozen = true;
                        stopCheckEventTimer();
                        deferred.resolve();
                    };

                    removeDataFromVisual().then(resetData, resetData);
                    return deferred.promise;
                };

                $scope.unFreeze = function() {
                    if ($scope.onUnFreeze) {
                        $scope.onUnFreeze({api: $scope.api});
                    }
                    isFrozen = false;
                    doRender();
                    startCheckEventTimer(1);
                    return $q.resolve();
                };

                $scope.wakeup = function() {
                    return $q.resolve({api: $scope.api});
                };

                $scope.sleep = function() {
                    return $q.resolve({api: $scope.api});
                };

                $scope.destroy = function() {
                    isFrozen = true;
                    removeDataFromVisual();
                    stopCheckEventTimer();
                    stopAnimate();
                    if (!$element) {
                        return;
                    }
                    if (isDestroyed) {
                        return;
                    }
                    isDestroyed = true;
                    $element.remove();
                    $element = undefined;
                };

                $scope.$watch('displayEvents', function(newVal) {
                    if (!newVal) {
                        return;
                    }
                    doRender();
                });

                $scope.onEventClicked = function(index) {
                    var event = $scope.displayEvents[index];
                    if (!event) {
                        return;
                    }
                    var startDate = new Date(event.startDate);
                    var endDate = new Date(event.endDate);
                    var params = {
                        index: index,
                        datasourceid: datasourceid,
                        title: event.title,
                        location: event.location,
                        description: event.description,
                        startdate: startDate.toString(),
                        enddate: endDate.toString(),
                        starttime: moment(startDate.getTime()).format('h:mm a'),
                        endtime: moment(endDate.getTime()).format('h:mm a'),

                    };
                    scriptingService.raiseEvent('element_' + $scope.id, 'oneventitemclick', params);
                };

                function startCheckEventTimer(interval) {
                    stopCheckEventTimer();
                    if (isFrozen) {
                        return;
                    }
                    checkEventIntervalHandler = $timeout(doCheckEvent, interval || 5000);
                }

                function stopCheckEventTimer() {
                    if (!checkEventIntervalHandler) {
                        return;
                    }
                    $timeout.cancel(checkEventIntervalHandler);
                    checkEventIntervalHandler = undefined;
                }

                function doCheckEvent() {
                    // if events are invalid, re-check later
                    if (!rawEvents) {
                        startCheckEventTimer();
                        return;
                    }

                    var currentDate = new Date();
                    var res = [];
                    var filteredLocations = rawEvents;
                    // filter location
                    if (filterLocation) {
                        filteredLocations = $filter('filter')(filteredLocations, {location: filterLocation});
                    }
                    var i;
                    // check if events are valid
                    for (i = 0; i < filteredLocations.length; i++) {
                        var event = filteredLocations[i];
                        if (!isEventDateValid(event, currentDate)) {
                            continue;
                        }
                        res.push(event);
                    }
                    filteredLocations = undefined;

                    // filter the events based on item range
                    res = filterByItemRange(res);
                    for (i = 0; i < res.length; i++) {
                        res[i].__position = i;
                    }
                    // only updates if there's new data
                    if (JSON.stringify($scope.displayEvents) != JSON.stringify(res)) {
                        $scope.displayEvents = res;
                    }
                    res = undefined;
                    startCheckEventTimer();
                }

                function isEventDateValid(event, c) {
                    var startDate = moment(event.startDate);
                    // adjust the event start time using the prerollTime
                    startDate.subtract({minutes: prerollTime});
                    startDate = startDate.toDate().getTime();
                    var endDate = moment(event.endDate).toDate().getTime();
                    var currentDate = c.getTime();
                    // if event already past and show past event config is set to false, return false
                    if (currentDate > endDate && !showPastEvent) {
                        return false;
                    }
                    var cStart;
                    var cEnd;
                    // get the valid range
                    switch (displayContext) {
                        case 'current':
                            cStart = currentDate;
                            cEnd = currentDate;
                            break;
                        case 'day':
                            cStart = moment(currentDate);
                            cStart.startOf('day');
                            cStart = cStart.toDate().getTime();
                            cEnd = moment(currentDate);
                            cEnd.endOf('day');
                            cEnd = cEnd.toDate().getTime();
                            break;
                        case 'week':
                            // 7 days ago & 7 days ahead
                            cStart = moment(currentDate);
                            cStart.subtract({days: 7});
                            cStart.startOf('day');
                            cStart = cStart.toDate().getTime();
                            cEnd = moment(currentDate);
                            cEnd.add({days: 7});
                            cEnd.endOf('day');
                            cEnd = cEnd.toDate().getTime();
                            break;
                        case 'weekactual':
                            // current week
                            // start of the week (Monday)
                            cStart = moment(currentDate);
                            cStart.startOf('day');
                            cStart.day(1);
                            cStart = cStart.toDate().getTime();
                            // end of the week (Sunday)
                            cEnd = moment(currentDate);
                            cEnd.endOf('day');
                            cEnd.day(7);
                            cEnd = cEnd.toDate().getTime();
                            break;
                        case 'month':
                            // 31 days ago and 31 days ahead
                            cStart = moment(currentDate);
                            cStart.subtract({days: 31});
                            cStart = cStart.toDate().getTime();
                            cEnd = moment(currentDate);
                            cEnd.add({days: 31});
                            cEnd = cEnd.toDate().getTime();
                            break;
                        case 'monthactual':
                            // current month
                            cStart = moment(currentDate);
                            cStart.startOf('month');
                            cStart = cStart.toDate().getTime();

                            cEnd = moment(currentDate);
                            cEnd.endOf('month');
                            cEnd = cEnd.toDate().getTime();
                            break;
                    }
                    if (!cStart || !cEnd) {
                        return false;
                    }
                    return new ScheduleUtil().isEventValid({start: startDate, end: endDate}, {start: cStart, end: cEnd});
                }

                function filterByItemRange(events) {
                    var res = [];
                    for (var i = 0; i < itemRange.length; i++) {
                        var range = itemRange[i];
                        // all
                        if (range == '*') {
                            Array.prototype.push.apply(res, events);
                            continue;
                        }
                        if (range.indexOf('-') > -1) { // pure range
                            var startRange = parseInt(range.split('-')[0], 10);
                            var endRange = parseInt(range.split('-')[1], 10);
                            // the start range starts with 1, but our array starts with 0
                            // slice will not take the endRange into the value
                            var sliced = events.slice(startRange - 1, endRange);
                            Array.prototype.push.apply(res, sliced);
                            continue;
                        }
                        // single number rangef
                        var singleEvent = events[parseInt(range, 10) - 1];
                        if (!singleEvent) {
                            continue;
                        }
                        res.push(singleEvent);
                    }

                    return res;
                }

                function doRender() {
                    removeDataFromVisual().then(appendDataToVisual);
                }

                function appendDataToVisual() {
                    if (isFrozen || !$scope.displayEvents) {
                        return;
                    }
                    currentPage = -1;
                    currentElementScope = $scope.$new();
                    currentElement = $compile(prepareTemplate())(currentElementScope);
                    $($element.children()[0]).append(currentElement);
                    currentElement.velocity({
                        opacity: 1
                    }, {
                            duration: 500,
                            complete: function() {
                                startAnimate(1);
                            }
                        });
                }

                function removeDataFromVisual() {
                    if (isRemoving) {
                        return $q.reject();
                    }

                    if (!currentElement) {
                        return $q.resolve();
                    }

                    var deferred = $q.defer();
                    var doResolve = function() {
                        currentElementScope.$destroy();
                        currentElementScope = undefined;
                        isRemoving = false;
                        currentElement.remove();
                        currentElement = undefined;
                        deferred.resolve();
                    };
                    isRemoving = true;

                    $(currentElement).stop().velocity({
                        opacity: 0
                    }, {
                            duration: 500,
                            complete: doResolve
                        });


                    return deferred.promise;
                }

                var totalPage;
                function prepareTemplate() {
                    var template = '<div style="width:100%;height:100%;opacity:0">';
                    totalPage = 0;

                    // if no event
                    if (!$scope.displayEvents || !$scope.displayEvents.length || numberofEvents <= 0) {
                        return template + '<as-media-text style="' + eventFont + ';float:left" text="' + noEventMessage + '" use-non-breaking-space="true"></as-media-text></div>';
                    }
                    var itemPerPageCount = 0;
                    var getDefaultPageTemplate = function(currentPage) {
                        return '<div style="width:100%;height:100%" page="page-' + currentPage + '">';
                    };
                    var pageTemplate = getDefaultPageTemplate(totalPage);
                    for (var i = 0; i < $scope.displayEvents.length; i++) {
                        var event = $scope.displayEvents[i];
                        var eventTemplate = replaceTemplateWithData(itemTemplate, event);
                        eventTemplate = setDOMStyling(eventTemplate, event.__position);
                        pageTemplate += eventTemplate;
                        itemPerPageCount++;
                        // if current page can still fit events, then continue;
                        if (itemPerPageCount < numberofEvents && i != $scope.displayEvents.length - 1) {
                            continue;
                        }
                        // last event item
                        itemPerPageCount = 0;
                        template += pageTemplate + '</div>';
                        totalPage++;
                        pageTemplate = getDefaultPageTemplate(totalPage);
                    }

                    template += '</div>';
                    return template;
                }

                function replaceTemplateWithData(template, event) {
                    var startDate = new Date(event.startDate);
                    var endDate = new Date(event.endDate);

                    var eventTemplate = template.split('<br/>').join('<br clear=\'left\'/>');
                    eventTemplate = eventTemplate.split('{EVENTNAME}').join('<as-media-text style="' + eventFont + ';float:left" text="' + event.title.split('"').join('&quot;') + '" use-non-breaking-space="true"></as-media-text>');
                    eventTemplate = eventTemplate.split('{EVENTSTART}').join('<as-ui-date-time style="' + timeFont + ';float:left" auto-update="false" format="' + startTimePattern + '" date="' + startDate.getTime() + '"></as-ui-date-time>');
                    eventTemplate = eventTemplate.split('{EVENTEND}').join('<as-ui-date-time style="' + timeFont + ';float:left" auto-update="false" format="' + endTimePattern + '" date="' + endDate.getTime() + '"></as-ui-date-time>');
                    eventTemplate = eventTemplate.split('{@@}').join('<as-media-text style="' + timeFont + ';float:left" text="-" use-non-breaking-space="true"></as-media-text>');
                    eventTemplate = eventTemplate.split('{EVENTLOCATION}').join('<as-media-text style="' + locationFont + ';float:left" text="' + event.location.split('"').join('&quot;') + '" use-non-breaking-space="true"></as-media-text>');
                    eventTemplate = eventTemplate.split('{EVENTDESCRIPTION}').join('<as-media-text style="' + descFont + ';float:left" text="' + event.description.split('"').join('&quot;') + '" use-non-breaking-space="true"></as-media-text>');
                    eventTemplate = eventTemplate.split('{EVENTSTARTDATE}').join('<as-ui-date-time style="' + timeFont + ';float:left" auto-update="false" format="' + startDatePattern + '" date="' + startDate.getTime() + '"></as-ui-date-time>');
                    eventTemplate = eventTemplate.split('{EVENTENDDATE}').join('<as-ui-date-time style="' + timeFont + ';float:left" auto-update="false" format="' + endDatePattern + '" date="' + endDate.getTime() + '"></as-ui-date-time>');

                    return eventTemplate;
                }

                function setDOMStyling(template, pos) {
                    var height = itemHeight && itemHeight != '0px' ? itemHeight : ((100 / numberofEvents) + '%');
                    var width = itemWidth && itemWidth != '0px' ? itemWidth : '100%';
                    template = '<div style="height:' + height + ';width:' + width + ';overflow:hidden;" ng-click="onEventClicked(' + pos + ')">' + template + '</div>';
                    return template;
                }

                function startAnimate(duration) {
                    if (isFrozen) {
                        return;
                    }
                    stopAnimate();
                    rotateIntervalHandler = $timeout(nextPage, duration || rotateDuration);
                }

                function stopAnimate() {
                    if (!rotateIntervalHandler) {
                        return;
                    }
                    $timeout.cancel(rotateIntervalHandler);
                    rotateIntervalHandler = undefined;
                }

                function nextPage() {
                    var pageToLoad = currentPage + 1;
                    // load the first page if out of bound
                    loadPage(pageToLoad >= totalPage ? 0 : pageToLoad);
                }

                function previousPage() {
                    var pageToLoad = currentPage - 1;
                    // load the last page if out of bound
                    loadPage(pageToLoad < 0 ? totalPage - 1 : pageToLoad);
                }

                function loadPage(page) {
                    stopAnimate();
                    if (!totalPage) {
                        return;
                    }
                    var animateDuration = 1000;
                    if (page >= totalPage) {
                        page = totalPage - 1;
                    } else if (page < 0) {
                        page = 0;
                    }
                    currentPage = page;
                    scrollElement(currentElement, page, animateDuration);
                }

                var currentTop = 0;
                function scrollElement(element, multiplication, duration) {
                    if (element != currentElement) {
                        return;
                    }

                    currentTop = 100 * multiplication;

                    // animate the elements
                    $(element).velocity({
                        translateY: '-' + currentTop + '%',
                        translateZ: 0
                    }, {
                            duration: duration,
                            // easing: 'linear',
                            complete: function() {
                                var start = (currentPage) * numberofEvents;
                                var end = (currentPage + 1) * numberofEvents;
                                for (var i = start; i < end; i++) {
                                    var event = $scope.displayEvents[i];
                                    if (!event) {
                                        break;
                                    }
                                    var startDate = new Date(event.startDate);
                                    var endDate = new Date(event.endDate);
                                    var params = {
                                        index: i,
                                        datasourceid: datasourceid,
                                        title: event.title,
                                        location: event.location,
                                        description: event.description,
                                        startdate: startDate.toString(),
                                        enddate: endDate.toString(),
                                        starttime: moment(startDate.getTime()).format('h:mm a'),
                                        endtime: moment(endDate.getTime()).format('h:mm a'),

                                    };
                                    scriptingService.raiseEvent('element_' + $scope.id, 'oneventitemloaded', params);
                                }
                                startAnimate();
                            }
                        });
                }

                function resetProperties() {
                    showPastEvent = true;
                    filterLocation = '';
                    itemWidth = null;
                    itemHeight = null;
                    itemRange = ['*'];
                    timeFont = null;
                    eventFont = null;
                    descFont = null;
                    locationFont = null;
                    rotateDuration = 3000;
                    prerollTime = 30;
                    numberofEvents = 1;
                    noEventMessage = 'No Event Message';
                    itemTemplate = '[div] {EVENTNAME}[br /]{EVENTSTART} {@@} {EVENTEND}[/div]';
                    displayContext = 'day';
                    startTimePattern = null;
                    endTimePattern = null;
                    startDatePattern = null;
                    endDatePattern = null;
                }

                $scope.executeAction = function(action) {
                    var res = true;
                    switch (action.name.toLowerCase()) {
                        case 'nextpage':
                            nextPage();
                            break;
                        case 'previouspage':
                            previousPage();
                            break;
                        default:
                            res = false;
                            break;
                    }
                    return res;
                };
            }]
        };
    }]);
})(window.angular);

(function ($angular) {

    "use strict";

    var app = $angular.module('signsHTML5');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('asSignsFlashContainerWidget', function () {
		return {
			restrict: "EA",
			scope: {
				id: '@',
                datasource: '=',
                configuration: '=',
				assetSourceId: '=',

                onApiReady: '&',
                onFreeze: '&',
                onUnFreeze: '&',
            },
			template: '<as-media-flash style="width:100%; height:100%; position:absolute;"'
			+ ' '
			+ ' on-api-ready="onFlashApiReady(api)"'
			+ ' on-flash-load="onMediaLoad()"'
			+ ' on-flash-load-error="onMediaLoadError()"'
			+ ' ></as-media-flash>',
			link: function (scope, element, attrs) {
				
				element.on('$destroy', function() {
                    //console.log("asSignsFlashContainerWidget element destroy");
                    scope.destroy();
                });
				
				scope.api = {
					id: scope.id,
					getConfiguration: scope.getConfiguration,
                    getDatasource: scope.getDatasource,
                    setConfiguration: scope.setConfiguration,
                    setDatasource: scope.setDatasource,
                    freeze: scope.freeze,
                    unFreeze: scope.unFreeze,
                    destroy: scope.destroy,
                    wakeup: scope.wakeup,
                    sleep: scope.sleep,
					executeAction: scope.executeAction
				}
				if (scope.onApiReady) {
					scope.onApiReady({ api: scope.api });
				}
			},
			controller: ['$scope', '$timeout', '$q', 'networkContentRouterService', '$element', 'scriptingService',
				function ($scope, $timeout, $q, networkContentRouterService, $element, scriptingService) {
					var flashApi;
					var currentUrl;
					var delayUrl;
					var flashVariables;
					
					$scope.onFlashApiReady = function (api) {
						flashApi = api;
						//if there was a request to load a url before api is ready
						if (delayUrl) {
							currentUrl = delayUrl;
							delayUrl = null;
							flashApi.setUrl(currentUrl);
						}
					}

					$scope.onMediaLoad = function () {
						var params = {};
						scriptingService.raiseEvent("element_" + $scope.id, "OnFlashLoad", params);
					}

					$scope.onMediaLoadError = function () {

					}

					$scope.setConfiguration = function (config) {
						var promise = $q.defer();
						var flashVars = {};
						for (var configKey in config) {
							var key = configKey.toLowerCase();
							var value = config[configKey];
							if (key.indexOf('custom.flashcontainer.flashvar.') > -1){
								var flashkey = key.split('custom.flashcontainer.flashvar.').join('');
								flashVars[flashkey] = value;
								continue;
							}
							//console.log(key + ' ' + value);
						}
						flashVariables = flashVars;
						promise.resolve();

						return promise.promise;
					}

					$scope.setDatasource = function (datasource) {
						var promise = $q.defer();
						var asset;
						if (datasource && datasource.result) {
							var widgets = datasource.result.widgets;
							for (var i = 0; i < widgets.length; i++) {
								var widget = widgets[i];
								if (widget.id == $scope.assetSourceId) {
									asset = widget.assets[0]
									break;
								}
							}
						}

						if (asset) {
							networkContentRouterService.resolve(asset.mediaFile, "signs-content-routing-service")
								.then(function (url) {
									loadFlash(url).then(
										function () {
											promise.resolve();
										}
										);
								}, function (err) {
									promise.resolve();
								})
						} else {
							promise.resolve();
						}

						return promise.promise;
					}

					function loadFlash(url, force) {
						var promise = $q.defer();
						if (url == currentUrl && force != true) {
							promise.resolve();
							return;
						}

						if (flashApi && flashApi.setUrl) {
							currentUrl = url;
							flashApi.setUrl(currentUrl,
								flashVariables,
								{ allowScriptAccess: 'always' },
								null);
						}
						//if api is not ready, delay the load
						else {
							delayUrl = url;
						}
						promise.resolve();

						return promise.promise;
					}

					$scope.freeze = function () {
						var deferred = $q.defer();
						if ($scope.onFreeze) {
							$scope.onFreeze({ api: $scope.api });
						}
						deferred.resolve();
						return deferred.promise;
					}

					$scope.unFreeze = function () {
						var deferred = $q.defer();
						if ($scope.onUnFreeze) {
							$scope.onUnFreeze({ api: $scope.api });
						}
						deferred.resolve();
						return deferred.promise;
					}

					$scope.wakeup = function () {
						var deffered = $q.defer();
						deffered.resolve({ api: $scope.api });
						return deffered.promise;
					};

					$scope.sleep = function () {
						var deffered = $q.defer();
						deffered.resolve({ api: $scope.api });
						return deffered.promise;
					};

					$scope.destroy = function () {
						if (flashApi && flashApi.destroy) {
							flashApi.destroy();
						}
						$element.empty();
					};

					$scope.executeAction = function (action) {
						var a = action.name.toLowerCase();
						switch (a) {
							case 'executeaction':
							case 'callfunction':
								if (action.function || action.xML) {
									var params = [];
									if (action.paramaters){
										var arr = action.parameters.split(';');
										for (var i = 0 ; i < arr.length; i++){
											params.push(arr[i]);
										}
									}
									var func = action.function;
									if (!func){
										func = action.xML;
									}
									flashApi.callFunction(func,params);
								}
								return true;
								break;
							case 'replaymedia':
							case 'replay':
								flashApi.callFunction('Replay');
								return true;
								break;
						}
						return false;
					}
				}
			]
		}
	})
})(window.angular);
(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('asSignsPlainTextWidget', ["FontUtil", "HttpWrapper", "dataUtil", "TimeoutUtil", function(FontUtil, HttpWrapper, dataUtil, TimeoutUtil) {
        return {
            restrict: 'EA',
            scope: {
                id: '@',
                datasource: '=',
                configuration: '=',

                onApiReady: '&',
                onFreeze: '&',
                onUnFreeze: '&',
            },

            template:
                '<div ng-attr-style="{{displayTextStyle}}word-wrap: break-word;width: 100%;height: 100%;overflow: hidden">'
                + '<as-media-text text="{{displayText}}" use-non-breaking-space="true"></as-media-text>'
                + '</div>',

            link: function(scope, element, attrs) {
                scope.$on('$destroy', function() {
                    scope.destroy();
                });

                element.on('$destroy', function() {
                    scope.destroy();
                });

                scope.$watch('datasource', function(newVal) {
                    if (!newVal) {
                        return;
                    }
                    scope.setDatasource(newVal);
                });

                scope.$watch('configuration', function(newVal) {
                    if (!newVal) {
                        return;
                    }
                    scope.setConfiguration(newVal);
                });

                // Construct the api interface
                scope.api = {
                    id: scope.id,
                    getConfiguration: scope.getConfiguration,
                    getDatasource: scope.getDatasource,
                    setConfiguration: scope.setConfiguration,
                    setDatasource: scope.setDatasource,
                    destroy: scope.destroy,
                    freeze: scope.freeze,
                    unFreeze: scope.unFreeze,
                    wakeup: scope.wakeup,
                    sleep: scope.sleep,
                    executeAction: scope.executeAction
                };

                if (scope.onApiReady) {
                    scope.onApiReady({api: scope.api});
                }
            },

            controller: ['$scope', '$timeout', '$q', '$element', 'scriptingService', 'ColorUtil', 'logService',
                function($scope, $timeout, $q, $element, scriptingService, ColorUtil, logService) {
                    $scope.localDatasource;
                    var textToDisplay = '';
                    var textStyle = '';
                    var dynamicRefreshInterval = 60;
                    var dynamicTextLimit = 20;
                    var dynamicTextDelimiter = '...';
                    var dynamicTextURL;
                    var dynamicTextXPath;
                    var dynamicTimeoutHandler;
                    var dynamicTextDefaultXMLNS;
                    var defaultDynamicRefreshInterval = 60 * 1000;
                    var isFrozen = true;
                    var pendingText = null;
                    var actionConfig = {
                        settext: setText,
                        appendtext: appendText,
                        replacetext: replaceText,
                        updatetextfromurl: updateTextFromUrl
                    };
                    $scope.displayText = null;
                    $scope.setConfiguration = function(config) {
                        resetProperties();
                        $scope.config = config;
                        var enableDynamicText = false;
                        for (var key in config) {
                            switch (key.toLowerCase()) {
                                case 'custom.text.data':
                                    textToDisplay = config[key];
                                    break;
                                case 'custom.text.style':
                                    textStyle = config[key];
                                    break;
                                case 'custom.text.dynamic.enable':
                                    enableDynamicText = config[key] == 'true';
                                    break;
                                case 'custom.text.dynamic.source':
                                    dynamicTextURL = config[key];
                                    break;
                                case 'custom.text.dynamic.xmlquery':
                                    dynamicTextXPath = config[key];
                                    break;
                                case 'custom.text.dynamic.interval':
                                    dynamicRefreshInterval = parseInt(config[key], 10) * 1000;
                                    break;
                                case 'custom.text.dynamic.delimeter':
                                    dynamicTextDelimiter = config[key];
                                    break;
                                case 'custom.text.dynamic.textlimit':
                                    dynamicTextLimit = parseInt(config[key], 10);
                                    break;
                                case 'custom.text.dynamic.default.xmlns':
                                    dynamicTextDefaultXMLNS = config[key];
                                    break;
                            }
                        }
                        if (!enableDynamicText) {
                            dynamicTextURL = null;
                            dynamicTextXPath = null;
                        }
                        dynamicRefreshInterval = isNaN(dynamicRefreshInterval) ? defaultDynamicRefreshInterval : dynamicRefreshInterval;
                        doRender();
                        return $q.resolve({api: $scope.api});
                    };

                    $scope.setDatasource = function(datasource) {
                        $scope.localDatasource = datasource;
                        return $q.resolve({api: $scope.api});
                    };

                    $scope.getConfiguration = function() {
                        return $scope.config;
                    };

                    $scope.getDatasource = function() {
                        return $scope.localDatasource;
                    };

                    $scope.destroy = function() {
                        stopDynamicTimer();
                        resetProperties();
                        if ($element) {
                            $element.empty();
                            $element = null;
                        }
                        return;
                    };

                    $scope.freeze = function() {
                        if ($scope.onFreeze) {
                            $scope.onFreeze({api: $scope.api});
                        }
                        isFrozen = true;
                        return $q.resolve();
                    };

                    $scope.unFreeze = function() {
                        if ($scope.onUnFreeze) {
                            $scope.onUnFreeze({api: $scope.api});
                        }
                        isFrozen = false;
                        doRender();
                        return $q.resolve();
                    };

                    $scope.wakeup = function() {
                        var deffered = $q.defer();
                        deffered.resolve({api: $scope.api});
                        return deffered.promise;
                    };

                    $scope.sleep = function() {
                        var deffered = $q.defer();
                        stopDynamicTimer();
                        deffered.resolve({api: $scope.api});
                        return deffered.promise;
                    };

                    function resetProperties() {
                        textToDisplay = '';
                        textStyle = '';
                        dynamicRefreshInterval = 60 * 1000;
                        dynamicTextLimit = 20;
                        dynamicTextDelimiter = '...';
                        dynamicTextURL = null;
                        dynamicTextXPath = null;
                        stopDynamicTimer();
                        dynamicTextDefaultXMLNS = null;
                    }

                    function doRender() {
                        // only render if unfreeze
                        if (isFrozen) {
                            return;
                        }
                        var style = FontUtil.reformatStyleToCSS(textStyle);
                        $scope.displayTextStyle = style;
                        if (dynamicTextURL) {
                            startDynamicTimer(500);
                            doDisplayText('');
                            return;
                        }
                        var text = textToDisplay;
                        // if we have pending text, show that instead
                        if (pendingText != null) {
                            text = pendingText;
                            pendingText = null;
                        }
                        doDisplayText(text);
                    }

                    function startDynamicTimer(interval) {
                        stopDynamicTimer();
                        if (!interval) {
                            interval = dynamicRefreshInterval;
                        }
                        dynamicTimeoutHandler = $timeout(retrieveDynamicText, interval);
                    }

                    function stopDynamicTimer() {
                        TimeoutUtil.cancel(dynamicTimeoutHandler);
                        dynamicTimeoutHandler = undefined;
                    }

                    function retrieveDynamicText() {
                        var wrapper = new HttpWrapper();
                        wrapper.handlerGet(dynamicTextURL).then(function(res) {
                            doDisplayText(res.data);
                            startDynamicTimer();
                        }, function(err) {
                            doDisplayText(textToDisplay);
                            logService.warn('appspace.signs.app', 'Unable to retrieve dynamic text:', err);
                            startDynamicTimer();
                        });
                    }

                    function getDynamicText(dynamicTextXPath, text) {
                        if (!text) {
                            return '';
                        }
                        try {
                            var xpath = dynamicTextXPath || '';
                            if (dynamicTextDefaultXMLNS) {
                                var paths = xpath.split('/');
                                for (var i = 0; i < paths.length; i++) {
                                    var path = paths[i];
                                    if (!path) {
                                        continue;
                                    }
                                    // if there's no info on namespace, add the default namespace
                                    if (path.indexOf(':') == -1) {
                                        path = 'asns:' + path;
                                    }
                                    paths[i] = path;
                                }
                                xpath = paths.join('/');
                            }
                            // fix xpath that ends with forward slash
                            if (xpath.charAt(xpath.length - 1) == '/') {
                                xpath = xpath.substring(0, xpath.length - 1);
                            }
                            var xmlDoc = jQuery.parseXML(text); // eslint-disable-line
                            if (!xmlDoc) {
                                return '';
                            }
                            var node = xmlDoc.evaluate(xpath, xmlDoc, function(prefix) {
                                return prefix == 'asns' ? dynamicTextDefaultXMLNS : null;
                            }, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
                            var currentNode = node.iterateNext();
                            text = '';
                            while (currentNode) {
                                if (text != '') {
                                    text += ',';
                                }
                                text += currentNode.textContent;
                                currentNode = node.iterateNext();
                            }
                            if (dynamicTextLimit == 0) {
                                return '';
                            }
                            return dataUtil.stripNewLines(text);
                        } catch (error) {
                            logService.debug('appspace.signs.app', 'Unable to parse dynamic text xml:', error);
                            return '';
                        }
                    }

                    function limitText(text) {
                        // limit text length is only for dynamic text
                        if (!dynamicTextURL) {
                            return text;
                        }
                        var splits = text.split(' ');
                        if (splits.length <= dynamicTextLimit) {
                            return text;
                        }
                        splits = splits.slice(0, dynamicTextLimit);
                        return splits.join(' ') + dynamicTextDelimiter;
                    }

                    function doDisplayText(text) {
                        // if widget is frozen, save it during unfreeze
                        if (isFrozen) {
                            pendingText = text;
                            return;
                        }
                        // only run the xpath if we have xpath information
                        if (dynamicTextXPath) {
                            text = getDynamicText(dynamicTextXPath, text);
                        }
                        text = limitText(text);
                        var params = {
                            text: text,
                            oldtext: $scope.displayText
                        };
                        var previousText = $scope.displayText;
                        if (previousText === text) {
                            return;
                        }
                        $scope.displayText = text;
                        logService.debug('appspace.signs.app', 'Text updated:', text);
                        scriptingService.raiseEvent('element_' + $scope.id, 'ontextchanged', params);
                    }

                    function setText(action) {
                        if (!action.text) {
                            return;
                        }
                        dynamicTextURL = null;
                        doDisplayText(action.text);
                    }

                    function appendText(action) {
                        if (!action.text) {
                            return;
                        }
                        dynamicTextURL = null;
                        var text = $scope.displayText == null ? textToDisplay : $scope.displayText;
                        text += action.text;
                        doDisplayText(text);
                    }

                    function replaceText(action) {
                        if (!action.oldtext || !action.newtext) {
                            return;
                        }
                        dynamicTextURL = null;
                        var text = $scope.displayText == null ? textToDisplay : $scope.displayText;
                        text = text.split(action.oldtext).join(action.newtext);
                        doDisplayText(text);
                    }

                    function updateTextFromUrl(action) {
                        if (!action.url) {
                            return;
                        }
                        dynamicTextURL = action.url;
                        dynamicTextXPath = action.xmlQuery || null;
                        startDynamicTimer(1);
                    }

                    $scope.executeAction = function(action) {
                        var executeAction = actionConfig[action.name.toLowerCase()];
                        if (!executeAction) {
                            return false;
                        }
                        executeAction(action);
                        return true;
                    };
                }]
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var angular = $angular;
    var app = $angular.module('signsHTML5');

    app.directive('asSignsRssTickerWidget', function() {
        return {
            restrict: 'EA',
            scope: {
                id: '@',
                datasource: '=',
                configuration: '=',

                onApiReady: '&',
                onFreeze: '&',
                onUnFreeze: '&'
            },
            template: '<as-ui-ticker on-api-ready="directiveAPIReady(api)"'
                + ' auto-start="false" speed="{{tickerSpeed}}" spacing="{{tickerSpacing}}"'
                + ' direction="{{tickerDirection}}"'
                + ' content-item="currentItem" on-ticker-completed="onTickerCompleted(state)"></as-ui-ticker>',

            link: function(scope, element, attrs) {
                scope.$on('$destroy', function() {
                    scope.destroy();
                });
                element.on('$destroy', function() {
                    scope.destroy();
                });
                scope.$watch('datasource', function(newVal) {
                    if (!newVal) {
                        return;
                    }

                    scope.setDatasource(newVal);
                });

                scope.$watch('configuration', function(newVal) {
                    if (!newVal) {
                        return;
                    }

                    scope.setConfiguration(newVal);
                });

                scope.api = {
                    id: scope.id,
                    getConfiguration: scope.getConfiguration,
                    getDatasource: scope.getDatasource,
                    setConfiguration: scope.setConfiguration,
                    setDatasource: scope.setDatasource,
                    freeze: scope.freeze,
                    unFreeze: scope.unFreeze,
                    wakeup: scope.wakeup,
                    sleep: scope.sleep,
                    executeAction: scope.executeAction,
                    destroy: scope.destroy,
                };

                if (scope.onApiReady) {
                    scope.onApiReady({api: scope.api});
                }
            },
            controller: ["$scope", "$element", "$q", "FontUtil", "networkContentRouterService", "HttpWrapper", "signsParserService", "scriptingService", "logService", "DateTimeUtil", "dataUtil", "tickerUtil", "halMediaService", function($scope, $element, $q, FontUtil, networkContentRouterService, HttpWrapper,
                signsParserService, scriptingService, logService, DateTimeUtil, dataUtil, tickerUtil,
                halMediaService) {
                var directiveAPI;
                var configSpeed = 'medium';
                var configTickerDirection;
                var configTickerSpacing = 50;
                var configRSSItemSpacing = 10;
                var configDisplaylayout = ['date', 'title', 'text'];
                var configTitleFont = '';
                var configTextFont = '';
                var configDateFont = '';
                var configDateFormat = 'MM/DD/YYYY HH:mm';
                var parserService = signsParserService;
                var feeds;
                var isFrozen = true;
                var isDestroyed = false;
                var currentTickerItemIndex = 0;
                var tickerItemCollection = [];
                var configTickerRenderType = 'multiple';
                var rawCurrentItems;
                $scope.tickerSpeed = 100;
                $scope.tickerSpacing = 50;
                $scope.tickerDirection = 1; // 1 = right to left; 2 = left to right; 3 = bottom to top; 4 = top to bottom
                var dateTimeUtil = new DateTimeUtil();

                $scope.directiveAPIReady = function(api) {
                    directiveAPI = api;
                };

                $scope.destroy = function() {
                    clean();
                    $scope.localDatasource = undefined;
                    if (directiveAPI) {
                        if (directiveAPI.destroy) {
                            directiveAPI.destroy();
                        }

                        directiveAPI = undefined;
                    }

                    isDestroyed = true;
                    $element.empty();
                };

                $scope.setDatasource = function(datasource) {
                    $scope.localDatasource = datasource;
                    clean();
                    // Check our their feeds to retreieve
                    if (!datasource || !datasource.result || !datasource.result.feeds) {
                        return $q.resolve();
                    }

                    feeds = datasource.result.feeds;
                    var promise = $q.defer();
                    $scope.retrieveFeeds().then(function(items) {
                        rawCurrentItems = items;
                        render();
                        promise.resolve();
                    }, promise.reject);
                    return promise.promise;
                };

                $scope.onTickerCompleted = function(state) {
                    if (configTickerRenderType != 'single') {
                        return;
                    }

                    currentTickerItemIndex++;
                    if (currentTickerItemIndex >= tickerItemCollection.length) {
                        currentTickerItemIndex = 0;
                    }

                    var item = tickerItemCollection[currentTickerItemIndex];
                    $scope.currentItem = [item];
                };

                function clean() {
                    // Reset current feeds
                    feeds = undefined;
                    // Reset current items
                    $scope.currentItems = undefined;
                    rawCurrentItems = undefined;
                    tickerItemCollection = undefined;
                }

                function render() {
                    rawCurrentItems = rawCurrentItems || [];
                    // Convert RSS item into ticker items
                    var tickerItems = [];
                    for (var j = 0; j < rawCurrentItems.length; j++) {
                        var item = rawCurrentItems[j];
                        item.pubDate = dateTimeUtil.fixTimeZone(item.pubDate);
                        var template = generateFeed(item);
                        tickerItems.push({
                            type: 'markup',
                            data: template
                        });
                    }

                    tickerItemCollection = tickerItems;
                    currentTickerItemIndex = 0;
                    $scope.currentItem = configTickerRenderType == 'single' ?
                        [tickerItemCollection[currentTickerItemIndex]] :
                        tickerItemCollection;

                    if (!isFrozen) {
                        scriptingService.raiseEvent('element_' + $scope.id, 'ontickeritemavailable', tickerItems);
                    }
                }

                $scope.retrieveFeeds = function() {
                    var deferred = $q.defer();
                    var promises = [];
                    configDateFormat = reformatDatePattern(configDateFormat);
                    for (var i = 0; i < feeds.length; i++) {
                        // For each feed we need to retreive its items
                        promises.push($scope.retrieveFeed(feeds[i]));
                    }

                    // Wait for completion
                    $q.allSettled(promises).then(function(results) {
                        // if widget is destroyed AFTER we retrieve RSS feeds, ignore everything
                        if (isDestroyed) {
                            deferred.reject();
                            return;
                        }

                        var items = [];
                        // Iterate through each promise result
                        for (var k = 0; k < results.length; k++) {
                            var promiseResult = results[k];
                            if (promiseResult.state != 'fulfilled') {
                                continue;
                            }

                            var channels = promiseResult.value;
                            for (var i = 0; i < channels.length; i++) {
                                var channel = channels[i];
                                var channelItems = angular.copy(channel.items);
                                items = items.concat(channelItems);
                            }
                        }

                        deferred.resolve(items);
                    }, deferred.reject);

                    return deferred.promise;
                };

                // Retrieve the feed
                $scope.retrieveFeed = function(feed) {
                    if (!feed.fileUrl) {
                        logService.warn('appspace.signs.widgets', 'Script is missing local file for feed: ' + feed.url);
                        return $q.reject('unable to load feed');
                    }

                    var deferred = $q.defer();
                    var url = 'c:\\fastnetv2\\ug media\\rssfeeds\\' + feed.fileUrl;
                    // We want the UTF_8 version
                    url = url.split('rss_converted.xml').join('rss_converted_utf8.xml');

                    var resolveFeedUrl = function(url) {
                        logService.debug('appspace.signs.widgets', 'rss feed resolved ' + url);
                        // Attempt to retrieve the feed file and parse its item
                        $scope.retrieveFeedFile(url).then(deferred.resolve, deferred.reject);
                    };

                    // On file resolve
                    var resolvedNetwork = function(resolvedUrl) {
                        halMediaService.resolveContentPath(resolvedUrl).then(resolveFeedUrl, function() {
                            resolveFeedUrl(resolvedUrl);
                        });
                    };

                    // On error
                    var errorResolvedNetwork = function(err) {
                        logService.warn('appspace.signs.widgets', 'Unable to resolve rss feed ' + url);
                        deferred.reject('unable to load feed');
                    };

                    // Resolve where the rss file is located
                    networkContentRouterService.resolve(url, 'signs-content-routing-service').then(resolvedNetwork, errorResolvedNetwork);
                    return deferred.promise;
                };

                // Retrieve the feed and its items
                $scope.retrieveFeedFile = function(url) {
                    var deferred = $q.defer();
                    var httpWrapper = new HttpWrapper();
                    // Download file from url
                    httpWrapper.handlerGet(url).then(function(res) {
                        // Parse the file into an RSS object
                        parserService.parse('signs.parser.rss-file', {url: url, data: res.data}).then(function(parsedRes) {
                            if (!parsedRes || !parsedRes.result || !parsedRes.result.channels) {
                                deferred.reject('no items in rss file');
                                return;
                            }

                            deferred.resolve(parsedRes.result.channels);
                        }, function() {
                            deferred.reject('error on parsing ' + url);
                        });
                    }, function() {
                        deferred.reject('error on retrieving ' + url);
                    });
                    return deferred.promise;
                };

                $scope.setConfiguration = function(configuration) {
                    for (var keyName in configuration) {
                        var key = keyName;
                        var value = configuration[keyName];
                        switch (key.toLowerCase()) {
                            case 'custom.rssticker.spacing2':
                                configRSSItemSpacing = parseInt(value, 10);
                                break;
                            case 'custom.ticker.direction':
                                configTickerDirection = value.toLowerCase();
                                break; // "righttoleft"
                            case 'custom.ticker.spacing':
                                configTickerSpacing = parseInt(value, 10);
                                break; // "50"
                            case 'custom.ticker.speed':
                                configSpeed = value.toLowerCase();
                                break; // "medium"
                            case 'custom.rssticker.displaylayout':
                                configDisplaylayout = value.toLowerCase().split(',');
                                break; // Date,Title,Text
                            case 'custom.rssticker.title.font':
                                configTitleFont = FontUtil.reformatStyleToCSS(value);
                                break; // fontfamily=arial;fontsize=48;align=left;fontstyle=;color=#ffffffff
                            case 'custom.rssticker.text.font':
                                configTextFont = FontUtil.reformatStyleToCSS(value);
                                break; // "fontfamily=Arial;fontsize=48;align=left;fontstyle=;color=#ffffffff;dropshadow=0"
                            case 'custom.rssticker.date.font':
                                configDateFont = FontUtil.reformatStyleToCSS(value);
                                break;
                            case 'custom.rssticker.date.format':
                                configDateFormat = value;
                                break;
                            case 'custom.ticker.render.type':
                                configTickerRenderType = (value.toLowerCase() == 'single') ? 'single' : 'multiple';
                                break;
                        }
                    }

                    setSpeed(configSpeed);
                    setDirection(configTickerDirection);
                    if (configTickerRenderType == 'single') {
                        configTickerSpacing = 0;
                    }

                    $scope.tickerSpacing = configTickerSpacing;
                    // if we already have raw items, rerender
                    if (rawCurrentItems) {
                        render();
                    }

                    return $q.resolve();
                };

                function reformatDatePattern(pattern) {
                    return dateTimeUtil.convertWindowsFormatDateToMoment(pattern);
                }

                $scope.freeze = function() {
                    isFrozen = true;
                    directiveAPI.pause();
                    if ($scope.onFreeze) {
                        $scope.onFreeze({api: $scope.api});
                    }

                    return $q.resolve();
                };

                $scope.unFreeze = function() {
                    isFrozen = false;
                    directiveAPI.play();
                    if ($scope.onFreeze) {
                        $scope.onUnFreeze({api: $scope.api});
                    }

                    return $q.resolve();
                };

                $scope.wakeup = function() {
                    return $q.resolve({api: $scope.api});
                };

                $scope.sleep = function() {
                    return $q.resolve({api: $scope.api});
                };

                function generateFeed(feed) {
                    var positionStyle = 'position:relative;';
                    var transformStyle = '';
                    var styling = 'white-space:nowrap;';
                    var itemsContainer = '<div ng-attr-style="{{styling-placeholder}}' + positionStyle + transformStyle + '">';
                    // loop configDisplaylayout
                    for (var k = 0; k < configDisplaylayout.length; k++) {
                        var keyName = configDisplaylayout[k].toLowerCase();
                        var currentFont;
                        var currentText;
                        switch (keyName) {
                            case 'date':
                                if (dataUtil.isNullOrUndefined(feed.pubDate)) {
                                    break;
                                }

                                currentText = moment(feed.pubDate).format(configDateFormat);
                                currentFont = configDateFont;
                                break;
                            case 'title':
                                if (dataUtil.isNullOrUndefined(feed.title)) {
                                    break;
                                }

                                currentText = dataUtil.stripNewLines(feed.title);
                                currentFont = configTitleFont;
                                break;
                            case 'text':
                                if (dataUtil.isNullOrUndefined(feed.description)) {
                                    break;
                                }

                                currentText = dataUtil.stripNewLines(feed.description);
                                currentFont = configTextFont;
                                break;
                        }

                        // if we only have 1 display layout & we're fade in fade out, then apply the font styling to the parent div and don't apply font styling at children
                        if (configDisplaylayout.length == 1 && $scope.tickerDirection == '5') {
                            styling += currentFont + ';';
                            currentFont = '';
                        }

                        itemsContainer += generateTextMarkup(currentText, currentFont);

                        if (k < configDisplaylayout.length - 1) {
                            itemsContainer += generateSpacing(configRSSItemSpacing);
                        }
                    }

                    itemsContainer += '</div>';
                    // replace placeholders
                    // parent styling
                    itemsContainer = itemsContainer.split('{{styling-placeholder}}').join(styling);

                    return itemsContainer;
                }

                function generateTextMarkup(text, font) {
                    return '<span style="' + font + '">' + text + '</span>';
                }

                function generateSpacing(spacing) {
                    return '<div style="display:inline-block;width:' + spacing + 'px">&nbsp;</div>';
                }

                function setSpeed(speed) {
                    var validSpeed = tickerUtil.getTickerSpeed(speed);
                    if (!validSpeed) {
                        return;
                    }

                    $scope.tickerSpeed = validSpeed;
                }

                function setDirection(direction) {
                    var validDirection = tickerUtil.getTickerDirection(direction);
                    if (!validDirection) {
                        return;
                    }

                    $scope.tickerDirection = validDirection;
                }

                $scope.executeAction = function(action) {
                    switch (action.name.toLowerCase()) {
                        case 'setspeed':
                            setSpeed(action.speed);
                            return true;
                        case 'setdirection':
                            setDirection(action.direction);
                            return true;
                    }

                    return false;
                };
            }]
        };
    });
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = $angular.module('signsHTML5');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('asSignsRssViewerWidget', ["FontUtil", "halMediaService", function(FontUtil, halMediaService) {
        return {
            restrict: 'EA',

            scope: {
                id: '@',
                datasource: '=',
                configuration: '=',
                onApiReady: '&',
                onFreeze: '&',
                onUnFreeze: '&'
            },

            template: '<as-ui-rss-item ' +
                'content-item = "currentItem" ' +
                'default-layout = "{{layoutOrder}}" ' +
                'title-style = "{{titleFont}}" ' +
                'description-style = "{{descFont}}" ' +
                'date-style = "{{dateFont}}" ' +
                'default-date-format = "{{dateFormat}}" ' +
                'media-stretch = "{{mediaStretch}}" ' +
                'media-size = "{{mediaSize}}" ' +
                'media-position = "{{mediaLayout}}" ' +
                'media-border = "{{mediaBorder}}" ' +
                '></as-ui-rss-item>',

            link: function(scope, element, attrs) {
                scope.$on('$destroy', function() {
                    scope.destroy();
                });

                element.on('$destroy', function() {
                    scope.destroy();
                });

                scope.$watch('datasource', function(newVal) {
                    if (!newVal) {
                        return;
                    }

                    scope.setDatasource(newVal);
                });

                scope.$watch('configuration', function(newVal) {
                    if (!newVal) {
                        return;
                    }

                    scope.setConfiguration(newVal);
                });

                // Construct the api interface
                scope.api = {
                    id: scope.id,
                    getConfiguration: scope.getConfiguration,
                    getDatasource: scope.getDatasource,
                    setConfiguration: scope.setConfiguration,
                    setDatasource: scope.setDatasource,
                    freeze: scope.freeze,
                    unFreeze: scope.unFreeze,
                    wakeup: scope.wakeup,
                    sleep: scope.sleep,
                    destroy: scope.destroy,
                    executeAction: scope.executeAction
                };

                if (scope.onApiReady) {
                    scope.onApiReady({
                        api: scope.api
                    });
                }
            },

            controller: ['$scope', '$q', '$interval', 'LinearPlaylist', 'networkContentRouterService', 'HttpWrapper', 'signsParserService', 'scriptingService', 'logService', 'ColorUtil', 'DateTimeUtil',
                function($scope, $q, $interval, LinearPlaylist, networkContentRouterService, HttpWrapper, signsParserService, scriptingService, logService, ColorUtil, DateTimeUtil) {
                    $scope.config;
                    $scope.localDatasource;
                    $scope.currentItem = null;
                    var dateFont;
                    var descFont;
                    var titleFont;
                    var layoutOrder;
                    var mediaLayout;
                    var mediaSize;
                    var mediaStretch;
                    var displayDuration;
                    var mediaBorder;
                    var dateFormat;
                    var parserService = signsParserService;
                    var activePlaylist = new LinearPlaylist(false, true, false);
                    var tempChannels = [];
                    var tempFeeds = [];
                    var currentTempFeed = 0;
                    var intervalPromise = -1;
                    var lastDisplayDurationCheck;
                    var activeChannel;
                    var isFrozen = true;
                    var stripComponents = [];
                    var dateTimeUtil = new DateTimeUtil();

                    function reset() {
                        dateFont = 'fontfamily=arial;fontsize=48;align=left;fontstyle=;color=#ffffffff';
                        dateFormat = 'MM/DD/YYYY HH:mm';
                        descFont = 'fontfamily=arial;fontsize=48;align=left;fontstyle=;color=#ffffffff';
                        titleFont = 'fontfamily=arial;fontsize=48;align=left;fontstyle=;color=#ffffffff';
                        layoutOrder = 'date,title,text';
                        mediaLayout = 'middle';
                        mediaSize = '50%';
                        mediaStretch = 'uniform';
                        displayDuration = 10;
                        mediaBorder = '0px solid red';
                        stripComponents = [];
                    }

                    $scope.setConfiguration = function(config) {
                        $scope.config = config;
                        var borderColor;
                        var borderWidth;
                        reset();
                        for (var key in config) {
                            switch (key.toLowerCase()) {
                                case 'custom.rssviewer.strip.text':
                                    stripComponents = config[key].split(',');
                                    break;
                                case 'custom.rssviewer.border.color':
                                    borderColor = config[key];
                                    break;
                                case 'custom.rssviewer.border.width':
                                    borderWidth = parseInt(config[key], 10) || 0;
                                    break;
                                case 'custom.rssviewer.date.font':
                                    dateFont = FontUtil.reformatStyleToCSS(config[key]);
                                    break;
                                case 'custom.rssviewer.date.format':
                                    dateFormat = config[key];
                                    break;
                                case 'custom.rssviewer.text.font':
                                    descFont = FontUtil.reformatStyleToCSS(config[key]);
                                    break;
                                case 'custom.rssviewer.title.font':
                                    titleFont = FontUtil.reformatStyleToCSS(config[key]);
                                    break;
                                case 'custom.rssviewer.layoutorder':
                                    layoutOrder = config[key];
                                    break;
                                case 'custom.rssviewer.media.layout':
                                    mediaLayout = config[key] == 'middle' ? 'center' : config[key];
                                    break;
                                case 'custom.rssviewer.media.size': // in percentage
                                    mediaSize = (parseInt(config[key], 10) || 50) + '%';
                                    break;
                                case 'custom.rssviewer.media.stretch':
                                    mediaStretch = config[key];
                                    break;
                                case 'custom.rssviewer.rssitemdisplayduration':
                                    displayDuration = parseInt(config[key], 10);
                                    displayDuration = isNaN(displayDuration) ? 10 : displayDuration;
                                    break;
                            }
                        }

                        if (borderWidth != 0) {
                            if (!borderColor) {
                                borderColor = '#ffff0000';
                            }

                            var cUtil = new ColorUtil();
                            borderColor = cUtil.parseHTMLColor(borderColor);
                            mediaBorder = borderWidth + 'px solid ' + borderColor;
                        }

                        if ($scope.currentItem) {
                            lastDisplayDurationCheck = undefined;
                        }

                        doRender();
                        doStartIntervalCheck();
                        return $q.resolve({
                            api: $scope.api
                        });
                    };

                    $scope.setDatasource = function(datasource) {
                        $scope.localDatasource = datasource;
                        if (!datasource || !datasource.result || !datasource.result.feeds) {
                            logService.debug('appspace.signs.widgets', 'No feeds available');
                            return $q.resolve();
                        }

                        var feeds = datasource.result.feeds;
                        var promise = $q.defer();
                        loadFeeds(feeds).then(function() {
                            logService.debug('appspace.signs.widgets', 'Load Feeds successfull');
                            // reset active channel so that we will start from the 1st feed
                            activeChannel = null;
                            // reset last display check so that we will call next directly
                            lastDisplayDurationCheck = undefined;
                            activePlaylist.setItems(tempChannels);
                            promise.resolve();
                        }, function(err) {
                            logService.debug('appspace.signs.widgets', 'Fail to load feeds:', err);
                            promise.reject();
                        });
                        return promise.promise;
                    };

                    $scope.getConfiguration = function() {
                        return $scope.config;
                    };

                    $scope.getDatasource = function() {
                        return $scope.localDatasource;
                    };

                    $scope.destroy = function() {
                        doStopIntervalCheck();
                    };

                    $scope.freeze = function() {
                        doStopIntervalCheck();
                        if ($scope.onFreeze) {
                            $scope.onFreeze({
                                api: $scope.api
                            });
                        }

                        isFrozen = true;
                        return $q.resolve();
                    };

                    $scope.unFreeze = function() {
                        doStartIntervalCheck();
                        if ($scope.onUnFreeze) {
                            $scope.onUnFreeze({
                                api: $scope.api
                            });
                        }

                        isFrozen = false;
                        return $q.resolve();
                    };

                    $scope.wakeup = function() {
                        return $q.resolve({
                            api: $scope.api
                        });
                    };

                    $scope.sleep = function() {
                        return $q.resolve({
                            api: $scope.api
                        });
                    };

                    function doStartIntervalCheck() {
                        doStopIntervalCheck();
                        lastDisplayDurationCheck = new Date();
                        intervalPromise = $interval(doInterval, 1000);
                    }

                    function doStopIntervalCheck() {
                        if (intervalPromise == -1) {
                            return;
                        }

                        $interval.cancel(intervalPromise);
                        intervalPromise = -1;
                    }

                    function loadFeeds(feeds) {
                        var deferred = $q.defer();
                        tempChannels = [];
                        tempFeeds = angular.copy(feeds);
                        currentTempFeed = 0;
                        loadFeed(currentTempFeed, deferred);
                        return deferred.promise;
                    }

                    function loadFeed(index, deferred) {
                        var feed = tempFeeds[index];
                        if (!feed) {
                            deferred.resolve();
                            return;
                        }

                        var url = 'c:\\fastnetv2\\ug media\\rssfeeds\\' + feed.fileUrl;
                        if (!feed.fileUrl) {
                            logService.warn('appspace.signs.widgets', 'Script is missing local file for feed: ' + feed.fileUrl);
                            deferred.reject('unable to load feed');
                            return;
                        }

                        if (url.toLocaleLowerCase().indexOf('rss_converted.xml') > -1) {
                            url = url.split('rss_converted.xml').join('rss_converted_utf8.xml');
                        }

                        var resolveRssUrlContentPath = function(rssUrl) {
                            logService.debug('appspace.signs.widgets', 'rss feed resolved ' + rssUrl);
                            retrieveFeedFromUrl(rssUrl, deferred, index, url);
                        };

                        var resolvedNetwork = function(resolvedUrl) {
                            halMediaService.resolveContentPath(resolvedUrl).then(function(newUrl) {
                                resolveRssUrlContentPath(newUrl);
                            }, function() {
                                resolveRssUrlContentPath(resolvedUrl);
                            });
                        };

                        var errorResolvedNetwork = function(err) {
                            logService.warn('appspace.signs.widgets', 'Unable to resolve rss feed ' + url);
                            deferred.reject('unable to load feed');
                        };

                        networkContentRouterService.resolve(url, 'signs-content-routing-service').then(resolvedNetwork, errorResolvedNetwork);
                    }

                    function retrieveFeedFromUrl(url, deferred, index, actualUrl) {
                        var httpWrapper = new HttpWrapper();
                        httpWrapper.handlerGet(url).then(function(res) {
                            parserService.parse('signs.parser.rss-file', {
                                url: actualUrl,
                                data: res.data
                            }).then(function(parsedRes) {
                                if (!parsedRes || !parsedRes.result || !parsedRes.result.channels) {
                                    loadFeed(++currentTempFeed, deferred);
                                    return;
                                }

                                for (var i = 0; i < parsedRes.result.channels.length; i++) {
                                    var channel = parsedRes.result.channels[i];
                                    var channelPlaylistItem = {};
                                    channelPlaylistItem.__position = index;
                                    channelPlaylistItem.channel = angular.copy(channel);
                                    delete channelPlaylistItem.channel.items;
                                    channelPlaylistItem.playlist = new LinearPlaylist(false, false, false);
                                    for (var j = 0; j < channel.items.length; j++) {
                                        var item = channel.items[j];
                                        item.__position = j;
                                        item.pubDate = dateTimeUtil.fixTimeZone(item.pubDate);
                                    }

                                    channelPlaylistItem.playlist.setItems(channel.items);
                                    tempChannels.push(channelPlaylistItem);
                                }

                                loadFeed(++currentTempFeed, deferred);
                            }, function(err) {
                                loadFeed(++currentTempFeed, deferred);
                            });
                        }, function(err) {
                            loadFeed(++currentTempFeed, deferred);
                        });
                    }

                    function doRender() {
                        dateFormat = reformatDatePattern(dateFormat);
                        $scope.layoutOrder = layoutOrder;
                        $scope.titleFont = titleFont;
                        $scope.descFont = descFont;
                        $scope.dateFont = dateFont;
                        $scope.dateFormat = dateFormat;
                        $scope.mediaStretch = mediaStretch;
                        $scope.mediaSize = mediaSize;
                        $scope.mediaLayout = mediaLayout;
                        $scope.mediaBorder = mediaBorder;
                    }

                    function reformatDatePattern(pattern) {
                        return dateTimeUtil.convertWindowsFormatDateToMoment(pattern);
                    }

                    function doInterval() {
                        var currentDate = new Date();
                        if ($scope.currentItem == null || !lastDisplayDurationCheck || currentDate.getTime() - lastDisplayDurationCheck.getTime() > (displayDuration * 1000)) {
                            next();
                        }
                    }

                    function loadSiblingRssItem(channelMethod, feedMethod) {
                        // if activechannel was never grab before, grab the first channel from playlist
                        activeChannel = activeChannel || channelMethod();
                        var rssItem = feedMethod();
                        if (!rssItem) {
                            // as we have reached at the end of playlist item, undefine the active channel
                            // so that it will get the next/previous playlist in the next interval
                            activeChannel = undefined;
                            return;
                        }

                        loadItem(rssItem);
                    }

                    function next() {
                        loadSiblingRssItem(retrieveNextChannel, retrieveNextFeed);
                    }

                    function previous() {
                        loadSiblingRssItem(retrievePreviousChannel, retrievePreviousFeed);
                    }

                    function retrieveSiblingFeed(method) {
                        if (!activeChannel) {
                            return;
                        }

                        return activeChannel.playlist[method]();
                    }

                    function retrieveNextFeed() {
                        return retrieveSiblingFeed('next');
                    }

                    function retrievePreviousFeed() {
                        return retrieveSiblingFeed('previous');
                    }

                    function retrieveFeedByOffset(index) {
                        return activeChannel.playlist.getItemAtIndex(index);
                    }

                    function retrieveSiblingChannel(method) {
                        var channel = activePlaylist[method]();
                        if (!channel) {
                            return;
                        }

                        channel.playlist.currentOffset = null;
                        return channel;
                    }

                    function retrieveNextChannel() {
                        return retrieveSiblingChannel('next');
                    }

                    function retrievePreviousChannel() {
                        return retrieveSiblingChannel('previous');
                    }

                    function retrieveChannelByName(name) {
                        var channel;
                        var index = -1;
                        for (var i = 0; i < activePlaylist.items.length; i++) {
                            var item = activePlaylist.items[i];
                            if (item.channel.title.toLowerCase() == name.toLowerCase()) {
                                index = i;
                                break;
                            }
                        }

                        if (index != -1) {
                            channel = activePlaylist.getItemAtIndex(index);
                            channel.playlist.currentOffset = null;
                        }

                        return channel;
                    }

                    function retrieveChannelByIndex(index) {
                        var res = activePlaylist.getItemAtIndex(index);
                        if (!res) {
                            return;
                        }

                        res.playlist.currentOffset = null;
                        return res;
                    }

                    function stripText(str, components) {
                        if (!str || !components || !components.length) {
                            return str;
                        }

                        var val = str;
                        for (var i = 0; i < components.length; i++) {
                            var component = components[i];
                            switch (component) {
                                case 'html':
                                    val = stripHTMLFromText(val);
                                    break;
                                case 'newline':
                                    val = stripNewLineFromText(val);
                                    break;
                                case 'all':
                                    val = stripHTMLFromText(val);
                                    val = stripNewLineFromText(val);
                                    break;
                            }
                        }

                        return val;
                    }

                    function stripHTMLFromText(str) {
                        return str.replace(/<(.*?)>/g, '');
                    }

                    function stripNewLineFromText(str) {
                        return str.replace(/[\n]/g, ' ');
                    }

                    function loadItem(rssItem) {
                        if (!rssItem) {
                            return;
                        }

                        lastDisplayDurationCheck = new Date();
                        var doSetItem = function(mediaUrl) {
                            setCurrentItem({
                                title: stripText(rssItem.title, stripComponents),
                                description: stripText(rssItem.description, stripComponents),
                                media: mediaUrl,
                                date: rssItem.pubDate,
                                position: rssItem.__position
                            });
                        };

                        if (!rssItem.media) {
                            doSetItem(null);
                            return;
                        }

                        networkContentRouterService.resolve(rssItem.media, 'signs-content-routing-service')
                            .then(function(url) {
                                doSetItem(url);
                            }, function(err) {
                                doSetItem(null);
                            });
                    }

                    function setCurrentItem(settings) {
                        $scope.currentItem = {
                            title: settings.title,
                            description: settings.description,
                            media: settings.media,
                            date: settings.date
                        };
                        var params = {
                            position: settings.position,
                            title: settings.title,
                            description: settings.description,
                            published: settings.date,
                            channelTitle: activeChannel.channel.title,
                            channelPosition: activeChannel.__position,
                            channelDescription: activeChannel.channel.description
                        };
                        scriptingService.raiseEvent('element_' + $scope.id, 'itemloaded', params);
                    }

                    $scope.executeAction = function(action) {
                        var a = action.name.toLowerCase();
                        var isActionExecuted = true;
                        switch (a) {
                            case 'next':
                                next();
                                break;
                            case 'previous':
                                previous();
                                break;
                            case 'loadsourcefeedbyindex':
                                var index = parseInt(action.sourceIndex, 10);
                                if (!isNaN(index)) {
                                    var res = retrieveChannelByIndex(index);
                                    if (res) {
                                        activeChannel = res;
                                        next();
                                    }
                                }

                                break;
                            case 'loadsourcefeedbyname':
                                if (action.sourceName) {
                                    var res = retrieveChannelByName(action.sourceName);
                                    if (res) {
                                        activeChannel = res;
                                        next();
                                    }
                                }

                                break;
                            case 'loaditem':
                                var index = parseInt(action.offset, 10);
                                if (!isNaN(index)) {
                                    var res = retrieveFeedByOffset(index);
                                    if (res) {
                                        loadItem(res);
                                    }
                                }

                                break;
                            default:
                                isActionExecuted = false;
                        }

                        return isActionExecuted;
                    };
                }
            ]
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('asSignsRemoteControlWidget', ["$timeout", "TimeoutUtil", function($timeout, TimeoutUtil) {
        return {
            restrict: 'EA',
            scope: {
                id: '@',
                datasource: '=',
                configuration: '=',

                onApiReady: '&',
                onFreeze: '&',
                onUnFreeze: '&'
            },
            template: '',
            link: function(scope, element, attrs) {
                scope.$on('$destroy', function() {
                    scope.destroy();
                });
                element.on('$destroy', function() {
                    scope.destroy();
                });

                scope.$watch('datasource', function(newVal) {
                    if (!newVal) {
                        return;
                    }
                    scope.setDatasource(newVal);
                });

                scope.$watch('configuration', function(newVal) {
                    if (!newVal) {
                        return;
                    }
                    scope.setConfiguration(newVal);
                });
                scope.api = {
                    id: scope.id,
                    getConfiguration: scope.getConfiguration,
                    getDatasource: scope.getDatasource,
                    setConfiguration: scope.setConfiguration,
                    setDatasource: scope.setDatasource,
                    destroy: scope.destroy,
                    freeze: scope.freeze,
                    unFreeze: scope.unfreeze,
                    wakeup: scope.wakeup,
                    sleep: scope.sleep,
                    executeAction: scope.executeAction
                };

                if (scope.onApiReady) {
                    scope.onApiReady({api: scope.api});
                }
            },
            controller: ['$scope', '$q', 'networkContentRouterService', 'HttpWrapper', 'signsParserService', 'halRemoteService', 'REMOTEVCODE', 'REMOTEEVENTTYPE', 'scriptingService',
                function($scope, $q, networkContentRouterService, HttpWrapper, signsParserService, halRemoteService, REMOTEVCODE, REMOTEEVENTTYPE, scriptingService) {
                    $scope.id;
                    var remoteTable = {};
                    var defaultRemoteTable = generateDefaultTable();
                    var numericTimeout = 1000;
                    var maxNumericInput = 3;
                    var numericQueue;
                    var sendEventHandler;
                    var isFrozen;
                    var remoteEventBinded = false;

                    $scope.setConfiguration = function(configuration) {
                        var promise = $q.defer();

                        for (var key in configuration) {
                            var value = configuration[key];
                            switch (key.toLowerCase()) {
                                case 'custom.remotecontrol.numbergroupingcount':
                                    maxNumericInput = parseInt(value, 10);
                                    break;
                                case 'custom.remotecontrol.numbergroupingtimeout':
                                    numericTimeout = parseInt(value, 10);
                                    break;
                            }
                        }

                        promise.resolve();
                        return promise.promise;
                    };
                    $scope.setDatasource = function(datasource) {
                        var promise = $q.defer();
                        if (datasource && datasource.result && datasource.result.workbooks && datasource.result.workbooks.length > 0) {
                            var instances = datasource.result.workbooks[0].workbookInstances;
                            var finalInstance;
                            var datasourceId = datasource.result.workbooks[0].id;
                            for (var i = 0; i < instances.length; i++) {
                                var instance = instances[i];
                                if (!finalInstance || instance.id > finalInstance.id) {
                                    finalInstance = instance;
                                }
                            }

                            if (finalInstance) {
                                var url = 'C:\\fastnetv2\\datasource\\' + datasourceId + '\\' + finalInstance.filename;
                                var onUrlResolved = function(newurl) {
                                    loadFeed(newurl);
                                };
                                var onUrlFailResolve = function(err) {
                                    promise.resolve();
                                };
                                var loadFeed = function(url) {
                                    var httpWrapper = new HttpWrapper();
                                    httpWrapper.handlerGet(url).then(onFeedDownloaded, onFeedFailDownload);
                                };
                                var onFeedDownloaded = function(res) {
                                    parseWorkbook(res.data, promise);
                                };
                                var onFeedFailDownload = function(err) {
                                    promise.resolve();
                                };
                                networkContentRouterService.resolve(url, 'signs-content-routing-service').then(onUrlResolved, onUrlFailResolve);
                            } else {
                                promise.resolve();
                            }
                        } else {
                            promise.resolve();
                        }
                        return promise.promise;
                    };

                    function parseWorkbook(data, promise) {
                        signsParserService.parse('signs.parser.workbook-datatable', data).then(function(parsedRes) {
                            if (parsedRes.result && parsedRes.result.sheets && parsedRes.result.sheets.length > 0) {
                                var sheet = parsedRes.result.sheets[0];
                                mapSheetToRemoteTable(sheet);
                                promise.resolve();
                            } else {
                                promise.resolve();
                            }
                        }, function(err) {
                            promise.reject('error on parsing ' + url);
                        });
                    }

                    function mapSheetToRemoteTable(sheet) {
                        remoteTable = {};
                        for (var rowKey in sheet.rows) {
                            // skip header
                            if (rowKey == 0) {
                                continue;
                            }
                            var row = sheet.rows[rowKey];
                            var action = row.c1.toLowerCase();
                            var key = row.c2;
                            if (key) {
                                remoteTable[key] = action.toLowerCase();
                            }
                        }
                    }

                    function generateDefaultTable() {
                        var table = {};
                        table[48] = 'on_number_0';// 0
                        table[49] = 'on_number_1';// 1
                        table[50] = 'on_number_2';// 2
                        table[51] = 'on_number_3';// 3
                        table[38] = 'on_control_up';
                        table[40] = 'on_control_down';
                        table[37] = 'on_control_left';
                        table[39] = 'on_control_right';
                        return table;
                    }

                    function onRemoteEvent(event) {
                        // Only listen to down button events
                        if (event.eventType != REMOTEEVENTTYPE.buttonDown) {
                            return;
                        }
                        var action = defaultRemoteTable[event.code];
                        if (remoteTable[event.code]) {
                            action = remoteTable[event.code];
                        }

                        // check built-in mapping
                        if (!action && !isNaN(event.vcode)) {
                            action = retrieveActionFromVirtualCode(event.vcode);
                        }
                        processAction(action);
                    }

                    function retrieveActionFromVirtualCode(vcode) {
                        var rawaction;
                        for (var key in REMOTEVCODE) {
                            if (REMOTEVCODE[key] == vcode) {
                                rawaction = key;
                                break;
                            }
                        }
                        var action;
                        switch (rawaction) {
                            case 'up':
                                action = 'on_control_up';
                                break;
                            case 'down':
                                action = 'on_control_down';
                                break;
                            case 'left':
                                action = 'on_control_left';
                                break;
                            case 'right':
                                action = 'on_control_right';
                                break;
                            case 'select':
                                action = 'on_control_enter';
                                break;
                            case 'back':
                                action = 'on_control_last';
                                break;
                            case 'menu':
                                action = 'on_control_menu';
                                break;
                            case 'power':
                                break;
                            case 'mute':
                                action = 'on_control_mute';
                                break;
                            case 'page_up':
                                action = 'on_control_pageup';
                                break;
                            case 'page_down':
                                action = 'on_control_pagedown';
                                break;
                            case 'channel_up':
                                action = 'on_control_channelup';
                                break;
                            case 'channel_down':
                                action = 'on_control_channeldown';
                                break;
                            case 'system':
                                break;
                            case 'esc':
                                action = 'on_control_exit';
                                break;
                            case 'no_0':
                                action = 'on_number_0';
                                break;
                            case 'no_1':
                                action = 'on_number_1';
                                break;
                            case 'no_2':
                                action = 'on_number_2';
                                break;
                            case 'no_3':
                                action = 'on_number_3';
                                break;
                            case 'no_4':
                                action = 'on_number_4';
                                break;
                            case 'no_5':
                                action = 'on_number_5';
                                break;
                            case 'no_6':
                                action = 'on_number_6';
                                break;
                            case 'no_7':
                                action = 'on_number_7';
                                break;
                            case 'no_8':
                                action = 'on_number_8';
                                break;
                            case 'no_9':
                                action = 'on_number_9';
                                break;
                            case 'play':
                                action = 'on_control_play';
                                break;
                            case 'pause':
                                action = 'on_control_pause';
                                break;
                            case 'guide':
                                action = 'on_control_guide';
                                break;
                            case 'volume_up ':
                                action = 'on_control_volumeup';
                                break;
                            case 'volume_down ':
                                action = 'on_control_volumedown';
                                break;
                        }
                        return action;
                    }

                    function processAction(action) {
                        if (action && isFrozen == false) {
                            if (!numericQueue) {
                                numericQueue = [];
                            }

                            TimeoutUtil.cancel(sendEventHandler);
                            sendEventHandler = undefined;

                            // check for numeric repetition
                            if (action.toLowerCase().indexOf('on_number_') > -1) {
                                var number = parseInt(action.toLowerCase().replace('on_number_', ''), 10);
                                numericQueue.push(number);

                                if (numericQueue.length >= maxNumericInput) {
                                    sendEvent();
                                } else {
                                    sendEventHandler = $timeout(sendEvent, numericTimeout);
                                }
                            } else {
                                sendEvent(action);
                            }
                        }
                    }

                    function sendEvent(event) {
                        if (numericQueue.length > 0) {
                            event = numericQueue.join('');
                            var params = {
                                numbers: numericQueue.join(''),
                                button: numericQueue.join('')
                            };
                            // send number specific event
                            scriptingService.raiseEvent('element_' + $scope.id, 'on_number_' + numericQueue.join(''), params);
                            // send number generic event
                            scriptingService.raiseEvent('element_' + $scope.id, 'on_number', params);
                        } else {
                            var params = {
                                commands: event.replace('on_control_', ''),
                                command: event.replace('on_control_', ''),
                                button: event.replace('on_control_', '')
                            };
                            // send command specific event
                            scriptingService.raiseEvent('element_' + $scope.id, event, params);
                            // send command generic event
                            scriptingService.raiseEvent('element_' + $scope.id, 'on_command', params);
                        }

                        // send generic event
                        var params = {
                            button: event.replace('on_control_', '').replace('on_number_', '')
                        };
                        scriptingService.raiseEvent('element_' + $scope.id, 'onbuttonpressed', params);

                        numericQueue = [];
                    }

                    function unbindRemoteEvent() {
                        halRemoteService.unRegisterEvents(onRemoteEvent);
                        remoteEventBinded = false;
                    }

                    $scope.destroy = function() {
                        unbindRemoteEvent();
                    };

                    $scope.freeze = function() {
                        var promise = $q.defer();
                        isFrozen = true;
                        unbindRemoteEvent();
                        promise.resolve();
                        return promise.promise;
                    };

                    $scope.unfreeze = function() {
                        var promise = $q.defer();
                        isFrozen = false;
                        if (!remoteEventBinded) {
                            halRemoteService.registerEvents(onRemoteEvent);
                            remoteEventBinded = true;
                        }
                        promise.resolve();
                        return promise.promise;
                    };

                    $scope.wakeup = function() {
                        var promise = $q.defer();
                        promise.resolve();
                        return promise.promise;
                    };

                    $scope.sleep = function() {
                        var promise = $q.defer();
                        promise.resolve();
                        return promise.promise;
                    };

                    $scope.executeAction = function(action) {
                        var a = action.name.toLowerCase();
                        switch (a) {
                            case 'emulatebutton':
                                if (action.button) {
                                    processAction(action.button);
                                }
                                return true;
                                break;
                        }
                        return false;
                    };
                }]
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('asSignsTableListing', ["ColorUtil", "FontUtil", "QueryUtil", "signsWidgetService", "halMediaService", function(ColorUtil, FontUtil, QueryUtil, signsWidgetService, halMediaService) {
        return {
            restrict: 'EA',
            scope: {
                id: '@',
                datasource: '=',
                configuration: '=',

                onApiReady: '&',
                onFreeze: '&',
                onUnFreeze: '&',
            },

            template: '<div style ="position: relative; width:100%; height:100%;  overflow:hidden;">' +
                '<table id="test" style="table-layout: fixed;">' +
                '<tbody><tr id="tr{{$index+1}}" ng-repeat="x in rowsData">' +
                '<td ng-attr-style=height:{{rowDefaultHeight}}px;' + '{{tableProperties["cell"+($parent.$index+1)+"_"+($index+1)]}};background-color:{{defaultBackgroundColor}};overflow:hidden; ng-repeat="y in columnsData"  colspan={{tableProperties["merge"+($parent.$index+1)+"_"+($index+1)]["colspan"]}} rowspan={{tableProperties["merge"+($parent.$index+1)+"_"+($index+1)]["rowspan"]}}><div class="{{id}}-tablelistingcell" ng-attr-style=height:auto;{{cellDivProperties["cell"+($parent.$index+1)+"_"+($index+1)]}};{{overallCellDivProperties}} >{{tableData["cell"+($parent.$index+1)+"_"+($index+1)]}}</div></td>' +
                '</tr></tbody>' +
                '</table>' +
                '</div>',

            link: function(scope, element, attrs) {
                scope.$on('$destroy', function() {
                    scope.destroy();
                });
                element.on('$destroy', function() {
                    scope.destroy();
                });

                scope.$watch('datasource', function(newVal) {
                    if (!newVal) {
                        return;
                    }

                    scope.setDatasource(newVal);
                });

                scope.$watch('configuration', function(newVal) {
                    if (!newVal) {
                        return;
                    }

                    scope.setConfiguration(newVal);
                });

                // Construct the api interface
                scope.api = {
                    id: scope.id,
                    getConfiguration: scope.getConfiguration,
                    getDatasource: scope.getDatasource,
                    setConfiguration: scope.setConfiguration,
                    setDatasource: scope.setDatasource,
                    freeze: scope.freeze,
                    unFreeze: scope.unFreeze,
                    destroy: scope.destroy,
                    retrieveDataByQuery: scope.retrieveDataByQuery,
                    executeAction: scope.executeAction
                };

                scope.init();

                if (scope.onApiReady) {
                    scope.onApiReady({api: scope.api});
                }
            },

            controller: ['$scope', '$timeout', '$q', '$element', 'networkContentRouterService', 'HttpWrapper', 'signsParserService', function($scope, $timeout, $q, $element, networkContentRouterService, HttpWrapper, signsParserService) {
                $scope.localDatasource = null;
                $scope.rowCount;
                $scope.columnCount;
                $scope.rowDefaultHeight = 30;
                $scope.columnDefaultWidth = 100;
                $scope.rowsData = [];
                $scope.columnsData = [];
                $scope.isShow = 1;
                $scope.transitionDuration = 0.5;
                $scope.fontDefaultStyle;
                $scope.pageInterval = 30000;
                $scope.datasourceQuery = '';
                $scope.pagingMode = 'autopage';
                $scope.defaultBackgroundColor = 'rgba(0,0,0,0)';

                var pagingTimeoutPromise;
                var cellProperties = [];
                var mergeProperties = [];
                var verticalBorderProperties = [];
                var horizonalBorderProperties = [];
                var columnProperties = [];
                var parserService = signsParserService;
                var isTransitioning = false;
                var widgetFrozen = true;
                var tableData;
                var oriTableProperties;
                var oriRowDefaultHeight = $scope.rowDefaultHeight;
                var dataCounter = -1;
                var customQuery = '';
                var dataPageList = [];

                $scope.init = function() {
                    signsWidgetService.subscribeToCanvasScaleUpdate($scope.onCanvasScaleChange);
                };

                $scope.destroy = function() {
                    signsWidgetService.unsubscribeToCanvasScaleUpdate($scope.onCanvasScaleChange);
                };

                $scope.tableProperties = {
                    // 'merge5_4':{colspan:3, rowspan:2}
                };

                $scope.overallCellDivProperties = 'width:inherit; overflow:hidden;';

                $scope.cellDivProperties = {
                    // 'merge5_4':{colspan:3, rowspan:2}
                };

                $scope.tableData = {

                };

                $scope.onCanvasScaleChange = function() {
                    // Recalculate config borders and such
                    var scale = signsWidgetService.getCanvasScale();
                    var scaleFactor;

                    if (scale.xScale < scale.yScale) {
                        scaleFactor = scale.xScale;
                    } else {
                        scaleFactor = scale.yScale;
                    }

                    $scope.rowDefaultHeight = oriRowDefaultHeight * scaleFactor;
                    for (var key in $scope.tableProperties) {
                        if (!oriTableProperties.hasOwnProperty(key)) {
                            continue;
                        }

                        var cssString = oriTableProperties[key];
                        $scope.tableProperties[key] = updateCssProperties(cssString, 'width', 'px', {newValue: null, valueFactor: scaleFactor});
                    }
                };

                $scope.setConfiguration = function(config) {
                    resetProperties();
                    $scope.config = config;

                    for (var key in config) {
                        switch (key.toLowerCase()) {
                            // TABLE
                            case 'custom.table.row.count':
                                $scope.rowCount = parseInt(config[key]);
                                break;
                            case 'custom.table.column.count':
                                $scope.columnCount = parseInt(config[key]);
                                break;
                            case 'custom.table.column.default.width':
                                $scope.columnDefaultWidth = config[key];
                                break;
                            case 'custom.table.row.default.height':
                                $scope.rowDefaultHeight = config[key];
                                oriRowDefaultHeight = $scope.rowDefaultHeight;
                                break;
                            case 'custom.table.default.font.style':
                                $scope.fontDefaultStyle = config[key];
                                break;
                            case 'custom.table.paging.interval':
                                $scope.pageInterval = parseInt(config[key]) * 1000;
                                break;
                            case 'custom.table.datasource.root':
                                $scope.datasourceQuery = config[key];
                                break;
                            case 'custom.table.paging.mode':
                                $scope.pagingMode = config[key];
                                break;
                            case 'custom.table.default.background.color':
                                var colorUtil = new ColorUtil();
                                $scope.defaultBackgroundColor = colorUtil.parseHTMLColor(config[key]);
                                break;
                            default:
                                // cell properties
                                if ((key.toLowerCase()).indexOf('custom.table.cell.') != -1) {
                                    cellProperties.push({key: key.toLowerCase(), value: config[key]});
                                }

                                // merge properties
                                if ((key.toLowerCase()).indexOf('custom.table.merge.') != -1) {
                                    mergeProperties.push({key: key.toLowerCase(), value: config[key]});
                                }

                                // vertical properties
                                if ((key.toLowerCase()).indexOf('custom.table.verticalborder.') != -1) {
                                    verticalBorderProperties.push({key: key.toLowerCase(), value: config[key]});
                                }

                                // horizontal properties
                                if ((key.toLowerCase()).indexOf('custom.table.horizonalborder.') != -1) {
                                    horizonalBorderProperties.push({key: key.toLowerCase(), value: config[key]});
                                }

                                // custom.table.column.2.width
                                if ((key.toLowerCase()).indexOf('custom.table.column.') != -1) {
                                    columnProperties.push({key: key.toLowerCase(), value: config[key]});
                                }

                                break;
                        }
                    }

                    doRender();
                    startPagination();
                    return $q.resolve({api: $scope.api});
                };

                $scope.setDatasource = function(datasource) {
                    if (JSON.stringify(datasource) != JSON.stringify($scope.localDatasource)) {
                        if (datasource) {
                            loadWorkBookXML(datasource.result.workbooks[0].id, datasource.result.workbooks[0].currentInstance);
                        }

                        $scope.localDatasource = datasource;
                    }

                    return $q.resolve({api: $scope.api});
                };

                $scope.getConfiguration = function() {
                    return $scope.config;
                };

                $scope.getDatasource = function() {
                    return $scope.localDatasource;
                };

                $scope.freeze = function() {
                    // console.log("freeze");
                    var deffered = $q.defer();
                    widgetFrozen = true;
                    stopPagination();
                    deffered.resolve({api: $scope.api});
                    return deffered.promise;
                };

                $scope.unFreeze = function() {
                    // console.log("unFreeze");
                    var deffered = $q.defer();
                    widgetFrozen = false;
                    $scope.onCanvasScaleChange();
                    // reset pagination
                    startPagination();
                    // render the current page
                    renderCurrentPage();
                    deffered.resolve({api: $scope.api});
                    return deffered.promise;
                };

                $scope.retrieveDataByQuery = function(query) {
                    var qu = new QueryUtil();
                    var dataQuery = qu.runQuery(tableData, query);
                    return dataQuery;
                };

                function fadeoutCurrentElement() {
                    var promise = $q.defer();
                    var oncomplete = function() {
                        isTransitioning = false;
                        promise.resolve();
                    };

                    if (!isTransitioning) {
                        isTransitioning = true;
                        var elems = $('.' + $scope.id + '-tablelistingcell');
                        if (elems.length > 0) {
                            elems.velocity({
                                opacity: 0
                            }, {
                                    duration: $scope.transitionDuration * 1000,
                                    complete: oncomplete
                                });
                        } else {
                            oncomplete();
                        }
                    } else {
                        promise.reject();
                    }

                    return promise.promise;
                }

                function fadeInCurrentElement() {
                    var promise = $q.defer();
                    var oncomplete = function() {
                        isTransitioning = false;
                        promise.resolve();
                    };

                    if (!isTransitioning) {
                        isTransitioning = true;
                        var elems = $('.' + $scope.id + '-tablelistingcell');
                        if (elems.length > 0) {
                            elems.velocity({
                                opacity: 1
                            }, {
                                    duration: $scope.transitionDuration * 1000,
                                    complete: oncomplete
                                });
                        } else {
                            oncomplete();
                        }
                    } else {
                        promise.reject();
                    }

                    return promise.promise;
                }

                function nextPage() {
                    dataCounter += 1;
                    // if data page index bigger than total page, set page index to 0
                    if (dataCounter >= dataPageList.length) {
                        dataCounter = 0;
                    }

                    renderCurrentPage();
                }

                function previousPage() {
                    dataCounter--;
                    // if data page index bigger than total page, set page index to total length
                    if (dataCounter < 0) {
                        dataCounter = dataPageList.length - 1;
                    }

                    renderCurrentPage();
                }

                function loadpage(offset, increment) {
                    if (isNaN(offset)) {
                        offset = 0;
                    }

                    if (isNaN(increment)) {
                        increment = 0;
                    }

                    var tempDataCounter = (offset + increment) - 1;

                    if (tempDataCounter < 0) {
                        tempDataCounter = 0;
                    }

                    dataCounter = tempDataCounter;
                    renderCurrentPage();
                }

                function filterTableData(text, columns) {
                    resetData();
                    customQuery = 'SELECT [sheet 1].* FROM [sheet 1] WHERE [sheet 1].' + columns + ' = "' + text + '"';
                    renderCurrentPage().then(function() {
                        // reset customQuery
                        customQuery = '';
                    });
                }

                function queryData(query) {
                    resetData();
                    customQuery = query;
                    renderCurrentPage().then(function() {
                        // reset customQuery
                        customQuery = '';
                    });
                }

                function resetData() {
                    dataCounter = -1;
                    dataPageList = [];
                }

                function renderCurrentPage() {
                    var promise = $q.defer();
                    var doResolve = function() {
                        startPagination();
                        promise.resolve();
                    };

                    var doPopulateData = function() {
                        populateData().then(doResolve, doResolve);
                    };

                    fadeoutCurrentElement().then(doPopulateData, promise.reject);
                    return promise.promise;
                }

                function populateData() {
                    var promise = $q.defer();
                    if (tableData == undefined) {
                        promise.resolve();
                        return promise.promise;
                    }

                    // populate data, only for first time
                    var i;
                    if (dataPageList.length == 0) {
                        // 1. do query
                        var qeuryStatement = 'select [' + tableData.sheets[0].name.toLowerCase() + '].* from [' + tableData.sheets[0].name.toLowerCase() + ']';
                        if ($scope.datasourceQuery != '') {
                            qeuryStatement = $scope.datasourceQuery;
                        }

                        if (customQuery != '') {
                            qeuryStatement = customQuery;
                        }

                        // 2. process dataQuery
                        var qu = new QueryUtil();
                        var dataQuery = qu.runQuery(tableData, qeuryStatement);
                        var tempRow;

                        for (i = 0; i < dataQuery[0].rows.length; i++) {
                            tempRow = i % $scope.rowCount;
                            if (tempRow == 0) {
                                var dataPage = {};
                                dataPageList.push(dataPage);
                            }

                            var tempColumnCount = 0;
                            var rows = dataQuery[0].rows[i];
                            for (var columnName in rows) {
                                var valueData = rows[columnName];
                                if (valueData != undefined) {
                                    tempColumnCount++;
                                    dataPage['cell' + (tempRow + 1) + '_' + (tempColumnCount)] = valueData;
                                }
                            }
                        }
                    }

                    // render data to UI
                    var tempObj = {};

                    // empty cells
                    for (i = 0; i < $scope.rowCount; i++) {
                        for (var j = 0; j < $scope.columnCount; j++) {
                            tempObj['cell' + (i + 1) + '_' + (j + 1)] = '';
                        }
                    }

                    // if data counter was never initialized before, initialize it now
                    if (dataCounter == -1) {
                        dataCounter = 0;
                    }

                    var datapage = dataPageList[dataCounter];
                    $scope.tableData = datapage;

                    $timeout(function() {
                        fadeInCurrentElement().then(promise.resolve, promise.resolve);
                    }, 1);
                    return promise.promise;
                }

                function startPagination() {
                    var paginationStart = false;
                    stopPagination();
                    if ($scope.pagingMode == 'autopage' && widgetFrozen == false) {
                        pagingTimeoutPromise = $timeout(nextPage, $scope.pageInterval);
                        paginationStart = true;
                    }

                    return paginationStart;
                }

                function stopPagination() {
                    if (pagingTimeoutPromise) {
                        $timeout.cancel(pagingTimeoutPromise);
                        pagingTimeoutPromise = null;
                    }
                }

                function loadWorkBookXML(workbookId, currentInstance) {
                    var deferred = $q.defer();
                    if (!currentInstance) {
                        deferred.resolve();
                        return;
                    }

                    var url = 'C:\\fastnetv2\\datasource\\' + workbookId + '\\' + currentInstance.filename;
                    // console.log(url);

                    var resolvedNetwork = function(url) {
                        halMediaService.resolveContentPath(url).then(function(feedUrl) {
                            retrieveFeedFromUrl(feedUrl, deferred);
                        }, function() {
                            retrieveFeedFromUrl(url, deferred);
                        });
                    };

                    var errorResolvedNetwork = function(err) {
                        deferred.reject('unable to load feed');
                    };

                    networkContentRouterService.resolve(url, 'signs-content-routing-service').then(resolvedNetwork, errorResolvedNetwork);
                }

                function retrieveFeedFromUrl(url, deferred) {
                    var httpWrapper = new HttpWrapper();
                    httpWrapper.handlerGet(url).then(function(res) {
                        // PARSER
                        parserService.parse('signs.parser.workbook-datatable', res.data).then(function(parsedRes) {
                            var samedata = JSON.stringify(tableData) == JSON.stringify(parsedRes.result);
                            // if data is not the same, reset markers
                            if (!samedata) {
                                tableData = parsedRes.result;
                                resetData();
                                // if widget is not frozen, re-render the widget
                                if (!widgetFrozen) {
                                    // reset paging
                                    startPagination();
                                    // re-render the current page
                                    renderCurrentPage();
                                }
                            }

                            deferred.resolve();
                        }, function(err) {
                            deferred.reject('error on parsing ' + url);
                        });
                    }, function(err) {
                        deferred.reject('error on retrieving ' + url);
                    });
                }

                // RENDER SECTION
                function resetProperties() {
                    // console.log("resetProperties");
                    cellProperties = [];
                    verticalBorderProperties = [];
                    horizonalBorderProperties = [];
                    columnProperties = [];
                }

                function doRender() {
                    // render row and column by create empty object
                    $scope.rowsData = [];
                    $scope.columnsData = [];
                    var i;
                    var j;
                    var str;
                    var strArr;
                    var column;
                    var row;
                    var keystr;
                    var cellArr;
                    var borderWidth;
                    var borderColor;
                    for (i = 0; i < $scope.rowCount; i++) {
                        $scope.rowsData.push({});
                    }

                    for (i = 0; i < $scope.columnCount; i++) {
                        $scope.columnsData.push({});
                    }

                    // set all default cell properties

                    var colorUtil = new ColorUtil();
                    for (i = 0; i < $scope.rowCount; i++) {
                        for (j = 0; j < $scope.columnCount; j++) {
                            $scope.tableProperties['cell' + (i + 1) + '_' + (j + 1)] = 'border-collapse: collapse; width:' + $scope.columnDefaultWidth + 'px;';
                            if ($scope.fontDefaultStyle) {
                                $scope.tableProperties['cell' + (i + 1) + '_' + (j + 1)] += FontUtil.reformatStyleToCSS($scope.fontDefaultStyle);
                            }
                        }
                    }

                    // set cell properties
                    for (i = 0; i < cellProperties.length; i++) {
                        str = cellProperties[i].key.replace('custom.table.cell.', '');
                        strArr = str.split('.');
                        column = strArr[0];
                        row = strArr[1];
                        strArr = str.replace(column + '.' + row + '.', '');

                        switch (strArr) {
                            case 'background.color':
                                $scope.tableProperties['cell' + row + '_' + column] += ' background-color:' + colorUtil.parseHTMLColor(cellProperties[i].value) + '!important;';
                                break;
                            case 'font.style':
                                $scope.tableProperties['cell' + row + '_' + column] += FontUtil.reformatStyleToCSS(cellProperties[i].value) + '';
                                break;
                            case 'wordwrap':
                                var isWordwrap = (cellProperties[i].value == 'false') ? false : true;
                                if (isWordwrap == true) {
                                    if (!$scope.cellDivProperties['cell' + row + '_' + column]) {
                                        $scope.cellDivProperties['cell' + row + '_' + column] = '';
                                    }

                                    $scope.cellDivProperties['cell' + row + '_' + column] += 'word-wrap:break-word; ';
                                }

                                break;
                            default:
                                break;
                        }
                    }

                    // set merge properties
                    for (i = 0; i < mergeProperties.length; i++) {
                        str = mergeProperties[i].key.replace('custom.table.merge.', '');
                        strArr = str.split('.');
                        column = parseInt(strArr[0]);
                        row = parseInt(strArr[1]);
                        var mergeValue = mergeProperties[i].value;
                        var mergeValueArr = mergeValue.split('.');
                        var colspan = parseInt(mergeValueArr[0]);
                        var rowspan = parseInt(mergeValueArr[1]);

                        // set apply colspan and rowspan
                        $scope.tableProperties['merge' + row + '_' + column] = {colspan: colspan, rowspan: rowspan};

                        // hide merged cell (accept first cell)
                        for (j = column; j < column + colspan; j++) {
                            for (var k = row; k < row + rowspan; k++) {
                                // skip first cell
                                if (!(j === column && k === row)) {
                                    $scope.tableProperties['cell' + k + '_' + j] += 'display:none' + ';';
                                }
                            }
                        }
                    }

                    // set vertical border properties
                    for (i = 0; i < verticalBorderProperties.length; i++) {
                        var vpobj = verticalBorderProperties[i];
                        keystr = vpobj.key;
                        var valstr = vpobj.value;

                        // keystr manipulation to get column n row number
                        keystr = keystr.replace('custom.table.verticalborder.', '');
                        cellArr = keystr.split('.');
                        column = cellArr[0];
                        row = cellArr[1];

                        // valstr manipulation to get multiple value
                        var valstrArr = valstr.split(',');
                        borderWidth = parseInt(valstrArr[0]);
                        borderColor = colorUtil.parseHTMLColor(valstrArr[1]);

                        if ((column <= $scope.columnCount + 1) && (row <= $scope.rowCount + 1)) {
                            if (column == $scope.columnCount + 1) {
                                // console.log("cell" + row + "_" + (column-1) + " : RIGHT");
                                $scope.tableProperties['cell' + row + '_' + (column - 1)] += 'border-right-style: solid; border-right-width:' + borderWidth + 'px; border-right-color:' + borderColor + ';';
                            } else {
                                // console.log("cell" + row + "_" + (column) + " : LEFT ");
                                $scope.tableProperties['cell' + row + '_' + column] += 'border-left-style: solid; border-left-width:' + borderWidth + 'px; border-left-color:' + borderColor + ';';
                            }
                        }
                    }

                    // set horizonal border properties
                    for (i = 0; i < horizonalBorderProperties.length; i++) {
                        var horzobj = horizonalBorderProperties[i];
                        keystr = horzobj.key;
                        var horzstr = horzobj.value;

                        // keystr manipulation to get column n row number
                        keystr = keystr.replace('custom.table.horizonalborder.', '');
                        cellArr = keystr.split('.');
                        column = cellArr[0];
                        row = cellArr[1];

                        // valstr manipulation to get multiple value
                        var horzstrArr = horzstr.split(',');
                        borderWidth = parseInt(horzstrArr[0]);
                        borderColor = colorUtil.parseHTMLColor(horzstrArr[1]);

                        if ((column <= $scope.columnCount + 1) && (row <= $scope.rowCount + 1)) {
                            if (row == $scope.rowCount + 1) {
                                // console.log("cell" + (row-1) + "_" + (column) + " : BOTTOM");
                                $scope.tableProperties['cell' + (row - 1) + '_' + column] += 'border-bottom-style: solid; border-bottom-width:' + borderWidth + 'px; border-bottom-color:' + borderColor + ';';
                            } else {
                                // console.log("cell" + row + "_" + (column) + " : TOP ");
                                $scope.tableProperties['cell' + row + '_' + column] += 'border-top-style: solid; border-top-width:' + borderWidth + 'px; border-top-color:' + borderColor + ';';
                            }
                        }
                    }

                    // set column properties
                    for (i = 0; i < $scope.rowCount; i++) {
                        for (j = 0; j < columnProperties.length; j++) {
                            str = columnProperties[j].key.replace('custom.table.column.', '');
                            strArr = str.split('.');
                            column = strArr[0];
                            var properties = strArr[1];
                            var value = columnProperties[j].value;

                            switch (properties) {
                                case 'width':
                                    var cssStr = $scope.tableProperties['cell' + (i + 1) + '_' + (column)];
                                    cssStr = updateCssProperties(cssStr, 'width', 'px', {newValue: value});
                                    $scope.tableProperties['cell' + (i + 1) + '_' + (column)] = cssStr;
                                    break;
                                default:
                                    break;
                            }
                        }
                    }

                    // clone and store original value from server
                    oriTableProperties = $angular.copy($scope.tableProperties);
                }

                // :: UTILS

                // to update a value in css string. option = {newValue:20, valueFactor:scalefactor})
                function updateCssProperties(cssString, propName, propUnit, option) {
                    if (cssString) {
                        if ((typeof cssString) == 'string') {
                            if (cssString.indexOf(propName + ':') >= 0) {
                                var regPropName = '\\' + propName + ':';
                                var regPropUnit = '\\' + propUnit;
                                var oriStr = cssString.match(new RegExp(regPropName + '(.*?)' + regPropUnit, 'g'))[0];
                                var valStr = oriStr.replace(new RegExp(regPropName + '|' + propUnit, 'g'), '');
                                var newVal;

                                if (option.valueFactor) {
                                    var val = parseFloat(valStr);
                                    newVal = val * option.valueFactor;
                                } else {
                                    newVal = option.newValue;
                                }

                                cssString = cssString.replace(oriStr, propName + ':' + newVal + propUnit);
                            }
                        }
                    }

                    return cssString;
                }

                $scope.executeAction = function(action) {
                    switch (action.name.toLowerCase()) {
                        case 'nextpage':
                            nextPage();
                            break;
                        case 'previouspage':
                            previousPage();
                            break;
                        case 'loadpage':
                            loadpage(parseInt(action.offset, 10), parseInt(action.increment, 10));
                            break;
                        case 'filtertabledata':
                            filterTableData(action.text, action.columns);
                            break;
                        case 'querydata':
                            queryData(action.query);
                            break;
                        default:
                            return false;
                    }

                    return true;
                };
            }]
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    app.directive('asSignsTextTickerWidget', function() {
        return {
            restric: 'EA',
            scope: {
                id: '@',
                datasource: '=',
                configuration: '=',

                onApiReady: '&',
                onFreeze: '&',
                onUnFreeze: '&'
            },
            template: '<as-ui-ticker on-api-ready="directiveAPIReady(api)"'
                + ' auto-start="true" speed="{{tickerSpeed}}"'
                + ' spacing="{{tickerSpacing}}" direction="{{tickerDirection}}"'
                + ' content-item="currentItem" on-ticker-completed="onTickerCompleted(state)"></as-ui-ticker>',
            link: function(scope, element, attrs) {
                scope.$on('$destroy', function() {
                    scope.destroy();
                });

                element.on('$destroy', function() {
                    scope.destroy();
                });

                scope.$watch('datasource', function(newVal) {
                    if (!newVal) {
                        return;
                    }
                    scope.setDatasource(newVal);
                });

                scope.$watch('configuration', function(newVal) {
                    if (!newVal) {
                        return;
                    }
                    scope.setConfiguration(newVal);
                });

                scope.api = {
                    id: scope.id,
                    getConfiguration: scope.getConfiguration,
                    getDatasource: scope.getDatasource,
                    setConfiguration: scope.setConfiguration,
                    setDatasource: scope.setDatasource,
                    freeze: scope.freeze,
                    unFreeze: scope.unFreeze,
                    wakeup: scope.wakeup,
                    sleep: scope.sleep,
                    destroy: scope.destroy,
                    executeAction: scope.executeAction,
                };

                if (scope.onApiReady) {
                    scope.onApiReady({api: scope.api});
                }
            },
            controller: ["$scope", "$element", "$q", "$timeout", "FontUtil", "scriptingService", "tickerUtil", function($scope, $element, $q, $timeout, FontUtil, scriptingService, tickerUtil) {
                var tickerItems;
                var directiveAPI;
                var configSpeed = 'medium';
                var configTickerFont = '';
                var configTickerDirection;
                var configTickerSpacing = 50;
                var isFrozen = true;
                var unfreezeTimeout = null;
                var currentTickerItemIndex = 0;
                var currentConfigTickerFont = '';
                var configTickerRenderType = 'multiple';
                var dataSourceItems = [];

                $scope.tickerSpeed = 100;
                $scope.tickerSpacing = 50;
                $scope.tickerDirection = 1; // 1 = right to left; 2 = left to right; 3 = bottom to top; 4 = top to bottom

                $scope.directiveAPIReady = function(api) {
                    directiveAPI = api;
                };

                $scope.destroy = function() {
                    $scope.currentItem = null;
                    if (unfreezeTimeout) {
                        $timeout.cancel(unfreezeTimeout);
                        unfreezeTimeout = null;
                    }
                    if (directiveAPI) {
                        if (directiveAPI.destroy) {
                            directiveAPI.destroy();
                        }
                        directiveAPI = null;
                    }
                    $element.empty();
                    return;
                };

                function generateTickerItems(items) {
                    var res = [];
                    for (var i = 0; i < items.length; i++) {
                        var item = items[i];
                        var tickerItem = {};
                        if (item.type.toLowerCase() != 'text') {
                            continue;
                        }
                        item.title = item.title.split('"').join('&quot;');
                        tickerItem.type = 'markup';
                        tickerItem.data = '<div style="white-space:pre;' + configTickerFont + 'position:relative;">' + item.title + '</div>';
                        res.push(tickerItem);
                    }
                    return res;
                }

                function render() {
                    tickerItems = [];
                    if (!dataSourceItems) {
                        return;
                    }
                    tickerItems = generateTickerItems($angular.copy(dataSourceItems));
                    currentTickerItemIndex = 0;
                    if (!isFrozen) {
                        $scope.currentItem = retrieveTickerItems();
                    }
                    scriptingService.raiseEvent('element_' + $scope.id, 'ontickeritemavailable', tickerItems);
                }

                $scope.setDatasource = function(datasource, shouldAppend) {
                    // Set the ticker items only if there is data supplied
                    if (!datasource || !datasource.result || !datasource.result.playlist) {
                        return;
                    }
                    var items = datasource.result.playlist.items;
                    dataSourceItems = shouldAppend ? dataSourceItems.concat(items) : items;
                    render();
                    return $q.resolve();
                };

                $scope.onTickerCompleted = function(state) {
                    if (configTickerRenderType != 'single') {
                        return;
                    }
                    currentTickerItemIndex++;
                    currentTickerItemIndex = currentTickerItemIndex >= tickerItems.length ? 0 : currentTickerItemIndex;
                    $scope.currentItem = retrieveTickerItems();
                };

                function retrieveTickerItems() {
                    return configTickerRenderType == 'single' ? [tickerItems[currentTickerItemIndex]] : tickerItems;
                }

                $scope.setConfiguration = function(configuration) {
                    currentConfigTickerFont = configTickerFont || currentConfigTickerFont;
                    for (var keyName in configuration) {
                        var key = keyName;
                        var value = configuration[keyName];
                        switch (key.toLowerCase()) {
                            case 'custom.textticker.text.font':
                                configTickerFont = FontUtil.reformatStyleToCSS(value);
                                break;
                            case 'custom.ticker.direction':
                                configTickerDirection = value.toLowerCase();
                                break;
                            case 'custom.ticker.spacing':
                                configTickerSpacing = parseInt(value, 10);
                                break;
                            case 'custom.ticker.speed':
                                configSpeed = value.toLowerCase();
                                break;
                            case 'custom.ticker.render.type':
                                configTickerRenderType = (value.toLowerCase() == 'single') ? 'single' : 'multiple';
                                break;
                        }
                    }
                    setSpeed({speed: configSpeed});
                    setDirection({direction: configTickerDirection});
                    if (dataSourceItems) {
                        render();
                    }
                    // ignore spacing if single item
                    configTickerSpacing = configTickerRenderType == 'single' ? 0 : configTickerSpacing;
                    $scope.tickerSpacing = configTickerSpacing;
                    return $q.resolve();
                };

                function setSpeed(action) {
                    var validSpeed = tickerUtil.getTickerSpeed(action.speed);
                    if (!validSpeed) {
                        return;
                    }
                    $scope.tickerSpeed = validSpeed;
                }

                function setDirection(action) {
                    var validDirection = tickerUtil.getTickerDirection(action.direction);
                    if (!validDirection) {
                        return;
                    }
                    $scope.tickerDirection = validDirection;
                }

                $scope.freeze = function() {
                    isFrozen = true;
                    directiveAPI.pause();
                    if ($scope.onFreeze) {
                        $scope.onFreeze({api: $scope.api});
                    }
                    return $q.resolve();
                };

                $scope.unFreeze = function() {
                    if (unfreezeTimeout) {
                        $timeout.cancel(unfreezeTimeout);
                        unfreezeTimeout = null;
                    }
                    isFrozen = false;
                    unfreezeTimeout = $timeout(function() {
                        $scope.currentItem = retrieveTickerItems();
                        if (directiveAPI) {
                            directiveAPI.play();
                        }
                    }, 1000);

                    if ($scope.onFreeze) {
                        $scope.onUnFreeze({api: $scope.api});
                    }
                    return $q.resolve();
                };

                $scope.wakeup = function() {
                    return $q.resolve({api: $scope.api});
                };

                $scope.sleep = function() {
                    return $q.resolve({api: $scope.api});
                };

                $scope.executeAction = function(action) {
                    var actionConfig = {
                        setspeed: setSpeed,
                        setdirection: setDirection
                    };
                    var executeAction = actionConfig[action.name.toLowerCase()];
                    if (!executeAction) {
                        return false;
                    }
                    executeAction(action);
                    return true;
                };
            }]
        };
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    app.directive('asSignsWeatherWidget', function() {
        return {
            restric: 'EA',
            scope: {
                id: '@',
                datasource: '=',
                configuration: '=',
                onApiReady: '&',
                onFreeze: '&',
                onUnFreeze: '&'
            },
            template: '<div style="width:100%;height:100%"></div>',
            link: function(scope, element) {
                scope.$on('$destroy', function() {
                    scope.destroy();
                });
                element.on('$destroy', function() {
                    scope.destroy(true);
                });

                scope.$watch('datasource', function(newVal) {
                    if (!newVal) {
                        return;
                    }

                    scope.setDatasource(newVal);
                });

                scope.$watch('configuration', function(newVal) {
                    if (!newVal) {
                        return;
                    }

                    scope.setConfiguration(newVal);
                });

                scope.api = {
                    id: scope.id,
                    getConfiguration: scope.getConfiguration,
                    getDatasource: scope.getDatasource,
                    setConfiguration: scope.setConfiguration,
                    setDatasource: scope.setDatasource,
                    destroy: scope.destroy,
                    freeze: scope.freeze,
                    unFreeze: scope.unFreeze,
                    wakeup: scope.wakeup,
                    sleep: scope.sleep
                };

                if (scope.onApiReady) {
                    scope.onApiReady({api: scope.api});
                }
            },
            controller: ["$scope", "$q", "$timeout", "$element", "$compile", "FontUtil", "networkContentRouterService", "DateTimeUtil", "halPlatformService", "halMediaService", function($scope, $q, $timeout, $element, $compile, FontUtil, networkContentRouterService, DateTimeUtil, halPlatformService, halMediaService) {
                var assetDatasource;
                var graphicsDatasource;
                var subDatasource;

                var configReady = false;
                var datasourceReady = false;
                var progressTimerPromise;
                var lastSnapshotUpdate;
                var rolloutDuration;
                var currentElement;
                var loadingTemplate;
                var previousTemplate;
                var currentSubscription;
                var currentEntry;
                var previousSubscriptionString;
                var childAPIs = [];
                var isAutoForecast;
                var currentData;
                var dataArray = {
                    'fact.': 'facts',
                    'label.': 'labels',
                    'asset.': 'assets'
                };

                $scope.setDatasource = function(datasource) {
                    datasourceReady = false;
                    assetDatasource = null;
                    graphicsDatasource = null;
                    subDatasource = null;

                    if (datasource && datasource.result && datasource.result.weather) {
                        assetDatasource = $angular.copy(datasource.result.weather.assets);
                        subDatasource = $angular.copy(datasource.result.weather.subscriptions);

                        var i;
                        // convert forecast date to date object
                        for (i = 0; i < subDatasource.length; i++) {
                            var sub = subDatasource[i];
                            for (var j = 0; j < sub.forecast.length; j++) {
                                var forecast = sub.forecast[j];
                                forecast.date = new Date(forecast.date);
                            }
                        }

                        graphicsDatasource = {};
                        // convert graphics into lookup table
                        for (i = 0; i < datasource.result.weather.graphics.length; i++) {
                            var graphic = datasource.result.weather.graphics[i];
                            graphicsDatasource['code_' + graphic.weatherCode] = graphic;
                        }
                    }

                    var tempSubString = JSON.stringify(subDatasource);
                    if (previousSubscriptionString != tempSubString) {
                        currentSubscription = -1;
                        currentEntry = -1;
                        previousSubscriptionString = tempSubString;
                    }

                    // reset snapshot as we have new data
                    lastSnapshotUpdate = undefined;
                    datasourceReady = true;
                    return $q.resolve();
                };

                $scope.setConfiguration = function(configuration) {
                    configReady = false;
                    resetProperties();
                    for (var key in configuration) {
                        var value = configuration[key];
                        if (convertConfigurationToBaseData(key, value)) {
                            continue;
                        }

                        // custom settings overwrite
                        if (key.toLowerCase() === 'custom.weather.general.rolloutduration') {
                            rolloutDuration = parseInt(value, 10) * 1000;
                        }
                    }

                    // convert remaining fact values
                    processFactValues();
                    configReady = true;
                    forceRender();
                    return $q.resolve();
                };

                function forceRender() {
                    if (!progressTimerPromise || !halPlatformService.getFeatureSupport('playback.mode.editor')) {
                        return;
                    }

                    lastSnapshotUpdate = undefined;
                    startProgressTimer();
                }

                function processFactValues() {
                    isAutoForecast = false;
                    for (var key in currentData.facts) {
                        var elem = currentData.facts[key];
                        if (!elem.bindtofact) {
                            elem.bindtofact = '';
                        }

                        if (!isAutoForecast && elem.bindtoday === 'auto') {
                            isAutoForecast = true;
                        }

                        var format = '';
                        switch (elem.bindtofact.toLowerCase()) {
                            case 'weathercode':
                                var formattings = elem.formatting.split(';');
                                var stretch = 'fill';
                                for (var i = 0; i < formattings.length; i++) {
                                    format = formattings[i];

                                    if (format.toLowerCase().indexOf('stretch=') > -1) {
                                        switch (format.toLowerCase().split('=')[1]) {
                                            case '2':
                                                stretch = 'fill';
                                                break;
                                            case '3':
                                                stretch = 'uniformfill';
                                                break;
                                            default:
                                                stretch = 'uniform';
                                                break;
                                        }
                                    }
                                }

                                formattings = [];
                                formattings = null;
                                elem.formatting = {
                                    stretch: stretch
                                };
                                break;
                            case 'date':
                                var dateTimeUtil = new DateTimeUtil();
                                format = dateTimeUtil.convertWindowsFormatDateToMoment(elem.formatting.split('=')[1]);
                                elem.formatting = {
                                    dateformat: format
                                };
                                break;
                            case 'location':
                                var location = elem.formatting;
                                if (location) {
                                    location = location.split('=')[1];
                                } else {
                                    location = 'city';
                                }

                                elem.formatting = {
                                    location: location
                                };
                                break;
                            case 'dewpoint':
                            case 'temperature':
                                var unit = elem.formatting;
                                if (unit) {
                                    unit = unit.split('scale=').join('');
                                } else {
                                    unit = 'C';
                                }

                                elem.formatting = {
                                    unit: unit
                                };
                                break;
                        }
                    }
                }

                function processCommonValue(key, value) {
                    if (key === 'placement') {
                        return convertPlacementStringToPosition(value);
                    }

                    if (key === 'text.style') {
                        return FontUtil.reformatStyleToCSS(value);
                    }

                    if (key === 'layer') {
                        return parseInt(value, 10);
                    }

                    return value;
                }

                $scope.destroy = function(ignoreElement) {
                    for (var i = 0; i < childAPIs.length; i++) {
                        var api = childAPIs[i];
                        if (api && api.unload) {
                            api.unload();
                        }

                        if (api && api.destroy) {
                            api.destroy();
                        }

                        api = null;
                    }

                    childAPIs = [];
                    clear();

                    if (!$element) {
                        return;
                    }

                    $element.empty();

                    if (ignoreElement) {
                        return;
                    }

                    if ($element.remove) {
                        $element.remove();
                    }

                    $element = null;
                };

                $scope.freeze = function() {
                    if ($scope.onFreeze) {
                        $scope.onFreeze({api: $scope.api});
                    }

                    clear();
                    return $q.resolve();
                };

                $scope.unFreeze = function() {
                    if ($scope.onUnFreeze) {
                        $scope.onUnFreeze({api: $scope.api});
                    }

                    // on unfreeze, set last snapshot update to now if we've updated it before
                    if (lastSnapshotUpdate) {
                        lastSnapshotUpdate = new Date();
                    }

                    startProgressTimer();
                    return $q.resolve();
                };

                $scope.wakeup = function() {
                    return $q.resolve({api: $scope.api});
                };

                $scope.sleep = function() {
                    return $q.resolve({api: $scope.api});
                };

                function startProgressTimer() {
                    stopProgressTimer();
                    progressTimerPromise = $timeout(doProgress, 1000);
                }

                function stopProgressTimer() {
                    if (!progressTimerPromise) {
                        return;
                    }

                    $timeout.cancel(progressTimerPromise);
                    progressTimerPromise = undefined;
                }

                function doProgress() {
                    stopProgressTimer();
                    if (configReady && datasourceReady &&
                        (!lastSnapshotUpdate || (new Date().getTime() - lastSnapshotUpdate.getTime() >= rolloutDuration))) {
                        lastSnapshotUpdate = new Date();
                        render();
                    }

                    startProgressTimer();
                }

                function render() {
                    updateCurrentSnapshot();
                    loadingTemplate = createTemplate();
                    if (loadingTemplate === previousTemplate) {
                        return;
                    }

                    previousTemplate = loadingTemplate;
                    removeDataFromVisual().then(appendDataToVisual);
                }

                function updateCurrentSnapshot() {
                    // if 1 or more elements subscribe forecast using auto-progress
                    if (isAutoForecast) {
                        if (currentSubscription == -1) {
                            currentSubscription = 0;
                        }

                        var sub = subDatasource[currentSubscription];
                        // progress current entry
                        currentEntry++;
                        // if current entry is more than current sub's forecast, go to next sub
                        if (currentEntry >= sub.forecast.length) {
                            currentEntry = 0;
                            currentSubscription++;
                        }
                    } else {
                        // if all of elements subscribe to single forecast
                        // progress next subscription
                        currentSubscription++;
                    }

                    if (currentSubscription >= subDatasource.length) {
                        currentSubscription = 0;
                        currentEntry = 0;
                    }
                }

                function createTemplate() {
                    var template = '<div style="opacity:0;position:inherit">';
                    template += createElementTemplate(currentData.facts, 'fact');
                    template += createElementTemplate(currentData.labels, 'label');
                    template += createElementTemplate(currentData.assets, 'asset');
                    template += '</div>';
                    return template;
                }

                function clear() {
                    stopProgressTimer();
                    if (currentElement) {
                        currentElement.scope.$destroy();
                        currentElement.remove();
                        currentElement = null;
                    }

                    previousTemplate = null;
                    lastSnapshotUpdate = undefined;
                }

                function appendDataToVisual() {
                    if (!loadingTemplate) {
                        return;
                    }

                    var isolatedScope = $scope.$new();
                    childAPIs = [];
                    currentElement = $compile(loadingTemplate)(isolatedScope);
                    currentElement.scope = isolatedScope;
                    $($element.children()[0]).append(currentElement);
                    $(currentElement).velocity({opacity: 1}, {duration: 500});
                }

                function removeDataFromVisual() {
                    if (!currentElement) {
                        return $q.resolve();
                    }

                    var promise = $q.defer();
                    $(currentElement).stop().velocity({
                        opacity: 0
                    }, {
                        duration: 500,
                        complete: function() {
                            for (var i = 0; i < childAPIs.length; i++) {
                                var api = childAPIs[i];
                                if (api && api.unload) {
                                    api.unload();
                                }

                                if (api && api.destroy) {
                                    api.destroy();
                                }

                                api = undefined;
                            }

                            childAPIs = undefined;
                            currentElement.empty();
                            currentElement.remove();
                            currentElement.scope.$destroy();
                            currentElement = undefined;
                            promise.resolve();
                        }
                    });
                    return promise.promise;
                }

                function setContentUrl(content, api) {
                    var mediaFile = (content || {}).mediaFile;
                    if (!mediaFile) {
                        return;
                    }

                    var setUrl = function(url) {
                        if (!api || !api.setUrl) {
                            return;
                        }

                        api.setUrl(url).bind(api);
                    };

                    var onContentUrlResolved = function(resolvedUrl) {
                        if (!resolvedUrl) {
                            return;
                        }

                        halMediaService.resolveContentPath(resolvedUrl).then(setUrl, function() {
                            setUrl(resolvedUrl);
                        });
                    };

                    networkContentRouterService.resolve(mediaFile, 'signs-content-routing-service').then(
                        onContentUrlResolved, setUrl);
                }

                // set asset
                $scope.OnAssetAPIReady = function(api, key) {
                    var elem = currentData.assets[key];
                    if (!api.setUrl) {
                        return;
                    }

                    childAPIs.push(api);
                    if (!elem.bindtoasset) {
                        return;
                    }

                    for (var i = 0; i < assetDatasource.length; i++) {
                        var ds = assetDatasource[i];
                        if (ds.name !== elem.bindtoasset || ds.type.toLowerCase() !== 'image') {
                            continue;
                        }

                        setContentUrl(ds, api);
                        break;
                    }
                };

                // set fact
                $scope.OnFactAPIReady = function(api, key, code) {
                    if (!api.setUrl) {
                        return;
                    }

                    childAPIs.push(api);
                    var graphic = graphicsDatasource['code_' + code];

                    if (!graphic ||
                        !graphic.mediaFile ||
                        !graphic.type ||
                        graphic.type.toLowerCase() !== 'image') {
                        return;
                    }

                    setContentUrl(graphic, api);
                };

                function createElementTemplate(elements, type) {
                    var template = '';

                    for (var key in elements) {
                        var elem = elements[key];

                        // left,top,width,height
                        var placement = elem.placement;
                        // text style
                        var textStyle = '';

                        if (elem['text.style'] && elem['text.style'] != '') {
                            textStyle = elem['text.style'];
                        }

                        // construct template
                        var style = 'position:absolute;left:' + placement.left + '%;top:' + placement.top + '%;width:' + placement.width + '%;height:' + placement.height + '%;';
                        style += 'overflow:hidden;';
                        style += 'z-index:' + elem.layer + ';';
                        style += textStyle;

                        var itemTemplate = '<div style="' + style + '">';

                        switch (type) {
                            case 'label':
                                itemTemplate += '<as-media-text text="' + elem.text + '"></as-media-text>';
                                break;
                            case 'asset':
                                itemTemplate += '<as-media-image on-api-ready="OnAssetAPIReady(api,\'' + key + '\')" style="width:100%; height:100%; position:absolute;" url="" stretch="fill"></as-media-image>';
                                break;
                            case 'fact':
                                itemTemplate += createFactTemplate(elem, key);
                                break;
                            default:
                                itemTemplate += key;
                                break;
                        }

                        itemTemplate += '</div>';
                        template += itemTemplate;
                    }

                    return template;
                }

                function createFactTemplate(elem, key) {
                    var res = '';
                    var subIndex = currentSubscription;
                    if (elem.bindtosubscription.toLowerCase() != 'auto') {
                        subIndex = parseInt(elem.bindtosubscription, 10);
                    }

                    var entryIndex = currentEntry;
                    if (elem.bindtoday.toLowerCase() != 'auto') {
                        entryIndex = parseInt(elem.bindtoday, 10);
                    }

                    var sub = subDatasource[subIndex];
                    var entry;
                    if (sub && sub.forecast[entryIndex]) {
                        entry = sub.forecast[entryIndex];
                    }

                    if (!entry) {
                        return '';
                    }

                    switch (elem.bindtofact.toLowerCase()) {
                        case 'weathercode':
                            var stretch = (elem.formatting.stretch) ? elem.formatting.stretch : 'fill';
                            res = '<as-media-image on-api-ready="OnFactAPIReady(api,\'' + key + '\',\'' + entry.weatherCode + '\')" stretch="' + stretch + '" url="" style="width:100%; height:100%; position:absolute;"></as-media-image>';
                            break;
                        case 'date':
                            var format = elem.formatting.dateformat;
                            res = '<as-ui-date-time format="' + format + '" date="' + entry.date.getTime() + '" auto-update="false"></as-ui-date-time>';
                            break;
                        case 'location':
                            var location = (elem.formatting.location) ? elem.formatting.location : 'city';
                            location = location.split('city').join(sub.location.city);
                            location = location.split('state').join(sub.location.state);
                            location = location.split('country').join(sub.location.country);
                            res = '<as-media-text text="' + location + '"></as-media-text>';
                            break;
                        case 'condition':
                            res = '<as-media-text text="' + entry.weatherData.condition + '"></as-media-text>';
                            break;
                        case 'dewpoint':
                        case 'temperature':
                            var temperature = (elem.bindtofact.toLowerCase() == 'temperature') ? entry.temperature : parseInt(entry.weatherData.dewpoint, 10);
                            var unit = (elem.formatting.unit) ? elem.formatting.unit : 'C';
                            if (unit == 'C') {
                                temperature = convertToCelcius(temperature);
                            }

                            res = '<as-media-text text="' + temperature + '&deg;"></as-media-text>';
                            break;
                        case 'humidity':
                        case 'pressure':
                            var text = (elem.bindtofact.toLowerCase() == 'humidity') ? entry.weatherData.humidity : entry.weatherData.pressure;
                            if (isNaN(text)) {
                                text = '-';
                            }

                            res = '<as-media-text text="' + text + '"></as-media-text>';
                            break;
                        case 'wind':
                            res = '<as-media-text text="' + entry.weatherData.wind + '"></as-media-text>';
                            break;
                    }

                    return res;
                }

                function convertToCelcius(number) {
                    return Math.round(((number - 32) * 5) / 9);
                }

                function resetProperties() {
                    currentData = {
                        facts: {},
                        labels: {},
                        assets: {}
                    };
                    isAutoForecast = undefined;
                    rolloutDuration = 5000;
                }

                function getBaseDataAndPropertyKey(key) {
                    var genericProperty = 'custom.weather.';
                    for (var dataKey in dataArray) {
                        if (key.toLowerCase().indexOf(dataKey) !== genericProperty.length) {
                            continue;
                        }

                        return {base: dataArray[dataKey], key: genericProperty + dataKey};
                    }
                }

                function convertConfigurationToBaseData(key, configuration) {
                    var data = getBaseDataAndPropertyKey(key);

                    if (!data || key.toLowerCase().indexOf(data.key) == -1) {
                        return false;
                    }

                    var baseConfig = key.split(data.key).join('');
                    var baseKey = baseConfig.split('.')[0];
                    var configKey = baseConfig.split(baseKey + '.').join('').toLowerCase();

                    currentData[data.base][baseKey] = currentData[data.base][baseKey] || {};
                    currentData[data.base][baseKey][configKey] = processCommonValue(configKey, configuration);

                    return true;
                }

                function convertPlacementStringToPosition(value) {
                    var arr = value.split(',');
                    var result = {left: parseFloat(arr[0]) * 100, top: parseFloat(arr[1]) * 100, width: parseFloat(arr[2]) * 100, height: parseFloat(arr[3]) * 100};
                    arr = [];
                    arr = null;
                    return result;
                }
            }]
        };
    });
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('asSignsWidgetContainer', ["signsWidgetService", "halMediaService", "mediaUtil", function(signsWidgetService, halMediaService, mediaUtil) {
        return {
            restrict: 'E',
            transclude: true,
            scope: {
                onApiReady: '&',
                onMouseOver: '&',
                onMouseLeave: '&',
                onNextContentClicked: '&',
                onPreviousContentClicked: '&',
                configuration: '=',
            },

            template: function() {
                return '<div ng-mouseover="onMouseOverContainer()" ' +
                    'ng-mouseleave="onMouseLeaveContainer()" ' +
                    'style="position: relative; width: 100%; height: 100%" ' +
                    'ng-style="inlineStyle" ' +
                    '>' +
                    '<ng-transclude></ng-transclude>' +
                    '<as-content-navigation-buttons ng-if="isNavigationButtonsShown && !noContentMessage" '+
                    'on-previous-clicked="onPreviousButtonClicked()" ' +
                    'on-next-clicked="onNextButtonClicked()" ' +
                    '></as-content-navigation-buttons>' +
                    '<span ng-if="noContentMessage" ' +
                    'class="no-content-message-container" ' +
                    '>{{noContentMessage}}</span>' +
                    '</div>';
            },

            link: function(scope, element) {
                scope.config = {};

                scope.$on('$destroy', function() {
                });
                element.on('$destroy', function() {
                    scope.destroy();
                });

                scope.$watch('configuration', function(newVal) {
                    if (!newVal) {
                        return;
                    }

                    scope.setConfiguration(newVal);
                });

                // Construct the api interface
                scope.api = {
                    id: scope.id,
                    getConfiguration: scope.getConfiguration,
                    setConfiguration: scope.setConfiguration,
                    setDatasource: scope.setDatasource,
                    destroy: scope.destroy,
                    showNavigationButtons: scope.showNavigationButtons,
                    hideNavigationButtons: scope.hideNavigationButtons,
                    showNoContentMessage: scope.showNoContentMessage,
                    hideNoContentMessage: scope.hideNoContentMessage
                };

                scope.init();

                if (scope.onApiReady) {
                    scope.onApiReady({api: scope.api});
                }
            },

            controller: ["$scope", "$q", "$element", "networkContentRouterService", "syncService", "ColorUtil", "dataUtil", "mediaUtil", function(
                $scope,
                $q,
                $element,
                networkContentRouterService,
                syncService,
                ColorUtil,
                dataUtil,
                mediaUtil
            ) {
                $scope.isNavigationButtonsShown = false;
                $scope.noContentMessage = undefined;
                var DEFAULT_INLINE_STYLE = undefined;
                var DEFAULT_NO_CONTENT_MESSAGE = 'There is no content in this zone';
                var OVERFLOW_KEY = 'overflow';
                var OVERFLOW_HIDDEN_VALUE = 'hidden';
                var BORDER_KEY = 'border';
                var WIDGET_STANDARD_PROPERTIES = {
                    CSS_STYLES: 'standard.cssstyles'
                };
                $scope.inlineStyle = DEFAULT_INLINE_STYLE;

                var convertHexToRGBA = function(hex) {
                    if (hex.length < 3) {
                        return '';
                    }

                    var colorUtil = new ColorUtil();
                    return colorUtil.parseHTMLColor(hex);
                };

                $scope.init = function() {
                    $scope.container = $element.children()[0];
                    signsWidgetService.subscribeToCanvasScaleUpdate($scope.onCanvasScaleChange);
                };

                $scope.destroy = function() {
                    signsWidgetService.unsubscribeToCanvasScaleUpdate($scope.onCanvasScaleChange);
                    $element.empty();
                };

                $scope.onCanvasScaleChange = function() {
                    // Recalc config borders and such
                    if ($scope.config) {
                        $scope.setConfiguration($scope.config);
                    }
                };

                function getBackgroundImageUrl(url) {
                    if (!url) {
                        return $q.resolve('');
                    }

                    var promise = $q.defer();
                    var opt = {
                        type: 'image'
                    };

                    var resolveBackgroundImagePath = function(backgroundImageUrl) {
                        promise.resolve(mediaUtil.toCssUrl(backgroundImageUrl));
                    };

                    networkContentRouterService.resolve(url, 'signs-content-routing-service', opt)
                        .then(function(newUrl) {
                            halMediaService.resolveContentPath(newUrl).then(resolveBackgroundImagePath, function() {
                                resolveBackgroundImagePath(newUrl);
                            });
                        }, function() {
                            resolveBackgroundImagePath(url);
                        });
                    return promise.promise;
                }

                function setBackgroundImage(url) {
                    var container = $scope.container;
                    if (container.style.backgroundImage == url) {
                        return;
                    }

                    container.style.backgroundImage = url;
                    container.style.backgroundSize = '100% 100%';
                }

                $scope.setDatasource = function(datasource) {
                    if (!datasource || !datasource.result) {
                        return $q.resolve();
                    }

                    var promise = $q.defer();
                    getBackgroundImageUrl(datasource.result.widgetBackgroundImage).then(function(url) {
                        setBackgroundImage(url);
                        promise.resolve();
                    }, promise.reject);
                    return promise.promise;
                };

                $scope.setConfiguration = function(config) {
                    var deferred = $q.defer();
                    $scope.config = config;

                    // Get the child div
                    var container = $scope.container;
                    // Get the current scale of the canvas as this affects the borders and padding
                    var scale = signsWidgetService.getCanvasScale();

                    var color;
                    // Build border style
                    $scope.border = '';
                    if (config['standard.border.width'] > 0 && config['standard.border.color']) {
                        color = convertHexToRGBA(config['standard.border.color']);
                        $scope.border = (config['standard.border.width'] * scale.yScale) + 'px solid ' + color;
                    }

                    container.style.border = $scope.border;

                    // Configure background color
                    $scope.backgroundColor = '';
                    if (config['standard.bgcolor']) {
                        color = convertHexToRGBA(config['standard.bgcolor']);
                        $scope.backgroundColor = color;
                        container.style.backgroundColor = $scope.backgroundColor;
                    }

                    // Configure background image
                    getBackgroundImageUrl(config['standard.image']).then(setBackgroundImage);

                    // Build padding style
                    $scope.padding = '';
                    if (config['standard.margin.top']) {
                        $scope.padding += ((config['standard.margin.top'] * scale.yScale) + 'px ');
                    } else {
                        $scope.padding += '0px ';
                    }

                    if (config['standard.margin.right']) {
                        $scope.padding += ((config['standard.margin.right'] * scale.xScale) + 'px ');
                    } else {
                        $scope.padding += '0px ';
                    }

                    if (config['standard.margin.bottom']) {
                        $scope.padding += ((config['standard.margin.bottom'] * scale.yScale) + 'px ');
                    } else {
                        $scope.padding += '0px ';
                    }

                    if (config['standard.margin.left']) {
                        $scope.padding += ((config['standard.margin.left'] * scale.xScale) + 'px ');
                    } else {
                        $scope.padding += '0px';
                    }

                    container.style.padding = $scope.padding;

                    var direction = null;// defaulted value to null to check if there is widget-specific configuration
                    // direction from widget configs
                    if (config['text.direction']) {
                        direction = config['text.direction'];
                    }

                    if (config['standard.text.direction']) {
                        direction = config['standard.text.direction'];
                    }

                    if (config['custom.text.direction']) {
                        direction = config['custom.text.direction'];
                    }

                    var textDirectionPromise;
                    // if there is no widget specific configuration, look for app properties
                    if (!direction) {
                        textDirectionPromise = syncService.getAppProperties('text.direction').then(function(value) {
                            container.style.direction = value;
                        }, function(err) {
                            // if no app properties, default it to ltr
                            container.style.direction = 'ltr';
                        });
                    } else {
                        container.style.direction = direction;
                    }

                    var cssInlineStyles = config[WIDGET_STANDARD_PROPERTIES.CSS_STYLES];
                    $scope.inlineStyle = DEFAULT_INLINE_STYLE;
                    if (cssInlineStyles) {
                        var inlineStyle = halMediaService.sanitizeContainerInlineStyling(
                            dataUtil.cssToJson(cssInlineStyles));

                        if (requiresOverflowHidden(inlineStyle)) {
                            inlineStyle[OVERFLOW_KEY] = OVERFLOW_HIDDEN_VALUE;
                        }

                        $scope.inlineStyle = inlineStyle;
                    }

                    var promises = [];
                    if (textDirectionPromise) {
                        promises.push(textDirectionPromise);
                    }

                    $q.all(promises).finally(function() {
                        deferred.resolve();
                    });
                    return deferred.promise;
                };

                $scope.getConfiguration = function() {
                    return $scope.config;
                };

                $scope.onMouseOverContainer = function() {
                    if (!$scope.onMouseOver) {
                        return;
                    }

                    $scope.onMouseOver();
                };

                $scope.onMouseLeaveContainer = function() {
                    if (!$scope.onMouseLeave) {
                        return;
                    }

                    $scope.onMouseLeave();
                };

                $scope.showNavigationButtons = function() {
                    $scope.isNavigationButtonsShown = true;
                };

                $scope.hideNavigationButtons = function() {
                    $scope.isNavigationButtonsShown = false;
                };

                $scope.onPreviousButtonClicked = function() {
                    if (!$scope.onPreviousContentClicked) {
                        return;
                    }

                    $scope.onPreviousContentClicked();
                };

                $scope.onNextButtonClicked = function() {
                    if (!$scope.onNextContentClicked) {
                        return;
                    }

                    $scope.onNextContentClicked();
                };

                $scope.showNoContentMessage = function(message) {
                    $scope.noContentMessage = message || DEFAULT_NO_CONTENT_MESSAGE;
                };

                $scope.hideNoContentMessage = function() {
                    $scope.noContentMessage = undefined;
                };

                function requiresOverflowHidden(style) {
                    // if we have border or border radius attributes and we don't have existing overflow
                    return (mediaUtil.containsBorderRadius(style) || Boolean(style[BORDER_KEY]))
                        && !style[OVERFLOW_KEY];
                }
            }]
        };
    }]);
})(window.angular);

(function($angular) {

    "use strict";

    var app = $angular.module('signsHTML5');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('asSignsWidgetDummy', function () {

    return {
        restrict: "EA",
        scope: {
            id: '@',
            datasource: '=',
			configuration: '=',

            onApiReady: '&',
            onFreeze: '&',
            onUnFreeze: '&',
        },

        template: '<div style ="position: relative; width:100%; height:100%; background-color:{{bgColor}}; overflow:hidden"></div>',


        link: function (scope, element, attrs) {

            var r = Math.floor(Math.random()*255);
            var g = Math.floor(Math.random()*255);
            var b = Math.floor(Math.random()*255);
            scope.bgColor = "rgba(" + r + ","  + g + "," + b + ",0.5)";
            scope.$on('$destroy', function() {
                console.log("dummy widget destroy");
            });

            element.on('$destroy', function() {
                console.log("dummy widget destroy");
            });
            scope.$watch('datasource', function(newVal) {
                if(!newVal) {
                    return;
                }
                scope.setDatasource(newVal);
            });

            scope.$watch('configuration', function(newVal) {
                if(!newVal) {
                    return;
                }
                scope.setConfiguration(newVal);
            });

            //Construct the api interface
            scope.api = {
                id: scope.id,
                getConfiguration: scope.getConfiguration,
                getDatasource: scope.getDatasource,
                setConfiguration: scope.setConfiguration,
                setDatasource: scope.setDatasource,
                freeze: scope.freeze,
                unFreeze: scope.unFreeze,
                wakeup: scope.wakeup,
                sleep: scope.sleep
            };

            if( scope.onApiReady) {
                scope.onApiReady({api:scope.api});
            }
        },

        controller: ['$scope', '$timeout', '$q', function($scope, $timeout, $q) {
            $scope.localDatasource = null;

            $scope.setConfiguration = function(config) {
                $scope.config = config;
                var deffered = $q.defer();
                deffered.resolve({api:$scope.api});
                return deffered.promise;
            };

            $scope.setDatasource = function(datasource) {
                $scope.localDatasource = datasource;
                var deffered = $q.defer();
                deffered.resolve({api:$scope.api});
                return deffered.promise;
            };

            $scope.getConfiguration = function() {
               return $scope.config;
            };

            $scope.getDatasource = function() {
                return $scope.localDatasource;
            };

            $scope.wakeup = function() {
                var deffered = $q.defer();
                deffered.resolve({api:$scope.api});
                return deffered.promise;
            };

            $scope.sleep = function() {
                var deffered = $q.defer();
                deffered.resolve({api:$scope.api});
                return deffered.promise;
            };

            $scope.freeze = function() {
                var deffered = $q.defer();
                deffered.resolve({api:$scope.api});
                return deffered.promise;
            };

            $scope.unFreeze = function() {
                var deffered = $q.defer();
                deffered.resolve({api:$scope.api});
                return deffered.promise;
            };
        }]
    };
});

})(window.angular);
(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('asSignsWidgetMediaContainer', ["FontUtil", "signsWidgetService", "dataUtil", function(FontUtil, signsWidgetService, dataUtil) {
        return {
            restrict: 'EA',
            // replace: "yes",
            scope: {
                id: '@',
                datasource: '=',
                configuration: '=',
                onApiReady: '&',
                onFreeze: '&',
                onUnFreeze: '&',
            },

            template: '',

            link: function(scope, element, attrs) {
                scope.$on('$destroy', function() {
                    scope.destroy();
                });

                element.on('$destroy', function() {
                    scope.destroy();
                });

                scope.$watch('datasource', function(newVal) {
                    if (!newVal) {
                        return;
                    }
                    scope.setDatasource(newVal);
                });

                scope.$watch('configuration', function(newVal) {
                    if (!newVal) {
                        return;
                    }
                    scope.setConfiguration(newVal);
                });

                // Construct the api interface
                scope.api = {
                    id: scope.id,
                    getConfiguration: scope.getConfiguration,
                    getDatasource: scope.getDatasource,
                    setConfiguration: scope.setConfiguration,
                    setDatasource: scope.setDatasource,
                    freeze: scope.freeze,
                    unFreeze: scope.unFreeze,
                    wakeup: scope.wakeup,
                    sleep: scope.sleep,
                    executeAction: scope.executeAction,
                    getDataById: scope.getDataById
                };

                scope.init();

                if (scope.onApiReady) {
                    scope.onApiReady({api: scope.api});
                }
            },

            controller: ["$scope", "$timeout", "$q", "$compile", "$element", "$filter", "networkContentRouterService", "ColorUtil", "scriptingService", "halPlatformService", function($scope, $timeout, $q, $compile, $element, $filter,
                networkContentRouterService, ColorUtil, scriptingService, halPlatformService) {
                $scope.localDatasource = null;

                // create carousel
                var gridAPI = null;
                var gridScope = null;
                var gridItemsListener;
                var gridElem;
                var currentItem;
                $scope.mediaList = []; // list of media info (text, id, asset)
                $scope.gridItems = []; // this variable we pass to asUiGrid
                var isEditor = halPlatformService.getFeatureSupport('playback.mode.editor');
                // config
                var configItemWidth;
                var configItemHeight;
                var configVerticalalign;
                var configSpacingX;
                var configSpacingY;
                var configItemselectedBorderwidth;
                var configItemselectedBordercolor;
                var configItemunselectedBorderwidth;
                var configItemunselectedBordercolor;
                var configAutoloadonclick;
                var configDefaultassettype;
                var configItemselectedAssettype;
                var configItemselectedBackgroundcolor;
                var configItemUnselectedBackgroundcolor;
                var configCustomContainerItemTitleShow = false;
                var configCustomContainerItemUnselectedTitleFont;
                var configCustomContainerItemSelectedTitleFont;
                var configCustomContainerItemUnselectedTitleMargin;
                var configCustomContainerItemSelectedTitleMargin;
                var configScrollDirection = 'horizontal';
                var currentWidgetClass;
                $scope.fontConfigUnselect = {
                    css: '',
                    paddingLeft: 0,
                    paddingTop: 0,
                    paddingRight: 0,
                    paddingBottom: 0,
                    containerHeight: 0,
                    containerWidth: 0,
                };

                var fontConfigUnSelected = {paddingTop: 0, paddingLeft: 0, paddingRight: 0, paddingBottom: 0};
                var fontConfigSelected = {paddingTop: 0, paddingLeft: 0, paddingRight: 0, paddingBottom: 0};

                var gridId;

                // execute action track
                var executeActions = [];
                var isOnExecute = false;

                $scope.init = function() {
                    signsWidgetService.subscribeToCanvasScaleUpdate($scope.onCanvasScaleChange);
                };

                $scope.destroy = function() {
                    cleanWidgetCssClass();
                    $scope.unloadGrid();
                    signsWidgetService.unsubscribeToCanvasScaleUpdate($scope.onCanvasScaleChange);
                    $element.empty();
                };

                // on grid api
                $scope.onGridApi = function(api) {
                    gridAPI = api;
                    gridAPI.setConfig($scope.gridConfig);
                    // watching $scope.gridItems data changes
                    gridItemsListener = $scope.$watchCollection('gridItems', function(newVal) {
                        api.clearItems();
                        api.addItems(newVal);
                        if (!isEditor) {
                            return;
                        }
                        gridAPI.moveToItem(0);
                    });
                };

                $scope.onGridItemFocused = function() {
                    if (!isEditor) {
                        return;
                    }
                    updateMediaHighlights();
                };

                // media api
                $scope.onMediaApiReady = function(api, id) {
                    $scope.mediaList[id].api = api;
                };

                $scope.onMediaLoadError = function(id) {
                    // console.log("onMediaLoadError : " + id);
                };

                $scope.onMediaComplete = function(id) {
                    // console.log("onMediaComplete : " + id);
                };

                $scope.onMediaClick = function(api, itemOffset) {
                    var media = $scope.mediaList[itemOffset];
                    widgetRaiseEvent('OnItemClick', {
                        itemId: media.ownerId,
                        id: media.ownerId,
                        caption: media.caption,
                        itemCaption: media.caption,
                        text: media.text,
                        itemText: media.text,
                        tag: media.tag,
                        mediaTag: media.tag
                    });

                    if (configAutoloadonclick) {
                        $scope.executeAction({name: 'loaditem', itemid: media.ownerId});
                    } else {
                        updateMediaHighlights();
                    }
                };

                $scope.onCanvasScaleChange = function() {
                    // Recalculate config borders and such
                    if (!$scope.config) {
                        return;
                    }
                    $scope.setConfiguration($scope.config);
                };

                function fixMinValue(value) {
                    return Math.max(1, value);
                }

                $scope.setConfiguration = function(config) {
                    $scope.config = config;

                    var scale = signsWidgetService.getCanvasScale();
                    var scaleFactor;

                    if (scale.xScale < scale.yScale) {
                        scaleFactor = scale.xScale;
                    } else {
                        scaleFactor = scale.yScale;
                    }

                    for (var keyName in config) {
                        var key = keyName;
                        var value = config[keyName];
                        switch (key.toLowerCase()) {
                            case 'custom.container.item.width':
                                configItemWidth = parseInt(value, 10) * scaleFactor;
                                break;
                            case 'custom.container.item.height':
                                configItemHeight = parseInt(value, 10) * scaleFactor;
                                break;
                            case 'custom.container.verticalalign':
                                configVerticalalign = value == 'bottom' ? 'end' : value;
                                break;
                            case 'custom.container.spacing.x':
                                configSpacingX = parseInt(value, 10) * scale.xScale;
                                break;
                            case 'custom.container.spacing.y':
                                configSpacingY = parseInt(value, 10) * scale.yScale;
                                break;
                            case 'custom.container.itemselected.borderwidth':
                                configItemselectedBorderwidth = fixMinValue(value * scaleFactor);
                                break;
                            case 'custom.container.itemselected.bordercolor':
                                configItemselectedBordercolor = value;
                                break;
                            case 'custom.container.itemunselected.borderwidth':
                                configItemunselectedBorderwidth = fixMinValue(value * scaleFactor);
                                break;
                            case 'custom.container.itemunselected.bordercolor':
                                configItemunselectedBordercolor = value;
                                break;
                            case 'custom.container.autoloadonclick':
                                configAutoloadonclick = value !== 'false';
                                break;
                            case 'custom.container.defaultassettype':
                                configDefaultassettype = value;
                                break;
                            case 'custom.container.itemselected.assettype':
                                configItemselectedAssettype = value;
                                break;
                            case 'custom.container.itemselected.backgroundcolor':
                                configItemselectedBackgroundcolor = value;
                                break;
                            case 'custom.container.itemunselected.backgroundcolor':
                                configItemUnselectedBackgroundcolor = value;
                                break;
                            case 'custom.container.item.title.show':
                                configCustomContainerItemTitleShow = value === 'true';
                                break;
                            case 'custom.container.itemunselected.title.font':
                                configCustomContainerItemUnselectedTitleFont = FontUtil.reformatStyleToCSS(value);
                                break;
                            case 'custom.container.itemselected.title.font':
                                configCustomContainerItemSelectedTitleFont = FontUtil.reformatStyleToCSS(value);
                                break;
                            case 'custom.container.itemunselected.title.margin':
                                configCustomContainerItemUnselectedTitleMargin = value;
                                break;
                            case 'custom.container.itemselected.title.margin':
                                configCustomContainerItemSelectedTitleMargin = value;
                                break;
                            case 'custom.container.item.scroll.direction':
                                configScrollDirection = value;
                                break;
                        }
                    }

                    configItemselectedBordercolor = configItemselectedBordercolor || '#ffffffff';
                    configItemunselectedBordercolor = configItemunselectedBordercolor || '#ff000000';
                    // if selected asset type is empty, means that it needs to inherit from default asset type
                    configItemselectedAssettype = configItemselectedAssettype || configDefaultassettype;

                    // font css for unselect
                    setFontDiv(fontConfigUnSelected, configCustomContainerItemUnselectedTitleMargin, configCustomContainerItemUnselectedTitleFont, scaleFactor);

                    // font css for selected
                    setFontDiv(fontConfigSelected, configCustomContainerItemSelectedTitleMargin, configCustomContainerItemSelectedTitleFont, scaleFactor);

                    // if we already have datasource, re-render
                    if ($scope.localDatasource) {
                        loadItemsByParent();
                    }

                    return $q.resolve({api: $scope.api});
                };

                function setFontDiv(fontDivConfig, marginConfig, fontConfig, scaleFactor) {
                    if (marginConfig) {
                        var unselectedTitleMargin = marginConfig.split(',');
                        fontDivConfig.paddingLeft = parseInt(unselectedTitleMargin[0], 10) * scaleFactor;
                        fontDivConfig.paddingTop = parseInt(unselectedTitleMargin[1], 10) * scaleFactor;
                        fontDivConfig.paddingRight = parseInt(unselectedTitleMargin[2], 10) * scaleFactor;
                        fontDivConfig.paddingBottom = parseInt(unselectedTitleMargin[3], 10) * scaleFactor;
                    }

                    fontDivConfig.containerHeight = configItemHeight - fontDivConfig.paddingTop - fontDivConfig.paddingBottom;
                    fontDivConfig.containerMaxHeight = 'calc(100% - ' + (fontDivConfig.paddingTop + fontDivConfig.paddingBottom) + 'px)';
                    fontDivConfig.containerWidth = configItemWidth;

                    var vAlign;

                    if (fontConfig) {
                        vAlign = grapCssPropertiesValue(fontConfig, 'vertical-align', ';');
                    }

                    if (!vAlign) {
                        vAlign = 'top';
                    }

                    var topAndTranslateSetting = getVerticalAlignSetting(vAlign, fontDivConfig.paddingTop, fontDivConfig.paddingBottom);
                    fontDivConfig.top = topAndTranslateSetting.top;
                    fontDivConfig.transform = topAndTranslateSetting.transform;
                }

                function getVerticalAlignSetting(vAlign, paddingTop, paddingBottom) {
                    var returnVal = {top: '', transform: ''};
                    if (vAlign == 'top') {
                        returnVal.top = paddingTop + 'px';
                        returnVal.transform = 0 + '%';
                    } else if (vAlign == 'middle') {
                        returnVal.top = 50 + '%';
                        returnVal.transform = -50 + '%';
                    } else if (vAlign == 'bottom') {
                        returnVal.top = 'calc(100% - ' + paddingBottom + 'px)';
                        returnVal.transform = -100 + '%';
                    }
                    return returnVal;
                }

                function grapCssPropertiesValue(cssString, propName, propEnd) {
                    if (cssString.indexOf(propName) == -1) {
                        return null;
                    }
                    var regPropName = '' + propName + ':';
                    var regPropUnit = '\\' + propEnd;
                    var oriStr = cssString.match(new RegExp(regPropName + '(.*?)' + regPropUnit, 'g'))[0];
                    var valStr = oriStr.replace(new RegExp(regPropName + '|' + propEnd, 'g'), '');
                    return valStr;
                }

                $scope.setDatasource = function(datasource) {
                    $scope.localDatasource = datasource;
                    loadItemsByParent();
                    return $q.resolve({api: $scope.api});
                };

                $scope.unloadGrid = function() {
                    $scope.mediaList = [];
                    if ($scope.gridItems) {
                        // Destroy scopes of each grid item
                        for (var i = 0; i < $scope.gridItems.length; i++) {
                            var item = $scope.gridItems[i];
                            item.scope.$destroy();
                            item.scope = null;
                        }
                    }
                    $scope.gridItems = [];
                    // clear watch
                    if (gridItemsListener) {
                        gridItemsListener();
                    }
                    gridId = null;

                    if (gridAPI) {
                        gridAPI.destroy();
                        gridAPI = null;
                    }
                    if (gridScope) {
                        gridScope.$destroy();
                        gridScope = null;
                    }
                    var currentEl = $angular.element($element);
                    currentEl.empty();
                };

                $scope.getConfiguration = function() {
                    return $scope.config;
                };

                $scope.getDatasource = function() {
                    return $scope.localDatasource;
                };

                $scope.wakeup = function() {
                    return $q.resolve({api: $scope.api});
                };

                $scope.sleep = function() {
                    return $q.resolve({api: $scope.api});
                };

                $scope.freeze = function() {
                    widgetRaiseEvent('Freeze');
                    return $q.resolve({api: $scope.api});
                };

                $scope.unFreeze = function() {
                    widgetRaiseEvent('UnFreeze');
                    return $q.resolve({api: $scope.api});
                };

                $scope.getDataById = function(id) {
                    return getDataById(id);
                };

                function orderItemsByPosition(items) {
                    return $filter('orderBy')(items, 'position');
                }

                function getDataById(id, items) {
                    if (!$scope.localDatasource || !$scope.localDatasource.result || !$scope.localDatasource.result.items) {
                        return;
                    }
                    items = items || $scope.localDatasource.result.items;
                    // if items are empty, just return undefined
                    if (!items.length) {
                        return;
                    }
                    // if id doesn't exists, return the items directly as children with sorted position
                    if (!id) {
                        return {children: orderItemsByPosition(items)};
                    }
                    // look id from the current level
                    var data = $filter('filter')(items, {id: id});
                    if (data.length > 0) {
                        var res = data[0];
                        if (res.children) {
                            res.children = orderItemsByPosition(res.children);
                        }
                        return res;
                    }
                    // look inside children
                    for (var i = 0; i < items.length; i++) {
                        var item = items[i];
                        data = getDataById(id, item.children);
                        // if we found it, stop looking
                        if (data) {
                            return data;
                        }
                    }
                }

                function updateGridData(dataItems) {
                    var items = [];
                    for (var i = 0; i < dataItems.length; i++) {
                        var item = dataItems[i];
                        var assets = $filter('orderBy')(item.assets, 'position');
                        var element = '';
                        var firstElement = '';
                        var secondElement = '';
                        var compiledElement;
                        var media = {};
                        media.ownerId = item.id;
                        media.parentId = item.parentId;
                        media.caption = item.caption;
                        media.tag = item.tag;
                        media.text = item.text;

                        for (var j = 0; j < assets.length; j++) {
                            media.asset = assets[j];
                            var type = media.asset.assetType.toLowerCase();
                            if (type == configDefaultassettype && firstElement == '') {
                                firstElement = getMediaTemplate(media, i, 'inline');
                            }
                            if (type == configItemselectedAssettype && secondElement == '') {
                                secondElement = getMediaTemplate(media, i, 'none');
                            }

                            if (firstElement != '' && secondElement != '') {
                                break; // break because only select the first asset found for both first and second element(base on asset type)
                            }
                        }

                        // if content dont have any asset
                        firstElement = firstElement || getEmptyTemplate();
                        secondElement = secondElement || getEmptyTemplate();

                        element = '<div>' +
                            getCaptionTemplate(media) +
                            firstElement +
                            secondElement +
                            '</div>';

                        $scope.mediaList.push(media);

                        var newscope = $scope.$new();
                        compiledElement = $compile(element)(newscope);
                        media.template = compiledElement;

                        if (compiledElement) {
                            var elementItem2 = {
                                type: 'element',
                                element: compiledElement,
                                scope: newscope
                            };
                        }
                        items.push(elementItem2);
                    }

                    $scope.gridItems = items;
                }

                function loadItemsByParent(id) {
                    $scope.unloadGrid();
                    gridId = 'grid' + (id || $scope.id);
                    var dataItem = getDataById(id);

                    if (!dataItem) {
                        return $q.reject();
                    }
                    var deffered = $q.defer();
                    // set current item
                    currentItem = dataItem.id ? dataItem : null;
                    var dataArray = dataItem.children;
                    convertMediaURLs(dataArray);
                    $timeout(function() {
                        fadeInCurrentElement().then(deffered.resolve);
                    }, 100);

                    setGridConfig();
                    gridScope = $scope.$new();
                    gridElem = $compile('<as-ui-grid style="opacity:0; transform:translateX(-100px); position:absolute; top:0; width:100%; height:100%;" id=' + gridId + ' config="gridConfig" on-item-click="onMediaClick(api, offset)" on-api-ready="onGridApi(api)" on-item-focus="onGridItemFocused(api, offset, fromClick)"> </as-ui-grid>')(gridScope);
                    $angular.element($element).append(gridElem);
                    return deffered.promise;
                }

                // Convert Urls
                function convertMediaURLs(items) {
                    var promises = [];
                    for (var i = 0; i < items.length; i++) {
                        var item = items[i];

                        for (var j = 0; j < item.assets.length; j++) {
                            var promise = convertMediaURL(item.assets[j]);
                            promises.push(promise);
                        }
                    }

                    $q.all(promises).then(function() {
                        updateGridData(items);
                    });
                }

                // Convert Url
                function convertMediaURL(media) {
                    var deffered = $q.defer();

                    networkContentRouterService.resolve(media.mediaFile, 'signs-content-routing-service', {type: media.type})
                        .then(function(url) {
                            media.convertedMediaFile = url;
                            deffered.resolve(media);
                        }, deffered.reject);

                    return deffered.promise;
                }

                function cleanWidgetCssClass() {
                    if (!currentWidgetClass) {
                        return;
                    }
                    document.getElementsByTagName('head')[0].removeChild(currentWidgetClass);
                    currentWidgetClass = undefined;
                }

                // grid configuration
                function setGridConfig() {
                    cleanWidgetCssClass();
                    // set css
                    var unSelectedBordercolor = parseColor(configItemunselectedBordercolor);
                    var selectedBordercolor = parseColor(configItemselectedBordercolor);
                    var unSelectedBgcolor = parseColor(configItemUnselectedBackgroundcolor);
                    var selectedBgcolor = parseColor(configItemselectedBackgroundcolor);

                    // create css class
                    currentWidgetClass = document.createElement('style');
                    currentWidgetClass.type = 'text/css';
                    currentWidgetClass.innerHTML = '#' + gridId + ' .cssClassActive {border: ' + configItemselectedBorderwidth + 'px solid ' + selectedBordercolor + ' !important;}' +
                        '#' + gridId + ' .cssClass {border: ' + configItemunselectedBorderwidth + 'px solid ' + unSelectedBordercolor + '; float: left; text-align: center; position: absolute;}' +
                        '#' + gridId + ' .cssFontClass {top:' + fontConfigUnSelected.top + '; transform: translateY(' + fontConfigUnSelected.transform + '); position:absolute; width: 100%; max-width:100%; height: auto; max-height:' + fontConfigUnSelected.containerMaxHeight + '; padding-right:' + fontConfigUnSelected.paddingRight + 'px; padding-left:' + fontConfigUnSelected.paddingLeft + 'px; word-wrap: break-word; overflow: hidden; ' + configCustomContainerItemUnselectedTitleFont + '}' +
                        '#' + gridId + ' .cssFontClassActive {top:' + fontConfigSelected.top + '; transform: translateY(' + fontConfigSelected.transform + '); position:absolute; width: 100%; max-width:100%; height: auto; max-height: ' + fontConfigSelected.containerMaxHeight + '; padding-right:' + fontConfigSelected.paddingRight + 'px; padding-left:' + fontConfigSelected.paddingLeft + 'px;  word-wrap: break-word; overflow: hidden;' + configCustomContainerItemSelectedTitleFont + '}' +
                        '#' + gridId + ' .cssMediaClass {background-color: ' + unSelectedBgcolor + '!important;}' +
                        '#' + gridId + ' .cssMediaClassActive {background-color: ' + selectedBgcolor + '!important;}';
                    document.getElementsByTagName('head')[0].appendChild(currentWidgetClass);

                    // carousel setting
                    $scope.gridConfig = {
                        margin: {top: 0, left: 0, right: 0, bottom: 0}, // default value
                        itemSpacing: {horizontal: configSpacingX, vertical: configSpacingY},
                        itemWidth: configItemWidth,
                        itemHeight: configItemHeight,
                        itemSizeCalculation: 'predefine', // auto, predefine
                        scrollDirection: configScrollDirection, // horizontal, vertical
                        scrollAlignment: 'page',
                        itemAlignment: configVerticalalign, // "start", "middle" and "end"
                        aspectRatio: configItemWidth / configItemWidth,
                        uiGridItemClass: 'cssClass ui-grid-item',
                        uiGridItemActiveClass: 'overwrite-as-grid-active', // overwrite as-grid default active class
                        highlightTimeout: 0,
                        autoHighlight: isEditor
                    };
                }

                // verify and parse color
                function parseColor(color) {
                    return !color ? 'unset' : new ColorUtil().parseHTMLColor(color);
                }

                function updateMediaHighlights() {
                    var offset = gridAPI.getCurrentItemOffset();
                    if (offset < 0) {
                        return;
                    }

                    /*
                    note:
                    for now cssMediaClassActive/cssMediaClass only implement background color change
                    border color change is handled in asUiGrid
                    children().eq(0) is font div
                    children().eq(1) is media div (empty div / as-media-image/ as-media-video) - first element
                    children().eq(2) is media div (empty div / as-media-image/ as-media-video) - second element (swap when item got selected)
                    */

                    // remove all active css media class
                    for (var i = 0; i < $scope.mediaList.length; i++) {
                        var tempMedia = $scope.mediaList[i];
                        modifyCssClass($(tempMedia.template).children().eq(0), 'removeClass');
                        // reset 2nd element if item consist of 2 elements
                        if ($(tempMedia.template).children().length == 2) {
                            modifyCssClass($(tempMedia.template).children().eq(1), 'removeClass');
                            $(tempMedia.template).children().eq(0).css('display', 'inline');
                            $(tempMedia.template).children().eq(1).css('display', 'none');
                        }
                    }

                    // add active css media class
                    var selectedMedia = $scope.mediaList[offset];
                    if (!selectedMedia) {
                        return;
                    }
                    // is second div/template available
                    if ($(selectedMedia.template).children().length == 2) {
                        // hide 1st element & show 2nd element
                        $(selectedMedia.template).children().eq(0).css('display', 'none');
                        $(selectedMedia.template).children().eq(1).css('display', 'inline');
                        modifyCssClass($(selectedMedia.template).children().eq(1), 'addClass');
                        return;
                    }
                    modifyCssClass($(selectedMedia.template).children().eq(0), 'addClass');
                }

                function modifyCssClass(element, method) {
                    // font
                    element[method]('cssFontClassActive');
                    // border
                    element[method]('cssClassActive');
                    // background
                    element[method]('cssMediaClassActive');
                }

                // run by script handler
                function loadItemById(obj) {
                    var deffered = $q.defer();
                    // animate fade out
                    fadeOutCurrentElement().then(function() {
                        loadItemsByParent(obj.itemId).then(deffered.resolve, deffered.reject);
                    });
                    return deffered.promise;
                }

                function loadItemByOffset(obj) {
                    var media = $scope.mediaList[obj.itemOffset];
                    var id = media.ownerId;
                    return loadItemById({itemId: id});
                }

                function loadParentItem() {
                    if (!currentItem) {
                        return $q.reject('You are on the root level.');
                    }
                    var parentId = !currentItem.parentId ? null : parseInt(currentItem.parentId, 10);
                    return loadItemById({itemId: parentId});
                }

                function loadItemLevelFromPath(obj) {
                    var path = obj.path || '';
                    var pathInArray;
                    var items = [];
                    if ($scope.localDatasource && $scope.localDatasource.result && $scope.localDatasource.result.items) {
                        items = $scope.localDatasource.result.items;
                    }
                    var idToLoad = null;
                    pathInArray = path.split('\\');
                    // e.g. path1\path2\path3\path4
                    for (var i = 0; i < pathInArray.length; i++) {
                        // if there's no item to look for, stop looking
                        if (items.length == 0) {
                            break;
                        }
                        var p = pathInArray[i];
                        items = $filter('filter')(items, {caption: p});
                        // if we can't find an item in this pass, meaning we can't find it
                        // each pass need to produce a result
                        if (items.length == 0) {
                            break;
                        }
                        // grab the first valid result
                        var item = items[0];
                        // if this is the last path, grab the item id
                        if (i == (pathInArray.length - 1)) {
                            idToLoad = parseInt(item.id, 10);
                            continue;
                        }
                        // if not, grab its children and continue looking
                        items = item.children;
                    }
                    return loadItemById({itemId: idToLoad});
                }

                function loadNextItem() {
                    return loadNextPreviousItem(
                        gridAPI.getCurrentItemOffset() >= gridAPI.getItems().length - 1,
                        'nextItem');
                }

                function loadPreviousItem() {
                    return loadNextPreviousItem(
                        gridAPI.getCurrentItemOffset() < 1,
                        'previousItem');
                }

                function loadNextPreviousItem(isOutOfBound, method) {
                    if (isOutOfBound) {
                        return $q.reject('reached out of bound item');
                    }
                    var deffered = $q.defer();
                    gridAPI[method](true);
                    $timeout(function() {
                        if (configAutoloadonclick) {
                            loadItemByOffset({itemOffset: gridAPI.getCurrentItemOffset()}).then(deffered.resolve, deffered.reject);
                            return;
                        }
                        updateMediaHighlights();
                        deffered.resolve();
                    }, 0);

                    return deffered.promise;
                }

                function loadItemLevel(obj) {
                    return loadItemById({itemId: obj.itemId});
                }

                function gotoNextPage() {
                    gridAPI.gotoNextPage();
                    return $q.resolve();
                }

                function gotoPreviousPage() {
                    gridAPI.gotoPreviousPage();
                    return $q.resolve();
                }

                function widgetRaiseEvent(eventname, properties) {
                    scriptingService.raiseEvent('Element_' + $scope.id, eventname, properties);
                }

                function getMediaTemplate(media, id, display) {
                    var fileUrl = media.asset.convertedMediaFile;
                    var folderPath = fileUrl.substring(0, fileUrl.lastIndexOf('/') + 1);
                    media.asset.thumbnail = fileUrl;
                    if (media.asset.type.toLowerCase() == 'video') {
                        media.asset.thumbnail = folderPath + '_vidThumbnail_medium.jpg';
                    } else if (media.asset.type.toLowerCase() == 'web page') {
                        media.asset.thumbnail = folderPath + '_thumbnail.jpg';
                    } else if (media.asset.type.toLowerCase() == 'external content') {
                        media.asset.thumbnail = folderPath + '_thumbnail.jpg';
                    }
                    return getImageTemplate(media, id, display);
                }

                function getImageTemplate(media, id, display) {
                    var url = media.asset.thumbnail;
                    return '<as-media-image style="display: ' + display + ';width:100%; height:100%; position:absolute; left:0; z-index:-1;" class=cssMediaClass url="' + url + '" stretch="' + 'uniformfill' + '"'
                        + ' on-api-ready="onMediaApiReady(api, ' + id + ')" on-image-load="onMediaLoad(' + id + ')" on-image-load-error="onMediaLoadError(' + id + ')"></as-media-image>';
                }

                function getEmptyTemplate(display) {
                    return '<div style="display: ' + display + ';width:100%; height:100%; position:absolute; left:0; top:0; z-index:-1;" class=cssMediaClass></div>';
                }

                function getCaptionTemplate(media) {
                    return configCustomContainerItemTitleShow ? '<div class=cssFontClass>' + media.caption + '</div>' : '';
                }

                // fadeInCurrentElement & fadeOutCurrentElement
                function fadeOutCurrentElement() {
                    return fadeGridElement('100px', 0);
                }

                function fadeInCurrentElement() {
                    return fadeGridElement('0', 1);
                }

                function fadeGridElement(translateX, opacity) {
                    var promise = $q.defer();
                    gridElem.velocity(
                        {opacity: opacity, translateX: translateX},
                        {duration: 400, complete: promise.resolve}
                    );
                    return promise.promise;
                }

                $scope.executeAction = function(action) {
                    var a = action.name.toLowerCase();
                    var use = false;
                    switch (a) {
                        case 'loaditem':
                            if (action.itemid) {
                                executeActions.push({callback: loadItemById, parameter: {itemId: action.itemid}});
                            }
                            use = true;
                            break;
                        case 'loaditemfromoffset':
                            var increment = 0;
                            var offset = 0;
                            if (action.increment) {
                                increment = parseInt(action.increment, 10);
                            }
                            if (action.offset) {
                                offset = parseInt(action.offset, 10);
                            }
                            executeActions.push({callback: loadItemByOffset, parameter: {offset: offset + increment}});
                            use = true;
                            break;
                        case 'loaditemlevelfrompath':
                            executeActions.push({callback: loadItemLevelFromPath, parameter: {path: action.path}});
                            use = true;
                            break;
                        case 'loaditemlevel':
                            executeActions.push({callback: loadItemLevel, parameter: {itemId: action.itemid}});
                            use = true;
                            break;
                        case 'loadnextitem':
                            executeActions.push({callback: loadNextItem, parameter: null});
                            use = true;
                            break;
                        case 'loadpreviousitem':
                            executeActions.push({callback: loadPreviousItem, parameter: null});
                            use = true;
                            break;
                        case 'loadparentitem':
                            executeActions.push({callback: loadParentItem, parameter: null});
                            use = true;
                            break;
                        case 'setdefaultassettype':
                            configDefaultassettype = action.itemid;
                            executeActions.push({callback: loadItemById, parameter: {itemId: null}});
                            use = true;
                            break;
                        case 'nextpage':
                            executeActions.push({callback: gotoNextPage, parameter: {itemId: null}});
                            use = true;
                            break;
                        case 'previouspage':
                            executeActions.push({callback: gotoPreviousPage, parameter: {itemId: null}});
                            use = true;
                            break;
                    }

                    if (use == true && isOnExecute == false) {
                        executeAction();
                    }

                    return use;
                };

                // run next action in que.
                function executeAction() {
                    var delayAction = executeActions.pop();
                    if (!delayAction) {
                        return;
                    }
                    isOnExecute = true;
                    var finalize = function() {
                        isOnExecute = false;
                        executeAction();
                    };
                    delayAction.callback(delayAction.parameter).then(finalize, finalize);
                }
            }]
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('asSignsWidgetMediaZone', function() {
        return {
            restrict: 'EA',
            scope: {
                id: '@',
                datasource: '=',
                configuration: '=',
                contentSourceId: '@',

                onApiReady: '&',
                onFreeze: '&',
                onUnFreeze: '&',
                onNoContentAvailable: '&',
                onContentAvailable: '&',
                onContentLoaded: '&',
            },

            template: '<as-ui-content-presenter on-api-ready="onapiready(api)" ' +
                'default-transition-duration="{{defaultTransitionDuration}}" on-item-load="onload(item)" ' +
                'on-item-complete="oncomplete()" on-item-load-error="onerror()" on-item-action-received="onactionreceived(action)" ' +
                'on-item-playback-progress="onItemPlaybackProgress(item, progress)"></as-ui-content-presenter>',

            link: function(scope, element, attrs) {
                scope.$on('$destroy', function() {
                    scope.destroy();
                });

                element.on('$destroy', function() {
                    scope.destroy();
                });

                scope.$watch('datasource', function(newVal) {
                    if (!newVal) {
                        return;
                    }

                    scope.setDatasource(newVal);
                });

                scope.$watch('configuration', function(newVal) {
                    if (!newVal) {
                        return;
                    }

                    scope.setConfiguration(newVal);
                });

                // Construct the api interface
                scope.api = {
                    id: scope.id,
                    getConfiguration: scope.getConfiguration,
                    getDatasource: scope.getDatasource,
                    setConfiguration: scope.setConfiguration,
                    setDatasource: scope.setDatasource,
                    setLayout: scope.setLayout,
                    freeze: scope.freeze,
                    unFreeze: scope.unFreeze,
                    destroy: scope.destroy,
                    wakeup: scope.wakeup,
                    sleep: scope.sleep,
                    executeAction: scope.executeAction,
                    isNavigationEnabled: scope.isNavigationEnabled,
                    nextContent: scope.nextContent,
                    previousContent: scope.previousContent,
                    mute: scope.mute,
                };

                if (scope.onApiReady) {
                    scope.onApiReady({
                        api: scope.api
                    });
                }
            },

            controller: ["$scope", "$timeout", "$interval", "$q", "$element", "LinearPlaylist", "ScheduablePlaylist", "networkContentRouterService", "Utils", "scriptingService", "metricService", "PassiveMetric", "deviceRegistrationService", "syncService", "halMediaService", "campaignService", "halPlatformService", "ActionUtil", "logService", "CONTENT_TYPE", "TRANSITION", "SYNC_EVENT", "configurationService", "networkConnectionService", "dataUtil", "coreConfigurationService", "mediaUtil", "PLAYER_PROPERTY_KEYS", "DateTimeUtil", "INTERLEAVE_TYPE", "interleaveUtil", "postMessagingService", "POST_MESSAGES", "FEATURE_CODE", "NETWORK_CONNECTION_STATE", "contentStateService", "CONTENT_STATE_EVENT", "STORAGE_COLLECTION_KEY", "STORAGE_KEY", function($scope, $timeout, $interval, $q, $element, LinearPlaylist,
                ScheduablePlaylist, networkContentRouterService, Utils, scriptingService,
                metricService, PassiveMetric, deviceRegistrationService, syncService, halMediaService,
                campaignService, halPlatformService, ActionUtil, logService, CONTENT_TYPE, TRANSITION, SYNC_EVENT,
                configurationService, networkConnectionService, dataUtil, coreConfigurationService, mediaUtil, PLAYER_PROPERTY_KEYS, DateTimeUtil, INTERLEAVE_TYPE,
                interleaveUtil, postMessagingService, POST_MESSAGES, FEATURE_CODE, NETWORK_CONNECTION_STATE,
                contentStateService, CONTENT_STATE_EVENT, STORAGE_COLLECTION_KEY, STORAGE_KEY
            ) {
                $scope.localDatasource = undefined;
                var dateTimeUtil = new DateTimeUtil();
                var isNextItemPreloading = false;
                var currentPassiveMetric;
                var utils = new Utils();
                var playlist = new LinearPlaylist(false, true, false);
                var presenterApi;
                var itemLoaded = false;
                var currentItem;
                var loadPromises = [];
                var audioMute;
                var audioLevel;
                var randomPlayout;
                var isFirstItem = false;
                var queueItemLoading = true;
                var pendingNext;
                var defaultTransitionDuration;
                var validCheckInterval;
                var checkNewContentInterval;
                var lastValidCheck = -1;
                var playingCampaign;
                var videoLayerIndex;
                var closecaption = false;
                var closecaptionInfoBrightsign = '';
                var closecaptionInfoChrome = '';
                var closecaptionInfoSamsung = '';
                var closecaptionTrack = 1;
                var allowWebRTC;
                var customWebFrameLayer;
                var useSynchronizedScheduling;
                var restartPlaylistOnWake = false;
                var interleaveConfiguration;
                var minLoadTime;
                var preloadBehavior;
                var loadNextTimeoutHandler;
                var VALID_PRELOAD_BEHAVIOR = {
                    PRE_COMPLETION: 'precompletion',
                    POST_COMPLETION: 'postcompletion'
                };
                var forceStreamType = '';
                var bypassFlashLocalPLaybackRestriction = false;
                var configBypassLoadWait;
                var defaultDuration;
                var campaignZoneId;
                var campaignZoneIdNum = -1;
                var previousAssetCampaign;
                var isFrozen = false;
                var fireEventTimeoutPromise;
                var previousConfigTransition;
                var previousConfigTransitionDuration;
                var previousConfigDuration;
                var previousConfigRandomPlayout;
                var buildPlaylistMethod;
                var LOAD_NEXT_TIMEOUT_DURATION = 5000;
                var appDefaultPreloadBehavior = VALID_PRELOAD_BEHAVIOR.POST_COMPLETION;
                var appDefaultMinLoadTime = 2000;
                var appDefaultTransition = TRANSITION.FADE_IN;
                var appDefaultTransitionDuration = 0.6;
                var appDefaultStretch = 'uniform';
                var appDefaultAudioMute = false;
                var appDefaultAudioLevel = 1;
                var appDefaultDuration = 12;
                var appDefaultRandomPlayout = false;
                var appDefaultCustomWebframeLayer;
                var appDefaultBypassLoadWait = null;
                var appDefaultAllowWebRTC;
                var appDefaultInterleaveConfiguration = {
                    enable: false,
                    type: INTERLEAVE_TYPE.DEFAULT,
                    options: {}
                };
                var isDisconnected = false;
                var appDefaultUseSynchronizedScheduling = false;
                var appDefaultVideoLayerIndex = null;
                var appDefaultCampaignZoneId = null;
                var appDefaultCloseCaption = false;
                var appDefaultCloseCaptionInfoBrightsign = '';
                var appDefaultCloseCaptionInfoChrome = '';
                var appDefaultCloseCaptionInfoSamsung = '';
                var appDefaultCloseCaptionTrack = 1;
                var DEFAULT_VOLUME_PERCENTAGE = 100;
                var CLOSED_CAPTIONS_KEYS = {
                    CUSTOM_MEDIA_VIDEO_CC: 'custom.media.video.cc',
                    CUSTOM_MEDIA_VIDEO_CC_BRIGHTSIGN: 'custom.media.video.cc.brightsign',
                    CUSTOM_MEDIA_VIDEO_CC_CHROME: 'custom.media.video.cc.chrome',
                    CUSTOM_MEDIA_VIDEO_CC_SAMSUNG: 'custom.media.video.cc.samsung',
                    CUSTOM_MEDIA_VIDEO_CC_TRACK: 'custom.media.video.cc.track'
                };
                var isVideoTransitionSupported = halPlatformService.getFeatureSupport('media.video.transition.supported');
                var isSychronizedSeekingSupported = halPlatformService.getFeatureSupport(
                    FEATURE_CODE.VIDEO_SYNCHRONIZED_SEEKING_SUPPORTED) !== false;
                var isMediaRequestsAllowed = halPlatformService.getFeatureSupport(FEATURE_CODE.MEDIA_REQUESTS_ALLOWED);
                var actionsConfig = {
                    loadmedia: function(action) {
                        if (!action.offset) {
                            return;
                        }

                        var offset = parseInt(action.offset, 10);
                        var options = {};
                        if (action.delay) {
                            options.delay = parseInt(action.delay, 10);
                        }

                        if (action.increment) {
                            offset += parseInt(action.increment, 10);
                            // if the offset after increment is out of bound, try getting the next loop offset
                            var playlistLength = playlist.getTotalItems();
                            if (offset >= playlistLength) {
                                offset = offset % playlistLength;
                            }
                        }

                        var item = playlist.seek(offset);
                        if (!item) {
                            return;
                        }

                        loadItem(item, options);
                    },
                    loadschedulebyname: function(action) {
                        if (!action.schedulename) {
                            return;
                        }

                        buildScheduablePlaylist(undefined, action.schedulename);
                        nextItem();
                    },
                    loadschedule: function(action) {
                        if (!action.id) {
                            return;
                        }

                        buildScheduablePlaylist(action.id);
                        nextItem();
                    },
                    unloadschedule: function() {
                        buildScheduablePlaylist();
                        nextItem();
                    },
                    loadmediafromurl: function(action) {
                        if (!action.type || !action.media) {
                            return;
                        }

                        var options;
                        if (action.delay) {
                            options = {delay: parseInt(action.delay, 10)};
                        }

                        loadItem({
                            type: action.type,
                            mediaFile: action.media,
                            duration: action.duration
                        }, options);
                    },
                    nextmedia: nextItem,
                    previousmedia: previousItem,
                    replaymedia: replay,
                    freeze: pause,
                    pause: pause,
                    unfreeze: resume,
                    play: resume,
                    unloadmedia: unload,
                    adjustaudiolevel: function(action) {
                        if (!presenterApi.setVolume || !action.amount) {
                            return;
                        }

                        var adjust = parseFloat(action.amount) / DEFAULT_VOLUME_PERCENTAGE;
                        audioLevel = fixAudioLevel(audioLevel + adjust);
                        presenterApi.setVolume(audioLevel);
                    },
                    setaudiolevel: function(action) {
                        if (!presenterApi.setVolume || !action.level) {
                            return;
                        }

                        audioLevel = fixAudioLevel(parseFloat(action.level) / DEFAULT_VOLUME_PERCENTAGE);
                        presenterApi.setVolume(audioLevel);
                    },
                    enableaudio: function(action) {
                        if (!presenterApi.mute || !action.enabled) {
                            return;
                        }

                        audioMute = action.enabled.toLowerCase() != 'true';
                        presenterApi.mute(audioMute);
                    },
                    sendcommandtocontent: function(action) {
                        var param = {};
                        for (var key in action) {
                            // ignore name & targetName parameter. This is system generated
                            if (!key.match(/\b(name|targetName)\b/gi)) {
                                param[key.toLowerCase()] = action[key];
                            }
                        }

                        presenterApi.executeContentAction(param);
                    },
                };

                function fixAudioLevel(val) {
                    // we only support value between 0 & 1
                    // if value is not a number or it is more than 1, set it to 1
                    if (isNaN(val) || val > 1) {
                        return 1;
                    }

                    // if value is lower than 0, set it to 0
                    return val < 0 ? 0 : val;
                }

                function isClosedCaptionEnabled(value) {
                    return value == 'true' || value == '1' || value == 1;
                }

                $scope.setLayout = function(layout) {
                    $scope.currentLayout = layout;
                };

                $scope.setConfiguration = function(config) {
                    $scope.config = config;
                    campaignZoneId = appDefaultCampaignZoneId;
                    videoLayerIndex = appDefaultVideoLayerIndex;
                    useSynchronizedScheduling = appDefaultUseSynchronizedScheduling;
                    interleaveConfiguration = appDefaultInterleaveConfiguration;
                    minLoadTime = appDefaultMinLoadTime;
                    preloadBehavior = appDefaultPreloadBehavior;
                    allowWebRTC = appDefaultAllowWebRTC;
                    configBypassLoadWait = appDefaultBypassLoadWait;
                    customWebFrameLayer = appDefaultCustomWebframeLayer;
                    $scope.defaultTransition = '';
                    $scope.defaultStretch = appDefaultStretch;
                    defaultTransitionDuration = appDefaultTransitionDuration;
                    audioMute = appDefaultAudioMute;
                    audioLevel = appDefaultAudioLevel;
                    defaultDuration = appDefaultDuration;
                    randomPlayout = appDefaultRandomPlayout;
                    closecaption = appDefaultCloseCaption;
                    closecaptionInfoBrightsign = appDefaultCloseCaptionInfoBrightsign;
                    closecaptionInfoChrome = appDefaultCloseCaptionInfoChrome;
                    closecaptionInfoSamsung = appDefaultCloseCaptionInfoSamsung;
                    closecaptionTrack = appDefaultCloseCaptionTrack;

                    // check if we have global scheduling mode
                    var schedulingMode = syncService.getCurrentAppProperty('player.scheduling.mode');
                    if (schedulingMode) {
                        useSynchronizedScheduling = schedulingMode.toLowerCase() == 'synchronized';
                    }

                    // check if we have global minloadtime
                    var minLoadTimeOverride = syncService.getCurrentAppProperty('player.content.preloadtime');
                    if (minLoadTimeOverride) {
                        minLoadTime = parseFloat(minLoadTimeOverride) * 1000;
                    }

                    // check if we have global preload behavior
                    var preloadBehaviorOverride = syncService.getCurrentAppProperty(PLAYER_PROPERTY_KEYS.PRELOAD_BEHAVIOR);
                    var behaviorValues = Object.keys(VALID_PRELOAD_BEHAVIOR).map(function(key) {
                        return VALID_PRELOAD_BEHAVIOR[key];
                    });
                    if (preloadBehaviorOverride && dataUtil.includesValue(preloadBehaviorOverride, behaviorValues)) {
                        preloadBehavior = preloadBehaviorOverride;
                    }

                    // check if we have playlist restart
                    var restartOnWake = syncService.getCurrentAppProperty('player.scheduling.playlist.restartonwake');
                    if (!dataUtil.isNullOrUndefined(restartOnWake)) {
                        // if set, use the value
                        restartPlaylistOnWake = restartOnWake.toLowerCase() == 'true';
                    }

                    // check if we have closed captions
                    var appProperties = syncService.getCurrentAppProperties();
                    if (!dataUtil.isNullOrUndefined(appProperties)) {
                        closecaption = appProperties[CLOSED_CAPTIONS_KEYS.CUSTOM_MEDIA_VIDEO_CC];
                        closecaption = isClosedCaptionEnabled(closecaption);
                        closecaptionInfoBrightsign = appProperties[CLOSED_CAPTIONS_KEYS.CUSTOM_MEDIA_VIDEO_CC_BRIGHTSIGN];
                        closecaptionInfoChrome = appProperties[CLOSED_CAPTIONS_KEYS.CUSTOM_MEDIA_VIDEO_CC_CHROME];
                        closecaptionInfoSamsung = appProperties[CLOSED_CAPTIONS_KEYS.CUSTOM_MEDIA_VIDEO_CC_SAMSUNG];
                        closecaptionTrack = appProperties[CLOSED_CAPTIONS_KEYS.CUSTOM_MEDIA_VIDEO_CC_TRACK];
                    }

                    for (var key in config) {
                        if (!Object.prototype.hasOwnProperty.call(config, key)) {
                            continue;
                        }

                        var val = config[key];
                        switch (key.toLowerCase()) {
                            case 'custom.audio.mute':
                                audioMute = val == '1';
                                break;
                            case 'custom.audio.level':
                                audioLevel = fixAudioLevel(parseFloat(val) / DEFAULT_VOLUME_PERCENTAGE);
                                break;
                            case 'custom.media.default.aspectratio':
                                $scope.defaultStretch = utils.toAspectRatio(val, 'widget') || appDefaultStretch;
                                break;
                            case 'custom.media.default.transition':
                                $scope.defaultTransition = utils.toTransition(val);
                                break;
                            case 'custom.media.default.transition.duration':
                                defaultTransitionDuration = parseFloat(val);
                                break;
                            case 'custom.media.video.layerindex':
                                videoLayerIndex = parseInt(val);
                                break;
                            case 'custom.playlist.playoutorder':
                                randomPlayout = val == 'randc';
                                break;
                            case 'custom.playlist.sync.minloadtime':
                                minLoadTime = parseFloat(val);
                                break;
                            case 'custom.media.schedulingmode':
                                useSynchronizedScheduling = val.toLowerCase() == 'synchronized';
                                break;
                            case 'custom.playlist.restartonload':
                                restartPlaylistOnWake = val.toLowerCase() == 'true';
                                break;
                            case 'custom.playlist.interleave':
                                interleaveConfiguration = interleaveUtil.processInterleaveProperty(appDefaultInterleaveConfiguration, val);
                                break;
                            case 'custom.media.default.duration':
                                defaultDuration = parseFloat(val);
                                break;
                            case 'campaign.source.zoneid':
                                campaignZoneId = val;
                                break;
                            case CLOSED_CAPTIONS_KEYS.CUSTOM_MEDIA_VIDEO_CC:
                                closecaption = isClosedCaptionEnabled(val);
                                break;
                            case CLOSED_CAPTIONS_KEYS.CUSTOM_MEDIA_VIDEO_CC_BRIGHTSIGN:
                                closecaptionInfoBrightsign = val;
                                break;
                            case CLOSED_CAPTIONS_KEYS.CUSTOM_MEDIA_VIDEO_CC_CHROME:
                                closecaptionInfoChrome = val;
                                break;
                            case CLOSED_CAPTIONS_KEYS.CUSTOM_MEDIA_VIDEO_CC_SAMSUNG:
                                closecaptionInfoSamsung = val;
                                break;
                            case CLOSED_CAPTIONS_KEYS.CUSTOM_MEDIA_VIDEO_CC_TRACK:
                                closecaptionTrack = val;
                                break;
                            case 'custom.media.webrtc.allow':
                            case 'custom.media.permissionrequest.allow':
                                allowWebRTC = val.toLowerCase() === 'true';
                                break;
                            case 'custom.media.stream.contenttype':
                                forceStreamType = val.toLowerCase();
                                break;
                            case 'custom.media.bypass.flash.local.playback.restriction':
                                bypassFlashLocalPLaybackRestriction = val.toLowerCase() === 'true';
                                break;
                            case 'custom.media.bypass.loadwait':
                                configBypassLoadWait = val.toLowerCase() === 'true';
                                break;
                            case 'custom.webframe.layer.enable':
                                customWebFrameLayer = val.toLowerCase() === 'true';
                                break;
                        }
                    }

                    // Set audio level
                    presenterApi.setVolume(audioLevel);

                    // Check if the zone should be muted
                    presenterApi.mute(audioMute);

                    presenterApi.setStretch($scope.defaultStretch);

                    if (campaignZoneId) {
                        campaignService.subscribe({
                            mediaZoneId: $scope.id,
                            campaignZoneId: campaignZoneId,
                            callback: onCampaignContentChanges
                        });
                    } else {
                        campaignService.unsubscribe({
                            mediaZoneId: $scope.id
                        });
                        // if previously we are playing campaign, call media zone next item
                        if (playingCampaign == true) {
                            nextItem();
                        }
                    }

                    playingCampaign = false;
                    // if transition+duration, duration or random playout configuration has been changed, rerender the playback
                    if (isConfigDifferent(previousConfigTransition, $scope.defaultTransition) ||
                        isConfigDifferent(previousConfigTransitionDuration, defaultTransitionDuration) ||
                        isConfigDifferent(previousConfigDuration, defaultDuration) ||
                        isConfigDifferent(previousConfigRandomPlayout, randomPlayout)) {
                        // need to set the default transition duration here as we're only setting this during wakeup
                        $scope.defaultTransitionDuration = defaultTransitionDuration;
                        render();
                    }

                    previousConfigTransition = $scope.defaultTransition;
                    previousConfigTransitionDuration = defaultTransitionDuration;
                    previousConfigDuration = defaultDuration;
                    previousConfigRandomPlayout = randomPlayout;

                    return $q.resolve({api: $scope.api});
                };

                function isConfigDifferent(previous, current) {
                    return previous !== undefined && previous !== current;
                }

                function onCampaignContentChanges(asset) {
                    if (previousAssetCampaign) {
                        previousAssetCampaign.endPlay = new Date();
                        var actualDuration = previousAssetCampaign.endPlay.getTime() - previousAssetCampaign.startPlay.getTime(); // eslint-disable-line max-len
                        actualDuration = Math.floor(actualDuration / 1000);
                        var startstring = moment(previousAssetCampaign.startPlay).utc().format('DD-MM-YYYY HH-mm-ss');
                        // grab zone id
                        campaignZoneIdNum = campaignService.retrieveZoneIdFromName(campaignZoneId); // eslint-disable-line max-len

                        campaignService.commitPOPRecord({
                            ACID: previousAssetCampaign.id,
                            BID: previousAssetCampaign.bookingLoop.bookingId,
                            BLID: previousAssetCampaign.bookingLoop.id,
                            CID: previousAssetCampaign.campaign.id,
                            FP: (actualDuration == previousAssetCampaign.duration).toString().toLowerCase(), // eslint-disable-line max-len
                            MLAID: previousAssetCampaign.mainAsset.id,
                            PID: deviceRegistrationService.getPlayerId(),
                            PT: halPlatformService.getAppspaceDeviceType(),
                            PD: actualDuration,
                            SID: (previousAssetCampaign.bookingLoop.site) ? previousAssetCampaign.bookingLoop.site.id : 0, // eslint-disable-line max-len
                            SP: startstring,
                            ZID: campaignZoneIdNum
                        });
                    }

                    if (asset && asset.contentFilePath) {
                        var media = asset.contentFilePath;
                        var mediaType = asset.contentType;
                        var duration = asset.duration;

                        var item = {
                            id: asset.contentId,
                            mediaFile: media,
                            type: mediaType,
                            playoutProperties: {
                                duration: duration
                            }
                        };
                        previousAssetCampaign = asset;
                        previousAssetCampaign.startPlay = new Date();
                        playingCampaign = true;
                        loadItem(item);
                    } else {
                        playingCampaign = false;
                        previousAssetCampaign = undefined;
                        nextItem();
                    }
                }

                $scope.destroy = function() {
                    configurationService.unsubscribe(onPlayerPropertiesUpdated);
                    networkConnectionService.unsubscribe(onNetworkConnectionEvent);
                    syncService.unsubscribe(onSyncEvent);
                    campaignService.unsubscribe({
                        mediaZoneId: $scope.id
                    });
                    stopValidIntervalCheck();
                    stopCheckNewContentInterval();
                    if (presenterApi) {
                        presenterApi.unload();
                        presenterApi = undefined;
                    }

                    $element.empty();
                    pendingNext = undefined;
                    buildPlaylistMethod = undefined;
                    return;
                };

                $scope.setDatasource = function(datasource) {
                    if (!datasource) {
                        return $q.resolve();
                    }

                    if (datasource.result.playlists) {
                        $scope.localDatasource = datasource.result;
                        buildPlaylistMethod = buildScheduablePlaylist;
                    } else {
                        $scope.localDatasource = datasource.result.mediaZone;
                        buildPlaylistMethod = buildSimplePlaylist;
                    }

                    render();
                    return $q.resolve({api: $scope.api});
                };

                function render() {
                    buildPlaylistMethod();
                    // Shuffle playlist if random playout requested
                    if (randomPlayout && playlist.randomise) {
                        playlist.randomise();
                    }

                    isFirstItem = true;
                    // Check if we are currently playing a content item, if so we progress to next incase the current is no longer valid
                    if (!currentItem) {
                        return;
                    }

                    // If the widget is frozen we unload this item as it may not be valid
                    if (isFrozen) {
                        unload();
                        return;
                    }

                    // If loaded just progress to next item
                    nextItem();
                }

                $scope.getConfiguration = function() {
                    return $scope.config;
                };

                $scope.getDatasource = function() {
                    return $scope.localDatasourc;
                };

                $scope.freeze = function() {
                    pause();
                    if (presenterApi && presenterApi.freeze) {
                        presenterApi.freeze();
                    }

                    campaignService.pause({
                        mediaZoneId: $scope.id
                    });
                    // Prevent any new content being loaded while frozen
                    queueItemLoading = true;

                    // Disable transitions while frozen
                    $scope.defaultTransitionDuration = 0;

                    // Restore widget so it can be displayed
                    if ($scope.onFreeze) {
                        $scope.onFreeze({
                            api: $scope.api
                        });
                    }

                    isFrozen = true;
                    stopCheckNewContentInterval();
                    stopValidIntervalCheck();
                    stopFireEventTimeout();
                    return $q.resolve({api: $scope.api});
                };

                $scope.unFreeze = function() {
                    // Allow content to be loaded now
                    queueItemLoading = false;

                    resume();
                    campaignService.resume({
                        mediaZoneId: $scope.id
                    });
                    if ($scope.onUnFreeze) {
                        $scope.onUnFreeze({
                            api: $scope.api
                        });
                    }

                    isFrozen = false;
                    return $q.resolve({api: $scope.api});
                };

                // Preload next content file but remain paused
                $scope.wakeup = function() {
                    var promise = $q.defer();
                    var fireEvent = function() {
                        // Restore transition duration after we wakeup
                        $scope.defaultTransitionDuration = defaultTransitionDuration;
                        promise.resolve({api: $scope.api});
                        startValidIntervalCheck();

                        // If synchroniztion enabled, we should play on wakeup
                        if (useSynchronizedScheduling) {
                            resume();
                        }
                    };

                    var delayedEvent = function(content) {
                        if (content) {
                            // If synchroniztion enabled, we show not delay the load
                            if (!useSynchronizedScheduling) {
                                // Gives time for image and first video frame to paint to the surface
                                $timeout(fireEvent, 100);
                                return;
                            }

                            fireEvent();
                            return;
                        }

                        // No content available, keep checking incase one becomes available based on the schedule
                        startCheckNewContentInterval();

                        // Fire endof playlist event as there is no content available
                        var params = {
                            title: '',
                            mediafile: '',
                            mediadescription: '',
                            mediaIndex: 0,
                            mediatitle: '',
                            mediaType: '',
                            noContentAvailable: true
                        };
                        sendEndOfPlaylist(params);
                        fireEvent();
                    };

                    var checkBypassLoad = function(functionPromise) {
                        var bypass = configBypassLoadWait;

                        if (!bypass) {
                            var prop = scriptingService.retrieveAppProperty('player.media.bypass.loadwait');
                            bypass = prop && (prop.toLowerCase() === 'true');
                        }

                        if (bypass) {
                            return $q.resolve();
                        }

                        return functionPromise;
                    };

                    // Allow content to be loaded now
                    queueItemLoading = false;

                    // If restart playlist is set
                    if (restartPlaylistOnWake) {
                        playlist.reset();
                        if (useSynchronizedScheduling) {
                            var startTime = $scope.currentLayout.startTime || new Date();
                            playlist.generateDeterministicSchedule(startTime, null, true);
                        }
                    }

                    if (pendingNext) {
                        checkBypassLoad(loadItem(pendingNext)).then(delayedEvent, fireEvent);
                    } else if (itemLoaded == false) {
                        checkBypassLoad(nextItem()).then(delayedEvent, fireEvent);
                    } else {
                        fireEvent();
                    }

                    return promise.promise;
                };

                // When put to sleep we unload the content to free memory
                $scope.sleep = function() {
                    stopValidIntervalCheck();
                    var resolve = function() {
                        return $q.resolve({api: $scope.api});
                    };

                    // Special case for when putting the widget to sleep with certain content type
                    if (!currentItem || (!restartPlaylistOnWake &&
                        !useSynchronizedScheduling &&
                        [CONTENT_TYPE.WEB, CONTENT_TYPE.FLASH, CONTENT_TYPE.CARD].indexOf(currentItem.type.toLowerCase()) === -1)) {
                        return resolve();
                    }

                    // Unload the content but keep any pending content
                    var pending = pendingNext;
                    unload();
                    if (!useSynchronizedScheduling) {
                        pendingNext = pending;
                    }

                    return resolve();
                };

                $scope.mute = function(shouldMute, isTemporary) {
                    if (!presenterApi || !presenterApi.mute) {
                        return;
                    }

                    presenterApi.mute(shouldMute, isTemporary);
                };

                function unload() {
                    presenterApi.unload();
                    stopValidIntervalCheck();
                    if (loadPromises) {
                        // Reject all outstanding proimises as we are unloading
                        for (var i = 0; i < loadPromises.length; i++) {
                            var p = loadPromises[i];
                            p.reject();
                        }
                    }

                    $timeout.cancel(loadNextTimeoutHandler);

                    loadPromises = [];
                    loadPromises = undefined;
                    currentItem = undefined;
                    itemLoaded = false;
                    pendingNext = undefined;
                    loadNextTimeoutHandler = undefined;
                }

                function pause() {
                    presenterApi.pause();
                }

                function resume() {
                    presenterApi.play();
                }

                function replay() {
                    if (presenterApi.replay) {
                        presenterApi.replay();
                    }
                }

                function setPlaylistItemFrequency(item) {
                    if (item &&
                        item.playoutProperties &&
                        item.playoutProperties.frequencyCondition &&
                        item.playoutProperties.frequencyUnit &&
                        !isNaN(item.playoutProperties.frequencyPlayout)) {
                        var frequencyCondition = item.playoutProperties.frequencyCondition;// eslint-disable-line max-len
                        var frequencyPlayout = item.playoutProperties.frequencyPlayout;
                        var frequencyUnit = item.playoutProperties.frequencyUnit;
                        if (frequencyCondition == 'displaytime') {
                            frequencyUnit = 'day';
                            frequencyCondition = 'exactly';
                            var contentDuration = item.playoutProperties.duration ||
                                item.playoutProperties.fixedVideoDuration ||
                                item.defaultDuration;
                            contentDuration = Math.floor(contentDuration);
                            // 86400 = total seconds in day
                            // find how many times a day we should play the content
                            frequencyPlayout = ((frequencyPlayout * 86400) / 100) / contentDuration; // eslint-disable-line max-len
                            logService.debug('appspace.signs.widgets', 'Converting content "' + item.title + '" (' + item.id + ') ' + item.playoutProperties.frequencyPlayout + '% airtime into ' + frequencyPlayout + '/day');
                        }

                        // if frequency is set
                        if (frequencyCondition != 'notset') {
                            switch (frequencyUnit) {
                                case 'minute':
                                case 'hour':
                                case 'day':
                                case 'loop':
                                    item.schedule.frequency = {
                                        type: frequencyUnit,
                                        condition: frequencyCondition,
                                        value: frequencyPlayout
                                    };
                                    break;
                            }
                        }
                    }
                }

                function buildSimplePlaylist() {
                    if (!$scope.localDatasource) {
                        return;
                    }

                    playlist = new LinearPlaylist(false, true, false);
                    for (var i = 0; i < $scope.localDatasource.playlist.items.length; i++) {
                        var item = $scope.localDatasource.playlist.items[i];
                        item.defaultDuration = defaultDuration;
                        setPlaylistItemFrequency(item);
                    }

                    playlist.setItems(
                        $angular.copy($scope.localDatasource.playlist.items),
                        $scope.localDatasource.id);
                }

                function buildScheduablePlaylist(scheduleId, scheduleName) {
                    if (!$scope.localDatasource) {
                        return;
                    }

                    playlist = new ScheduablePlaylist(
                        false,
                        true,
                        false,
                        interleaveConfiguration.enable,
                        interleaveConfiguration);

                    var items = [];
                    var playlists = $scope.localDatasource.playlists;
                    for (var i = 0; i < playlists.length; i++) {
                        var p = playlists[i];
                        // Create the schedule item
                        var item = {
                            name: p.name,
                            id: p.id,
                            blocker: p.schedule.isExclusive,
                            items: []
                        };

                        // Filter schedules that dont match the criteria
                        if (scheduleId && item.id != scheduleId) {
                            continue;
                        }

                        if (scheduleName && item.name.toLowerCase() != scheduleName.toLowerCase()) { // eslint-disable-line max-len
                            continue;
                        }

                        // Create the scheduling restrictions
                        var itemSchedule = {
                            startDate: new Date(p.schedule.startDate),
                            endDate: new Date(p.schedule.endDate)
                        };

                        itemSchedule.endDate.setHours(23, 59, 59);
                        var k;
                        // If there is a timespan pattern, add it
                        var timespans = [];
                        if (p.schedule.timespans.length > 0) {
                            for (k = 0; k < p.schedule.timespans.length; k++) {
                                var span = p.schedule.timespans[k];
                                var start = moment(span.startTime, 'hh:mm:ss').toDate();
                                var end = moment(span.endTime, 'hh:mm:ss').toDate();
                                var s = {
                                    startTime: start,
                                    endTime: end
                                };
                                timespans.push(s);
                            }
                        }

                        // If there is a recurrance pattern, add it
                        if (p.schedule.days.length > 0) {
                            itemSchedule.recur = {};
                            itemSchedule.recur.days = [];
                            // For each day in the pattern
                            var days = p.schedule.days.toLowerCase().split(';');
                            for (k = 0; k < days.length; k++) {
                                var day = {};
                                switch (days[k]) {
                                    case 'mon':
                                        day.day = 1;
                                        break;
                                    case 'tue':
                                        day.day = 2;
                                        break;
                                    case 'wed':
                                        day.day = 3;
                                        break;
                                    case 'thu':
                                        day.day = 4;
                                        break;
                                    case 'fri':
                                        day.day = 5;
                                        break;
                                    case 'sat':
                                        day.day = 6;
                                        break;
                                    case 'sun':
                                        day.day = 0;
                                        break;
                                    case 'everyday':
                                        day.day = -1;
                                        break;
                                }

                                if (timespans.length > 0) {
                                    day.timespans = timespans;
                                }

                                itemSchedule.recur.days.push(day);
                            }

                            // If not recur pattern, just null the days array
                            if (itemSchedule.recur.days.length == 0) {
                                itemSchedule.recur.days = null;
                            }
                        }

                        // Assign schedule
                        item.schedule = itemSchedule;

                        // Add playlist items
                        for (k = 0; k < p.items.length; k++) {
                            var playlistItem = p.items[k];
                            var pi = $angular.copy(playlistItem);
                            pi.defaultDuration = defaultDuration;

                            // Apply schedule restrictions
                            if (pi.schedule) {
                                if (pi.schedule.playForever) {
                                    pi.schedule.endDate = null;
                                }
                            } else {
                                pi.schedule = {};
                            }

                            setPlaylistItemFrequency(pi);
                            item.items.push(pi);
                        }

                        // Add this schedule to the items collection
                        items.push(item);
                    }

                    playlist.setItems(items);
                }

                function loadItem(newItem, options) {
                    stopValidIntervalCheck();
                    // if presenter pi is no longer there, don't do anything
                    if (!presenterApi) {
                        return $q.resolve();
                    }

                    // Check should we queue the item load?
                    if (queueItemLoading) {
                        pendingNext = newItem;
                        return $q.resolve(newItem);
                    }

                    // check for item's mediaFile, mediaId, type
                    // to make sure that only a playable item can be load.
                    if (!newItem.mediaFile && !newItem.mediaId && !newItem.type) {
                        return $q.resolve(null);
                    }

                    // update the last playout time for this item as the item can be in the state where time on load != time play
                    playlist.updateItemLastPlayTime(newItem);
                    // If its an image and its alread loaded, dont reload unless we are using synchronized scheduling
                    if (!useSynchronizedScheduling && itemLoaded && currentItem && newItem.type.toLowerCase() == CONTENT_TYPE.IMAGE && newItem.mediaFile == currentItem.mediaFile) {
                        presenterApi.restart();
                        $scope.onload();
                        return $q.resolve(newItem);
                    }

                    // Reset pending load
                    var promise = $q.defer();
                    options = options || {};
                    pendingNext = undefined;
                    loadPromises = loadPromises || [];
                    // Add promise to queue to be processed once we get an event from the content presenter for load or error
                    loadPromises.push(promise);

                    newItem.playoutProperties = newItem.playoutProperties || {transition: null, duration: null, aspectRatio: null, volumeLevel: audioLevel};

                    // Check is a transition  supplied with this item
                    var itemType = newItem.type.toLowerCase();
                    var isItemAudioOrVideo = itemType === CONTENT_TYPE.VIDEO || itemType === CONTENT_TYPE.AUDIO;
                    var transition = newItem.playoutProperties.transition === null ? $scope.defaultTransition : newItem.playoutProperties.transition; // eslint-disable-line max-len
                    var duration = !newItem.playoutProperties.duration ? defaultDuration : newItem.playoutProperties.duration; // eslint-disable-line max-len
                    var stretch = newItem.playoutProperties.aspectRatio == null ? $scope.defaultStretch : newItem.playoutProperties.aspectRatio; // eslint-disable-line max-len
                    // if no transition
                    if (!transition) {
                        // if content is video/audio and driver doesn't support transition, set it to none
                        // else, use app default transition
                        transition = (isItemAudioOrVideo && !isVideoTransitionSupported) ? TRANSITION.NONE : appDefaultTransition;
                    }

                    // optimize transition
                    if (currentItem) {
                        var currentItemType = currentItem.type.toLowerCase();
                        if (transition == TRANSITION.FADE_IN && (currentItemType == CONTENT_TYPE.HTML || currentItemType == CONTENT_TYPE.HTML_SHORTEN)) {
                            // if we're fade in ONLY & previous item is rich text content, we need to do cross fade
                            transition = TRANSITION.CROSS_FADE;
                        } else if (transition == TRANSITION.CROSS_FADE && (currentItemType == CONTENT_TYPE.VIDEO || currentItemType == CONTENT_TYPE.CARD || currentItemType == CONTENT_TYPE.WEB)) {
                            // Optimize fadeIn,fadeOut transitions by removing fadeOut for content that is fully opaque on fade in
                            // reduce performance hit by removing the video/card/web page directly after new content transitioned in instead of fading it out
                            transition = TRANSITION.FADE_IN;
                        }
                    }

                    // Check whether content should use its native duration or not
                    if (useNativeDuration(newItem.playoutProperties, isItemAudioOrVideo)) {
                        duration = 0; // use natural duration of the content
                    }

                    var webEngine = syncService.getCurrentAppProperty('player.media.webengine');

                    // Generate a content item for the content presenter
                    var presenterItem = {
                        id: newItem.mediaId || newItem.id, // mediaId = scheduling table, id = az table
                        contentId: newItem.contentId,
                        mediaType: itemType,
                        mediaUrl: newItem.mediaFile,
                        duration: duration,
                        stretch: stretch,
                        enterTransition: transition.split(',').shift(),
                        exitTransition: transition.split(',').pop(),
                        options: {
                            webEngine: webEngine ? webEngine.toLowerCase() : undefined,
                        },
                        playback: {
                            contentDuration: newItem.playoutProperties.duration
                        },
                        data: {
                            properties: scriptingService.retrieveAppProperties(),
                            isSynchronizedPlayback: useSynchronizedScheduling
                        }
                    };

                    // Set content audio level if present
                    var targetAudioLevel = audioLevel;
                    if (!dataUtil.isNullOrUndefined(newItem.playoutProperties.volumeLevel)) {
                        presenterItem.options.volume = fixAudioLevel(newItem.playoutProperties.volumeLevel / DEFAULT_VOLUME_PERCENTAGE);
                        targetAudioLevel = presenterItem.options.volume;
                    }

                    presenterApi.setVolume(targetAudioLevel);

                    // Set content audio mute if present
                    var isDeviceMute = halMediaService.getDeviceMute();
                    presenterItem.options.muted = isDeviceMute || audioMute;
                    if (!isDeviceMute && !dataUtil.isNullOrUndefined(newItem.playoutProperties.isMuted)) {
                        presenterItem.options.muted = newItem.playoutProperties.isMuted;
                    }

                    presenterApi.mute(presenterItem.options.muted);

                    if (closecaption) {
                        presenterItem.options.closecaption = closecaption;
                        presenterItem.options.closecaptionInfoBrightsign = closecaptionInfoBrightsign; // eslint-disable-line max-len
                        presenterItem.options.closecaptionInfoChrome = closecaptionInfoChrome;
                        presenterItem.options.closecaptionInfoSamsung = closecaptionInfoSamsung;
                        presenterItem.options.closecaptionTrack = closecaptionTrack;

                        if (!videoLayerIndex) {
                            videoLayerIndex = -1;
                        }
                    }

                    if (videoLayerIndex != null) {
                        presenterItem.options.layerindex = videoLayerIndex;
                    }

                    var forceAC3 = scriptingService.retrieveAppProperty('player.brightsign.forceac3');
                    if (forceAC3 && (forceAC3 == 'true' || forceAC3 == true)) {
                        presenterItem.options.forceac3audio = 'true';
                    }

                    var webRTC = allowWebRTC; // from widget
                    // only check settings from player property if widget doesn't have any setting
                    if (!webRTC) {
                        var playerAllowWebRTC = scriptingService.retrieveAppProperty('player.webrtc.allow') || scriptingService.retrieveAppProperty('player.media.permissionrequest.allow');
                        webRTC = playerAllowWebRTC && playerAllowWebRTC.toLowerCase() == 'true';
                    }

                    if (isMediaRequestsAllowed || webRTC) {
                        presenterItem.options.allowmediarequest = true;
                    }

                    presenterItem.options.enablewebframelayer =
                        customWebFrameLayer !== undefined ?
                            customWebFrameLayer : (scriptingService.retrieveAppProperty('player.webframe.layer.enable') || '').toLowerCase() === 'true';

                    mediaUtil.processForceIframeOption(presenterItem.options,
                        configurationService.getCurrentProperty(PLAYER_PROPERTY_KEYS.WEB_RENDERER));

                    var currentTime = new Date().getTime();
                    if (!processPreloadTime(presenterItem, newItem, currentTime) && options.delay) {
                        presenterItem.loadByTime = currentTime + options.delay;
                    }

                    currentItem = newItem;

                    var preloadContent = function(presenterItem, options) {
                        if (!presenterApi || !presenterApi.preloadContentItem || !options.preload) {
                            return false;
                        }

                        presenterApi.preloadContentItem(presenterItem.mediaUrl);
                        return true;
                    };

                    var onWpgRoutingResolve = function(resolvedUrl) {
                        presenterItem.mediaUrl = resolvedUrl;
                        if (preloadContent(presenterItem, options)) {
                            return;
                        }

                        presenterApi.setContentItem(presenterItem);
                    };

                    var onResolveError = function(url) {
                        if (preloadContent(presenterItem, options)) {
                            return;
                        }

                        presenterApi.setContentItem(presenterItem);
                    };

                    var mediaType = presenterItem.type;
                    if (!mediaType) {
                        mediaType = presenterItem.mediaType;
                    }

                    var onResolve = function(url) {
                        // resolve after all routing has been done to ensure the cache content url is
                        // called for wpg files.
                        networkContentRouterService.resolve(url, 'signs-wpg-routing-service').then(onWpgRoutingResolve, function() {
                            // if we receive error while resolving wpg, just resolve it directly
                            onWpgRoutingResolve(url);
                        });
                    };

                    var resolveMediaUrl = function() {
                        // Attempt to resolve the url
                        networkContentRouterService.resolve(presenterItem.mediaUrl, 'signs-content-routing-service', {
                            type: mediaType,
                            forceStreamType: forceStreamType,
                            bypassFlashLocalPlayback: bypassFlashLocalPLaybackRestriction
                        }).then(onResolve, onResolveError);
                    };

                    resolveMediaUrl();
                    return promise.promise;
                }

                function processPreloadTime(presenterItem, newItem, currentTime) {
                    if (!useSynchronizedScheduling || !newItem.$_endTime) {
                        return false;
                    }

                    // disable video fade in transition when using synchronized scheduling
                    if (presenterItem.mediaType === CONTENT_TYPE.VIDEO) {
                        presenterItem.enterTransition = '';
                    }

                    var startTime;
                    var startTimeOffset;
                    var desiredDuration;
                    var adjustedDuration;
                    if (preloadBehavior === VALID_PRELOAD_BEHAVIOR.POST_COMPLETION) {
                        // The desired start time is schedule start time + minLoadTime
                        startTime = newItem.$_startTime.getTime() + minLoadTime;
                        var availableTime = newItem.$_startTime.getTime() + minLoadTime - currentTime;
                        startTimeOffset = 0;
                        desiredDuration = dateTimeUtil.toSeconds(newItem.$_endTime.getTime() - newItem.$_startTime.getTime());
                        // Check if we can meet the required start time
                        startTime = availableTime <= 0 ?
                            currentTime + minLoadTime : // If not, aim to start from current time + min time
                            newItem.$_startTime.getTime() + minLoadTime;
                        presenterItem.loadByTime = startTime;
                        presenterItem.endByTime = newItem.$_endTime.getTime();
                        adjustedDuration = dateTimeUtil.toSeconds(newItem.$_endTime.getTime() - startTime);
                        // Default to 1 second for missing playback window
                        presenterItem.duration = 1;
                        if (adjustedDuration > 0) {
                            // Figure our what our start offset is
                            startTimeOffset = desiredDuration - adjustedDuration;
                            if (!isSychronizedSeekingSupported) {
                                startTimeOffset -= dateTimeUtil.toSeconds(minLoadTime);
                            }

                            presenterItem.duration = adjustedDuration;
                            presenterItem.seekOffset = startTimeOffset;
                        }
                    } else {
                        startTime = newItem.$_startTime.getTime();
                        var overTime = currentTime - startTime;
                        startTimeOffset = 0;
                        desiredDuration = dateTimeUtil.toSeconds(newItem.$_endTime.getTime() - newItem.$_startTime.getTime());
                        startTime = overTime <= 0 ? startTime : startTime + overTime;
                        presenterItem.loadByTime = startTime;
                        presenterItem.endByTime = newItem.$_endTime.getTime();
                        adjustedDuration = dateTimeUtil.toSeconds(newItem.$_endTime.getTime() - startTime);
                        presenterItem.duration = 1;
                        if (adjustedDuration > 0) {
                            startTimeOffset = desiredDuration - adjustedDuration;
                            presenterItem.duration = adjustedDuration;
                            presenterItem.seekOffset = startTimeOffset;
                        }
                    }

                    return preloadBehavior === VALID_PRELOAD_BEHAVIOR.PRE_COMPLETION;
                }

                function useNativeDuration(playoutProperties, isItemAudioOrVideo) {
                    // if content has custom duration, don't use native duration
                    if (playoutProperties.duration) {
                        return false;
                    }

                    // if content has natural duration, use native duration
                    // if content doesn't have natural duration but it's a audio or video, use native duration (backward compatibility)
                    return playoutProperties.hasNaturalDuration || isItemAudioOrVideo;
                }

                function sendEndOfPlaylist(parameters) {
                    if (parameters && parameters.noContentAvailable) {
                        return;
                    }

                    contentStateService.updateContentState({
                        zoneId: parseInt($scope.id, 10),
                        content: {},
                        state: CONTENT_STATE_EVENT.END_OF_PLAYLIST
                    });

                    postMessagingService.raiseMessage({
                        message: POST_MESSAGES.NAME,
                        type: POST_MESSAGES.WIDGET_END_OF_PLAYLIST,
                        info: parameters
                    });
                    return scriptingService.raiseEvent('element_' + $scope.id, 'endofplaylist', parameters);
                }

                // Checks if the current item is valid, if not it calls next
                function checkItemValidility() {
                    var ctime = new Date().getMinutes();
                    if (lastValidCheck != ctime) {
                        lastValidCheck = ctime;
                        if (currentItem) {
                            if (!playlist.isValid(currentItem)) {
                                // Send on media complete
                                var params = {
                                    title: currentItem.title,
                                    mediafile: currentItem.mediaFile,
                                    mediadescription: currentItem.description,
                                    mediaIndex: getItemPosition(currentItem),
                                    mediatitle: currentItem.title,
                                    mediaType: currentItem.type
                                };

                                var onMediaCompleteEvent = function() {
                                    // After processing try to load next item
                                    nextItem().then(function(item) {
                                        if (item == null) {
                                            // No next item available, so unload current
                                            unload();
                                            startCheckNewContentInterval();
                                            sendEndOfPlaylist(params);
                                        }
                                    });
                                };

                                scriptingService.raiseEvent('element_' + $scope.id, 'mediacomplete', params).then(onMediaCompleteEvent, onMediaCompleteEvent);
                            }
                        }
                    }
                }

                // Starts an interval if the current item has a schedule
                function startValidIntervalCheck() {
                    stopValidIntervalCheck();

                    if (useSynchronizedScheduling) {
                        // skip as we are using deterministic scheduling
                        return;
                    }

                    if (!currentItem) {
                        return;
                    }

                    // if we don't have any schedules, don't check the item validity
                    if (!currentItem.schedule && (!currentItem._parent || !currentItem._parent.schedule)) {
                        return;
                    }

                    validCheckInterval = $interval(checkItemValidility, 1000);
                }

                // Stops the interval that checks the current item's schedule is valid
                function stopValidIntervalCheck() {
                    lastValidCheck = -1;
                    if (!validCheckInterval) {
                        return;
                    }

                    $interval.cancel(validCheckInterval);
                    validCheckInterval = undefined;
                }

                function stopFireEventTimeout() {
                    if (!fireEventTimeoutPromise) {
                        return;
                    }

                    $timeout.cancel(fireEventTimeoutPromise);
                    fireEventTimeoutPromise = undefined;
                }

                function onCheckNewContent() {
                    if (loadPromises && loadPromises.length > 0) {
                        return;
                    }

                    if (currentItem) {
                        stopCheckNewContentInterval();
                        return;
                    }

                    nextItem().then(function(item) {
                        if (!item) {
                            return;
                        }

                        // New content now loaded
                        stopCheckNewContentInterval();
                    });
                }

                // Starts an interval
                function startCheckNewContentInterval() {
                    stopCheckNewContentInterval();
                    checkNewContentInterval = $interval(onCheckNewContent, 1000);
                }

                // Stops the interval
                function stopCheckNewContentInterval() {
                    if (!checkNewContentInterval) {
                        return;
                    }

                    $interval.cancel(checkNewContentInterval);
                    checkNewContentInterval = undefined;
                }

                function nextItem(options) {
                    options = options || {};
                    if (!$scope.localDatasource) {
                        triggerNoContentAvailable();
                        return $q.resolve();
                    }

                    // We  add minLoadTime to current date when getting playlist by time
                    var item = useSynchronizedScheduling && playlist.getPlaylistItemByTime ?
                        playlist.getPlaylistItemByTime(new Date(new Date().getTime() + minLoadTime)) : playlist.next();

                    if (item && item.mediaFile) {
                        if ($scope.onContentAvailable) {
                            $scope.onContentAvailable();
                        }

                        var promise = $q.defer();
                        loadItem(item, options).then(promise.resolve, promise.resolve);
                        return promise.promise;
                    }

                    // No next item available, so unload current
                    unload();
                    // No content available, keep scanning for new content.
                    startCheckNewContentInterval();
                    triggerNoContentAvailable();
                    return $q.resolve(null);
                }

                function triggerNoContentAvailable() {
                    logService.debug('appspace.signs.widgets', 'No content available.');
                    var params = {
                        title: '',
                        mediafile: '',
                        mediadescription: '',
                        mediaIndex: 0,
                        mediatitle: '',
                        mediaType: ''
                    };
                    sendEndOfPlaylist(params);

                    if (!$scope.onNoContentAvailable) {
                        return;
                    }

                    $scope.onNoContentAvailable();
                }

                function previousItem() {
                    if (!$scope.localDatasource) {
                        return $q.resolve();
                    }

                    var item = playlist.previous();
                    if (!item) {
                        return $q.resolve();
                    }

                    var promise = $q.defer();
                    loadItem(item).then(promise.resolve, promise.resolve);
                    return promise.promise;
                }

                function startLoadNextTimer() {
                    loadNextTimeoutHandler = $timeout(function() {
                        $scope.onnext();
                    }, LOAD_NEXT_TIMEOUT_DURATION);
                }

                function processLastItem() {
                    var eventPromises = [];

                    // Check are we at the end of the playlist
                    if (playlist.isLastItem()) {
                        isFirstItem = true;
                        // Randomise the playlist
                        if (randomPlayout && playlist.randomise) {
                            playlist.randomise();
                        }

                        var params = {
                            title: currentItem.title,
                            mediafile: currentItem.mediaFile,
                            mediadescription: currentItem.description,
                            mediaIndex: getItemPosition(currentItem),
                            mediatitle: currentItem.title,
                            mediaType: currentItem.type
                        };
                        // invalidate the current offset
                        playlist.currentOffset = undefined;
                        eventPromises.push(sendEndOfPlaylist(params));
                    }

                    return eventPromises;
                }

                // Content Presenter Event Handlers
                $scope.onapiready = function(api) {
                    presenterApi = api;
                    presenterApi.pause();
                };

                $scope.onactionreceived = function(actionString) {
                    if (!actionString) {
                        return;
                    }

                    actionString = scriptingService.resolvePropertyExpression(actionString);
                    var actions = ActionUtil.convertStringToActions(actionString);
                    for (var i = 0; i < actions.length; i++) {
                        var action = actions[i];
                        scriptingService.executeAction(action);
                    }
                };

                $scope.onload = function(loadedItem) {
                    if (!currentItem) {
                        logService.warn('appspace.signs.widgets', 'Receive onload event when current item is empty');
                        return;
                    }

                    var params = {
                        title: currentItem.title,
                        mediafile: currentItem.mediaFile,
                        mediadescription: currentItem.description,
                        mediaIndex: getItemPosition(currentItem),
                        mediatitle: currentItem.title,
                        mediaType: currentItem.type,
                        isFirstItem: isFirstItem,
                        isLastItem: playlist.isLastItem()
                    };
                    var currentProperties = configurationService.getCurrentProperties();
                    scriptingService.raiseEvent('element_' + $scope.id, 'medialoaded', params);

                    // Remove flag as the first item
                    isFirstItem = false;

                    // Create a playout metric
                    currentPassiveMetric = new PassiveMetric();
                    // use mediaId if available
                    // in scheduleable media zone, id is schedule id
                    // in non-scheduleable media zone, id is media id
                    // mediaId only exists in schedulable media zone
                    currentPassiveMetric.contentId = currentItem.mediaId || currentItem.id;
                    currentPassiveMetric.startTime = new Date();
                    currentPassiveMetric.playerGuid = deviceRegistrationService.getPlayerId();
                    currentPassiveMetric.applicationId = parseInt(syncService.retrieveAppId(), 10); // eslint-disable-line max-len
                    // for new metric
                    currentPassiveMetric.deviceType = halPlatformService.getAppspaceDeviceType();
                    // Ensure data parity with the current properties
                    currentPassiveMetric.channelGuid = currentItem.channelGuid || currentProperties[PLAYER_PROPERTY_KEYS.CHANNEL_ID];
                    currentPassiveMetric.contentGuid = currentItem.contentId;
                    currentPassiveMetric.contentType = currentItem.type;
                    currentPassiveMetric.contentDuration = currentItem.defaultDuration;
                    currentPassiveMetric.accountGuid = coreConfigurationService.get(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.ACCOUNT_ID);
                    currentPassiveMetric.deviceGroupGuid = currentProperties[PLAYER_PROPERTY_KEYS.DEVICE_GROUP_ID] || '';
                    currentPassiveMetric.deviceTags = dataUtil.convertCSVToArray(currentProperties[PLAYER_PROPERTY_KEYS.DEVICE_TAGS]);
                    if (loadedItem && loadedItem.metadata && loadedItem.metadata.contentInfo) {
                        for (var key in loadedItem.metadata.contentInfo) {
                            currentPassiveMetric[key] = loadedItem.metadata.contentInfo[key];
                        }
                    }

                    // widgetDatasourceId = Referring to widget's datasource id. meant for non-mediazone widgets
                    currentPassiveMetric.mediaZoneDatasourceId = currentPassiveMetric.widgetDatasourceId = parseInt($scope.contentSourceId, 10); // eslint-disable-line max-len
                    var mediaZoneDescription = syncService.getMediaZoneDescription(currentPassiveMetric.mediaZoneDatasourceId);
                    if (mediaZoneDescription) {
                        currentPassiveMetric.mediaZoneGuid = mediaZoneDescription.guid;
                    }

                    itemLoaded = true;
                    if (loadPromises) {
                        // Resolve all queued promises
                        for (var i = 0; i < loadPromises.length; i++) {
                            var p = loadPromises[i];
                            p.resolve(currentItem);
                        }
                    }

                    loadPromises = [];
                    loadPromises = undefined;

                    $scope.onContentLoaded({content: currentItem});
                    startValidIntervalCheck();
                };

                $scope.onnext = function() {
                    nextItem();
                };

                $scope.oncomplete = function() {
                    var eventPromises = [];

                    // Send on media complete
                    var params = {
                        title: currentItem.title,
                        mediafile: currentItem.mediaFile,
                        mediadescription: currentItem.description,
                        mediaIndex: getItemPosition(currentItem),
                        mediatitle: currentItem.title,
                        mediaType: currentItem.type
                    };
                    eventPromises.push(scriptingService.raiseEvent('element_' + $scope.id, 'mediacomplete', params));

                    if (currentPassiveMetric) {
                        currentPassiveMetric.endTime = new Date();
                        metricService.addPassiveMetric(currentPassiveMetric);
                    }

                    currentPassiveMetric = undefined;

                    var eventPromisesLastItem = processLastItem();

                    if (eventPromisesLastItem) {
                        eventPromises = eventPromises.concat(eventPromisesLastItem);
                    }

                    var onCompletionCallback = function() {
                        nextItem().then(function() {
                            isNextItemPreloading = false;
                        });
                    };

                    // Once all events are executed only then go next
                    $q.allSettled(eventPromises).then(onCompletionCallback, onCompletionCallback);
                };

                $scope.onerror = function(err) {
                    if (loadPromises) {
                        // Reject all queued promises
                        for (var i = 0; i < loadPromises.length; i++) {
                            var p = loadPromises[i];
                            p.reject(currentItem);
                        }
                    }

                    loadPromises = [];
                    loadPromises = undefined;

                    var eventPromises = processLastItem();

                    var onCompletionCallback = function() {
                        if (playlist.activeItems && playlist.activeItems.length > 1) {
                            $scope.onnext();
                        } else {
                            startLoadNextTimer();
                        }
                    };

                    contentStateService.updateContentState({
                        zoneId: parseInt($scope.id, 10),
                        content: currentItem,
                        state: CONTENT_STATE_EVENT.ITEM_LOAD_ERROR
                    });
                    $q.allSettled(eventPromises).then(onCompletionCallback, onCompletionCallback);
                };

                $scope.executeAction = function(action) {
                    var actionToExecute = actionsConfig[action.name.toLowerCase()];
                    if (!actionToExecute) {
                        return false;
                    }

                    actionToExecute(action);
                    return true;
                };

                $scope.isNavigationEnabled = function() {
                    return !playlist ? false : playlist.getTotalItems() > 1;
                };

                $scope.nextContent = nextItem;

                $scope.previousContent = previousItem;

                function preloadNextItem() {
                    isNextItemPreloading = true;
                    nextItem({preload: true});
                }

                $scope.onItemPlaybackProgress = function(item, progress) {
                    if (!useSynchronizedScheduling || preloadBehavior === VALID_PRELOAD_BEHAVIOR.POST_COMPLETION ||
                        progress.position - item.content.seekOffset + dateTimeUtil.toSeconds(minLoadTime) < item.duration ||
                        isNextItemPreloading) {
                        return;
                    }

                    preloadNextItem();
                };

                function getItemPosition(currentItem) {
                    return currentItem.$_position || currentItem.position;
                }

                function onPlayerPropertiesUpdated(properties) {
                    if (!presenterApi) {
                        return;
                    }

                    presenterApi.updateDeviceProperties(properties);
                }

                function updateResourceManifest(manifest) {
                    if (!presenterApi || !presenterApi.updateResourceManifest) {
                        return;
                    }

                    presenterApi.updateResourceManifest(manifest);
                }

                function onNetworkConnectionEvent(event) {
                    switch ((event || '').toLowerCase()) {
                        case NETWORK_CONNECTION_STATE.CONNECTED:
                            if (!isDisconnected) {
                                return;
                            }

                            isDisconnected = false;
                            if (!presenterApi || !presenterApi.reload) {
                                return;
                            }

                            presenterApi.reload(syncService.getCurrentAppProperties());
                            break;
                        case NETWORK_CONNECTION_STATE.DISCONNECTED:
                            isDisconnected = true;
                            break;
                    }
                }

                function onSyncEvent(syncEvent) {
                    syncEvent = syncEvent || {};
                    switch ((syncEvent.event || '').toLowerCase()) {
                        case SYNC_EVENT.MANIFEST_UPDATED:
                            updateResourceManifest(syncEvent.manifest);
                            break;
                    }
                }

                function init() {
                    configurationService.subscribe(onPlayerPropertiesUpdated);
                    networkConnectionService.subscribe(onNetworkConnectionEvent);
                    isDisconnected = networkConnectionService.getCurrentState() !== NETWORK_CONNECTION_STATE.CONNECTED;
                    syncService.subscribe(onSyncEvent);
                }

                init();
            }]
        };
    });
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');

    app.service('singleAppService',
        ["$q", "syncService", "SYNC_EVENT", "halPlatformService", "dataUtil", "deviceAPIService", "deviceRegistrationService", "FEATURE_CODE", "PLAYER_PROPERTY_KEYS", function(
            $q,
            syncService,
            SYNC_EVENT,
            halPlatformService,
            dataUtil,
            deviceAPIService,
            deviceRegistrationService,
            FEATURE_CODE,
            PLAYER_PROPERTY_KEYS
        ) {
            var NOT_SUPPORTED_ERROR_MESSAGE = 'Single app mode is not supported on this platform';
            var subscribers = [];

            function start() {
                var defer = $q.defer();
                isSingleAppSupported().then(function(result) {
                    if (!result) {
                        defer.reject(NOT_SUPPORTED_ERROR_MESSAGE);
                        return;
                    }

                    syncService.subscribe(onSyncServiceUpdated);
                    defer.resolve();
                });

                return defer.promise;
            }

            function isSingleAppSupported() {
                return $q.resolve(dataUtil.parseBoolean(halPlatformService.getFeatureSupport(FEATURE_CODE.DEVICE_SUPPORT_SINGLE_APP_MODE)));
            }

            function getProperties() {
                return syncService.getCurrentAppProperties() || {};
            }

            function onSyncServiceUpdated(event) {
                if (!event) {
                    return;
                }

                switch (event.event) {
                    case SYNC_EVENT.ERROR_RETRIEVING_TIMESTAMP:
                        exitSingleAppMode();
                        break;
                    case SYNC_EVENT.SCRIPT_READY:
                    case SYNC_EVENT.SCRIPT_SAVED:
                        var properties = getProperties();
                        if (!dataUtil.parseBoolean(properties[PLAYER_PROPERTY_KEYS.ASAM_ALLOWED])) {
                            disableSingleAppMode();
                            return;
                        }

                        updateSingleAppMode(dataUtil.parseBoolean(properties[PLAYER_PROPERTY_KEYS.ASAM_ENABLED] || ''));
                        break;
                }
            }

            function disableSingleAppMode() {
                return exitSingleAppMode(true).finally(function() {
                    deviceAPIService.setProperty(deviceRegistrationService.getPlayerId(), PLAYER_PROPERTY_KEYS.ASAM_ENABLED, false);
                    notifySubscribers(false, false, true);
                });
            }

            function notifySubscribers(allow, enabled, success) {
                var result = {
                    allow: allow,
                    enabled: enabled,
                    success: success
                };
                for (var i = 0; i < subscribers.length; i++) {
                    var callback = subscribers[i];
                    if (!callback) {
                        continue;
                    }

                    callback(result);
                }

                return result;
            }

            function subscribe(callback) {
                var l = subscribers.length;
                var found = false;
                for (var i = 0; i < l; i++) {
                    var cb = subscribers[i];
                    if (cb == callback) {
                        found = true;
                        break;
                    }
                }

                if (found) {
                    return;
                }

                subscribers.push(callback);
            }

            function unsubscribe(callback) {
                var l = subscribers.length;
                for (var i = 0; i < l; i++) {
                    var cb = subscribers[i];
                    if (cb == callback) {
                        subscribers.splice(i, 1);
                        break;
                    }
                }
            }

            function updatePropertyAndNotifySubscribers(enabled, success) {
                if (success) {
                    deviceAPIService.setProperty(deviceRegistrationService.getPlayerId(), PLAYER_PROPERTY_KEYS.ASAM_ENABLED, enabled);
                }

                return notifySubscribers(true, enabled, success);
            }

            function enterSingleAppMode() {
                return updateSingleAppMode(true);
            }

            function exitSingleAppMode(skipNotifyAndUpdate) {
                return updateSingleAppMode(false, skipNotifyAndUpdate);
            }

            function getSingleAppMode() {
                return halPlatformService.getSingleAppMode();
            }

            function updateSingleAppMode(value, skipNotifyAndUpdate) {
                var defer = $q.defer();
                isSingleAppSupported().then(function(result) {
                    if (!result) {
                        defer.reject(NOT_SUPPORTED_ERROR_MESSAGE);
                        return;
                    }

                    var isValueEnabled = dataUtil.parseBoolean(value);
                    getSingleAppMode().then(function(mode) {
                        if (mode === isValueEnabled) {
                            defer.resolve(notifySubscribers(true, mode));
                            return;
                        }

                        var notify = function(value, success) {
                            if (skipNotifyAndUpdate) {
                                return;
                            }

                            return updatePropertyAndNotifySubscribers(value, success);
                        };

                        if (isValueEnabled) {
                            halPlatformService.enterSingleAppMode().then(function() {
                                defer.resolve(notify(value, true));
                            }, function() {
                                defer.reject(notify(value, false));
                            });
                            return;
                        }

                        halPlatformService.exitSingleAppMode().then(function() {
                            defer.resolve(notify(value, true));
                        }, function() {
                            defer.reject(notify(value, false));
                        });
                    });
                });

                return defer.promise;
            }

            return {
                start: start,
                subscribe: subscribe,
                unsubscribe: unsubscribe,
                enterSingleAppMode: enterSingleAppMode,
                exitSingleAppMode: exitSingleAppMode,
                getSingleAppMode: getSingleAppMode,
                disableSingleAppMode: disableSingleAppMode,
                isSingleAppSupported: isSingleAppSupported
            };
        }]);
})(window.angular);

/* eslint-disable new-cap */
/* eslint-disable prefer-rest-params */
(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');
    app.factory('DateOverload', ["$window", "DateTimeUtil", "halPlatformService", "FEATURE_CODE", function($window, DateTimeUtil, halPlatformService, FEATURE_CODE) {
        function DateOverload() {
            var ret = {};
            var dateTimeUtil = new DateTimeUtil();
            var isPerformanceApiSupported = halPlatformService.getFeatureSupport(FEATURE_CODE.RUNTIME_PERFORMANCE_API_SUPPORTED);

            ret.overload = function() {
                if ($window.originalDate) {
                    return;
                }

                $window.originalDate = Date;

                var getDelta = function() {
                    if (!$window.activeWebSocketsTimeSyncTask) {
                        return 0;
                    }

                    return $window.activeWebSocketsTimeSyncTask.delta || 0;
                };

                var getCurrentTimeWithDelta = function() {
                    var currentTime = isPerformanceApiSupported ? dateTimeUtil.getTimeOrigin() + $window.performance.now() : $window.originalDate.now();
                    return currentTime + getDelta();
                };

                // eslint-disable-next-line no-global-assign
                Date = function() {
                    // Cannot use originalDate.apply(this, arguments).  See http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply
                    if (arguments.length === 1) {
                        return new $window.originalDate(arguments[0]);
                    } else if (arguments.length === 2) {
                        return new $window.originalDate(arguments[0], arguments[1]);
                    } else if (arguments.length === 3) {
                        return new $window.originalDate(arguments[0], arguments[1], arguments[2]);
                    } else if (arguments.length === 4) {
                        return new $window.originalDate(arguments[0], arguments[1], arguments[2], arguments[3]);
                    } else if (arguments.length === 5) {
                        return new $window.originalDate(
                            arguments[0],
                            arguments[1],
                            arguments[2],
                            arguments[3],
                            arguments[4]
                        );
                    } else if (arguments.length === 6) {
                        return new $window.originalDate(
                            arguments[0],
                            arguments[1],
                            arguments[2],
                            arguments[3],
                            arguments[4],
                            arguments[5]
                        );
                    } else if (arguments.length === 7) {
                        return new $window.originalDate(
                            arguments[0],
                            arguments[1],
                            arguments[2],
                            arguments[3],
                            arguments[4],
                            arguments[5],
                            arguments[6]
                        );
                    }

                    return new $window.originalDate(getCurrentTimeWithDelta());
                };

                Date.prototype = $window.originalDate.prototype;
                Date.now = getCurrentTimeWithDelta;

                Date.originalDate = $window.originalDate;
                Date.UTC = $window.originalDate.UTC;
                Date.parse = $window.originalDate.parse;
                Date.toLocaleString = $window.originalDate.toLocaleString;
                Date.toString = $window.originalDate.toLocaleString;
            };

            ret.reset = function() {
                // eslint-disable-next-line no-global-assign
                Date = $window.originalDate;
                delete $window.originalDate;
            };

            return ret;
        }

        return (DateOverload);
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');
    app.factory('MasterMode', ["$window", "$timeout", "OverlayGenerator", "TimerHelper", "logService", "CLOCK_SYNC", "helperUtil", "DateOverload", function($window, $timeout, OverlayGenerator, TimerHelper, logService, CLOCK_SYNC, helperUtil, DateOverload) {
        function MasterMode(debugEnabled, colors, remoteEnabled) {
            var connectedDevices = {};
            var cleanUpThread;
            var overlay;
            var timer;
            var mode = 'MASTER';
            var stopped = true;
            var cleanUpInterval = 60000;
            var serverResubscribeInterval = 30000;
            var defaultColor = 'rgba(0, 0, 0, 0.65)';
            var ret = {};
            var LOG_PREFIX = 'Controller Mode - ';
            var dateOverload;

            var getConnectedDevicesCount = function() {
                return Object.keys(connectedDevices).length;
            };

            var updateDebugData = function(currentTime) {
                if (stopped) {
                    return;
                }

                overlay.setClockText(moment(currentTime).format('hh:mm:ss A'));
                overlay.setModeText(mode, remoteEnabled ? '' : ' (Remote Disabled)');
                overlay.setConnectedDevicesText(getConnectedDevicesCount());
                overlay.setBackgroundColor(colors[currentTime.getSeconds() % colors.length] || defaultColor);
            };

            var handleIncomingWebSocket = function(uid) {
                if (stopped) {
                    return;
                }

                connectedDevices[uid] = {
                    timestamp: new Date()
                };

                logService.info(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Received Connection from Worker Id: ' + uid);
            };

            var subscribeServerMessages = function() {
                $window.$asCoreFrameworkService
                    .getServices()
                    .hal.halNetworkService.subscribeServerMessages(handleIncomingWebSocket);
                logService.info(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Subscribed to WebSocket Messages');
            };

            var unSubscribeServerMessages = function() {
                $window.$asCoreFrameworkService
                    .getServices()
                    .hal.halNetworkService.unSubscribeServerMessages(handleIncomingWebSocket);
                logService.info(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Unsubscribed from WebSocket Messages');
            };

            var tryResetOnNoConectedDevices = function() {
                if (getConnectedDevicesCount() > 0) {
                    return;
                }

                logService.info(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'WebSocket server needs resubscribing, refreshing');

                unSubscribeServerMessages();
                subscribeServerMessages();
            };

            var clearCleanUpThread = function() {
                helperUtil.clearTimeoutIfActive(cleanUpThread);
                cleanUpThread = undefined;
            };

            var cleanConnectedDevicesList = function() {
                for (var i in connectedDevices) {
                    if (connectedDevices[i].timestamp && new Date() - connectedDevices[i].timestamp >= cleanUpInterval) {
                        logService.debug(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Inactive worker device will be removed, Id: ' + i);
                        delete connectedDevices[i];
                    }
                }

                clearCleanUpThread();
                cleanUpThread = $timeout(cleanConnectedDevicesList, cleanUpInterval);
            };

            var getConnectedDevicesDetails = function() {
                // return a copy of connected devices for debugging purposes
                return Object.assign({}, connectedDevices);
            };

            var attachGetConnectedDevicesToWindow = function() {
                if (!$window.activeWebSocketsTimeSyncTask) {
                    $window.activeWebSocketsTimeSyncTask = {};
                }

                $window.activeWebSocketsTimeSyncTask.getConnectedDevices = function() {
                    return getConnectedDevicesDetails();
                };
            };

            var init = function() {
                logService.info(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Initializing');
                attachGetConnectedDevicesToWindow();

                if (debugEnabled) {
                    logService.info(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Generating debug overlay');
                    overlay = new OverlayGenerator(true);
                    timer = new TimerHelper();
                    timer.setSecondsChangeCallback(updateDebugData);
                    timer.start();
                    overlay.show();
                }

                dateOverload = new DateOverload();
                dateOverload.overload();

                // Sometimes we run into a race condition when the websocket server is not started, so we try again if there are no connected devices after 30 seconds
                $timeout(tryResetOnNoConectedDevices, serverResubscribeInterval);
                cleanUpThread = $timeout(cleanConnectedDevicesList, cleanUpInterval);
                subscribeServerMessages();
            };

            ret.stop = function() {
                stopped = true;
                logService.info(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Stopping');
                clearCleanUpThread();
                for (var i in connectedDevices) {
                    delete connectedDevices[i];
                }

                if (overlay) {
                    overlay.destroy();
                    overlay = undefined;
                }

                if (timer) {
                    timer.stop();
                    timer = undefined;
                }

                if (dateOverload) {
                    dateOverload.reset();
                    dateOverload = undefined;
                }

                logService.info(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Stopped');
            };

            ret.start = function() {
                stopped = false;
                logService.info(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Received Start Command');
                init();
            };

            ret.isRunning = function() {
                return !stopped;
            };

            ret.mode = mode;

            ret.debugEnabled = debugEnabled;

            ret.remoteEnabled = remoteEnabled;

            return ret;
        }

        return (MasterMode);
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');
    app.factory('OverlayGenerator', ["$window", "dataUtil", function($window, dataUtil) {
        function OverlayGenerator(isMaster) {
            var ret = {};
            var WORKER_MODE = 'Worker';
            var CONTROLLER_MODE = 'Controller';
            var SLAVE_MODE = 'slave';
            var MILLISECONDS_SUFFIX = ' MS';
            var divElement;
            var slaveLeftOverlayTemplate = '<div style="display:flex; flex-direction:row; font-size: 3vmin; margin-bottom:1vmin;">\n           <div class="websocket-mode-label">\n           </div>\n           &nbsp;|&nbsp; \n           <div class="websocket-clock-label">\n           </div>\n        </div>\n        <div>' + CONTROLLER_MODE + ' Ip</div>\n        <div class="websocket-masterip-label" style="font-size: 3vmin;"></div>\n     ';
            var slaveRightOverlayTemplate = '<div>Delta</div>\n        <div class="websocket-delta-label" style="font-size: 3vmin; margin-bottom:1vmin;"></div>\n        <div>Roundtrip</div>\n        <div class="websocket-delay-label" style="font-size: 3vmin;"></div>\n';
            var masterLeftOverlayTemplate = '<div style="display:flex; flex-direction:row; font-size: 3vmin">\n           <div class="websocket-mode-label">\n\n           </div>\n           &nbsp;|&nbsp; \n           <div class="websocket-clock-label">\n\n           </div>\n        </div>\n     ';
            var masterRightOverlayTemplate = '<div>Connected Devices</div>\n        <div class="websocket-connecteddevices-label" style="font-size: 3vmin;"></div>\n     ';

            var getBaseTemplate = function(overlayLeftContent, overlayRightContent) {
                return '\n  <div class="websocket-overlay" style=\'z-index:99999; display:flex; position: absolute; bottom:0; left:0; height: 18vh; width: 100vw; color: white; font-family: Arial,Helvetica,sans-serif; background-color: black; flex-direction: row; font-size:1vmin; justify-content: space-between;\'>\n     '
                    + '<div class="websocket-overlay-left" style="min-width: 15%; display: flex; flex-direction: column; justify-content: center; margin-left:3vmin;">\n        '
                    + '<div style="margin-bottom:1vmin">\n           Sync Info\n        </div>\n        '
                    + overlayLeftContent
                    + '</div>\n     ' // close for websocket-overlay-left
                    + '<div class="websocket-overlay-right" style="min-width: 15%; display: flex; flex-direction: column; text-align:right; justify-content: center; margin-right:3vmin;">\n        '
                    + overlayRightContent
                    + '</div>\n        ' // close for websocket-overlay-right
                    + '</div>\n     '; // close for websocket-overlay
            };

            var getSlaveTemplate = function() {
                return getBaseTemplate(slaveLeftOverlayTemplate, slaveRightOverlayTemplate);
            };

            var getMasterTemplate = function() {
                return getBaseTemplate(masterLeftOverlayTemplate, masterRightOverlayTemplate);
            };

            var generateBaseElement = function() {
                var template = isMaster ? getMasterTemplate() : getSlaveTemplate();
                divElement = $window.$(template);
                divElement.appendTo('body');
            };

            ret.show = function() {
                divElement.show();
            };

            ret.hide = function() {
                divElement.hide();
            };

            ret.destroy = function() {
                divElement.remove();
                divElement = undefined;
            };

            ret.setModeText = function(mode, additionalMessage) {
                var textDiv = divElement.find('.websocket-mode-label');
                var displayMode = mode && mode.toLowerCase() === SLAVE_MODE ? WORKER_MODE : CONTROLLER_MODE;
                textDiv.text(displayMode.toUpperCase() + (additionalMessage || ''));
            };

            ret.setClockText = function(value) {
                var textDiv = divElement.find('.websocket-clock-label');
                textDiv.text(value);
            };

            ret.setBackgroundColor = function(value) {
                divElement.css('background-color', value);
            };

            ret.setConnectedDevicesText = function(value) {
                if (!isMaster) {
                    return;
                }

                var textDiv = divElement.find('.websocket-connecteddevices-label');
                textDiv.text(value);
            };

            ret.setDeltaText = function(value) {
                setText('.websocket-delta-label', value);
            };

            ret.setMasterIpText = function(value) {
                if (isMaster) {
                    return;
                }

                var textDiv = divElement.find('.websocket-masterip-label');
                textDiv.text(value);
            };

            ret.setDelayText = function(value) {
                setText('.websocket-delay-label', value);
            };

            var setText = function(element, value) {
                if (isMaster) {
                    return;
                }

                var textDiv = divElement.find(element);
                if (!dataUtil.isNumber(value)) {
                    textDiv.text(value);
                    return;
                }

                textDiv.text(Math.round(Math.abs(value)) + MILLISECONDS_SUFFIX);
            };

            var init = function() {
                generateBaseElement();
            };

            init();

            return ret;
        }

        return (OverlayGenerator);
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');
    app.factory('PlayerPropertiesHelper', ["$window", function($window) {
        function PlayerPropertiesHelper() {
            /* eslint-disable dot-location */
            var ret = {};

            // get player properties
            ret.getPlayerProperties = function() {
                return $window.$asAppPublicService.getServices().scripting.retrieveAppProperties();
            };

            // get a single player property
            ret.getPlayerProperty = function(property) {
                if (!property || typeof property !== 'string') {
                    return undefined;
                }

                return $window.$asAppPublicService.getServices().scripting.retrieveAppProperty(property);
            };

            // gets multiple player properties and returns them in an key/value pair object
            ret.batchGetPlayerProperty = function(propertyList) {
                var retObj = {};

                if (!propertyList || !Array.isArray(propertyList)) {
                    return retObj;
                }

                var properties = ret.getPlayerProperties();

                for (var i = 0; i < propertyList.length; i++) {
                    retObj[propertyList[i]] = properties[propertyList[i].toLowerCase()];
                }

                return retObj;
            };

            return ret;
        }

        return (PlayerPropertiesHelper);
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');
    app.factory('SlaveFallbackMode', ["$timeout", "WebSocketService", "logService", "CLOCK_SYNC", "helperUtil", "dataUtil", "$window", "DateTimeUtil", "halPlatformService", "FEATURE_CODE", function($timeout, WebSocketService, logService, CLOCK_SYNC, helperUtil, dataUtil, $window, DateTimeUtil, halPlatformService, FEATURE_CODE) {
        function SlaveFallbackMode(
            masterIp,
            port,
            playerGuid,
            refreshInterval,
            maxAcceptedDelay,
            failedRetryInterval,
            webSocketConnectionTimeoutInterval,
            maxAcceptedDeltasRange,
            originalDateGenerator,
            LOG_PREFIX
        ) {
            var websockets;
            var getTimestampThread;
            var stopped = true;
            var lastClockValues = {};
            var delta = null;
            var dateTimeUtil = new DateTimeUtil();
            var isPerformanceApiSupported = halPlatformService.getFeatureSupport(FEATURE_CODE.RUNTIME_PERFORMANCE_API_SUPPORTED);

            originalDateGenerator = helperUtil.isFunction(originalDateGenerator) ? originalDateGenerator : function() {
                return isPerformanceApiSupported ? new Date(dateTimeUtil.getTimeOrigin() + $window.performance.now()) : new Date();
            };

            refreshInterval = refreshInterval || 20000;
            port = port || 55537;
            masterIp = masterIp || '';
            var noop = function() { };

            var timeUpdateCallback = noop;
            var errorStatusUpdateCallback = noop;

            var ret = {};

            ret.onDeltaReceived = function(callback) {
                if (!helperUtil.isFunction(callback)) {
                    return;
                }

                timeUpdateCallback = callback;
            };

            ret.onErrorStatusUpdate = function(callback) {
                if (!helperUtil.isFunction(callback)) {
                    return;
                }

                errorStatusUpdateCallback = callback;
            };

            var closeWebsocket = function() {
                if (!websockets || !websockets.isSocketObject || !websockets.isSocketOpen()) {
                    return;
                }

                websockets.closeSocketConnection();
            };

            var openWebsocket = function() {
                closeWebsocket();
                return websockets.openSocketConnection();
            };

            var isWithinRange = function(newVal, oldVal, range) {
                if (!dataUtil.isNumber(newVal)) {
                    return false;
                }

                if (!dataUtil.isNumber(oldVal)) {
                    return true;
                }

                return Math.abs(newVal - oldVal) <= (range || 10);
            };

            var calculateDeltaAndDelay = function(originalTimestamp, masterTimestamp, currentTimestamp) {
                var retValues = {
                    websocketDelay: 0,
                    websocketDelta: 0,
                    isAccepted: false
                };

                if (!originalTimestamp || !masterTimestamp || !currentTimestamp) {
                    return retValues;
                }

                retValues.websocketDelay = currentTimestamp - originalTimestamp;
                retValues.websocketDelta = masterTimestamp - originalTimestamp;
                // if round-trip is bigger then maximum accepted value then we reject it
                retValues.isAccepted = retValues.websocketDelay >= 0 && retValues.websocketDelay <= maxAcceptedDelay;

                return retValues;
            };

            var isSimilarToCurrentDelta = function(data) {
                if (!data) {
                    return false;
                }

                return delta && isWithinRange(data.websocketDelta, delta, maxAcceptedDeltasRange);
            };

            var updateDeltaAndDelayValues = function(data) {
                if (stopped || !data || !data.isAccepted || isSimilarToCurrentDelta(data)) {
                    logService.debug(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Fallback - Current Delta is similar to the previous one, ignoring');
                    return;
                }

                delta = data.websocketDelta;
                timeUpdateCallback(data);
            };

            var retrySocketOnError = function(error, callback) {
                logService.warn(CLOCK_SYNC.LOGGER, error.message || error);
                logService.info(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Fallback - Retrying to connect websocket now');
                if (!helperUtil.isFunction(callback)) {
                    return;
                }

                getTimestampThread = $timeout(callback, failedRetryInterval);
            };

            var clearGetTimestampThread = function() {
                helperUtil.clearTimeoutIfActive(getTimestampThread);
                getTimestampThread = undefined;
            };

            var requestTimestampFromMaster = function() {
                if (!masterIp || !websockets || stopped) {
                    return;
                }

                clearGetTimestampThread();
                openWebsocket().then(
                    function onConnected() {
                        var startTimestamp = originalDateGenerator().getTime();
                        websockets.getMasterTime().then(
                            function(masterTime) {
                                closeWebsocket();
                                errorStatusUpdateCallback(false);

                                var currentClockValues = calculateDeltaAndDelay(startTimestamp, masterTime, originalDateGenerator().getTime());

                                if (!currentClockValues.isAccepted) {
                                    logService.info(CLOCK_SYNC.LOGGER,
                                        LOG_PREFIX + 'Fallback - roundtrip took more than minimum accepted value, retrying soon.'
                                    );
                                    retrySocketOnError('roundtrip too large', requestTimestampFromMaster);
                                    return;
                                }

                                // Only update delta if it is different from the current delta
                                if (
                                    isWithinRange(
                                        currentClockValues.websocketDelta,
                                        lastClockValues.websocketDelta,
                                        maxAcceptedDeltasRange
                                    )
                                ) {
                                    logService.debug(CLOCK_SYNC.LOGGER,
                                        LOG_PREFIX + 'Fallback - Response from Controller with Delta: ' +
                                        currentClockValues.websocketDelta +
                                        ' and roundtrip:' +
                                        currentClockValues.websocketDelay
                                    );
                                    updateDeltaAndDelayValues(currentClockValues);
                                }

                                lastClockValues = Object.assign({}, currentClockValues);
                                getTimestampThread = $timeout(requestTimestampFromMaster, refreshInterval);
                            },
                            function(error) {
                                logService.warn(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Fallback - Received wrong response from Controller.');
                                closeWebsocket();
                                retrySocketOnError(error, requestTimestampFromMaster);
                                errorStatusUpdateCallback(true);
                            }
                        );
                    },
                    function onNotConnected(error) {
                        logService.warn(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Fallback - Web Socket could not connect.');
                        retrySocketOnError(error, requestTimestampFromMaster);
                        errorStatusUpdateCallback(true);
                    }
                );
            };

            ret.start = function() {
                if (!stopped) {
                    return;
                }

                logService.info(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Starting in Fallback Mode and Initializing Web Sockets');
                websockets = new WebSocketService(
                    masterIp,
                    playerGuid,
                    port,
                    false,
                    webSocketConnectionTimeoutInterval
                );

                logService.debug(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Fallback - WebSocket Initialized');

                logService.debug(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Fallback - Will start syncing clock after 10 seconds');
                // Delay start by 10 seconds at start
                $timeout(requestTimestampFromMaster, 10000);

                stopped = false;
            };

            ret.stop = function() {
                logService.info(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Stopping Fallback Mode');
                stopped = true;
                clearGetTimestampThread();
                closeWebsocket();
                websockets = undefined;
                delta = undefined;
            };

            return ret;
        }

        return (SlaveFallbackMode);
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');
    app.factory('SlaveMode', ["$window", "SlaveWorkerMode", "SlaveFallbackMode", "DateOverload", "OverlayGenerator", "TimerHelper", "logService", "CLOCK_SYNC", "helperUtil", "DateTimeUtil", "halPlatformService", "FEATURE_CODE", function($window, SlaveWorkerMode, SlaveFallbackMode, DateOverload,
        OverlayGenerator, TimerHelper, logService, CLOCK_SYNC, helperUtil, DateTimeUtil, halPlatformService, FEATURE_CODE) {
        function SlaveMode(
            masterIp,
            port,
            playerGuid,
            refreshInterval,
            debugEnabled,
            colors,
            maxAcceptedDelay,
            failedRetryInterval,
            webSocketConnectionTimeoutInterval,
            maxAcceptedDeltasRange
        ) {
            var overlay;
            var timer;
            var mode = 'SLAVE';
            var currentAgent;
            var dateOverload;
            var stopped = true;
            var hasError = false;
            var offlineErrorMessage = 'offline';

            var workersSupported = false;
            var workerSupportFeature = FEATURE_CODE.WEB_WORKER_SUPPORTED;
            var dateTimeUtil = new DateTimeUtil();
            var isPerformanceApiSupported = halPlatformService.getFeatureSupport(FEATURE_CODE.RUNTIME_PERFORMANCE_API_SUPPORTED);

            refreshInterval = refreshInterval || 20000;
            port = port || 55537;
            masterIp = masterIp || '';

            var ret = {};
            var LOG_PREFIX = 'Worker Mode - ';
            var isNullOrNotANumber = function(value) {
                return (!value || isNaN(value)) && value !== 0;
            };

            var generateNativeDateInstance = function() {
                if (isPerformanceApiSupported) {
                    return new Date(dateTimeUtil.getTimeOrigin() + $window.performance.now());
                }

                if ($window.originalDate) {
                    // eslint-disable-next-line new-cap
                    return new $window.originalDate();
                }

                return new Date();
            };

            var updateWindowDeltaAndDelayValues = function(data) {
                if (stopped) {
                    return;
                }

                if (!data || !data.isAccepted) {
                    return;
                }

                logService.info(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Assigning received Delta and roundtrip to Main thread');

                $window.activeWebSocketsTimeSyncTask.websocketdelay = data.websocketDelay;
                $window.activeWebSocketsTimeSyncTask.delta = data.websocketDelta;
                $window.activeWebSocketsTimeSyncTask.lastUpdate = generateNativeDateInstance().getTime();
            };

            var updateErrorStatus = function(status) {
                if (stopped) {
                    return;
                }

                hasError = status;
            };

            var getLastUpdateTimeString = function() {
                if (!$window.activeWebSocketsTimeSyncTask.lastUpdate) {
                    return;
                }

                return moment($window.activeWebSocketsTimeSyncTask.lastUpdate).from(generateNativeDateInstance());
            };

            var windowDataNotAvailable = function() {
                return (
                    !$window.activeWebSocketsTimeSyncTask ||
                    isNullOrNotANumber($window.activeWebSocketsTimeSyncTask.delta) ||
                    isNullOrNotANumber($window.activeWebSocketsTimeSyncTask.websocketdelay)
                );
            };

            var changeDebugData = function(currentTime) {
                if (stopped) {
                    return;
                }

                overlay.setClockText(moment(currentTime).format('hh:mm:ss A'));
                overlay.setModeText(mode);

                var masterIpText = masterIp;

                if ($window.activeWebSocketsTimeSyncTask.lastUpdate) {
                    masterIpText += ' (Updated: ' + getLastUpdateTimeString() + ')';
                }

                if (hasError) {
                    masterIpText += ' (' + offlineErrorMessage + ')';
                }

                overlay.setMasterIpText(masterIpText);

                overlay.setBackgroundColor(colors[currentTime.getSeconds() % colors.length]);

                if (windowDataNotAvailable()) {
                    overlay.setDeltaText('-');
                    overlay.setDelayText('-');
                    return;
                }

                overlay.setDeltaText($window.activeWebSocketsTimeSyncTask.delta);
                overlay.setDelayText($window.activeWebSocketsTimeSyncTask.websocketdelay);
            };

            var startWorkerMode = function() {
                if (!workersSupported) {
                    logService.debug(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Trying to start in worker mode but workers not supported, terminating');
                    return;
                }

                if (currentAgent) {
                    currentAgent.stop();
                    currentAgent = undefined;
                }

                currentAgent = new SlaveWorkerMode(
                    masterIp,
                    port,
                    playerGuid,
                    refreshInterval,
                    maxAcceptedDelay,
                    failedRetryInterval,
                    webSocketConnectionTimeoutInterval,
                    maxAcceptedDeltasRange,
                    LOG_PREFIX
                );

                currentAgent.onDeltaReceived(updateWindowDeltaAndDelayValues);
                currentAgent.onErrorStatusUpdate(updateErrorStatus);
                currentAgent.start();
            };

            var startFallbackMode = function() {
                if (workersSupported) {
                    logService.debug(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Trying to start in fallback mode but workers are supported, terminating');
                    return;
                }

                if (currentAgent) {
                    currentAgent.stop();
                    currentAgent = undefined;
                }

                currentAgent = new SlaveFallbackMode(
                    masterIp,
                    port,
                    playerGuid,
                    refreshInterval,
                    maxAcceptedDelay,
                    failedRetryInterval,
                    webSocketConnectionTimeoutInterval,
                    maxAcceptedDeltasRange,
                    generateNativeDateInstance,
                    LOG_PREFIX
                );

                currentAgent.onDeltaReceived(updateWindowDeltaAndDelayValues);
                currentAgent.onErrorStatusUpdate(updateErrorStatus);
                currentAgent.start();
            };

            var determinePlatformSupportsWorker = function() {
                if (!$window.$asCoreFrameworkService) {
                    // eslint-disable-next-line no-implicit-coercion
                    return !!$window.worker;
                }

                var platformSpecificGetFeatureSupport = $window.$asCoreFrameworkService
                    .getServices()
                    .hal.halDriverService.findImplementation('platform', 'getFeatureSupport');

                var genericGetFeatureSupport = $window.$asCoreFrameworkService.getServices().hal.halPlatformService
                    .getFeatureSupport;

                var platformDriverFeatureSupported = helperUtil.isFunction(platformSpecificGetFeatureSupport)
                    ? platformSpecificGetFeatureSupport(workerSupportFeature)
                    : null;

                return platformDriverFeatureSupported == undefined
                    ? genericGetFeatureSupport(workerSupportFeature)
                    : platformDriverFeatureSupported;
            };

            var init = function() {
                if (!$window.activeWebSocketsTimeSyncTask) {
                    $window.activeWebSocketsTimeSyncTask = {};
                }

                if (currentAgent) {
                    currentAgent.stop();
                    currentAgent = undefined;
                }

                workersSupported = determinePlatformSupportsWorker();

                logService.debug(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Worker Support Status = ' + String(workersSupported));

                logService.info(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Initializing');

                $window.activeWebSocketsTimeSyncTask.delta = null;
                $window.activeWebSocketsTimeSyncTask.websocketdelay = null;

                dateOverload = new DateOverload();
                dateOverload.overload();

                if (debugEnabled) {
                    logService.debug(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Generating debug overlay');

                    overlay = new OverlayGenerator(false);

                    timer = new TimerHelper();
                    timer.setSecondsChangeCallback(changeDebugData);
                    timer.start();
                    overlay.show();
                }

                logService.debug(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Determining Mode');

                if (workersSupported) {
                    startWorkerMode();
                } else {
                    startFallbackMode();
                }

                logService.info(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Started');
            };

            ret.stop = function() {
                stopped = true;
                logService.info(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Stopping');

                if (overlay) {
                    overlay.destroy();
                    overlay = undefined;
                }

                if (timer) {
                    timer.stop();
                    timer = undefined;
                }

                if (dateOverload) {
                    dateOverload.reset();
                    delete $window.activeWebSocketsTimeSyncTask.delta;
                    delete $window.activeWebSocketsTimeSyncTask.websocketdelay;
                    dateOverload = undefined;
                }

                if (currentAgent) {
                    currentAgent.stop();
                    currentAgent = undefined;
                }

                logService.info(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Stopped');
            };

            ret.start = function() {
                stopped = false;
                logService.info(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Received start command');
                init();
            };

            ret.isRunning = function() {
                return !stopped;
            };

            ret.mode = mode;
            ret.masterIp = masterIp;
            ret.port = port;
            ret.debugEnabled = debugEnabled;
            ret.refreshInterval = refreshInterval;
            ret.maxAcceptedDelay = maxAcceptedDelay;
            ret.failedRetryInterval = failedRetryInterval;
            ret.webSocketConnectionTimeoutInterval = webSocketConnectionTimeoutInterval;
            ret.maxAcceptedDeltasRange = maxAcceptedDeltasRange;
            return ret;
        }

        return (SlaveMode);
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');
    app.factory('SlaveWorkerMode', ["$window", "$timeout", "WorkerHelpers", "logService", "CLOCK_SYNC", "helperUtil", "halPlatformService", "FEATURE_CODE", function($window, $timeout, WorkerHelpers, logService, CLOCK_SYNC, helperUtil, halPlatformService, FEATURE_CODE) {
        function SlaveWorkerMode(
            masterIp,
            port,
            playerGuid,
            refreshInterval,
            maxAcceptedDelay,
            failedRetryInterval,
            webSocketConnectionTimeoutInterval,
            maxAcceptedDeltasRange,
            LOG_PREFIX
        ) {
            var workerTimestampMonitoringThread;
            var helpers;
            var worker;
            var workerBlob;
            var respawnWorkerThread;
            var workerPingThread;
            var workerPingInterval = 5000;
            var workerPingTimeout = 10000;
            var workerRespawnOnNoDeltaUpdateInterval = 40000;
            var workerRunning = false;
            var ensureWorkerTerminatedThread;
            var ensureTerminationTimeout = 5000;
            var stopped = true;
            var isPerformanceApiSupported = halPlatformService.getFeatureSupport(FEATURE_CODE.RUNTIME_PERFORMANCE_API_SUPPORTED);
            // eslint-disable-next-line no-empty-function
            var noop = function() { };

            var timeUpdateCallback = noop;
            var errorStatusUpdateCallback = noop;

            var ret = {};

            ret.onDeltaReceived = function(callback) {
                if (!helperUtil.isFunction(callback)) {
                    return;
                }

                timeUpdateCallback = callback;
            };

            ret.onErrorStatusUpdate = function(callback) {
                if (!helperUtil.isFunction(callback)) {
                    return;
                }

                errorStatusUpdateCallback = callback;
            };

            var clearWorkerUrl = function() {
                if (!workerBlob || workerBlob instanceof Blob == false) {
                    return;
                }

                logService.debug(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Clearing Worker Url.');

                URL.revokeObjectURL(workerBlob);
                workerBlob = undefined;
            };

            var clearEnsureWorkerTerminatedThread = function() {
                helperUtil.clearTimeoutIfActive(ensureWorkerTerminatedThread);
                ensureWorkerTerminatedThread = undefined;
            };

            var clearWorkerTimestampMonitoringThread = function() {
                helperUtil.clearTimeoutIfActive(workerTimestampMonitoringThread);
                workerTimestampMonitoringThread = undefined;
            };

            var clearWorkerPingThread = function() {
                helperUtil.clearTimeoutIfActive(workerPingThread);
                workerPingThread = undefined;
            };

            var clearRespawnWorkerThread = function() {
                helperUtil.clearTimeoutIfActive(respawnWorkerThread);
                respawnWorkerThread = undefined;
            };

            var terminateWorker = function() {
                if (!worker) {
                    return;
                }

                clearEnsureWorkerTerminatedThread();
                logService.debug(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Stopping Worker.');
                worker.terminate();
                worker = undefined;

                clearWorkerUrl();

                workerRunning = false;
            };

            var spawnWorker = function() {
                if (!$window.Worker) {
                    return;
                }

                terminateWorker();

                workerBlob = new Blob(['(' + $window.clockSyncWorker.toString() + '())'], {
                    type: 'text/javascript'
                });

                logService.info(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Initialized Worker Code.');

                worker = new Worker(URL.createObjectURL(workerBlob));

                logService.debug(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Worker Spawned.');

                worker.onmessage = helpers.onMessage;
                helpers.emit(
                    helpers.events.START,
                    {
                        masterIp: masterIp,
                        port: port,
                        playerGuid: playerGuid,
                        refreshInterval: refreshInterval,
                        maxAcceptedDelay: maxAcceptedDelay,
                        failedRetryInterval: failedRetryInterval,
                        webSocketConnectionTimeoutInterval: webSocketConnectionTimeoutInterval,
                        maxAcceptedDeltasRange: maxAcceptedDeltasRange,
                        isPerformanceApiSupported: isPerformanceApiSupported
                    },
                    worker
                );

                logService.debug(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Sent start command to worker');
                workerRunning = true;

                helpers.ping(worker);
            };

            var startWorkerWebSocketMonitoring = function() {
                clearWorkerTimestampMonitoringThread();
                workerTimestampMonitoringThread = $timeout(spawnWorker, workerRespawnOnNoDeltaUpdateInterval);
            };

            var pingWorker = function() {
                if (!helpers || !worker || stopped) {
                    return;
                }

                if (!workerRunning) {
                    clearWorkerPingThread();
                    clearRespawnWorkerThread();
                    return;
                }

                helpers.ping(worker);

                clearRespawnWorkerThread();
                respawnWorkerThread = $timeout(spawnWorker, workerPingTimeout);
            };

            var workerLog = function(payload) {
                if (!payload || !payload.message) {
                    return;
                }

                var type = payload.type || helpers.logTypes.INFO;

                logService[type](CLOCK_SYNC.LOGGER, payload.message);
            };

            var refreshPing = function() {
                clearWorkerPingThread();
                workerPingThread = $timeout(pingWorker, workerPingInterval);
            };

            ret.start = function() {
                if (!stopped) {
                    return;
                }

                logService.info(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Starting in Worker Mode');

                clearEnsureWorkerTerminatedThread();
                helpers = new WorkerHelpers();
                helpers.on(helpers.events.DELTA_UPDATED, function(data) {
                    if (stopped) {
                        return;
                    }

                    startWorkerWebSocketMonitoring();
                    timeUpdateCallback(data);
                });

                helpers.on(helpers.events.MASTER_OFFLINE, function() {
                    startWorkerWebSocketMonitoring();
                    errorStatusUpdateCallback(true);
                });

                helpers.on(helpers.events.MASTER_ONLINE, function() {
                    startWorkerWebSocketMonitoring();
                    errorStatusUpdateCallback(false);
                });

                helpers.on(helpers.events.WORKER_STOPPED, terminateWorker);

                helpers.on(helpers.events.PONG, clearRespawnWorkerThread);

                helpers.on(helpers.events.LOG, workerLog);

                helpers.on(helpers.events.PING_SENT, refreshPing);

                spawnWorker();

                startWorkerWebSocketMonitoring();

                stopped = false;
            };

            ret.stop = function() {
                logService.info(CLOCK_SYNC.LOGGER, LOG_PREFIX + 'Stopping Worker Mode');

                stopped = true;

                clearWorkerTimestampMonitoringThread();
                clearRespawnWorkerThread();
                clearWorkerPingThread();

                if (worker) {
                    helpers.emit(helpers.events.STOP, undefined, worker);
                    clearEnsureWorkerTerminatedThread();
                    ensureWorkerTerminatedThread = $timeout(terminateWorker, ensureTerminationTimeout);
                }
            };

            return ret;
        }

        return (SlaveWorkerMode);
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');
    app.factory('TimerHelper', ["$window", "$timeout", "helperUtil", function($window, $timeout, helperUtil) {
        function TimerHelper() {
            var ret = {};
            var callback;
            var started = false;
            var timeout;
            var clear;
            var thread;

            ret.setSecondsChangeCallback = function(customCallback) {
                if (!helperUtil.isFunction(customCallback)) {
                    return;
                }

                callback = customCallback;
            };

            var monitorSecondsChange = function(previousTime) {
                if (!started || !previousTime) {
                    return;
                }

                var now = new Date();

                if (now.getSeconds() != previousTime.getSeconds()) {
                    callback(now);
                }

                if (thread) {
                    clear(thread);
                }

                thread = timeout(function() {
                    monitorSecondsChange(now);
                });
            };

            ret.start = function() {
                if (!helperUtil.isFunction(callback)) {
                    return;
                }

                started = true;

                monitorSecondsChange(new Date());
            };

            ret.stop = function() {
                started = false;
            };

            var init = function() {
                timeout = $window.requestAnimationFrame || $window.webkitRequestAnimationFrame || $timeout;
                clear = $window.cancelAnimationFrame || $window.webkitCancelAnimationFrame || $timeout.cancel;
            };

            init();

            return ret;
        }

        return (TimerHelper);
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');
    app.factory('WebSocketService', ["$q", "$timeout", "logService", "CLOCK_SYNC", "helperUtil", function($q, $timeout, logService, CLOCK_SYNC, helperUtil) {
        function WebSocketService(url, slaveId, port, wss, timeoutInterval) {
            if (!url) {
                throw new Error('url is required');
            }

            var ret = {};
            var opened = false;
            var defaultMessage = 'ping';
            var ws;
            var connectingThread;
            var getTimestampTimeoutThread;
            var pingTimeoutThread;

            var constructWebSocketUrl = function() {
                return (wss ? 'wss' : 'ws') + '://' + url + (port ? ':' + port : '');
            };

            ret.isSocketOpen = function() {
                return opened && ws && ws instanceof WebSocket && ws.readyState == ws.OPEN;
            };

            var onDisconnect = function() {
                opened = false;
                ws = undefined;
            };

            var clearGetTimestampTimeoutThread = function() {
                helperUtil.clearTimeoutIfActive(getTimestampTimeoutThread);
                getTimestampTimeoutThread = undefined;
            };

            var clearPingTimeoutThread = function() {
                helperUtil.clearTimeoutIfActive(pingTimeoutThread);
                pingTimeoutThread = undefined;
            };

            var clearConnectingThread = function() {
                helperUtil.clearTimeoutIfActive(connectingThread);
                connectingThread = undefined;
            };

            ret.closeSocketConnection = function() {
                clearGetTimestampTimeoutThread();
                clearPingTimeoutThread();
                clearConnectingThread();

                if (ret.isSocketOpen()) {
                    ws.close();
                }

                onDisconnect();
            };

            ret.openSocketConnection = function() {
                var promise = $q.defer();
                if (ret.isSocketOpen()) {
                    ret.closeSocketConnection();
                }

                ws = new WebSocket(constructWebSocketUrl());

                connectingThread = $timeout(function() {
                    promise.reject('could not connect to websocket server');
                }, timeoutInterval);

                ws.onerror = promise.reject;
                ws.onclose = onDisconnect;
                ws.onopen = function() {
                    clearConnectingThread();
                    opened = true;
                    promise.resolve('connected');
                };
                return promise.promise;
            };

            ret.getMasterTime = function() {
                if (!ret.isSocketOpen()) {
                    return $q.reject('socket not connected');
                }
                var promise = $q.defer();
                clearGetTimestampTimeoutThread();
                getTimestampTimeoutThread = $timeout(function() {
                    ret.closeSocketConnection();
                    logService.warn(CLOCK_SYNC.LOGGER, 'WebSocket - Web Socket connection timeout.');
                    promise.reject('request timedout');
                }, timeoutInterval);

                ws.onmessage = function(messageEvent) {
                    clearGetTimestampTimeoutThread();
                    if (!messageEvent || !messageEvent.data) {
                        logService.warn(CLOCK_SYNC.LOGGER, 'WebSocket - Web Socket response is empty.');
                        promise.reject('No Data in response');
                    }

                    var response = messageEvent.data.split('_');
                    // Support for legacy app responses
                    var altResponse = messageEvent.data.split(' ');

                    if (response && response.length > 1 && !isNaN(response[1])) {
                        promise.resolve(parseInt(response[1]));
                    } else if (altResponse && altResponse.length > 1 && !isNaN(altResponse[1])) {
                        promise.resolve(parseInt(altResponse[1]));
                    } else {
                        promise.reject(messageEvent);
                    }
                };

                ws.send(slaveId || defaultMessage);
                return promise.promise;
            };

            ret.isSocketObject = true;

            var init = function() {
                timeoutInterval = timeoutInterval || 500;
            };

            init();

            return ret;
        }
        return (WebSocketService);
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');
    app.factory('WorkerHelpers', ["helperUtil", function(helperUtil) {
        function WorkerHelpers() {
            // Helpers for web worker and window communication
            var noop = function() { };
            var ret = {};

            var workerEventType = 'worker-event';

            ret.events = {
                START: 'start',
                STOP: 'stop',
                UPDATE: 'update',
                MASTER_OFFLINE: 'master_offline',
                DELTA_UPDATED: 'delta_updated',
                MASTER_ONLINE: 'master_online',
                WORKER_STOPPED: 'worker_stopped',
                PING: 'ping',
                PONG: 'pong',
                LOG: 'log',
                SIMILAR_DELTAS: 'similar_deltas',
                PING_SENT: 'pingsent'
            };

            ret.logTypes = {
                LOG: 'log',
                INFO: 'info',
                DEBUG: 'debug',
                WARN: 'warn',
                ERROR: 'error'
            };

            ret.callbacks = {};

            var getGlobal = function() {
                return self || window || {postMessage: noop};
            };

            var tryCleanObject = function(obj) {
                if (obj === undefined || helperUtil.isFunction(obj)) {
                    return;
                }

                try {
                    return JSON.parse(
                        JSON.stringify(obj, function(key, value) {
                            // NaN is the only value that does not equal itself
                            if (value !== value) {
                                return 'NaN';
                            }

                            if (value === Infinity) {
                                return 'Infinity';
                            }

                            if (value === -Infinity) {
                                return '-Infinity';
                            }

                            return value;
                        })
                    );
                } catch (e) {
                    return obj;
                }
            };

            ret.emit = function(event, values, target) {
                if (!event) {
                    return;
                }

                (target || getGlobal()).postMessage({
                    type: tryCleanObject(workerEventType),
                    payload: {
                        event: tryCleanObject(event),
                        values: tryCleanObject(values)
                    }
                });
            };

            ret.on = function(event, callback) {
                if (!event || !helperUtil.isFunction(callback)) {
                    return;
                }

                ret.callbacks[event] = callback;
            };

            ret.onMessage = function(ev) {
                if (
                    !ev ||
                    !ev.data ||
                    ev.data.type !== workerEventType ||
                    !ev.data.payload ||
                    !ev.data.payload.event ||
                    !ret.callbacks[ev.data.payload.event]
                ) {
                    return;
                }

                ret.callbacks[ev.data.payload.event](ev.data.payload.values);
            };

            ret.callbacks[ret.events.START] = noop;
            ret.callbacks[ret.events.STOP] = noop;
            ret.callbacks[ret.events.UPDATE] = noop;
            ret.callbacks[ret.events.PING_SENT] = noop;
            ret.callbacks[ret.events.PING] = function() {
                ret.emit(ret.events.PONG);
            };

            ret.ping = function(target) {
                ret.emit(ret.events.PING, undefined, target || getGlobal());
                ret.callbacks[ret.events.PING_SENT]();
            };

            var sendLog = function(type, message) {
                ret.emit(ret.events.LOG, {
                    type: type || ret.logTypes.INFO,
                    message: message
                });
            };

            ret.logger = {
                log: function(message) {
                    sendLog(ret.logTypes.INFO, message);
                },
                debug: function(message) {
                    sendLog(ret.logTypes.DEBUG, message);
                },
                info: function(message) {
                    sendLog(ret.logTypes.INFO, message);
                },
                warn: function(message) {
                    sendLog(ret.logTypes.WARN, message);
                },
                error: function(message) {
                    sendLog(ret.logTypes.ERROR, message);
                }
            };

            return ret;
        }

        return (WorkerHelpers);
    }]);
})(window.angular);

(function($angular) {
   var angular = $angular;
   'use strict';

   var app = angular.module('signsHTML5');
   app.service('clockSyncService', ["$window", "$timeout", "helperUtil", "webSocketsTimeSyncTaskService", function($window, $timeout, helperUtil, webSocketsTimeSyncTaskService) {
      var replaceTaskInterval = 5000;

      function attachToWindow() {
         webSocketsTimeSyncTaskService.initialize();
      }

      function start() {
         // This makes sure that only one instance of the clock sync service is running
         if ($window.activeWebSocketsTimeSyncTask) {
            if (helperUtil.isFunction($window.activeWebSocketsTimeSyncTask.destroy)) {
               $window.activeWebSocketsTimeSyncTask.destroy();
            }
            $timeout(function() {
               delete $window.activeWebSocketsTimeSyncTask;
               attachToWindow();
            }, replaceTaskInterval);
         } else {
            attachToWindow();
         }
      }

      return {
         start: start
      };
   }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');
    app.service('webSocketsTimeSyncTaskService', ["$q", "$window", "$timeout", "logService", "MasterMode", "SlaveMode", "PlayerPropertiesHelper", "CLOCK_SYNC", "dataUtil", "helperUtil", function($q, $window, $timeout, logService,
        MasterMode, SlaveMode, PlayerPropertiesHelper, CLOCK_SYNC, dataUtil, helperUtil) {
        var playerPropertiesHelper;
        var taskVersion = '1.0';
        var masterIp = '';
        var refreshInterval;
        var maxAcceptedDelay;
        var maxAcceptedDeltasRange;
        var failedRetryInterval;
        var webSocketConnectionTimeoutInterval;
        var frameworkDetectionRetryInterval = 100;
        var debugEnabled = false;
        var masterMode = 'MASTER';
        var slaveMode = 'SLAVE';
        var CONTROLLER_MODE = 'controller';
        var WORKER_MODE = 'worker';
        var offMode = 'OFF';
        var remoteEnabled = false;
        var port = 55537;
        var generatedGuid = null;
        var playerGuid;
        var agent;
        var updateThread;
        var clockSyncMode = offMode;
        var propertiesRefreshInterval = 30000;
        var uid;
        var colors = [
            'rgba(74, 20, 140, 0.65)',
            'rgba(13, 71, 161, 0.65)',
            'rgba(0, 77, 64, 0.65)',
            'rgba(33, 33, 33, 0.65)'
        ];
        var defaults = {
            refreshInterval: 20000,
            maxAcceptedDelay: 20,
            failedRetryInterval: 5000,
            masterIp: '',
            webSocketConnectionTimeoutInterval: 500,
            maxAcceptedDeltasRange: 10
        };

        function getCurrentMode() {
            if (masterIp && clockSyncMode == slaveMode.toLowerCase()) {
                return slaveMode;
            } else if (clockSyncMode == masterMode.toLowerCase()) {
                return masterMode;
            }

            return offMode;
        }

        function generateUid(length) {
            if (generatedGuid) {
                return generatedGuid;
            }

            var retStr = '';
            var arr = new Uint32Array(length || 3);

            if ($window.crypto && $window.crypto.getRandomValues) {
                $window.crypto.getRandomValues(arr);
            } else {
                for (var j = 0; j < arr.length; j++) {
                    arr[j] = parseInt(2147483647 * Math.random());
                }
            }

            for (var i = 0; i < arr.length; i++) {
                retStr += arr[i].toString(16);
            }

            generatedGuid = retStr;

            return retStr;
        }

        function resetProperties() {
            masterIp = defaults.masterIp;
            refreshInterval = defaults.refreshInterval;
            debugEnabled = false;
            playerGuid = uid;
            clockSyncMode = offMode;
            maxAcceptedDelay = defaults.maxAcceptedDelay;
            failedRetryInterval = defaults.failedRetryInterval;
            webSocketConnectionTimeoutInterval = defaults.webSocketConnectionTimeoutInterval;
            remoteEnabled = false;
            maxAcceptedDeltasRange = defaults.maxAcceptedDeltasRange;
        }

        function assignParameterValues() {
            var isRegistered = !!$window.$asCoreFrameworkService.getServices().core.coreConfigurationService.get('device', 'playerguid');

            if (!isRegistered) {
               resetProperties();
               return;
            }

            var masterIpPropertyKey = 'player.clocksync.masterip';
            var controllerIpPropertyKey = 'player.clocksync.controller.ip';
            var enableDebugPropertyKey = 'player.clocksync.debug';
            var clockSyncModePropertyKey = 'player.clocksync.mode';
            var maximumAcceptedDelayPropertyKey = 'player.clocksync.connection.maxaccepteddelay';
            var maximumAcceptedDeltasRangePropertyKey = 'player.clocksync.connection.maxaccepteddeltarange';
            var refreshIntervalPropertyKey = 'player.clocksync.connection.refreshinterval';
            var refreshFailedClockPropertyKey = 'player.clocksync.connection.failedretryinterval';
            var webSocketConnectionTimeoutIntervalPropertyKey = 'player.clocksync.connection.timeout';
            var playerGuidPropertyKey = 'player.guid';
            var remoteEnablePropertyKey = 'remote.enable';

            var properties = playerPropertiesHelper.batchGetPlayerProperty([
                masterIpPropertyKey,
                controllerIpPropertyKey,
                refreshIntervalPropertyKey,
                enableDebugPropertyKey,
                playerGuidPropertyKey,
                clockSyncModePropertyKey,
                maximumAcceptedDelayPropertyKey,
                refreshFailedClockPropertyKey,
                webSocketConnectionTimeoutIntervalPropertyKey,
                remoteEnablePropertyKey,
                maximumAcceptedDeltasRangePropertyKey
            ]);

            masterIp = properties[controllerIpPropertyKey] || properties[masterIpPropertyKey] || defaults.masterIp;

            refreshInterval = dataUtil.isNumber(properties[refreshIntervalPropertyKey]) ?
                parseInt(properties[refreshIntervalPropertyKey])
                : defaults.refreshInterval;

            debugEnabled =
                properties[enableDebugPropertyKey] && properties[enableDebugPropertyKey].toLowerCase() == 'true';

            playerGuid = properties[playerGuidPropertyKey] ? properties[playerGuidPropertyKey] : uid;

            clockSyncMode = getClockSyncModeFromProperties(properties[clockSyncModePropertyKey]);

            maxAcceptedDelay = dataUtil.isNumber(properties[maximumAcceptedDelayPropertyKey])
                    ? parseInt(properties[maximumAcceptedDelayPropertyKey])
                    : defaults.maxAcceptedDelay;

            failedRetryInterval = dataUtil.isNumber(properties[refreshFailedClockPropertyKey])
                    ? parseInt(properties[refreshFailedClockPropertyKey])
                    : defaults.failedRetryInterval;

            webSocketConnectionTimeoutInterval = dataUtil.isNumber(properties[webSocketConnectionTimeoutIntervalPropertyKey])
                    ? parseInt(properties[webSocketConnectionTimeoutIntervalPropertyKey])
                    : defaults.webSocketConnectionTimeoutInterval;

            remoteEnabled =
                properties[remoteEnablePropertyKey] && properties[remoteEnablePropertyKey].toLowerCase() == 'true';

            maxAcceptedDeltasRange = dataUtil.isNumber(properties[maximumAcceptedDeltasRangePropertyKey])
                    ? parseInt(properties[maximumAcceptedDeltasRangePropertyKey])
                    : defaults.maxAcceptedDeltasRange;
        }

        function getClockSyncModeFromProperties(clockSyncModePropertyValue) {
            if (!dataUtil.isString(clockSyncModePropertyValue)) {
                return offMode;
            }

            var valueToCheck = clockSyncModePropertyValue.toLowerCase();
            // process new properties value for clocksync mode
            if (valueToCheck === CONTROLLER_MODE) {
                return masterMode.toLowerCase();
            }

            if (valueToCheck === WORKER_MODE) {
                return slaveMode.toLowerCase();
            }

            return valueToCheck;
        }

        function clearUpdateThread() {
            helperUtil.clearTimeoutIfActive(updateThread);
            updateThread = undefined;
        }

        function stop() {
            clearUpdateThread();
            if (agent) {
                agent.stop();
            }

            agent = undefined;

            $window.activeWebSocketsTimeSyncTask.isStopped = true;
        }

        function needsSlaveRefresh(currentAgent) {
            return (
                !currentAgent ||
                slaveMode != currentAgent.mode ||
                debugEnabled != currentAgent.debugEnabled ||
                masterIp != currentAgent.masterIp ||
                refreshInterval != currentAgent.refreshInterval ||
                maxAcceptedDelay != currentAgent.maxAcceptedDelay ||
                failedRetryInterval != currentAgent.failedRetryInterval ||
                webSocketConnectionTimeoutInterval != currentAgent.webSocketConnectionTimeoutInterval ||
                maxAcceptedDeltasRange != currentAgent.maxAcceptedDeltasRange
            );
        }

        function needsMasterRefresh(currentAgent) {
            return (
                !currentAgent ||
                masterMode != currentAgent.mode ||
                debugEnabled != currentAgent.debugEnabled ||
                remoteEnabled != currentAgent.remoteEnabled
            );
        }

        function start() {
            if ($window.activeWebSocketsTimeSyncTask.isStopped == true) {
                return;
            }

            assignParameterValues();

            var currentMode = getCurrentMode();

            if (currentMode == masterMode && needsMasterRefresh(agent)) {
                if (agent) {
                    agent.stop();
                }

                agent = new MasterMode(debugEnabled, colors, remoteEnabled);
                agent.start();
            } else if (currentMode == slaveMode && needsSlaveRefresh(agent)) {
                if (agent) {
                    agent.stop();
                }

                agent = new SlaveMode(
                    masterIp,
                    port,
                    playerGuid,
                    refreshInterval,
                    debugEnabled,
                    colors,
                    maxAcceptedDelay,
                    failedRetryInterval,
                    webSocketConnectionTimeoutInterval,
                    maxAcceptedDeltasRange
                );
                agent.start();
            } else if (currentMode == offMode && agent) {
                agent.stop();
                agent = undefined;
            }

            clearUpdateThread();
            updateThread = $timeout(start, propertiesRefreshInterval);
        }

        function destroy() {
            stop();
            $window.activeWebSocketsTimeSyncTask = undefined;
            delete $window.activeWebSocketsTimeSyncTask;
        }

        function install() {
            $window.activeWebSocketsTimeSyncTask = {
                start: function() {
                    $window.activeWebSocketsTimeSyncTask.isStopped = false;
                    start();
                },
                stop: function() {
                    stop();
                },
                destroy: function() {
                    destroy();
                },
                currentVersion: taskVersion
            };

            $window.activeWebSocketsTimeSyncTask.start();
        }

        function init() {
            playerPropertiesHelper = new PlayerPropertiesHelper();

            // fallback device guid if guid is not injected as a player property
            uid = generateUid();
            install();
            logService.debug(CLOCK_SYNC.LOGGER, 'ClockSync - Service Started');
        }

        function waitForCoreFramework() {
            var promise = $q.defer();
            var checkframeworkLoaded = function() {
                var retry = function() {
                    $timeout(checkframeworkLoaded, frameworkDetectionRetryInterval);
                };

                if (
                    !$window.$asCoreFrameworkService ||
                    !$window.$asAppPublicService ||
                    Object.keys($window.$asAppPublicService.getServices().scripting.retrieveAppProperties())
                        .length == 0
                ) {
                    retry();
                    return;
                }

                $window.$asCoreFrameworkService.isReady().then(promise.resolve, retry);
            };

            checkframeworkLoaded();
            return promise.promise;
        }

        function initialize() {
            waitForCoreFramework().then(init);
        }

        return {
            initialize: initialize
        };
    }]);
})(window.angular);

(function ($angular) {
	'use strict';
	var angular = $angular;
	var app = angular.module('signsHTML5');

	app.factory('CampaignPlaylist', ["$q", "$filter", "$timeout", function ($q, $filter, $timeout) {
		function CampaignPlaylist() {
			this.tempDatasource;
			this.tempMainLoop;
			this.tempFillerLoop;
			this.tempTagLoops;
			this.tempGroupLoops;
			this.tempMarker;
			this.tempTimeblocks;

			this.activeCampaign;
			this.activeMarker;
			this.activeTimeblocks;

			this.onAssetChanges;
			this.timeoutHandler;

			this.previousAsset;

			this.isplaying;
		}

		//public
		CampaignPlaylist.prototype.generatePlaylist = function (campaign) {
			var promise = $q.defer();

			this.tempDatasource = campaign;
			this.tempMainLoop = null;
			this.tempFillerLoop = null;
			this.tempTagLoops = [];
			this.tempGroupLoops = [];
			this.tempMarker = [];
			this.tempTimeblocks = [];

			if (!this.tempDatasource) {
				this.tempDatasource = { campaign: null };
			} else {
				if (this.tempDatasource.campaign.startDate.replace) {
					this.tempDatasource.campaign.startDate = moment(this.tempDatasource.campaign.startDate.replace('Z', '')).toDate();
				}
				if (this.tempDatasource.campaign.endDate.replace) {
					this.tempDatasource.campaign.endDate = moment(this.tempDatasource.campaign.endDate.replace('Z', '')).toDate();
				}
				this.tempDatasource.campaign.playoutMode = this.tempDatasource.campaign.playoutMode.toLowerCase();
				this.identifyLoops();
				this.tempTimeblocks = this.constructTimeBlocks();
				this.tempMarker = this.constructContentPlayback(this.tempTimeblocks);
			}

			this.commit();
			this.resume();
			promise.resolve();

			return promise.promise;
		}

		//public
		CampaignPlaylist.prototype.destroy = function (config) {
			this.stopTimer();
			this.generatePlaylist(null);
		}

		CampaignPlaylist.prototype.startTimer = function () {
			this.stopTimer();
			var owner = this;

			if (this.isplaying == false) {
				return;
			}

			this.timeoutHandler = $timeout(function () {
				var d = new Date();
				var asset = owner.retrieveContentItemAtTime(d);
				if (owner.previousAsset != asset) {
					owner.previousAsset = asset;
					if (owner.onAssetChanges) {
						owner.onAssetChanges(asset);
					}
				}
				owner.startTimer();
			}, 1000);
		}

		CampaignPlaylist.prototype.stopTimer = function () {
			if (this.timeoutHandler != -1) {
				$timeout.cancel(this.timeoutHandler);
				this.timeoutHandler = -1;
			}
		}

		//public
		CampaignPlaylist.prototype.pause = function () {
			this.isplaying = false;
			this.stopTimer();
		}

		//public
		CampaignPlaylist.prototype.resume = function () {
			this.isplaying = true;
			this.startTimer();
		}

		//public
		CampaignPlaylist.prototype.retrieveContentItemAtTime = function (currentTime) {
			var asset;
			for (var i = 0; i < this.activeTimeblocks.length - 1; i++) {
				var startTimeblock = this.activeTimeblocks[i];
				var endTimeblock = this.activeTimeblocks[i + 1];
				//current time is in valid time blocks
				if (currentTime.getTime() >= startTimeblock.date.getTime() && currentTime.getTime() < endTimeblock.date.getTime()) {
					var timeblockMarker = $filter('filter')(this.activeMarker, function (value, index) {
						return value.start.getTime() == startTimeblock.date.getTime() && value.end.getTime() == endTimeblock.date.getTime();
					});

					//if there is result for timeblock marker, get the 1st tiem
					if (timeblockMarker) {
						timeblockMarker = timeblockMarker[0]
					}
					//no timeblock. something is wrong
					else {
						continue;
					}


					if (this.activeCampaign.playoutMode == 'strict') {
						asset = this.retrieveContentItemAtTimeStrict(currentTime, startTimeblock.date, timeblockMarker.marker);
					} else if (this.activeCampaign.playoutMode == 'loose') {
						asset = this.retrieveContentItemAtTimeLoose(currentTime, startTimeblock.date, timeblockMarker.marker);
					} else if (this.activeCampaign.playoutMode == 'dynamic') {
						asset = this.retrieveContentItemAtTimeDynamic(currentTime, startTimeblock.date, timeblockMarker.marker);
					}
					break;
				}
			}
			//console.log(asset);
			return asset;
		}

		CampaignPlaylist.prototype.retrieveContentItemAtTimeStrict = function (currentTime, startTime, timeblockMarker) {
			//get seconds since start of time block
			var elapsedSinceStart = currentTime.getTime() - startTime.getTime();
			elapsedSinceStart = Math.floor(elapsedSinceStart / 1000);

			//get # loops since start of time block
			var numberOfLoopSinceStart = Math.floor(elapsedSinceStart / timeblockMarker.length);

			//get current seconds inside loop
			var currentSecondInLoop = elapsedSinceStart - (numberOfLoopSinceStart * timeblockMarker.length);

			//get main asset for current time
			var mainAssetPlayback = timeblockMarker[currentSecondInLoop];
			var asset = null;
			if (mainAssetPlayback) {
				//get current second in main asset
				var currentSecondInMainAsset = currentSecondInLoop - mainAssetPlayback.startSecondsInLoop;

				//get total loops of the main asset playback
				var mainAssetTotalLoop = mainAssetPlayback.marker.length / mainAssetPlayback.mainAsset.duration;

				//get current loop index of main asset
				var currentLoopIndex = numberOfLoopSinceStart % mainAssetTotalLoop;

				//get current second in main asset marker
				var markerCurrentSecond = (currentLoopIndex * mainAssetPlayback.mainAsset.duration) + currentSecondInMainAsset;

				//get content
				asset = mainAssetPlayback.marker[markerCurrentSecond];
				if (asset) {
					asset.mainAsset = mainAssetPlayback.mainAsset;
				}
			}
			//console.log('overall:' + currentSecondInLoop + '\nmainasset-'+mainAssetPlayback.mainAsset.id+':' + markerCurrentSecond + '\nmedia: ' + asset.contentFilePath + ' ' + asset.duration);
			return asset;
		}

		CampaignPlaylist.prototype.retrieveContentItemAtTimeLoose = function (currentTime, startTime, timeblockMarker) {
			//get seconds since start of time block
			var elapsedSinceStart = currentTime.getTime() - startTime.getTime();
			elapsedSinceStart = Math.floor(elapsedSinceStart / 1000);

			//get # loops since start of time block
			var numberOfLoopSinceStart = Math.floor(elapsedSinceStart / timeblockMarker.length);

			//get current seconds inside loop
			var currentSecondInLoop = elapsedSinceStart - (numberOfLoopSinceStart * timeblockMarker.length);

			var markerObject = timeblockMarker[currentSecondInLoop];
			var asset = null;
			if (markerObject) {
				asset = markerObject.asset;
				if (asset) {
					asset.mainAsset = markerObject.mainAsset;
				}
			}
			return asset;
		}

		CampaignPlaylist.prototype.retrieveContentItemAtTimeDynamic = function (currentTime, startTime, timeblockMarker) {
			//get seconds since start of time block
			var elapsedSinceStart = currentTime.getTime() - startTime.getTime();
			elapsedSinceStart = Math.floor(elapsedSinceStart / 1000);

			//get # loops since start of time block
			var numberOfLoopSinceStart = Math.floor(elapsedSinceStart / timeblockMarker.length);

			//get current seconds inside loop
			var currentSecondInLoop = elapsedSinceStart - (numberOfLoopSinceStart * timeblockMarker.length);

			var markerObject = timeblockMarker[currentSecondInLoop];
			var asset = null;
			if (markerObject) {
				asset = markerObject.asset;
				if (asset) {
					asset.mainAsset = markerObject.mainAsset;
				}
			}
			return asset;

		}

		CampaignPlaylist.prototype.commit = function () {
			this.activeCampaign = $angular.copy(this.tempDatasource.campaign);
			this.activeMarker = $angular.copy(this.tempMarker);
			this.activeTimeblocks = $angular.copy(this.tempTimeblocks);
		}

		CampaignPlaylist.prototype.identifyLoops = function () {
			for (var i = 0; i < this.tempDatasource.bookings.length; i++) {
				var booking = this.tempDatasource.bookings[i];
				for (var j = 0; j < booking.bookingLoopIds.length; j++) {
					var bookingLoopId = booking.bookingLoopIds[j];
					var bookingLoop = $filter('filter')(this.tempDatasource.bookingLoops, { 'id': bookingLoopId })[0];
					if (!bookingLoop) {
						continue;
					}
					bookingLoop.site = undefined;
					if (bookingLoop.siteId != undefined) {
						bookingLoop.site = $filter('filter')(this.tempDatasource.sites, { 'id': bookingLoop.siteId })[0];
					}
					bookingLoop.filterType = 'none';
					if (bookingLoop.site && bookingLoop.site.filters && bookingLoop.site.filters.length > 0) {
						bookingLoop.filterType = bookingLoop.site.filters[0]['d5p1:Type'].toLowerCase().replace('filter', '');
					}

					//resolve assets
					bookingLoop.assets = this.resolveBookingLoopAssets(bookingLoop, this.tempDatasource.assets);
					delete bookingLoop.assetIds;
					//main loop
					if (!bookingLoop.siteId && !bookingLoop.isDefaultContentLoop) {
						this.tempMainLoop = bookingLoop;
					}
					//filler
					else if (!bookingLoop.siteId && bookingLoop.isDefaultContentLoop) {
						this.tempFillerLoop = bookingLoop;
					}
					//tag rule
					else if (bookingLoop.filterType == 'rule') {
						this.tempTagLoops.push(bookingLoop);

					}
					//player group
					else if (bookingLoop.filterType == 'playergroup') {
						this.tempGroupLoops.push(bookingLoop);
					}

				}
			}
		}

		CampaignPlaylist.prototype.resolveBookingLoopAssets = function (bookingLoop, assets) {
			var res = [];

			if (!bookingLoop || !bookingLoop.assetIds)
				return res;

			for (var k = 0; k < bookingLoop.assetIds.length; k++) {
				var assetId = bookingLoop.assetIds[k];
				var asset = $filter('filter')(assets, { 'id': assetId })[0];
				if (asset && asset.campaignId == this.tempDatasource.campaign.id) {
					asset.type = asset.type.toLowerCase();
					asset.schedule = $filter('filter')(this.tempDatasource.schedules, { 'id': asset.scheduleId })[0];
					if (asset.schedule) {
						asset.schedule.startDate = moment(asset.schedule.start.replace('Z', '')).toDate();
						asset.schedule.endDate = moment(asset.schedule.end.replace('Z', '')).toDate();
					}
					delete asset.scheduleId;
					asset.duration = asset.duration;
					delete asset.durationType;
					asset.campaign = this.tempDatasource.campaign;
					delete asset.campaignId;
					asset.bookingLoop = bookingLoop;
					delete asset.bookingLoopId;

					if (asset.contentType) {
						asset.contentType = asset.contentType.toLowerCase();
					}
					res.push(asset);
				}
			}
			res = $filter('orderBy')(res, 'order');
			return res;
		}

		CampaignPlaylist.prototype.constructTimeBlocks = function () {
			var assets = [];
			var campaign = this.tempDatasource.campaign;
			//console.log(this.tempMainLoop);
			//retrieve all assets for main loop
			for (var i = 0; i < this.tempMainLoop.assets.length; i++) {
				var asset = this.tempMainLoop.assets[i];
				//if not linked smart asset, retrieve assets
				if (!asset.isLinkedSmartObject) {
					var mainAssets = this.retrieveAssets(asset);
					asset.assets = mainAssets;
					assets = assets.concat(mainAssets);
				}

			}

			assets = assets.concat(this.tempFillerLoop.assets);
			//create time blocks for overall assets
			var timeblocksObject = {};
			for (var i = 0; i < assets.length; i++) {
				var asset = assets[i];
				//if the asset start before the campaign started but end within the campaign,
				//change the start value to campaign start
				if (asset.schedule.startDate.getTime() < campaign.startDate.getTime()) {
					asset.schedule.startDate = new Date(campaign.startDate.getTime());
				}
				//if the asset ends after the campaign ended but start within the campaign,
				//change the end value to end of campaign
				if (asset.schedule.endDate.getTime() > campaign.endDate.getTime()) {
					asset.schedule.endDate = new Date(campaign.endDate.getTime());
				}
				timeblocksObject[asset.schedule.startDate.getTime()] = asset.schedule.startDate;
				timeblocksObject[asset.schedule.endDate.getTime()] = asset.schedule.endDate;
			}
			//convert timeblocks object to array
			var timeblocks = [];
			for (var key in timeblocksObject) {
				timeblocks.push({ id: parseInt(key, 10), date: timeblocksObject[key] })
			}
			timeblocks = $filter('orderBy')(timeblocks, 'id');

			//if time blocks empty,create time blocks based on campaign's
			if (timeblocks.length == 0) {
				timeblocks.push({ id: campaign.startDate.getTime(), date: campaign.startDate });
				timeblocks.push({ id: campaign.endDate.getTime(), date: campaign.endDate });
			}

			return timeblocks;
		}

		CampaignPlaylist.prototype.retrieveAssets = function (mainAsset) {
			if (mainAsset.metadata.smartassettype.toLowerCase() == "rule") {
				return this.retrieveAssetsFromTag(mainAsset);
			} else if (mainAsset.metadata.smartassettype.toLowerCase() == "playergroup") {
				return this.retrieveAssetsFromGroup(mainAsset);
			}
		}

		CampaignPlaylist.prototype.retrieveAssetsFromTag = function (mainAsset) {
			var res = [];
			//this is a content
			if (mainAsset.type == 'assetcontent') {
				res.push(mainAsset);
			} else {
				//filter item
				var targetCategory = mainAsset.metadata.category;
				var targetSite = mainAsset.metadata.sitetype;

				for (var i = 0; i < this.tempTagLoops.length; i++) {
					var groupLoop = this.tempTagLoops[i];

					if (targetSite != 0 && groupLoop.site.typeId != targetSite) {
						continue;
					}

					if (targetCategory != 0) {
						var arr = $filter('filter')(groupLoop.assets, { 'entityTypeId': targetCategory });
						res = res.concat(arr);
					} else {
						res = res.concat(groupLoop.assets);
					}
				}
			}

			return res;
		}

		CampaignPlaylist.prototype.retrieveAssetsFromGroup = function (mainAsset) {
			var res = [];
			//this is a content
			if (mainAsset.type == 'assetcontent') {
				res.push(mainAsset);
			} else {
				//filter item
				var targetCategory = mainAsset.metadata.category;
				var targetLevel = (mainAsset.metadata.grouplevel.toLowerCase() == 'all') ? 0 : parseInt(mainAsset.metadata.grouplevel.toLowerCase().replace('level ', ''), 10);
				var targetSite = mainAsset.metadata.sitetype;

				for (var i = 0; i < this.tempGroupLoops.length; i++) {
					var groupLoop = this.tempGroupLoops[i];

					if (targetSite != 0 && groupLoop.site.typeId != targetSite) {
						continue;
					}

					if (targetLevel != 0 && groupLoop.site.level != targetLevel) {
						continue;
					}

					if (targetCategory != 0) {
						var arr = $filter('filter')(groupLoop.assets, { 'entityTypeId': targetCategory });
						res = res.concat(arr);
					} else {
						res = res.concat(groupLoop.assets);
					}
				}
			}
			return res;
		}

		CampaignPlaylist.prototype.retrieveValidAssets = function (assets, start, end) {
			var res = $filter('filter')(assets, function (value, index, array) {
				//which content that I can play between start & end
				var valid = (start.getTime() >= value.schedule.startDate.getTime() && end.getTime() <= value.schedule.endDate.getTime());
				return valid;
			})
			return res;
		}

		CampaignPlaylist.prototype.constructContentPlayback = function (timeblocks) {
			var campaign = this.tempDatasource.campaign;
			if (campaign.playoutMode == 'strict') {
				return this.constructContentPlaybackStrict(timeblocks);
			} else if (campaign.playoutMode == 'loose') {
				return this.constructContentPlaybackLoose(timeblocks);
			} else if (campaign.playoutMode == 'dynamic') {
				return this.constructContentPlaybackDynamic(timeblocks);
			}
		}

		CampaignPlaylist.prototype.constructContentPlaybackStrict = function (timeblocks) {
			var timeblockMarker = [];
			for (var i = 0; i < timeblocks.length - 1; i++) {
				var start = timeblocks[i].date;
				var end = timeblocks[i + 1].date;
				var currentDuration = 0;
				//retrieve valid filler assets
				var fillerAssets = this.retrieveValidAssets(this.tempFillerLoop.assets, start, end);
				var mainMarker = [];
				//go through main loop assets
				for (var j = 0; j < this.tempMainLoop.assets.length; j++) {
					var mainAsset = this.tempMainLoop.assets[j];
					var assets = this.retrieveValidAssets(mainAsset.assets, start, end);
					var mainAssetMarker = this.constructStrictMarker(mainAsset, assets, fillerAssets);
					var mainAssetPlayback = {
						mainAsset: mainAsset,
						startSecondsInLoop: currentDuration,
						marker: mainAssetMarker
					};
					currentDuration += mainAsset.duration;

					for (var k = 0; k < mainAsset.duration; k++) {
						mainMarker.push(mainAssetPlayback);
					}

				}
				timeblockMarker.push({
					start: start,
					end: end,
					marker: mainMarker
				});
			}
			return timeblockMarker;
		}

		CampaignPlaylist.prototype.constructContentPlaybackLoose = function (timeblocks) {
			var timeblockMarker = [];
			for (var i = 0; i < timeblocks.length - 1; i++) {
				var start = timeblocks[i].date;
				var end = timeblocks[i + 1].date;
				var maxloop = 0;
				var validAssets = [];
				var fillerAssets = this.retrieveValidAssets(this.tempFillerLoop.assets, start, end);
				//find max loop needed to play all content for inside all main asset
				for (var j = 0; j < this.tempMainLoop.assets.length; j++) {
					var mainAsset = this.tempMainLoop.assets[j];
					var assets = this.retrieveValidAssets(mainAsset.assets, start, end);
					validAssets.push(assets);
					if (assets.length > maxloop) {
						maxloop = assets.length;
					}
				}

				//create playback
				var currentDuration = 0;
				var marker = [];

				for (var j = 0; j < maxloop; j++) {
					for (var k = 0; k < this.tempMainLoop.assets.length; k++) {
						var mainAsset = this.tempMainLoop.assets[k];
						var assets = validAssets[k];
						var asset;
						//if main asset doesn't have any assets, use filler if any
						if (assets.length == 0 && fillerAssets.length > 0) {
							asset = $angular.copy(fillerAssets[0]);
						} else {
							var assetIndex = j % assets.length;
							asset = assets[assetIndex];
						}
						for (var m = currentDuration; m < (currentDuration + asset.duration); m++) {
							marker.push({ mainAsset: mainAsset, asset: asset });
						}
						currentDuration += asset.duration;
					}
				}
				timeblockMarker.push({
					start: start,
					end: end,
					marker: marker
				})
			}
			return timeblockMarker;
		}

		CampaignPlaylist.prototype.constructContentPlaybackDynamic = function (timeblocks) {
			var timeblockMarker = [];
			for (var i = 0; i < timeblocks.length - 1; i++) {
				var start = timeblocks[i].date;
				var end = timeblocks[i + 1].date;

				var mainAssetMaxContainer = {};
				var validAssets = [];
				//retrieve assets & find the number of occurences for each main asset (for linked asset)
				for (var j = 0; j < this.tempMainLoop.assets.length; j++) {
					var mainAsset = this.tempMainLoop.assets[j];

					if (!mainAssetMaxContainer[mainAsset.id]) { mainAssetMaxContainer[mainAsset.id] = 1; }

					if (mainAsset.metadata.isLinkedSmartObject) {
						mainAssetMaxContainer[mainAsset.metadata.linkedParentSmartAsset]++;
						continue;
					}

					var assets = this.retrieveValidAssets(mainAsset.assets, start, end);
					if (assets.length > 1) {
						assets = $filter('orderBy')(assets, 'duration', true);
					}
					validAssets[j] = assets;
				}
				var mainAssetPlayback = [];
				var mainAssetPlaybackDuration = [];

				//construct playback
				for (var j = 0; j < this.tempMainLoop.assets.length; j++) {
					var mainAsset = this.tempMainLoop.assets[j];

					//if linked asset, get playback from the main asset
					if (mainAsset.metadata.isLinkedSmartObject) {
						continue;
					}
					var id = mainAsset.id;
					var maxContainerCount = mainAssetMaxContainer[id];
					var assets = $angular.copy(validAssets[j]);
					var assetsLength = assets.length;
					mainAssetPlayback[id] = [];
					mainAssetPlaybackDuration[id] = [];

					while (assetsLength > 0) {
						var k = 0;
						var assetsUsedIndex = [];
						//fit assets across linked assets for 1 round
						for (var m = 0; m < maxContainerCount; m++) {
							if (!mainAssetPlaybackDuration[id][m]) {
								mainAssetPlaybackDuration[id][m] = 0;
							}
							if (!mainAssetPlayback[id][m]) {
								mainAssetPlayback[id][m] = [];
							}

							//find asset that can fit our duration
							for (; k < assetsLength; k++) {
								var asset = assets[k];
								//check if asset can fit
								if (asset.duration + mainAssetPlaybackDuration[id][m] <= mainAsset.duration) {
									assetsUsedIndex.push(k);
									k++;
									mainAssetPlayback[id][m].push(asset);
									mainAssetPlaybackDuration[id][m] += asset.duration;
									break;
								}
							}
						}

						//done for 1 round
						//remove used assets
						assets = $filter('filter')(assets, function (element, index) {
							var found = false;
							for (var m = 0; m < assetsUsedIndex.length; m++) {
								var checkIndex = assetsUsedIndex[m];
								if (checkIndex == index) {
									found = true;
									break;
								}
							}
							return !found;
						});

						//if we're not using any assets on previous run, meaning we can't fit it anymore
						var stillvalid = (assetsUsedIndex.length > 0);
						if (stillvalid == false) {
							break;
						}

						//reverse the order (business logic)
						assets.reverse();

						//update assets length
						assetsLength = assets.length;
					}
				}

				var marker = [];
				var duration = 0;
				for (var j = 0; j < this.tempMainLoop.assets.length; j++) {
					var mainAsset = this.tempMainLoop.assets[j];
					var id = mainAsset.id;
					if (mainAsset.metadata.isLinkedSmartObject) {
						id = mainAsset.metadata.linkedParentSmartAsset;
					}
					//grab the first main asset container playback
					var playback = mainAssetPlayback[id][0];
					for (var k = 0; k < playback.length; k++) {
						var asset = playback[k];
						var m = duration;
						for (; m < (asset.duration + duration); m++) {
							marker[m] = { mainAsset: mainAsset, asset: asset };
						}
						duration = m;
					}

					//remove the first main asset container playback
					mainAssetPlayback[id].splice(0, 1);
				}
				timeblockMarker.push({
					start: start,
					end: end,
					marker: marker
				})
			}
			return timeblockMarker;
		}

		CampaignPlaylist.prototype.constructStrictMarker = function (mainAsset, assets, fillerAssets) {
			var loops = [];
			//if no assets, use filler

			if (assets.length == 0 && fillerAssets.length > 0) {
				var fillerAsset = $angular.copy(fillerAssets[0]);
				fillerAsset.duration = mainAsset.duration;
				loops.push([fillerAsset]);
			}
			var loop = [];
			var currentLoopDuration = 0;
			var currentFillerIndex = 0;
			var totalDuration = 0;
			for (var i = 0; i < assets.length; i++) {
				var asset = assets[i];
				//if we can fit to the current loop, push
				if (currentLoopDuration + asset.duration <= mainAsset.duration) {
					currentLoopDuration += asset.duration;
					totalDuration += asset.duration;
					loop.push($angular.copy(asset));
				}
				//if this is the first item in the loop and we can't fit it in, push the asset
				//using the main asset duration
				else if (loop.length == 0 && currentLoopDuration + asset.duration > mainAsset.duration) {
					var newAsset = $angular.copy(asset);
					newAsset.duration = mainAsset.duration;

					currentLoopDuration += newAsset.duration;
					totalDuration += newAsset.duration;
					loop.push(newAsset);
				}

				//check whether we can fit the next asset
				var nextAsset = assets[i + 1];
				//if we can, don't commit yet. process the next item
				if (nextAsset && (nextAsset.duration + currentLoopDuration <= mainAsset.duration)) {
					continue;
				}
				//if we can't (or there's no more next item), commit this loop
				else {
					//check if there's time left for the current loop
					//if there is, fill it with filler
					var durationLeft = mainAsset.duration - currentLoopDuration;

					while (durationLeft > 0) {
						if (fillerAssets.length > 0) {
							var fillerIndex = currentFillerIndex++ % fillerAssets.length;
							var filler = $angular.copy(fillerAssets[fillerIndex]);
							//if the filler's duration is more than what's left, shorten it
							if (filler.duration > durationLeft) {
								filler.duration = durationLeft;
							}
							durationLeft -= filler.duration;
							totalDuration += filler.duration;
							loop.push(filler);
						}
						else {
							loop.push({
								contentFilePath: null,
								duration: durationLeft
							})
							durationLeft = 0;
						}
					}
					//start commiting
					//reset current loop duration
					currentLoopDuration = 0;
					loops.push(loop);
					loop = [];
				}
			}

			//construct seconds-based marker
			var marker = [];
			for (var i = 0; i < loops.length; i++) {
				var loop = loops[i];
				for (var j = 0; j < loop.length; j++) {
					var asset = loop[j];
					for (var k = 0; k < asset.duration; k++) {
						marker.push(asset);
					}
				}
			}
			return marker;
		}

		return (CampaignPlaylist);
	}])

})(window.angular);
(function ($angular) {

    "use strict";

	var app = $angular.module('signsHTML5');

	app.service("campaignService",
		["$q", "$filter", "$timeout", "$interval", "networkContentRouterService", "HttpWrapper", "CampaignXMLParser", "CampaignPlaylist", function ($q, $filter, $timeout,$interval, networkContentRouterService, HttpWrapper, CampaignXMLParser, CampaignPlaylist) {

			var campaigns;
			var subscribers = {};
			var pops;
			var timeouthandler;
			var zonesLookup;
			var isSendingPop;
			init();
			//public interface
			return {
				clear: clear,//clear all the saved campaigns
				add: add, //add campaign
				retrieveCampaignByZoneId: retrieveCampaignByZoneId,
				subscribe: subscribe,
				unsubscribe: unsubscribe,
				commitPOPRecord: commitPOPRecord,
				retrieveZoneIdFromName : retrieveZoneIdFromName,
				pause : pause,
				resume : resume
			}

			function init() {
				campaigns = [];
				isSendingPop = false;
				timeouthandler = $interval(function(){
					//we're currently sending pop, wait for the next cycle
					if (isSendingPop == true){
						return;
					}
					isSendingPop = true;
					postPOPRecords().then(function(){
						isSendingPop = false;
					})
				},30000,0,false);
			}

			function clear() {
				campaigns = [];
			}

			function commitPOPRecord(pop) {
				if (!pops) { pops = [] }
				pops.push(pop);
			}

			function postPOPRecords() {
				var promise = $q.defer();
				var arr = $angular.copy(pops);
				pops = [];
				if (!arr || arr.length == 0){
					promise.resolve();
				}else{
					var postdata = '';
					for (var i = 0; i < arr.length; i++) {
						if (postdata != '') { postdata += ',' }
						var a = arr[i];
						var astring = '';
						for (var key in a) {
							if (astring != '') { astring += ";"; }
							astring += key + ':' + a[key];
						}
						postdata += astring;
					}
					var url = '{{appspace-url}}/api/Handlers/CampaignPOP.ashx?proofofplays=' + postdata;
					var httpWrapper = new HttpWrapper();
					httpWrapper.apiPost(url, null, null, 30000).then(function (res) {
						promise.resolve();
					}, function (err) {
						promise.resolve();
					})
				}
				return promise.promise;
			}

			function pause(config){
				if (!config){
					return;
				}
				var sub = subscribers[config.mediaZoneId];
				if (sub && sub.playlist){
					sub.playlist.pause();
				}
			}

			function resume(config){
				if (!config){
					return;
				}
				var sub = subscribers[config.mediaZoneId];
				if (sub && sub.playlist){
					sub.playlist.resume();
				}
			}

			function subscribe(config) {
				if (!config){
					return;
				}
				if (!subscribers) { subscribers = {}; }
				var subscriber = subscribers[config.mediaZoneId];

				//if already subscribed
				if (subscriber && subscriber.zone == config.campaignZoneId && subscriber.callback == config.callback) {
					return;
				}

				subscribers[config.mediaZoneId] = { zone: config.campaignZoneId, callback: config.callback, playlist: null };
				//check if subscriber's zone is already exists
				var campaign = retrieveCampaignByZoneId(config.campaignZoneId);
				if (campaign) {
					subscribers[config.mediaZoneId].playlist = createPlaylistForSubscriber(campaign,config.callback);
				}
			}

			function createPlaylistForSubscriber(campaign,callback){
				//create playlist for this subscriber
				var playlist = new CampaignPlaylist();
				playlist.onAssetChanges = callback;
				playlist.generatePlaylist(campaign);
				return playlist;
			}

			function unsubscribe(config) {
				if (!config){
					return;
				}
				if (!subscribers) {
					return;
				}
				if (subscribers[config.mediaZoneId]) {
					if (subscribers[config.mediaZoneId].playlist){
						subscribers[config.mediaZoneId].playlist.onAssetChanges = null;
						subscribers[config.mediaZoneId].playlist.destroy();
						subscribers[config.mediaZoneId].playlist = null;
					}
					subscribers[config.mediaZoneId] = null;
					delete subscribers[config.mediaZoneId];
				}
			}

			function add(campaign) {
				var promise = $q.defer();
				var c = {
					id: campaign.campaigns[0].id,
					filename: campaign.campaigns[0].filename,
					datasourceid: campaign.campaigns[0].path,
					identities: campaign.identities
				}
				loadCampaignXml(c).then(function (res) {
					if (res.campaignObject) {
						var campaign = res.campaignObject;
						for (var key in subscribers) {
							var sub = subscribers[key];
							if (isZoneExists(sub.zone, campaign)) {
								//sub.callback(campaign);
								var playlist = sub.playlist;
								if (!playlist){
									playlist = createPlaylistForSubscriber(campaign,sub.callback);
									sub.playlist = playlist;
								}else{
									playlist.generatePlaylist(campaign);
								}
							}
						}
					}
					promise.resolve();
				}, function (err) {
					promise.reject(err);
				});
				return promise.promise;
			}

			function retrieveZoneIdFromName(zonename){
				var id = null;

				if (zonesLookup && zonesLookup[zonename] != undefined && zonesLookup[zonename] != null){
					id = zonesLookup[zonename];
					return id;
				}

				for (var i = 0 ; i < campaigns.length; i++){
					var c = campaigns[i];

					if (!c.campaignObject){continue;}

					for (var j = 0 ; j < c.campaignObject.zones.length; j++){
						var zone = c.campaignObject.zones[j];
						if (zone.zoneId.toString().toLowerCase() == zonename.toString().toLowerCase()){
							id = zone.id;
							break;
						}
					}
				}
				if (id != null){
					if (!zonesLookup){zonesLookup={};}
					zonesLookup[zonename] = id;
				}
				return id;
			}

			function loadCampaignXml(c) {
				var promise = $q.defer();
				if (!c){
					promise.reject('campaign is null');
				}
				var filename = c.filename;
				filename = filename.replace('.xml','_xml.xml');
				var url = 'c:\\fastnetv2\\datasource\\' + c.datasourceid + '\\' + filename;
				var onXMLParsed = function (res) {
					c.campaignObject = res.campaign;
					//filter out sites
					var newSites = [];
					for (var i = 0 ; i < c.identities.length; i++){
						var checkid = c.identities[i].siteid;
						var site = $filter('filter')(c.campaignObject.sites,function(elem,index){if (elem.id == checkid){return true;}})[0];
						if (!site){
							continue;
						}
						newSites.push(site);
					}
					c.campaignObject.sites = newSites;
					campaigns.push(c);
					if (promise) { promise.resolve(c); }
				}

				var onXMLParseFail = function () {
					if (promise) { promise.reject('fail to parse xml'); }
				}

				var onXmlLoaded = function (res) {
					if (c.id == 91) {
						console.log(res.data.length);
					}
					var parser = new CampaignXMLParser();
					parser.parse(res.data).then(onXMLParsed, onXMLParseFail);
				}

				var onXmlLoadFail = function () {
					if (promise) { promise.reject('fail to load xml'); }
				}

				var onUrlResolved = function (newurl) {
					if (c.id == 91) {
						console.log(newurl);
					}
					var httpWrapper = new HttpWrapper();
					httpWrapper.handlerGet(newurl).then(onXmlLoaded, onXmlLoadFail);
				}

				var onUrlFailResolve = function () {
					if (promise) { promise.reject('fail to resolve xml\'s path'); }
				}

				networkContentRouterService.resolve(url, 'signs-content-routing-service')
					.then(onUrlResolved, onUrlFailResolve);

				return promise.promise;
			}

			function retrieveCampaignByZoneId(zoneid) {
				var res;
				for (var i = 0; i < campaigns.length; i++) {
					var campaign = campaigns[i].campaignObject;
					if (campaign) {
						if (isZoneExists(zoneid, campaign)) {
							res = campaign;
							break;
						}
					}
				}
				return res;
			}

			function isZoneExists(zoneid, campaign) {
				var res = false;
				if (!campaign){
					return res;
				}
				for (var j = 0; j < campaign.zones.length; j++) {
					var zone = campaign.zones[j];
					if (!zone || !zone.zoneId){
						continue;
					}
					if (zone.zoneId.toString().toLowerCase() == zoneid.toLowerCase()) {
						res = true;
						break;
					}
				}
				return res;
			}
		}]);

})(window.angular);
(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    app.factory('signsParserService', ["$q", "FileReader", "AppScriptParser", "AZParser", "SchedulingParser", "InterfaceDescriptionParser", "ApplicationXMLParser", "ApplicationAssetParser", "TextMsgParser", "ApplicationPropertiesParser", "RSSParser", "PMScheduleParser", "RSSFileParser", "ContentDistributionParser", "CalendarParser", "WeatherInfoParser", "WorkbookParser", "WorkbookDatatableParser", "InfoMenuParser", "VODAppParser", "CampaignParser", "DeviceLicensingParser", "LayoutTargetsParser", function($q, FileReader, AppScriptParser, AZParser, SchedulingParser,
        InterfaceDescriptionParser, ApplicationXMLParser, ApplicationAssetParser,
        TextMsgParser, ApplicationPropertiesParser, RSSParser, PMScheduleParser,
        RSSFileParser, ContentDistributionParser, CalendarParser, WeatherInfoParser,
        WorkbookParser, WorkbookDatatableParser, InfoMenuParser,
        VODAppParser, CampaignParser, DeviceLicensingParser, LayoutTargetsParser) {
        var parsers = {};
        var log = [];

        function init() {
            installParser(new AppScriptParser());
            installParser(new AZParser());
            installParser(new SchedulingParser());
            installParser(new InterfaceDescriptionParser());
            installParser(new ApplicationXMLParser());
            installParser(new ApplicationAssetParser());
            installParser(new TextMsgParser());
            installParser(new ApplicationPropertiesParser());
            installParser(new RSSParser());
            installParser(new RSSFileParser());
            installParser(new PMScheduleParser());
            installParser(new ContentDistributionParser());
            installParser(new CalendarParser());
            installParser(new WeatherInfoParser());
            installParser(new WorkbookParser());
            installParser(new WorkbookDatatableParser());
            installParser(new InfoMenuParser());
            installParser(new CampaignParser());
            installParser(new VODAppParser());
            installParser(new DeviceLicensingParser());
            installParser(new LayoutTargetsParser());
        }

        // Initialise the service
        init();

        // Return the log
        function getLogs() {
            return log;
        }

        // Parses the script returned by the url
        function parseUrl(type, url) {
            // promise object for asyn response
            var deferred = $q.defer();
            // Read a script file
            var fileReader = new FileReader();
            fileReader.read(url)
                .then(function(script) {
                    parse(type, script).then(deferred.resolve, deferred.reject);
                }, function(err) {
                    deferred.reject(err);
                });
            return deferred.promise;
        }

        // Parses the obect based on type
        function parse(type, object, opt) {
            var deferred = $q.defer();
            var startTime = new Date().getTime();
            // Find a parser for this type
            var p = findParser(type);

            // If no parser installed, throw error
            if (p === undefined) {
                // Create a log entry
                var le = {
                    type: type,
                    time: startTime,
                    elapsed: new Date().getTime() - startTime,
                    result: 0, // fail
                    error: 'No parser installed'
                };
                log.push(le);
                deferred.reject({result: 'No parser installed for this type.', log: le, source: object});
                return;
            }

            // Use installed parser for this type
            p.parse(object, opt).
                then(function(o) {
                    // Create a log entry
                    var le = {
                        type: type,
                        time: startTime,
                        elapsed: new Date().getTime() - startTime,
                        result: 1, // Pass
                    };
                    log.push(le);
                    // Notify
                    deferred.resolve({result: o, log: le, source: object});
                },
                    function(err) {
                        // Create a log entry
                        var le = {
                            type: type,
                            time: startTime,
                            elapsed: new Date().getTime() - startTime,
                            result: 0, // fail
                            error: err
                        };
                        log.push(le);

                        // Notify
                        deferred.reject({result: err, log: le, source: object});
                    });
            return deferred.promise;
        }

        // Installs a parser
        function installParser(parser) {
            if (parser.type === undefined || parser.parse === undefined || parser.parse === null) {
                // parser is not valid
                return undefined;
            }

            // Add the parser
            parsers[parser.type] = parser;
            return parser.type;
        }

        // Find parser by type
        function findParser(type) {
            return parsers[type];
        }

        // Remove parser type
        function removeParser(type) {
            if (findParser(type) === undefined) {
                return false;
            }

            parsers[type] = undefined;
            return true;
        }

        // Returns all installed parsers
        function getParsers() {
            return parsers;
        }

        return {
            // Public interface
            installParser: installParser,
            removeParser: removeParser,
            findParser: findParser,
            getParsers: getParsers,
            getLogs: getLogs,
            parse: parse,
            parseUrl: parseUrl
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    app.factory('PassiveMetric', function() {
        function PassiveMetric() {

        }

        PassiveMetric.prototype = {
            contentId: null,
            startTime: null,
            endTime: null,
            playerGuid: null,
            applicationId: null,
            scheduleId: null,
            mediaZoneDatasourceId: null, // media zone datasource id
            widgetDatasourceId: null, // widget data source id (non-mz datasource id). for media zone, this should be the same as mz datasource id
            deviceType: null,
            channelGuid: null,
            contentGuid: null,
            contentType: null,
            contentDuration: null,
            cardTemplateId: null,
            cardThemeId: null,
            cardThemeName: null,
            mediaZoneGuid: null,
            deviceGroupGuid: null,
            deviceTags: null,
        };

        return (PassiveMetric);
    });

    app.service('metricService', ["$window", "HttpWrapper", "logService", "dataUtil", "versionUtil", "deviceTokenService", "authenticationLibraryService", function($window, HttpWrapper, logService, dataUtil, versionUtil, deviceTokenService, authenticationLibraryService) {
        var httpWrapper = new HttpWrapper();
        var currentAnalyticsPlayoutMetricFlag = false;
        var playoutMetricMedianetService = undefined;
        var playoutMetricAnalyticsService = undefined;
        var logkey = 'appspace.signs.metric.service';
        var SUPPORTED_SERVICE_VERSIONS = {
            CLOUD: {
                MIN: '8.10.0-ac.2',
                MAX: '8.10.0'
            },
            GLOBAL: '8.10.0'
        };
        var FEATURE_FLAG = {
            ANALYTICS_PLAYOUT_METRIC: 'feature.devices.analytics.contentplayback',
        };
        // init service
        init();

        return {
            addPassiveMetric: addPassiveMetric,
            registerMetricSchedule: registerMetricSchedule,
            clearAllMetricSchedule: clearAllMetricSchedule,
            clearPendingMetrics: clearPendingMetrics,
            suspend: suspend,
            resume: resume,
            stop: stop,
            updateConfiguration: updateConfiguration
        };

        function init() {
            logService.info(logkey, 'Metric service intialized');
            playoutMetricMedianetService = new $window.AppspaceCloudDeviceMedia.PlayoutMetricMedianetService(logService, handlerPost);
            logService.debug(logkey, 'Playout metric medianet service intialized');
        }

        function addPassiveMetric(metric) {
            if (playoutMetricMedianetService) {
                playoutMetricMedianetService.addPassiveMetric(metric);
            }

            if (playoutMetricAnalyticsService) {
                playoutMetricAnalyticsService.addPassiveMetric(metric);
            }
        }

        function registerMetricSchedule(metric) {
            // only applicable for medianet service
            if (!playoutMetricMedianetService) {
                return;
            }

            playoutMetricMedianetService.registerMetricSchedule(metric);
        }

        function clearAllMetricSchedule() {
            // only applicable for medianet service
            if (!playoutMetricMedianetService) {
                return;
            }

            playoutMetricMedianetService.clearAllMetricSchedule();
        }

        function clearPendingMetrics() {
            if (playoutMetricMedianetService) {
                playoutMetricMedianetService.clearPendingMetrics();
            }

            if (playoutMetricAnalyticsService) {
                playoutMetricAnalyticsService.clearPendingMetrics();
            }
        }

        function suspend() {
            if (playoutMetricMedianetService) {
                playoutMetricMedianetService.suspend();
            }

            if (playoutMetricAnalyticsService) {
                playoutMetricAnalyticsService.suspend();
            }
        }

        function resume() {
            if (playoutMetricMedianetService) {
                playoutMetricMedianetService.resume();
            }

            if (playoutMetricAnalyticsService) {
                playoutMetricAnalyticsService.resume();
            }
        }

        function stop() {
            if (playoutMetricMedianetService) {
                playoutMetricMedianetService.stop();
            }

            if (playoutMetricAnalyticsService) {
                playoutMetricAnalyticsService.stop();
            }
        }

        function updateConfiguration(options) {
            var properties = options.properties;
            var analyticsPlayoutMetricFlag = dataUtil.isValueTrue(properties[FEATURE_FLAG.ANALYTICS_PLAYOUT_METRIC]);
            // don't process if the flag is the same
            if (currentAnalyticsPlayoutMetricFlag === analyticsPlayoutMetricFlag) {
                return;
            }

            // if doesn't support, stop the analytics service
            if (!analyticsPlayoutMetricFlag) {
                currentAnalyticsPlayoutMetricFlag = analyticsPlayoutMetricFlag;
                destroyPlayoutMetricAnalyticsService();
                return;
            }

            // check if Appspace supports it
            versionUtil.isVersionSupported(SUPPORTED_SERVICE_VERSIONS, logkey).then(function(supported) {
                // if not supported, stop the analytics service
                if (!supported) {
                    currentAnalyticsPlayoutMetricFlag = false;
                    destroyPlayoutMetricAnalyticsService();
                    return;
                }

                currentAnalyticsPlayoutMetricFlag = analyticsPlayoutMetricFlag;
                playoutMetricAnalyticsService = new $window.AppspaceCloudDeviceMedia.PlayoutMetricAnalyticsService(logService, apiPost);
                logService.debug(logkey, 'Playout metric analytics service intialized');
                // if medianet service already running, run the analytics service as well
                if (playoutMetricMedianetService.isSuspended || playoutMetricMedianetService.isStopped) {
                    return;
                }

                playoutMetricAnalyticsService.resume();
            });
        }

        function apiPost(url, data, headers) {
            // wrap the request in a standard promise as the services are non-framework specifics
            return new Promise(function(resolve, reject) {
                authenticationLibraryService.requestConstellationService(url, deviceTokenService.getConstellationTokens(), {
                    method: 'POST',
                    data: data,
                    contentType: 'application/json',
                    headers: headers
                }).then(resolve, reject);
            });
        }

        function handlerPost(url, data, headers, timeout) {
            // wrap the request in a standard promise as the services are non-framework specifics
            return new Promise(function(resolve, reject) {
                httpWrapper.apiPost(url, data, headers, timeout).then(resolve, reject);
            });
        }

        function destroyPlayoutMetricAnalyticsService() {
            if (!playoutMetricAnalyticsService) {
                return;
            }

            playoutMetricAnalyticsService.stop();
            playoutMetricAnalyticsService = undefined;
        }
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');
    app.service('deviceAlertService', ["syncService", "logService", "SYNC_EVENT", "TimeoutUtil", "channelService", "channelsHelper", "LinearPlaylist", "dataUtil", "contentHelper", "DateTimeUtil", function(syncService,
        logService,
        SYNC_EVENT,
        TimeoutUtil,
        channelService,
        channelsHelper,
        LinearPlaylist,
        dataUtil,
        contentHelper,
        DateTimeUtil) {
        var LOG_KEY = 'appspace.alerts.devicealertservice';
        var EXCLUSIVE_CHANNEL_KEY = 'player.play.exclusive.channel';
        var EXCLUSIVE_CHANNEL_TYPE = 'playlist';
        var EXCLUSIVE_CHANNEL_TIMEOUT = 1000;
        var CHANNEL_SEARCH_FIELDS = ['name', 'title', 'channelGuid'];
        var exclusiveChannelStartSubscribers = [];
        var exclusiveChannelStopSubscribers = [];
        var exclusiveChannelProperty;
        var exclusiveChannelLastMinute;
        var exclusiveChannelTimeoutPromise;
        var lastExclusiveChannel;
        var isExclusiveChannelValid = false;
        var isStarted = false;

        function start() {
            if (isStarted) {
                return;
            }

            isStarted = true;
            syncService.subscribe(onSyncServiceUpdated);
            logService.debug(LOG_KEY, 'Device Alert Service started.');
        }

        function stop() {
            if (!isStarted) {
                return;
            }

            isStarted = false;
            resetState();
            syncService.unsubscribe(onSyncServiceUpdated);
            logService.debug(LOG_KEY, 'Device Alert Service stopped.');
        }

        function resetState() {
            isExclusiveChannelValid = false;
            exclusiveChannelProperty = undefined;
            stopExclusiveChannelTimeout();
        }

        function processExclusiveChannel() {
            var properties = syncService.getCurrentAppProperties();
            if (!properties || !properties[EXCLUSIVE_CHANNEL_KEY]) {
                resetState();
                notifyExclusiveChannelSubscribers();
                return;
            }

            exclusiveChannelProperty = properties[EXCLUSIVE_CHANNEL_KEY];
            startExclusiveChannelTimeout();
        }

        function startExclusiveChannelTimeout() {
            stopExclusiveChannelTimeout();
            exclusiveChannelTimeoutPromise = TimeoutUtil.start(onCheckExclusiveChannel, EXCLUSIVE_CHANNEL_TIMEOUT);
        }

        function stopExclusiveChannelTimeout() {
            TimeoutUtil.cancel(exclusiveChannelTimeoutPromise);
        }

        function onCheckExclusiveChannel() {
            stopExclusiveChannelTimeout();
            checkExclusiveChannel();
            startExclusiveChannelTimeout();
        }

        function checkExclusiveChannel() {
            var dateTimeUtil = new DateTimeUtil();
            var currentMinute = dateTimeUtil.getCurrentMinute();
            if (currentMinute === exclusiveChannelLastMinute) {
                return;
            }

            exclusiveChannelLastMinute = currentMinute;
            var channel = channelService.searchChannelByChannelField(
                exclusiveChannelProperty,
                CHANNEL_SEARCH_FIELDS);
            notifyExclusiveChannelSubscribers(getExclusiveChannel(channel));
        }

        function getExclusiveChannel(channel) {
            // only support playlist
            if (!channel || channel.type !== EXCLUSIVE_CHANNEL_TYPE) {
                isExclusiveChannelValid = false;
                return;
            }

            var contentItems = channelsHelper.getChannelContent(channel);
            if (!contentItems || !contentItems.length) {
                isExclusiveChannelValid = false;
                return;
            }

            // get content and set into playlist.
            contentItems = $angular.copy(contentItems);
            contentHelper.applyContentItemsPlayoutProperties(contentItems);
            var playlist = new LinearPlaylist(false, true);
            playlist.setItems(contentItems);
            var nextValidItem = playlist.next();
            var result;
            var logMessage = 'Exclusive channel "' + channel.title + '" is currently ';
            // If playlist doesn't return anything, means no valid item
            if (nextValidItem) {
                logMessage += 'valid.';
                result = $angular.copy(channel);
            } else {
                logMessage += 'invalid.';
            }

            isExclusiveChannelValid = Boolean(nextValidItem);
            logService.debug(LOG_KEY, logMessage);
            return result;
        }

        function notifyExclusiveChannelSubscribers(channel) {
            // if we don't have channel to play right now but we have a channel previously, notify stop
            if (!channel && lastExclusiveChannel) {
                notifyExclusiveChannelStopSubscribers();
            }

            // we don't want to process start if there's no channel
            if (!channel) {
                return;
            }

            lastExclusiveChannel = channel;
            notifyExclusiveChannelStartSubscribers();
        }

        function notifyExclusiveChannelStopSubscribers() {
            for (var i = 0; i < exclusiveChannelStopSubscribers.length; i++) {
                exclusiveChannelStopSubscribers[i](lastExclusiveChannel);
            }
        }

        function notifyExclusiveChannelStartSubscribers() {
            for (var i = 0; i < exclusiveChannelStartSubscribers.length; i++) {
                exclusiveChannelStartSubscribers[i](lastExclusiveChannel);
            }
        }

        function onSyncServiceUpdated(event) {
            if (!event || !dataUtil.includesValue(event.event, [
                SYNC_EVENT.SCRIPT_SAVED,
                SYNC_EVENT.SCRIPT_READY
            ])) {
                return;
            }

            exclusiveChannelLastMinute = undefined;
            processExclusiveChannel();
        }

        function getSubscriberIndex(collection, callback) {
            for (var i = 0; i < collection.length; i++) {
                var subscriber = collection[i];
                if (callback !== subscriber) {
                    continue;
                }

                return i;
            }
        }

        function subscribe(collection, callback) {
            if (!callback) {
                return false;
            }

            var index = getSubscriberIndex(collection, callback);
            if (!dataUtil.isNullOrUndefined(index)) {
                return false;
            }

            collection.push(callback);
            return true;
        }

        function unsubscribe(collection, callback) {
            if (!callback) {
                return false;
            }

            var index = getSubscriberIndex(collection, callback);
            if (dataUtil.isNullOrUndefined(index)) {
                return false;
            }

            dataUtil.removeArrayItemAtIndex(collection, index);
            return true;
        }

        function subscribeExclusiveChannelStart(callback) {
            // if subscribe is not successful, OR
            // if we don't have a running channel, don't update callback directly
            if (!subscribe(exclusiveChannelStartSubscribers, callback) ||
                !isExclusiveChannelValid ||
                !lastExclusiveChannel) {
                return;
            }

            callback(lastExclusiveChannel);
        }

        function unsubscribeExclusiveChannelStart(callback) {
            unsubscribe(exclusiveChannelStartSubscribers, callback);
        }

        function subscribeExclusiveChannelStop(callback) {
            // if subscribe is not successful, OR
            // if we have a running exclusive channel, don't update callback directly
            if (!subscribe(exclusiveChannelStopSubscribers, callback) ||
                isExclusiveChannelValid ||
                !lastExclusiveChannel) {
                return;
            }

            callback(lastExclusiveChannel);
        }

        function unsubscribeExclusiveChannelStop(callback) {
            unsubscribe(exclusiveChannelStopSubscribers, callback);
        }

        return {
            start: start,
            stop: stop,
            subscribeExclusiveChannelStart: subscribeExclusiveChannelStart,
            unsubscribeExclusiveChannelStart: unsubscribeExclusiveChannelStart,
            subscribeExclusiveChannelStop: subscribeExclusiveChannelStop,
            unsubscribeExclusiveChannelStop: unsubscribeExclusiveChannelStop
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');
    app.factory('ClientRemoteService', ["BaseRemoteService", "halNetworkService", "logService", "deviceInfoUtil", "UrlUtil", function(
        BaseRemoteService,
        halNetworkService,
        logService,
        deviceInfoUtil,
        UrlUtil
    ) {
        function ClientRemoteService(serverUrl) {
            if (!serverUrl) {
                return;
            }
            var server = normalizeServer(serverUrl);
            var base = new BaseRemoteService();

            this.subscribe = base.subscribe;
            this.unsubscribe = base.unsubscribe;

            this.start = function() {
                deviceInfoUtil.getDeviceIdentifier().then(function(res) {
                    var deviceIdentifier = encodeURIComponent(JSON.stringify(res));
                    halNetworkService.startClient(server, deviceIdentifier);
                    halNetworkService.subscribeClientMessages(onClientMessage);
                });
            };

            this.stop = function() {
                halNetworkService.stopClient();
                halNetworkService.unsubscribeClientMessages(onClientMessage);
            };

            function normalizeServer(serverUrl) {
                var url = UrlUtil.retrieveUrlAnatomy(serverUrl);

                url.protocol = url.protocol || 'ws://';
                url.ip = (!url.ip || url.ip === 'localhost' || url.ip === 'local') ? '127.0.0.1' : url.ip;
                url.port = url.port || '55538';

                return url;
            }

            function onClientMessage(message) {
                logService.debug('appspace.network.comm', 'receiving message from remote:', message);
                base.notifySubscribers(message);
            }
        }

        return (ClientRemoteService);
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');
    app.factory('ServerRemoteService',
        ["halNetworkService", "logService", "syncService", "BaseRemoteService", function(
            halNetworkService,
            logService,
            syncService,
            BaseRemoteService
        ) {
            function ServerRemoteService(config) {
                var base = new BaseRemoteService();
                var started;
                this.subscribe = base.subscribe;
                this.unsubscribe = base.unsubscribe;

                this.start = function() {
                    if (started) {
                        logService.debug('appspace.network.comm', 'Web socket server already started at 55537');
                        return;
                    }
                    // start http/socket server via driver
                    logService.debug('appspace.network.comm', 'Starting web socket server at 55537');
                    halNetworkService.startServer({
                        port: 55537,
                        forceUdp: (config || {}).forceUdp
                    });
                    halNetworkService.subscribeServerMessages(onReceivedNetworkMessage);
                    started = true;
                };

                this.stop = function() {
                    if (!started) {
                        logService.debug('appspace.network.comm', 'Web socket server was not stopped because it has not been started');
                        return;
                    }
                    // stop http/socket server via driver
                    logService.debug('appspace.network.comm', 'Stopping web socket server');
                    halNetworkService.unSubscribeServerMessages(onReceivedNetworkMessage);
                    halNetworkService.stopServer();
                    started = false;
                };

                function onReceivedNetworkMessage(message) {
                    var enableProp = (syncService.getCurrentAppProperty('remote.enable') || '').toLowerCase() == 'true';
                    if (!enableProp) {
                        logService.debug('appspace.network.comm', 'Remote.Enable value:', enableProp);
                        logService.warn('appspace.network.comm', 'Action scripting not executed. Remote.Enable property is not true');
                        return;
                    }
                    logService.debug('appspace.network.comm', 'Receiving message:', message);
                    base.notifySubscribers(message);
                }
            }

            return (ServerRemoteService);
        }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');
    app.service('applicationCommandService', ["dataUtil", function(dataUtil) {
        var subscribers = [];

        function execute(scripting) {
            notifySubscribers(scripting);
        }

        function subscribe(callback) {
            if (!callback || subscribers.indexOf(callback) > -1) {
                return;
            }
            subscribers.push(callback);
        }

        function unsubscribe(callback) {
            var index = subscribers.indexOf(callback);
            if (index === -1) {
                return;
            }
            subscribers.splice(index, 1);
        }

        function notifySubscribers(event) {
            if (dataUtil.isObjectEmpty(event)) {
                return;
            }
            for (var i = 0; i < subscribers.length; i++) {
                subscribers[i](event);
            }
        }

        return {
            execute: execute,
            subscribe: subscribe,
            unsubscribe: unsubscribe
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');
    app.service('remoteControlService', ["REMOTEEVENTTYPE", "halRemoteService", "RemoteEvent", "applicationCommandService", function(REMOTEEVENTTYPE, halRemoteService, RemoteEvent, applicationCommandService) {
        function start() {
            applicationCommandService.subscribe(onCommandReceived);
        }

        function stop() {
            applicationCommandService.unsubscribe(onCommandReceived);
        }

        function onCommandReceived(event) {
            if (!event || event.type !== 'remote' || !event.key) {
                return;
            }

            var re = {eventtype: REMOTEEVENTTYPE.buttonDown, vcode: event.key};
            halRemoteService.raiseRemoteEvent(new RemoteEvent(re), false);
        }

        return {
            start: start,
            stop: stop
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');
    app.service('remoteServiceManager', ["$window", "ServerRemoteService", "ClientRemoteService", "applicationCommandService", "ActionUtil", "REMOTEVCODE", "logService", "sessionService", "remoteControlService", "dataUtil", function(
        $window,
        ServerRemoteService,
        ClientRemoteService,
        applicationCommandService,
        ActionUtil,
        REMOTEVCODE,
        logService,
        sessionService,
        remoteControlService,
        dataUtil) {
        var params;
        var clientRemote;
        var serverRemote;
        var currentArcUrl;
        var currentForceUdpValue;
        var actionMappings = [
            {key: '?actionscript=', processCallback: parseActionScriptingMessage},
            {key: '?remote=', processCallback: parseRemoteMessage},
            {key: '?system=', processCallback: parseSystemMessage},
            {key: '?channel=', processCallback: parseChannelMessage},
        ];

        function init() {
            params = sessionService.getUrlParameters();
        }

        function initiateClientRemote(arcUrl) {
            // If clientRemote was instantiated but arcUrl was changed, stop clientRemote
            if (clientRemote && arcUrl != currentArcUrl) {
                stopArc();
            }

            // if clientRemote was not instantiated and arcUrl is defined, instantiate clientRemote
            if (!clientRemote && arcUrl) {
                currentArcUrl = arcUrl;
                clientRemote = new ClientRemoteService(arcUrl);
                clientRemote.subscribe(execute);
                clientRemote.start();
            }
        }

        function raiseCommand(command) {
            if (!command || !command.type || !command.command) {
                return;
            }
            var commandQueryParam = '?' + command.type + '=' + command.command;
            if (command.data) {
                commandQueryParam += '&' + dataUtil.httpParamSerializer(command.data);
            }
            applicationCommandService.execute(parseNetworkMessage(commandQueryParam));
        }

        function start(config) {
            remoteControlService.start();
            var startConfig = config || {};
            initiateClientRemote(params.arc || startConfig.arc);
            initiateServerRemote(startConfig);
        }

        function initiateServerRemote(config) {
            // If serverRemote has been started and previous force udp config is the same, then we don't instantiate it again
            if (serverRemote && currentForceUdpValue === config.forceUdp) {
                logService.debug('appspace.network.comm', 'Remote service has already been started');
                return;
            }

            currentForceUdpValue = config.forceUdp;
            stopServerRemote();
            serverRemote = new ServerRemoteService({forceUdp: config.forceUdp});
            serverRemote.subscribe(execute);
            serverRemote.start();
        }

        function stopArc() {
            if (clientRemote) {
                clientRemote.stop();
                clientRemote.unsubscribe(execute);
                clientRemote = undefined;
                currentArcUrl = undefined;
            }
        }

        function stop() {
            remoteControlService.stop();
            stopArc();
            stopServerRemote();
        }

        function stopServerRemote() {
            if (!serverRemote) {
                return;
            }
            serverRemote.stop();
            serverRemote.unsubscribe(execute);
            serverRemote = undefined;
        }

        function execute(res) {
            if (!res) {
                return;
            }
            applicationCommandService.execute(parseNetworkMessage(res));
        }

        function parseNetworkMessage(message) {
            var messageLowerCase = message.toLowerCase();
            for (var i = 0; i < actionMappings.length; i++) {
                var action = actionMappings[i];
                if (messageLowerCase.indexOf(action.key) > -1) {
                    return action.processCallback(messageLowerCase, message);
                }
            }

            return {};
        }

        function parseActionScriptingMessage(message, originalMessage) {
            var item = {type: 'actionscript', actions: []};
            var raw = decodeURIComponent(originalMessage).substring('?actionscript='.length);
            item.actions = ActionUtil.convertStringToActions(raw);
            return item;
        }

        function parseRemoteMessage(message) {
            var item = {type: 'remote', key: ''};
            var raw = message.substring('?remote='.length);
            item.key = REMOTEVCODE[raw];

            return item;
        }

        function parseSystemMessage(message) {
            var item = {type: 'system', action: ''};
            var raw = message.substring('?system='.length);
            item.action = raw && $window.humps ? $window.humps.camelize(raw) : undefined;

            return item;
        }

        function parseChannelMessage(message) {
            var item = {type: 'channel', action: ''};
            var raw = message.substring('?channel='.length);
            item.action = raw ? ActionUtil.convertStringToChannelActions(raw) : undefined;

            return item;
        }

        init();

        return {
            start: start,
            stop: stop,
            raiseCommand: raiseCommand
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');

    app.service('publicChannelService', ["channelService", "networkContentRouterService", function(channelService, networkContentRouterService) {
        function convertPlayChannelToBasicChannel(playChannel) {
            var i;
            var asset;

            var channel = {
                id: playChannel.id,
                name: playChannel.title,
                description: playChannel.description,
                type: playChannel.type,
                iconColor: playChannel.iconColor,
                position: playChannel.position,
                channelGroupId: playChannel.groupId,
                defaultContentDuration: playChannel.defaultContentDuration,
                defaultSequence: playChannel.defaultSequence,
                updatedAt: playChannel.updateddate,
                content: []
            };

            // Enumerate through sub channels to convert to content
            for (i = 0; i < playChannel.assets.length; i++) {
                asset = playChannel.assets[i];
                if (asset.type == 'thumbnail') {
                    channel.iconMedia = asset.mediaUrl;
                    channel.iconMediaType = asset.mediaType;
                    break;
                }
            }

            // Enumerate through sub channels to convert to content
            for (var k = 0; k < playChannel.subChannels.length; k++) {
                var sc = playChannel.subChannels[k];
                var content = {
                    id: sc.id,
                    name: sc.title,
                    description: sc.description,
                    position: sc.position,
                    duration: playChannel.defaultContentDuration,
                    updatedAt: sc.updateddate
                };

                // Enumerate through sub channels to convert to content
                for (i = 0; i < sc.assets.length; i++) {
                    asset = sc.assets[i];
                    var assetType = asset.type;
                    if (assetType !== 'thumbnail' && assetType !== 'content') {
                        continue;
                    }
                    var isThumbnail = assetType === 'thumbnail';
                    content[isThumbnail ? 'thumbnailMedia' : 'media'] = asset.mediaUrl;
                    content[isThumbnail ? 'thumbnailMediaType' : 'mediaType'] = asset.mediaType;
                }
                channel.content.push(content);
            }
            return channel;
        }

        function convertPlayGroupToBasicGroup(playGroup) {
            var group = {
                id: playGroup.id,
                name: playGroup.name,
                description: playGroup.description
            };
            return group;
        }

        function getChannels() {
            var basicChannels = [];
            var playChannels = channelService.getChannels();
            for (var i = 0; i < playChannels.length; i++) {
                if (playChannels[i].type == 'content') {
                    continue;
                }
                basicChannels.push(convertPlayChannelToBasicChannel(playChannels[i]));
            }
            return basicChannels;
        }

        function getChannelGroups() {
            var basicGroups = [];
            var playGroups = channelService.getChannelGroups();
            for (var i = 0; i < playGroups.length; i++) {
                if (playGroups[i].type == 'content') {
                    continue;
                }
                basicGroups.push(convertPlayGroupToBasicGroup(playGroups[i]));
            }
            return basicGroups;
        }

        function resolveContentPath(url, type) {
            return networkContentRouterService.resolve(url, 'play-content-routing-service', {type: type, cache: true});
        }

        return {
            getChannels: getChannels,
            getChannelGroups: getChannelGroups,
            resolveContentPath: resolveContentPath
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');

    app.service('publicService', ["$window", "logService", "coreFrameworkService", "publicChannelService", "syncService", "scriptingService", function($window, logService, coreFrameworkService, publicChannelService, syncService, scriptingService) {
        var services = {};

        function registerService(serviceName, service) {
            logService.debug('appspace.public.service', 'Register ' + serviceName + ' to public service');
            services[serviceName] = service;
            return services;
        }

        function unregisterService(serviceName) {
            // if service doesn't exist, don't do anything
            if (!services[serviceName]) {
                return false;
            }
            logService.debug('appspace.public.service', 'Removing ' + serviceName + ' from public service');
            services[serviceName] = undefined;
            return true;
        }

        function init() {
            logService.debug('appspace.public.service', 'Init App public service');
            $window.$asAppPublicService = {
                getServices: getServices
            };
            registerService('channels', publicChannelService);
            registerService('sync', syncService);
            registerService('scripting', scriptingService);
        }

        function getServices() {
            // todo : log every single call to the services & their methods
            logService.trace('appspace.public.service', 'Accessing a service from public services');
            return services;
        }

        coreFrameworkService.isReady().then(init);

        return {
            registerService: registerService,
            unregisterService: unregisterService,
            getServices: getServices
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('signsHTML5');

    app.service('rendezvousService', ["$q", "$timeout", "HttpWrapper", "logService", "halPlatformService", "coreConfigurationService", "Hub", "SYSTEM_DATA", "STORAGE_KEY", "STORAGE_COLLECTION_KEY", "dataUtil", "driverUtil", "coreFrameworkService", "DateTimeUtil", "TimeoutUtil", function(
        $q,
        $timeout,
        HttpWrapper,
        logService,
        halPlatformService,
        coreConfigurationService,
        Hub,
        SYSTEM_DATA,
        STORAGE_KEY,
        STORAGE_COLLECTION_KEY,
        dataUtil,
        driverUtil,
        coreFrameworkService,
        DateTimeUtil,
        TimeoutUtil) {
        var started = false;
        var subscribers = [];
        var deviceFingerprint = null;
        var mainHub;
        var LOG_KEY = 'appspace.signs.rendezvous';
        var currentRes = null;
        var connected;
        var connectionTimeout = 15000;
        var connectionTimeoutHandler;
        var lastConnectionTimeoutCheck;
        var totalConsecutiveConnectionTimeout;
        var restartServiceHandler;
        var httpWrapper = new HttpWrapper();
        var DEVICE_KEYWORD = 'device';
        var EMPTY_STRING_KEYWORD = '';
        var CLAIMABLE_IDS_SEPARATOR = ',';
        var DEVICE_ID_PLACEHOLDER = '{{deviceId}}';
        var DEVICES_API_PATH = '/rendezvous/api/v1/devices/';
        var GET_REGISTRATION_INFO_PATH = DEVICES_API_PATH + 'registration/';
        var DELETE_REGISTRATION_INFO_PATH = DEVICES_API_PATH + DEVICE_ID_PLACEHOLDER + '/registration';
        var DEFAULT_DISCOVERY_URL = 'https://discovery.cloud.appspace.com';
        var updateRegistrationInfoAttempt = {};
        var updateRegistrationInfoDeferred;
        var UPDATE_REGISTRATION_INFO_ERROR_MESSAGE = 'Failed to update registration information. Reason: ';
        var UPDATE_REGISTRATION_INFO_CONFIG = {
            exponentialBase: 2,
            backoffInterval: 60,
            failureBackoffMax: 1800,
        };
        var dateTimeUtil = new DateTimeUtil();

        // public methods
        function start() {
            logService.info(LOG_KEY, 'Rendezvous service initiate');
            connected = false;
            cancelRestartServiceHandler();

            var onDeviceInfoRetrieveFailure = function() {
                logService.warn(LOG_KEY, 'Failed to get device info');
            };

            var onDeviceInfoRetrieved = function(deviceInfo) {
                if (!deviceInfo || !deviceInfo.hardware || !deviceInfo.hardware.fingerprint || !deviceInfo.hardware.fingerprint.fingerprint) {
                    logService.warn(LOG_KEY, 'Unable to retrieve fingerprint');
                    return;
                }

                deviceFingerprint = deviceInfo.hardware.fingerprint.fingerprint;
                var queryParams = {
                    fingerprint: deviceFingerprint,
                    origin: 'device'
                };

                if (deviceInfo.claimableIds && deviceInfo.claimableIds.length) {
                    queryParams.claimableIds = deviceInfo.claimableIds.join(CLAIMABLE_IDS_SEPARATOR);
                }

                var clientURL = getDiscoveryUrl() + '/rendezvous/signalr';
                logService.debug(LOG_KEY, 'Rendezvous service started. Fingerprint: ' + deviceFingerprint + ' Discovery URL: ' + clientURL);
                started = true;
                lastConnectionTimeoutCheck = null;
                connectionTimeoutHandler = $timeout(checkRVState, 1000);
                mainHub = new Hub('RendezvousHub', {
                    listeners: {
                        onRendezvousMessage: function(res) {
                            logService.debug(LOG_KEY, 'On received response: ' + res);
                            if (res) {
                                try {
                                    currentRes = JSON.parse(res);
                                    onReceiveRVFile(currentRes);
                                } catch (e) {
                                    logService.error(LOG_KEY, 'Could not parse', e);
                                }
                            } else {
                                logService.error(LOG_KEY, 'Empty response');
                            }
                        }
                    },
                    methods: ['claimCode', 'notify'],
                    queryParams: queryParams,
                    errorHandler: function(err) {
                        logService.error(LOG_KEY, 'Error');
                        if (!navigator.onLine) {
                            notifySubscribers({event: 'offline'});
                        } else {
                            notifySubscribers({event: 'service-down'});
                        }

                        stop();
                    },
                    stateChanged: function(state) {
                        switch (state.newState) {
                            case $.signalR.connectionState.connecting:
                                connected = false;
                                logService.debug(LOG_KEY, 'Connecting');
                                break;
                            case $.signalR.connectionState.connected:
                                connected = true;
                                totalConsecutiveConnectionTimeout = 0;
                                cancelConnectionTimeoutHandler();
                                cancelRestartServiceHandler();
                                // claim code directly to send device identity
                                claimCode();
                                logService.debug(LOG_KEY, 'Connected');
                                break;
                            case $.signalR.connectionState.reconnecting:
                                connected = false;
                                logService.warn(LOG_KEY, 'Reconnecting');
                                break;
                            case $.signalR.connectionState.disconnected:
                                connected = false;
                                logService.debug(LOG_KEY, 'Disconnected');
                                break;
                        }
                    },
                    rootPath: clientURL,
                    useSharedConnection: false,
                    withCredentials: false,
                    jsonp: false
                });
            };

            getDeviceInfo().then(onDeviceInfoRetrieved, onDeviceInfoRetrieveFailure);
        }

        function cancelRestartServiceHandler() {
            TimeoutUtil.cancel(restartServiceHandler);
            restartServiceHandler = undefined;
        }

        function cancelConnectionTimeoutHandler() {
            TimeoutUtil.cancel(connectionTimeoutHandler);
            connectionTimeoutHandler = undefined;
        }

        function checkRVState() {
            if (!lastConnectionTimeoutCheck) {
                lastConnectionTimeoutCheck = new Date();
            }

            var c = new Date();
            if ((c.getTime() - lastConnectionTimeoutCheck.getTime()) >= connectionTimeout) {
                logService.warn(LOG_KEY, 'Unable to connect to RV in time.');

                if (!totalConsecutiveConnectionTimeout) {
                    totalConsecutiveConnectionTimeout = 0;
                }

                totalConsecutiveConnectionTimeout++;
                cancelConnectionTimeoutHandler();
                // if we already try more than 3 times, send service timeout message
                if (totalConsecutiveConnectionTimeout >= 3) {
                    totalConsecutiveConnectionTimeout = 0;
                    logService.warn(LOG_KEY, 'Max connection retries due to timeout issue. Sending service-down message.');
                    notifySubscribers({event: 'service-timeout'});
                } else {
                    logService.warn(LOG_KEY, 'Retrying in 5 seconds.');
                    cancelRestartServiceHandler();
                    restartServiceHandler = $timeout(function() {
                        logService.debug(LOG_KEY, 'Restarting service.');
                        // stop service before retrying
                        stop();
                        start();
                    }, 5000);
                }
            } else {
                connectionTimeoutHandler = $timeout(checkRVState, 1000);
            }
        }

        function stop() {
            currentRes = null;
            logService.info(LOG_KEY, 'Rendezvous service stopped');
            started = false;
            cancelRestartServiceHandler();
            if (mainHub) {
                mainHub.disconnect();
                mainHub = null;
            }
        }

        function isStarted() {
            return started;
        }

        function createEventFromResponse(res) {
            var event = {};
            if (!res) {
                return event;
            }

            if (res.PolicyJson) {
                logService.debug(LOG_KEY, 'Received policy');
                logService.debug(LOG_KEY, 'Policy : ', res.PolicyJson);
                var policy = {};
                policy.registration = JSON.parse(res.PolicyJson);
                event = {event: 'on-policy', policy: policy};
            } else if (res.ClaimCode) {
                logService.debug(LOG_KEY, 'Received code:' + res.ClaimCode);
                event = {event: 'on-code', code: res.ClaimCode};
            } else if (res.DeviceIdentityJwt) {
                logService.debug(LOG_KEY, 'Received device identity JWT:' + res.DeviceIdentityJwt);
                event = {event: 'on-jwt', deviceIdentityJwt: res.DeviceIdentityJwt};
            } else if (res.RegistrationInformationDeleted) {
                logService.info(LOG_KEY, 'Registration information has been deleted');
                event = {event: 'on-deleted'};
            }

            return event;
        }

        function onReceiveRVFile(res) {
            var event = createEventFromResponse(res);
            notifySubscribers(event);
        }

        function notify() {
            if (mainHub && started && connected) {
                mainHub.notify(deviceFingerprint, 0, '');
            }
        }

        function claimCode() {
            if (!mainHub || !started || !connected) {
                return;
            }

            getDeviceInfo().then(function(deviceInfo) {
                mainHub.claimCode(deviceFingerprint,
                    DEVICE_KEYWORD,
                    EMPTY_STRING_KEYWORD,
                    EMPTY_STRING_KEYWORD,
                    JSON.stringify(deviceInfo));
            });
        }

        function getDeviceInfo() {
            // Ensure that URL-parameter-based device information is updated
            halPlatformService.updateReferenceDeviceId(halPlatformService.getReferenceDeviceIdKey());
            halPlatformService.updateIntegrationType();
            return halPlatformService.getDeviceInfo();
        }

        function subscribe(callback) {
            var l = subscribers.length;
            var found = false;
            for (var i = 0; i < l; i++) {
                var cb = subscribers[i];
                if (cb == callback) {
                    found = true;
                    break;
                }
            }

            if (found == false) {
                if (currentRes) {
                    var event = createEventFromResponse(currentRes);
                    callback(event);
                }

                subscribers.push(callback);
            }
        }

        function unsubscribe(callback) {
            var l = subscribers.length;
            for (var i = 0; i < l; i++) {
                var cb = subscribers[i];
                if (cb == callback) {
                    subscribers.splice(i, 1);
                    break;
                }
            }
        }

        // private
        function notifySubscribers(event) {
            var l = subscribers.length;
            for (var i = 0; i < l; i++) {
                var cb = subscribers[i];
                cb(event);
            }
        }

        function getDiscoveryUrl() {
            return coreFrameworkService.getDiscoveryUrl() || DEFAULT_DISCOVERY_URL;
        }

        function getRegistrationInfoUrl(fingerprint) {
            return getDiscoveryUrl() + GET_REGISTRATION_INFO_PATH + fingerprint;
        }

        function getDeleteRegistrationInfoUrl(deviceId) {
            var deleteRegistrationInfoUrl = getDiscoveryUrl() + DELETE_REGISTRATION_INFO_PATH;
            return deleteRegistrationInfoUrl.replace(DEVICE_ID_PLACEHOLDER, deviceId);
        }

        function getRegistrationInfo(fingerprint) {
            var deferred = $q.defer();
            var onFailedToGetRegistrationInfo = function(error) {
                logService.info(LOG_KEY, 'Failed to retrieve previous registration. Reason: ' + error);
                deferred.reject(error);
            };

            if (!fingerprint) {
                onFailedToGetRegistrationInfo('Attempt to restore previous registration skipped due to no fingerprint');
                return deferred.promise;
            }

            httpWrapper.handlerGet(getRegistrationInfoUrl(fingerprint)).then(function(result) {
                var registrationInfo = (result || {}).data;
                if (!registrationInfo) {
                    onFailedToGetRegistrationInfo('No previous registration found');
                    return;
                }

                deferred.resolve(registrationInfo);
            }, function(error) {
                onFailedToGetRegistrationInfo(JSON.stringify(error));
            });
            return deferred.promise;
        }

        function processStaleFingerprint(deviceFingerprint) {
            var persistedFingerprint = coreConfigurationService.get(
                STORAGE_COLLECTION_KEY.SIGNS_REGISTRATION,
                STORAGE_KEY.PERSISTED_FINGERPRINT
            );
            if (persistedFingerprint === deviceFingerprint) {
                // No changes to persisted fingerprint
                return;
            }

            var generatedFingerprint = driverUtil.getGeneratedFingerprint();
            if (!generatedFingerprint.fingerprint) {
                // No generated fingerprint or it has been removed already; the persisted fingerprint is now deemed stale
                return persistedFingerprint;
            }

            driverUtil.removeGeneratedFingerprint(generatedFingerprint.key);
            return generatedFingerprint.fingerprint;
        }

        function cleanUpRegistrationInfo(deviceFingerprint, deviceId) {
            if (deviceFingerprint.type !== SYSTEM_DATA.FINGERPRINT_TYPE.HARDWARE) {
                // Clean up is only required when hardware (non-generated) fingerprint changes
                return $q.resolve();
            }

            var previousFingerprint = processStaleFingerprint(deviceFingerprint.fingerprint);
            if (!previousFingerprint) {
                // No old fingerprint found; no clean up required
                return $q.resolve();
            }

            var deferred = $q.defer();
            logService.info(LOG_KEY, 'Cleaning up stale registration information for ' + previousFingerprint);
            // Clean up registration info that was previously updated using an older fingerprint
            deleteRegistrationInfo(deviceId).then(function() {
                logService.info(LOG_KEY, 'Stale registration information for ' + previousFingerprint + ' has been removed');
                deferred.resolve();
            });
           return deferred.promise;
        }

        function getCurrentHash() {
            return coreConfigurationService.get(STORAGE_COLLECTION_KEY.SIGNS_REGISTRATION, STORAGE_KEY.PERSISTED_REGISTRATION_INFO_HASH);
        }

        function cancelUpdateRegistrationInfoRetryTimeout() {
            if (!updateRegistrationInfoAttempt.retryTimeout) {
                return;
            }

            $timeout.cancel(updateRegistrationInfoAttempt.retryTimeout);
            updateRegistrationInfoAttempt.retryTimeout = undefined;
        }

        function resetUpdateRegistrationInfoAttempt(deviceFingerprint, registrationInformation) {
            cancelUpdateRegistrationInfoRetryTimeout();
            updateRegistrationInfoAttempt = {
                retryTimeout: undefined,
                failureCount: 0,
                deviceFingerprint: deviceFingerprint,
                registrationInformation: registrationInformation,
                maxRetryCount: 6,
            };
            updateRegistrationInfoDeferred = undefined;
        }

        function doUpdateRegistrationInfo(deviceFingerprint, registrationInformation) {
            var hash = createHash(deviceFingerprint, registrationInformation);
            cleanUpRegistrationInfo(deviceFingerprint, registrationInformation.deviceId).then(function() {
                httpWrapper.handlerPut(getRegistrationInfoUrl(deviceFingerprint.fingerprint), registrationInformation).then(function(result) {
                    var registrationInformationResult = (result || {}).data;
                    if (!registrationInformationResult) {
                        var errorMessage = 'No previous registration found';
                        logService.info(LOG_KEY, UPDATE_REGISTRATION_INFO_ERROR_MESSAGE + errorMessage);
                        updateRegistrationInfoDeferred.reject(errorMessage);
                        return;
                    }

                    logService.info(LOG_KEY, 'Updated device registration information for restoration.');
                    coreConfigurationService.set(
                        STORAGE_COLLECTION_KEY.SIGNS_REGISTRATION,
                        STORAGE_KEY.PERSISTED_FINGERPRINT,
                        deviceFingerprint.fingerprint
                    );
                    coreConfigurationService.set(
                        STORAGE_COLLECTION_KEY.SIGNS_REGISTRATION,
                        STORAGE_KEY.PERSISTED_REGISTRATION_INFO_HASH,
                        hash
                    );
                    updateRegistrationInfoDeferred.resolve(registrationInformationResult);
                }, function(error) {
                    logService.error(LOG_KEY, UPDATE_REGISTRATION_INFO_ERROR_MESSAGE + JSON.stringify(error));
                    retryUpdateRegistrationInfo();
                });
            });
        }

        function retryUpdateRegistrationInfo() {
            if (updateRegistrationInfoAttempt.retryTimeout) {
                logService.debug(LOG_KEY, 'Update registration information will be re-attempted soon. Skipping repeated attempt...');
                return;
            }

            updateRegistrationInfoAttempt.failureCount++;
            if (updateRegistrationInfoAttempt.failureCount >= updateRegistrationInfoAttempt.maxRetryCount) {
                var maxRetryMessage = 'Update registration information failed after ' + updateRegistrationInfoAttempt.maxRetryCount + ' attempts.';
                logService.error(LOG_KEY, maxRetryMessage);
                updateRegistrationInfoDeferred.reject(maxRetryMessage);
                resetUpdateRegistrationInfoAttempt();
                return;
            }

            var backOffTime = dateTimeUtil.getBackoffTimeInSeconds(updateRegistrationInfoAttempt.failureCount, UPDATE_REGISTRATION_INFO_CONFIG);
            cancelUpdateRegistrationInfoRetryTimeout();
            var timeText = dateTimeUtil.toTimeText(backOffTime);
            logService.debug(LOG_KEY, 'Retrying update registration information in ' + timeText + '. Current failure count: ' + updateRegistrationInfoAttempt.failureCount);
            updateRegistrationInfoAttempt.retryTimeout = $timeout(function() {
                cancelUpdateRegistrationInfoRetryTimeout();
                doUpdateRegistrationInfo(updateRegistrationInfoAttempt.deviceFingerprint, updateRegistrationInfoAttempt.registrationInformation);
            }, dateTimeUtil.toMilliseconds(backOffTime));
        }

        function createHash(deviceFingerprint, registrationInformation) {
            var hashedRegistrationInfo = $angular.copy(registrationInformation);
            // To detect fingerprint changes
            hashedRegistrationInfo.fingerprint = deviceFingerprint;
            return dataUtil.toHash(hashedRegistrationInfo);
        }

        function updateRegistrationInfo(deviceFingerprint, registrationInformation) {
            if (!deviceFingerprint ||
                !deviceFingerprint.fingerprint ||
                !registrationInformation ||
                !registrationInformation.accountId ||
                !registrationInformation.apiUrl ||
                !registrationInformation.deviceId) {
                var errorMessage = 'Attempt to update registration information skipped due invalid information.';
                logService.info(LOG_KEY, UPDATE_REGISTRATION_INFO_ERROR_MESSAGE + errorMessage);
                return $q.reject(errorMessage);
            }

            var hash = createHash(deviceFingerprint, registrationInformation);
            if (hash === getCurrentHash()) {
                logService.debug(LOG_KEY, 'Registration information for restoration has been updated before. Skipping current attempt...');
                return $q.resolve();
            }

            resetUpdateRegistrationInfoAttempt(deviceFingerprint, registrationInformation);
            updateRegistrationInfoDeferred = $q.defer();
            doUpdateRegistrationInfo(deviceFingerprint, registrationInformation);
            return updateRegistrationInfoDeferred.promise;
        }

        function deleteRegistrationInfo(deviceId) {
            var deferred = $q.defer();
            var finalize = function(result) {
                if (!dataUtil.isString(result)) {
                    result = JSON.stringify(result);
                }

                /* Delete registration information request failure is non-blocking as
                   the device is not connected to signalR at this point but
                   the registration information would've been deleted from rendezvous already. */
                logService.info(LOG_KEY, 'Delete registration information request result: ' + result);
                deferred.resolve();
            };

            if (!deviceId) {
                finalize('Attempt to delete registration information skipped due to missing device ID.' );
                return deferred.promise;
            }

            httpWrapper.handlerDelete(getDeleteRegistrationInfoUrl(deviceId)).then(finalize, finalize);
            return deferred.promise;
        }

        return {
            start: start,
            stop: stop,
            isStarted: isStarted,
            subscribe: subscribe,
            unsubscribe: unsubscribe,
            notify: notify,
            claimCode: claimCode,
            getRegistrationInfo: getRegistrationInfo,
            updateRegistrationInfo: updateRegistrationInfo,
            deleteRegistrationInfo: deleteRegistrationInfo,
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');

    app.service('scriptingService',
        ["$q", "$timeout", "syncService", "logService", "coreFrameworkService", "rebootService", "applicationCommandService", "SYNC_EVENT", function(
            $q,
            $timeout,
            syncService,
            logService,
            coreFrameworkService,
            rebootService,
            applicationCommandService,
            SYNC_EVENT
        ) {
            var currentApp = null;
            var triggers = {};
            var targets = {};
            var started = false;
            var subscribers = [];
            var APPLICATION_UPDATE_EVENTS = [SYNC_EVENT.SCRIPT_SAVED, SYNC_EVENT.SCRIPT_READY];

            // Public interface
            return {
                start: start,
                retrieveAppProperty: retrieveAppProperty,
                retrieveAppProperties: retrieveAppProperties,
                appendAppProperties: appendAppProperties,
                registerTarget: registerTarget,
                resolvePropertyExpression: resolvePropertyExpression,
                unregisterTarget: unregisterTarget,
                raiseEvent: raiseEvent,
                retrieveWidgetApi: retrieveWidgetApi,
                executeAction: executeAction,
                retrieveWidgetApiByDatasourceId: retrieveWidgetApiByDatasourceId,
                subscribe: subscribe,
                unsubscribe: unsubscribe
            };

            function start() {
                if (started) {
                    return;
                }

                started = true;
                logService.info('appspace.signs.scripting', 'Scripting service initialised');
                $timeout(function() {
                    syncService.subscribe(onApplicationUpdated);
                    applicationCommandService.subscribe(onCommandReceived);
                    syncService.isAppAvailable().then(function(res) {
                        if (res) {
                            buildScriptingDB();
                        }
                    });
                });
            }

            function subscribe(callback) {
                if (!callback) {
                    return;
                }

                // Check is the callback already in subscriber collection
                if (getCallbackIndex(callback) !== -1) {
                    return;
                }

                subscribers.push(callback);
            }

            function unsubscribe(callback) {
                if (!callback) {
                    return;
                }

                // Find the callback and remove if its in the collection
                var index = getCallbackIndex(callback);
                if (index === -1) {
                    return;
                }

                subscribers.splice(index, 1);
            }

            function getCallbackIndex(callback) {
                return subscribers.indexOf(callback);
            }

            function onCommandReceived(event) {
                if (!event || event.type !== 'actionscript' || !event.actions) {
                    return;
                }

                var actions = event.actions;
                for (var i = 0; i < actions.length; i++) {
                    executeAction(actions[i]);
                }
            }

            function registerTarget(targetName, target) {
                targetName = targetName.toLowerCase();
                targets[targetName] = target;
            }

            function unregisterTarget(targetName) {
                targetName = targetName.toLowerCase();
                targets[targetName] = undefined;
            }

            function onApplicationUpdated(event) {
                if (APPLICATION_UPDATE_EVENTS.indexOf(event.event) == -1) {
                    return;
                }

                buildScriptingDB();
            }

            function setRebootService() {
                var rebootTime = retrieveAppProperty('system.rebootevents');
                if (!rebootTime) {
                    return;
                }

                rebootService.setRebootTime(rebootTime);
            }

            function appendAppProperties(properties) {
                syncService.appendAppProperties(properties);
            }

            function retrieveAppProperties() {
                return syncService.getCurrentAppProperties();
            }

            function retrieveAppProperty(key) {
                return syncService.getCurrentAppProperty(key);
            }

            function buildScriptingDB() {
                function onAppScript(res) {
                    currentApp = res.result.application;
                    // Build the triggers DB
                    buildTriggersDatabase();
                    setRebootService();
                }

                syncService.retrieveAppScriptObject().then(onAppScript);
            }

            function buildTriggersDatabase() {
                // Initlize the emoty trigger lookup
                triggers = {};
                // Iterate through the triggers in the application
                for (var i = 0; i < currentApp.triggers.length; i++) {
                    var t = currentApp.triggers[i];
                    var hash = t.sourceName + '_' + t.routedEvent;
                    hash = hash.toLowerCase();
                    if (triggers[hash] === undefined) {
                        triggers[hash] = [];
                    }

                    triggers[hash].push(t);
                }
            }

            // called when an event is fired
            function raiseEvent(source, event, params) {
                var promise = $q.defer();
                // delay execution to prevent call stack error for recursive functions

                $timeout(function() {
                    // notify subscribers of event
                    notifySubscribers(event, source, params);

                    // Check has anyone subscribed to this event
                    var hash = source + '_' + event;
                    hash = hash.toLowerCase();
                    var triggerCollection = triggers[hash];
                    if (triggerCollection === undefined) {
                        // No one is subscribed to this event
                        promise.resolve();
                        return;
                    }

                    // Make a clone of the trigger so we can resolve properties
                    triggerCollection = angular.copy(triggerCollection);

                    // Resolve all property expressions in the trigger
                    resolveTriggerPropertyExpressions(triggerCollection, params);

                    var actionExecuted = false;
                    // Process each action for this event
                    for (var i = 0; i < triggerCollection.length; i++) {
                        var t = triggerCollection[i];
                        // console.log(t);
                        // Check have the trigger conditions been met
                        if (!vaidateConditions(t)) {
                            continue;
                        }

                        // Execute all actions
                        for (var k = 0; k < t.actions.length; k++) {
                            var a = t.actions[k];
                            if (executeAction(a, params)) {
                                actionExecuted = true;
                            }
                        }
                    }

                    promise.resolve(actionExecuted);
                }, 0);

                return promise.promise;
            }

            function notifySubscribers(event, source, params) {
                if (!subscribers.length) {
                    return;
                }

                var subscriberEvent = {
                    event: event,
                    source: source,
                    params: params
                };
                for (var i = 0; i < subscribers.length; i++) {
                    subscribers[i](subscriberEvent);
                }
            }

            function retrieveWidgetApi(widgetId) {
                var deferred = $q.defer();
                var widgetElementId = 'element_' + widgetId;

                if (widgetId == 'application') {
                    widgetElementId = 'application';
                }

                var wApi = targets[widgetElementId];

                // Check did we find the widget api
                if (wApi == null || wApi == 'undefined') {
                    deferred.reject('No widget API found with ID: ' + widgetId);
                    return deferred.promise;
                }

                deferred.resolve(wApi);
                return deferred.promise;
            }

            function retrieveWidgetApiByDatasourceId(datasourceId) {
                var promise = $q.defer();
                var found = false;
                for (var key in targets) {
                    var widget = targets[key];
                    if (widget && widget.getDatasource) {
                        var datasource = widget.getDatasource();
                        if (datasource && datasource.result) {
                            var id = datasource.result.id;
                            id = id.toLowerCase().split('infomenu_').join('');
                            id = parseInt(id, 10);
                            if (id == datasourceId) {
                                found = true;
                                promise.resolve(widget);
                                break;
                            }
                        }
                    }
                }

                if (found == false) {
                    promise.reject('No widget API found with datasource ID:' + datasourceId);
                }

                return promise.promise;
            }

            function replaceExpression(source, expression, value) {
                var regEx = new RegExp(expression, 'ig');
                return source.replace(regEx, value);
            }

            // Resolve property expression
            function resolvePropertyExpression(property) {
                if (!property) {
                    return property;
                }

                var indexCurly = property.indexOf('${property.');
                var indexNormal = property.indexOf('$property.');
                if (indexCurly == -1 && indexNormal == -1) {
                    // No event expressions
                    return property;
                }

                if (indexCurly != -1) {
                    // Reg EX to find any instances of ${property.abc}
                    var re = /\${property\.([\w\.]*)}/gi;
                    var props = property.match(re);
                    if (props) {
                        for (var i = 0; i < props.length; i++) {
                            // Remove the ${property.xxxx} from the prop
                            var propInstace = props[i];
                            var propName = propInstace.toLowerCase().replace('${property.', '').replace('}', '');
                            // Check the app properties look up as its already in lowercase
                            var val = retrieveAppProperty(propName);
                            if (val) {
                                // Escape the $ by prefixing the instance string with a \
                                property = replaceExpression(property, '\\${property.' + propName + '}', val);
                            }
                        }
                    }
                }

                if (indexNormal != -1) {
                    // Reg EX to find any instances of $property.abc
                    var re = /\$property\.([\w\.]*)/gi;
                    var props = property.match(re);
                    if (props) {
                        for (var i = 0; i < props.length; i++) {
                            // Remove the $property. from the prop
                            var propInstace = props[i];
                            var propName = propInstace.toLowerCase().replace('$property.', '');
                            // Check the app properties look up as its already in lowercase
                            var val = retrieveAppProperty(propName);
                            if (val) {
                                // Escape the $ by prefixing the instance string with a \
                                property = replaceExpression(property, '\\$property.' + propName, val);
                            }
                        }
                    }
                }

                // console.log(property);
                return property;
            }

            // Resolve event expression
            function resolveEventExpression(property, params) {
                if (!property) {
                    return property;
                }

                if (!params) {
                    return property;
                }

                var indexCurly = property.indexOf('${event.');
                var indexNormal = property.indexOf('$event.');
                if (indexCurly == -1 && indexNormal == -1) {
                    // No event expressions
                    return property;
                }

                if (indexCurly != -1) {
                    // Reg EX to find any instances of $event.abc
                    var re = /\${event\.([\w\.]*)}/gi;
                    var props = property.match(re);
                    if (props) {
                        for (var i = 0; i < props.length; i++) {
                            // Remove the $event. from the prop
                            var propInstace = props[i];
                            var propName = propInstace.toLowerCase().replace('${event.', '').replace('}', '');
                            for (var key in params) {
                                if (key.toLowerCase() == propName) {
                                    property = replaceExpression(property, '\\${event.' + propName + '}', params[key]);
                                    continue;
                                }
                            }
                        }
                    }
                }

                if (indexNormal != -1) {
                    // Reg EX to find any instances of $event.abc
                    var re = /\$event\.([\w\.]*)/gi;
                    var props = property.match(re);
                    if (props) {
                        for (var i = 0; i < props.length; i++) {
                            // Remove the $event. from the prop
                            var propInstace = props[i];
                            var propName = propInstace.toLowerCase().replace('$event.', '');
                            for (var key in params) {
                                if (key.toLowerCase() == propName) {
                                    property = replaceExpression(property, '\\$event.' + propName, params[key]);
                                    continue;
                                }
                            }
                        }
                    }
                }

                return property;
            }

            // Resolve trigger property expressions
            function resolveTriggerPropertyExpressions(triggerCollection, params) {
                // Enumerate each trigger in the collection
                for (var i = 0; i < triggerCollection.length; i++) {
                    var t = triggerCollection[i];
                    if (!t.conditions || !t.conditions.conditions) {
                        continue;
                    }

                    for (var j = 0; j < t.conditions.conditions.length; j++) {
                        var condition = t.conditions.conditions[j];
                        // Check each expression whether it has a property evaluation
                        for (var k = 0; k < condition.expressions.length; k++) {
                            var expression = condition.expressions[k];
                            // Resolve event expression
                            expression.arg1 = resolveEventExpression(expression.arg1, params);
                            expression.arg2 = resolveEventExpression(expression.arg2, params);
                            // Resolve property expression
                            expression.arg1 = resolvePropertyExpression(expression.arg1);
                            expression.arg2 = resolvePropertyExpression(expression.arg2);
                        }
                    }
                }
            }

            // Validate an expression
            function vaidateExpression(trigger, expression) {
                // console.log(expression);
                switch (expression.operator) {
                    case '=':
                        return (expression.arg1 == expression.arg2);
                    case '!=':
                        return (expression.arg1 != expression.arg2);
                    case '>':
                        return (parseFloat(expression.arg1) > parseFloat(expression.arg2));
                    case '>=':
                        return (parseFloat(expression.arg1) >= parseFloat(expression.arg2));
                    case '<':
                        return (parseFloat(expression.arg1) < parseFloat(expression.arg2));
                    case '<=':
                        return (parseFloat(expression.arg1) <= parseFloat(expression.arg2));
                    case 'contains':
                        return (expression.arg1.toLowerCase().indexOf(
                            expression.arg2.toLowerCase()) != -1
                        );
                }

                return true;
            }

            // Confirms all conditions are met for this trigger
            function vaidateConditions(trigger) {
                var passAll = trigger.conditions.operator == 'or' ? false : true;
                var passedExpressions = 0;
                var expressionCount = 0;

                if (!trigger.conditions || !trigger.conditions.conditions) {
                    return true;
                }

                for (var i = 0; i < trigger.conditions.conditions.length; i++) {
                    var condition = trigger.conditions.conditions[i];
                    expressionCount += condition.expressions.length;
                    // Check each expression whether it passed
                    for (var k = 0; k < condition.expressions.length; k++) {
                        var expression = condition.expressions[k];
                        // check for invalid property
                        if (!expression.operator && expression.operation) {
                            expression.operator = expression.operation;
                        }

                        // Validate
                        if (vaidateExpression(trigger, expression)) {
                            passedExpressions++;
                        }
                    }
                }

                // Validate condition based on the group condition
                if (passAll && expressionCount == passedExpressions) {
                    // console.log("Validate All Conditions passed");
                    return true;
                } else if (passAll) {
                    // console.log("Validate All Conditions failed");
                    return false;
                } else if (passedExpressions > 0) {
                    // console.log("Validate Or Conditions passed:");
                    return true;
                } else if (expressionCount == 0) {
                    // console.log("Validate Zero Conditions passed");
                    return true;
                }

                // console.log("Validate Or Conditions failed");
                return false;
            }

            function executeAction(action, params) {
                // Look for the target
                var targetName = action.targetName.toLowerCase();
                var target = targets[targetName];

                if (!target) {
                    logService.warn('appspace.signs.scripting', 'An invalid target was specified: ' + targetName);
                    return false;
                }

                if (!target.executeAction) {
                    logService.warn('appspace.signs.scripting', 'Target does not implement executeAction: ' + targetName);
                    return false;
                }

                for (var prop in action) {
                    action[prop] = resolveEventExpression(action[prop], params);
                    action[prop] = resolvePropertyExpression(action[prop]);
                }

                // Check was the action executed
                if (!target.executeAction(action)) {
                    logService.warn('appspace.signs.scripting', 'Target does not implement the action: ' + targetName + '->' + action.name);
                }

                return true;
            }
        }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');

    app.service('socialAnimationService', ["$interval", "$q", "domUtil", "dataUtil", "contentTileTemplateHelper", function($interval, $q, domUtil, dataUtil, contentTileTemplateHelper) {
        var QUEUE_PROCESS_DURATION = 500;
        var REACTION_LEFT_POSITION = ['8vmin', '14vmin', '20vmin'];
        var REACTION_USER_STOP_POSITION = ['20vmin', '15vmin', '25vmin'];
        var queue = [];
        var activeReactions = [];
        var renderIntervalPromise;
        var currentReactionLeftPositionIndex = 0;
        var currentUserStopPositionIndex = 0;
        function addReaction(animationItem) {
            var finalize = function(item) {
                queue.push(item);

                if (renderIntervalPromise) {
                    return;
                }

                startRenderInterval();
            };

            if (!animationItem.avatarIcon) {
                finalize(animationItem);
                return $q.resolve();
            }

            var defer = $q.defer();
            contentTileTemplateHelper.isImageValid(animationItem.avatarIcon).then(function(item) {
                item.avatarIcon = 'url(\'' + item.avatarIcon + '\')';
                finalize(item);
                defer.resolve();
            }.bind(undefined, animationItem), function(item) {
                item.avatarIcon = undefined;
                finalize(item);
                defer.resolve();
            }.bind(undefined, animationItem));

            return defer.promise;
        }

        function stopRenderInterval() {
            if (!renderIntervalPromise) {
                return;
            }

            $interval.cancel(renderIntervalPromise);
            renderIntervalPromise= undefined;
        }

        function startRenderInterval() {
            stopRenderInterval();

            renderIntervalPromise = $interval(processQueue, QUEUE_PROCESS_DURATION);
        }

        function processQueue() {
            // no pending & running animations, reset everything
            if (!queue.length && !activeReactions.length) {
                clearReactions();
                return;
            }

            // render initial reaction
            if (queue.length > 0) {
                var reaction = queue.shift();
                renderReaction(reaction);
            }

            // process active reactions
            var now = Date.now();
            for (var i = 0; i < activeReactions.length; i++) {
                var reactionRenderItem = activeReactions[i];
                if ((dataUtil.isNullOrUndefined(reactionRenderItem.animationEndAt) || now >= reactionRenderItem.animationEndAt)
                    && reactionRenderItem.animations.length > 0) {
                    var animationItem = reactionRenderItem.animations.shift();
                    reactionRenderItem.containerElement.className = reactionRenderItem.baseCssClass + animationItem.animation;
                    reactionRenderItem.animationEndAt = now + animationItem.duration;

                    if (animationItem.randomizeStopPosition) {
                        reactionRenderItem.containerElement.style.bottom = REACTION_USER_STOP_POSITION[currentUserStopPositionIndex++ % REACTION_USER_STOP_POSITION.length];
                    }

                    if (animationItem.hideAvatar) {
                        reactionRenderItem.avatarElement.className += ' animated flipOutY';
                    }

                    if (animationItem.showReaction) {
                        reactionRenderItem.reactionElement.style.visibility = 'visible';
                    }

                    continue;
                }

                if (now < reactionRenderItem.animationEndAt) {
                    continue;
                }

                clearReactionItem(reactionRenderItem);
                reactionRenderItem = undefined;
                activeReactions.splice(i, 1);
                i--;
            }
        }

        function renderReaction(reactionItem) {
            var containerElement = domUtil.createElement('<div></div>');

            containerElement.style.left = REACTION_LEFT_POSITION[currentReactionLeftPositionIndex++ % REACTION_LEFT_POSITION.length];
            var cssClasses = 'reaction-animation animated';
            reactionItem.baseCssClass = cssClasses;
            containerElement.className = cssClasses;
            reactionItem.containerElement = containerElement;

            // reaction element
            var reactionElement = domUtil.createElement('<div></div>');
            reactionElement.className = 'reaction-emoji';
            reactionElement.style.backgroundImage = 'url(\'' + reactionItem.reactionIcon + '\')';
            containerElement.appendChild(reactionElement);
            reactionItem.reactionElement = reactionElement;

            // avatar element
            var avatarElement = domUtil.createElement('<div></div>');
            avatarElement.className = 'reaction-avatar';
            if (reactionItem.avatarIcon) {
                avatarElement.style.backgroundImage = reactionItem.avatarIcon;
            } else {
                avatarElement.className += ' reaction-avatar-abbreviation';
                avatarElement.innerHTML = reactionItem.avatarAbbreviation;
            }

            containerElement.appendChild(avatarElement);
            reactionItem.avatarElement = avatarElement;

            domUtil.getElement(reactionItem.containerDomId).append(containerElement);
            activeReactions.push(reactionItem);
        }

        function clearReactions() {
            stopRenderInterval();
            queue = [];
            currentReactionLeftPositionIndex = 0;
            currentUserStopPositionIndex = 0;
            for (var i = 0; i < activeReactions.length; i++) {
                var reactionItem = activeReactions[i];
                clearReactionItem(reactionItem);
                reactionItem = undefined;
            }

            activeReactions = [];
        }

        function clearReactionItem(item) {
            item.avatarElement.remove();
            item.avatarElement = undefined;
            item.reactionElement.remove();
            item.reactionElement = undefined;
            item.containerElement.remove();
            item.containerElement = undefined;
        }

        return {
            addReaction: addReaction,
            clearReactions: clearReactions
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');

    app.factory('TimestampPolling', ["$q", "$timeout", "SYNC_EVENT", "STORAGE_KEY", "STORAGE_COLLECTION_KEY", "dataUtil", "logService", "sessionService", "signsAPIService", "deviceStateService", "coreConfigurationService", "deviceRegistrationService", function(
        $q,
        $timeout,
        SYNC_EVENT,
        STORAGE_KEY,
        STORAGE_COLLECTION_KEY,
        dataUtil,
        logService,
        sessionService,
        signsAPIService,
        deviceStateService,
        coreConfigurationService,
        deviceRegistrationService
    ) {
        var currentConfiguration = {
            logKey: '',
            interval: undefined,
        };

        var currentState = {
            pollingTimer: undefined,
            failCount: 0
        };

        function TimestampPolling(configuration) {
            setConfiguration(configuration);
        }

        function resetToDefaultState() {
            currentConfiguration = {
                logKey: '',
                interval: undefined,
            };

            currentState = {
                pollingTimer: undefined,
                failCount: 0
            };
        }

        function retrieveAppIdFromQueryString() {
            // To handle advanced signs in channel browsing mode
            return (sessionService.getUrlParameters() || {}).appid;
        }

        function retrieveAppIdFromInjection() {
            return coreConfigurationService.get(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.APP_ID);
        }

        function getTimestamp() {
            var deferred = $q.defer();
            var sessionId = deviceStateService.getSessionId();
            var configuration = {
                sessionid: sessionId
            };
            var appId = retrieveAppIdFromInjection() || retrieveAppIdFromQueryString();
            var retrieveTimestamp;
            if (appId) {
                configuration.appid = appId;
                retrieveTimestamp = signsAPIService.retrieveApplicationTimestamp;
            } else {
                configuration.deviceguid = deviceRegistrationService.getPlayerId();
                retrieveTimestamp = signsAPIService.retrievePlayerTimestamp;
            }

            retrieveTimestamp(configuration).then(function(result) {
                currentState.failCount = 0;
                deferred.resolve(result);
            }, function(error) {
                currentState.failCount++;
                deferred.reject(error);
            });

            return deferred.promise;
        }

        function notify(event, data) {
            if (!currentConfiguration.onEvent) {
                return;
            }

            currentConfiguration.onEvent(event, data);
        }

        function retrieveCurrentTimestamp(shouldNotify) {
            var deferred = $q.defer();
            var finalize = function(event, data, isResolved) {
                if (shouldNotify) {
                    notify(event, data);
                }

                deferred[isResolved ? 'resolve' : 'reject']({
                    event: event,
                    data: data,
                });
            };

            getTimestamp().then(function(timestamp) {
                finalize(SYNC_EVENT.TIMESTAMP_RETRIEVED, timestamp, true);
            }, function(error) {
                finalize(SYNC_EVENT.ERROR_RETRIEVING_TIMESTAMP, error);
            });
            return deferred.promise;
        }

        function poll() {
            logService.debug(currentConfiguration.logKey, 'Polling for timestamp...');
            retrieveCurrentTimestamp(true).then($angular.noop, $angular.noop).finally(startPollingTimer);
        }

        function setConfiguration(configuration) {
            if (!configuration || !dataUtil.isNumber(configuration.interval) || !configuration.logKey) {
                return false;
            }

            currentConfiguration = $angular.copy(configuration);
            return true;
        }

        function updateConfiguration(newConfiguration) {
            if (!setConfiguration(newConfiguration)) {
                logService.error(newConfiguration.logKey, 'Invalid timestamp polling configuration');
                return;
            }

            startPollingTimer();
        }

        function startPollingTimer() {
            cancelPollingTimer();
            if (dataUtil.isNullOrUndefined(currentConfiguration.interval)) {
                logService.info(currentConfiguration.logKey, 'Invalid timestamp polling interval');
                return;
            }

            if (currentConfiguration.interval === 0) {
                logService.info(currentConfiguration.logKey, 'Timestamp polling disabled');
                return;
            }

            logService.debug(currentConfiguration.logKey, 'Next timestamp polling in ' + (currentConfiguration.interval / 1000) + ' seconds');
            currentState.pollingTimer = $timeout(poll, currentConfiguration.interval);
        }

        function cancelPollingTimer() {
            if (!currentState.pollingTimer) {
                return;
            }

            $timeout.cancel(currentState.pollingTimer);
            currentState.pollingTimer = undefined;
        }

        function start() {
            startPollingTimer();
        }

        function stop() {
            logService.info(currentConfiguration.logKey, 'Stopping timestamp polling...');
            cancelPollingTimer();
            resetToDefaultState();
        }

        function getConfiguration() {
            return currentConfiguration;
        }

        function getCurrentState() {
            return currentState;
        }

        TimestampPolling.prototype = {
            start: start,
            stop: stop,
            retrieveCurrentTimestamp: retrieveCurrentTimestamp,
            updateConfiguration: updateConfiguration,
            getConfiguration: getConfiguration,
            getCurrentState: getCurrentState,
        };
        return (TimestampPolling);
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var app = $angular.module('signsHTML5');
    app.service('configurationService', ["$q", "dataUtil", "syncService", "coreFrameworkService", "SYNC_EVENT", "Observable", function(
        $q,
        dataUtil,
        syncService,
        coreFrameworkService,
        SYNC_EVENT,
        Observable) {
        var checkCurrentProperties;
        var currentProperties;
        var observable = new Observable();
        var isReadyDefer = $q.defer();
        var PROMISE_RESOLVED_STATE = 1;

        function init() {
            syncService.subscribe(onApplicationUpdated);
        }

        function isReadyPromiseResolved() {
            return isReadyDefer.promise.$$state && isReadyDefer.promise.$$state.status === PROMISE_RESOLVED_STATE;
        }

        function resolveReadyPromise() {
            if (isReadyPromiseResolved()) {
                return;
            }

            isReadyDefer.resolve();
        }

        function onApplicationUpdated(event) {
            var updatedEvent = (event || {}).event;
            switch (updatedEvent) {
                case SYNC_EVENT.SCRIPT_READY:
                    resolveReadyPromise();
                    break;
                case SYNC_EVENT.SCRIPT_SAVED:
                    resolveReadyPromise();
                    var newProperties = syncService.getCurrentAppProperties();
                    var checkNewProperties = JSON.stringify(newProperties);
                    // don't do anything if the new properties is the same as the 1 that we already have
                    if (checkCurrentProperties == checkNewProperties) {
                        return;
                    }

                    checkCurrentProperties = checkNewProperties;
                    currentProperties = newProperties;

                    observable.notifyObservers(currentProperties);
                    break;
            }
        }

        function subscribe(callback) {
            observable.registerObserver(callback);
        }

        function unsubscribe(callback) {
            observable.removeObserver(callback);
        }

        function getProperty(key) {
            return syncService.getAppProperties(key);
        }

        function getCurrentProperty(key) {
            return syncService.getCurrentAppProperty(key);
        }

        function getCurrentProperties() {
            return syncService.getCurrentAppProperties();
        }

        function isReady() {
            return isReadyDefer.promise;
        }

        function isFeatureFlagEnabled(featureFlag) {
            return dataUtil.isValueTrue(getCurrentProperty(featureFlag));
        }

        coreFrameworkService.isReady().then(init);

        return {
            isReady: isReady,
            isFeatureFlagEnabled: isFeatureFlagEnabled,
            subscribe: subscribe,
            unsubscribe: unsubscribe,
            getProperty: getProperty,
            getCurrentProperty: getCurrentProperty,
            getCurrentProperties: getCurrentProperties,
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var app = $angular.module('signsHTML5');
    app.service('deviceStateService', ["$q", "$window", "syncService", "admService", "pushUpdateService", "jobSchedulerService", "alertsService", "deviceHealthService", "metricService", "logService", "storageLocalStorageService", "halPlatformService", "networkContentCacheService", "pushNotificationClientService", "SYNC_EVENT", "SERVICES", "STORAGE_KEY", "FEATURE_CODE", "STORAGE_COLLECTION_KEY", "embedProfileService", "deviceAlertService", "configurationService", "dataUtil", "NetworkUtil", "DateTimeUtil", "deviceTokenService", "coreConfigurationService", function($q,
        $window,
        syncService,
        admService,
        pushUpdateService,
        jobSchedulerService,
        alertsService,
        deviceHealthService,
        metricService,
        logService,
        storageLocalStorageService,
        halPlatformService,
        networkContentCacheService,
        pushNotificationClientService,
        SYNC_EVENT,
        SERVICES,
        STORAGE_KEY,
        FEATURE_CODE,
        STORAGE_COLLECTION_KEY,
        embedProfileService,
        deviceAlertService,
        configurationService,
        dataUtil,
        NetworkUtil,
        DateTimeUtil,
        deviceTokenService,
        coreConfigurationService) {
        var isSuspended = true;
        var isStopped = false;
        var subscribers = [];
        var currentState;
        var LOG_KEY = 'appspace.device.state';
        var serviceKeys = {
            'services.enable.health': SERVICES.HEALTH,
            'services.enable.alerts': SERVICES.ALERTS,
            'services.enable.pnclient': SERVICES.PUSH_NOTIFICATION_CLIENT,
            'services.enable.pushupdates': SERVICES.PUSH_UPDATE,
        };
        var ALERT_LICENSE_KEY = 'feature.device.services.alert';
        var DEVICE_TASK_LICENSE_KEY = 'feature.device.services.adm';
        var ANALYTICS_LICENSE_KEY = 'feature.device.services.analytics';
        var HEALTH_LICENSE_KEY = 'feature.device.services.health';
        var BROADCAST_LICENSE_KEY = 'feature.broadcast';
        var VALID_LICENSE_KEYS = [
            ALERT_LICENSE_KEY,
            DEVICE_TASK_LICENSE_KEY,
            ANALYTICS_LICENSE_KEY,
            HEALTH_LICENSE_KEY,
            BROADCAST_LICENSE_KEY,
        ];
        var FEATURE_FLAG = {
            PUSH_UPDATES: 'feature.devices.pushupdates'
        };
        var METHOD = {
            STOP: 'stop',
            START: 'start',
            RESTART: 'restart'
        };
        var SESSION_ID = $window.uuid.v1();
        var NOT_FOUND_INDEX = -1;
        var dateTimeUtil = new DateTimeUtil();
        var serviceLicenses;
        var nonCriticalServices = {};
        var nonCriticalServicesDefinitions = {};
        nonCriticalServicesDefinitions[SERVICES.ADM] = {
            start: admService.start,
            stop: admService.stop,
            key: DEVICE_TASK_LICENSE_KEY,
            name: SERVICES.ADM,
            skippedEvents: [SYNC_EVENT.ERROR_RETRIEVING_TIMESTAMP],
        };
        nonCriticalServicesDefinitions[SERVICES.JOB_SCHEDULER] = {
            start: jobSchedulerService.start,
            stop: jobSchedulerService.stop,
            key: DEVICE_TASK_LICENSE_KEY,
            name: SERVICES.JOB_SCHEDULER,
            skippedEvents: [SYNC_EVENT.ERROR_RETRIEVING_TIMESTAMP],
        };
        nonCriticalServicesDefinitions[SERVICES.ALERTS] = {
            start: alertsService.start,
            stop: alertsService.stop,
            key: ALERT_LICENSE_KEY,
            name: SERVICES.ALERTS,
        };
        nonCriticalServicesDefinitions[SERVICES.HEALTH] = {
            start: deviceHealthService.start,
            stop: deviceHealthService.stop,
            key: HEALTH_LICENSE_KEY,
            name: SERVICES.HEALTH,
            skippedEvents: [SYNC_EVENT.ERROR_RETRIEVING_TIMESTAMP],
        };
        nonCriticalServicesDefinitions[SERVICES.PLAYOUT_METRIC] = {
            start: metricService.resume,
            stop: metricService.suspend,
            key: ANALYTICS_LICENSE_KEY,
            name: SERVICES.PLAYOUT_METRIC,
        };
        nonCriticalServicesDefinitions[SERVICES.PUSH_NOTIFICATION_CLIENT] = {
            restart: pushNotificationClientService.restart,
            start: pushNotificationClientService.start,
            stop: pushNotificationClientService.stop,
            key: BROADCAST_LICENSE_KEY,
            name: SERVICES.PUSH_NOTIFICATION_CLIENT,
            skippedEvents: [SYNC_EVENT.ERROR_RETRIEVING_TIMESTAMP]
        };
        nonCriticalServicesDefinitions[SERVICES.PUSH_UPDATE] = {
            restart: pushUpdateService.start,
            start: pushUpdateService.start,
            stop: pushUpdateService.stop,
            featureFlag: FEATURE_FLAG.PUSH_UPDATES,
            name: SERVICES.PUSH_UPDATE,
            skippedEvents: [SYNC_EVENT.ERROR_RETRIEVING_TIMESTAMP]
        };

        function start() {
            syncService.subscribe(onSyncServiceUpdate);

            Object.keys(serviceKeys).forEach(function(key) {
                if (halPlatformService.getFeatureSupport(key) == 'yes') {
                    var service = serviceKeys[key];
                    nonCriticalServices[service] = nonCriticalServicesDefinitions[service];
                    logService.trace(LOG_KEY, 'Non-critical ' + service + ' is enabled');
                }
            });

            if (!dataUtil.isObjectEmpty(nonCriticalServices)) {
                return;
            }

            // if embed profile is ondemand or driver disable all non-critical services, don't instantiate any non-critical services
            if (embedProfileService.shouldDisableNonCriticalServices() ||
                halPlatformService.getFeatureSupport('services.disable.noncritical') == 'yes') {
                logService.warn(LOG_KEY, 'Disable all non-critical services');
                return;
            }

            nonCriticalServices = nonCriticalServicesDefinitions;
            logService.trace(LOG_KEY, 'All non-critical services are registered');
        }

        function stop() {
            syncService.unsubscribe(onSyncServiceUpdate);
        }

        function onSyncServiceUpdate(event) {
            switch (event.event) {
                case SYNC_EVENT.NO_DEVICE_FOUND:
                    stopAllServices(event.event);
                    break;
                case SYNC_EVENT.NO_APP:
                case SYNC_EVENT.TIMESTAMP_RETRIEVED:
                    resumeNonCriticalServices(event.event);
                    break;
                case SYNC_EVENT.ERROR_RETRIEVING_TIMESTAMP:
                case SYNC_EVENT.NO_PLAYER_ID:
                case SYNC_EVENT.SERVICE_STOPPED:
                    stopNonCriticalServices(event.event);
                    break;
                case SYNC_EVENT.SCRIPT_SAVED:
                case SYNC_EVENT.SCRIPT_READY:
                    processDeviceLicensing();
                    restartNonCriticalServices(event.event);
                    break;
            }
        }

        function getServiceActionMessage(action, service) {
            var licenseTerm = service.featureFlag ? 'feature flag' : 'license';
            var licenseKey = service.featureFlag ? service.featureFlag : service.key;
            return 'Service ' + service.name + ' ' + action + ' due to ' + licenseTerm + ': ' + licenseKey;
        }

        function processDeviceLicensing() {
            getDeviceLicenses().then(function(licenses) {
                serviceLicenses = licenses;
                logService.debug(LOG_KEY, 'Retrieved service license information:' + JSON.stringify(serviceLicenses));
                // don't process if we already suspended
                if (isSuspended) {
                    logService.warn(LOG_KEY, 'Skipping device licensing processing in suspended state');
                    return;
                }

                // refresh service
                for (var serviceName in nonCriticalServices) {
                    var service = nonCriticalServices[serviceName];
                    // if license is valid, continue OR
                    // if service already stopped, don't do anything
                    if (processValidLicense(service) || service.currentMethod === METHOD.STOP) {
                        continue;
                    }

                    logService.info(LOG_KEY, getServiceActionMessage('stopped', service));
                    processServiceMethod(service, METHOD.STOP);
                }
            });
        }

        function validateService(service) {
            return service.featureFlag ?
                isFeatureFlagValid(service.featureFlag) :
                isServiceLicenseValid(service);
        }

        function processValidLicense(service) {
            var isServiceValid = validateService(service);
            if (!isServiceValid) {
                return false;
            }

            // if service current state is not start, start the service
            if (service.currentMethod !== METHOD.START) {
                logService.info(LOG_KEY, getServiceActionMessage('started', service));
                processServiceMethod(service, METHOD.START);
            }

            return true;
        }

        function processServiceMethod(service, method, event) {
            service.currentMethodCallback = undefined;
            // Skip if event is not applicable
            var skippedEvents = service.skippedEvents || [];
            if (dataUtil.includesValue(event, skippedEvents)) {
                return;
            }

            service.currentMethod = method;
            var methodCallback = service[method];
            if (!methodCallback) {
                return;
            }

            methodCallback();
            service.currentMethodCallback = methodCallback;
        }

        function isServiceLicenseValid(service) {
            if (!serviceLicenses) {
                return true;
            }

            var result = serviceLicenses[service.key];
            // license considered valid if
            // we don't have license information or the service license is true;
            return dataUtil.isNullOrUndefined(result) || result;
        }

        function isFeatureFlagValid(featureFlag) {
            return dataUtil.isValueTrue(configurationService.getCurrentProperty(featureFlag));
        }

        function getDeviceLicenses() {
            var deferred = $q.defer();
            syncService.retrieveDeviceLicensing().then(function(licenses) {
                var result = {};
                for (var i = 0; i < licenses.length; i++) {
                    var license = licenses[i];
                    if (!dataUtil.includesValue(license.key, VALID_LICENSE_KEYS)) {
                        continue;
                    }

                    result[license.key] = dataUtil.parseBoolean(license.value);
                }

                deferred.resolve(result);
            }, function() {
                deferred.resolve();
            });

            return deferred.promise;
        }

        function resumeNonCriticalServices(event) {
            processNonCriticalServices(METHOD.START, event, isSuspended, 'resumed', 'resume', true);
        }

        function restartNonCriticalServices(event) {
            processNonCriticalServices(METHOD.RESTART, event, isSuspended, 'restarted', 'restart', true);
        }

        function resumeCriticalServices() {
            if (!isStopped) {
                logService.trace(LOG_KEY, 'Critical services already ran');
                return;
            }

            logService.debug(LOG_KEY, 'Running critical services (Sync).');
            isStopped = false;
            syncService.startSync();
            deviceAlertService.start();
        }

        function startAllServices() {
            resumeCriticalServices();
            resumeNonCriticalServices();
        }

        function stopAllServices(event) {
            stopCriticalServices(event);
            stopNonCriticalServices(event);
        }

        function stopCriticalServices(event) {
            if (isStopped) {
                logService.trace(LOG_KEY, 'Critical services already stopped (Received ' + event + ' event)');
                return;
            }

            logService.warn(LOG_KEY, 'Stopping critical services (Sync). Reason:' + event);
            deviceAlertService.stop();
            // stop syncService
            syncService.stopSync();
            isStopped = true;
        }

        function stopNonCriticalServices(event) {
            processNonCriticalServices(METHOD.STOP, event, !isSuspended, 'stopped', METHOD.STOP);
        }

        function processNonCriticalServices(methodName, eventName, canProcess, successMessage, actionMessage, enforceLicense) {
            if (!canProcess) {
                logService.trace(LOG_KEY, 'Non-critical services already ' + successMessage + ' (Received ' + eventName + ' event)');
                return;
            }

            if (Object.keys(nonCriticalServices).length == 0) {
                logService.info(LOG_KEY, 'No non-critical services to ' + actionMessage);
                return;
            }

            var serviceNames = [];
            for (var serviceName in nonCriticalServices) {
                var service = nonCriticalServices[serviceName];
                var isServiceValid = validateService(service);
                if (service.key && !enforceLicense) {
                    // Service is valid as long as license is not enforced
                    isServiceValid = true;
                }

                if (!isServiceValid) {
                    logService.info(LOG_KEY, getServiceActionMessage('ignored', service));
                    continue;
                }

                processServiceMethod(service, methodName, eventName);
                if (!service.currentMethodCallback) {
                    continue;
                }

                serviceNames.push(serviceName);
            }

            logService.info(LOG_KEY, 'Services ' + successMessage + ' (' + serviceNames.join(',') + ') (Received ' + eventName + ' event)');
            isSuspended = !isSuspended;
        }

        function resetContent() {
            clearCachedScripts();
            storageLocalStorageService.content.removeAll();
            var promise = $q.defer();
            var logSuccess = function() {
                logService.info(LOG_KEY, 'Content reset on device');
            };

            var onClearCompleted = function() {
                logSuccess();
                promise.resolve();
            };

            var onClearFailed = function() {
                logService.warn(LOG_KEY, 'Failed to reset content');
                promise.resolve();
            };

            var logTmpClearResult = function(message, logLevel) {
                logService[logLevel](LOG_KEY, message);
            };

            var onTmpClearSuccess = function() {
                logTmpClearResult('Tmp files successfully cleared', 'debug');
                clearContent();
            };

            var onTmpClearFailed = function(error) {
                logTmpClearResult('Failed to clear tmp files due to: ' + JSON.stringify(error), 'error');
                clearContent();
            };

            var clearContent = function() {
                var clearCachePromise = networkContentCacheService.clearRepository('signs.content');
                if (!clearCachePromise) {
                    logSuccess();
                    promise.resolve();
                    return;
                }

                clearCachePromise.then(onClearCompleted, onClearFailed);
            };

            networkContentCacheService.clearTmp().then(onTmpClearSuccess, onTmpClearFailed);
            deviceTokenService.clearContentTokens();
            return promise.promise;
        }

        function clearCachedScripts() {
            storageLocalStorageService.removeCollection('device.sync.script.blocks');
            storageLocalStorageService.removeCollection('device.sync');
        }

        function isRegistered() {
            return Boolean(coreConfigurationService.get(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.DEVICE_ID));
        }

        function isPlaybackReady() {
            if (!isRegistered() || !halPlatformService.getFeatureSupport(FEATURE_CODE.REQUIRES_CONNECTION_FOR_PLAYBACK)) {
                return $q.resolve();
            }

            // Wait for Appspace online if the device is registered and requires connection for playback
            var deferred = $q.defer();
            logService.debug(LOG_KEY, 'Establishing connection with Appspace as the device requires connection for playback...');
            NetworkUtil.waitForAppspaceOnline().then(deferred.resolve, function() {
                logService.warn(LOG_KEY, 'Failed to establish connection with Appspace.');
                deferred.resolve();
            });
            return deferred.promise;
        }

        function isServiceAvailable(serviceKey) {
            return Boolean(nonCriticalServices && nonCriticalServices[serviceKey]);
        }

        function getUpTime() {
            var startUpTime = coreConfigurationService.get(STORAGE_COLLECTION_KEY.DEVICE_INFO, STORAGE_KEY.START_UP_TIME);
            return !startUpTime || !dateTimeUtil.isValidDate(startUpTime) ?
                0 : dateTimeUtil.getSecondsDifference(startUpTime, dateTimeUtil.getCurrentTime());
        }

        function getSessionId() {
            return SESSION_ID;
        }

        function subscribe(callback) {
            if (!callback || subscribers.indexOf(callback) !== NOT_FOUND_INDEX) {
                return;
            }

            subscribers.push(callback);
        }

        function unsubscribe(callback) {
            var subscriberIndex = subscribers.indexOf(callback);
            if (subscriberIndex === NOT_FOUND_INDEX) {
                return;
            }

            subscribers.splice(subscriberIndex, 1);
        }

        function notifySubscribers() {
            for (var i = 0; i < subscribers.length; i++) {
                subscribers[i](currentState);
            }
        }

        function setCurrentState(state) {
            if (!state || !state.code) {
                if (currentState !== state) {
                    if (currentState) {
                        // Log only when transitioning from a different state
                        logService.info(LOG_KEY, 'Current state cleared');
                    }

                    currentState = undefined;
                    notifySubscribers();
                }

                return;
            }

            // No changes to the current state
            if (currentState && currentState.code === state.code) {
                return;
            }

            currentState = $angular.copy(state);
            logService.info(LOG_KEY, 'Current state updated to: ' + JSON.stringify(state));
            notifySubscribers();
        }

        function getCurrentState() {
            return currentState;
        }

        return {
            start: start,
            stop: stop,
            subscribe: subscribe,
            unsubscribe: unsubscribe,
            setCurrentState: setCurrentState,
            getCurrentState: getCurrentState,
            startAllServices: startAllServices,
            stopAllServices: stopAllServices,
            resetContent: resetContent,
            clearCachedScripts: clearCachedScripts,
            getUpTime: getUpTime,
            getSessionId: getSessionId,
            isRegistered: isRegistered,
            isPlaybackReady: isPlaybackReady,
            isServiceAvailable: isServiceAvailable,
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');

    app.service('syncService',
        ["$q", "$timeout", "$injector", "deviceRegistrationService", "logService", "halPlatformService", "networkContentCacheService", "signsAPIService", "coreConfigurationService", "storageLocalStorageService", "deviceAPIService", "cdnService", "CDN", "halNetworkService", "FileUtil", "UrlUtil", "sessionService", "embedProfileService", "applicationCommandService", "dataUtil", "signsParserService", "Utils", "networkContentRouterService", "TIMESTAMP_EVENT", "ASERRORCODE", "SYNC_EVENT", "FEATURE_CODE", "PLAYER_PROPERTY_KEYS", "STORAGE_COLLECTION_KEY", "STORAGE_KEY", function($q, $timeout, $injector,
            // external services
            deviceRegistrationService, logService, halPlatformService, networkContentCacheService,
            signsAPIService, coreConfigurationService, storageLocalStorageService, deviceAPIService,
            cdnService, CDN, halNetworkService, FileUtil, UrlUtil, sessionService, embedProfileService,
            applicationCommandService, dataUtil, signsParserService, Utils, networkContentRouterService,
            // constants
            TIMESTAMP_EVENT,
            ASERRORCODE, SYNC_EVENT, FEATURE_CODE, PLAYER_PROPERTY_KEYS, STORAGE_COLLECTION_KEY, STORAGE_KEY
        ) {
            var registrationService = deviceRegistrationService;
            var signsAPI = signsAPIService;
            var deviceAPI = deviceAPIService;
            var parserService = signsParserService;
            var storage = storageLocalStorageService;

            var syncing = false;
            var syncHandler = null;
            var isInSync = false;
            var abort = false;
            var lastContentCache = null;
            var prefetchPromise = null;
            var cyclesSincePurge = 0;
            var purgePending = false;
            var purgeTimer = null;
            var resyncTimer = null;
            var isSyncProcessed = null;

            var retrievedTimestamp;
            var appProperties = {};
            var currentAppProperties = {};
            var appendedAppProperties = {};
            var validScriptBlocks = null;
            var subscribers = [];
            var syncSchedules = [];
            var synchronizeSchedulesRaw = '';
            var overwriteSynchronizeSchedules = [];
            var syncScheduleTimeoutHandler;
            var timestampService;
            var deviceStateService;
            var isCacheDisable;
            var started;
            var lastError;
            var convertedScriptBlocks = {};
            var mediaZoneDescriptions = {};
            var _defaultConcurrentDownload = 4;
            var syncState = {
                timestamp: null,
                appId: null,
                scriptBlocks: {},
                manifest: {}
            };
            var lastTimestamp = {};

            var failureRequireReboot = {required: false, message: ''};
            var logKey = 'appspace.signs.sync';
            var supportLocalStorage;
            var isTransparentCacheSupported;
            var isUsingSynchronizedPlayback = false;
            var isPreviewMode = embedProfileService.isPreviewMode();
            var DATA_SOURCE_KEY = {
                DEVICE_LICENSING: 'devicelicensing_',
                APPLICATION_PROPERTIES: 'applicationproperties_',
                CONTENT_DISTRIBUTION: 'contentdistribution_',
                LAYOUT_TARGETS: 'layouttargets_',
                INTERFACE_DESCRIPTION: '_interfacedescription_'
            };
            var PROMISE_STATUS = {
                FULFILLED: 'fulfilled',
                REJECTED: 'rejected'
            };
            var IMAGE_PREVIEW_URL = 'imagepreview.axd';
            var VIDEO_THUMBNAIL_FILE_NAME_PREFIX = '_vidThumbnail_';
            var STATUS_CODE = {
                NOT_FOUND: 404
            };
            var CACHE_PURGE_SYNC_CYCLE = {
                DEFAULT: 3,
                MIN: 0,
                MAX: 3
            };
            var BACKSLASH = '\\';
            var DEFAULT_SYNC_CYCLE = 30000;

            var syncCycle = DEFAULT_SYNC_CYCLE;
            var scriptRetrievalDelayDuration = {
                min: 2000,
                max: 7000,
            };
            var scriptRetrievalState = {
                deferred: null,
                timeout: null,
            };

            // Public interface
            return {
                isSyncing: isSyncing,
                getSyncState: getSyncState,
                isStarted: isStarted,
                isAppAvailable: isAppAvailable,
                isWithinSyncSchedule: isSyncSchedulesValid,
                retrieveDatasource: retrieveDatasource,
                retrieveDatasourceByType: retrieveDatasourceByType,
                retrieveAppScriptObject: retrieveAppScriptObject,
                retrieveAppId: retrieveAppId,
                getManifest: getManifest,
                getContentFileSizeMap: getContentFileSizeMap,
                getSavedProperties: getSavedProperties,
                getAppProperties: getAppProperties,
                getCurrentAppProperties: getCurrentAppProperties,
                getCurrentAppProperty: getCurrentAppProperty,
                appendAppProperties: appendAppProperties,
                startSync: startSync,
                stopSync: stopSync,
                resync: resync,
                subscribe: subscribe,
                unsubscribe: unsubscribe,
                getStartupApp: getStartupApp,
                getSyncProgress: getSyncProgress,
                retrieveWidgetAssetDatasource: retrieveWidgetAssetDatasource,
                getCurrentTimestamp: getCurrentTimestamp,
                getTimestampFailCount: getTimestampFailCount,
                enableCache: enableCache,
                disableCache: disableCache,
                saveScriptToLocal: saveScriptFromSyncState,
                isAppUnassigned: isAppUnassigned,
                processAppScript: processAppScript,
                retrieveDeviceLicensing: retrieveDeviceLicensing,
                getMediaZoneDescription: getMediaZoneDescription
            };

            function getTimestampService() {
                if (!timestampService) {
                    timestampService = $injector.get('timestampService');
                }

                return timestampService;
            }

            function getDeviceStateService() {
                if (!deviceStateService) {
                    deviceStateService = $injector.get('deviceStateService');
                }

                return deviceStateService;
            }

            function getTimestampFromServer() {
                getTimestampService().getCurrentTimestamp();
            }

            function initiateTimestampService() {
                startTimestampService();
                getTimestampFromServer();
            }

            function init() {
                supportLocalStorage = halPlatformService.getFeatureSupport('file.localstorage') === 'yes';
                isTransparentCacheSupported = halPlatformService.getFeatureSupport(FEATURE_CODE.NETWORK_TRANSPARENT_CACHE);
                var appID = retrieveAppId();
                if (!appID) {
                    initiateTimestampService();
                    return;
                }

                loadCachedIntoMemory();
            }

            function saveAppProperties(properties) {
                currentAppProperties = properties;
                storage.set(
                    STORAGE_COLLECTION_KEY.PLAY,
                    STORAGE_KEY.DEVICE_PROPERTIES,
                    JSON.stringify(currentAppProperties));
            }

            function getSavedProperties() {
                var savedProperties = dataUtil.tryParseJSON(storage.get(STORAGE_COLLECTION_KEY.PLAY, STORAGE_KEY.DEVICE_PROPERTIES));
                return savedProperties || {};
            }

            function loadContentInfoFromMediaZone(parsedMediaZone, contentIdMappings) {
                if (!parsedMediaZone ||
                    !parsedMediaZone.playlist ||
                    !parsedMediaZone.playlist.items ||
                    !parsedMediaZone.playlist.items.length) {
                    return;
                }

                var playlistItems = parsedMediaZone.playlist.items;
                for (var j = 0; j < playlistItems.length; j++) {
                    var playlistItem = playlistItems[j];
                    contentIdMappings[playlistItem.id.toString()] = playlistItem.contentId;
                }
            }

            function loadContentInfoFromMediaZones(contentIdMappings) {
                var defer = $q.defer();
                retrieveDatasourceByType('az').then(function(sources) {
                    for (var i = 0; i < sources.length; i++) {
                        var source = sources[i];
                        if (!source ||
                            !source.result) {
                            continue;
                        }

                        loadContentInfoFromMediaZone(source.result.mediaZone, contentIdMappings);
                    }

                    defer.resolve();
                }, defer.resolve);

                return defer.promise;
            }

            function loadContentInfoFromSchedulingMediaZones(mediaZones, contentIdMappings) {
                if (!mediaZones ||
                    !mediaZones.length) {
                    return;
                }

                for (var i = 0; i < mediaZones.length; i++) {
                    var mediaZone = mediaZones[i];
                    if (!mediaZone.playlists || !mediaZone.playlists.length) {
                        continue;
                    }

                    for (var j = 0; j < mediaZone.playlists.length; j++) {
                        var playlist = mediaZone.playlists[j];
                        if (!playlist.items || !playlist.items.length) {
                            continue;
                        }

                        for (var k = 0; k < playlist.items.length; k++) {
                            var playlistItem = playlist.items[k];
                            contentIdMappings[playlistItem.mediaId.toString()] = playlistItem.contentId;
                        }
                    }
                }
            }

            function loadContentInfoFromScheduling(contentIdMappings) {
                var defer = $q.defer();
                retrieveDatasourceByType('scheduling').then(function(sources) {
                    for (var i = 0; i < sources.length; i++) {
                        var source = sources[i];
                        if (!source ||
                            !source.result) {
                            continue;
                        }

                        loadContentInfoFromSchedulingMediaZones(source.result.mediaZones, contentIdMappings);
                    }

                    defer.resolve();
                }, defer.resolve);
                return defer.promise;
            }

            function getContentIdMappings() {
                var defer = $q.defer();
                var contentIdMappings = {};
                // load content info from media zone
                var promises = [];
                promises.push(loadContentInfoFromMediaZones(contentIdMappings));
                promises.push(loadContentInfoFromScheduling(contentIdMappings));
                $q.all(promises).then(function() {
                    defer.resolve(contentIdMappings);
                });
                return defer.promise;
            }

            function loadCachedIntoMemory() {
                var onAppPropertiesRetrieved = function(res) {
                    appProperties = res.result.properties;
                    saveAppProperties(angular.copy(appProperties));
                    applySettingsFromProperties();
                    notifySubscribers({event: SYNC_EVENT.SCRIPT_READY});
                    initiateTimestampService();
                };

                // load app properties from cache
                retrieveDatasource('applicationproperties_' + retrieveAppId()).then(onAppPropertiesRetrieved, $angular.noop);
            }

            function startTimestampService() {
                getTimestampService().subscribe(onTimestampUpdate);
                getTimestampService().start();
            }

            // enables syncing
            function startSync() {
                startTimestampService();
                if (started == true) {
                    logService.info(logKey, 'Sync service already started');
                    return;
                }

                init();
                convertedScriptBlocks = {};
                started = true;
                abort = false;
                logService.info(logKey, 'Sync service started');
                startSyncScheduleCheck();
                scheduleSync(0);
            }

            function stopSyncScheduleCheck() {
                if (!syncScheduleTimeoutHandler) {
                    return;
                }

                $timeout.cancel(syncScheduleTimeoutHandler);
                syncScheduleTimeoutHandler = undefined;
            }

            function startSyncScheduleCheck() {
                var timeout = 40000;
                stopSyncScheduleCheck();
                logService.debug(logKey, 'Next sync schedule check in ' + (timeout / 1000) + ' seconds');
                syncScheduleTimeoutHandler = $timeout(function() {
                    var valid = isSyncSchedulesValid();
                    logService.debug(logKey, 'Sync Schedule timer checks');
                    logService.debug(logKey, 'Schedules valid?:' + valid + ' Are we currently syncing:' + syncing);
                    if (!valid && syncing == true) {
                        logService.debug(logKey, 'Aborting current sync process');
                        syncAbort();
                    }

                    startSyncScheduleCheck();
                }, timeout);
            }

            // Schedules a sync operation
            function scheduleSync(delay) {
                var timeout = delay;
                if (timeout == null) {
                    timeout = syncCycle;
                }

                if (syncHandler != null) {
                    $timeout.cancel(syncHandler);
                    syncHandler = null;
                }

                // Already syncing
                if (syncing) {
                    // console.log("is already syncing")
                    logService.debug(logKey, 'Sync service already syncing');
                    return;
                }

                logService.debug(logKey, 'Next sync in ' + (timeout / 1000) + ' seconds');
                syncHandler = $timeout(checkSync, timeout, false);
                startTimestampService();
            }

            function getStartupApp() {
                var deferred = $q.defer();

                function onAppScript(res) {
                    var app = res.result.application;
                    // If the app has no ID, then its Play
                    if (app.applicationId == 0) {
                        deferred.resolve({
                            type: 'play'
                        });
                    } else {
                        deferred.resolve({
                            type: 'signs',
                            sign: app
                        });
                    }
                }

                // Retreive the default app object
                retrieveAppScriptObject().then(onAppScript, deferred.reject);
                return deferred.promise;
            }

            function getCurrentAppProperties() {
                // append properties have higher precedence over script properties
                return dataUtil.mergeObject(currentAppProperties, appendedAppProperties);
            }

            function getCurrentAppProperty(property) {
                return typeof property == 'string' ? getCurrentAppProperties()[property.toLowerCase()] : undefined;
            }

            function appendAppProperties(properties) {
                appendedAppProperties = angular.copy(properties);
            }

            function getAppProperties(property, appId) {
                var deferred = $q.defer();

                if (!appId) {
                    appId = retrieveAppId();
                }

                function onAppProperties(res) {
                    var appProperties = res.result.properties;
                    var appPropertiesLookUp = {};
                    for (var key in appProperties) {
                        appPropertiesLookUp[key.toLowerCase()] = appProperties[key];
                    }

                    if (property) {
                        var val = appPropertiesLookUp[property.toLowerCase()];
                        if (val != undefined) {
                            deferred.resolve(val);
                        } else {
                            deferred.reject();
                        }
                    } else {
                        deferred.resolve(appPropertiesLookUp);
                    }
                }

                retrieveDatasource('applicationproperties_' + retrieveAppId()).then(onAppProperties, deferred.reject);
                return deferred.promise;
            }

            // disables syncing
            function stopSync() {
                abort = true;
                syncing = false;
                if (syncHandler != null) {
                    $timeout.cancel(syncHandler);
                    syncHandler = null;
                }

                // Abort current sync
                syncAbort();
                stopSyncScheduleCheck();
                started = false;
                logService.info(logKey, 'Sync service stopped');
                notifySubscribers({event: SYNC_EVENT.SERVICE_STOPPED});
                getTimestampService().stop();
                // Reset device properties and sync cycle in unregistered state
                if (!isDeviceRegistered()) {
                    appProperties = {};
                    currentAppProperties = {};
                    syncCycle = DEFAULT_SYNC_CYCLE;
                }

                return $q.resolve();
            }

            function resync() {
                getDeviceStateService().clearCachedScripts();
                getTimestampFromServer();
            }

            // Begins the sync cycle
            function checkSync() {
                // reboot if there's an xhr failure
                if (failureRequireReboot.required) {
                    logService.warn(logKey, failureRequireReboot.message + ' Rebooting the device in 3 seconds.');
                    failureRequireReboot.required = false;
                    failureRequireReboot.message = '';
                    $timeout(halPlatformService.reboot, 3000);
                    return;
                }

                if (abort) return syncAbort(); // Break workflow if abort signaled

                syncing = true;
                resetSyncState();
                if (shouldCheckTimestamp()) {
                    // Timestamp service should handle the timestamp sync already
                    return;
                }

                notifySubscribers({event: SYNC_EVENT.NO_PLAYER_ID});
                // retry
                syncComplete();
            }

            function isDeviceRegistered() {
                return registrationService.getPlayerId();
            }

            function shouldCheckTimestamp() {
                return isDeviceRegistered() ||
                    retrieveAppIdFromInjection() ||
                    retrieveAppIdFromQueryString();
            }

            // End of sync ccule
            function syncComplete() {
                lastError = null;
                syncing = false;
                if (abort) return syncAbort(); // Break workflow if abort signaled
                scheduleSync();
            }

            // on sync error
            function syncError(err, type) {
                isSyncProcessed = true;
                lastError = null;
                // Check is the err an object, if so stringify it
                if (typeof err === 'object') {
                    lastError = JSON.stringify(err, null, 4);
                } else if (typeof err === 'string') {
                    lastError = err;
                }

                logService.error(logKey, 'Sync service encountered an error: ' + lastError);
                syncing = false;
                notifySubscribers({event: SYNC_EVENT.SYNC_ERROR, err: lastError, type: type});
                if (abort) return syncAbort(); // Break workflow if abort signaled
                scheduleSync();
            }

            // on sync abort
            function syncAbort() {
                isSyncProcessed = true;
                syncing = false;
                abortPrefetch();
                notifySubscribers({event: SYNC_EVENT.SYNC_ABORT});
                return;
            }

            // Gets the progress of the current sync if current syncing
            function getSyncProgress() {
                if (prefetchPromise == null) {
                    return null;
                }

                var syncProgress = {
                    cached: 0,
                    total: 0,
                    failed: 0,
                    queued: 0
                };

                for (var i = 0; i < prefetchPromise._cachePromises.length; i++) {
                    var p = prefetchPromise._cachePromises[i];
                    if (p.$$state.status === 0) { // pending
                        syncProgress.queued++;
                        syncProgress.total++;
                    } else if (p.$$state.status === 1) { // resolved
                        syncProgress.cached++;
                        syncProgress.total++;
                    } else if (p.$$state.status === 2) { // rejected
                        syncProgress.failed++;
                        syncProgress.total++;
                    }
                }

                return syncProgress;
            }

            function isRetrievingScript() {
                return Boolean(scriptRetrievalState.deferred);
            }

            function isSyncInProgress(state) {
                return Boolean(state.isRetrievingScript || state.syncProgress);
            }

            function enableCache() {
                isCacheDisable = false;
            }

            function disableCache() {
                isCacheDisable = true;
            }

            function abortPrefetch() {
                if (prefetchPromise) {
                    logService.info(logKey, 'Aborting resource prefetch');
                    networkContentCacheService.abortPrefetch(prefetchPromise);
                    prefetchPromise = null;
                }
            }

            function doLocalStorageCaching(manifest) {
                logService.info(logKey, 'Prefetching resources to device storage');
                var deferred = $q.defer();
                var isRetryExecuted = false;

                abortPrefetch();
                var maxConcurrentDownload = halPlatformService.getFeatureSupport('network.maxconcurrentdownloads');
                var concurrentDownload = parseInt(appProperties['cdnagent.contentmaxconcurrentdownloads'], 10) || maxConcurrentDownload || _defaultConcurrentDownload;
                if (concurrentDownload <= 0 || concurrentDownload > maxConcurrentDownload) {
                    logService.warn(logKey, 'Number of concurrent download requested exceeds the limit of the platform (' + maxConcurrentDownload + '). Setting back to platform\'s limit.');
                    concurrentDownload = maxConcurrentDownload;
                }

                var doPreFetch = function() {
                    prefetchPromise = networkContentCacheService.preFetch('signs.content', manifest, concurrentDownload);
                    prefetchPromise.then(prefetchComplete, prefetchFailed);
                };

                // Download manifest
                var prefetchComplete = function() {
                    prefetchPromise = null;
                    logService.debug(logKey, 'Content manifest successfully prefetched to local storage');
                    deferred.resolve();
                };

                var prefetchFailed = function(err) {
                    prefetchPromise = null;
                    var promises = err;
                    var success = 0;
                    var listOfFails = [];
                    var listOfImagePreviewFails = [];
                    var listOfRetryableFails = [];
                    for (var i = 0; i < promises.length; i++) {
                        var promise = promises[i];
                        if (promise.state === PROMISE_STATUS.FULFILLED) {
                            success++;
                        } else if (promise.state === PROMISE_STATUS.REJECTED) {
                            if (isPreviewImageFailure(promise.reason.code, promise.reason.url)) {
                                listOfImagePreviewFails.push(promise.reason);
                            } else {
                                var reason = promise.reason;
                                // check if the require reboot.
                                // currently only xhr failure requires it.
                                if (reason.requirereboot) {
                                    failureRequireReboot.required = true;
                                    failureRequireReboot.message = reason.message;
                                }

                                if (reason.code === ASERRORCODE.RETRYABLE_DOWNLOAD_ERROR) {
                                    listOfRetryableFails.push(reason);
                                }

                                listOfFails.push(reason);
                            }
                        }
                    }

                    // if all of the failures is because image preview, consider sync complete
                    if (listOfFails.length == 0) {
                        // this code path will guaranteed listOfImagePreviewFails will have more than 0 item
                        logService.warn(logKey, 'Ignoring imagepreview sync error (total failures:' + listOfImagePreviewFails.length + ')');
                        logService.debug(logKey, 'List of failed image preview downloads:', listOfImagePreviewFails);
                        prefetchComplete();
                        return;
                    }

                    if (listOfRetryableFails.length > 0 && !isRetryExecuted) {
                        // log any retry download
                        logService.info(logKey, 'Retrying failed downloads (total failures:' + listOfRetryableFails.length + ')');
                        isRetryExecuted = true;
                        doPreFetch();
                        return;
                    }

                    var errorMessage = 'Failed to prefetch all contents to local storage (success:' + success + ', failed:' + listOfFails.length + '(' + listOfRetryableFails.length + ' retryable)' + ', ignored:' + listOfImagePreviewFails.length + ', total:' + promises.length + ')';
                    logService.error(logKey, errorMessage);
                    logService.debug(logKey, 'List of failed downloads:', listOfFails);

                    if (listOfImagePreviewFails.length > 0) {
                        logService.debug(logKey, 'List of failed image preview downloads:', listOfImagePreviewFails);
                    }

                    deferred.reject(errorMessage);
                };

                doPreFetch();

                return deferred.promise;
            }

            // convert reason code to number if it is a number string
            // otherwise return the original reason code
            function tryConvertReasonCodeToNumber(reasonCode) {
                return dataUtil.isNumber(reasonCode) ? parseInt(reasonCode, 10) : reasonCode;
            }

            // true if reason code == '404' and remote url contains 'imagepreview.axd'
            function isPreviewImageFailure(reasonCode, remoteUrl) {
                var reasonCodeInNumber = tryConvertReasonCodeToNumber(reasonCode);
                return reasonCodeInNumber === STATUS_CODE.NOT_FOUND && dataUtil.includesValue(IMAGE_PREVIEW_URL, remoteUrl);
            }

            function doTransparentCaching(manifest) {
                prefetchPromise = halNetworkService.prefetchResources(manifest);
                if (!prefetchPromise) {
                    logService.debug(logKey, 'Device doesn\'t support caching to file storage');
                    return $q.resolve();
                }

                var transparentCachePromise = $q.defer();
                var prefetchComplete = function() {
                    prefetchPromise = null;
                    logService.debug(logKey, 'Prefetch resources success');
                    transparentCachePromise.resolve();
                };

                logService.debug(logKey, 'Prefetching resources to device cache');
                prefetchPromise.then(prefetchComplete, function(err) {
                    logService.warn(logKey, 'Failed during prefetch resources: ' + JSON.stringify(err));
                    transparentCachePromise.reject('Failed during prefetch resources');
                });
                return transparentCachePromise.promise;
            }

            // Use the cache service to cache content based on the current manifest
            function cacheContent(manifest) {
                if (isCacheDisable || !manifest) {
                    return $q.resolve();
                }

                halNetworkService.updateResourceManifest(manifest);
                notifySubscribers({
                    event: SYNC_EVENT.MANIFEST_UPDATED,
                    manifest: manifest
                });
                if (isTransparentCacheSupported) {
                    return doTransparentCaching(manifest);
                }

                if (supportLocalStorage) {
                    return doLocalStorageCaching(manifest);
                }

                logService.debug(logKey, 'Device doesn\'t support caching');
                return $q.resolve();
            }

            function cancelPurgeTimer() {
                if (!purgeTimer) {
                    return;
                }

                $timeout.cancel(purgeTimer);
                purgeTimer = undefined;
            }

            function startPurgeTimer(purgeDelay) {
                cancelPurgeTimer();
                logService.debug(logKey, 'Starting cache purge in ' + (purgeDelay / 1000) + ' seconds...');
                purgeTimer = $timeout(doPurge, purgeDelay);
            }

            function getCachePurgeSyncCycle() {
                var syncCycle = getCurrentAppProperty(PLAYER_PROPERTY_KEYS.CACHE_PURGE_SYNC_CYCLE);
                return !dataUtil.isNumber(syncCycle) ?
                    CACHE_PURGE_SYNC_CYCLE.DEFAULT :
                    Math.min(
                        Math.max(parseInt(syncCycle, 10), CACHE_PURGE_SYNC_CYCLE.MIN),
                        CACHE_PURGE_SYNC_CYCLE.MAX);
            }

            function doPurge() {
                cyclesSincePurge = 0;
                purgePending = false;
                var manifest = storage.get('device.sync', 'manifest');
                if (!manifest) {
                    return;
                }

                manifest = JSON.parse(manifest);
                var purgeRepository = function() {
                    logService.debug(logKey, 'Checking files/directories to purge');
                    // Ensure only content in the repository is from this manifest
                    networkContentCacheService.purgeRepository('signs.content', manifest).then(
                        function(collection) {
                            logService.debug(logKey, 'Content repository successfully purged of old content');

                            var collectionStr = [];
                            for (var i = 0; i < collection.length; i++) {
                                var keys = storage.content.removeCollection(collection[i]);
                                if (keys && keys.length > 0) {
                                    collectionStr.push(collection[i]);
                                }
                            }

                            if (collectionStr.length > 0) {
                                logService.debug(logKey, 'Removing collection from local storage. ' + collectionStr.join(','));
                            }
                        }, function(err) {
                            // Check is the err an object, if so stringify it
                            if (err && typeof err === 'object') {
                                err = JSON.stringify(err, null, 4);
                            }

                            logService.debug(logKey, 'Content repository failed to purge old content: ' + err);
                        }
                    );
                };

                getContentIdMappings().then(function(contentIdMappings) {
                    manifest.contentIdMappings = contentIdMappings;
                    purgeRepository();
                });
            }

            function checkPurge() {
                if (!purgePending || !isCachePurgeSupported()) {
                    cancelPurgeTimer();
                    logService.debug(logKey, 'Ignoring check content purge (pending:' + purgePending + ', support local storage:' + supportLocalStorage + ', support transparent cache:' + isTransparentCacheSupported + ')');
                    return;
                }

                var timestampConfiguration = getTimestampService().getConfiguration();
                var cachePurgeSyncCycle = getCachePurgeSyncCycle();
                if (timestampConfiguration.isSyncCycleEnabled) {
                    // Timed cache purge process is no longer valid
                    cancelPurgeTimer();
                    // We allow 3 sync cycles (by default) before we do a purge
                    if (cyclesSincePurge > cachePurgeSyncCycle) {
                        logService.debug(logKey, cachePurgeSyncCycle + ' sync cycles since last cache purge');
                        doPurge(cachePurgeSyncCycle);
                    }

                    cyclesSincePurge++;
                    return;
                }

                // Cache purge process is no longer bound to sync cycles
                cyclesSincePurge = 0;
                // Initiate timed cache purge process
                startPurgeTimer(cachePurgeSyncCycle * syncCycle);
            }

            function isCachePurgeSupported() {
                return Boolean(supportLocalStorage || isTransparentCacheSupported);
            }

            function resetPurge() {
                cancelPurgeTimer();
                if (!isCachePurgeSupported()) {
                    return;
                }

                // Check does this device support local storage, if so we reset the purge
                cyclesSincePurge = 0;
                purgePending = true;
            }

            function verifyCurrentScript() {
                var promise = $q.defer();
                logService.debug(logKey, 'Retrieving app xml for current app ' + retrieveAppId());
                retrieveAppScriptObject().then(promise.resolve, promise.reject);
                return promise.promise;
            }

            function onTimestampUpdate(event, data) {
                logService.debug(logKey, 'Received timestamp ' + event + ' event with ' + JSON.stringify(data));
                switch (event) {
                    case TIMESTAMP_EVENT.RETRIEVED:
                        logService.debug(logKey, 'Timestamp (' + data.timestamp + ') retrieved from ' + data.source);
                        lastTimestamp = {
                            timestamp: data.timestamp,
                            source: data.source
                        };
                        refreshSyncSchedule().then(function() {
                            processTimestamp(data.timestamp);
                        });
                        break;
                    case TIMESTAMP_EVENT.FAILED:
                        logService.error(logKey, 'Failed to get timestamp from ' + data.source);
                        handleTimestampFailure(data.error);
                        break;
                }
            }

            function onInvalidSyncSchedule() {
                logService.debug(logKey, 'Sync aborted as no valid schedules found');
                syncing = false;
                scheduleSync();
                isSyncProcessed = true;
            }

            function onContentCacheError(error) {
                logService.error(logKey, 'Failed to cache content: ', error);
                syncError(error, 'cache-error');
                var timestampConfiguration = getTimestampService().getConfiguration();
                if (timestampConfiguration.isSyncCycleEnabled) {
                    // Timed resync for cache content retry is no longer valid
                    cancelResyncTimer();
                    return;
                }

                // Initiate timed resync to reattempt cache content
                startResyncTimer(syncCycle);
            }

            function cancelResyncTimer() {
                if (!resyncTimer) {
                    return;
                }

                $timeout.cancel(resyncTimer);
                resyncTimer = undefined;
            }

            function startResyncTimer(delay) {
                cancelResyncTimer();
                logService.debug(logKey, 'Resyncing in ' + (delay / 1000) + ' seconds');
                resyncTimer = $timeout(getTimestampFromServer, delay);
            }

            function processTimestamp(rawTimestamp) {
                cancelResyncTimer();
                // No channel assigned
                if (rawTimestamp == '') {
                    syncComplete();
                    onEmptyTimestamp('Empty timestamp');
                    return;
                }

                // The current sync process is still ongoing
                if (isSyncProcessed === false && !isPreviewMode) {
                    var currentSyncState = getSyncState();
                    var syncProgress = currentSyncState.syncProgress;
                    var syncProgressMessage = syncProgress ?
                        '(total: ' + syncProgress.total + ', cached: ' + syncProgress.cached + ', failed: ' + syncProgress.failed + ', queued: ' + syncProgress.queued + ')' :
                        '-';
                    // No pending content cache nor script retrieval process but the current sync process is still ongoing,
                    // consider this a sync error to restart the sync process
                    if (!isSyncInProgress(currentSyncState)) {
                        onContentCacheError('Invalid sync state [sync progress ' + syncProgressMessage + '; is retrieving script: ' + currentSyncState.isRetrievingScript + ']');
                        return;
                    }

                    var reason = 'Ignoring timestamp update (' + rawTimestamp + ') due to ongoing ';
                    reason += syncProgress ?
                        'sync process ' + syncProgressMessage :
                        'script retrieval process';
                    logService.info(logKey, reason);
                    return;
                }

                var timestamp = rawTimestamp;
                notifySubscribers({event: SYNC_EVENT.TIMESTAMP_RETRIEVED});
                // if timestamp retrieved from Appspace is the same as last time
                // and we've never update our cached timestamp
                // we're in the scenario of sync schedules are invalid. so do another round of sync schedule checking
                if (retrievedTimestamp == timestamp && retrievedTimestamp != getCurrentTimestamp()) {
                    var isvalid = isSyncSchedulesValid();
                    if (!isvalid) {
                        onInvalidSyncSchedule();
                        return;
                    }
                }

                retrievedTimestamp = timestamp;
                syncState.timestamp = timestamp;
                // Check is the local timestamp same as server
                if (timestamp != getCurrentTimestamp()) {
                    // Reset purge cycles
                    resetPurge();

                    logService.debug(logKey, 'Local timestamp does not match, retrieving app script');
                    // Not in sync with the server
                    resetSyncProcess();
                    // notify subscribers
                    notifySubscribers({event: SYNC_EVENT.RETRIEVING_SCRIPT});
                    checkScript();
                } else { // We are in sync with the server
                    logService.debug(logKey, 'Local timestamp matches with server');
                    logService.debug(logKey, 'Verifying current script');
                    verifyCurrentScript().then(function() {
                        logService.debug(logKey, 'Current script is valid');
                        // Check have we already cached the content for this timestamp
                        if (lastContentCache != getCurrentTimestamp()) {
                            // Reset purge cycles
                            resetPurge();

                            // Attempt to cache content for the app
                            var manifest = JSON.parse(storage.get('device.sync', 'manifest'));
                            cacheContent(manifest).then(function() {
                                logService.debug(logKey, 'Content cached for current timestamp: ' + getCurrentTimestamp());
                                // Record last timestamp for successfull content cache
                                lastContentCache = getCurrentTimestamp();
                                checkInSync();
                                if (abort) return syncAbort(); // Break workflow if abort signaled
                                removeUnusedScripts();

                                syncComplete();
                                // notify subscribers
                                notifySubscribers({event: SYNC_EVENT.IN_SYNC});
                            }, onContentCacheError);
                        } else {
                            // Check if we need to purge
                            checkPurge();
                            removeUnusedScripts();
                            checkInSync();
                            syncComplete();
                            // notify subscribers
                            notifySubscribers({event: SYNC_EVENT.IN_SYNC});
                        }
                    }, function(err) {
                        logService.error(logKey, 'Failed to verify current script. Retrying sync');
                        logService.debug(logKey, 'Resetting timestamp to null');
                        retrievedTimestamp = null;
                        setCurrentTimestamp('');
                        syncError('Failed to verify current script');
                    });
                }
            }

            function onEmptyTimestamp(error) {
                logService.error(logKey, 'no application assigned', error);
                abortPrefetch();
                // if the device had been assigned an app before
                if (getHasChannelAssigned()) {
                    setHasChannelAssigned(false);
                    notifySubscribers({event: SYNC_EVENT.APP_UNASSIGNED});
                    return;
                }

                notifySubscribers({event: SYNC_EVENT.NO_APP});
            }

            function handleTimestampFailure(error) {
                syncComplete();
                cancelResyncTimer();
                if (!error) {
                    return;
                }

                // no application assigned
                switch (error.errorCode) {
                    case ASERRORCODE.DEVICE_NOT_FOUND:// device not found on server
                        logService.error(logKey, 'Device not found', error.error);
                        notifySubscribers({event: SYNC_EVENT.NO_DEVICE_FOUND});
                        logService.info(logKey, 'Device has been deleted from the console');
                        applicationCommandService.execute({type: 'system', action: 'resetdevice'});
                        break;
                    case ASERRORCODE.EMPTY_TIMESTAMP:// no application assigned
                        onEmptyTimestamp(error.error);
                        break;
                    default:// generic error
                        logService.error(logKey, 'get timestamp failed', error.error);
                        notifySubscribers({event: SYNC_EVENT.ERROR_RETRIEVING_TIMESTAMP});
                        break;
                }
            }

            function setHasChannelAssigned(value) {
                storageLocalStorageService.set('device', 'haschannelassigned', JSON.stringify(value));
            }

            function getHasChannelAssigned() {
                var value = storageLocalStorageService.get('device', 'haschannelassigned');
                if (value === undefined) {
                    return;
                }

                return JSON.parse(value);
            }

            function isAppUnassigned() {
                return getHasChannelAssigned() === false;
            }

            function updateCDN() {
                var promise = $q.defer();
                var licenses;
                var contentDistribution;
                var onCDNConfigured = function() {
                    // verify cdns within 1 second each
                    cdnService.forceCheckCDN(1000).then(promise.resolve, promise.resolve);
                };

                var configureCDNs = function() {
                    cdnService.configureCDNs(appProperties, licenses, contentDistribution).then(onCDNConfigured);
                };

                var onContentDistributionRetrieved = function(result) {
                    contentDistribution = result;
                    configureCDNs();
                };

                var onDeviceLicensingRetrieved = function(result) {
                    licenses = {};
                    for (var i = 0; i < result.length; i++) {
                        var license = result[i];
                        if (dataUtil.isNullOrUndefined(license)) {
                            continue;
                        }

                        licenses[license.key] = license.value;
                    }

                    retrieveContentDistribution().then(onContentDistributionRetrieved, configureCDNs);
                };

                // clear all cdns from cdn service
                cdnService.removeAllCDN();
                retrieveDeviceLicensing().then(onDeviceLicensingRetrieved, configureCDNs);
                return promise.promise;
            }

            function saveSyncState() {
                logService.debug(logKey, 'Saving sync state', {appId: syncState.appid, scriptBlocksLength: syncState.scriptBlocks.length, timestamp: syncState.timestamp});

                // Record last timestamp for successfull content cache
                lastContentCache = syncState.timestamp;

                saveScriptFromSyncState();

                    // Save app timestamp
                    setCurrentTimestamp(syncState.timestamp);

                    resetSyncState();
            }

            function resetSyncState() {
                syncState.timestamp = null;
                syncState.appId = null;
                syncState.scriptBlocks = {};
                syncState.manifest = {};
            }

            function finalizedProcessApp() {
                isSyncProcessed = true;
                logService.debug(logKey, 'Application was updated successfully');

                saveSyncState();
                removeUnusedScripts();

                storageLocalStorageService.set('device', 'haschannelassigned', true);
                notifySubscribers({event: SYNC_EVENT.APP_UPDATED});
            }

            function processAppScript(script) {
                // Parse the app script, if successful then we checkTimestamp so we can signal insync
                parseAppScript(script).then(function() {
                    if (abort) return syncAbort(); // Break workflow if abort signaled
                    if (halPlatformService.getFeatureSupport('playback.mode.editor')) {
                        logService.info(logKey, 'Application loaded in editor mode, will not continue checking timestamp');
                        finalizedProcessApp();
                        return;
                    }

                    // update cdn/smarthub
                    updateCDN().then(function() {
                        // Attempt to cache content for the app
                        cacheContent(syncState.manifest).then(function() {
                            if (abort) return syncAbort(); // Break workflow if abort signaled
                            finalizedProcessApp();
                            // Check timestamp against server so we can signal its insync
                            getTimestampFromServer();
                        }, onContentCacheError);
                    });
                }, function(err) {
                    if (err == 'schedule not valid') {
                        onInvalidSyncSchedule();
                    } else {
                        logService.error(logKey, 'Failed to parse app script: ', err);
                        syncError('Failed to parse application script');
                    }
                });
            }

            function checkScript() {
                getScript().then(function(res) {
                    if (abort) return syncAbort(); // Break workflow if abort signaled
                    processAppScript(res);
                }, function(err) {
                    logService.error(logKey, 'Failed to retrieve the application script: ', err);
                    syncError('Failed to retrieve application script');
                });
            }

            function retrieveDatasource(dataSourceId) {
                dataSourceId += '';
                var deferred = $q.defer();
                var start = new Date().getTime();
                convertScriptToDataSource(dataSourceId).then(function(res) {
                    var end = new Date().getTime();
                    logService.trace(logKey, 'Datasource ' + dataSourceId + ' retrieved in ' + (end - start) + 'ms');
                    deferred.resolve(res);
                }, function(err) {
                    deferred.reject(err);
                });
                return deferred.promise;
            }

            function retrieveSyncStateScriptBlock(key) {
                var deferred = $q.defer();
                var onRetrievedPlayerId = function(playerId) {
                    var scriptBlock = syncState.scriptBlocks[key + playerId];
                    if (!scriptBlock) {
                        deferred.reject();
                        return;
                    }

                    convertScriptBlockToDataSource(scriptBlock).then(deferred.resolve, deferred.reject);
                };

                retrievePlayerIntegerId().then(onRetrievedPlayerId, deferred.reject);
                return deferred.promise;
            }

            function retrieveContentDistribution() {
                var deferred = $q.defer();
                retrieveSyncStateScriptBlock(DATA_SOURCE_KEY.CONTENT_DISTRIBUTION).then(function(response) {
                    if (!response || !response.result || !response.result.contentDistribution) {
                        deferred.reject('invalid response');
                        return;
                    }

                    deferred.resolve(response.result.contentDistribution);
                }, deferred.reject);

                return deferred.promise;
            }

            function retrieveDeviceLicensing() {
                var deferred = $q.defer();

                var onRetrievedPlayerId = function(playerId) {
                    retrieveDatasource(DATA_SOURCE_KEY.DEVICE_LICENSING + playerId).then(function(response) {
                        if (!response || !response.result || !response.result.licenses) {
                            deferred.reject('invalid response');
                            return;
                        }

                        deferred.resolve(response.result.licenses);
                    }, deferred.reject);
                };

                retrievePlayerIntegerId().then(onRetrievedPlayerId, deferred.reject);

                return deferred.promise;
            }

            function retrieveLayoutTargets() {
                var deferred = $q.defer();
                retrieveDatasource(DATA_SOURCE_KEY.LAYOUT_TARGETS + retrieveAppId()).then(function(response) {
                    if (!response || !response.result || !response.result.layoutTargets) {
                        deferred.reject('invalid response');
                        return;
                    }

                    deferred.resolve(response.result.layoutTargets);
                }, deferred.reject);

                return deferred.promise;
            }

            function retrievePlayerIntegerId() {
                var DEVICE_SYNC_KEY = 'device.sync';
                var PLAYER_INTEGER_ID_KEY = 'playerintid';
                var PLAYER_ID_PROPERTY_KEY = 'player.id';
                var result = coreConfigurationService.get(DEVICE_SYNC_KEY, PLAYER_INTEGER_ID_KEY);
                if (result) {
                    return $q.resolve(result);
                }

                var propertyId = appProperties[PLAYER_ID_PROPERTY_KEY];
                if (propertyId) {
                    return $q.resolve(propertyId);
                }

                var deferred = $q.defer();
                retrieveDatasource(DATA_SOURCE_KEY.APPLICATION_PROPERTIES + retrieveAppId()).then(function(scriptObject) {
                    if (!scriptObject || !scriptObject.result || !scriptObject.result.properties) {
                        deferred.reject('Unable to retrieve data source for application properties.');
                        return;
                    }

                    result = scriptObject.result.properties[PLAYER_ID_PROPERTY_KEY];
                    if (!result) {
                        deferred.reject('Unable to find ' + PLAYER_ID_PROPERTY_KEY + ' property.');
                        return;
                    }

                    coreConfigurationService.set(DEVICE_SYNC_KEY, PLAYER_INTEGER_ID_KEY, result);
                    deferred.resolve(result);
                }, deferred.reject);

                return deferred.promise;
            }

            function retrieveWidgetAssetDatasource(widgetAssetSourceId) {
                var promise = $q.defer();

                if (!widgetAssetSourceId) {
                    promise.reject('invalid widget asset source id ' + widgetAssetSourceId);
                } else {
                    retrieveDatasource('applicationasset_' + retrieveAppId()).then(function(res) {
                        if (res.result && res.result.widgets) {
                            var assets;
                            for (var i = 0; i < res.result.widgets.length; i++) {
                                var widget = res.result.widgets[i];
                                if (widget.id == widgetAssetSourceId) {
                                    assets = widget.assets;
                                    break;
                                }
                            }

                            promise.resolve(assets);
                        } else {
                            promise.reject('invalid response');
                        }
                    }, function(err) {
                        promise.reject(err);
                    });
                }

                return promise.promise;
            }

            function retrieveDatasourceByType(type) {
                var promise = $q.defer();
                var keys = storage.getKeys('device.sync.script.blocks', false);
                var promises = [];
                for (var i = 0; i < keys.length; i++) {
                    var key = keys[i];
                    if (key.key.toLowerCase().indexOf(type) > -1) {
                        promises.push(retrieveDatasource(key.key));
                    }
                }

                $q.all(promises).then(function(e) {
                    promise.resolve(e);
                }, function(err) {
                    promise.resolve();
                });

                return promise.promise;
            }

            function retrieveAppScriptObject(appId) {
                if (!appId) {
                    appId = retrieveAppId();
                }

                var deferred = $q.defer();
                mediaZoneDescriptions = {};

                var loadApplicationXml = function() {
                    retrieveDatasource('applicationxml_' + appId).then(function(applicationXmlResult) {
                        retrieveLayoutTargets().then(function(layoutTargets) {
                            applicationXmlResult.result.application.layoutTargets = layoutTargets;
                        }, function(error) {
                            logService.debug(logKey, 'No layout targets for this application:', error);
                        }).finally(function() {
                            deferred.resolve(applicationXmlResult);
                        });
                    }, deferred.reject);
                };

                retrieveDatasource(DATA_SOURCE_KEY.INTERFACE_DESCRIPTION + appId).then(function(interfaceDescriptionResult) {
                    if (!interfaceDescriptionResult || !interfaceDescriptionResult.result || !interfaceDescriptionResult.result.interfaceDescription) {
                        loadApplicationXml();
                        return;
                    }

                    var mediaZones = interfaceDescriptionResult.result.interfaceDescription.mediaZones || [];
                    for (var i = 0; i < mediaZones.length; i++) {
                        var mediaZone = mediaZones[i];
                        mediaZoneDescriptions[mediaZone.id] = mediaZone;
                    }

                    loadApplicationXml();
                }, loadApplicationXml);
                return deferred.promise;
            }

            function getMediaZoneDescription(mediaZoneId) {
                return mediaZoneDescriptions[mediaZoneId];
            }

            function retrieveAppId() {
                return syncState.appId || retrieveAppIdFromStorage();
            }

            function getManifest() {
                return JSON.parse(storage.get(STORAGE_COLLECTION_KEY.DEVICE_SYNC, STORAGE_KEY.MANIFEST)) || {};
            }

            function setContentFileSizeMap(contentManifest) {
                storage.set(STORAGE_COLLECTION_KEY.DEVICE_SYNC, STORAGE_KEY.CONTENT_FILE_SIZE_MAP, JSON.stringify(contentManifest));
            }

            function getContentFileSizeMap() {
                var contentFileSizeMap = storage.get(STORAGE_COLLECTION_KEY.DEVICE_SYNC, STORAGE_KEY.CONTENT_FILE_SIZE_MAP);
                contentFileSizeMap = contentFileSizeMap ?
                    (dataUtil.tryParseJSON(contentFileSizeMap) || {}) :
                    updateContentFileSizeMap();
                return contentFileSizeMap;
            }

            function updateContentFileSizeMap() {
                var manifest = getManifest();
                var contentFileSizeMap = {};
                if (manifest.files) {
                    for (var i = 0; i < manifest.files.length; i++) {
                        var file = manifest.files[i];
                        if (!file || !file.url) {
                            continue;
                        }

                        var pathName = UrlUtil.getPathName(file.url).toLowerCase();
                        if (!pathName) {
                            continue;
                        }

                        contentFileSizeMap[pathName] = file.fileSize || 0;
                    }
                }

                setContentFileSizeMap(contentFileSizeMap);
                return contentFileSizeMap;
            }

            function retrieveAppIdFromInjection() {
                return coreConfigurationService.get('device', 'appid');
            }

            function refreshSyncSchedule() {
                // resolve directly if we don't have existing sync schedule from script
                if (!syncSchedules.length) {
                    return $q.resolve();
                }

                var onSyncScheduleRetrieved = function(scheduleRaw) {
                    // ignore if the schedule from the property is the same as Appspace's
                    if (synchronizeSchedulesRaw !== scheduleRaw) {
                        overwriteSynchronizeSchedules = transformSyncSchedule(scheduleRaw);
                    }

                    defer.resolve();
                };

                var defer = $q.defer();
                getSynchronizeScheduleFromAppspace().then(onSyncScheduleRetrieved, defer.resolve);
                return defer.promise;
            }

            function getSynchronizeScheduleFromAppspace() {
                return signsAPI.retrieveSynchronizeSchedule();
            }

            function getTimestampFailCount() {
                return getTimestampService().getTimestampFailCount();
            }

            function retrieveScript() {
                var deferred = $q.defer();
                var appid = retrieveAppIdFromInjection() || retrieveAppIdFromQueryString();
                var id = registrationService.getPlayerId();
                var metadata = {
                    timestamp: lastTimestamp
                };
                var scriptType = 'device';
                var retrieveScriptImplementation = signsAPI.retrievePlayerScript;
                if (appid) {
                    id = appid;
                    retrieveScriptImplementation = signsAPI.retrieveApplicationScript;
                    scriptType = 'channel';
                }

                logService.info(logKey, 'Retrieving ' + scriptType + ' script for ' + id);
                retrieveScriptImplementation(id, metadata).then(function(res) {
                    deferred.resolve(res);
                }, function(err) {
                    deferred.reject(err);
                });

                return deferred.promise;
            }

            function cancelScriptRetrievalDelayTimeout() {
                if (scriptRetrievalState.timeout) {
                    $timeout.cancel(scriptRetrievalState.timeout);
                }

                scriptRetrievalState.timeout = null;
            }

            function getScriptRetrievalDelay() {
                return dataUtil.generateRandomNumber(
                    scriptRetrievalDelayDuration.min,
                    scriptRetrievalDelayDuration.max
                );
            }

            function resetScriptRetrievalState() {
                scriptRetrievalState.deferred = null;
                cancelScriptRetrievalDelayTimeout();
            }

            function doGetScript() {
                scriptRetrievalState.deferred = scriptRetrievalState.deferred || $q.defer();
                retrieveScript().then(
                    scriptRetrievalState.deferred.resolve,
                    scriptRetrievalState.deferred.reject
                ).finally(resetScriptRetrievalState);
            }

            function delayGetScript() {
                if (scriptRetrievalState.deferred) {
                    return scriptRetrievalState.deferred.promise;
                }

                resetScriptRetrievalState();
                scriptRetrievalState.deferred = $q.defer();
                var delay = getScriptRetrievalDelay();
                logService.debug(logKey, 'Retrieving script in ' + delay + 'ms');
                // Start delay timer to retrieve script
                scriptRetrievalState.timeout = $timeout(doGetScript, delay);
                return scriptRetrievalState.deferred.promise;
            }

            function getScript() {
                if (isPreviewMode) {
                    resetScriptRetrievalState();
                    return retrieveScript();
                }

                return delayGetScript();
            }

            function finishSyncProcess() {
                isInSync = true;
                isSyncProcessed = true;
            }

            function resetSyncProcess() {
                isInSync = false;
                isSyncProcessed = false;
            }

            function checkInSync() {
                if (isInSync) {
                    return;
                }

                sendInSync();
                logService.info(logKey, 'Sync complete, sending in-sync');
            }

            function sendInSync() {
                // set is in sync to true first to avoid multiple calls to send in sync
                // if the sending fail, it will be set back to false
                finishSyncProcess();
                var playerGuid = registrationService.getPlayerId();
                // if we don't have any player ID or driver forced to bypass sending insync, don't send in-sync
                if (!playerGuid || halPlatformService.getFeatureSupport('bypass.send.insync')) {
                    return;
                }

                deviceAPI.sendInsyncStatus(playerGuid).then(function() {
                    finishSyncProcess();
                    storage.set('device.sync', 'lastSyncTime', new Date());
                }, function(err) {
                    isInSync = false;
                });
            }

            function parseAppScript(script) {
                var deferred = $q.defer();
                var onScriptBlocksProcessed = function() {
                    // for vod app, we need to modify the thumbnails url inside manifest
                    // to include the content updated time to fix APC-2517
                    // when parsing the VOD script, we're going to get all the thumbnails used and these thumbnails
                    // will have content updated time inside its URL
                    var vodBlock = syncState.scriptBlocks['vodapp_' + syncState.appId];
                    if (vodBlock) {
                        var files = syncState.manifest.files || undefined;
                        convertScriptBlockToDataSource(vodBlock, {files: files})
                            .then(function(res) {
                                var play = res.result.play;
                                var exactMatch = [];
                                // check if file in thumbnails exists in manifest,
                                // if it does, replace the manifest
                                // if not, add it
                                for (var i = 0; i < play.thumbnails.length; i++) {
                                    var thumbnailUrl = play.thumbnails[i];
                                    var thumbnailUrlLowered = thumbnailUrl.toLowerCase();
                                    var isInManifest = false;
                                    for (var j = 0; j < files.length; j++) {
                                        var manifestUrlLowered = files[j].url.toLowerCase();
                                        // if a longer version of the manifest url exists in thumbnail
                                        // and the actual manifest url (shorter) doesn't exists in thumbnails
                                        // replace manifest url with the thumbnails
                                        if (thumbnailUrlLowered.indexOf(manifestUrlLowered) >= 0 && exactMatch.indexOf(manifestUrlLowered) == -1) {
                                            isInManifest = true;
                                            if (thumbnailUrlLowered == manifestUrlLowered) {
                                                exactMatch.push(thumbnailUrlLowered);
                                            } else {
                                                files[j].url = thumbnailUrl;
                                            }
                                        }
                                    }

                                    if (!isInManifest) {
                                        files.push({url: thumbnailUrl});
                                    }
                                }

                                exactMatch = [];
                                syncState.manifest.files = files;
                                deferred.resolve();
                            }, deferred.resolve);
                    } else {
                        deferred.resolve();
                    }
                };

                parserService.parse('signs.parser.app-script', script).then(function(res) {
                    // Process app properties first
                    processApplicationProperties(res.result).then(
                        function() {
                            // check are we inside the sync process
                            var isvalid = isSyncSchedulesValid();
                            logService.debug(logKey, 'Is sync schedule valid? ', isvalid);
                            // if current time is invalid for sync schedule, abort
                            if (!isvalid) {
                                deferred.reject('schedule not valid');
                                return;
                            }

                            // Process all script blockss
                            processScriptBlocks(res.result)
                                .then(onScriptBlocksProcessed, deferred.reject);
                        });
                }, function(err) {
                    deferred.reject(err);
                });
                return deferred.promise;
            }

            function processApplicationProperties(blocks) {
                var deferred = $q.defer();
                for (var i = 0; i < blocks.length; i++) {
                    var block = blocks[i];
                    if (block.type.toLowerCase() != 'applicationproperties') {
                        // console.log("Skipping software package");
                        continue;
                    }

                    convertScriptBlockToDataSource(block).then(
                        function(res) {
                            // Update app properties
                            appProperties = res.result.properties;
                            applySettingsFromProperties();
                            deferred.resolve();
                        },
                        deferred.resolve
                    );
                    // Found the block, return promise
                    return deferred.promise;
                }

                // No block found so just resolve and return
                deferred.resolve();
                return deferred.promise;
            }

            function applySettingsFromProperties() {
                isUsingSynchronizedPlayback = appProperties['player.scheduling.mode'] == 'synchronized';

                // set download cycle
                if (appProperties['cdnagent.contentdownloadcycle']) {
                    var cycle = appProperties['cdnagent.contentdownloadcycle'];
                    cycle = parseInt(cycle, 10);
                    // if cycle less than 10 seconds, limit it to 10 seconds to avoid hitting the server too frequent
                    if (cycle < 10000) {
                        cycle = 10000;
                    }

                    syncCycle = cycle;
                }

                // Max sync cycule in syncrhonized playback is 30s
                if (isUsingSynchronizedPlayback && syncCycle > 30000) {
                    syncCycle = 30000;
                }

                // set sync schedule
                // reset sync schedules to empty if properties doesn't exists
                synchronizeSchedulesRaw = appProperties['cdnagent.syncschedule'] || '';
                syncSchedules = synchronizeSchedulesRaw ?
                    transformSyncSchedule(synchronizeSchedulesRaw) : [];

                // update player name if new
                if (appProperties['player.name'] !== storage.get('signs.registration', 'devicename')) {
                    storage.set('signs.registration', 'devicename', appProperties['player.name']);
                }

                updateCDN();
            }

            function isSyncSchedulesValid() {
                // if driver (i.e Preview) doesn't support sync schedule, returns true
                // on normal devices, this can only happen if a user do a Reset Content in an invalid schedule window
                if (halPlatformService.getFeatureSupport('ignore.sync.schedule')) {
                    return true;
                }

                var schedulesToCheck = overwriteSynchronizeSchedules.length > 0 ?
                    overwriteSynchronizeSchedules :
                    syncSchedules;

                // if we have no schedules, consider valid
                if (schedulesToCheck.length == 0) {
                    return true;
                }

                var currentDate = new Date();
                var valid = false;

                for (var i = 0; i < schedulesToCheck.length; i++) {
                    var schedule = schedulesToCheck[i];
                    // if the current day is not the same as schedule's day, check the next schedule
                    if (schedule.day != 'all') {
                        if (currentDate.getDay() != schedule.day) {
                            continue;
                        }
                    }

                    var from = new Date(currentDate.getTime());
                    from.setHours(schedule.from.hours);
                    from.setMinutes(schedule.from.minutes);
                    from.setSeconds(schedule.from.seconds);
                    from.setMilliseconds(0);

                    var to = new Date(currentDate.getTime());
                    to.setHours(schedule.to.hours);
                    to.setMinutes(schedule.to.minutes);
                    to.setSeconds(schedule.to.seconds);
                    to.setMilliseconds(999);

                    // if current time is in the valid range, schedule is valid
                    if (currentDate.getTime() >= from.getTime() &&
                        currentDate.getTime() < to.getTime()) {
                        valid = true;
                        break;
                    } else if (from.getTime() > to.getTime()) {// if from is bigger than to, check using start of the day -> to & from -> end of the day
                        var startOfTheDay = new Date();
                        startOfTheDay.setHours(0);
                        startOfTheDay.setMinutes(0);
                        startOfTheDay.setSeconds(0);
                        startOfTheDay.setMilliseconds(0);

                        var endOfTheDay = new Date();
                        endOfTheDay.setHours(23);
                        endOfTheDay.setMinutes(59);
                        endOfTheDay.setSeconds(59);
                        endOfTheDay.setMilliseconds(999);

                        if (currentDate.getTime() >= startOfTheDay.getTime() &&
                            currentDate.getTime() < to.getTime()) {
                            valid = true;
                            break;
                        } else if (currentDate.getTime() >= from.getTime() &&
                            currentDate.getTime() < endOfTheDay.getTime()) {
                            valid = true;
                            break;
                        }
                    }
                }

                return valid;
            }

            function transformSyncSchedule(res) {
                var response = [];
                // test data
                // res = '{7,23:30,14:38}';
                // sample data
                // {1,00:00,01:00},{2,00:00,01:00},{3,00:00,01:00}
                // {day,start,end}
                // day = 7 (all), 0-6 (Sunday to Saturday)
                if (res) {
                    // transform data
                    var rawData = res.split('{').join('"').split('}').join('"');
                    rawData = '{"schedules":[' + rawData + ']}';
                    rawData = JSON.parse(rawData);
                    // convert data to sync schedule object
                    for (var i = 0; i < rawData.schedules.length; i++) {
                        var item = rawData.schedules[i];
                        var schedule = {};
                        var datas = item.split(',');
                        if (datas[0] == '7') {
                            schedule.day = 'all';
                        } else {
                            schedule.day = parseInt(datas[0], 10);
                        }

                        var from = datas[1] + ':00';
                        from = from.split(':');
                        schedule.from = {
                            hours: parseInt(from[0], 10),
                            minutes: parseInt(from[1], 10),
                            seconds: parseInt(from[2], 10)
                        };
                        var to = datas[2] + ':59';
                        to = to.split(':');
                        schedule.to = {
                            hours: parseInt(to[0], 10),
                            minutes: parseInt(to[1], 10),
                            seconds: parseInt(to[2], 10)
                        };
                        response.push(schedule);
                    }
                }

                return response;
            }

            function processScriptBlocks(blocks) {
                var deferred = $q.defer();
                // Validate the app is good
                var validApp = false;
                var i;
                var block;
                for (i = 0; i < blocks.length; i++) {
                    block = blocks[i];
                    // we capture the first application in the script, and this is considered the default app
                    if (block.type.toLowerCase() == 'applicationxml') {
                        var appid = parseInt(block.id.toLowerCase().split('applicationxml_').join(''), 10);
                        syncState.appId = appid;
                        validApp = true;
                        break;
                    }
                }

                // app doesn't have app xml script, recheck
                if (validApp == false) {
                    deferred.reject('No valid app');
                    return deferred.promise;
                }

                // Set play ignore files
                var playIgnoreFileTypes = {};
                var cacheType = appProperties['play.cache'];
                if (!cacheType) {
                    cacheType = 'image';
                }

                cacheType = cacheType.toLowerCase();
                logService.debug(logKey, 'Play cache mode: ' + cacheType);
                switch (cacheType) {
                    case 'none':
                        playIgnoreFileTypes = {all: true};
                        break;
                    case 'video':
                    case 'audio':
                        playIgnoreFileTypes = {
                            bmp: true,
                            jpg: true,
                            jpeg: true,
                            tiff: true,
                            png: true,
                            gif: true
                        };
                        break;
                    case 'image':
                        playIgnoreFileTypes = {
                            mpg: true,
                            mpeg: true,
                            wmv: true,
                            mov: true,
                            mpg4: true,
                            mp4: true,
                            mpg2: true,
                            mp2: true,
                            ts: true,
                            wav: true,
                            mp3: true,
                            aac: true,
                            flac: true,
                            ogg: true,
                            mkv: true,
                            webm: true
                        };
                        break;
                }

                logService.debug(logKey, 'Play cache ignore list', playIgnoreFileTypes);

                // Manifest object for files
                var fileManifest = {
                    files: [],
                    size: 0
                };
                // Keeps a hash lookup of all files
                var files = {};
                var file;
                var util = new Utils();
                // app is good, save script to cache
                for (i = 0; i < blocks.length; i++) {
                    block = blocks[i];
                    if (block.type.toLowerCase() == 'softwarepackage') {
                        // console.log("Skipping software package");
                        continue;
                    }

                    // Check if this is a vod script
                    var isVod = block.type.toLowerCase() == 'vodapp' ? true : false;
                    var cardsContentIds = util.retrieveCardsContentIds(block.content);
                    // Go through each file in the block and add to the manifest
                    for (var k = 0; k < block.content.length; k++) {
                        var content = block.content[k];
                        file = content.path + '\\' + content.fileName;
                        var fileLower = file.toLowerCase();
                        var pathInfo = util.retrieveInfoFromRawContentPath(content.path);
                        // If this is a VOD script we selectivly cache content
                        if (isVod) {
                            // Skip files that are associated with the web theme
                            if (fileLower.indexOf('\\vodthemes\\') >= 0) {
                                // console.log("Skipping vod theme file: " + file);
                                continue;
                            }

                            // Skip system thumbnails files (_thumbnails, _vidthumbnails) as we're using the thumbnail from imagepreview handler
                            if (fileLower.indexOf('_thumbnail') >= 0 || fileLower.indexOf('_vidthumbnail') >= 0) {
                                continue;
                            }

                            // Determine the file extension
                            var extension = FileUtil.getFileExtension(fileLower);
                            // Only download if content is card OR content doesn't exists in ignore list
                            if (cardsContentIds[pathInfo.contentId.toString()] || (!playIgnoreFileTypes['all'] && !playIgnoreFileTypes[extension])) {
                                // Cache the content file
                                addFileToManifest(
                                    file,
                                    content.fileSize,
                                    files,
                                    fileManifest,
                                    {isCard: cardsContentIds[pathInfo.contentId.toString()], pathInfo: pathInfo});
                            } else {
                                logService.debug(logKey, 'Skipping file sync for: ' + file);
                            }

                            // add the thumbnail for the file eventhough we're not caching it
                            addFileToManifest(util.getContentPreviewUrl(file, 'mid'), content.fileSize, files, fileManifest);
                            addFileToManifest(util.getContentPreviewUrl(file, 'low'), content.fileSize, files, fileManifest);
                        } else {
                            // Default behaviour is to cache all files
                            addFileToManifest(
                                file,
                                content.fileSize,
                                files,
                                fileManifest,
                                {isCard: cardsContentIds[pathInfo.contentId.toString()], pathInfo: pathInfo});
                            // add the content thumbnails
                            for (var l = 0; l < content.thumbnails.length; l++) {
                                var thumbnail = content.thumbnails[l];
                                var fileThumbnail = content.path + '\\' + thumbnail.fileName;
                                addFileToManifest(
                                    fileThumbnail,
                                    thumbnail.fileSize,
                                    files,
                                    fileManifest);
                            }
                        }
                    }

                    syncState.scriptBlocks[block.id.toLowerCase()] = block;
                }

                // clear blocks from memory
                blocks = [];
                blocks = null;

                // Resolve the addresses using the mapping function of the signs router service
                var service = networkContentRouterService.findService('signs-content-routing-service');
                var map = null;
                for (i = 0; i < service.maps.length; i++) {
                    var m = service.maps[i];
                    if (m.name.toLowerCase() == 'signs-content-mapping') {
                        map = m;
                        break;
                    }
                }

                // Remove unused script blocks
                // TODO

                // Found the map that convers from local FastNetV2 to Appspace URL
                if (map) {
                    var resolvePromises = [];
                    for (i = 0; i < fileManifest.files.length; i++) {
                        file = fileManifest.files[i].url;
                        resolvePromises.push(map.map(file));
                    }

                    // Wait until all promises has been resolved
                    $q.all(resolvePromises).then(function(results) {
                        var originalFileManifest = $angular.copy(fileManifest);
                        // Reset manifest
                        fileManifest.files = [];
                        for (var i = 0; i < results.length; i++) {
                            var url = results[i];
                            // Set the resolved url;
                            fileManifest.files.push({
                                url: url,
                                fileSize: originalFileManifest.files[i].fileSize
                            });
                        }

                        // Save file manifest to storage
                        syncState.manifest = $angular.copy(fileManifest);
                        // All files for manifest resolved
                        resolvePromises = [];
                        resolvePromises = null;

                        // clear file manifest
                        fileManifest.files = [];
                        fileManifest.files = null;
                        fileManifest = null;
                        deferred.resolve();
                    }, function(err) {
                        logService.error(logKey, 'failed to resolve all manifest files');
                        // We still resovle if we cant resolve all manifest files
                        // clear file manifest
                        fileManifest.files = [];
                        fileManifest.files = null;
                        fileManifest = null;
                        deferred.resolve();
                    });
                    return deferred.promise;
                }

                deferred.resolve();
                return deferred.promise;
            }

            function processFilePath(file, opt) {
                if (opt.pathInfo && opt.pathInfo.contentId != 0 &&
                    (Boolean(opt.isCard) || dataUtil.includesString(VIDEO_THUMBNAIL_FILE_NAME_PREFIX, file))) {
                    // only lower case the front portion of the URL. Leave the contents and its subfolders intact.
                    var contentId = opt.pathInfo.contentId;
                    var contentIdWithBackslashes = BACKSLASH + contentId + BACKSLASH;
                    var fileSections = file.split(contentIdWithBackslashes);
                    file = fileSections[0].toLowerCase() + contentIdWithBackslashes + fileSections[1];
                } else {
                    file = file.toLowerCase();
                }

                return file;
            }

            function addFileToManifest(file, fileSize, files, fileManifest, opt) {
                if (!file) {
                    return;
                }

                file = processFilePath(file, opt || {});
                // Check has this file been added to the manifest already
                if (files[file]) {
                    return;
                }

                // ignore file that contains __macosx, .ds_store
                var filetoignore = {
                    macosx: /__macosx/i,
                    dsstore: /\.ds_store/i
                };
                for (var key in filetoignore) {
                    if (filetoignore[key].test(file)) {
                        return;
                    }
                }

                // Add the files to our lookup so we dont add it again
                files[file] = true;
                fileManifest.files.push({
                    url: file,
                    fileSize: fileSize
                });
                fileManifest.size += fileSize;
            }

            function saveScriptBlock(block) {
                var id = block.id.toLowerCase();
                // flag script block as valid
                validScriptBlocks[id] = true;
                // Check if this block is already cached
                var cachedBlock = storage.get('device.sync.script.blocks', id);
                if (cachedBlock) {
                    cachedBlock = JSON.parse(cachedBlock);
                    if (cachedBlock.hash == block.hash) {
                        return;
                    }
                }

                if (block.content) {
                    block.content = null;
                }

                // Block needs to be commited
                storage.set('device.sync.script.blocks', id, JSON.stringify(block));
                // Remove data source entity from storage so that it will be parsed again on the next call
                if (convertedScriptBlocks[id]) {
                    convertedScriptBlocks[id] = null;
                }
            }

            function removeUnusedScripts() {
                // Check do we have a collection of valid script blocks
                if (validScriptBlocks) {
                    // Get all blocks in storage
                    var blocks = storage.getKeys('device.sync.script.blocks', false);
                    // Enumerate and remove script blocks that are not in the valid collection
                    for (var i = 0; i < blocks.length; i++) {
                        var block = blocks[i];
                        if (!validScriptBlocks[block.key]) {
                            storage.removeKey('device.sync.script.blocks', block.key);
                            convertedScriptBlocks[block.key] = null;
                        }
                    }
                }

                return;
            }

            function checkIfAppIsAvailable() {
                // Check do we have a local timestamp
                if (!getCurrentTimestamp()) {
                    return $q.resolve(false);
                }

                // Check do we have a saved app id
                var appId = retrieveAppIdFromStorage();
                if (!appId) {
                    return $q.resolve(false);
                }

                var deferred = $q.defer();
                // Check do we have the app xml for this app
                retrieveAppScriptObject(appId).then(function() {
                    deferred.resolve(true);
                }, function(err) {
                    deferred.resolve(false);
                });
                return deferred.promise;
            }

            function retrieveScriptBlock(id) {
                var res = storage.get('device.sync.script.blocks', id);
                if (res) {
                    res = JSON.parse(res);
                }

                return res;
            }

            // subscribe for updates
            function subscribe(callback) {
                var l = subscribers.length;
                var found = false;
                for (var i = 0; i < l; i++) {
                    var cb = subscribers[i];
                    if (cb == callback) {
                        found = true;
                        break;
                    }
                }

                if (found == false) {
                    subscribers.push(callback);
                }
            }

            // unsub
            function unsubscribe(callback) {
                var l = subscribers.length;
                for (var i = 0; i < l; i++) {
                    var cb = subscribers[i];
                    if (cb == callback) {
                        subscribers.splice(i, 1);
                        break;
                    }
                }
            }

            // check if device is syncing
            function isSyncing() {
                return syncing;
            }

            function getSyncState() {
                return {
                    started: started,
                    syncing: isSyncing(),
                    isInSynced: isInSync,
                    isRetrievingScript: isRetrievingScript(),
                    error: isInSync ? null : lastError,
                    syncProgress: getSyncProgress(),
                    syncCycle: syncCycle,
                };
            }

            // check if application is available
            function isAppAvailable() {
                var deferred = $q.defer();
                checkIfAppIsAvailable().then(deferred.resolve, deferred.reject);
                return deferred.promise;
            }

            function notifySubscribers(event) {
                var subscribersLength = subscribers.length;
                for (var i = 0; i < subscribersLength; i++) {
                    var callback = subscribers[i];
                    if (!callback) {
                        continue;
                    }

                    callback(event);
                }
            }

            function getCurrentTimestamp() {
                return storage.get('device.sync', 'playerTimestamp');
            }

            function setCurrentTimestamp(value) {
                storage.set('device.sync', 'playerTimestamp', value);
            }

            function saveAppId(id) {
                storage.set('device.sync', 'appid', id);
            }

            function retrieveAppIdFromStorage() {
                return storage.get('device.sync', 'appid');
            }

            function retrieveAppIdFromQueryString() {
                // this is to handle signs in channel case
                var params = sessionService.getUrlParameters();
                if (!params) {
                    return;
                }

                return params.appid;
            }

            function convertScriptToDataSource(id) {
                var deferred = $q.defer();
                if (convertedScriptBlocks[id]) {
                    deferred.resolve(convertedScriptBlocks[id]);
                } else {
                    var block = retrieveScriptBlock(id);
                    if (block === undefined || block == null) {
                        deferred.reject('No script block: ' + id);
                    } else {
                        convertScriptBlockToDataSource(block).then(function(res) {
                            convertedScriptBlocks[id] = res;
                            deferred.resolve(res);
                        }, deferred.reject);
                    }
                }

                return deferred.promise;
            }

            function convertScriptBlockToDataSource(block, options) {
                var deferred = $q.defer();
                if (block === undefined || block == null) {
                    deferred.reject('No script block: ' + block.id);
                    return deferred.promise;
                }

                var parser = block.type;
                switch (block.type.toLowerCase()) {
                    case 'az':
                        parser = 'signs.parser.az';
                        break;
                    case 'scheduling':
                        parser = 'signs.parser.scheduling';
                        break;
                    case 'interfacedescription':
                        parser = 'signs.parser.interface-description';
                        break;
                    case 'applicationxml':
                        parser = 'signs.parser.application-xml';
                        break;
                    case 'applicationasset':
                        parser = 'signs.parser.application-asset';
                        break;
                    case 'textmsg':
                        parser = 'signs.parser.textmsg';
                        break;
                    case 'applicationproperties':
                        parser = 'signs.parser.application-properties';
                        break;
                    case 'rss':
                        parser = 'signs.parser.rss';
                        break;
                    case 'pmschedule':
                        parser = 'signs.parser.pmschedule';
                        break;
                    case 'workbook':
                        parser = 'signs.parser.workbook';
                        break;
                    case 'calendar':
                        parser = 'signs.parser.calendar';
                        break;
                    case 'contentdistribution':
                        parser = 'signs.parser.content-distribution';
                        break;
                    case 'weatherinfo':
                        parser = 'signs.parser.weatherinfo';
                        break;
                    case 'applicationlinking':
                        parser = 'signs.parser.application-linking';
                        break;
                    case 'infomenu':
                        parser = 'signs.parser.infomenu';
                        break;
                    case 'vodapp':
                        parser = 'signs.parser.vod-app';
                        break;
                    case 'campaign':
                        parser = 'signs.parser.campaign';
                        break;
                    case 'devicelicensing':
                        parser = 'signs.parser.device-licensing';
                        break;
                    case 'layouttargets':
                        parser = 'signs.parser.layout-targets';
                        break;
                    default:
                        deferred.resolve({id: block.id, type: block.type});
                        return deferred.promise;
                }

                parserService.parse(parser, block, options).then(function(res) {
                    // Add the hash for the DS for easier detection of changes
                    if (res && res.result) {
                        res.result.hash = block.hash;
                    }

                    deferred.resolve(res);
                }, function(err) {
                    deferred.reject(err);
                });
                return deferred.promise;
            }

            function isStarted() {
                return started;
            }

            function saveScriptFromSyncState() {
                if (!syncState.appId) {
                    return;
                }

                // save app ip
                saveAppId(syncState.appId);
                // save app properties
                saveAppProperties($angular.copy(appProperties));
                // reset valid script blocks
                validScriptBlocks = {};
                // save script blocks
                for (var key in syncState.scriptBlocks) {
                    var block = syncState.scriptBlocks[key];
                    saveScriptBlock(block);
                }

                // save manifest
                storage.set('device.sync', 'manifest', JSON.stringify(syncState.manifest));
                updateContentFileSizeMap();
                notifySubscribers({event: SYNC_EVENT.SCRIPT_SAVED});
            }
        }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');

    app.service('timestampService', ["SYNC_EVENT", "ASERRORCODE", "TIMESTAMP_EVENT", "PUSH_UPDATE_EVENT", "PLAYER_PROPERTY_KEYS", "TimestampPolling", "dataUtil", "logService", "syncService", "telemetryService", "pushUpdateService", "DateTimeUtil", "$timeout", function(
        SYNC_EVENT,
        ASERRORCODE,
        TIMESTAMP_EVENT,
        PUSH_UPDATE_EVENT,
        PLAYER_PROPERTY_KEYS,
        TimestampPolling,
        dataUtil,
        logService,
        syncService,
        telemetryService,
        pushUpdateService,
        DateTimeUtil,
        $timeout
    ) {
        var LOG_KEY = 'appspace.signs.sync';
        var NOT_FOUND_INDEX = -1;
        var LONG_POLLING_DURATION = {
            DEFAULT: 600000,
            MIN: 60000,
        };
        var PUSH_MESSAGE_DEBOUNCE = 1000;
        var pollingInterval = {
            short: undefined,
            long: undefined
        };
        var UPDATE_SOURCE = {
            PUSH: 'push notification service',
            POLLING: 'polling'
        };
        var timestampUpdate = {};
        timestampUpdate[UPDATE_SOURCE.PUSH] = undefined;
        timestampUpdate[UPDATE_SOURCE.POLLING] = undefined;
        var isPushUpdateEnabled;
        var isStarted = false;
        var subscribers = [];
        var currentTimestamp = {
            timestamp: undefined,
            source: undefined
        };
        var timestampPolling = new TimestampPolling();
        var pollingIntervalDuration;
        var dateTimeUtil = new DateTimeUtil();
        var debouncePromise;

        function getLongPollingInterval() {
            var duration = syncService.getCurrentAppProperty(PLAYER_PROPERTY_KEYS.PUSH_UPDATE_LONG_POLLING_DURATION);
            if (!dataUtil.isNumber(duration)) {
                return LONG_POLLING_DURATION.DEFAULT;
            }

            duration = parseInt(duration, 10);
            return duration === 0 ?
                duration : Math.max(duration, LONG_POLLING_DURATION.MIN);
        }

        function updatePollingInterval() {
            pollingInterval.short = (syncService.getSyncState() || {}).syncCycle;
            pollingInterval.long = getLongPollingInterval();
            pollingIntervalDuration = isPushUpdateEnabled ? pollingInterval.long : pollingInterval.short;
        }

        function onTimestampUpdated(timestamp, source) {
            if (timestamp) {
                // Convert timestamp value to string ticks to ensure consistency for checking
                timestamp = dateTimeUtil.parseUtcString(timestamp).getTime();
            }

            timestampUpdate[source] = timestamp;

            // This is the first time that we are retrieving the timestamp,
            // so we need to update both push and polling timestamps to ensure consistency
            if (dataUtil.isNullOrUndefined(currentTimestamp.timestamp)) {
                timestampUpdate[UPDATE_SOURCE.PUSH] = timestamp;
                timestampUpdate[UPDATE_SOURCE.POLLING] = timestamp;
            }

            // Check for timestamp discrepancy between push notification service and polling updates
            if (isPushUpdateEnabled) {
                if (source === UPDATE_SOURCE.PUSH && timestamp === timestampUpdate[UPDATE_SOURCE.POLLING]) {
                    logService.warn(LOG_KEY, 'Possibly delayed or redundant push notification service message');
                } else if (source === UPDATE_SOURCE.POLLING && timestamp !== timestampUpdate[UPDATE_SOURCE.PUSH]) {
                    logService.warn(LOG_KEY, 'Possibly missed push notification service message');
                    telemetryService.addToQueue({
                        module: 'device-channel-synchronization',
                        type: 'timestamp-discrepancy',
                        description: 'Timestamp retrieved from long polling is different from Push Notification Service',
                        metadata: {
                            'pns_timestamp': timestampUpdate[UPDATE_SOURCE.PUSH],
                            'polling_timestamp': timestamp,
                        },
                    });
                }

                // Set the timestamp value for both sources to be the same to avoid re-detecting timestamp discrepancy
                // if there is no timestamp changes in the subsequent retrieved timestamp
                timestampUpdate[UPDATE_SOURCE.POLLING] = timestamp;
                timestampUpdate[UPDATE_SOURCE.PUSH] = timestamp;
            }

            currentTimestamp = {
                timestamp: timestamp,
                source: source
            };
            notifySubscribers(TIMESTAMP_EVENT.RETRIEVED, currentTimestamp);
            updatePollingConfiguration();
        }

        function getCurrentTimestampFromServer() {
            var finalize = function(result) {
                result = result || {};
                onTimestampPollingEvent(result.event, result.data);
            };

            timestampPolling.retrieveCurrentTimestamp().then(finalize, finalize);
        }

        function updatePollingConfiguration() {
            var currentPollingIntervalDuration = $angular.copy(pollingIntervalDuration);
            updatePollingInterval();
            if (currentPollingIntervalDuration === pollingIntervalDuration) {
                return;
            }

            timestampPolling.updateConfiguration({
                interval: pollingIntervalDuration,
                logKey: LOG_KEY,
                onEvent: onTimestampPollingEvent,
            });
        }

        function onTimestampPollingEvent(event, data) {
            logService.debug(LOG_KEY, 'Received timestamp polling ' + event + ' event with data: ' + JSON.stringify(data));
            switch (event) {
                case SYNC_EVENT.TIMESTAMP_RETRIEVED:
                    onTimestampUpdated(data, UPDATE_SOURCE.POLLING);
                    break;
                case SYNC_EVENT.ERROR_RETRIEVING_TIMESTAMP:
                    notifySubscribers(TIMESTAMP_EVENT.FAILED, {
                        error: data,
                        source: UPDATE_SOURCE.POLLING
                    });
                    break;
            }
        }

        function onPushUpdateEnabled() {
            isPushUpdateEnabled = true;
            // Consider that push update timestamp is the same as polled timestamp once enabled
            timestampUpdate[UPDATE_SOURCE.PUSH] = $angular.copy(timestampUpdate[UPDATE_SOURCE.POLLING]);
        }

        function onPushUpdate(event, data, payload) {
            var skipTelemetryReporting = false;
            switch (event) {
                case PUSH_UPDATE_EVENT.INITIALIZED:
                    skipTelemetryReporting = true;
                    onPushUpdateEnabled();
                    updatePollingConfiguration();
                    break;
                case PUSH_UPDATE_EVENT.STARTED:
                    skipTelemetryReporting = true;
                    onPushUpdateEnabled();
                    // To get the current active timestamp from server
                    getCurrentTimestampFromServer();
                    updatePollingConfiguration();
                    break;
                case PUSH_UPDATE_EVENT.STOPPED:
                    isPushUpdateEnabled = false;
                    skipTelemetryReporting = true;
                    updatePollingConfiguration();
                    break;
                case PUSH_UPDATE_EVENT.TIMESTAMP_UPDATE:
                    isPushUpdateEnabled = true;
                    debouncePushTimestampUpdate(data);
                    break;
                case PUSH_UPDATE_EVENT.CHANNEL_ASSIGNED:
                case PUSH_UPDATE_EVENT.DEVICE_TAGS_UPDATED:
                    getCurrentTimestampFromServer();
                    break;
                case PUSH_UPDATE_EVENT.DEVICE_UNREGISTERED:
                    notifySubscribers(TIMESTAMP_EVENT.FAILED, {
                        error: {
                            errorCode: ASERRORCODE.DEVICE_NOT_FOUND,
                            error: 'Device has been unregistered'
                        },
                        source: UPDATE_SOURCE.PUSH
                    });
                    break;
            }

            if (skipTelemetryReporting) {
                return;
            }

            telemetryService.addToQueue({
                module: 'device-channel-synchronization',
                type: 'push-update-event',
                description: 'Push update event for device channel synchronization received',
                metadata: {
                    event: event,
                    data: data,
                    payload: payload,
                },
            });
        }

        function cancelDebouncePushTimestampUpdate() {
            if (!debouncePromise) {
                return;
            }

            $timeout.cancel(debouncePromise);
            debouncePromise = undefined;
        }

        function debouncePushTimestampUpdate(data) {
            cancelDebouncePushTimestampUpdate();
            debouncePromise = $timeout(processDebouncePushTimestampUpdate.bind(null, data), PUSH_MESSAGE_DEBOUNCE);
        }

        function processDebouncePushTimestampUpdate(data) {
            onTimestampUpdated(data, UPDATE_SOURCE.PUSH);
        }

        function start() {
            if (isStarted) {
                updatePollingConfiguration();
                return;
            }

            isStarted = true;
            pushUpdateService.subscribe(onPushUpdate);
            updatePollingConfiguration();
            timestampPolling.start();
        }

        function stop() {
            if (!isStarted) {
                return;
            }

            isStarted = false;
            pushUpdateService.unsubscribe(onPushUpdate);
            timestampPolling.stop();
            pollingIntervalDuration = undefined;
        }

        function subscribe(callback) {
            if (!callback || subscribers.indexOf(callback) !== NOT_FOUND_INDEX) {
                return;
            }

            subscribers.push(callback);
        }

        function unsubscribe(callback) {
            var subscriberIndex = subscribers.indexOf(callback);
            if (subscriberIndex === NOT_FOUND_INDEX) {
                return;
            }

            subscribers.splice(subscriberIndex, 1);
        }

        function notifySubscribers(event, data) {
            for (var i = 0; i < subscribers.length; i++) {
                subscribers[i](event, data);
            }
        }

        function getTimestampFailCount() {
            return timestampPolling.getCurrentState().failCount;
        }

        function getConfiguration() {
            return {
                isSyncCycleEnabled: !isPushUpdateEnabled,
                pollingInterval: pollingInterval,
            };
        }

        return {
            start: start,
            stop: stop,
            subscribe: subscribe,
            unsubscribe: unsubscribe,
            getTimestampFailCount: getTimestampFailCount,
            getCurrentTimestamp: getCurrentTimestampFromServer,
            getConfiguration: getConfiguration,
        };
    }]);
})(window.angular);

(function($angular) {
	'use strict';
	var angular = $angular;
	var app = angular.module('signsHTML5');

	// Temporary class

	app.service('watchdogService', ["$q", "$interval", "$location", "coreFrameworkService", "halPlatformService", function($q, $interval, $location, coreFrameworkService, halPlatformService) {
        var watchdogIntervalPromise;
        var error;

        function start() {
            stop();
            coreFrameworkService.isReady().then(function() {
                watchdogIntervalPromise = $interval(watchdogCheck, 30000);
                watchdogCheck();
            });
        }

        function stop() {
            if (watchdogIntervalPromise) {
                $interval.cancel(watchdogIntervalPromise);
                watchdogIntervalPromise = undefined;
            }
        }

        function watchdogCheck() {
            error = null;

            // ping the device
            var pingDevicePromise = halPlatformService.pingDevice();
            if (pingDevicePromise) {
                pingDevicePromise.then($angular.noop, $angular.noop);
            }

            // check the system clock is correct
            var date = new Date();
            if (date.getFullYear() < 2016) {
                error = 'Attempting to connect to your time server';
            }
            if (error) {
                $location.url('/tv/watchdog');
            }
        }

        function getError() {
            return error;
        }

        // Public interface
        return {
            start: start,
            stop: stop,
			getError: getError
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');

    app.service('rebootService',
        ["$interval", "$timeout", "logService", "coreConfigurationService", "halPlatformService", "halNetworkService", "FEATURE_CODE", function($interval, $timeout, logService, coreConfigurationService, halPlatformService, halNetworkService, FEATURE_CODE) {
            var checkDuration = 20000;// ms
            var rebootTime;
            var checkIntervalId;
            var rawreboottime;
            var logKey = 'appspace.signs';

            // Public interface
            return {
                setRebootTime: setRebootTime
            };

            // SET REBOOT TIME
            function setRebootTime(reboottimevalue) {
                if (rawreboottime == reboottimevalue) {
                    return;
                }

                rawreboottime = reboottimevalue;
                var reboottimevaluesplits = reboottimevalue.split(':');
                // invalid value
                if (reboottimevaluesplits.length < 2) {
                    return;
                }

                rebootTime = new Date();
                rebootTime.setHours(reboottimevaluesplits[0]);
                rebootTime.setMinutes(reboottimevaluesplits[1]);
                rebootTime.setSeconds(0);
                rebootTime.setMilliseconds(0);
                resetCheck();
            }

            function cancelInterval() {
                if (!checkIntervalId) {
                    return;
                }

                $interval.cancel(checkIntervalId);
                checkIntervalId = undefined;
            }

            function resetCheck() {
                cancelInterval();
                if (!rebootTime) {
                    return;
                }

                checkIntervalId = $interval(checkHandler, checkDuration);
            }

            function checkHandler() {
                var now = new Date();

                // if now is not the configured time to reboot, don't reboot
                if (now.getHours() !== rebootTime.getHours() || now.getMinutes() !== rebootTime.getMinutes()) {
                    return;
                }

                // if last reboot is within 1 minute, don't reboot
                var lastReboot = coreConfigurationService.get('device.state', 'lastRebootTime');
                if (lastReboot && now.getTime() - parseInt(lastReboot, 10) < 60000) {
                    logService.warn(logKey, 'Last daily reboot is less than a minute ago. Skipping.');
                    return;
                }

                // execute reboot
                cancelInterval();
                coreConfigurationService.set('device.state', 'lastRebootTime', now.getTime());
                halNetworkService.stopServer();
                var delay = halPlatformService.getFeatureSupport(FEATURE_CODE.DEVICE_REBOOT_DELAY);
                logService.debug(logKey, 'Executing daily reboot for ' + rebootTime.getHours() + ':' + rebootTime.getMinutes() + ')');
                if (!delay) {
                    halPlatformService.reboot();
                    return;
                }
                logService.debug(logKey, 'Device requires ' + delay + 'ms delay for reboot');
                $timeout(halPlatformService.reboot, delay);
            }
        }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');
    app.factory('helperUtil', ["$timeout", function($timeout) {
        function clearTimeoutIfActive(timeoutThread) {
            if (!timeoutThread) {
                return;
            }
            $timeout.cancel(timeoutThread);
        }

        function isFunction(fn) {
            return fn && typeof fn === 'function';
        }

        return {
            clearTimeoutIfActive: clearTimeoutIfActive,
            isFunction: isFunction
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    'use strict';

    var app = angular.module('signsHTML5');
    app.factory('interleaveUtil', ["INTERLEAVE_TYPE", "dataUtil", function(INTERLEAVE_TYPE, dataUtil) {
        function isInterleaveTypeValid(interleave) {
            // check against constant
            return dataUtil.includesValue(interleave.option, Object.keys(INTERLEAVE_TYPE).map(function(k) {
                return INTERLEAVE_TYPE[k];
            }));
        }

        function processInterleaveProperty(appDefaultInterleaveConfiguration, interleaveValue) {
            var interleaveConfiguration = $angular.copy(appDefaultInterleaveConfiguration);
            if (!interleaveValue) {
                return interleaveConfiguration;
            }

            if (dataUtil.isValueTrue(interleaveValue.toLowerCase())) {
                interleaveConfiguration.enable = true;
                return interleaveConfiguration;
            }

            var interleave = dataUtil.tryParseJSON(interleaveValue.toLowerCase());
            if (!interleave || !isInterleaveTypeValid(interleave)) {
                return interleaveConfiguration;
            }

            interleaveConfiguration.enable = true;
            interleaveConfiguration.type = dataUtil.isNumber(interleave.option) ? interleave.option : INTERLEAVE_TYPE.DEFAULT;
            interleaveConfiguration.options = {
                contentCount: dataUtil.isNumber(interleave.contentcount) ? interleave.contentcount : undefined
            };

            return interleaveConfiguration;
        }

        return {
            processInterleaveProperty: processInterleaveProperty
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    // Reads file into string
    app.factory('tickerUtil', ["dataUtil", "TICKER_SPEED", "TICKER_DIRECTION", function(dataUtil, TICKER_SPEED, TICKER_DIRECTION) {
        function getTickerSpeed(speed) {
            if (!speed) {
                return;
            }
            var speedInt = dataUtil.stripSpaces(speed).toLowerCase();
            return TICKER_SPEED[speedInt];
        }

        function getTickerDirection(direction) {
            if (!direction) {
                return;
            }
            var directionInt = dataUtil.stripSpaces(direction);
            return TICKER_DIRECTION[directionInt];
        }

        return {
            getTickerSpeed: getTickerSpeed,
            getTickerDirection: getTickerDirection
        };
    }]);
})(window.angular);


(function(angular) {
    'use strict';
    var app = angular.module('signsHTML5');

    app.factory('WidgetEditor', ["$document", "$timeout", "$window", "dataUtil", "EDITOR_SETTINGS", "halRemoteService", "REMOTEVCODE", "REMOTEEVENTTYPE", "MOUSE_CLICK", function($document, $timeout, $window, dataUtil, EDITOR_SETTINGS, halRemoteService, REMOTEVCODE, REMOTEEVENTTYPE, MOUSE_CLICK) {
        var EDITOR_VISIBILITY = {
            show: {
                method: 'on',
                visibility: 'visible'
            },
            hide: {
                method: 'off',
                visibility: 'hidden'
            }
        };
        function WidgetEditor(widgetDom, widgetId, onWidgetPositionChanged, onWidgetStartMove, onWidgetStopMove, onWidgetMove, scale) {
            var visualScale = scale;
            var vectorConfig = {};
            vectorConfig[REMOTEVCODE.up] = {
                left: 0,
                top: -1

            };
            vectorConfig[REMOTEVCODE.down] = {
                left: 0,
                top: 1
            };
            vectorConfig[REMOTEVCODE.left] = {
                left: -1,
                top: 0
            };
            vectorConfig[REMOTEVCODE.right] = {
                left: 1,
                top: 0
            };
            var scaleConfig = {};
            scaleConfig[REMOTEVCODE.plus] = 'up';
            scaleConfig[REMOTEVCODE.minus] = 'down';
            var anchors = {
                left: {
                    getStyle: function() {
                        return {
                            'left': getAnchorPositionOrigin(),
                            'top': getAnchorPositionCenter(),
                            'cursor': EDITOR_SETTINGS.CURSOR_LEFT_RIGHT_STYLE
                        };
                    },
                    movements: [getAnchorLeftMovementPosition]
                },
                top: {
                    getStyle: function() {
                        return {
                            'left': getAnchorPositionCenter(),
                            'top': getAnchorPositionOrigin(),
                            'cursor': EDITOR_SETTINGS.CURSOR_TOP_BOTTOM_STYLE,
                        };
                    },
                    movements: [getAnchorTopMovementPosition]
                },
                right: {
                    getStyle: function() {
                        return {
                            'right': getAnchorPositionOrigin(),
                            'top': getAnchorPositionCenter(),
                            'cursor': EDITOR_SETTINGS.CURSOR_LEFT_RIGHT_STYLE,
                        };
                    },
                    movements: [getAnchorRightMovementPosition]
                },
                bottom: {
                    getStyle: function() {
                        return {
                            'left': getAnchorPositionCenter(),
                            'bottom': getAnchorPositionOrigin(),
                            'cursor': EDITOR_SETTINGS.CURSOR_TOP_BOTTOM_STYLE,
                        };
                    },
                    movements: [getAnchorBottomMovementPosition]
                },
                topleft: {
                    getStyle: function() {
                        return {
                            'left': getAnchorPositionOrigin(),
                            'top': getAnchorPositionOrigin(),
                            'cursor': EDITOR_SETTINGS.CURSOR_TOP_LEFT_BOTTOM_RIGHT_STYLE,
                        };
                    },
                    movements: [getAnchorTopMovementPosition, getAnchorLeftMovementPosition],
                    lockedStateMovement: getTopLeftPlacement
                },
                bottomleft: {
                    getStyle: function() {
                        return {
                            'left': getAnchorPositionOrigin(),
                            'bottom': getAnchorPositionOrigin(),
                            'cursor': EDITOR_SETTINGS.CURSOR_TOP_RIGHT_BOTTOM_LEFT_SYTLE,
                        };
                    },
                    movements: [getAnchorBottomMovementPosition, getAnchorLeftMovementPosition],
                    lockedStateMovement: getBottomLeftPlacement
                },
                topright: {
                    getStyle: function() {
                        return {
                            'right': getAnchorPositionOrigin(),
                            'top': getAnchorPositionOrigin(),
                            'cursor': EDITOR_SETTINGS.CURSOR_TOP_RIGHT_BOTTOM_LEFT_SYTLE,
                        };
                    },
                    movements: [getAnchorTopMovementPosition, getAnchorRightMovementPosition],
                    lockedStateMovement: getTopRightPlacement
                },
                bottomright: {
                    getStyle: function() {
                        return {
                            'right': getAnchorPositionOrigin(),
                            'bottom': getAnchorPositionOrigin(),
                            'cursor': EDITOR_SETTINGS.CURSOR_TOP_LEFT_BOTTOM_RIGHT_STYLE
                        };
                    },
                    movements: [getAnchorBottomMovementPosition, getAnchorRightMovementPosition],
                    lockedStateMovement: getBottomRightPlacement
                }
            };

            this.isSelected = undefined;
            var isInitialized = false;
            var isWidgetMoving = false;
            var aspectRatio = undefined;
            var editorElementId = 'editor-border-' + widgetId;
            var inputStartState;
            var anchorDownState;
            var inputMoveState;
            var delayInputEventEndPromise;
            var currentWidget = this;
            var lastAnchorMoveEvent = {};
            var shiftKeyTimeoutPromise;
            var isSnapEnabled;

            this.init = function() {
                if (isInitialized) {
                    return;
                }
                isInitialized = true;
                widgetDom.on('mousedown', onWidgetMouseDown);
                halRemoteService.registerEvents(onWidgetKeyDown);
                drawAnchors();
                widgetDom.css('cursor', 'move');
            };

            this.onWidgetMouseDown = function(event) {
                if (!isInitialized) {
                    return;
                }
                onWidgetMouseDown(event);
            };

            this.dispose = function() {
                notifyNewPosition();
                widgetDom.off('mousedown');
                widgetDom.off('keydown');
                this.deselect();
                removeAnchors();
                getElement(editorElementId).remove();
                inputStartState = undefined;
                isInitialized = false;
                editorElementId = undefined;
                anchorDownState = undefined;
                inputMoveState = undefined;
                $document.off('mouseup', onAnchorUp);
                $document.off('mouseup', onWidgetMouseUp);
                $document.off('mousemove', onAnchorMove);
                $document.off('mousemove', onWidgetMouseMove);
            };

            this.select = function() {
                if (this.isSelected) {
                    return;
                }
                this.isSelected = true;
                activateAnchorElements(EDITOR_VISIBILITY.show);
            };

            this.deselect = function() {
                if (!this.isSelected) {
                    return;
                }
                this.isSelected = false;
                activateAnchorElements(EDITOR_VISIBILITY.hide);
            };

            this.move = function(key, value) {
                var vector = getVector(REMOTEVCODE[key]);
                if (!vector) {
                    return;
                }
                cancelDelayInputEventEndTimeout();
                onKeyDown(vector, value || getDefaultMovementValue());
                delayInputEventEnd();
            };

            this.scale = function(option) {
                if (option !== 'up' && option !== 'down') {
                    return;
                }
                cancelDelayInputEventEndTimeout();
                scaleWidget(option);
                delayInputEventEnd();
            };

            this.enableSnap = function() {
                isSnapEnabled = true;
            };

            this.disableSnap = function() {
                isSnapEnabled = false;
            };

            this.setVisualScale = function(scale) {
                visualScale = scale;
                this.deselect();
                removeAnchors();
                drawAnchors();
                this.select();
            };

            function delayInputEventEnd() {
                // delay the undimming so that the widget will appear dimmed for at least a split second
                delayInputEventEndPromise = $timeout(function() {
                    onInputEventEnd();
                }, 100);
            }

            function cancelDelayInputEventEndTimeout() {
                if (!delayInputEventEndPromise) {
                    return;
                }
                $timeout.cancel(delayInputEventEndPromise);
                delayInputEventEndPromise = undefined;
            }

            function onAnchorDown(event) {
                if (event.shiftKey && !aspectRatio) {
                    // when shift key is held when the editor was not active
                    onShiftKeyHeld({eventType: REMOTEEVENTTYPE.buttonDown});
                }
                isWidgetMoving = true;
                var currentPosition = getCurrentPosition();
                var anchorTypeSelected = angular.element(event.target).attr('anchor');
                anchorDownState = {
                    vertical: {
                        cursor: event.clientY,
                        length: currentPosition.height,
                        position: currentPosition.y,
                    },
                    horizontal: {
                        cursor: event.clientX,
                        length: currentPosition.width,
                        position: currentPosition.x
                    },
                    movements: anchors[anchorTypeSelected].movements,
                    lockedStateMovement: anchors[anchorTypeSelected].lockedStateMovement,
                    eventType: {
                        type: EDITOR_SETTINGS.MOVE_TYPE_ANCHOR,
                        anchor: anchorTypeSelected
                    }
                };
                onMouseDown(event, onAnchorUp, onAnchorMove);
            }

            function recalculateWidgetSize() {
                // timeout is added to handle cases where the shift key and mouse are released simultaneously
                // so that the widget will not unlock the aspect ratio when that happens
                shiftKeyTimeoutPromise = $timeout(function() {
                    // to handle shift key being pressed after releasing mouse and
                    // mouse up event is delayed due to 3-finger drag on Mac
                    if (!anchorDownState) {
                        return;
                    }
                    setAnchorMovePosition(lastAnchorMoveEvent);
                }, 1000);
            }

            function clearShiftKeyTimeout() {
                if (!shiftKeyTimeoutPromise) {
                    return;
                }
                $timeout.cancel(shiftKeyTimeoutPromise);
                shiftKeyTimeoutPromise = undefined;
            }

            function getAspectRatio() {
                if (anchorDownState) {
                    return anchorDownState.horizontal.length / anchorDownState.vertical.length;
                }
                // if shift key is pressed before anchor down
                var currentPosition = getCurrentPosition();
                return currentPosition.width / currentPosition.height;
            }

            function onShiftKeyHeld(event) {
                var isKeyDown = event.eventType === REMOTEEVENTTYPE.buttonDown;
                aspectRatio = isKeyDown ? getAspectRatio() : undefined;
                // don't do anything if widget is not resizing / moving
                if (!isWidgetMoving) {
                    return;
                }
                // to handle shift key being released or pressed mid-resizing
                recalculateWidgetSize();
            }

            function onAnchorUp() {
                clearShiftKeyTimeout();
                isWidgetMoving = false;
                aspectRatio = undefined;
                anchorDownState = undefined;
                lastAnchorMoveEvent = {};
                onMouseUp(onAnchorUp, onAnchorMove);
            }

            function moveWidgetByKeyEvent(position, event) {
                onInputMove(dataUtil.toAppPlacement(position), event);
                setWidgetPosition(position);
                startMovingWidget();
            }

            function onAnchorMove(event) {
                clearShiftKeyTimeout();
                event.preventDefault();
                setAnchorMovePosition(event);
            }

            function setAnchorMovePosition(event) {
                clearShiftKeyTimeout();
                lastAnchorMoveEvent = {clientX: event.clientX, clientY: event.clientY};
                var position = inputStartState;
                for (var i in anchorDownState.movements) {
                    position = dataUtil.mergeObject(
                        position,
                        anchorDownState.movements[i](event));
                }
                // if aspect ratio is locked and the current anchor is one of the corner anchors
                if (aspectRatio && anchorDownState && anchorDownState.lockedStateMovement) {
                    position = anchorDownState.lockedStateMovement(inputStartState, position);
                }
                onInputMove({
                    x: position.left,
                    y: position.top,
                    width: position.width,
                    height: position.height
                }, anchorDownState.eventType);
                setWidgetPosition(position);
            }

            function markInitialPlacement() {
                if (inputStartState) {
                    return;
                }
                var currentPosition = getCurrentPosition();
                inputStartState = {
                    left: currentPosition.x,
                    top: currentPosition.y,
                    width: currentPosition.width,
                    height: currentPosition.height
                };
            }

            function startMovingWidget() {
                if (!onWidgetStartMove) {
                    return;
                }
                onWidgetStartMove({
                    id: widgetId
                });
            }

            function onInputMove(position, event) {
                if (!onWidgetMove) {
                    return;
                }
                // snapping with locked aspect ratio is only valid when resizing with either of the corner anchors
                if (event && anchorDownState && anchorDownState.lockedStateMovement) {
                    // set aspect ratio so that it will be taken into account when snapping mid-resizing
                    // undefine aspect ratio when shift key is released mid-resizing so that snapping doesn't attempt to lock the size
                    event.aspectRatio = aspectRatio || undefined;
                }
                onWidgetMove(position, event);
            }

            function onInputEventEnd() {
                cancelDelayInputEventEndTimeout();
                if (onWidgetStopMove) {
                    onWidgetStopMove();
                }
                notifyNewPosition();
                inputStartState = undefined;
                inputMoveState = undefined;
            }

            function getVector(vcode) {
                return vectorConfig[vcode];
            }

            function getScaledSize(scaleOption) {
                if (!aspectRatio) {
                    aspectRatio = getAspectRatio();
                }
                var currentState = getCurrentPosition();
                var lengthDelta = getDefaultMovementValue() * (scaleOption === 'down' ? -1 : 1);
                currentState.width = currentState.width + lengthDelta;
                return getBottomRightPlacement(inputStartState, currentState);
            }

            function scaleWidget(option) {
                markInitialPlacement();
                if (!option) {
                    return;
                }
                var newSize = getScaledSize(option);
                if (!newSize) {
                    return;
                }
                inputMoveState = {
                    width: newSize.width,
                    height: newSize.height
                };
                moveWidgetByKeyEvent(inputMoveState, {type: EDITOR_SETTINGS.RESIZE_TYPE_KEYDOWN});
            }

            function getDefaultMovementValue() {
                return parseInt(0.01 * Math.max($window.innerWidth, $window.innerHeight));
            }

            function onKeyDown(vector, movementValue) {
                markInitialPlacement();
                if (!vector) {
                    return;
                }
                var currentPosition = getCurrentPosition();
                inputMoveState = {
                    left: parseInt(currentPosition.x + (vector.left * movementValue), 10),
                    top: parseInt(currentPosition.y + (vector.top * movementValue), 10)
                };
                moveWidgetByKeyEvent(inputMoveState, {type: EDITOR_SETTINGS.MOVE_TYPE_KEYDOWN});
            }

            function onWidgetKeyDown(event) {
                if (!currentWidget.isSelected || !isInitialized) {
                    return;
                }
                var vCode = event.vcode;
                // for shift
                if (vCode === REMOTEVCODE.shift) {
                    onShiftKeyHeld(event);
                    return;
                }
                // don't process up, down, left, right if the widget is currently being resized or moved (by mouse)
                if (isWidgetMoving) {
                    return;
                }

                if (event.eventType !== REMOTEEVENTTYPE.buttonDown) {
                    onInputEventEnd();
                    return;
                }
                switch (vCode) {
                    case REMOTEVCODE.plus:
                    case REMOTEVCODE.minus:
                        scaleWidget(scaleConfig[vCode]);
                        return;
                    case REMOTEVCODE.up:
                    case REMOTEVCODE.down:
                    case REMOTEVCODE.left:
                    case REMOTEVCODE.right:
                        onKeyDown(getVector(vCode), event.shiftKey ? 1 : getDefaultMovementValue());
                        return;
                }
            }

            function onWidgetMouseDown(event) {
                if (event.which != MOUSE_CLICK.LEFT || !isInitialized) {
                    return;
                }
                isWidgetMoving = true;
                var currentPosition = getCurrentPosition();
                inputMoveState = {
                    left: event.clientX - currentPosition.x,
                    top: event.clientY - currentPosition.y
                };
                onMouseDown(event, onWidgetMouseUp, onWidgetMouseMove);
            }

            function onWidgetMouseMove(event) {
                if (!isInitialized) {
                    return;
                }
                var newPosition = {
                    left: event.clientX - inputMoveState.left,
                    top: event.clientY - inputMoveState.top
                };
                onInputMove({
                    x: newPosition.left,
                    y: newPosition.top,
                    width: inputStartState.width,
                    height: inputStartState.height
                });
                setWidgetPosition(newPosition);
            }

            function onWidgetMouseUp(event) {
                event.stopPropagation();
                onMouseUp(onWidgetMouseUp, onWidgetMouseMove);
            }

            function notifyNewPosition() {
                if (!onWidgetPositionChanged || !inputStartState || !widgetDom) {
                    return;
                }
                var currentPosition = getCurrentPosition();
                if (currentPosition.x == inputStartState.left &&
                    currentPosition.y == inputStartState.top &&
                    currentPosition.width == inputStartState.width &&
                    currentPosition.height == inputStartState.height) {
                    return;
                }
                onWidgetPositionChanged({
                    id: widgetId,
                    position: {
                        x: currentPosition.x,
                        y: currentPosition.y,
                        width: currentPosition.width,
                        height: currentPosition.height
                    }
                });
            }

            function onMouseDown(event, callbackMouseUp, callbackMouseMove) {
                event.stopPropagation();
                $document.on('mouseup', callbackMouseUp);
                $document.on('mousemove', callbackMouseMove);
                markInitialPlacement();
                startMovingWidget();
            }

            function getRelativeWidth(height) {
                return Math.round(height * aspectRatio);
            }

            function getRelativeHeight(width) {
                return Math.round(width / aspectRatio);
            }

            function getRelativePosition(initialLength, initialPosition, currentLength) {
                var lengthDelta = currentLength - initialLength;
                var maxPosition = initialPosition + initialLength;
                var newPosition = initialPosition - lengthDelta;
                return newPosition < maxPosition ? newPosition : maxPosition;
            }

            function getRelativePlacement(lengthType, positionType, reversedLengthType, getLength, initialState, currentState, useReverseForPosition) {
                var positionLengthType = useReverseForPosition ? reversedLengthType : lengthType;
                currentState[lengthType] = getLength(currentState[reversedLengthType]);
                currentState[positionType] = getRelativePosition(initialState[positionLengthType], initialState[positionType], currentState[positionLengthType]);
                return currentState;
            }

            function getTopLeftPlacement(initialState, currentState) {
                return getRelativePlacement('width', 'left', 'height', getRelativeWidth, initialState, currentState);
            }

            function getTopRightPlacement(initialState, currentState) {
                return getRelativePlacement('width', 'top', 'height', getRelativeWidth, initialState, currentState, true);
            }

            function getBottomLeftPlacement(initialState, currentState) {
                return getRelativePlacement('height', 'left', 'width', getRelativeHeight, initialState, currentState, true);
            }

            function getBottomRightPlacement(initialState, currentState) {
                currentState.height = getRelativeHeight(currentState.width);
                currentState.top = initialState.top;
                currentState.left = initialState.left;
                return currentState;
            }

            function getPlacementForTopLeftMovement(data, mousePosition) {
                var diff = data.cursor - mousePosition;
                var max = data.length + data.position;
                var newPosition = data.position - diff;
                newPosition = newPosition < max ? newPosition : max;
                return {
                    position: newPosition,
                    length: data.length + diff
                };
            }

            function getPlacementForBottomRightMovement(data, mousePosition) {
                var diff = data.cursor - mousePosition;
                return {length: data.length - diff};
            }

            function getAnchorTopMovementPosition(mouseEvent) {
                var newPlacement = getPlacementForTopLeftMovement(
                    anchorDownState.vertical, mouseEvent.clientY);
                return {
                    top: newPlacement.position,
                    height: newPlacement.length
                };
            }

            function getAnchorLeftMovementPosition(mouseEvent) {
                var newPlacement = getPlacementForTopLeftMovement(
                    anchorDownState.horizontal, mouseEvent.clientX);
                return {
                    left: newPlacement.position,
                    width: newPlacement.length
                };
            }

            function getAnchorBottomMovementPosition(mouseEvent) {
                var newPlacement = getPlacementForBottomRightMovement(
                    anchorDownState.vertical, mouseEvent.clientY);
                return {height: newPlacement.length};
            }

            function getAnchorRightMovementPosition(mouseEvent) {
                var newPlacement = getPlacementForBottomRightMovement(
                    anchorDownState.horizontal, mouseEvent.clientX);
                return {width: newPlacement.length};
            }

            function onMouseUp(callbackMouseUp, callbackMouseMove) {
                onInputEventEnd();
                isWidgetMoving = false;
                $document.off('mouseup', callbackMouseUp);
                $document.off('mousemove', callbackMouseMove);
            }

            function activateAnchorElements(config) {
                for (var key in anchors) {
                    getElement(getAnchorId(key))[config.method]('mousedown', onAnchorDown);
                    getElement(getAnchorId(key)).css({
                        visibility: config.visibility
                    });
                }
            }

            function getAnchorWidth() {
                return EDITOR_SETTINGS.ANCHOR_WIDTH + ' / ' + visualScale;
            }

            function getAnchorBorderWidth() {
                return EDITOR_SETTINGS.ANCHOR_BORDER_WIDTH + ' / ' + visualScale;
            }

            function getAnchorStyling() {
                return EDITOR_SETTINGS.DEFAULT_ANCHOR_STYLING
                    + 'background-color:' + EDITOR_SETTINGS.ANCHOR_COLOR + ';'
                    + 'width: calc(' + getAnchorWidth() + ');'
                    + 'height: calc(' + getAnchorWidth() + ');'
                    + 'border: calc(' + getAnchorBorderWidth() + ') ' + EDITOR_SETTINGS.ANCHOR_BORDER_STYLING;
            }

            function getAnchorPositionOrigin() {
                return 'calc(0px - ((' + getAnchorWidth() + ') / 2) - ((' + EDITOR_SETTINGS.BORDER_WIDTH + '/' + visualScale + ') / 2));';
            }

            function getAnchorPositionCenter() {
                return 'calc(50% - ((' + getAnchorWidth() + ') / 2));';
            }

            function generateAnchor() {
                var result = '';
                for (var key in anchors) {
                    var anchor = anchors[key];
                    result += '<div id="' + getAnchorId(key) + '"'
                        + ' style="' + dataUtil.objectToCssStyling(anchor.getStyle())
                        + getAnchorStyling()
                        + '" anchor="' + key + '"></div>';
                }
                return result;
            }

            function drawAnchors() {
                widgetDom.append(generateAnchor());
            }

            function removeAnchors() {
                for (var key in anchors) {
                    var id = getAnchorId(key);
                    getElement(id).off('mousedown', onAnchorDown);
                    getElement(id).remove();
                }
            }

            function setWidgetPosition(position) {
                if (isSnapEnabled) {
                    return;
                }
                var newPosition = {};
                for (var key in position) {
                    newPosition[key] = position[key] + 'px';
                }
                widgetDom.css(newPosition);
            }

            function getCurrentPosition() {
                var element = widgetDom;
                return {
                    x: element[0].offsetLeft,
                    y: element[0].offsetTop,
                    width: parseInt(element.css('width'), 10),
                    height: parseInt(element.css('height'), 10),
                };
            }

            function getAnchorId(anchorName) {
                return editorElementId + '-anchor-' + anchorName;
            }

            function getElement(id) {
                return angular.element('#' + id);
            }
        }

        return (WidgetEditor);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    app.service('signsWidgetService', ["$q", "$window", "syncService", "$compile", function($q, $window, syncService, $compile) {
        function init() {
        }

        var currentApplication = null;
        var canvasSize = {width: 1920, height: 1080};
        var canvasScaleSubscribers = [];

        // Initialise the service
        init();

        function setApplication(application) {
            currentApplication = application;
        }

        function getApplication() {
            return currentApplication;
        }

        function setCanvasSize(size) {
            var canvasChanged = false;
            if (canvasSize.width != size.width || canvasSize.height != size.height) {
                canvasChanged = true;
            }

            canvasSize = size;

            if (canvasChanged) {
                notifyCanvasScaleUpdate();
            }
        }

        function getCanvasSize() {
            return canvasSize;
        }

        // Registers a callback to be called when the canvas scale changes
        function subscribeToCanvasScaleUpdate(callback) {
            // Check is this callback already registered
            for (var i = 0; i < canvasScaleSubscribers.length; i++) {
                var c = canvasScaleSubscribers[i];
                if (c == callback) {
                    return;
                }
            }

            // Add a callback handler
            canvasScaleSubscribers.push(callback);
        }

        // Registers a callback to be called when the canvas scale changes
        function unsubscribeToCanvasScaleUpdate(callback) {
            // Find the callback and remove
            for (var i = 0; i < canvasScaleSubscribers.length; i++) {
                var c = canvasScaleSubscribers[i];
                if (c == callback) {
                    canvasScaleSubscribers.splice(i, 1);
                    return;
                }
            }
        }

        // Notify subscribers when canvas scale changes
        function notifyCanvasScaleUpdate() {
            var scale = getCanvasScale();
            // Enumerate through each callback
            for (var i = 0; i < canvasScaleSubscribers.length; i++) {
                var c = canvasScaleSubscribers[i];
                c(scale);
            }
        }

        function getCanvasScale() {
            if (!currentApplication || currentApplication.pixelWidth == 0 || currentApplication.pixelHeight == 0) {
                return {
                    xScale: 1,
                    yScale: 1
                };
            }

            // Calculate the canvas scaling
            var scale = {
                xScale: canvasSize.width / currentApplication.pixelWidth,
                yScale: canvasSize.height / currentApplication.pixelHeight
            };
            return scale;
        }

        setCanvasSize;

        // Requests a widget
        function retrieveWidget(widgetId) {
            var deferred = $q.defer();
            var i;

            if (currentApplication == null) {
                deferred.reject('No application loaded');
                return deferred.promise;
            }

            // Find widget based on id;
            var w = null;
            for (i = 0; i < currentApplication.elements.length; i++) {
                var e = currentApplication.elements[i];
                if (e.elementId == widgetId) {
                    // Found the element based on widget id
                    w = e;
                    break;
                }
            }

            // Check did we find the widget
            if (w == null) {
                deferred.reject('No widget found with ID: ' + widgetId);
                return deferred.promise;
            }

            // Get the widget template
            var template = createWidgetTemplate(w);
            // Create the response object to resolve
            var res = {
                template: template,
                widget: w,
                datasource: null
            };

            function getBackgroundImage(assets) {
                if (!assets) {
                    return;
                }

                for (var i = 0; i < assets.length; i++) {
                    var asset = assets[i];
                    if (asset.name != 'standard.image') {
                        continue;
                    }

                    // if asset is background image, inject to properties
                    return asset.mediaFile;
                }
            }

            var onAssetDataSource = function(assets) {
                var backgroundImage = getBackgroundImage(assets);
                w.properties['standard.image'] = backgroundImage;
                res.datasource = res.datasource || {};
                res.datasource.result = res.datasource.result || {};
                res.datasource.result.widgetBackgroundImage = backgroundImage;
                deferred.resolve(res);
            };

            var onAssetDataSourceFail = function(err) {
                deferred.resolve(res);
            };

            // On retrieve of the widget's datasource
            var onDataSource = function(ds) {
                if (ds) {
                    res.datasource = ds;
                }

                syncService.retrieveWidgetAssetDatasource(w.assetSourceId).then(onAssetDataSource, onAssetDataSourceFail);
            };

            // On failure to retrieve the datasource
            var onDataSourceFail = function(err) {
                // console.log(err);
                // Just resolve and continue
                deferred.resolve(res);
            };

            // If the widget has a datasource, retrieve it
            var skipDSRetrieve = false;
            if (w.contentSourceId || (w.elementType.toLowerCase() == 'flashcontainer' && w.contentSourceId == 0)) {
                // Is this a media zone?
                if (currentApplication.mediazoneSchedules.mediaZones != null && w.elementType.toLowerCase() == 'mediazone') {
                    // Check does this mediazone have a schedule
                    for (i = 0; i < currentApplication.mediazoneSchedules.mediaZones.length; i++) {
                        var mzDs = currentApplication.mediazoneSchedules.mediaZones[i];
                        if (mzDs.id == w.contentSourceId) {
                            // Set the hash for the DS result
                            mzDs.hash = currentApplication.mediazoneSchedules.hash;
                            // Use the scheduleing datasource instead of the default AZ
                            onDataSource({result: mzDs});
                            // console.log(mzDs);
                            skipDSRetrieve = true;
                            break;
                        }
                    }
                }

                if (!skipDSRetrieve) {
                    syncService.retrieveDatasource(mapDatasourceId(w)).then(onDataSource, onDataSourceFail);
                }
            } else if (w.assetSourceId) {
                // if we have asset source Id but we don't have any content datasource, retrieve asset datasource
                onDataSource(null);
            } else {
                // Else just resolve
                deferred.resolve(res);
            }

            return deferred.promise;
        }

        function getWidgetDirective(widgetType) {
            switch (widgetType.toLowerCase()) {
                case 'datetime':
                    return 'as-signs-date-time-widget';
                case 'plaintext':
                    return 'as-signs-plain-text-widget';
                case 'mediazone':
                    return 'as-signs-widget-media-zone';
                case 'rssviewer':
                    return 'as-signs-rss-viewer-widget';
                case 'textticker':
                    return 'as-signs-text-ticker-widget';
                case 'rssticker':
                    return 'as-signs-rss-ticker-widget';
                case 'eventcalendar':
                    return 'as-signs-event-calendar-widget';
                case 'weatherinfo':
                    return 'as-signs-weather-widget';
                case 'tablelisting':
                    return 'as-signs-table-listing';
                case 'flashcontainer':
                    return 'as-signs-flash-container-widget';
                case 'mediacontainer':
                    return 'as-signs-widget-media-container';
                case 'remotecontrol':
                    return 'as-signs-remote-control-widget';
                default:
                    return 'as-signs-widget-dummy';
            }
        }

        function createWidgetTemplate(widget) {
            var directive = getWidgetDirective(widget.elementType);
            var directiveTemplate = '<as-signs-widget-container ' +
                'on-api-ready="onWidgetContainerApiReady(api, ' + widget.elementId + ')" ' +
                'on-mouse-over="onMouseOver(' + widget.elementId + ')" ' +
                'on-mouse-leave="onMouseLeave(' + widget.elementId + ')" ' +
                'on-next-content-clicked="onNextContentClicked(' + widget.elementId + ')" ' +
                'on-previous-content-clicked="onPreviousContentClicked(' + widget.elementId + ')" ' +
                '>' +
                '<' + directive + ' id="' + widget.elementId + '" ' +
                'content-source-id="' + widget.contentSourceId + '" ' +
                'asset-source-id="' + widget.assetSourceId + '" '+
                'on-no-content-available="onNoContentAvailable(' + widget.elementId + ')" ' +
                'on-content-available="onContentAvailable(' + widget.elementId + ')" ' +
                'on-content-loaded="onContentLoaded(content)" ' +
                'on-api-ready="onWidgetApiReady(api, ' + widget.elementId + ')">' +
                '</' + directive + '>' +
                '</as-signs-widget-container>';

            return directiveTemplate;
        }

        function mapDatasourceId(widget) {
            switch (widget.elementType.toLowerCase()) {
                case 'datetime':
                    return '';
                case 'plaintext':
                    return '';
                case 'mediazone':
                    return 'az_' + widget.contentSourceId;
                case 'rssviewer':
                    return 'rss_' + widget.contentSourceId;
                case 'textticker':
                    return 'textmsg_' + widget.contentSourceId;
                case 'rssticker':
                    return 'rss_' + widget.contentSourceId;
                case 'eventcalendar':
                    return 'calendar_' + widget.contentSourceId;
                case 'weatherinfo':
                    return 'weatherinfo_' + widget.contentSourceId;
                case 'tablelisting':
                    return 'workbook_' + widget.contentSourceId;
                case 'flashcontainer':
                    return 'applicationasset_' + currentApplication.applicationId;
                case 'mediacontainer':
                    return 'infomenu_' + widget.contentSourceId;
                case 'remotecontrol':
                    return 'workbook_' + widget.contentSourceId;
                default:
                    return '';
            }
        }

        return {
            // Public interface
            retrieveWidget: retrieveWidget,
            setApplication: setApplication,
            setCanvasSize: setCanvasSize,
            getApplication: getApplication,
            getCanvasSize: getCanvasSize,
            getCanvasScale: getCanvasScale,
            subscribeToCanvasScaleUpdate: subscribeToCanvasScaleUpdate,
            unsubscribeToCanvasScaleUpdate: unsubscribeToCanvasScaleUpdate
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    var app = angular.module('signsHTML5');
    app.controller('RegAccountCtrl', ["$window", "$scope", "$location", "$timeout", "coreFrameworkService", "authenticationAPIService", "coreConfigurationService", "halPlatformService", "logService", "halRemoteService", "REMOTEVCODE", "REMOTEEVENTTYPE", "RegistrationUtil", "halMediaService", "KEYBOARD_CODE", function($window, $scope, $location, $timeout, coreFrameworkService, 
        authenticationAPIService, coreConfigurationService, halPlatformService, logService, halRemoteService, REMOTEVCODE, REMOTEEVENTTYPE, 
        RegistrationUtil, halMediaService, KEYBOARD_CODE) {
        var tabIndex = 1;
        var animationDuration = 300;
        var needKeyboard = false;
        var keyboardVisible = false;
        var keyboardApi = null;
        var idleTimer = null;
        var getElement = angular.element;
        $scope.urlFocus = true;
        $scope.usernameFocus = false;
        $scope.passwordFocus = false;
        $scope.submitDisabled = false;
        $scope.elementInFocus = null;

        $scope.appspaceAcc = {
            username: '',
            password: '',
            appspaceUrl: 'cloud.appspace.com'
        };

        $scope.onLeftPanelClicked = function(e) {
            // hide keyboard if we're clicking non-input element
            if (e.target && e.target.tagName && e.target.tagName.toLowerCase() != 'input') {
                hideKeyboard();
            }
        };

        function init() {
            // Handle view destroy
            $scope.$on('$destroy', function() {
                // clean up stuff
                halRemoteService.unRegisterEvents(onRemoteEvent);
                if (initTimer) {
                    $timeout.cancel(initTimer);
                }
                cancelIdleTimer();
            });

            // clear all registration info
            coreConfigurationService.removeCollection('signs.registration');

            // Register for remote input
            halRemoteService.registerEvents(onRemoteEvent);

            var serverUrl = coreConfigurationService.get('appspace', 'appspaceURL');
            var cloudUrl = coreConfigurationService.get('appspace', 'appspaceCloudUrl');
            // if cloud URL is available from last registration, use it
            if (cloudUrl && cloudUrl != '') {
                if (cloudUrl.toLowerCase().indexOf('http://') >= 0) {
                    cloudUrl = cloudUrl.substring(7);
                } else if (cloudUrl.toLowerCase().indexOf('https://') >= 0) {
                    cloudUrl = cloudUrl.substring(8);
                }
                $scope.appspaceAcc.appspaceUrl = cloudUrl;
            } else if (serverUrl == null || serverUrl.length == 0) {// An invalid URL is provided, just reset to cloud
                $scope.appspaceAcc.appspaceUrl = 'cloud.appspace.com';
            } else {
                // A url is available, check protocol if one is supplied
                var urlParts = serverUrl.split('://');
                var protocol = null;
                var useDefault = false;
                if (urlParts.length >= 2) {
                    // Protocol specified
                    protocol = urlParts[0].toLowerCase();
                    // Check if a correct protocol is used
                    if (protocol != 'http' && protocol != 'https') {
                        useDefault = true; // no, just fall back to default url
                    }
                }
                // Use the default value of cloud
                if (useDefault) {
                    $scope.appspaceAcc.appspaceUrl = 'cloud.appspace.com';
                } else {
                    var urlSplit = splitUrl(serverUrl);
                    if (urlSplit) {
                        $scope.appspaceAcc.appspaceUrl = urlSplit.host;
                    }
                }
            }

            // init virtual keyboard if we need one
            var ks = halPlatformService.getFeatureSupport('input.keyboard');
            if (ks == null || ks.toLowerCase() == 'none') {
                needKeyboard = true;
            }
            if ($window.enableVirtualKeyboardGlobal === undefined) {
                $window.enableVirtualKeyboardGlobal = needKeyboard;
            }
            // needKeyboard = true;
            // focus on first form item
            var initTimer = $timeout(function() {
                getElement('[tabindex=\'1\']').focus();
            }, 300);

            startIdleTimer();
        }

        function startIdleTimer() {
            cancelIdleTimer();
            // go back to login page if idle for 2 minutes
            idleTimer = $timeout(function() {
                $location.path('/tv/registrationlanguage');
            }, 120000);
        }

        function cancelIdleTimer() {
            if (idleTimer) {
                $timeout.cancel(idleTimer);
                idleTimer = null;
            }
        }

        $scope.onMouseMove = function() {
            // startIdleTimer();
        };

        // Event handler for remote events
        var onRemoteEvent = function(remoteEvent) {
            // Only listen to down button events
            if (remoteEvent.eventType != REMOTEEVENTTYPE.buttonDown) {
                return;
            }

            // reset idle timer
            startIdleTimer();
            // these set of actions will run regardless of keyboard visibility
            switch (remoteEvent.vcode) {
                case REMOTEVCODE.toggle_keyboard:
                    remoteEvent.preventDefault();
                    $window.enableVirtualKeyboardGlobal = !$window.enableVirtualKeyboardGlobal;
                    toggleKeyboard();
                    return;
                case REMOTEVCODE.back: //eslint-disable-line
                    if (remoteEvent.code === KEYBOARD_CODE.ESC) {
                        remoteEvent.preventDefault();
                        toggleKeyboard();
                    }
                    return;
            }
            // these set of actions is only meant when keyboard is hidden
            if (keyboardVisible) {
                return;
            }

            switch (remoteEvent.vcode) {
                case REMOTEVCODE.up: // Up Key
                    // because default behavior is already moving the text caret from an input field (e.g. Password) to previous one (e.g. Username)
                    remoteEvent.preventDefault();
                    onActionUp();
                    break;
                case REMOTEVCODE.down: // Down Key
                    // because default behavior is already moving the text caret from an input field (e.g. IP address) to next one (e.g. Username)
                    remoteEvent.preventDefault();
                    onActionDown();
                    break;
                case REMOTEVCODE.left: // Left Key
                    onActionLeft();
                    break;
                case REMOTEVCODE.right: // Right Key
                    onActionRight();
                    break;
                case REMOTEVCODE.select: // option/alt Key
                    onActionSelect();
                    break;
                case REMOTEVCODE.back: // command/windows Key
                    if (remoteEvent.code != 8 && remoteEvent.code != 16) {// ignore backspace key and shift (user still can use command  key to back)
                        onActionBack();
                    } else {
                        var activeElement = document.activeElement.tagName.toLowerCase();
                        if (activeElement.toLowerCase() != 'input') {
                            // remove below code to Allow user to use backspace to  perform back (default machine behavior)
                            // when not focusing on input element
                            remoteEvent.preventDefault();
                        }
                    }
                    break;
            }
        };

        function onActionUp() {
            // dont move to the next item if currently on url input
            if (tabIndex == 1) {
                return;
            } else if (tabIndex == 4 || tabIndex == 5) { // go to the first field if we're selecting back or submit
                tabIndex = 1;
            } else {
                tabIndex--;
            }
            getElement('[tabindex=\'' + tabIndex + '\']').focus();
        }

        function onActionDown() {
            // do nothing if on the register now link
            if (tabIndex == 6) {
                return;
            }
            if (tabIndex == 4 || tabIndex == 5) {
                tabIndex = 6;
            } else {
                tabIndex++;
                // if login is disabled, go to back button
                if (tabIndex == 4 && getElement('#submitBtn').is(':disabled')) {
                    tabIndex = 5;
                }
            }
            if (tabIndex == 4 || tabIndex == 5) {
                // need to delay to prevent button from auto-clicked
                $timeout(function() {
                    getElement('[tabindex=\'' + tabIndex + '\']').focus();
                }, 100);
            } else {
                getElement('[tabindex=\'' + tabIndex + '\']').focus();
            }
        }

        function onActionLeft() {
            // if tab index is on 'Continue' button, go to back button
            if (tabIndex == 4) {
                tabIndex = 5;
            } else {
                return;
            }
            getElement('[tabindex=\'' + tabIndex + '\']').focus();
        }

        function onActionRight() {
            // if tab index is not on back or login is disabled, do nothing
            if (tabIndex != 5 || getElement('#submitBtn').is(':disabled')) {
                return;
            } else {
                tabIndex = 4;
            }
            getElement('[tabindex=\'' + tabIndex + '\']').focus();
        }

        function onActionBack() {
            if (keyboardVisible) {
                hideKeyboard();
                // highlight continue button
                tabIndex = 4;
                getElement('[tabindex=\'' + tabIndex + '\']').focus();
            } else {
                $scope.back();
            }
        }

        function onActionSelect() {
            switch (tabIndex) {
                case 1:
                case 2:
                case 3:
                    // check if all fields has values, submit
                    if ($scope.appspaceAcc.appspaceUrl &&
                        $scope.appspaceAcc.username &&
                        $scope.appspaceAcc.password) {
                        $scope.submit();
                    }
                    break;
                case 4:
                    // Login is focused
                    $scope.submit();
                    break;
                case 5:
                    // Back in focused
                    $scope.back();
                    break;
                case 6:
                    window.open(getElement('#registerLink').attr('href'));
                    break;
            }
        }

        function toggleKeyboard() {
            if (keyboardVisible) {
                hideKeyboard();
            } else {
                showKeyboard();
            }
        }

        function showKeyboard() {
            // if globally we don't want to show keyboard, don't show
            if ($window.enableVirtualKeyboardGlobal === false) {
                return;
            }
            // only show keyboard if device requires it and keyboard is currently not visible and we're highlighting input box
            if (needKeyboard && !keyboardVisible && (tabIndex >= 1 && tabIndex <= 3)) {
                // show keyboard
                slideInRightPanel();
                // enable keyboard navigation
                enableKeyboardNavigation(true);
                // focus on first button
                // keyboardApi.selectKey('a');
                getElement(':input').attr('readonly', 'readonly');
            }
        }

        function hideKeyboard() {
            if (keyboardVisible) {
                // hide keyboard
                slideOutRightPanel();
                // disable keyboard navigation
                enableKeyboardNavigation(false);
                getElement(':input').removeAttr('readonly');
            }
        }

        $scope.isSubmitDisabled = function() {
            var url = getElement('#appspaceUrl').val();
            var username = getElement('#username').val();
            var password = getElement('#password').val();
            if ($scope.submitDisabled || url === '' || username === '' || password === '') {
                return true;
            }
            return false;
        };

        function enableKeyboardNavigation(enable) {
            keyboardApi.enableRemoteNavigation(enable);
        }
        function slideInRightPanel() {
            keyboardVisible = true;
            // shrink left panel and footer to 60% of window width
            getElement('.left-panel').velocity({width: '60%'}, {duration: animationDuration});
            // getElement(".reg-footer").velocity({width: '60%'}, { duration: animationDuration });

            // slide right panel to the left to show it
            getElement('.right-panel').velocity({left: '60%', width: '40%'}, {duration: animationDuration});
        }

        function slideOutRightPanel() {
            keyboardVisible = false;
            // expand left panel and footer to 100% of window width
            getElement('.left-panel').velocity({width: '100%'}, {duration: animationDuration});
            // getElement(".reg-footer").velocity({width: '100%'}, { duration: animationDuration });

            // slide right panel to the right to hide it
            getElement('.right-panel').velocity({left: '100%'}, {duration: animationDuration});
        }

        $scope.onInputFocus = function(event) {
            startIdleTimer();
            focusInput(event.target.id);
        };

        function focusInput(inputId) {
            // set focus flag to change the style
            switch (inputId) {
                case 'appspaceUrl':
                    showError(false);
                    $scope.urlFocus = true;
                    $scope.usernameFocus = false;
                    $scope.passwordFocus = false;
                    tabIndex = 1;
                    getElement('#appspaceUrl').css('-webkit-text-fill-color', '#fff');
                    getElement('#username').removeAttr('style');
                    getElement('#password').removeAttr('style');
                    showKeyboard();
                    if (keyboardApi) {
                        keyboardApi.setInputValue(getElement('#appspaceUrl').val());
                    }
                    break;
                case 'username':
                    showError(false);
                    $scope.urlFocus = false;
                    $scope.usernameFocus = true;
                    $scope.passwordFocus = false;
                    tabIndex = 2;
                    getElement('#username').css('-webkit-text-fill-color', '#fff');
                    getElement('#appspaceUrl').removeAttr('style');
                    getElement('#password').removeAttr('style');
                    showKeyboard();
                    if (keyboardApi) {
                        keyboardApi.setInputValue(getElement('#username').val());
                    }
                    break;
                case 'password':
                    showError(false);
                    $scope.urlFocus = false;
                    $scope.usernameFocus = false;
                    $scope.passwordFocus = true;
                    tabIndex = 3;
                    getElement('#password').css('-webkit-text-fill-color', '#fff');
                    getElement('#appspaceUrl').removeAttr('style');
                    getElement('#username').removeAttr('style');
                    showKeyboard();
                    if (keyboardApi) {
                        keyboardApi.setInputValue(getElement('#password').val());
                    }
                    break;
                case 'btnBack':
                    showError(false);
                    $scope.urlFocus = false;
                    $scope.usernameFocus = false;
                    $scope.passwordFocus = false;
                    tabIndex = 5;
                    getElement('#appspaceUrl').removeAttr('style');
                    getElement('#username').removeAttr('style');
                    getElement('#password').removeAttr('style');
                    hideKeyboard();
                    break;
                case 'submitBtn':
                    tabIndex = 4;
                    $scope.urlFocus = false;
                    $scope.usernameFocus = false;
                    $scope.passwordFocus = false;
                    getElement('#appspaceUrl').removeAttr('style');
                    getElement('#username').removeAttr('style');
                    getElement('#password').removeAttr('style');
                    hideKeyboard();
                    break;
            }
        }

        $scope.onKeyboardApi = function(api) {
            keyboardApi = api;

            // import layouts into the keyboard
            var file = 'assets/keyboard-layouts/registration.json';
            keyboardApi.importLayouts(file).then(function(layouts) {
                if (layouts && layouts.length > 0) {
                    logService.warn('appspace.signs.registration', 'Keyboard layout is empty');
                }
            }, function(err) {
                logService.error('appspace.signs.registration', 'Failed to load keyboard', err);
            });
        };

        $scope.onKeyboardValueChange = function(previousVal, currentVal) {
            var input = null;
            var enableContinueBtn = true;
            var val;
            if ($scope.urlFocus) {
                input = getElement('#appspaceUrl');
                val = input.val();
                if (val == null ||
                    val.length == 0 ||
                    currentVal == null ||
                    currentVal.length == 0) {
                    enableContinueBtn = false;
                }
                $scope.appspaceAcc.appspaceUrl = currentVal;
            } else if ($scope.usernameFocus) {
                input = getElement('#username');
                val = input.val();
                if (val == null ||
                    val.length == 0 ||
                    currentVal == null ||
                    currentVal.length == 0) {
                    enableContinueBtn = false;
                }
                $scope.appspaceAcc.username = currentVal;
            } else if ($scope.passwordFocus) {
                input = getElement('#password');
                val = input.val();
                if (val == null ||
                    val.length == 0 ||
                    currentVal == null ||
                    currentVal.length == 0) {
                    enableContinueBtn = false;
                }
                $scope.appspaceAcc.password = currentVal;
            }

            // enable/disable continue btn
            getElement('#submitBtn').attr('disabled', !enableContinueBtn);
        };

        $scope.onKeyboardActionSelected = function(key) {
            if (key) {
                var layout;
                switch (key.value) {
                    case 'abc':
                        // load the lowercase layout
                        layout = keyboardApi.getLayout('alphanumeric-symbols');
                        keyboardApi.setLayout(layout);
                        keyboardApi.focusKey('ABC');
                        break;
                    case 'ABC':
                        // load the uppercase layout
                        layout = keyboardApi.getLayout('ALPHANUMERIC-SYMBOLS');
                        keyboardApi.setLayout(layout);
                        keyboardApi.focusKey('abc');
                        break;
                    case 'symbols':
                        // load the symbols layout
                        layout = keyboardApi.getLayout('symbols');
                        keyboardApi.setLayout(layout);
                        keyboardApi.focusKey('abc');
                        break;
                    case 'next':
                        onActionDown();
                        break;
                }
            }
        };

        $scope.back = function(e) {
            logService.debug('appspace.signs.registration', 'User navigated back from login');
            $location.path('/tv/registrationlanguage');
        };

        $scope.submit = function(e) {
            // disable idle timer
            cancelIdleTimer();
            showError(false);
            // disable submit button
            $scope.submitDisabled = true;
            getElement('#submitBtn').html('Logging in...');

            // check if device is online
            if (navigator.onLine == false) {
                logService.warn('appspace.signs.registration', 'Device is offline');
            }

            // resolve URL
            RegistrationUtil.resolveServerUrl($scope.appspaceAcc.appspaceUrl).then(
                function(serverUrl) {
                    login(serverUrl);
                },
                function(err) {
                    showError(true, 'Could not contact Appspace, please check your Appspace URL and network connection.');
                }
            );
        };

        function login(serverUrl) {
            logService.debug('appspace.signs.registration', 'Appspace URL: ' + serverUrl);
            var cloudApiUrl;
            var cloudApiUrls = RegistrationUtil.retrieveCloudApiUrls();
            // check if the server url is CS
            var checkServer = serverUrl;
            checkServer = checkServer.toLowerCase().split('http://').join('').split('https://').join('');
            for (var key in cloudApiUrls) {
                if (checkServer.toLowerCase().indexOf(key) == 0) {
                    cloudApiUrl = cloudApiUrls[key];
                    break;
                }
            }
            coreConfigurationService.set('appspace', 'appspaceURL', serverUrl);
            // if Appspace is on cloud (or have cloud service), validate credentials through cloud
            if (cloudApiUrl) {
                coreConfigurationService.set('appspace', 'appspaceCloudApiURL', cloudApiUrl);
                coreConfigurationService.set('appspace', 'appspaceCloudUrl', serverUrl);
                authenticationAPIService.loginCloud(
                    $scope.appspaceAcc.username,
                    $scope.appspaceAcc.password)
                    .then(onLoginCloudSuccess, onLoginFail);
            } else {
                // login using authenticationAPIService
                coreConfigurationService.set('appspace', 'appspaceCloudApiURL', '');
                coreConfigurationService.set('appspace', 'appspaceCloudUrl', '');
                authenticationAPIService.login(
                    $scope.appspaceAcc.username,
                    $scope.appspaceAcc.password)
                    .then(onLoginSuccess, onLoginFail);
            }
        }

        function onLoginCloudSuccess(response) {
            logService.debug('appspace.signs.registration', 'User succesfully authenticated on cloud: ' + $scope.appspaceAcc.username);
            // retrieve instances
            authenticationAPIService.retrieveTokenInstances(response)
                .then(onRetrievedInstance, onRetrievedInstanceFail);
        }

        function onRetrievedInstance(instances) {
            if (instances.length == 1) {
                RegistrationUtil.resolveServerUrl(instances[0].Instance.IP)
                    .then(function(serverUrl) {
                        coreConfigurationService.set('appspace', 'appspaceURL', serverUrl);
                        // login to core using authenticationAPIService
                        authenticationAPIService.login(
                            $scope.appspaceAcc.username,
                            $scope.appspaceAcc.password,
                            instances[0].InstanceAccount.InstanceAccountId)
                            .then(onLoginSuccess, onLoginFail);
                    }, function(err) {
                        showError(true, 'Login failed. Please ensure device is online and Appspace URL is correct.');
                        logService.warn('appspace.signs.registration', 'Fail to authenticate API service ');
                    });
            } else {
                var str = JSON.stringify(instances);
                coreConfigurationService.set('signs.registration', 'instances', str);
                str = JSON.stringify({
                    u: $scope.appspaceAcc.username,
                    p: $scope.appspaceAcc.password
                });
                coreConfigurationService.set('signs.registration', 'cred', str);
                $location.path('/tv/registrationinstance');
            }
        }

        function onRetrievedInstanceFail(err) {
            logService.warn('appspace.signs.registration', 'Retrieve instances fails : ' + $scope.appspaceAcc.username);
            showError(true, 'Hmmm... looks like we can\'t fetch yours instances from Cloud');
        }

        function onLoginSuccess(response) {
            if (response.token != null) {
                // Disable display refresh on ios
                halMediaService.setSyncDisplayResolutionFlag(false);

                authenticationAPIService.startValidateToken();
                logService.debug('appspace.signs.registration', 'User succesfully authenticated: ' + $scope.appspaceAcc.username);
                $location.path('/tv/registrationdevicename');
            } else {
                authenticationAPIService.logout();
                authenticationAPIService.stopValidateToken();
                logService.warn('appspace.signs.registration', 'User authentication failed: ' + $scope.appspaceAcc.username);
                showError(true, 'Login failed. Response token is null');
            }
        }

        function onLoginFail(err) {
            authenticationAPIService.logout();
            authenticationAPIService.stopValidateToken();
            logService.warn('appspace.signs.registration', 'User authentication error: ' + $scope.appspaceAcc.username + '.');
            showError(true, 'Please check your username and password');
            var errorMessage = err.status == -1 ? 'Please ensure device is online and the Appspace URL is correct.' : 'Please check your username and password';
            showError(true, errorMessage);
        }

        function showError(show, msg) {
            var failMsg = getElement('#failMessage');
            if (show) {
                // start back the idle timer when we receive error
                startIdleTimer();
                failMsg.html(msg);
                $scope.submitDisabled = false;
                getElement('#submitBtn').html('SIGN IN');
                // focus back on sign in
                $timeout(function() {
                    getElement('[tabindex=\'4\']').focus();
                }, 0);
            } else {
                failMsg.text('');
            }
        }

        function splitUrl(url) {
            var res = null;
            if (url) {
                var a = url.split('://');
                if (a.length == 2) {
                    res = {protocol: a[0] + '://', host: a[1]};
                }
            }
            return res;
        }

        coreFrameworkService.isReady().then(init);
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    var app = angular.module('signsHTML5');
    var getElement = angular.element;
    app.controller('RegChannelCtrl', ["$scope", "$location", "$timeout", "$q", "signsAPIService", "deviceRegistrationService", "coreConfigurationService", "deviceAPIService", "logService", "halPlatformService", "halRemoteService", "authenticationAPIService", "REMOTEVCODE", "REMOTEEVENTTYPE", "storageLocalStorageService", "halMediaService", "RegistrationUtil", "sessionService", function($scope, $location, $timeout, $q, signsAPIService, deviceRegistrationService, coreConfigurationService, deviceAPIService, logService, halPlatformService, halRemoteService, authenticationAPIService, REMOTEVCODE,
        REMOTEEVENTTYPE, storageLocalStorageService, halMediaService, RegistrationUtil, sessionService) {
        $scope.channels = [];
        $scope.selectedChannel = null;
        $scope.displaySpinner = '';
        $scope.message = 'Retrieving sign channels from your account <div class="la-ball-pulse la-sm"><div></div><div></div><div></div></div>';
        $scope.gridItems = [];
        $scope.skipDisabled = false;
        $scope.btnHide = false;
        $scope.interactiveModeDisable = true;
        $scope.isPassive = false;
        var currentGridOffset = 0;
        var skipToTop = true;
        var gridApi = null;
        var interactiveMode;
        var returnToRegistrationTimeout = 10000;
        var returnToRegistrationHandler = null;

        $scope.gridConfig = {
            padding: {top: 0, left: 0, right: 0, bottom: 0},
            itemSpacing: {horizontal: 10, vertical: 5},
            itemWidth: 0,
            itemHeight: 45,
            scrollDirection: 'vertical',
        };

        function init() {
            if (halPlatformService.getFeatureSupport('rendering.support.viewport.width.height') == 'no') {
                // manual height for iec and ssd as css:vh doesnt work
                var height = getElement('.reg-channel-container').height();
                getElement('.reg-channel-container').height(height + 'px');
            }

            // Handle view destroy
            $scope.$on('$destroy', function() {
                // clean up stuff
                halRemoteService.unRegisterEvents(onRemoteEvent);
            });

            // Register for remote input
            halRemoteService.registerEvents(onRemoteEvent);

            // var policy = {registration: {api_key: "93c07a6a-0ef9-42b3-88f7-5cd71a600777", appspace_url: "http://cse.demo.appspace.com", default_channelid: 12}};
            // autoBind(policy);


            // get flag to skip channel
            var registrationMode = storageLocalStorageService.get('signs.registration', 'registrationmode');

            // selectchannel flag is true, then select channel.
            // else, a policy file exist, automatically assign channel
            if (registrationMode == 'manual') {
                initChannels();
            } else {
                // get app policy
                RegistrationUtil.getPolicy().then(autoBind, initChannels);
            }

            $scope.isPassive = sessionService.get('passiveRegistration');
            if ($scope.isPassive) {
                startReturnToRegistrationTimer();
            }
        }

        function returnToRegistration() {
            // Press up key or move your mouse to reveal auto registration code
            clearTimerHandler();
            // Go back to registration page 
            $location.path('/tv/appinit');
        }

        function startReturnToRegistrationTimer() {
            // set timer for 10 seconds
            // return to registration page when time is up
            clearTimerHandler();
            returnToRegistrationHandler = $timeout(returnToRegistration, returnToRegistrationTimeout);
        }

        function clearTimerHandler() {
            if (returnToRegistrationHandler) {
                $timeout.cancel(returnToRegistrationHandler);
                returnToRegistrationHandler = null;
            }
        }

        function autoBind(policy) {
            if (!policy.registration || !policy.registration.default_channelid) {
                initChannels();
            } else {
                $scope.selectedChannel = {id: policy.registration.default_channelid, name: ''};
                // clear force app policy
                halPlatformService.clearAppPolicy();
                // bind device
                $scope.submit();
            }
        }

        function initChannels() {
            // retrieve apps
            signsAPIService.retrieveApplications({max: 100})
                .then(onRetrieveAppsSuccess, onRetrieveAppsFail);

            // highlight skip button
            $timeout(function() { // focus on skip button
                getElement('#skipBtn').focus();
                $scope.selectedChannel = null;
            }, 500);
        }

        function onRetrieveAppsSuccess(response) {
            if (response != null) {
                // console.log(response);
                logService.debug('appspace.signs.registration', 'Application list retrieved successfully');
                var channels = [];
                for (var i = 0; i < response.length; i++) {
                    var c = response[i];
                    // console.log(c);
                    // if (c.Type != 0) {
                    if (c.Name.length > 0 && c.Name != ' ') {
                        if (c.Name.toLowerCase().indexOf('interactive mode') >= 0 && c.Type == 0 && $scope.interactiveModeDisable) {
                            $scope.interactiveModeDisable = false;
                            interactiveMode = {id: c.Id, name: c.Name};
                        } else {
                            channels.push({id: c.Id, name: c.Name});
                        }
                    }
                    // }
                }
                channels.sort(compare);
                $scope.channels = channels;
                $scope.displaySpinner = 'none';
                generateChannelListing();

                // Looks like you have'nt created a channel, goto your account to create one
            } else {
                logService.warn('appspace.signs.registration', 'Failed to retrieve application list ' + response);
            }
        }

        function onRetrieveAppsFail(err) {
            logService.warn('appspace.signs.registration', 'Failed to retrieve application list. Error: ' + err);
            $scope.message = 'There appears to be a problem, we cannot retrieve any channels from your account.';
            $scope.interactiveModeDisable = true;
            /* var channels = [{id: 1, name: 'App 1'},
                            {id: 2, name: 'App 2App 2App 2App 2App 2App 2App 2App 2App 2'},
                            {id: 3, name: 'App 3'},
                            {id: 4, name: 'App 4'},
                            {id: 5, name: 'App 5'},
                            {id: 6, name: 'App 6'},
                            {id: 7, name: 'App 7'},
                            {id: 8, name: 'App 8'},
                            {id: 9, name: 'App 9'},
                            {id: 10, name: 'App 10'}];

            $scope.channels = channels;
            $scope.displaySpinner = 'none';
            generateChannelListing();*/
        }

        function compare(a, b) {
            if (a.name.toLowerCase() < b.name.toLowerCase()) {
                return -1;
            }
            if (a.name.toLowerCase() > b.name.toLowerCase()) {
                return 1;
            }
            return 0;
        }

        function generateChannelListing() {
            var items = [];
            // Add apps to griditems
            if ($scope.channels != undefined) {
                for (var i = 0; i < $scope.channels.length; i++) {
                    var element = createGridElement($scope.channels[i]);
                    if (element) {
                        items.push(element);
                    }
                }
            }
            $scope.gridItems = items;
        }

        // on api ready
        $scope.onGridApi = function(api) {
            gridApi = api;
        };

        // on item selected
        $scope.onGridItem = function(api, offset, fromClick) {
            if (fromClick || $scope.selectedChannel == $scope.channels[offset]) {
                $timeout(function() {
                    // Delay so we can highlight the current item
                    onActionSelect();
                }, 100);
            }
            $scope.selectedChannel = $scope.channels[offset];
        };

        function createGridElement(channel) {
            if (channel && channel.name.length > 0 && channel.name != ' ') {
                var markup = '<div class="list-group-item" id="' + channel.id + '">' + channel.name + '</div>';

                var t = {type: 'markup', markup: markup};
                return t;
            }
            return null;
        }

        // Event handler for remote events
        var onRemoteEvent = function(remoteEvent) {
            // Only listen to down button events
            if (remoteEvent.eventType != REMOTEEVENTTYPE.buttonDown) {
                return;
            }
            // Perform action based on Virtual code
            switch (remoteEvent.vcode) {
                case REMOTEVCODE.up: // Up Key
                    onActionUp();
                    remoteEvent.preventDefault();
                    break;
                case REMOTEVCODE.down: // Down Key
                    onActionDown();
                    remoteEvent.preventDefault();
                    break;
                case REMOTEVCODE.right: // Right Key
                    onActionRight();
                    remoteEvent.preventDefault();
                    break;
                case REMOTEVCODE.left:
                    onActionLeft();
                    remoteEvent.preventDefault();
                    break;
                case REMOTEVCODE.select: // option/alt Key
                    onActionSelect();
                    remoteEvent.preventDefault();
                    break;
                case REMOTEVCODE.back: // command/windows Key
                    onActionBack();
                    remoteEvent.preventDefault();
                    break;
            }
        };

        function onActionUp() {
            // if over the array bound, select the last item
            var length = $scope.channels.length;

            // If no channels just focus on skip
            if (length == 0) {
                // focus on skip button
                getElement('#skipBtn').focus();
                return;
            }

            var isSkipFocus = getElement('#skipBtn').is(':focus');
            var interactiveFocused = getElement('#interactiveBtn').is(':focus');
            if (isSkipFocus) {
                getElement('#interactiveBtn').focus();
                getElement('#skipBtn').blur();
            } else if (interactiveFocused) {
                // remove focus from skip button
                getElement('#interactiveBtn').blur();

                if (skipToTop) {
                    gridApi.moveToItem(0);
                    skipToTop = false;
                } else {
                    gridApi.moveToItem(currentGridOffset);
                }
            } else {
                gridApi.upItem();
            }
        }

        function onActionDown() {
            var isSkipFocus = getElement('#skipBtn').is(':focus');
            if (isSkipFocus) {
                return;
            }
            var interactiveFocused = getElement('#interactiveBtn').is(':focus');
            if (interactiveFocused) {
                getElement('#interactiveBtn').blur();
                getElement('#skipBtn').focus();
                $scope.selectedChannel = null;
                return;
            }

            // If no channels just focus on skip
            if (length == 0) {
                // focus on skip button
                getElement('#skipBtn').focus();
                return;
            }

            var currentOffset = gridApi.getCurrentItemOffset();
            // if over the array bound, select the skip/interactive button
            var length = $scope.channels.length;
            if (currentOffset >= length - 1) {
                currentGridOffset = currentOffset;

                // focus on skip button
                if ($scope.interactiveModeDisable) {
                    getElement('#skipBtn').focus();
                } else {
                    getElement('#interactiveBtn').focus();
                }
                // remove highlight on last item
                gridApi.resetFocusedItem();
                $scope.selectedChannel = null;
            } else {
                gridApi.downItem();
            }
        }
        function onActionRight() {
            var isSkipFocus = getElement('#interactiveBtn').is(':focus') == true || getElement('#skipBtn').is(':focus') == true ? true : false;
            if (isSkipFocus) {
                return;
            }

            currentGridOffset = gridApi.getCurrentItemOffset();

            // remove highlight on last item
            gridApi.resetFocusedItem();
            $scope.selectedChannel = null;


            // focus on skip button if interactive mode is disabled
            if ($scope.interactiveModeDisable) {
                getElement('#skipBtn').focus();
            } else {
                getElement('#interactiveBtn').focus();
            }
        }

        function onActionLeft() {
            var length = $scope.channels.length;
            // If no channels just return
            if (length == 0) {
                return;
            }

            currentGridOffset = 0;
            getElement('#interactiveBtn').blur();
            getElement('#skipBtn').blur();

            // move all the way to the top
            gridApi.moveToItem(0);
        }

        function onActionSelect() {
            var isSkipFocus = getElement('#skipBtn').is(':focus');
            if (isSkipFocus) {
                $scope.skip();
            } else {
                var interactiveFocused = getElement('#interactiveBtn').is(':focus');
                if (interactiveFocused) {
                    $scope.assignInteractiveMode();
                } else {
                    $scope.submit();
                }
            }
        }

        function onActionBack() {
            // $scope.skip();
        }

        $scope.submit = function() {
            $scope.displaySpinner = '';
            $scope.message = 'Assigning the channel to this device  <div class="la-ball-pulse la-sm"><div></div><div></div><div></div></div>';
            getElement('.reg-channel-list').css('display', 'none');
            $scope.btnHide = true;

            // prevent from submitting more than once
            $scope.skipDisabled = true;
            // add spinner to submit button
            getElement('#skipBtn').html('ASSIGNING');
            // Bind device
            var deviceGuid = deviceRegistrationService.getPlayerId();
            if (deviceGuid) {
                deviceAPIService.retrievePlayerIntIdFromGuid(deviceGuid)
                    .then(onPlayerId, onBindFail);
            } else {
                onBindFail('Device Guid is null');
            }
        };

        $scope.skip = function() {
            launchApp();
        };

        $scope.assignInteractiveMode = function() {
            if (interactiveMode) {
                $scope.selectedChannel = interactiveMode;
                $scope.submit();
                $scope.interactiveModeDisable = true;
            }
        };

        function onPlayerId(res) {
            if (res && res.Devices && res.Devices.length > 0) {
                var deviceId = res.Devices[0].Id;
                var applicationId = $scope.selectedChannel.id;
                deviceAPIService.boundDeviceToApplication(deviceId, applicationId)
                    .then(onBindSuccess, onBindFail);
            } else {
                // Check is the err an object, if so stringify it
                if (res && typeof res === 'object') {
                    res = JSON.stringify(res, null, 4);
                }
                onBindFail('Error retrieving player ID: ' + res);
            }
        }

        function onBindSuccess(response) {
            logService.debug('appspace.signs.registration', 'Device bound to application successfully');
            launchApp();
        }

        function launchApp() {
            // enable back sync orientation for ios
            halMediaService.setSyncDisplayResolutionFlag(true);
            authenticationAPIService.stopValidateToken();
            // reset user info
            authenticationAPIService.resetUserInfo();
            $location.path('/tv/appinit');
        }

        function onBindFail(err) {
            $scope.btnHide = false;
            coreConfigurationService.set('signs.registration', 'message.error', 'Failed to bind device to application');
            logService.warn('appspace.signs.registration', 'Failed to bind device to application. Error: ' + err);
            $scope.message = 'Looks like there was problem, we could not assign the channel to this device.';
            // re-enable skip
            $scope.skipDisabled = false;
            // disable interactive button
            $scope.interactiveModeDisable = true;
            // change back the button to show SKIP
            getElement('#skipBtn').html('SKIP');
        }

        init();
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    var app = angular.module('signsHTML5');
    app.controller('RegDeviceNameCtrl', ["$window", "$scope", "$location", "$timeout", "logService", "coreConfigurationService", "coreFrameworkService", "halPlatformService", "RegistrationUtil", "authenticationAPIService", "halRemoteService", "REMOTEVCODE", "REMOTEEVENTTYPE", "KEYBOARD_CODE", function($window, $scope, $location, $timeout, logService, coreConfigurationService,
        coreFrameworkService, halPlatformService, RegistrationUtil, authenticationAPIService, halRemoteService, REMOTEVCODE, REMOTEEVENTTYPE, KEYBOARD_CODE) {
        var tabIndex = 2;
        var animationDuration = 300;
        var needKeyboard = false;
        var keyboardVisible = false;
        var keyboardApi = null;

        $scope.submitDisabled = false;
        $scope.deviceName = null;
        $scope.deviceNamePlaceholder = null;

        function init() {
            // Handle view destroy
            $scope.$on('$destroy', function() {
                // clean up stuff
                halRemoteService.unRegisterEvents(onRemoteEvent);
            });

            // Register for remote input
            halRemoteService.registerEvents(onRemoteEvent);

            var name = coreConfigurationService.get('signs.registration', 'devicename');
            if (!name) {
                RegistrationUtil.processDeviceName().then(function(response) {
                    $scope.deviceNamePlaceholder = response;
                });
            } else {
                $scope.deviceNamePlaceholder = name;
            }

            // init virtual keyboard if we need one
            var keyboardSupport = halPlatformService.getFeatureSupport('input.keyboard');
            needKeyboard = keyboardSupport == null || keyboardSupport.toLowerCase() == 'none';
            $window.enableVirtualKeyboardGlobal = $window.enableVirtualKeyboardGlobal === undefined ? needKeyboard : $window.enableVirtualKeyboardGlobal;
            // focus on device name input
            $timeout(function() {
                $('[tabindex=\'' + tabIndex + '\']').focus();
            }, 500);
        }

        $scope.onLeftPanelClicked = function(e) {
            if (!e.target || e.target.tagName || e.target.tagName.toLowerCase() == 'input') {
                return;
            }

            // hide keyboard if we're clicking non-input element
            hideKeyboard();
        };

        // Event handler for remote events
        var onRemoteEvent = function(remoteEvent) {
            // Only listen to down button events
            if (remoteEvent.eventType != REMOTEEVENTTYPE.buttonDown) {
                return;
            }

            // these set of actions will run regardless of keyboard visibility
            switch (remoteEvent.vcode) {
                case REMOTEVCODE.toggle_keyboard:
                    remoteEvent.preventDefault();
                    $window.enableVirtualKeyboardGlobal = !$window.enableVirtualKeyboardGlobal;
                    toggleKeyboard();
                    return;
                case REMOTEVCODE.back: //eslint-disable-line
                    if (remoteEvent.code === KEYBOARD_CODE.ESC) {
                        remoteEvent.preventDefault();
                        toggleKeyboard();
                    }

                    return;
            }

            // these actions are only meant for when keyboard is hidden
            if (keyboardVisible) {
                return;
            }

            // Perform action based on Virtual code
            switch (remoteEvent.vcode) {
                case REMOTEVCODE.up: // Up Key
                    onActionUp();
                    break;
                case REMOTEVCODE.down: // Down Key
                    onActionDown();
                    break;
                case REMOTEVCODE.left: // Left Key
                    onActionLeft();
                    break;
                case REMOTEVCODE.right: // Right Key
                    onActionRight();
                    break;
                case REMOTEVCODE.select: // option/alt Key
                    onActionSelect();
                    break;
                case REMOTEVCODE.back: // command/windows Key
                    // ignore backspace key and shift
                    if (remoteEvent.code != 8 && remoteEvent.code != 16) {
                        onActionBack();
                    }

                    break;
            }
        };

        function onActionUp() {
            // do nothing if textbox is in focus
            if (tabIndex == 1) {
                return;
            }

            tabIndex = 1;
            // focus on textbox
            $('[tabindex=\'' + tabIndex + '\']').focus();
        }

        function onActionDown() {
            tabIndex = tabIndex == 0 ? 1 : tabIndex;

            if (tabIndex != 1) {
                return;
            }

            // go to continue if we're highlighting the device name box
            tabIndex = 2;
            $timeout(function() {
                $('[tabindex=\'' + tabIndex + '\']').focus();
            }, 200);
        }

        function onActionLeft() {
            if (tabIndex != 2) {
                return;
            }

            // go to cancel button if register is in focus
            tabIndex = 3;
            $('[tabindex=\'' + tabIndex + '\']').focus();
        }

        function onActionRight() {
            if (tabIndex != 3) {
                return;
            }

            // go to register button if cancel is in focus
            tabIndex = 2;
            $('[tabindex=\'' + tabIndex + '\']').focus();
        }

        function onActionBack() {
            if (keyboardVisible) {
                hideKeyboard();
                return;
            }

            $scope.cancel();
        }

        function onActionSelect() {
            switch (tabIndex) {
                case 1:
                case 2:
                    $scope.submit();
                    break;
                case 3:
                    $scope.cancel();
                    break;
            }
        }

        function toggleKeyboard() {
            if (keyboardVisible) {
                hideKeyboard();
                return;
            }

            showKeyboard();
        }

        function showKeyboard() {
            // if globally we don't want to show keyboard, don't show
            if ($window.enableVirtualKeyboardGlobal === false) {
                return;
            }

            if (needKeyboard && !keyboardVisible) {
                // show keyboard
                slideInRightPanel();
                // enable keyboard navigation
                enableKeyboardNavigation(true);
                $(':input').attr('readonly', 'readonly');
            }
        }

        function hideKeyboard() {
            if (keyboardVisible) {
                // hide keyboard
                slideOutRightPanel();
                // disable keyboard navigation
                enableKeyboardNavigation(false);
                $(':input').removeAttr('readonly');
            }
        }

        function enableKeyboardNavigation(enable) {
            keyboardApi.enableRemoteNavigation(enable);
        }

        function slideInRightPanel() {
            keyboardVisible = true;
            // shrink left panel and footer to 60% of window width
            $('.left-panel').velocity({width: '60%'}, {duration: animationDuration});
            $('.reg-footer').velocity({width: '60%'}, {duration: animationDuration});

            // slide right panel to the left to show it
            $('.right-panel').velocity({left: '60%', width: '40%'}, {duration: animationDuration});
        }

        function slideOutRightPanel() {
            keyboardVisible = false;
            // expand left panel and footer to 100% of window width
            $('.left-panel').velocity({width: '100%'}, {duration: animationDuration});
            $('.reg-footer').velocity({width: '100%'}, {duration: animationDuration});

            // slide right panel to the right to hide it
            $('.right-panel').velocity({left: '100%'}, {duration: animationDuration});
        }

        $scope.onInputFocus = function(event) {
            focusInput(event.target.id);
        };

        function focusInput(inputId) {
            switch (inputId) {
                case 'deviceName':
                    tabIndex = 1;
                    $scope.deviceNameFocus = true;
                    if (keyboardApi) {
                        keyboardApi.setInputValue($('#deviceName').val());
                    }

                    showKeyboard();
                    break;
                case 'submitBtn':
                    $scope.deviceNameFocus = false;
                    tabIndex = 2;
                    hideKeyboard();
                    break;
                case 'btnBack':
                    tabIndex = 3;
                    $scope.deviceNameFocus = false;
                    hideKeyboard();
                    break;
            }
        }

        $scope.onKeyboardApi = function(api) {
            keyboardApi = api;

            // import layouts into the keyboard
            var file = 'assets/keyboard-layouts/registration.json';
            keyboardApi.importLayouts(file).then(function(layouts) {
                if (layouts && layouts.length > 0) {
                    logService.warn('appspace.signs.registration', 'Keyboard layout is empty');
                }
            }, function(err) {
                logService.error('appspace.signs.registration', 'Failed to load keyboard');
            });
        };

        function addNextButtonToKeyboard() {
            // get the built-in alpahenuric layout
            var layout = keyboardApi.getLayout('alphanumeric');
            // change the id and name
            layout.id = 99;
            layout.name = 'alphanumeric' + layout.id;
            // modify the layout, add next key
            layout.rows.push([{id: 999, label: 'Next', value: 'next', type: 'action', width: 6, cssClass: 'key-small key-primary'}]);
            // add the new layout to the keyboard
            keyboardApi.addLayout(layout);
            // set the layout
            keyboardApi.setLayout(layout);
        }

        $scope.onKeyboardValueChange = function(previousVal, currentVal) {
            // set the input value
            $scope.deviceName = currentVal;
        };

        $scope.onKeyboardActionSelected = function(key) {
            if (!key) {
                return;
            }

            switch (key.value) {
                case 'abc':
                    // load the lowercase layout
                    setLayout(keyboardApi.getLayout('alphanumeric-symbols'), 'ABC');
                    break;
                case 'ABC':
                    // load the uppercase layout
                    setLayout(keyboardApi.getLayout('ALPHANUMERIC-SYMBOLS'), 'abc');
                    break;
                case 'symbols':
                    // load the symbols layout
                    setLayout(keyboardApi.getLayout('symbols'), 'abc');
                    break;
                case 'next':
                    onActionDown();
                    break;
            }
        };

        function setLayout(layout, focusKey) {
            if (!keyboardApi) {
                return;
            }

            if (keyboardApi.setLayout) {
                keyboardApi.setLayout(layout);
            }

            if (keyboardApi.focusKey) {
                keyboardApi.focusKey(focusKey);
            }
        }

        $scope.submit = function() {
            // prevent from submitting more than once
            $scope.submitDisabled = true;

            // add spinner to submit button
            $('#submitBtn').html('CONTINUE &nbsp;&nbsp;&nbsp;<i class="fa fa-circle-o-notch fa-spin"></i>');

            // submit
            var deviceName = $scope.deviceName;

            if (deviceName == null && deviceName == undefined) {
                deviceName = $scope.deviceNamePlaceholder;
            }

            coreConfigurationService.set('signs.registration', 'devicename', deviceName);
            logService.debug('appspace.signs.registration', 'Device name: ' + deviceName);
            $location.path('/tv/registrationprogress');
        };

        $scope.cancel = function() {
            var str = coreConfigurationService.get('signs.registration', 'instances');
            var instances = str ? JSON.parse(str) : [];
            if (instances.length > 1) {
                $location.path('/tv/registrationinstance');
                return;
            }

            authenticationAPIService.logout();
            $location.path('/tv/registrationaccount');
        };

        coreFrameworkService.isReady().then(init);
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    var getElement = angular.element;
    var app = angular.module('signsHTML5');
    app.controller('RegFailCtrl', ["$scope", "$location", "$timeout", "coreConfigurationService", "halRemoteService", "REMOTEVCODE", "REMOTEEVENTTYPE", "rendezvousService", "halPlatformService", "halMediaService", function($scope,
        $location,
        $timeout,
        coreConfigurationService,
        halRemoteService,
        REMOTEVCODE,
        REMOTEEVENTTYPE,
        rendezvousService,
        halPlatformService,
        halMediaService
    ) {
        var tabIndex = 1;
        var cancelTimeoutHandler;
        var DEFAULT_ERROR_VALUE = {
            TITLE: 'There was an error registering your device. Please contact Appspace support.',
            MESSAGE: '',
            SHOW_BUTTON: true,
            SHOW_LOADER: false,
        };
        $scope.isButtonVisible = DEFAULT_ERROR_VALUE.SHOW_BUTTON;
        $scope.disableretry = false;
        $scope.errorTitle = DEFAULT_ERROR_VALUE.TITLE;

        function showDriverErrorMessage() {
            var error = halPlatformService.getRegistrationError();
            $scope.errorTitle = error ? error.title : DEFAULT_ERROR_VALUE.TITLE;
            $scope.errorMessage = error ? error.message : DEFAULT_ERROR_VALUE.MESSAGE;
            $scope.isButtonVisible = error ? error.showButton : DEFAULT_ERROR_VALUE.SHOW_BUTTON;
            $scope.showLoader = error ? error.showLoader : DEFAULT_ERROR_VALUE.SHOW_LOADER;
        }

        function init() {
            // Handle view destroy
            $scope.$on('$destroy', function() {
                // clean up stuff
                halRemoteService.unRegisterEvents(onRemoteEvent);
            });

            stopRendezvousService();

            // Register for remote input
            halRemoteService.registerEvents(onRemoteEvent);

            // Enable back rotation for IOS
            halMediaService.setSyncDisplayResolutionFlag(true);

            var errMessage = coreConfigurationService.get('signs.registration', 'message.error');
            if (!errMessage) {
                errMessage = '';
            }

            $scope.errorMessage = errMessage;

            // check for retry
            var qs = $location.search();
            if (qs.retry == 'false') {
                $scope.disableretry = true;
                cancelTimeoutHandler = $timeout($scope.cancelRegistration, 60000);
                $scope.errorMessage += ' Redirecting in 60 seconds.';
            }

            showDriverErrorMessage();
        }

        function stopRendezvousService() {
            // stop rv service and notfiy the code is claimed
            if (rendezvousService.isStarted()) {
                rendezvousService.notify();
                rendezvousService.stop();
            }
        }

        // Event handler for remote events
        var onRemoteEvent = function(remoteEvent) {
            // Only listen to down button events
            if (remoteEvent.eventType != REMOTEEVENTTYPE.buttonDown) {
                return;
            }

            // Perform action based on Virtual code
            switch (remoteEvent.vcode) {
                case REMOTEVCODE.left: // Left Key
                    onActionLeft();
                    remoteEvent.preventDefault();
                    break;
                case REMOTEVCODE.right: // Right Key
                    onActionRight();
                    remoteEvent.preventDefault();
                    break;
                case REMOTEVCODE.select: // option/alt Key
                    onActionSelect();
                    remoteEvent.preventDefault();
                    break;
                case REMOTEVCODE.back: // command/windows Key
                    if (!$scope.disableretry) {
                        onActionBack();
                        remoteEvent.preventDefault();
                    }

                    break;
            }
        };

        function onActionLeft() {
            // focus on retry
            if (tabIndex == 2) {
                tabIndex = 1;
                getElement('[tabindex=\'' + tabIndex + '\']').focus();
            }
        }

        function onActionRight() {
            // focus on cancel
            if (tabIndex == 1) {
                tabIndex = 2;
                getElement('[tabindex=\'' + tabIndex + '\']').focus();
            }
        }

        function onActionBack() {
            $scope.retryRegistration();
        }

        function onActionSelect() {
            if (tabIndex == 1) { // Retry button is in focused
                $scope.retryRegistration();
            } else if (tabIndex == 2) { // Cancel button is in focused
                $scope.cancelRegistration();
            }
        }

        $scope.retryRegistration = function() {
            // Disable ios screen rotation is registration retry
            halMediaService.setSyncDisplayResolutionFlag(false);
            $location.path('/tv/registrationdevicename');
        };

        $scope.cancelRegistration = function() {
            if (cancelTimeoutHandler) {
                $timeout.cancel(cancelTimeoutHandler);
                cancelTimeoutHandler = null;
            }

            coreConfigurationService.removeCollection('signs.registration');
            $location.url('/tv/registrationlanguage');
        };

        init();
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    var app = angular.module('signsHTML5');
    app.controller('RegCtrl', ["$location", "$scope", "$timeout", "logService", "halPlatformService", "RegistrationUtil", "coreFrameworkService", "DateTimeUtil", "ROUTE", function(
        $location,
        $scope,
        $timeout,
        logService,
        halPlatformService,
        RegistrationUtil,
        coreFrameworkService,
        DateTimeUtil,
        ROUTE) {
        var LOG_KEY = 'appspace.signs.registration';
        var SECONDS_TO_MILLISECONDS = 1000;
        $scope.message = undefined;
        var failureCount = 0;
        var claimDeviceTimeoutPromise = undefined;
        var dateTimeUtil = new DateTimeUtil();
        var BACK_OFF_INTERVAL_IN_SECONDS = 30;
        var BACK_OFF_MAX_IN_SECONDS = 300; // 5 minutes
        var BACK_OFF_CONFIGURATION = {
            backoffInterval: BACK_OFF_INTERVAL_IN_SECONDS,
            failureBackoffMax: BACK_OFF_MAX_IN_SECONDS
        };

        function init() {
            if (halPlatformService.isClaimDeviceMode()) {
                logService.info(LOG_KEY, 'Claim device mode is in force. Skipping redirection to registration page...');
                claimDevice();
                return;
            }

            RegistrationUtil.retrievePolicyWithInjection().then(onPolicy, goToRegistrationPage);
        }

        function retryClaimDevice() {
            cancelClaimDevice();
            var backOffTime = dateTimeUtil.getBackoffTimeInSeconds(failureCount, BACK_OFF_CONFIGURATION);

            var nextRetry = dateTimeUtil.toTimeText(backOffTime);
            logService.debug(LOG_KEY, 'Claim device will be retried in ' + nextRetry + '. Failure count: ' + failureCount);
            $scope.message = 'Failed to provision device. Retrying in ' + nextRetry + '...';
            claimDeviceTimeoutPromise = $timeout(claimDevice, backOffTime * SECONDS_TO_MILLISECONDS);
        }

        function cancelClaimDevice() {
            if (!claimDeviceTimeoutPromise) {
                return;
            }

            $timeout.cancel(claimDeviceTimeoutPromise);
            claimDeviceTimeoutPromise = undefined;
        }

        function claimDevice() {
            cancelClaimDevice();
            $scope.message = 'Provisioning device...';
            halPlatformService.claimDevice().then(function() {
                $location.path(ROUTE.APP_INIT);
            }, function(error) {
                logService.error(LOG_KEY, error && error.message ? error.message : 'Failed to provision device.');
                failureCount++;
                retryClaimDevice();
            });
        }

        function onPolicy(res) {
            if (res && res.isProcessed) {
                logService.debug(LOG_KEY, 'Policy injection processed. Initializing app...');
                // if we inject device GUID or Appspace URL, directly go to app init
                $location.path(ROUTE.APP_INIT);
                return;
            }

            goToRegistrationPage();
        }

        function goToRegistrationPage() {
            logService.debug(LOG_KEY, 'Proceed to registration page');
            $location.path(ROUTE.REGISTRATION);
        }

        coreFrameworkService.isReady().then(init);
    }]);
})(window.angular);

(function (angular) {
    var app = angular.module('signsHTML5');
    app.controller('RegInstanceCtrl', ["$scope", "$location", "$timeout", "$filter", "$q", "coreConfigurationService", "logService", "coreFrameworkService", "halRemoteService", "authenticationAPIService", "REMOTEVCODE", "REMOTEEVENTTYPE", "halPlatformService", function ($scope, $location, $timeout, $filter, $q,
        coreConfigurationService, logService, coreFrameworkService, halRemoteService, authenticationAPIService, REMOTEVCODE, REMOTEEVENTTYPE, halPlatformService) {
        $scope.instances = [];
        $scope.displaySpinner = '';
        $scope.message = 'Retrieving instances <div class="la-ball-pulse la-sm"><div></div><div></div><div></div></div>';
        $scope.gridItems = [];
        $scope.skipDisabled = false;
        var gridApi = null;
        var selectedInstance = null;
        var skipToTop = true;
        var username;
        var password;

        $scope.gridConfig = {
            padding: { top: 0, left: 0, right: 0, bottom: 0 },
            itemSpacing: { horizontal: 10, vertical: 5 },
            itemWidth: 0,
            itemHeight: 45,
            scrollDirection: "vertical",
        }

        function init() {
            //Handle view destroy
            $scope.$on('$destroy', function () {
                // clean up stuff
                halRemoteService.unRegisterEvents(onRemoteEvent);
            });

            if(halPlatformService.getFeatureSupport("rendering.support.viewport.width.height") == "no"){
				// manual height for iec and ssd as css:vh doesnt work
				var height = $('.reg-channel-container').height();
				$('.reg-channel-container').height(height + "px");
			}

            //Register for remote input
            halRemoteService.registerEvents(onRemoteEvent);

            var cred = coreConfigurationService.get('signs.registration', 'cred');
            var obj = JSON.parse(cred);
            username = obj.u;
            password = obj.p;

            var str = coreConfigurationService.get('signs.registration', 'instances');
            onRetrievedInstances(JSON.parse(str));
            //highlight skip button
            $timeout(function () { //focus on skip button
                $("#backBtn").focus();
                selectedInstance = null;
            }, 500);
        }

        function onRetrievedInstances(response) {
            $timeout(function () {
                if (response.length > 0) {
                    var instances = [];
                    logService.debug("appspace.signs.registration", "Instance list retrieved successfully");
                    //if only 1 instance, skip to device name

                    for (var i = 0; i < response.length; i++) {
                        var instance = response[i];
                        var name = instance.Name;
                        var ip = instance.Instance.IP;
                        var accountId = instance.InstanceAccount.InstanceAccountId;
                        if (!name || name == '' || name == ' ') {
                            name = ip;
                        }
                        instances.push({ name: name, ip: ip, accountId: accountId });
                    }
                    $scope.instances = $filter('orderBy')(instances, 'name');
                    $scope.displaySpinner = 'none';
                    generateListing();
                } else {
                    logService.warn("appspace.signs.registration", "Instance list is 0");
                }
            }, 500);
        }

        function onFailedToRetrievedInstances() {
            $scope.message = 'Hmmm... looks like we can\'t fetch yours instances from Cloud';

        }

        function generateListing() {
            var items = [];
            if ($scope.instances) {
                for (var i = 0; i < $scope.instances.length; i++) {
                    var instance = $scope.instances[i];
                    var element = createGridElement(instance);
                    if (element) {
                        items.push(element);
                    }
                }
            }
            $scope.gridItems = items;
            $timeout(function () {
                onActionUp();
            }, 500);

        }

        function createGridElement(instance) {
            if (instance && instance.name.length > 0 && instance.name != " ") {
                var markup = '<div class="list-group-item" id="' + instance.ip + '">' + instance.name + "</div>";

                var t = { type: "markup", markup: markup };
                return t;
            }
            return null;
        }

        $scope.submit = function () {
            $scope.displaySpinner = 'none';

            $scope.skipDisabled = true;
            $("#backBtn").html('AUTHENTICATING');

            onInstanceSelected().then(function () {
                $location.path('/tv/registrationdevicename')
            }, function (err) {
                $(".reg-channel-list").css("display", 'none');
                $scope.displaySpinner = '';
                $scope.message = 'Unable to authenticate on the selected instance';
                logService.warn("appspace.signs.registration", "Failed to authenticate to instance. Error: " + JSON.stringify(err));
                //re-enable listing
                $timeout(function () {
                    $scope.displaySpinner = 'none';
                    $scope.message = '';
                    $scope.skipDisabled = false;
                    $("#backBtn").html('BACK');
                    $(".reg-channel-list").css("display", '');
                }, 2000)
            });
        }

        $scope.cancel = function () {
            authenticationAPIService.logout();
            $location.path('/tv/registrationaccount');
        }

        //on api ready
        $scope.onGridApi = function (api) {
            gridApi = api;
        };

        //on item selected
        $scope.onGridItem = function (api, offset, fromClick) {
            if (fromClick || selectedInstance == $scope.instances[offset]) {
                $timeout(function () {
                    //Delay so we can highlight the current item
                    onActionSelect();
                }, 100);
            }
            selectedInstance = $scope.instances[offset];
        }

        function onInstanceSelected() {
            var promise = $q.defer();
            if (selectedInstance) {
                coreConfigurationService.set('appspace', 'appspaceURL', selectedInstance.ip);
                //login to core using authenticationAPIService
                authenticationAPIService.login(username, password, selectedInstance.accountId).then(function () {
                    promise.resolve();

                }, function (err) {
                    promise.reject(err);

                });
            }
            return promise.promise;
        }

        //Event handler for remote events
        function onRemoteEvent(remoteEvent) {
            //Only listen to down button events
            if (remoteEvent.eventType != REMOTEEVENTTYPE.buttonDown) {
                return;
            }
            //Perform action based on Virtual code
            switch (remoteEvent.vcode) {
                case REMOTEVCODE.up: //Up Key
                    onActionUp();
                    remoteEvent.preventDefault();
                    break;
                case REMOTEVCODE.down: //Down Key
                    onActionDown();
                    remoteEvent.preventDefault();
                    break;
                case REMOTEVCODE.right: //Right Key
                    onActionRight();
                    remoteEvent.preventDefault();
                    break;
                case REMOTEVCODE.left: //Right Key
                    onActionRight();
                    remoteEvent.preventDefault();
                    break;
                case REMOTEVCODE.select: //option/alt Key
                    onActionSelect();
                    remoteEvent.preventDefault();
                    break;
                case REMOTEVCODE.back: //command/windows Key
                    onActionBack();
                    remoteEvent.preventDefault();
                    break;
            }

        };

        function onActionUp() {
            //if over the array bound, select the last item
            var length = $scope.instances.length;

            //If no channels just focus on skip
            if (length == 0) {
                //focus on skip button
                $("#backBtn").focus();
                return;
            }

            var isBackFocus = $("#backBtn").is(":focus")
            if (isBackFocus) {
                //remove focus from back button
                $("#backBtn").blur();

                if (skipToTop) {
                    gridApi.moveToItem(0);
                    skipToTop = false;
                }
                else {
                    gridApi.moveToItem(length - 1);
                }
            }
            else {
                gridApi.upItem();
            }
        }

        function onActionDown() {
            var isBackFocus = $("#backBtn").is(":focus")
            if (isBackFocus) {
                return;
            }

            var currentOffset = gridApi.getCurrentItemOffset();
            //if over the array bound, select the skip button
            var length = $scope.instances.length;

            //If no channels just focus on skip
            if (length == 0) {
                //focus on skip button
                $("#backBtn").focus();
                return;
            }

            if (currentOffset >= length - 1) {
                //focus on back button
                $("#backBtn").focus();
                selectedInstance = null;
                //remove highlight on last item
                gridApi.resetFocusedItem();
            }
            else {
                gridApi.downItem();
            }
        }

        function onActionRight() {
            var isBackFocus = $("#backBtn").is(":focus");
            if (isBackFocus) {
                return;
            }

            //remove highlight on last item
            gridApi.resetFocusedItem();
            //focus on skip button
            $("#backBtn").focus();
            selectedInstance = null;

        }

        function onActionSelect() {
            var isBackFocus = $("#backBtn").is(":focus");

            if (isBackFocus) {
                $scope.cancel();
            } else {
                $scope.submit();
            }
        }

        function onActionBack() {
            $scope.cancel();
        }

        coreFrameworkService.isReady().then(init);
    }])
})(window.angular);

(function($angular) {
    var angular = $angular;
    var app = angular.module('signsHTML5');
    app.controller('RegLanguageCtrl', ["$window", "$scope", "$q", "$location", "$timeout", "syncService", "halRemoteService", "coreFrameworkService", "registrationRestorationService", "REMOTEVCODE", "REMOTEEVENTTYPE", "RegistrationUtil", "jwtUtil", "telemetryService", "halPlatformService", "rendezvousService", "logService", "halMediaService", "sessionService", "deviceStateService", "postMessagingService", "POST_MESSAGES_ERROR", "ASERRORCODE", "ROUTE", function(
        $window,
        $scope,
        $q,
        $location,
        $timeout,
        syncService,
        halRemoteService,
        coreFrameworkService,
        registrationRestorationService,
        REMOTEVCODE,
        REMOTEEVENTTYPE,
        RegistrationUtil,
        jwtUtil,
        telemetryService,
        halPlatformService,
        rendezvousService,
        logService,
        halMediaService,
        sessionService,
        deviceStateService,
        postMessagingService,
        POST_MESSAGES_ERROR,
        ASERRORCODE,
        ROUTE) {
        var selectedIndex = 0;
        var checkPolicyInitialHandler = null;
        var checkPolicyLoopHandler = null;

        // info box
        var INFOBOXHEIGHT = '2em';
        $scope.showSwitchMode = false;
        $scope.showInfo = false;
        $scope.showCode = true;
        $scope.showInfoHolder = {'visibility': 'hidden', 'height': INFOBOXHEIGHT};
        $scope.autoCode = '';
        $scope.qrCodeClaimUrl;
        $scope.isPassive = false;
        $scope.isPolicyBased = false;
        $scope.registrationOptionMessage = '';
        $scope.codeHidden = false;
        $scope.statusMessage = '';
        var registrationRestorationState = {
            isInformationRequested: false,
            deviceState: {},
        };
        var isRegistrationRestorationApproved = false;
        var isPolicyRegistrationCancelled = false;
        var hideCodeHandler = null;
        var infoMessage = '';
        var currCode = '';
        var executingSilentRegDelayHandler;
        var policy;
        var countdown;
        var hideCodeTimeout = 120000;
        var logKey = 'appspace.signs.registration';
        var registrationSource;
        var REGISTRATION_SOURCE = {
            network: {
                value: 'network',
                platform: 'network'
            },
            device: {
                value: 'device'
            },
            local: {
                value: 'local'
            },
            chrome: {
                value: 'chrome'
            },
            appspaceconfiguration: {
                value: 'Appspace configuration'
            },
        };

        function restoreRegistration() {
            var deferred = $q.defer();
            var restorePlatformRegistration = function() {
                logService.debug(logKey, 'Unable to restore previous registration. Attempting to restore platform-based registration...');
                RegistrationUtil.restorePlatformRegistration().then(function() {
                    logService.info(logKey, 'Platform registration information restored');
                    goTo(ROUTE.REGISTRATION_RESTORE_SUCCESS);
                    deferred.resolve({
                        isSuccess: true
                    });
                }, function(error) {
                    logService.debug(logKey, 'No registration information to restore: ', JSON.stringify(error));
                    deferred.resolve({
                        isSuccess: false
                    });
                });
            };

            logService.debug(logKey, 'Attempting to restore previous registration...');
            registrationRestorationService.getRegistrationInfo().then(function(registrationInfo) {
                logService.info(logKey, 'Registration restoration info retrieved: ' + JSON.stringify(registrationInfo));
                registrationRestorationState.deviceState = registrationInfo.deviceState || {};
                registrationRestorationService.createRestoreRequest(registrationInfo).then(function(response) {
                    if (!response) {
                        logService.debug(logKey, 'No response received upon creating registration restoration request');
                        restorePlatformRegistration();
                        return;
                    }

                    startRendezvousService();
                    registrationRestorationState.isInformationRequested = true;
                    var isApproved = Boolean(response.data && response.data.status === 'Approved');
                    isRegistrationRestorationApproved = isApproved;
                    deferred.resolve({
                        isSuccess: isApproved,
                        disablePolicyRegistration: true,
                        message: isApproved ? '' : 'Please be informed that your device is currently undergoing registration restoration, and policy-based registration is not used. This is due to a pending approval process.'
                    });
                    if (!isApproved) {
                        logService.debug(logKey, 'Registration restoration request is pending approval.');
                        return;
                    }
                }, restorePlatformRegistration);
            }, restorePlatformRegistration);
            return deferred.promise;
        }

        function updateState(state) {
            deviceStateService.setCurrentState(state);
        }

        function goTo(path) {
            updateState();
            $location.path(path);
        }

        function init() {
            updateState({
                code: ASERRORCODE.DEVICE_NOT_REGISTERED,
                message: 'Device is not registered',
            });
            var displayRotationPromise = halMediaService.setDisplayRotation('unlock');
            if (displayRotationPromise) {
                displayRotationPromise.then($angular.noop, $angular.noop);
            }

            // Handle view destroy
            $scope.$on('$destroy', function() {
                // clean up stuff
                halRemoteService.unRegisterEvents(onRemoteEvent);
                stopPolicyLoop();
                clearCodeHandler();
            });

            // Register for remote input
            halRemoteService.registerEvents(onRemoteEvent);

            // Stops the sync service
            syncService.stopSync();

            $scope.languages = [{name: 'Lets get started!', value: 'english'},
                // { name: "Francais", value: "francais" },
                // { name: "Deutsch", value: "deutsch" },
                // { name: "Espanol", value: "espanol" }
            ];

            // Get passive mode
            $scope.isPassive = sessionService.get('passiveRegistration');

            // Init initial language
            $scope.language = $scope.languages[selectedIndex];

            showInitMessage();
            rendezvousService.subscribe(onRendezvousEvent);
            restoreRegistration().then(function(restorationStatus) {
                // Disable policy registration if restoration is in progress or
                // policy registration was explicitly cancelled
                isPolicyRegistrationCancelled = restorationStatus.disablePolicyRegistration || isPolicyRegistrationCancelled;
                $scope.statusMessage = restorationStatus.message || '';
                if (!restorationStatus.isSuccess) {
                    startCheckPolicy(0);
                }

                var deviceType = halPlatformService.getAppspaceDeviceType();
                if (deviceType === 'ANDROID' || deviceType === 'IOS') {
                    $scope.showSwitchMode = true;
                }

                postMessagingService.raiseCrestronLoadedMessage(POST_MESSAGES_ERROR.NOT_REGISTERED);
                $scope.headline = '<p><a>From a computer</a><br>Log into the Appspace Console and enter the claim code.</p>' +
                    '<p><a>From a smartphone or tablet</a><br>Log into the Appspace Console and enter the claim code or scan the QR code.</p>';

                if (!$scope.isPassive) {
                    $scope.registrationOptionMessage = 'Please choose an option below to register this device.';
                    logService.info(logKey, 'Registration mode: interactive');
                    return;
                }

                logService.info(logKey, 'Registration mode: passive');
            });
        }

        function showInitMessage() {
            resetMessage();
            infoMessage = 'Give us a moment while we retrieve a registration code.';
        }

        function doSilentRegistration(p) {
            if (!RegistrationUtil.isPolicyValid((p || {}).registration)) {
                logService.debug(logKey, 'Invalid policy', p);
                return;
            }

            halPlatformService.setAppPolicy(p);
            stopPolicyLoop();
            goTo('/tv/silentregistration');
        }

        function stopPolicyLoop() {
            if (checkPolicyInitialHandler) {
                $timeout.cancel(checkPolicyInitialHandler);
                checkPolicyInitialHandler = null;
            }

            if (checkPolicyLoopHandler) {
                $timeout.cancel(checkPolicyLoopHandler);
                checkPolicyLoopHandler = null;
            }

            if (executingSilentRegDelayHandler) {
                $timeout.cancel(executingSilentRegDelayHandler);
                executingSilentRegDelayHandler = null;
            }

            rendezvousService.unsubscribe(onRendezvousEvent);
        }

        function startCheckPolicy(delay) {
            if (checkPolicyLoopHandler) {
                $timeout.cancel(checkPolicyLoopHandler);
            }

            if (delay == undefined || delay == null) {
                delay = 60000;
            }

            checkPolicyLoopHandler = $timeout(checkPolicy, delay);
        }

        function checkPolicy() {
            rendezvousService.claimCode();
            policy = null;
            countdown = 15;
            if (isPolicyRegistrationCancelled) {
                logService.debug(logKey, 'Skipping policy registration as it was previously cancelled');
                return;
            }

            var onRetrievePolicyInvalid = function() {
                // start rendezvousService
                startRendezvousService();
                // sleep for 60 secs before checking again for policy
                startCheckPolicy();
            };

            RegistrationUtil.retrievePolicyWithInjection().then(function(res) {
                if (res.isProcessed) {
                    goTo('/tv/appinit');
                    return;
                }

                var p = res.policy;
                if (!p ||
                    !RegistrationUtil.isPolicyValid(p.registration)) {
                    logService.debug(logKey, 'Invalid policy', p);
                    onRetrievePolicyInvalid();
                    return;
                }

                // silent registration
                // Log INFO: Device will log attempt and specifies if successful or not
                logService.info(logKey, 'Policy found');
                logService.debug(logKey, 'Policy file : ', p);
                policy = p;
                registrationSource = policy.registration.source;
                // change message saying that we will register in 5 seconds
                // start silent registration in 5 seconds
                resetMessage();
                var registrationConfig = REGISTRATION_SOURCE[registrationSource];
                var policySource = '';
                var policyPlatform = REGISTRATION_SOURCE['device'].value;
                if (registrationConfig) {
                    policySource = registrationConfig.value;
                    policyPlatform = registrationConfig.platform || policyPlatform;
                }

                $scope.isPolicyBased = true;
                $scope.headline = 'We have found a ' + policySource + ' <a>registration policy</a> on your ' + policyPlatform;
                executingSilentRegDelayHandler =
                    $timeout(countdownToSilentRegistration, 1000);
            }, function(err) {
                logService.info(logKey, 'No policy found, retrying again in 60 seconds');
                logService.debug(logKey, 'Policy error,', err);
                onRetrievePolicyInvalid();
            });
        }

        function countdownToSilentRegistration() {
            // if we already cancel the timeout, don't do anything
            if (executingSilentRegDelayHandler == null ||
                executingSilentRegDelayHandler == undefined) {
                return;
            }

            // if time is up, start silent registration
            if (countdown == 0) {
                doSilentRegistration(policy);
                return;
            }

            infoMessage = 'This device will start automatic registration in ' + countdown + ' ' + (countdown > 1 ? 'seconds' : 'second');
            $scope.countdown = countdown + 's';
            countdown--;
            executingSilentRegDelayHandler =
                $timeout(countdownToSilentRegistration, 1000);
        }

        function startRendezvousService() {
            if (!rendezvousService.isStarted()) {
                rendezvousService.start();
            }
        }

        function onRendezvousEvent(event) {
            switch (event.event) {
                case 'on-policy':
                    if (event.policy && event.policy.registration) {
                        event.policy.registration.source = 'discovery';
                    }

                    if (isRegistrationRestorationApproved) {
                        logService.info(logKey, 'Received policy but registration restoration is already approved, skipping policy registration');
                        return;
                    }

                    doSilentRegistration(event.policy);
                    break;
                case 'on-code':
                    if (currCode != event.code) {
                        currCode = event.code;
                        showCode();
                    }

                    break;
                case 'offline':
                    showOfflineMessage();
                    break;
                case 'service-down':
                    showInternalIssueMessage();
                    break;
                case 'on-jwt':
                    if (!event.deviceIdentityJwt || !registrationRestorationState.isInformationRequested) {
                        return;
                    }

                    restoreRegistrationInfoFromJWT(event.deviceIdentityJwt);
                    break;
                case 'on-deleted':
                    if (!registrationRestorationState.isInformationRequested) {
                        return;
                    }

                    isPolicyRegistrationCancelled = false;
                    $scope.statusMessage = 'The device registration restoration process has been stopped as the device was deleted from Console.';
                    // Device has been deleted, proceed to check for registration policy
                    startCheckPolicy(0);
                    break;
            }
        }

        function restoreRegistrationInfoFromJWT(jwt) {
            var registrationInformation = null;
            var deviceIdentityJwt = jwtUtil.decode(jwt);
            var onFailRestoreRegistrationInfo = function(error) {
                logService.error(logKey, 'Unable to restore registration information from jwt: ' + error);
            };

            if (!deviceIdentityJwt || !deviceIdentityJwt.metadata) {
                return onFailRestoreRegistrationInfo('Invalid device identity jwt: ' + JSON.stringify(deviceIdentityJwt));
            }

            registrationInformation = deviceIdentityJwt.metadata;
            if (!registrationInformation || !registrationInformation.accountId
                || !registrationInformation.deviceId || !registrationInformation.instanceUrl
                || !registrationInformation.instanceId) {
                return onFailRestoreRegistrationInfo('Invalid registration information: ' + JSON.stringify(registrationInformation));
            }

            RegistrationUtil.saveRegistrationInfo(registrationInformation);
            RegistrationUtil.saveDeviceState(registrationRestorationState.deviceState);
            logService.info(logKey, 'Registration restoration request approved. Restoring registration information...');
            telemetryService.addToQueue({
                module: 'device-registration',
                type: 'recovery',
                description: 'Device recovered from registration loss',
            });
            goTo(ROUTE.REGISTRATION_RESTORE_SUCCESS);
        }

        // Show code
        function showCode() {
            // if policy file is already there, don't show the code
            if (policy && $scope.isPolicyBased) {
                return;
            }

            if (currCode && !$scope.codeHidden) {
                $scope.autoCode = currCode;
                $scope.qrCodeClaimUrl = RegistrationUtil.getQrClaimCodeUrl($scope.autoCode);
                infoMessage = 'Use the code below to register this device online in the Appspace console.';
                $scope.showInfoHolder = {'visibility': 'visible', 'height': INFOBOXHEIGHT};
                $scope.showInfo = true;
                $scope.showCode = true;
                if (!$scope.isPassive) {
                    startCodeTimer();
                }
            }
        }

        function hideCode() {
            // Press up key or move your mouse to reveal auto registration code
            clearCodeHandler();
            infoMessage = 'Press <i class="icon-up-arrow" aria-hidden="true"></i> or move your mouse to reveal the registration code.';
            $scope.showCode = false;
            $scope.codeHidden = true;
        }

        function startCodeTimer() {
            if ($scope.isPolicyBased && !$scope.isPolicyRegistrationCancelled) {
                return;
            }

            // set timer for 2 minutes.
            // if time is up, hide the code.
            clearCodeHandler();
            hideCodeHandler = $timeout(hideCode, hideCodeTimeout);
        }

        function clearCodeHandler() {
            if (hideCodeHandler) {
                $timeout.cancel(hideCodeHandler);
                hideCodeHandler = null;
            }

            $scope.codeHidden = false;
        }

        function showOfflineMessage() {
            // clear claim code
            $scope.codeHidden = true;
            $scope.autoCode = '';
            $scope.qrCodeClaimUrl = '';
            resetMessage();
            infoMessage = 'Connect this device to the internet to retrieve your code and register online.';
            // stop rendezvous service so that it will auto-retry after checking policy
            rendezvousService.stop();
        }

        function showInternalIssueMessage() {
            resetMessage();
            infoMessage = 'Contact Appspace support for more information. Looks like we cannot retrieve your registration code.';
            // stop rendezvous service so that it will auto-retry after checking policy
            rendezvousService.stop();
        }

        // to reset the show code flag, and info
        function resetMessage() {
            $scope.showInfoHolder = {'visibility': 'visible', 'height': INFOBOXHEIGHT};
            $scope.showInfo = true;
            $scope.showCode = false;
            $scope.codeHidden = false;
            currCode = '';
            infoMessage = '';
        }

        $scope.getInfoMessage = function() {
            return infoMessage;
        };

        // Event handler for remote events
        var onRemoteEvent = function(remoteEvent) {
            // Only listen to down button events
            if (remoteEvent.eventType != REMOTEEVENTTYPE.buttonDown) {
                return;
            }

            // Perform action based on Virtual code
            switch (remoteEvent.vcode) {
                case REMOTEVCODE.up: // Up Key
                    onActionUp();
                    remoteEvent.preventDefault();
                    break;
                case REMOTEVCODE.down: // Down Key
                    onActionDown();
                    remoteEvent.preventDefault();
                    break;
                case REMOTEVCODE.left: // Left Key
                    // onActionLeft();
                    break;
                case REMOTEVCODE.right: // Right Key
                    // onActionRight();
                    break;
                case REMOTEVCODE.select: // option/alt Key
                    onActionSelect();
                    remoteEvent.preventDefault();
                    break;
                case REMOTEVCODE.back: // command/windows Key
                    // onActionBack();
                    break;
            }
        };

        function onActionUp() {
            $scope.codeHidden = false;
            showCode();
        }

        function onActionDown() {
            selectedIndex += 1;

            // if over the array bound, select the last item
            var length = $scope.languages.length;
            if (selectedIndex >= length) {
                selectedIndex = length - 1;
            }

            $timeout(function() {
                $scope.language = $scope.languages[selectedIndex];
            }, 0);
        }

        function onActionSelect() {
            $scope.submit();
        }

        $scope.isSelected = function(selectedLanguage) {
            return $scope.language === selectedLanguage;
        };

        $scope.submit = function() {
            if ($scope.language == null) {
                $scope.language = 'english';
            }

            goTo('/tv/registrationaccount');
        };

        $scope.cancel = function() {
            stopPolicyLoop();
            $scope.isPolicyBased = false;
            isPolicyRegistrationCancelled = true;
            init();
            startRendezvousService();
        };

        $scope.isInteractiveRegistration = function() {
            return !$scope.isPassive && !$scope.isPolicyBased;
        };

        $scope.onMouseMove = function() {
            $scope.codeHidden = false;
            showCode();
        };

        $scope.switchMode = function() {
            $window.location.replace('index.html?mode=mobile');
        };

        coreFrameworkService.isReady().then(init);
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    var app = angular.module('signsHTML5');

    app.controller('RegProgressCtrl', ["$scope", "$location", "$q", "$timeout", "logService", "storageLocalStorageService", "coreConfigurationService", "deviceRegistrationService", "halPlatformService", "coreFrameworkService", "rendezvousService", "RegistrationUtil", "postMessagingService", "deviceStateService", function($scope, $location, $q, $timeout,
        logService, storageLocalStorageService, coreConfigurationService,
        deviceRegistrationService, halPlatformService, coreFrameworkService,
        rendezvousService, RegistrationUtil, postMessagingService, deviceStateService) {
        var deviceName = '';
        var REGISTRATION_COMPLETE_DELAY = 3000;

        $scope.registrationMessage = '';
        $scope.registrationProgress = 0;

        function init() {
            startRegistration();
        }

        function startRegistration() {
            // Clear previous sync values if they still exist
            deviceStateService.clearCachedScripts();

            // save the selection channel to true indicate manual registration
            storageLocalStorageService.set('signs.registration', 'registrationmode', 'manual');

            $scope.registrationProgress = 0;
            $scope.registrationMessage = 'Starting registration';

            retrieveInformation();
            dostart();
        }

        function retrieveInformation() {
            deviceName = coreConfigurationService.get('signs.registration', 'devicename');
            if (!deviceName || deviceName == 'null') {
                deviceName = 'default-device-name';
            }
        }

        function runStartupScripts() {
            var promise = $q.defer();
            // retrieve app policy
            RegistrationUtil.getPolicy().then(function(policy) {
                var script = null;
                // brightsign script
                if (policy && policy.startup && policy.startup.scriptBRS) {
                    script = policy.startup.scriptBRS;
                }

                if (script) {
                    logService.info('appspace.signs.registration', 'Executing Brightsign startup script:' + script);
                    var executePromise = halPlatformService.executePlatformScript({type: 'file', args: script});
                    if (executePromise) {
                        executePromise.then(function() {
                            logService.debug('appspace.signs.registration', 'Brightsign startup script executed');
                            promise.resolve();
                        }, function(err) {
                            logService.debug('appspace.signs.registration', 'Brightsign startup script fail to execute', err);
                            promise.resolve();
                        });
                    } else {
                        logService.warn('appspace.signs.registration', 'Driver doesn\'t support executePlatformScript');
                        promise.resolve();
                    }
                } else {
                    promise.resolve();
                }
            }, promise.resolve);
            return promise.promise;
        }

        function dostart() {
            deviceRegistrationService.register(deviceName).then(function(registrationConfig) {
                $scope.registrationProgress = 100;
                runStartupScripts().then(function() {
                    $timeout(loadSuccessPage, REGISTRATION_COMPLETE_DELAY);
                });
            }, function(err) {
                $scope.registrationMessage = err;
                coreConfigurationService.set('signs.registration', 'message.error', err);
                loadErrorPage(err);
            }, function(progress) {
                $scope.registrationProgress = (progress.step / progress.total) * 100;
                $scope.registrationMessage = progress.message;
            });
        }

        function loadSuccessPage() {
            RegistrationUtil.postRegistrationCallback();
            postMessagingService.raiseSuccessfulRegistrationMessage();
            RegistrationUtil.persistRegistrationInfo();
            logService.info('appspace.signs.registration', 'Device successfully registered');
            $location.path('/tv/registrationchannel');
            stopRendezvousService();
        }

        function stopRendezvousService() {
            // stop rv service and notfiy the code is claimed
            if (rendezvousService.isStarted()) {
                rendezvousService.notify();
                rendezvousService.stop();
            }
        }

        function loadErrorPage(err) {
            if (!err) {
                err = '';
            }

            logService.warn('appspace.signs.registration', 'Device failed to register: ', err);
            $location.path('/tv/registrationfail');
        }

        coreFrameworkService.isReady().then(init);
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');
    app.factory('RegistrationUtil', ["$q", "$http", "$location", "$window", "coreConfigurationService", "sessionService", "contentFallbackService", "halPlatformService", "softwareUpdateService", "telemetryService", "halNetworkService", "deviceStateService", "logService", "coreFrameworkService", "APPSPACE_CLOUD_API_MAPPING", "metricService", "playlistSchedulerService", "admService", "deviceMetricsService", "$timeout", "STORAGE_COLLECTION_KEY", "STORAGE_KEY", "cronService", "FEATURE_CODE", "SYSTEM_DATA", "APPSPACE_CONFIGURATION_PROPERTY", "deviceTokenService", "versionUtil", "dataUtil", function($q, $http, $location, $window, coreConfigurationService, sessionService, contentFallbackService,
        halPlatformService, softwareUpdateService, telemetryService, halNetworkService, deviceStateService, logService, coreFrameworkService, APPSPACE_CLOUD_API_MAPPING,
        metricService, playlistSchedulerService, admService, deviceMetricsService, $timeout, STORAGE_COLLECTION_KEY, STORAGE_KEY,
        cronService, FEATURE_CODE, SYSTEM_DATA, APPSPACE_CONFIGURATION_PROPERTY, deviceTokenService, versionUtil, dataUtil) {
        var cloudApiUrls = APPSPACE_CLOUD_API_MAPPING;
        var LOG_KEY = 'appspace.signs.registration';
        var URL_PARAMETERS_KEY = 'urlparameters';
        var DEFAULT_RESET_NAVIGATION_DELAY = 3000;
        var DEFAULT_DEVICE_TASK_TIMESTAMP = 0;
        var TASK_QUEUE_MARKER_PERSISTENCE_SUPPORTED_VERSIONS = {
            CLOUD: {
                MIN: '8.9.0-ac.1',
                MAX: '8.9.0'
            },
            GLOBAL: '8.9.0'
        };
        var DEFAULT_QR_CLAIM_CODE_URL = 'https://cloud.appspace.com/register';
        var QR_CLAIM_CODE_PARAM = '?code=';

        function injectConfiguration(config) {
            if (!config) {
                return false;
            }

            var isInject = false;
            var forceInject = config.forceInject;
            var tryInject = function(collection, key, value) {
                var existingValue = coreConfigurationService.get(collection, key);
                if (existingValue && !forceInject) {
                    return;
                }

                if (value != existingValue) {
                    isInject = true;
                    coreConfigurationService.set(collection, key, value);
                }
            };

            tryInject('device', 'playerguid', decodeURIComponent(config.deviceGuid));

            if (config.serverUrl) {
                var url = decodeURIComponent(config.serverUrl);
                var checkurl = url.toLowerCase();
                if (checkurl.indexOf('http://') != 0 && checkurl.indexOf('https://') != 0) {
                    url = $location.protocol() + '://' + url;
                }

                tryInject('appspace', 'appspaceURL', url);
            }

            return isInject;
        }

        function retrieveCloudApiUrls() {
            return cloudApiUrls;
        }

        function resolveServerUrl(serverUrl) {
            var deferred = $q.defer();
            if (!serverUrl) {
                deferred.resolve('https://cloud.appspace.com');
                return deferred.promise;
            }

            var url;
            for (var key in cloudApiUrls) {
                if (serverUrl.toLowerCase().indexOf(key.toLowerCase()) > -1) {
                    url = serverUrl;
                    if (url.toLowerCase().indexOf('https://') < 0 && url.toLowerCase().indexOf('http://') < 0) {
                        url = 'https://' + url;
                    }

                    deferred.resolve(url);
                    return deferred.promise;
                }
            }

            if (serverUrl == 'localhost:3000') {
                deferred.resolve('http://localhost:3000');
                return deferred.promise;
            } else if (serverUrl.toLowerCase().indexOf('file://') == 0) {
                deferred.resolve('https://cloud.appspace.com');
                return deferred.promise;
            } else {
                // Check was a protocol supplied
                url = serverUrl.toLowerCase();
                if (url.indexOf('http://') == 0 || url.indexOf('https://') == 0) {
                    deferred.resolve(serverUrl);
                    return deferred.promise;
                }

                var onHttpsSuccess = function(response) {
                    if (response.data && response.data.Status === 1) {
                        deferred.resolve(protcol + serverUrl);
                    } else {
                        deferred.reject(protcol + serverUrl);
                    }
                };

                var onHttpSuccess = function(response) {
                    if (response.data && response.data.Status === 1) {
                        deferred.resolve(protcol + serverUrl);
                    } else {
                        deferred.reject(protcol + serverUrl);
                    }
                };

                var onHttpsFail = function(err) {
                    protcol = 'http://';
                    urlToCheck = 'http://' + serverUrl + '/api/v1/highavailability/nodes/ping';
                    $http.get(urlToCheck).then(onHttpSuccess, onHttpFail);
                };

                var onHttpFail = function(err) {
                    deferred.reject(err);
                };

                var protcol = 'https://';

                // check if protocol is http or https
                var urlToCheck = 'https://' + serverUrl + '/api/v1/highavailability/nodes/ping';
                $http.get(urlToCheck).then(onHttpsSuccess, onHttpsFail);
                return deferred.promise;
            }
        }

        function getAppspaceConfigurationPolicy() {
            var appspaceConfiguration = coreConfigurationService.getAppspaceConfiguration() || {};
            var policy = appspaceConfiguration[APPSPACE_CONFIGURATION_PROPERTY.REGISTRATIONPOLICY] || {};
            var registrationPolicy = {
                registration: {}
            };
            for (var key in policy) {
                if (!key || key.toLowerCase() !== 'registration') {
                    continue;
                }

                registrationPolicy.registration = policy[key];
            }

            if (!isPolicyValid(registrationPolicy.registration)) {
                return $q.reject('invalid policy');
            }

            registrationPolicy.registration.source = 'appspaceconfiguration';
            return $q.resolve(registrationPolicy);
        }

        function getNetworkPolicy() {
            var promise = $q.defer();

            var timeStamp = new Date().getTime();
            var url = coreFrameworkService.getPolicyUrl() || 'http://appspace-discovery/discovery/api/v1/policy.json';
            $http.get(url + '?' + timeStamp).then(
                function(response) {
                    var data = response.data;
                    // check response validity
                    if (data && data.registration) {
                        data.registration.source = 'network';
                        promise.resolve(data);
                    } else {
                        promise.reject('response in wrong format,' + JSON.stringify(data));
                    }
                },
                function(err) {
                    promise.reject(err);
                }
            );
            // promise.resolve();

            return promise.promise;
        }

        function getQueryParamPolicy() {
            var urlParameters = sessionService.getUrlParameters();
            var policyFromQueryParam = urlParameters.policy || '';
            if (!policyFromQueryParam) {
                return {
                    error: 'no policy in query param'
                };
            }

            policyFromQueryParam = decodePolicy(policyFromQueryParam);
            if (!policyFromQueryParam.registration) {
                return {
                    error: 'query param policy in wrong format'
                };
            }

            policyFromQueryParam.registration.source = 'query';
            return policyFromQueryParam;
        }

        function decodePolicy(policy) {
            try {
                return JSON.parse(atob(policy));
            } catch (e) {
                logService.error(LOG_KEY, 'Invalid encoded policy');
                return {};
            }
        }

        function getPolicy() {
            var promise = $q.defer();

            var resolvePolicy = function(policy, errorMessage, errorCallback) {
                if (policy && policy.registration) {
                    promise.resolve(policy);
                } else {
                    var message = errorMessage + ' :' + JSON.stringify(policy);
                    logService.debug(LOG_KEY, message);
                    errorCallback(message);
                }
            };

            var onAppspaceConfigurationPolicyResolved = function(policy) {
                resolvePolicy(policy, 'Appspace configuration policy retrieved not valid', promise.reject);
            };

            var onAppspaceConfigurationPolicyFailed = function() {
                var message = 'No query param, local, network, Appspace configuration policy found';
                logService.debug(LOG_KEY, message);
                promise.reject(message);
            };

            var onNetworkPolicyResolved = function(policy) {
                resolvePolicy(policy, 'Network policy retrieved not valid', promise.reject);
            };

            var onNetworkPolicyFailed = function() {
                getAppspaceConfigurationPolicy().then(onAppspaceConfigurationPolicyResolved, onAppspaceConfigurationPolicyFailed);
            };

            var onLocalPolicyResolved = function(policy) {
                resolvePolicy(policy, 'Local policy retrieved not valid', onLocalPolicyFailed);
            };

            var onLocalPolicyFailed = function() {
                getNetworkPolicy().then(onNetworkPolicyResolved, onNetworkPolicyFailed);
            };

            var queryParamPolicy = getQueryParamPolicy();
            if (queryParamPolicy && !queryParamPolicy.error) {
                resolvePolicy(queryParamPolicy, 'Query param policy retrieved not valid', promise.reject);
            } else {
                // check local policy first, followed by network policy and then Appspace configuration policy
                halPlatformService.getAppPolicy().then(onLocalPolicyResolved, onLocalPolicyFailed);
            }

            return promise.promise;
        }

        function isGuidValid(deviceguid) {
            return deviceguid &&
                isNaN(deviceguid) &&
                typeof (deviceguid) == 'string' &&
                // the format guid 8-4-4-4-12 for hex values
                deviceguid.match(/^[0-9A-F]{8}[-]?([0-9A-F]{4}[-]?){3}[0-9A-F]{12}$/i);
        }

        function processInjection(deviceguid, appspaceurl, policyBased) {
            var isInjected = false;
            var isInjectionProcessed = false;
            if (isGuidValid(deviceguid)) {// if we have injection data
                isInjectionProcessed = true;
                var config = {
                    deviceGuid: deviceguid,
                    forceInject: !policyBased,
                    serverUrl: appspaceurl || $window.location.origin
                };
                if (config && injectConfiguration(config)) {
                    logService.debug(LOG_KEY, 'Device GUID and/or Appspace URL injected. Clearing cached script.');
                    isInjected = true;
                    // if we inject device GUID or URL into storage, we need to clear cache first
                    deviceStateService.clearCachedScripts();
                } else {
                    logService.debug(LOG_KEY, 'Device GUID and/or Appspace URL not injected');
                }
            }

            return {
                isInjected: isInjected,
                isInjectionProcessed: isInjectionProcessed
            };
        }

        function retrievePolicyWithInjection() {
            var promise = $q.defer();
            logService.debug(LOG_KEY, 'Retrieve policy and check for injection');
            var onRetrievePolicySuccess = function(p) {
                logService.debug(LOG_KEY, 'Policy retrieved:' + JSON.stringify(p));
                var res = processInjection(
                    p.registration.deviceguid,
                    p.registration.appspace_url,
                    true);
                promise.resolve({
                    policy: p,
                    isInjected: res.isInjected,
                    isProcessed: res.isInjectionProcessed
                });
            };

            getPolicy().then(onRetrievePolicySuccess, promise.reject);
            return promise.promise;
        }

        function processQueryInjection() {
            var params = sessionService.getUrlParameters();
            logService.debug(LOG_KEY, 'Checking query-based injection:', params);
            processInjection(
                params.deviceguid,
                params.appspaceurl,
                false
            );
        }

        function isPolicyIdBased(policy) {
            return Boolean(policy && policy.policy_id && policy.registration_url);
        }

        function isPolicyBased(policy) {
            return Boolean(policy && policy.appspace_url && policy.api_key);
        }

        function isPolicyValid(policy) {
            return isPolicyIdBased(policy) || isPolicyBased(policy);
        }

        function saveRegistrationInfo(registrationInfo) {
            if (!registrationInfo) {
                return;
            }

            coreConfigurationService.set(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.ACCOUNT_ID, registrationInfo.accountId);
            coreConfigurationService.set(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.INSTANCE_ID, registrationInfo.instanceId);
            coreConfigurationService.set(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.DEVICE_ID, registrationInfo.deviceId);
            coreConfigurationService.set(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.APPSPACE_URL, registrationInfo.instanceUrl);
        }

        function postRegistrationCallback() {
            telemetryService.start();
            deviceMetricsService.start();
            cronService.start();
            softwareUpdateService.start();
            deviceTokenService.start();
            halPlatformService.postRegistrationCallback();
        }

        function isTaskQueueMarkerSupported() {
            return versionUtil.isVersionSupported(TASK_QUEUE_MARKER_PERSISTENCE_SUPPORTED_VERSIONS, LOG_KEY);
        }

        function saveDeviceState(state) {
            if (!state) {
                return;
            }

            var deviceTaskTimestamp = state.deviceTaskTimestamp;
            if (!dataUtil.isNumber(deviceTaskTimestamp) || deviceTaskTimestamp <= DEFAULT_DEVICE_TASK_TIMESTAMP) {
                return;
            }

            isTaskQueueMarkerSupported().then(function(isSupported) {
                if (!isSupported) {
                    logService.warn(LOG_KEY, 'Platform version does not meet the minimum device task marker persistence requirement');
                    return;
                }

                admService.saveQueueMarker(state.deviceTaskTimestamp, true);
            });
        }

        function cleanUpRegistrationInfo(resetNavigation, resetNavigationDelay) {
            coreConfigurationService.removeCollection(STORAGE_COLLECTION_KEY.ADM);
            coreConfigurationService.removeCollection(STORAGE_COLLECTION_KEY.SIGNS_REGISTRATION);
            coreConfigurationService.removeCollection(STORAGE_COLLECTION_KEY.PLAY);
            coreConfigurationService.removeConfiguration(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.CACHE_VERSION);
            coreConfigurationService.removeConfiguration(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.LAST_PASSCODE_SUCCESS_TIME);
            coreConfigurationService.removeConfiguration(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.API_KEY);
            coreConfigurationService.removeConfiguration(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.DEVICE_ID);
            coreConfigurationService.removeConfiguration(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.HAS_CHANNEL_ASSIGNED);
            coreConfigurationService.removeConfiguration(STORAGE_COLLECTION_KEY.APPSPACE_INTERACTIVE, STORAGE_KEY.THEME);
            var skippedKeys = [URL_PARAMETERS_KEY];
            if (!halPlatformService.isUrlPolicyModeEnforced()) {
                coreConfigurationService.removeRegistrationPolicyId();
                skippedKeys = [];
            }

            halPlatformService.reset();
            // When removing registration information, we want to persist it immediately for consistency.
            // persist would be done upon clear
            clearRegistrationInformation();
            // Clear everything but urlparameters in session storage if URL policy mode is enforced
            sessionService.clear(skippedKeys);
            // Reset passive registration mode flag
            setPassiveMode(sessionService.getUrlParameters());

            telemetryService.stop();
            deviceMetricsService.stop();
            cronService.stop();
            softwareUpdateService.stop();
            deviceTokenService.stop();
            metricService.clearPendingMetrics();
            playlistSchedulerService.clearRecordItem();
            admService.clear();
            deviceStateService.setCurrentState();
            contentFallbackService.reset();
            if (!resetNavigation) {
                return;
            }

            resetNavigationDelay = dataUtil.isNumber(resetNavigationDelay) ?
                resetNavigationDelay : DEFAULT_RESET_NAVIGATION_DELAY;
            $timeout(resetNavigation, resetNavigationDelay);
        }

        function isRegistrationInfoDifferent(registrationInfo) {
            var savedInfo = {
                accountId: coreConfigurationService.get(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.ACCOUNT_ID),
                instanceId: coreConfigurationService.get(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.ACCOUNT_ID),
                deviceId: coreConfigurationService.get(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.ACCOUNT_ID),
                instanceUrl: coreConfigurationService.get(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.ACCOUNT_ID),
            };
            var difference = {
                accountId: false,
                instanceId: false,
                deviceId: false,
                instanceUrl: false,
                isDifferent: false
            };

            for (var key in savedInfo) {
                var savedInformation = savedInfo[key];
                var information = registrationInfo[key];
                if (savedInformation === information) {
                    continue;
                }

                difference[key] = true;
                difference.isDifferent = true;
            }

            return difference;
        }

        function processDeviceName(policy, skipSaving) {
            policy = policy || {};
            policy.registration = policy.registration || {};
            var saveDeviceName = function() {
                if (!skipSaving) {
                    coreConfigurationService.set(STORAGE_COLLECTION_KEY.SIGNS_REGISTRATION, STORAGE_KEY.DEVICE_NAME, policy.registration.device_name);
                }
            };

            var setPolicyRegistrationDeviceName = function(deviceName) {
                policy.registration.device_name = deviceName;
                saveDeviceName();
            };

            if (policy.registration.device_name) {
                saveDeviceName();
                return $q.resolve(policy.registration.device_name);
            }

            var urlParameters = sessionService.getUrlParameters();
            var urlParamDeviceName = urlParameters.devicename;
            if (urlParamDeviceName) {
                setPolicyRegistrationDeviceName(urlParamDeviceName);
                return $q.resolve(urlParamDeviceName);
            }

            var deferred = $q.defer();
            var finalizeDeviceName = function(deviceName) {
                setPolicyRegistrationDeviceName(deviceName);
                deferred.resolve(deviceName);
            };

            var getFingerprint = function() {
                halPlatformService.getFingerprint().then(function(response) {
                    finalizeDeviceName(response.fingerprint);
                }, function() {
                    deferred.reject('Error getting device fingerprint.');
                });
            };

            var getMacAddress = function() {
                halPlatformService.getMacAddress().then(function(macAddress) {
                    if (!macAddress) {
                        getFingerprint();
                        return;
                    }

                    finalizeDeviceName(macAddress);
                }, getFingerprint);
            };

            halPlatformService.getSerialNumber().then(function(serialNumber) {
                if (!serialNumber) {
                    getMacAddress();
                    return;
                }

                finalizeDeviceName(serialNumber);
            }, getMacAddress);

            return deferred.promise;
        }

        function retrieveRegistrationFile() {
            var deferred = $q.defer();
            var registrationFilePath = halPlatformService.getRegistrationFilePath();
            if (!registrationFilePath) {
                return $q.reject('Registration file path not defined');
            }

            halNetworkService.getJSON(registrationFilePath).then(function(information) {
                if (!information ||
                    !dataUtil.isObject(information) ||
                    !information.deviceId ||
                    !information.appspaceUrl) {
                    deferred.reject('Invalid registration information');
                    return;
                }

                deferred.resolve(information);
            }, function(error) {
                deferred.reject('Failed to retrieve registration file: ' + JSON.stringify(error));
            });
            return deferred.promise;
        }

        function retrievePlatformRegistration() {
            return halPlatformService.getRegistrationInformation();
        }

        function restorePlatformRegistration() {
            if (deviceStateService.isRegistered()) {
                return $q.reject('Device already registered');
            }

            var promise = $q.defer();
            var onRegistrationInfoRetrieved = function(information) {
                if (!information && !dataUtil.isObject(information)) {
                    promise.reject('Invalid registration information');
                    return;
                }

                // restore info to local storage
                coreConfigurationService.set(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.DEVICE_ID, information.deviceId);
                coreConfigurationService.set(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.APPSPACE_URL, information.appspaceUrl);
                if (!information.tokens) {
                    logService.warn(LOG_KEY, 'Tokens not found in restored registration information');
                }

                deviceTokenService.save(information.tokens);
                halPlatformService.clearRegistrationInformation();
                promise.resolve();
            };

            var onFailedToRetrieveRegistrationFile = function(error) {
                logService.info(LOG_KEY, 'Unable to restore information from registration file: ' + error);
                retrievePlatformRegistration().then(onRegistrationInfoRetrieved, promise.reject);
            };

            retrieveRegistrationFile().then(onRegistrationInfoRetrieved, onFailedToRetrieveRegistrationFile);
            return promise.promise;
        }

        function persistRegistrationInfo() {
            halPlatformService.persistRegistrationInfo(getRegistrationInfo()).then(function() {
                logService.debug(LOG_KEY, 'Registration information persisted');
            }, function(error) {
                logService.debug(LOG_KEY, 'Failed to persist registration info: ', error);
            });
        }

        function clearRegistrationInformation() {
            halPlatformService.clearRegistrationInformation().then(function() {
                logService.debug(LOG_KEY, 'Registration information cleared');
            }, function(error) {
                logService.debug(LOG_KEY, 'Failed to clear registration info: ', error);
            });
        }

        function getRegistrationInfo() {
            var tokens = deviceTokenService.getConstellationTokens();
            return {
                tokens: tokens,
                deviceId: coreConfigurationService.get(STORAGE_COLLECTION_KEY.DEVICE, STORAGE_KEY.DEVICE_ID),
                appspaceUrl: coreConfigurationService.get(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.APPSPACE_URL)
            };
        }

        function getDeviceState() {
            return {
                deviceTaskTimestamp: admService.getQueueMarker() || DEFAULT_DEVICE_TASK_TIMESTAMP,
            };
        }

        function persistRegistrationInfoOnBoot() {
            if (halPlatformService.getFeatureSupport(FEATURE_CODE.REGISTRATION_INFO_PERSISTENCE_ON_BOOT_SUPPORTED) === false) {
                logService.debug(LOG_KEY, 'Device does not support persisting registration info on boot');
                return;
            }

            halPlatformService.persistRegistrationInfoOnBoot(getRegistrationInfo()).then(function() {
                logService.debug(LOG_KEY, 'Registration information persisted');
            }, function(error) {
                logService.debug(LOG_KEY, 'Failed to persist registration info: ', error);
            });
        }

        function isPassiveMode(urlParameters) {
            urlParameters = $angular.copy(urlParameters || {});
            var registrationMode = halPlatformService.getFeatureSupport(FEATURE_CODE.ENFORCED_REGISTRATION_MODE) || urlParameters.registrationmode || '';
            return registrationMode.toLowerCase() === SYSTEM_DATA.PASSIVE_REGISTRATION_MODE;
        }

        function setPassiveMode(urlParameters) {
            sessionService.set(STORAGE_KEY.PASSIVE_REGISTRATION, isPassiveMode(urlParameters));
        }

        function getQrClaimCodeUrl(registrationCode) {
            return !registrationCode ?
                '' :
                (coreFrameworkService.getQrClaimCodeUrl() || DEFAULT_QR_CLAIM_CODE_URL) + QR_CLAIM_CODE_PARAM + registrationCode;
        }

        return {
            // Public interface
            resolveServerUrl: resolveServerUrl,
            getNetworkPolicy: getNetworkPolicy,
            retrieveCloudApiUrls: retrieveCloudApiUrls,
            getPolicy: getPolicy,
            retrievePolicyWithInjection: retrievePolicyWithInjection,
            processQueryInjection: processQueryInjection,
            isPolicyIdBased: isPolicyIdBased,
            isPolicyValid: isPolicyValid,
            setPassiveMode: setPassiveMode,
            decodePolicy: decodePolicy,
            getUrlPolicy: getQueryParamPolicy,
            postRegistrationCallback: postRegistrationCallback,
            saveRegistrationInfo: saveRegistrationInfo,
            saveDeviceState: saveDeviceState,
            cleanUpRegistrationInfo: cleanUpRegistrationInfo,
            isRegistrationInfoDifferent: isRegistrationInfoDifferent,
            restorePlatformRegistration: restorePlatformRegistration,
            persistRegistrationInfo: persistRegistrationInfo,
            processDeviceName: processDeviceName,
            persistRegistrationInfoOnBoot: persistRegistrationInfoOnBoot,
            getRegistrationInfo: getRegistrationInfo,
            getDeviceState: getDeviceState,
            getQrClaimCodeUrl: getQrClaimCodeUrl,
        };
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    var app = angular.module('signsHTML5');
    app.controller('SilentRegCtrl', ["$scope", "$q", "$timeout", "$location", "logService", "coreConfigurationService", "deviceRegistrationService", "halPlatformService", "authenticationAPIService", "storageLocalStorageService", "coreFrameworkService", "deviceIdentityService", "deviceTokenService", "RegistrationUtil", "rendezvousService", "NetworkUtil", "postMessagingService", "deviceStateService", "STORAGE_COLLECTION_KEY", "STORAGE_KEY", function($scope, $q, $timeout, $location,
        logService, coreConfigurationService, deviceRegistrationService,
        halPlatformService, authenticationAPIService, storageLocalStorageService,
        coreFrameworkService, deviceIdentityService, deviceTokenService,
        RegistrationUtil, rendezvousService, NetworkUtil, postMessagingService,
        deviceStateService, STORAGE_COLLECTION_KEY, STORAGE_KEY) {
        $scope.registrationMessage = '';
        $scope.registrationProgress = 0;
        var policy = {};
        var registrationMessage;
        var REGISTRATION_COMPLETE_DELAY = 3000;

        $scope.getRegMessage = function() {
            return registrationMessage;
        };

        function init() {
            RegistrationUtil.getPolicy().then(onPolicy, function(err) {
                displayError('Error retrieving registration policy,', err);
            });
        }

        function onPolicy(p) {
            policy = p;
            if (!RegistrationUtil.isPolicyValid(policy.registration)) {
                displayError('Invalid policy');
                return;
            }

            switch ((policy.registration.source || '').toLowerCase()) {
                case 'local':
                    registrationMessage = 'Hold tight, we are registering this device with Appspace using your <a>local policy file</a>.';
                    break;
                case 'chrome':
                    registrationMessage = 'Hold tight, we are registering this device with Appspace using your <a>Google CDM policy</a>.';
                    break;
                case 'network':
                    registrationMessage = 'Hold tight, we are auto registering this device using your <a>network policy</a>.';
                    break;
                case 'discovery':
                    registrationMessage = 'Hold tight, we are registering this device with Appspace using your <a>registration code</a>.';
                    break;
                default:
                    registrationMessage = 'Hold tight, we are registering this device with Appspace.';
                    break;
            }

            policy.registration.isPolicyIdBased = RegistrationUtil.isPolicyIdBased(policy.registration);
            if (policy.registration.isPolicyIdBased) {
                registerByPolicyId();
                return;
            }

            registerByPolicy();
        }

        function registerByPolicyId() {
            processDeviceName().then(function() {
                startRegistration().then(function() {
                    var registrationInfo = deviceTokenService.getRegistrationInfo();
                    if (!registrationInfo) {
                        displayError('Failed to retrieve registration info');
                        return;
                    }

                    RegistrationUtil.saveRegistrationInfo(registrationInfo);
                    var policyId = (RegistrationUtil.getUrlPolicy().registration || {}).policy_id;
                    if (policyId) {
                        coreConfigurationService.setRegistrationPolicyId(policyId);
                    }

                    endRegistration();
                });
            }, function(error) {
                displayError('Failed to process device name: ', error);
            });
        }

        function registerByPolicy() {
            var rejectHandler = function(err) {
                displayError('Failed to reach Appspace at ' + policy.registration.appspace_url + ': ' + err);
            };

            RegistrationUtil.resolveServerUrl(policy.registration.appspace_url)
                .then(function(serverUrl) {
                    policy.registration.appspace_url = serverUrl;
                    NetworkUtil.waitForAppspaceOnline(serverUrl).then(function() {
                        logService.debug('appspace.signs.registration', 'Appspace reached. Starting silent registration');
                        processDeviceName().then(function() {
                            // Update the server URL to the one from the policy
                            coreConfigurationService.set(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.APPSPACE_URL, policy.registration.appspace_url);
                            logService.debug('appspace.signs.registration', 'Appspace URL: ' + policy.registration.appspace_url);
                            startRegistration().then(endRegistration);
                        }, function(error) {
                            displayError(error);
                        });
                    }, rejectHandler);
                }, rejectHandler);
        }

        function processDeviceName() {
            return RegistrationUtil.processDeviceName(policy);
        }

        function endRegistration(cleanUpCallback) {
            RegistrationUtil.postRegistrationCallback();
            runStartupScripts().then(function() {
                $timeout(function() {
                    displaySuccess('Registration successful.');
                    if (!cleanUpCallback) {
                        return;
                    }

                    cleanUpCallback();
                }, REGISTRATION_COMPLETE_DELAY);
            });
        }

        function startRegistration() {
            var appVersion = coreFrameworkService.getAppVersion() || '';
            var deviceModel = halPlatformService.getAppspaceDeviceType() || '';
            var deferred = $q.defer();
            var onSuccessRegister = function(res) {
                $scope.registrationProgress = 100;
                deviceStateService.clearCachedScripts();
                storageLocalStorageService.removeCollection('adm');
                policy.isPolicyBased = res.isPolicyBased;
                // if this is a policy based registration, save the app version directly
                if (policy.isPolicyBased && appVersion) {
                    storageLocalStorageService.set(STORAGE_COLLECTION_KEY.APPSPACE, 'cacheVersion', appVersion);
                }

                // flag to false, indicate automatic channel selection
                storageLocalStorageService.set('signs.registration', 'registrationmode', 'auto');
                deferred.resolve();
            };

            var onFailRegister = function(err) {
                deferred.reject(err);
                displayError(err);
            };

            var onProgressRegister = function(progress) {
                logService.debug('appspace.signs.registration', '(' + progress.step + '/' + progress.total + ') ' + progress.message + ' - ' + (progress.skip ? 'SKIPPED' : 'SUCCESS'));
                $scope.registrationProgress = parseInt((progress.step / progress.total) * 100, 10);
            };

            policy.registration.device_properties = [
                {key: 'Player.Model.Name', value: deviceModel},
                {key: 'Player.Software.Version', value: appVersion}
            ];

            deviceRegistrationService.registerByPolicy(policy.registration).then(
                onSuccessRegister,
                onFailRegister,
                onProgressRegister
            );
            return deferred.promise;
        }

        function runStartupScripts() {
            policy.startup = policy.startup || {};
            var script = policy.startup.scriptBRS;
            if (!script) {
                return $q.resolve();
            }

            logService.info('appspace.signs.registration', 'Executing Brightsign startup script:' + script);
            var executePromise = halPlatformService.executePlatformScript({type: 'file', args: script});
            if (!executePromise) {
                logService.warn('appspace.signs.registration', 'Driver doesn\'t support executePlatformScript');
                return $q.resolve();
            }

            var promise = $q.defer();
            executePromise.then(function() {
                logService.debug('appspace.signs.registration', 'Brightsign startup script executed');
                promise.resolve();
            }, function(err) {
                logService.debug('appspace.signs.registration', 'Brightsign startup script fail to execute');
                promise.resolve();
            });
            return promise.promise;
        }

        function displayError(message) {
            clearRegistrationInfo();
            logService.error('appspace.signs.registration', message);
            coreConfigurationService.set('signs.registration', 'message.error', message);
            $location.path('/tv/registrationfail').search('retry', 'false');
        }

        function displaySuccess(message) {
            postMessagingService.raiseSuccessfulRegistrationMessage();
            RegistrationUtil.persistRegistrationInfo();

            // stop rendezvous service, and notify rv that code is claimed.
            stopRendezvousService();
            deviceIdentityService.restart();

            // if we have channel id inside policy file, continue to channel assignment
            if (!policy.isPolicyBased &&
                policy.registration.default_channelid &&
                policy.registration.security_token) {
                continueToChannelSelection();
            } else {
                continueToAppInit();
            }
        }

        function stopRendezvousService() {
            if (rendezvousService.isStarted()) {
                rendezvousService.notify();
                rendezvousService.stop();
            }
        }

        function continueToChannelSelection() {
            $location.path('/tv/registrationchannel');
        }

        function clearRegistrationInfo() {
            // reset user info
            authenticationAPIService.resetUserInfo();
            // else, skip to app init
            halPlatformService.clearAppPolicy();
        }

        function continueToAppInit() {
            clearRegistrationInfo();
            $location.path('/tv/appinit');
        }

        init();
    }]);
})(window.angular);

(function($angular) {
    var angular = $angular;
    var app = angular.module('signsHTML5');
    app.controller('RegSuccessCtrl', ["$scope", "$location", "$timeout", "coreConfigurationService", "deviceStateService", "deviceIdentityService", "RegistrationUtil", function($scope, $location, $timeout,
        coreConfigurationService, deviceStateService, deviceIdentityService, RegistrationUtil) {
        function init() {
            // Handle view destroy
            $scope.$on('$destroy', function() {
                // clean up stuff
            });

            coreConfigurationService.removeCollection('signs.registration');
            deviceStateService.clearCachedScripts();
            RegistrationUtil.postRegistrationCallback();

            $timeout(function() {
                $location.path('/tv/appinit');
                deviceIdentityService.restart();
            }, 1000);
        }

        init();
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('signsHTML5');

    app.controller('UnregisterCtrl', ["$scope", "$location", "syncService", "coreFrameworkService", "deviceRegistrationService", "deviceStateService", "postMessagingService", "halPlatformService", "UrlUtil", "dataUtil", "RegistrationUtil", "singleAppService", "FEATURE_CODE", function($scope, $location,
        syncService, coreFrameworkService, deviceRegistrationService,
        deviceStateService, postMessagingService, halPlatformService,
        UrlUtil, dataUtil, RegistrationUtil, singleAppService, FEATURE_CODE) {
        function init() {
            if (halPlatformService.getFeatureSupport(FEATURE_CODE.UNREGISTER_SUPPORTED) === 'no') {
                $location.url('/tv/splash');
                return;
            }

            syncService.stopSync();
            var params = UrlUtil.retrieveParams();
            startUnregister(params && dataUtil.isValueTrue(params.clean));
        }

        function startUnregister(cleanOnly) {
            $scope.message = 'Start to unregister device from Appspace';
            deviceRegistrationService.unregister(cleanOnly).then(function(regConfig) {
                $scope.unregistrationProgress = 100;
                $scope.message = 'Device has been successfully removed from Appspace';
                finishUp();
            }, function(err) {
                $scope.unregistrationProgress = 100;
                $scope.message = err + '. Removing device registration information.';
                finishUp();
            }, function(progress) {
                $scope.unregistrationProgress = (progress.step / progress.total) * 100;
                $scope.message = progress.message;
            });
        }

        function resetNavigation() {
            $location.path('/tv/registrationlanguage').search({}); // clear any query string
        }

        function finishUp() {
            // reset play customization
            var playStyle = document.getElementById('playOverwriteStyle');
            if (playStyle) {
                playStyle.remove();
            }

            var overwriteStyleProp = document.getElementById('overwriteStyleProperties');
            if (overwriteStyleProp) {
                overwriteStyleProp.remove();
            }

            postMessagingService.raiseSuccessfullUnregistrationMessage();
            deviceStateService.resetContent().then(function() {
                RegistrationUtil.cleanUpRegistrationInfo(resetNavigation);
            });

            singleAppService.disableSingleAppMode().then($angular.noop, $angular.noop);

            halPlatformService.postUnregistrationCallback();
        }

        coreFrameworkService.isReady().then(init);
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('signsHTML5');

    app.directive('asLayoutEditor', ["WidgetEditor", "dataUtil", "EDITOR_SETTINGS", "MOUSE_CLICK", function(WidgetEditor, dataUtil, EDITOR_SETTINGS, MOUSE_CLICK) {
        return {
            restrict: 'E',
            scope: {
                onApiReady: '=',
                onWidgetPositionChanged: '=',
                onWidgetStartMove: '=',
                onWidgetStopMove: '=',
                onWidgetSelected: '=',
                onWidgetDoubleClicked: '=',
                getAllDisplayedWidgetsPosition: '=',
                onGridConfigured: '=',
                getGridSize: '='
            },
            template: '<div class="layout-editor"></div>',
            link: function(scope) {
                scope.$on('$destroy', function() {
                    scope.destroy();
                });
            },
            controller: ["$scope", "$element", "$window", function($scope, $element, $window) {
                var currentWidget = {};
                var snapElement = {
                    horizontal: [],
                    vertical: []
                };
                var snapGrid = {
                    horizontal: [],
                    vertical: []
                };
                var snapRange = EDITOR_SETTINGS.DEFAULT_SNAP_RANGE;
                var isWidgetEditorEnabled = false;
                var visualScale = EDITOR_SETTINGS.DEFAULT_VISUAL_SCALE;
                $scope.onApiReady({
                    initialize: initialize,
                    setSelectedWidget: setSelectedWidget,
                    updateSelectedWidgetPosition: updateSelectedWidgetPosition,
                    moveSelectedWidget: moveSelectedWidget,
                    scaleSelectedWidget: scaleSelectedWidget,
                    setGridConfiguration: setGridConfiguration,
                    enableWidgetEditor: enableWidgetEditor,
                    isWidgetEditorEnabled: checkIsWidgetEditorEnabled,
                    destroy: destroy,
                    setVisualScale: setVisualScale
                });

                var anchorSnapConfig = {
                    top: {
                        validAnchors: ['top', 'topright', 'topleft'],
                        snapMethod: processOriginAnchorMove,
                        length: 'height',
                        position: 'top'
                    },
                    bottom: {
                        validAnchors: ['bottom', 'bottomleft', 'bottomright'],
                        snapMethod: processEndAnchorMove,
                        length: 'height',
                        position: 'top',
                        coordinate: 'horizontal'
                    },
                    left: {
                        validAnchors: ['left', 'bottomleft', 'topleft'],
                        snapMethod: processOriginAnchorMove,
                        length: 'width',
                        position: 'left'
                    },
                    right: {
                        validAnchors: ['right', 'topright', 'bottomright'],
                        snapMethod: processEndAnchorMove,
                        length: 'width',
                        position: 'left',
                        coordinate: 'vertical'
                    }
                };

                var lockAnchorPositionConfig = {
                    topleft: {
                        left: true,
                        top: true
                    },
                    topright: {
                        top: true
                    },
                    bottomleft: {
                        left: true
                    }
                };

                function addToDictionary(dictionary, key, value) {
                    dataUtil.pushIfNotExist(dictionary[key], value);
                }

                function recreateGridDictionary(gridConfig) {
                    // reset the snap grid first
                    snapGrid = {
                        horizontal: [],
                        vertical: []
                    };
                    var gridSize = (gridConfig || {}).size;
                    if (!gridSize) {
                        snapRange = EDITOR_SETTINGS.DEFAULT_SNAP_RANGE;
                        return;
                    }
                    snapRange = Math.round(gridSize * 0.5);
                    var minNumber = $window.innerWidth;
                    var maxNumber = $window.innerHeight;
                    var minData = 'vertical';
                    var maxData = 'horizontal';
                    if ($window.innerWidth > $window.innerHeight) {
                        maxNumber = $window.innerWidth;
                        minNumber = $window.innerHeight;
                        minData = 'horizontal';
                        maxData = 'vertical';
                    }
                    var gridValue = 0;
                    for (; gridValue <= minNumber; gridValue += gridSize) {
                        addToDictionary(snapGrid, maxData, gridValue);
                        addToDictionary(snapGrid, minData, gridValue);
                    }
                    for (; gridValue <= maxNumber; gridValue += gridSize) {
                        addToDictionary(snapGrid, maxData, gridValue);
                    }
                    snapGrid.horizontal = dataUtil.sortNumericArray(snapGrid.horizontal);
                    snapGrid.vertical = dataUtil.sortNumericArray(snapGrid.vertical);
                }

                function recreateElementDictionary() {
                    if (!currentWidget.instance) {
                        return;
                    }
                    // to snap to corners
                    snapElement = {
                        horizontal: [0, $window.innerHeight],
                        vertical: [0, $window.innerWidth]
                    };
                    var widgets = $scope.getAllDisplayedWidgetsPosition();
                    for (var i = 0; i < widgets.length; i++) {
                        var widget = widgets[i];
                        // ignore the selected widget
                        if (currentWidget.instance.id == widget.instance.id) {
                            continue;
                        }
                        var position = widget.position;
                        addToDictionary(snapElement, 'vertical', position.x);
                        addToDictionary(snapElement, 'vertical', position.x + position.width);
                        addToDictionary(snapElement, 'horizontal', position.y);
                        addToDictionary(snapElement, 'horizontal', position.y + position.height);
                    }
                    snapElement.horizontal = dataUtil.sortNumericArray(snapElement.horizontal);
                    snapElement.vertical = dataUtil.sortNumericArray(snapElement.vertical);
                }

                function initialize() {
                    bindEvent('on');
                }

                function setGridConfiguration(config) {
                    if (!$scope.onGridConfigured ||
                        (!config &&
                            !config.show && !config.size && !config.color && !config.backgroundColor)) {
                        return;
                    }
                    $scope.onGridConfigured(config).then(recreateGridDictionary);
                }

                function bindEvent(eventMethod) {
                    var layoutEditorLayer = $($element.children()[0]);
                    layoutEditorLayer[eventMethod]('mousedown', onLayoutEditorClicked);
                    layoutEditorLayer[eventMethod]('dblclick', onLayoutEditorDoubleClicked);
                }

                function enableWidgetEditor(enable) {
                    isWidgetEditorEnabled = enable;
                    if (isWidgetEditorEnabled) {
                        return;
                    }
                    // if widget editor is disabled, clear current widget editor if any
                    clearCurrentWidgetInstance();
                }

                function checkIsWidgetEditorEnabled() {
                    return isWidgetEditorEnabled;
                }

                $scope.destroy = destroy;

                function destroy() {
                    bindEvent('off');
                    clearCurrentWidgetInstance();
                    snapGrid = undefined;
                    snapRange = undefined;
                    snapElement = undefined;
                }

                function onLayoutEditorClicked(event) {
                    if (!isWidgetEditorEnabled || event.which != MOUSE_CLICK.LEFT) {
                        return;
                    }
                    setSelectedWidget(getWidgetAtPointer(event.clientX, event.clientY));
                    onWidgetMouseDown(event);
                }

                function getWidgetAtPointer(mouseX, mouseY) {
                    var widgets = $scope.getAllDisplayedWidgetsPosition();
                    for (var i = 0; i < widgets.length; i++) {
                        var widget = widgets[i];
                        if (mouseX < widget.position.x ||
                            mouseX > widget.position.x + widget.position.width ||
                            mouseY < widget.position.y ||
                            mouseY > widget.position.y + widget.position.height) {
                            continue;
                        }
                        return widget.instance;
                    }
                }

                function onLayoutEditorDoubleClicked() {
                    if (!currentWidget.instance || !currentWidget.instance.id) {
                        return;
                    }
                    $scope.onWidgetDoubleClicked({id: currentWidget.instance.id});
                }

                function setSelectedWidget(widgetInstance) {
                    clearCurrentWidgetInstance();
                    if (!widgetInstance) {
                        // clear editor
                        return false;
                    }
                    currentWidget.instance = widgetInstance;
                    createEditorLayer(currentWidget.instance.dom);
                    $scope.onWidgetSelected({id: widgetInstance.id});
                    return true;
                }

                function updateSelectedWidgetPosition(widgetInstanceId, newPosition) {
                    if (!currentWidget.instance || widgetInstanceId != currentWidget.instance.id) {
                        return;
                    }
                    var currentPosition = getDomPosition(currentWidget.editorDom);
                    if (currentPosition.left === newPosition.left &&
                        currentPosition.top === newPosition.top &&
                        currentPosition.width === newPosition.width &&
                        currentPosition.height === newPosition.height) {
                        return;
                    }
                    currentWidget.editorDom.css(newPosition);
                }

                function moveSelectedWidget(direction, value) {
                    if (!currentWidget.instance) {
                        return false;
                    }
                    currentWidget.api.move(direction, value);
                    return true;
                }

                function scaleSelectedWidget(option) {
                    if (!currentWidget.instance) {
                        return false;
                    }
                    currentWidget.api.scale(option);
                    return true;
                }

                function onWidgetMouseDown(event) {
                    if (!currentWidget.instance) {
                        return;
                    }
                    currentWidget.api.onWidgetMouseDown(event);
                }

                function getDomPosition(dom) {
                    return {
                        width: dom.outerWidth(),
                        height: dom.outerHeight(),
                        left: dom.css('left'),
                        top: dom.css('top')
                    };
                }

                function getLayoutEditorDom() {
                    return $element.find('.layout-editor');
                }

                function onLayoutEditorItemClicked(event) {
                    if (event.which != MOUSE_CLICK.LEFT) {
                        return;
                    }
                    // check when we click, is there any widget on top of us
                    var widgetInstance = getWidgetAtPointer(event.clientX, event.clientY);
                    if (!widgetInstance || !currentWidget.instance || widgetInstance.id == currentWidget.instance.id) {
                        return;
                    }
                    event.stopPropagation();
                    setSelectedWidget(widgetInstance);
                    onWidgetMouseDown(event);
                }

                function createEditorLayer(dom) {
                    currentWidget.editorDom = $('<div class="layout-editor-item"></div>');
                    currentWidget.editorDom.css(getDomPosition(dom));
                    currentWidget.editorDom.css({border: getBorderStyling()});
                    currentWidget.editorDom.on('mousedown', onLayoutEditorItemClicked);
                    getLayoutEditorDom().append(currentWidget.editorDom);
                    currentWidget.api = new WidgetEditor(
                        currentWidget.editorDom,
                        currentWidget.instance.id,
                        onPositionChanged,
                        onWidgetStartMove,
                        onWidgetStopMove,
                        onWidgetMove,
                        visualScale);
                    currentWidget.api.init();
                    currentWidget.api.select();
                }

                function onPositionChanged(event) {
                    $scope.onWidgetPositionChanged(event);
                }

                function onWidgetStartMove(event) {
                    recreateElementDictionary();
                    dimWidget();
                    $scope.onWidgetStartMove(event);
                }

                function onWidgetStopMove() {
                    unDimWidget();
                    $scope.onWidgetStopMove();
                }

                function getSnap(coordinates, positionToCheck) {
                    for (var i = 0; i < coordinates.length; i++) {
                        var coordinate = coordinates[i];
                        if ((positionToCheck > coordinate && positionToCheck <= coordinate + snapRange) ||
                            (positionToCheck < coordinate && positionToCheck >= coordinate - snapRange)) {
                            return coordinate;
                        }
                    }
                }

                function getSnapPositionFromDictionary(currentPosition, currentLength, dictionaryType) {
                    var res = getSnapPosition(currentPosition, currentLength, snapGrid[dictionaryType]);
                    if (!dataUtil.isNullOrUndefined(res)) {
                        return res;
                    }
                    return getSnapPosition(currentPosition, currentLength, snapElement[dictionaryType]);
                }

                function getSnapPosition(currentPosition, currentLength, coordinates) {
                    // snap left/top
                    var snapOrigin = getSnap(coordinates, currentPosition);
                    if (!dataUtil.isNullOrUndefined(snapOrigin)) {
                        return snapOrigin;
                    }
                    // snap opposite (right/bottom)
                    var snapOpposite = getSnap(coordinates, currentLength + currentPosition);
                    if (dataUtil.isNullOrUndefined(snapOpposite)) {
                        return;
                    }
                    return snapOpposite - currentLength;
                }

                function processOriginAnchorMove(snap, newPosition, positionType, lengthType, isAnchorValid) {
                    // if we're anchoring using the top/left anchors, just add the delta of top/left to the existing height/width
                    if (dataUtil.isNullOrUndefined(snap[positionType]) || !isAnchorValid) {
                        return snap;
                    }
                    snap[lengthType] = newPosition[positionType] - snap[positionType] + newPosition[lengthType];
                    return snap;
                }

                function processEndAnchorMove(snap, newPosition, positionType, lengthType, isAnchorValid, gridDictType) {
                    if (!isAnchorValid) {
                        return snap;
                    }
                    // if we're anchoring using the bottom/right anchors, ensure that if height/width is snapped, top/left will remain the same
                    var snapLength = getSnapLength(gridDictType, newPosition[lengthType] + newPosition[positionType]);
                    if (dataUtil.isNullOrUndefined(snapLength)) {
                        return snap;
                    }
                    snap[lengthType] = snapLength - newPosition[positionType];
                    // prevent the top/left snap position based on bottom/right
                    snap[positionType] = undefined;
                    return snap;
                }

                function getSnapLength(gridDictType, length) {
                    var res = getSnap(snapGrid[gridDictType], length);
                    if (!dataUtil.isNullOrUndefined(res)) {
                        return res;
                    }
                    return getSnap(snapElement[gridDictType], length);
                }

                function getSnapValue(newPosition, event) {
                    // don't snap when the mouse cursor goes over the widget's min boundaries
                    if (newPosition.height <= 0 || newPosition.width <= 0) {
                        return;
                    }
                    var res = {
                        left: getSnapPositionFromDictionary(newPosition.left, newPosition.width, 'vertical'),
                        top: getSnapPositionFromDictionary(newPosition.top, newPosition.height, 'horizontal')
                    };
                    // just drag/drop
                    if (!event || !event.anchor) {
                        return res;
                    }
                    // process anchor snapping
                    for (var key in anchorSnapConfig) {
                        var anchorSnap = anchorSnapConfig[key];
                        res = anchorSnap.snapMethod(res, newPosition, anchorSnap.position, anchorSnap.length,
                            anchorSnap.validAnchors.indexOf(event.anchor) > -1, anchorSnap.coordinate);
                        // prevent snapping for opposite anchor movement
                        if (event.anchor.indexOf(key) == -1) {
                            res[key] = undefined;
                        }
                    }
                    if (dataUtil.isNullOrUndefined(event.aspectRatio)) {
                        return res;
                    }
                    // aspect ratio is locked
                    var lengthType = '';
                    var positionType = '';
                    // snapping to width takes precedence over snapping to height
                    if (!dataUtil.isNullOrUndefined(res.width)) {
                        lengthType = 'height';
                        positionType = 'top';
                    } else if (!dataUtil.isNullOrUndefined(res.height)) {
                        lengthType = 'width';
                        positionType = 'left';
                    }
                    if (!lengthType && !positionType) {
                        return res;
                    }
                    return setAspectRatio(event.anchor, event.aspectRatio, lengthType, positionType, res, newPosition);
                }

                function setAspectRatio(anchor, aspectRatio, lengthType, positionType, placement, newPlacement) {
                    placement[lengthType] = getRelativeLength(lengthType, placement, aspectRatio);
                    var lockAnchorPosition = lockAnchorPositionConfig[anchor];
                    if (!lockAnchorPosition || !lockAnchorPosition[positionType]) {
                        return placement;
                    }
                    placement[positionType] = newPlacement[positionType] - (placement[lengthType] - newPlacement[lengthType]);
                    return placement;
                }

                function getRelativeLength(lengthType, placement, aspectRatio) {
                    var length = lengthType == 'height' ? placement.width / aspectRatio : placement.height * aspectRatio;
                    return parseInt(length, 10);
                }

                function isSnapValid(snap) {
                    return snap &&
                        (!dataUtil.isNullOrUndefined(snap.left) ||
                            !dataUtil.isNullOrUndefined(snap.top) ||
                            !dataUtil.isNullOrUndefined(snap.width) ||
                            !dataUtil.isNullOrUndefined(snap.height));
                }

                function processSnap(newPosition, event) {
                    currentWidget.api.disableSnap();
                    if (event.type === EDITOR_SETTINGS.MOVE_TYPE_KEYDOWN || event.type === EDITOR_SETTINGS.RESIZE_TYPE_KEYDOWN ) {
                        return newPosition;
                    }
                    var snap = getSnapValue(newPosition, event);
                    if (!isSnapValid(snap)) {
                        return newPosition;
                    }
                    currentWidget.api.enableSnap();
                    newPosition.left = dataUtil.getValidValue(snap.left, newPosition.left);
                    newPosition.top = dataUtil.getValidValue(snap.top, newPosition.top);
                    newPosition.width = dataUtil.getValidValue(snap.width, newPosition.width);
                    newPosition.height = dataUtil.getValidValue(snap.height, newPosition.height);
                    currentWidget.editorDom.css(newPosition);
                    return newPosition;
                }

                function onWidgetMove(position, event) {
                    event = event || {};
                    var newPosition = dataUtil.toCssPlacement(position);
                    currentWidget.instance.dom.css(processSnap(newPosition, event));
                }

                function dimWidget() {
                    if (!dataUtil.isNullOrUndefined(currentWidget.opacity)) {
                        return;
                    }
                    currentWidget.opacity = getOpacity(currentWidget.instance.dom);
                    setOpacity(currentWidget.instance.dom, currentWidget.opacity * 0.8);
                }

                function unDimWidget() {
                    if (dataUtil.isNullOrUndefined(currentWidget.opacity)) {
                        return;
                    }
                    setOpacity(currentWidget.instance.dom, currentWidget.opacity);
                    currentWidget.opacity = undefined;
                }

                function setOpacity(dom, opacity) {
                    dom.css({opacity: opacity});
                }

                function getOpacity(dom) {
                    return dom.css('opacity');
                }

                function clearCurrentWidgetInstance() {
                    unDimWidget();
                    if (currentWidget.api) {
                        currentWidget.api.dispose();
                        currentWidget.api = undefined;
                        delete currentWidget.api;
                    }
                    if (currentWidget.editorDom) {
                        currentWidget.editorDom.off('mousedown', onLayoutEditorItemClicked);
                        currentWidget.editorDom.remove();
                        currentWidget.editorDom = undefined;
                        delete currentWidget.editorDom;
                    }
                    if (currentWidget.instance) {
                        currentWidget.instance = undefined;
                        delete currentWidget.instance;
                    }
                    snapElement = {
                        horizontal: [],
                        vertical: []
                    };
                }

                function getBorderStyling() {
                    return 'calc(' + EDITOR_SETTINGS.BORDER_WIDTH + ' / ' + visualScale + ') ' + EDITOR_SETTINGS.BORDER_STYLING;
                }

                function setVisualScale(scale) {
                    visualScale = scale || EDITOR_SETTINGS.DEFAULT_VISUAL_SCALE;
                    if (currentWidget.editorDom) {
                        currentWidget.editorDom.css({
                            border: getBorderStyling()
                        });
                    }
                    if (currentWidget.api) {
                        currentWidget.api.setVisualScale(visualScale);
                    }
                }
            }]
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('signsHTML5');

    app.controller('AppDisplayController',
        ["$scope", "$window", "$q", "$location", "$timeout", "logService", "halPlatformService", "coreFrameworkService", "signsWidgetService", "scriptingService", "syncService", "ScheduablePlaylist", "coreConfigurationService", "deviceIdentityService", "metricService", "halRemoteService", "REMOTEVCODE", "REMOTEEVENTTYPE", "MacroCommands", "campaignService", "$interval", "admService", "alertsService", "sessionService", "jobSchedulerService", "deviceHealthService", "displayUtil", "NetworkUtil", "FontUtil", "remoteServiceManager", "SYNC_EVENT", "postMessagingService", "POST_MESSAGES", "POST_MESSAGES_ERROR", "dataUtil", "LAYOUT_EVENT", "embedProfileService", "layoutUtil", "ROUTE", "INVALID_PAGE_ERROR", "webPublishingService", "FEATURE_CODE", "SOCIAL_BAR_EVENT", "socialService", "deviceStateService", "PLAYER_PROPERTY_KEYS", "contentStateService", "CONTENT_STATE_EVENT", function($scope, $window, $q, $location, $timeout,
            logService, halPlatformService, coreFrameworkService, signsWidgetService,
            scriptingService, syncService, ScheduablePlaylist, coreConfigurationService,
            deviceIdentityService,
            metricService, halRemoteService, REMOTEVCODE, REMOTEEVENTTYPE,
            MacroCommands, campaignService, $interval,
            admService, alertsService, sessionService,
            jobSchedulerService, deviceHealthService, displayUtil, NetworkUtil,
            FontUtil, remoteServiceManager, SYNC_EVENT, postMessagingService, POST_MESSAGES,
            POST_MESSAGES_ERROR, dataUtil, LAYOUT_EVENT, embedProfileService, layoutUtil,
            ROUTE, INVALID_PAGE_ERROR, webPublishingService, FEATURE_CODE,
            SOCIAL_BAR_EVENT, socialService, deviceStateService, PLAYER_PROPERTY_KEYS, contentStateService, CONTENT_STATE_EVENT
        ) {
            var layoutPlaylist;
            var currentApp = null;
            var nextLayoutTimerPromise;
            var expectedLayoutEnd;
            var remainingLayoutDuration;
            var layoutLoaded = false;
            var layoutLoading = false;
            var currentLayoutPlaylistItem = null;
            var currentState;
            var pendingLayout;
            var LAYOUT_DURATION = 30;
            var DEFAULT_PLAYLIST_LAYOUT_ID = 1;
            var DEFAULT_PLAYLIST_DURATION = 2147483;
            var currentLayoutName = '';
            var RESIZE_EVENT = {
                RESIZE: 'resize',
                ORIENTATION_CHANGE: 'orientationchange',
            };
            var currentScreenOrientation = {};

            var validLayoutTimer = null;
            var isRemoteDisable = false;
            var _useSynchronizedScheduling = false;
            var layoutHistories = [];
            var isEditor = halPlatformService.getFeatureSupport('playback.mode.editor');
            var resizeOnReload = halPlatformService.getFeatureSupport('signs.behaviour.reloadonresize');
            var isDestroyed = false;
            // References the layout controller api. Its initialised with the correct reference once the layout controller is loaded
            $scope.layoutControllerApi = {};
            var resizeTimer;
            var logKey = 'appspace.signs.app';
            var currentTileIndex;
            var currentTileMatrix;
            var tileNotSet = 'tilenotset';
            var playOnToDayConfig = {
                playOnAllDays: -1,
                playOnSun: 0,
                playOnMon: 1,
                playOnTue: 2,
                playOnWed: 3,
                playOnThu: 4,
                playOnFri: 5,
                playOnSat: 6
            };
            var placementKeys = ['x', 'y', 'width', 'height'];
            var noChannelPublishedApplicationKey = 'IsNoChannelPublishedLayoutApplication';
            var api = {
                executeAction: executeAction
            };

            var actionsConfiguration = {
                nextlayout: nextLayout,
                previouslayout: previousLayout,
                back: back,
                loadlayout: handleLoadLayoutAction,
                loadlayoutbyname: handleLoadLayoutByNameAction,
                updatetextfromquery: updateTextFromQueryHandler,
                updatetickerfromquery: updateTickerFromQuery,
                loadplaylistfrominfomenuitem: loadPlaylistFromInfomenuItem,
                hidecomponent: handleHideComponentAction,
                showcomponent: handleShowComponentAction,
                pauselayout: pauseLayout,
                resumelayout: resumeLayout
            };

            var editorCommandConfig = {};
            editorCommandConfig[POST_MESSAGES.ENABLE_WIDGET_EDITOR] = {
                callback: function(data) {
                    $scope.layoutControllerApi.enableWidgetEditor(data === true);
                },
                validOnEditorDisabled: true
            };
            editorCommandConfig[POST_MESSAGES.SELECT_WIDGET] = {
                callback: function(data) {
                    if (dataUtil.isNullOrUndefined(data.id)) {
                        return;
                    }

                    $scope.layoutControllerApi.selectWidget(data.id);
                }
            };
            editorCommandConfig[POST_MESSAGES.POSITION_WIDGET] = {
                callback: function(data) {
                    if (dataUtil.isNullOrUndefined(data.id)) {
                        return;
                    }

                    var widget = getWidgetPlacement(data.id, currentState.layout);
                    if (!isPlacementValid(data.placement) || !widget) {
                        return;
                    }

                    var placement = dataUtil.toAppPlacement(data.placement);
                    var placementUpdated = isPlacementUpdated(placement, widget);
                    var zIndexUpdated = isZIndexUpdated(placement.zIndex, widget);
                    // to make sure both functions are invoked and check if either has been updated
                    if (!placementUpdated && !zIndexUpdated) {
                        return;
                    }

                    raisePostMessage(POST_MESSAGES.WIDGET_POSITION_CHANGED, {
                        widgetId: data.id,
                        placement: dataUtil.toCssPlacement(placement)
                    });
                    nextLayout();
                }
            };
            editorCommandConfig[POST_MESSAGES.MOVE_WIDGET] = {
                callback: function(data) {
                    if (!data.direction || $scope.layoutControllerApi.moveSelectedWidget(data.direction.toLowerCase(), data.value)) {
                        return;
                    }

                    raisePostMessage(POST_MESSAGES.NO_WIDGETS_SELECTED);
                }
            };
            editorCommandConfig[POST_MESSAGES.SET_GRID_CONFIGURATION] = {
                callback: function(data) {
                    if (!data) {
                        return false;
                    }

                    $scope.layoutControllerApi.setGridConfiguration(data);
                }
            };
            editorCommandConfig[POST_MESSAGES.SCALE_WIDGET] = {
                callback: function(data) {
                    if (!data.option || $scope.layoutControllerApi.scaleSelectedWidget(data.option.toLowerCase())) {
                        return;
                    }

                    raisePostMessage(POST_MESSAGES.NO_WIDGETS_SELECTED);
                }
            };
            editorCommandConfig[POST_MESSAGES.SET_WIDGET_OPACITY] = {
                callback: function(data) {
                    if (dataUtil.isNullOrUndefined(data.id) || dataUtil.isNullOrUndefined(data.opacity)) {
                        return;
                    }

                    var elementPlacementObject = getWidgetPlacement(data.id, currentState.layout);
                    if (!elementPlacementObject) {
                        return;
                    }

                    elementPlacementObject.widget.opacity = data.opacity;
                    nextLayout();
                }
            };
            editorCommandConfig[POST_MESSAGES.SET_EDITOR_SCALE] = {
                callback: function(data) {
                    $scope.layoutControllerApi.setEditorScale(data.scale);
                }
            };

            function handleShowComponentAction(action) {
                showComponent(parseInt(action.componentID, 10), true);
            }

            function handleHideComponentAction(action) {
                var inverse = action.inverse == 'true';
                var show = action.hide != 'true';
                showComponent(parseInt(action.componentID, 10), inverse ? !show : show);
            }

            function handleLoadLayoutByNameAction(action) {
                loadLayoutByName(action.layoutName, getDurationFromAction(action));
            }

            function handleLoadLayoutAction(action) {
                loadLayoutById(parseInt(action.layoutID, 10), getDurationFromAction(action));
            }

            function redirectToInvalidErrorPage(errorMessage, error) {
                logService.debug(logKey, errorMessage);
                var path = ROUTE.INVALID_INJECTION;
                if (error) {
                    path += '/' + error;
                }

                $location.path(path);
            }

            function processPreviewMode() {
                // Redirect to invalid error page if web publishing is restricted
                if (webPublishingService.isRestricted()) {
                    redirectToInvalidErrorPage('Web publishing is restricted', INVALID_PAGE_ERROR.WEB_PUBLISHING);
                    return;
                }

                var params = sessionService.getUrlParameters() || {};
                if (params.appspaceurl) {
                    coreConfigurationService.set('appspace', 'appspaceURL', params.appspaceurl);
                }

                // don't check the id injection if we're running in editor mode or not running in preview mode or injections required for the preview mode are not there
                if (!NetworkUtil.isIdInjectionValidationRequired()) {
                    syncService.startSync();
                    return;
                }

                // running signs in preview mode, disable unnecessary service
                NetworkUtil.validateIdInjection(params).then(function() {
                    logService.debug(logKey, 'Running in preview mode: ', params);
                    deviceStateService.clearCachedScripts();
                    coreConfigurationService.set('device', 'mode', 'preview');
                    isRemoteDisable = true;
                    // stop services
                    $timeout(function() {
                        admService.stop();
                        alertsService.stop();
                        jobSchedulerService.stop();
                        deviceHealthService.stop();
                        syncService.disableCache();
                        syncService.startSync();
                        remoteServiceManager.stop();
                    }, 100);
                    if (!params.appid) {
                        return;
                    }

                    coreConfigurationService.set('device', 'appid', params.appid);
                }, function(err) {
                    redirectToInvalidErrorPage(err.errormessage);
                });
            }

            function init() {
                if (isDestroyed) {
                    logService.warn(logKey, 'AppDisplayController has been disposed during initialization');
                    return;
                }

                processPreviewMode();
                layoutPlaylist = new ScheduablePlaylist(false, true, false);
                subscribeToSyncService();
                onSyncEvent({event: SYNC_EVENT.APP_UPDATED});
                logService.debug(logKey, 'AppDisplayController initialised');

                // Register for remote input
                halRemoteService.registerEvents(onRemoteEvent);

                scriptingService.registerTarget('application', api);

                // Start valid layout timer check
                validLayoutTimer = $interval(checkLayoutValid, 1000);

                currentScreenOrientation = displayUtil.getScreenOrientation();
                $window.addEventListener(RESIZE_EVENT.RESIZE, function() {
                    onResize(RESIZE_EVENT.RESIZE);
                });
                $window.addEventListener(RESIZE_EVENT.ORIENTATION_CHANGE, function() {
                    onResize(RESIZE_EVENT.ORIENTATION_CHANGE);
                });
                postMessagingService.raiseCrestronLoadedMessage(POST_MESSAGES_ERROR.LOADED_SINGLE_CHANNEL);
                postMessagingService.subscribe(onReceivedPostMessage);
                postMessagingService.start();
                socialService.subscribe(onSocialBarEvent);
                processOperationHours();

                var profile = embedProfileService.getProfile();
                if (!profile) {
                    return;
                }

                raisePostMessage(POST_MESSAGES.PROFILE_READY, {profile: profile});
            }

            $scope.$on('$destroy', function() {
                isDestroyed = true;
                // clean up stuff
                unsubscribeToLayoutEvents();
                $scope.layoutControllerApi.unload();
                scriptingService.unregisterTarget('application', api);

                // Kill valid layout timer
                $interval.cancel(validLayoutTimer);

                // Cancel previous layout timer
                cancelNextLayoutTimerPromise();
                unsubscribeToSyncService();
                logService.debug(logKey, 'Display controller destroyed');
                halRemoteService.unRegisterEvents(onRemoteEvent);
                $window.removeEventListener(RESIZE_EVENT.RESIZE, onResize);
                $window.removeEventListener(RESIZE_EVENT.ORIENTATION_CHANGE, onResize);

                postMessagingService.unsubscribe(onReceivedPostMessage);
                socialService.unsubscribe(onSocialBarEvent);
            });

            function subscribeToSyncService() {
                syncService.subscribe(onSyncEvent);
            }

            function unsubscribeToSyncService() {
                syncService.unsubscribe(onSyncEvent);
            }

            function getDurationFromAction(action) {
                // if action.duration is not there, set it to 0
                var duration = action.duration * 1000;
                return isNaN(duration) ? 0 : duration;
            }

            function executeAction(action) {
                if (!action || !action.name) {
                    return false;
                }

                var actionMethod = actionsConfiguration[action.name.toLowerCase()];
                if (!actionMethod) {
                    return false;
                }

                actionMethod(action);
                return true;
            }

            function pauseLayout() {
                // if we don't have expected layout, don't do anything
                if (!expectedLayoutEnd) {
                    return;
                }

                remainingLayoutDuration = expectedLayoutEnd.getTime() - new Date().getTime();
                // if layout ended, don't do anything
                if (remainingLayoutDuration <= 0) {
                    remainingLayoutDuration = undefined;
                    return;
                }

                logService.debug(logKey, 'Pause layout with remaining ' + remainingLayoutDuration + 'ms');
                cancelNextLayoutTimerPromise();
            }

            function resumeLayout() {
                // if we don't have remaining layout duration, don't do anything
                if (!remainingLayoutDuration) {
                    return;
                }

                logService.debug(logKey, 'Resume layout with remaining ' + remainingLayoutDuration + 'ms');
                createNextLayoutTimeoutPromise(remainingLayoutDuration);
            }

            function showComponent(widgetId, show) {
                $scope.layoutControllerApi.showWidget(widgetId, show);
            }

            function updateTextFromQueryHandler(action) {
                // sample data:  <Action Name="updatetextfromquery" TargetName="Application" PlainTextId="1223" TableListingId="1222" Query="select * from [sheet 1]" RowOffset="4" ColumnOffset="3" DefaultValue="" Append="false"/>
                var actionName = (action.append == 'true') ? 'appendtext' : 'settext';
                var mc = new MacroCommands();
                mc.updateTextFromQuery(
                    actionName,
                    action.plainTextId,
                    action.tableListingId,
                    action.query,
                    parseInt(action.rowOffset, 10),
                    parseInt(action.columnOffset),
                    action.defaultValue);
            }

            function updateTickerFromQuery(action) {
                // sample data: <Action Name="UpdateTickerFromQuery" TargetName="Application" TickerId="1238" TableListingId="1222" Query="select * from [sheet 1]" RowStartOffset="1" RowEndOffset="4" ColumnOffset="3" BreakOnBlank="true"/>
                var breakonblank = (action.breakOnBlank == 'true') ? true : false;
                var append = (action.append == 'false') ? false : true;// default value true
                var mc = new MacroCommands();
                mc.updateTickerFromQuery(
                    action.tickerId,
                    action.tableListingId,
                    action.query,
                    parseInt(action.rowStartOffset, 10),
                    parseInt(action.rowEndOffset, 10),
                    parseInt(action.columnOffset, 10),
                    breakonblank,
                    append);
            }

            function loadPlaylistFromInfomenuItem(action) {
                // sample data: <Action Name="loadplaylistfrominfomenuitem" TargetName="Application" MediaZoneId="1238" InfoMenuId="1246" InfoMenuItemId="40" AssetType="Thumbnail"/>
                var mc = new MacroCommands();
                mc.loadPlaylistFromInfomenuItem(
                    parseInt(action.infoMenuId, 10),
                    parseInt(action.infoMenuItemId, 10),
                    parseInt(action.mediaZoneId, 10),
                    action.assetType);
            }

            function loadLayoutByName(name, duration) {
                if (!currentApp || !name) {
                    return;
                }

                name = name.toLowerCase();
                for (var i = 0; i < currentApp.layouts.length; i++) {
                    var layout = currentApp.layouts[i];
                    if (layout.name.toLowerCase() == name) {
                        loadLayoutById(layout.layoutId, duration);
                        return true;
                    }
                }
            }

            function onFirstLayoutLoaded() {
                layoutLoaded = true;
                disableLoading();
                logService.info(logKey, 'Application loaded: ' + currentApp.name + ' (id: ' + currentApp.applicationId + ')');
                contentStateService.updateContentState({
                    state: CONTENT_STATE_EVENT.CHANNEL_LOADED,
                });
            }

            function onApplicationUpdated() {
                if (shouldResetApp()) {
                    resetApp();
                }

                updateCampaign();

                // Update the list of scheduling zones.
                var latestApp = null;

                function finalizeAppScript(script) {
                    socialService.updatePostSummaryBySchedulingScript(script);
                    processApp(latestApp);
                }

                function onSchedulingDatasourceLoaded(datasource) {
                    var datasourceScript = datasource.result;
                    latestApp.mediazoneSchedules = datasourceScript;
                    finalizeAppScript(datasourceScript);
                }

                function onSchedulingDatasourceFailed() {
                    // Channel has no mediazone
                    $scope.currentPost = undefined;
                    finalizeAppScript();
                }

                function onAppScript(res) {
                    latestApp = res.result.application;
                    // Retrieve the mediza zone scheduling datasource for this app
                    syncService.retrieveDatasource('scheduling_' + latestApp.applicationId).then(onSchedulingDatasourceLoaded, onSchedulingDatasourceFailed);
                }

                function retrieveAppScript() {
                    syncService.retrieveAppScriptObject().then(onAppScript, $angular.noop);
                }

                syncService.getAppProperties().then(function(properties) {
                    // Update metric schedule
                    updateMetric(properties);
                    retrieveAppScript();
                }, retrieveAppScript);
            }

            function onSyncEvent(event) {
                if (!event) {
                    return;
                }

                var eventName = event.event;
                switch (eventName) {
                    case SYNC_EVENT.SCRIPT_SAVED:
                        processOperationHours();
                        break;
                    case SYNC_EVENT.APP_UPDATED:
                        onApplicationUpdated();
                        break;
                }
            }

            function shouldResetApp() {
                var newTileIndex = layoutUtil.getTileIndex() || tileNotSet;
                var newTileMatrix = layoutUtil.getTileMatrix() || tileNotSet;

                // 1st time load, just set the value
                if (dataUtil.isNullOrUndefined(currentTileIndex) && dataUtil.isNullOrUndefined(currentTileMatrix)) {
                    currentTileIndex = newTileIndex;
                    currentTileMatrix = newTileMatrix;
                    return false;
                }

                if (newTileIndex === currentTileIndex && newTileMatrix === currentTileMatrix) {
                    return false;
                }

                currentTileIndex = newTileIndex;
                currentTileMatrix = newTileMatrix;
                return true;
            }

            function getProperty(key) {
                return syncService.getCurrentAppProperty(key);
            }

            function isDisplayResponsive() {
                return (getProperty('display.responsive') || '').toLowerCase() == 'true' ||
                    embedProfileService.isResponsiveDisplay();
            }

            function isDisplayReloadOnResize() {
                return dataUtil.isValueTrue(getProperty('display.onresize.reload') || 'true');
            }

            function applyDisplayRotation(app) {
                var promise = $q.defer();
                // set aspect ration based on app width & height
                var rotation;
                // do auto rotation if not in video wall mode
                if (!layoutUtil.isVideoWallMode()) {
                    rotation = app.pixelWidth >= app.pixelHeight ? 0 : 90;
                }

                var onRotationApplied = function() {
                    logService.info(logKey, 'Display orientation set to: ' + rotation);

                    deviceIdentityService.restart();
                    promise.resolve(rotation);
                };

                var onRotationApplyFailed = function(message) {
                    logService.debug(logKey, 'fail to apply rotation (' + rotation + '):', message);
                    promise.reject(rotation);
                };

                var ignoreSameRotation = halPlatformService.getFeatureSupport('display.force.rotation') == 'yes';
                displayUtil.applyRotation(syncService.getCurrentAppProperties(), rotation, ignoreSameRotation)
                    .then(onRotationApplied, onRotationApplyFailed);

                return promise.promise;
            }

            function processApp(app) {
                loadApp(app);
            }

            function updateMetric(properties) {
                var appid = syncService.retrieveAppId();
                metricService.clearAllMetricSchedule();
                metricService.updateConfiguration({
                    properties: properties,
                });
                syncService.retrieveDatasource('pmschedule_' + appid).then(function(res) {
                    if (res && res.result && res.result.pmSchedules) {
                        for (var i = 0; i < res.result.pmSchedules.length; i++) {
                            var schedule = res.result.pmSchedules[i];
                            metricService.registerMetricSchedule(schedule);
                        }
                    }
                }, function(err) {
                });
            }

            function updateCampaign() {
                campaignService.clear();
                syncService.retrieveDatasourceByType('campaign').then(function(res) {
                    for (var i = 0; i < res.length; i++) {
                        var ds = res[i].result;
                        campaignService.add(ds.campaign);
                    }
                });
            }

            function enableLoading() {
                $scope.layoutControllerApi.enableLoading(true);
            }

            function disableLoading() {
                $scope.layoutControllerApi.enableLoading(false);
            }

            function reloadApp() {
                // store currentApp in appToLoad as currentApp will be reset in resetApp
                var appToLoad = currentApp;
                resetApp();
                loadApp(appToLoad);
            }

            function resetApp() {
                clearResizeTimer();
                pendingLayout = undefined;
                currentLayoutPlaylistItem = null;
                layoutLoaded = false;
                enableLoading();
                $scope.layoutControllerApi.unload();
                cancelNextLayoutTimerPromise();
                currentApp = undefined;
                currentState = undefined;
            }

            function updateLayoutSchedule(schedules) {
                var items = [];
                processSheduleMode();
                // Add in reverse order
                for (var i = schedules.length - 1; i >= 0; i--) {
                    var schedule = schedules[i];
                    // Create the schedule item
                    var item = {
                        name: schedule.name,
                        enabled: !schedule.disable,
                        id: schedule.layoutScheduleId,
                        blocker: schedule.playoutSchedule.isExclusive,
                        items: []
                    };

                    // Create the scheduling restrictions
                    var itemSchedule = {
                        startDate: moment(schedule.playoutSchedule.startDate, 'YYYY-MM-DD HH:mm:ss').toDate(),
                        scheduleMode: schedule.playoutSchedule.scheduleMode
                    };
                    // If there is an end date, add it
                    if (schedule.playoutSchedule.noEndDate === false) {
                        itemSchedule.endDate = moment(schedule.playoutSchedule.endDate, 'YYYY-MM-DD HH:mm:ss').toDate();
                        itemSchedule.endDate.setHours(23, 59, 59);
                    }

                    var recur = {
                        days: []
                    };
                    var k;
                    // If there is a recurrance pattern, add it
                    if (schedule.playoutSchedule.useRecurrencePattern == true) {
                        for (k = 0; k < schedule.playoutSchedule.recurrencePattern.length; k++) {
                            var pattern = schedule.playoutSchedule.recurrencePattern[k];
                            // As start and end times are passed in as UTC we need to convert to local
                            pattern.startTime = pattern.startTime.substring(pattern.startTime.indexOf('T') + 1);
                            pattern.endTime = pattern.endTime.substring(pattern.endTime.indexOf('T') + 1);
                            var startTime = moment(pattern.startTime, 'HH:mm:ss').toDate();
                            var endTime = moment(pattern.endTime, 'HH:mm:ss').toDate();
                            for (var key in playOnToDayConfig) {
                                if (!pattern[key]) {
                                    continue;
                                }

                                recur.days.push({
                                    timespans: [{startTime: startTime, endTime: endTime}], day: playOnToDayConfig[key]
                                });
                            }
                        }

                        if (recur.days.length > 0) {
                            itemSchedule.recur = recur;
                        }
                    }

                    // Assign schedule
                    item.schedule = itemSchedule;

                    // Add playlist items
                    for (k = 0; k < schedule.layoutScheduleItems.length; k++) {
                        var layoutItem = schedule.layoutScheduleItems[k];
                        var duration = layoutItem.duration;
                        // For single playlist and autoplay with channels assigned
                        // in synchronized playback, we need to set the duration to a smaller number, 30s
                        // as by default, the layout duration is 2147483.
                        // In synchronized playback, to generate the deterministic schedule
                        // it depends on the layout duration. Having a large duration will break the schedule
                        // this is no longer needed once APC-6929 is implemented
                        if (layoutItem.layoutId === DEFAULT_PLAYLIST_LAYOUT_ID && duration === DEFAULT_PLAYLIST_DURATION && _useSynchronizedScheduling) {
                            duration = LAYOUT_DURATION;
                        }

                        var scheduleItem = {
                            layoutId: layoutItem.layoutId,
                            duration: duration,
                            enabled: !layoutItem.disable,
                            _parent: item
                        };
                        item.items.push(scheduleItem);
                    }

                    // Add this schedule to the items collection
                    items.push(item);
                }

                // Update playlist
                layoutPlaylist.setItems(items);
            }

            function processSheduleMode() {
                // check if we have global scheduling mode
                var scheduleMode = getProperty('player.layoutschedulingmode') || syncService.getCurrentAppProperty('player.scheduling.mode');
                if (!scheduleMode) {
                    return;
                }

                _useSynchronizedScheduling = scheduleMode.toLowerCase() === 'synchronized';
                logService.info(logKey, 'Sign Channel synchronized layout scheduling:' + _useSynchronizedScheduling);
            }

            function clearResizeTimer() {
                if (resizeTimer) {
                    $timeout.cancel(resizeTimer);
                    resizeTimer = undefined;
                }
            }

            function isReloadOnOrientationChangeRequired() {
                return halPlatformService.getFeatureSupport(FEATURE_CODE.RELOAD_ON_ORIENTATION_CHANGE);
            }

            function isResizeRequired(event) {
                var shouldResize = false;
                var screenOrientation = displayUtil.getScreenOrientation();
                switch (event) {
                    case RESIZE_EVENT.ORIENTATION_CHANGE:
                        shouldResize = isReloadOnOrientationChangeRequired();
                        break;
                    case RESIZE_EVENT.RESIZE:
                        // On some devices, "orientationchange event" will not be fired
                        // hence the need for manual orientation change detection
                        var orientationChanged = dataUtil.isObjectPropertiesDifferent(currentScreenOrientation, screenOrientation);
                        shouldResize = orientationChanged ? isReloadOnOrientationChangeRequired() : false;
                        break;
                }

                currentScreenOrientation = screenOrientation;
                return shouldResize;
            }

            function isDisplayResizable(event) {
                return (currentApp && currentApp.layoutTargets && currentApp.layoutTargets.length) ||
                    isResizeRequired(event) ||
                    (resizeOnReload && isDisplayResponsive() && isDisplayReloadOnResize());
            }

            function onResize(event) {
                clearResizeTimer();
                // if driver don't allow resize OR display is not set to responsive, don't reload app
                if (!isDisplayResizable(event)) {
                    return;
                }

                $scope.layoutControllerApi.showLoading();
                $scope.layoutControllerApi.unload();
                resizeTimer = $timeout(reloadApp, 1000);
            }

            function isScriptUpdateRequiresReload(newApp, oldApp) {
                // if we don't have any old app, then load the app normally (doesn't require reload)
                if (!oldApp) {
                    return true;
                }

                var paramsToCheck = ['layouts', 'elements', 'layoutSchedules'];
                for (var i = 0; i < paramsToCheck.length; i++) {
                    var param = paramsToCheck[i];
                    if (!dataUtil.isObjectEqual(newApp[param], oldApp[param])) {
                        return true;
                    }
                }

                return false;
            }

            function checkAppProperties(app) {
                if (!app || !app.properties) {
                    return;
                }

                // check autoplay mode have no channel published
                if (dataUtil.isValueTrue(app.properties[noChannelPublishedApplicationKey])) {
                    contentStateService.updateContentState({
                        state: CONTENT_STATE_EVENT.AUTOPLAY_NO_CONTENT,
                    });
                    return;
                }
            }

            function loadApp(app) {
                if (isDisplayResponsive()) {
                    var windowElement = $angular.element($window);
                    app.pixelWidth = windowElement.width();
                    app.pixelHeight = windowElement.height();
                }

                signsWidgetService.setApplication(app);
                logService.debug(logKey, 'Load app "' + app.name + '" (' + app.applicationId + ')');
                var oldApp = currentApp;
                currentApp = app;
                contentStateService.setCurrentChannel(app);
                checkAppProperties(app);

                var resetLayoutInfo = function() {
                    layoutLoading = false;
                    currentLayoutPlaylistItem = null;
                    layoutLoaded = false;
                };

                // No app to load, unload current
                if (!app) {
                    resetLayoutInfo();
                    logService.info(logKey, 'No app to load');
                    $scope.layoutControllerApi.unload();
                    return;
                }

                // Check has app id changed
                if (oldApp && oldApp.applicationId != currentApp.applicationId) {
                    // If so unload old app
                    resetLayoutInfo();
                    $scope.layoutControllerApi.unload();
                    $location.path('/tv/appinit');
                    return;
                }

                if (currentApp.applicationId == 0) {
                    // load play (temporary fix)
                    resetLayoutInfo();
                    logService.debug(logKey, 'App ID is 0, redirecting back to play');
                    $location.path('/tv/appinit');
                    return;
                }

                // we only reload the whole app if driver doesn't support script transparent udpate AND there are changes to the script
                if (!halPlatformService.getFeatureSupport('support.script.transparent.update') && isScriptUpdateRequiresReload(currentApp, oldApp)) {
                    // reload the app
                    resetLayoutInfo();
                    logService.debug(logKey, 'App reloading due to changes in app layout or elements');
                    enableLoading();
                    $scope.layoutControllerApi.unload();
                    currentState = undefined;
                }

                oldApp = null;
                var onRotateComplete = function() {
                    // Set application size
                    $scope.layoutControllerApi.setApplicationSize(
                        currentApp.pixelWidth,
                        currentApp.pixelHeight);

                    // if there's no schedules, there's no layout available to play
                    if (currentApp.layoutSchedules == null ||
                        currentApp.layoutSchedules.length == 0) {
                        // No layouts in this app
                        disableLoading();
                        layoutPlaylist.setItems(null);
                        return;
                    }

                    // Update the schedule playlist
                    updateLayoutSchedule(currentApp.layoutSchedules);

                    // if we haven't load anything, preload the widgets and load the first layout
                    if (!currentState) {
                        $scope.layoutControllerApi.preloadWidgets(app).then(nextLayout, nextLayout);
                        return;
                    }

                    // if we already running an app, update the widgets
                    logService.info(logKey, 'Application updated transparently, updating widget datasource');
                    $scope.layoutControllerApi.updateWidgets();
                    if (!isEditor) {
                        return;
                    }

                    logService.debug(logKey, 'Forcing widget placement updates in editor mode');
                    // if we are in editor mode, call next layout to force widget repositioning
                    nextLayout();
                };

                FontUtil.installCustomFonts(
                    FontUtil.convertPropertiesToFonts(
                        scriptingService.retrieveAppProperties()
                    )
                );
                applyDisplayRotation(app).then(onRotateComplete, onRotateComplete);
            }

            function cancelNextLayoutTimerPromise() {
                if (!nextLayoutTimerPromise) {
                    return;
                }

                $timeout.cancel(nextLayoutTimerPromise);
                nextLayoutTimerPromise = undefined;
                expectedLayoutEnd = undefined;
            }

            function createNextLayoutTimeoutPromise(timeout) {
                cancelNextLayoutTimerPromise();
                if (isEditor) {
                    logService.debug(logKey, 'Blocked next layout timer');
                    return;
                }

                expectedLayoutEnd = moment().add(timeout, 'ms').toDate();
                // reset remaining layout duration
                remainingLayoutDuration = undefined;
                nextLayoutTimerPromise = $timeout(nextLayout, timeout);
            }

            function nextLayout() {
                // Cancel previous layout timer
                cancelNextLayoutTimerPromise();
                currentLayoutPlaylistItem = _useSynchronizedScheduling ?
                    layoutPlaylist.getPlaylistItemByTime(new Date()) : layoutPlaylist.next();
                if (!currentLayoutPlaylistItem) {
                    logService.debug(logKey, 'No layout scheduled to playout at this time');
                    currentLayoutPlaylistItem = null;
                    $scope.layoutControllerApi.unload();
                    disableLoading();
                    // Retry in 1 second
                    createNextLayoutTimeoutPromise(1000);
                    return;
                }

                var duration = currentLayoutPlaylistItem.duration;
                if (currentLayoutPlaylistItem.$_endTime) {
                    currentLayoutPlaylistItem.startTime = currentLayoutPlaylistItem.$_startTime;
                    currentLayoutPlaylistItem.endTime = currentLayoutPlaylistItem.$_endTime;
                    var cDate = new Date();
                    var adjustedDuration =
                        (currentLayoutPlaylistItem.$_endTime.getTime() - cDate.getTime()) / 1000;
                    duration = adjustedDuration || duration;
                }

                logService.info(logKey, 'Loading next layout');
                // Find the layout and load it
                var layout = findLayoutById(currentLayoutPlaylistItem.layoutId);
                if (!layout) {
                    return;
                }

                layout = angular.copy(layout);
                layout.startTime = currentLayoutPlaylistItem.$_startTime;
                layout.endTime = currentLayoutPlaylistItem.$_endTime;
                loadLayout(layout, duration * 1000, false, currentLayoutPlaylistItem._parent);
            }

            function findLayoutById(layoutId) {
                var targetLayout;
                for (var i = 0; i < currentApp.layouts.length; i++) {
                    var layout = currentApp.layouts[i];
                    if (layoutId !== layout.layoutId) {
                        continue;
                    }

                    targetLayout = layout;
                    break;
                }

                return layoutUtil.findLayoutTarget(currentApp, targetLayout, currentApp.pixelWidth, currentApp.pixelHeight);
            }

            function previousLayout() {
                // Cancel previous layout timer
                cancelNextLayoutTimerPromise();

                currentLayoutPlaylistItem = layoutPlaylist.previous();
                if (!currentLayoutPlaylistItem) {
                    // Retry in 1 second
                    createNextLayoutTimeoutPromise(1000);
                    return;
                }

                loadLayoutById(
                    currentLayoutPlaylistItem.layoutId, currentLayoutPlaylistItem.duration * 1000
                );
            }

            function back() {
                // Cancel previous layout timer
                cancelNextLayoutTimerPromise();

                var lastLayout = retrieveLastLayout();
                if (!lastLayout) {
                    return;
                }

                currentLayoutPlaylistItem = null;
                loadLayoutById(lastLayout.layoutId, lastLayout.duration * 1000, true);
            }

            function processPendingLayout() {
                cancelNextLayoutTimerPromise();
                // Check do we have a pending load
                logService.debug(logKey, 'Loading pending layout: ' + pendingLayout.layoutId);
                loadLayoutById(pendingLayout.layoutId, pendingLayout.duration);
            }

            function checkLayoutValid() {
                if (layoutLoaded &&
                    !layoutLoading &&
                    currentLayoutPlaylistItem &&
                    !layoutPlaylist.isValid(currentLayoutPlaylistItem)) {
                    logService.debug(logKey, 'Current layout is no longer valid: ' + currentLayoutPlaylistItem.layoutId);
                    nextLayout();
                }
            }

            function saveLayoutHistory(state) {
                layoutHistories.push({
                    layoutId: state.layout.layoutId,
                    duration: state.duration / 1000
                });
                if (layoutHistories.length > 20) {
                    layoutHistories.shift();
                }
            }

            function retrieveLastLayout() {
                return layoutHistories.pop();
            }

            function subscribeToLayoutEvents() {
                if (!$scope.layoutControllerApi) {
                    return;
                }

                unsubscribeToLayoutEvents();
                $scope.layoutControllerApi.subscribe(onReceivedLayoutEvents);
            }

            function unsubscribeToLayoutEvents() {
                $scope.layoutControllerApi.unsubscribe(onReceivedLayoutEvents);
            }

            function onReceivedLayoutEvents(event) {
                switch (event.type) {
                    case LAYOUT_EVENT.WIDGET_POSITION_CHANGED:
                        var widget = getWidgetPlacement(event.id, currentState.layout);
                        if (widget && isPlacementUpdated(event.placement, widget)) {
                            var placement = dataUtil.toCssPlacement(event.placement);
                            if (placement) {
                                var placementInPixels = dataUtil.toPlacementInPixels({width: $window.innerWidth, height: $window.innerHeight}, placement);
                                logService.debug('appspace.signs.editor', 'Widget placement changed  (left: ' + placementInPixels.left + 'px, top: ' +
                                    placementInPixels.top + 'px, width: ' + placementInPixels.width + 'px, height: ' + placementInPixels.height + 'px)');
                            }

                            raisePostMessage(POST_MESSAGES.WIDGET_POSITION_CHANGED, {
                                widgetId: event.id,
                                placement: placement
                            });
                        }

                        break;
                    case LAYOUT_EVENT.WIDGET_SELECTED:
                        raisePostMessage(POST_MESSAGES.WIDGET_SELECTED, {widgetId: event.id});
                        break;
                    case LAYOUT_EVENT.WIDGET_DOUBLE_CLICKED:
                        raisePostMessage(POST_MESSAGES.WIDGET_DOUBLE_CLICKED, {widgetId: event.id});
                        break;
                    case LAYOUT_EVENT.ON_CONTENT_LOADED:
                        if (!event.content || !event.content.postId) {
                            return;
                        }

                        var postId = event.content.postId;
                        var currentSocialPost = socialService.getCurrentPost();
                        if (currentSocialPost.id === postId) {
                            // Manually trigger a social post update event to set the current post
                            // since the event will only be triggered when there are actual changes to the current post
                            onSocialBarEvent(SOCIAL_BAR_EVENT.POST_UPDATE, currentSocialPost.post);
                            return;
                        }

                        socialService.setCurrentPost(postId);
                        break;
                }
            }

            function processOperationHours() {
                var deviceProperties = syncService.getCurrentAppProperties() || {};
                var isOperationHoursScheduleEnabled = dataUtil.isValueTrue(deviceProperties[PLAYER_PROPERTY_KEYS.OPERATION_HOURS_SCHEDULE_ENABLED]);
                var nonOperationHoursLayoutName = deviceProperties[PLAYER_PROPERTY_KEYS.NON_OPERATION_HOURS_LAYOUT_NAME];
                $scope.isWithinOperationHours = Boolean(!isOperationHoursScheduleEnabled ||
                    nonOperationHoursLayoutName !== currentLayoutName);
            }

            function raisePostMessage(type, property) {
                var postMessage = {
                    message: POST_MESSAGES.NAME,
                    type: type,
                    info: property || {}
                };
                if (dataUtil.isObjectEmpty(postMessage.info)) {
                    delete postMessage.info;
                }

                postMessagingService.raiseMessage(postMessage);
            }

            function isPlacementValid(placement) {
                return placement &&
                    !dataUtil.isNullOrUndefined(placement.top) &&
                    !dataUtil.isNullOrUndefined(placement.left) &&
                    !dataUtil.isNullOrUndefined(placement.width) &&
                    !dataUtil.isNullOrUndefined(placement.height) &&
                    !dataUtil.isNullOrUndefined(placement.zIndex);
            }

            function onReceivedPostMessage(data) {
                var postMessageData = data ? angular.copy(data) : {};
                postMessageData.message = postMessageData.message || '';
                postMessageData.data = postMessageData.data || {};
                if (!isEditor ||
                    postMessageData.message.toLowerCase() != 'appspaceapp.editor' ||
                    !postMessageData.command) {
                    return;
                }

                var commandConfig = editorCommandConfig[postMessageData.command.toLowerCase()];
                if (!commandConfig) {
                    return;
                }

                // don't execute  editor command if it requires widget editor to be enabled first but widget editor is currently disabled
                if (!commandConfig.validOnEditorDisabled && !$scope.layoutControllerApi.isWidgetEditorEnabled()) {
                    raisePostMessage(POST_MESSAGES.WIDGET_EDITOR_DISABLED);
                    return;
                }

                commandConfig.callback(postMessageData.data);
            }

            function isPlacementUpdated(placement, currentPlacement) {
                var widgetPlacement = currentPlacement.widget;
                var result = false;
                // if any of the placement is different, set the widget placement to the new placement
                for (var i = 0; i < placementKeys.length; i++) {
                    var key = placementKeys[i];
                    if (widgetPlacement[key] == placement[key]) {
                        continue;
                    }

                    widgetPlacement[key] = placement[key];
                    result = true;
                }

                return result;
            }

            function isZIndexUpdated(zIndex, currentPlacement) {
                // if we need to reorder the widget (z-index)
                if (!dataUtil.isNullOrUndefined(zIndex) &&
                    zIndex != currentPlacement.zIndex &&
                    currentState.layout.elementPlacements[zIndex]) {
                    // remove the widget from the array and then slot it back in the targeted z-index
                    var widget = currentState.layout.elementPlacements.splice(currentPlacement.zIndex, 1);
                    currentState.layout.elementPlacements.splice(zIndex, 0, widget[0]);
                    return true;
                }

                return false;
            }

            function getWidgetPlacement(id, layout) {
                // look for the correct widget placement
                for (var j = 0; j < layout.elementPlacements.length; j++) {
                    var widgetPlacement = layout.elementPlacements[j];
                    if (widgetPlacement.elementId != id) {
                        continue;
                    }

                    return {
                        widget: widgetPlacement,
                        zIndex: j
                    };
                }
            }

            function loadLayoutById(layoutId, duration, bypassHistory, schedule) {
                // Find the layout and load it
                var layout = findLayoutById(layoutId);
                if (!layout) {
                    return;
                }

                loadLayout(layout, duration, bypassHistory, schedule);
            }

            function loadLayout(layout, duration, bypassHistory, schedule) {
                // Cancel previous layout timer
                cancelNextLayoutTimerPromise();

                if (isNaN(duration) || duration == null) {
                    duration = 60000;
                } else if (duration == 0 || duration > 2147483646) {
                    duration = 2147483646;
                }

                var loadStartTime = new Date();
                // Reset pending load
                pendingLayout = undefined;
                layoutLoading = true;

                if (currentState && !bypassHistory) {
                    saveLayoutHistory(currentState);
                }

                subscribeToLayoutEvents();
                $scope.layoutControllerApi.load(layout).then(function(resolvedLayout) {
                    var currentTime = new Date().getTime();
                    layoutLoading = false;
                    // On layout load
                    if (!layoutLoaded) {
                        onFirstLayoutLoaded();
                    }

                    if (pendingLayout) {
                        processPendingLayout();
                        return;
                    }

                    // If we are using synchronized scheduling, we need to reduce the duration by how long it took to load
                    var loadElapse = currentTime - loadStartTime.getTime();
                    if (_useSynchronizedScheduling) {
                        duration -= loadElapse;
                        if (duration < 0) {
                            duration = 1000;
                        }
                    }

                    scriptingService.raiseEvent('application', 'layoutloaded', {layoutId: layout.layoutId, id: layout.layoutId, layoutName: layout.name, name: layout.name});
                    currentState = {layout: layout, duration: duration};
                    var postMessageData = {layoutId: layout.layoutId};
                    if (schedule) {
                        postMessageData.scheduleId = schedule.id;
                    }

                    currentLayoutName = layout.name || '';
                    processOperationHours();
                    contentStateService.populateCurrentContentLayout(resolvedLayout);
                    postMessagingService.raiseMessage({
                        message: POST_MESSAGES.NAME,
                        type: POST_MESSAGES.LAYOUT_LOADED,
                        data: postMessageData
                    });
                    // Set timeout for next layout
                    createNextLayoutTimeoutPromise(duration);
                    logService.debug(logKey, 'Layout "' + layout.name + '" loaded in ' + loadElapse + 'ms (duration:' + duration + 'ms)');
                }, function(err) {
                    // Check is the err an object, if so stringify it
                    if (err && typeof err === 'object') {
                        err = JSON.stringify(err, null, 4);
                    }

                    layoutLoading = false;

                    // Failed to load, try again in a few secs
                    logService.warn(logKey, 'Unable to load layout: ' + err + '. Retrying...');
                    pendingLayout = {
                        layoutId: layout.layoutId,
                        duration: duration
                    };
                    // Retry again in 1sec
                    createNextLayoutTimeoutPromise(1000);
                });
            }

            $scope.onClick = function(event) {
            };

            // Event handler for remote events
            var onRemoteEvent = function(remoteEvent) {
                // disable event when in preview mode
                if (isRemoteDisable) {
                    remoteEvent.preventDefault();
                    return;
                }

                // Only listen to down button events
                if (remoteEvent.eventType != REMOTEEVENTTYPE.buttonDown) {
                    return;
                }

                // Perform action based on Virtual code
                switch (remoteEvent.vcode) {
                    case REMOTEVCODE.up: // Up Key
                        break;
                    case REMOTEVCODE.down: // Down Key
                        break;
                    case REMOTEVCODE.left: // Left Key
                        // only call previous layout if ctrl+alt is used
                        if ((remoteEvent.iskeyboard && remoteEvent.altKey && remoteEvent.ctrlKey)) {
                            previousLayout();
                        }

                        break;
                    case REMOTEVCODE.right: // Right Key
                        // only call next layout if ctrl+alt is used
                        if ((remoteEvent.iskeyboard && remoteEvent.altKey && remoteEvent.ctrlKey)) {
                            nextLayout();
                        }

                        break;
                    case REMOTEVCODE.select:
                        if (remoteEvent.code != 13) {// ignore ENTER
                            reloadApp();
                        }

                        break;
                    case REMOTEVCODE.back: // command/windows Key
                        if (halPlatformService.getFeatureSupport(FEATURE_CODE.APP_EXIT_ON_BACK)) {
                            halPlatformService.closeApplication();
                            return;
                        }

                        break;
                }
            };

            function onSocialBarEvent(event, data) {
                if (!event) {
                    return;
                }

                switch (event.toLowerCase()) {
                    case SOCIAL_BAR_EVENT.POST_UPDATE:
                        $scope.currentPost = data;
                        break;
                }
            }

            coreFrameworkService.isReady().then(init);
        }]);
})(window.angular);

(function($angular) {
    'use strict';

    var angular = $angular;
    var app = angular.module('signsHTML5');

    app.controller('LayoutController', ["$scope", "$element", "$compile", "$timeout", "$q", "$log", "signsWidgetService", "scriptingService", "logService", "ColorUtil", "halPlatformService", "halVideoLayerManager", "syncService", "contentStateService", "CONTENT_STATE_EVENT", "SYSTEM_DATA", "APP_STRETCH", "Observable", "EDITOR_SETTINGS", "LAYOUT_EVENT", "dataUtil", "layoutUtil", "TimeoutUtil", "broadcastService", "embedProfileService", "pageUtil", function(
        $scope,
        $element,
        $compile,
        $timeout,
        $q,
        $log,
        signsWidgetService,
        scriptingService,
        logService,
        ColorUtil,
        halPlatformService,
        halVideoLayerManager,
        syncService,
        contentStateService,
        CONTENT_STATE_EVENT,
        SYSTEM_DATA,
        APP_STRETCH,
        Observable,
        EDITOR_SETTINGS,
        LAYOUT_EVENT,
        dataUtil,
        layoutUtil,
        TimeoutUtil,
        broadcastService,
        embedProfileService,
        pageUtil
    ) {
        var widgetInstances = [];
        var appWidth = 1920;
        var appHeight = 1080;
        var layoutLoading;
        var displayLoadingIcon = true;
        var canvasSize = {w: 1920, h: 1080};
        var currentLayout = undefined;
        var useCssPlacement = halPlatformService.getFeatureSupport('rendering.css3-transitions') !== 'no';
        var canvasContainer = $($element.children()[0]);
        var resizeNeeded = false;
        var repositionTimer = undefined;
        var loadingBlockedUntil = new Date().getTime();
        var loadTimeoutDuration = 0;
        var observable = new Observable();
        var logKey = 'appspace.signs.app';
        var isMovingWidget;
        var layoutEditorApi;
        var gridEditorApi;
        var animationEaseType = 'ease-in-out';
        var selectedWidgetId;
        $scope.isEditingMode = halPlatformService.getFeatureSupport('playback.mode.editor');
        var editorScale = EDITOR_SETTINGS.DEFAULT_EDITOR_SCALE;
        var isUsingSynchronizedPlayback = false;
        var onBackmostVideoLayerEventOff;
        var previousBackgroundColor;
        var forcedBackgroundColor;
        var appBackgroundColor = embedProfileService.getBackgroundColor();
        var DEFAULT_COLOR = {
            BACKGROUND: '#000000',
            ALPHA: 1
        };

        var viewportInfo = {
            xTiles: 2,
            yTiles: 1,
            tileIndex: 1
        };

        // Initialise the service
        function init() {
            setCanvasBackground(appBackgroundColor);
            pageUtil.setAppBackgroundColor(appBackgroundColor);
            // Get the media load timeout, and set that to the max load duration
            loadTimeoutDuration = halPlatformService.getFeatureSupport('media.timeout.duration');
            loadTimeoutDuration = loadTimeoutDuration < SYSTEM_DATA.MIN_TIMEOUT_DURATION ?
                SYSTEM_DATA.MIN_TIMEOUT_DURATION : loadTimeoutDuration;
            var api = {
                setApplicationSize: setApplicationSize,
                preloadWidgets: preloadWidgetsInApp,
                updateWidgets: updateWidgets,
                load: load,
                unload: unload,
                enableLoading: enableLoading,
                showLoading: showLoading,
                showWidget: showWidget,
                subscribe: subscribe,
                unsubscribe: unsubscribe,
                selectWidget: selectWidget,
                moveSelectedWidget: moveSelectedWidget,
                scaleSelectedWidget: scaleSelectedWidget,
                setGridConfiguration: setGridConfiguration,
                enableWidgetEditor: enableWidgetEditor,
                isWidgetEditorEnabled: isWidgetEditorEnabled,
                setEditorScale: setEditorScale
            };
            onBackmostVideoLayerEventOff = broadcastService.onBackmostVideoLayerEvent(onBackmostVideoLayerEventFired);
            // Handle view destroy
            $scope.$on('$destroy', function() {
                if (gridEditorApi) {
                    gridEditorApi = undefined;
                }

                if (layoutEditorApi) {
                    layoutEditorApi.destroy();
                    layoutEditorApi = undefined;
                }

                // Unload all widgets
                unload();
                // Cancel reposition timer
                cancelRepositionTimer();

                // clean up stuff
                logService.debug(logKey, 'LayoutController controller destroyed');

                // Remove the resize event handler
                $(window).off('resize', windowResize);
                if (!onBackmostVideoLayerEventOff) {
                    return;
                }

                onBackmostVideoLayerEventOff();
            });

            $scope.$parent.layoutControllerApi = api;

            logService.debug(logKey, 'LayoutController initialised');

            // On browser resize, resize the canvas
            $(window).resize(windowResize);

            showLoadingIcon();
            resizeCanvas();
            setBackgroundColor();
        }

        function enableLoading(show) {
            displayLoadingIcon = show;
            if (!show) {
                hideLoadingIcon();
            }
        }

        function showLoading() {
            enableLoading(true);
            showLoadingIcon();
        }

        function showLoadingIcon() {
            if (!displayLoadingIcon || $scope.isEditingMode) {
                return;
            }

            $scope.showLoader = true;
        }

        function hideLoadingIcon() {
            $scope.showLoader = false;
        }

        function setApplicationSize(width, height) {
            appWidth = width;
            appHeight = height;
        }

        function repositionWidgets() {
            if (!layoutLoading) {
                resizeCanvas();
                resizeNeeded = false;
                load(currentLayout).then($angular.noop, $angular.noop);
            } else {
                resizeNeeded = true;
            }
        }

        function cancelRepositionTimer() {
            if (!repositionTimer) {
                return;
            }

            $timeout.cancel(repositionTimer);
            repositionTimer = undefined;
        }

        function windowResize() {
            cancelRepositionTimer();
            repositionTimer = $timeout(repositionWidgets, 200);
        }

        function resizeCanvas() {
            var canvas = $element.children()[0];
            var parent = $($element.children()[0].parentElement);

            var parentWidth = parent.width();
            var parentHeight = parent.height();
            if (parentHeight <= 0) {
                parentHeight = $(window).height();
            }

            var displayAspectRatio = parentWidth / parentHeight;
            logService.trace(logKey, 'Display ratio: ' + displayAspectRatio);

            // if we're in video wall mode, don't resize the app
            if (layoutUtil.isVideoWallMode()) {
                canvasSize = {w: canvas.offsetWidth, h: canvas.offsetHeight};
                signsWidgetService.setCanvasSize({width: canvasSize.w, height: canvasSize.h});
                return;
            }

            var appAspectRatio = appWidth / appHeight;
            logService.trace(logKey, 'App ratio: ' + appAspectRatio);
            var width = '100%';
            var height = '100%';
            var left = '0%';
            var top = '0%';

            var fontScaling = 1;
            var scaleMode = APP_STRETCH.FULL; // by default (same orientation between display & app), stretch the app to display
            var isAppLandscape = appAspectRatio >= 1;
            var isDisplayLandscape = displayAspectRatio >= 1;
            // if orientation doesn't match, we need to create black bars
            if (isAppLandscape != isDisplayLandscape) {
                // if app is landscape but display is portrait, create horizontal black bars (1)
                // if app is portrait but display is landscape, create vertical black bars (2)
                scaleMode = isAppLandscape ? APP_STRETCH.WIDTH : APP_STRETCH.HEIGHT;
            }

            // Based on the scale mode we adjust the canvas size and position
            switch (scaleMode) {
                // Stretch canvas to fill full width and height
                case APP_STRETCH.FULL:
                    height = width = 100 + '%';
                    top = left = 0 + '%';
                    fontScaling = parentHeight / appHeight;
                    break;
                // Stretch canvas to fill width but constrain height
                case APP_STRETCH.WIDTH:
                    var requiredHeight = (parentWidth / appAspectRatio);
                    var offset = (parentHeight - requiredHeight) / 2;
                    height = (requiredHeight / parentHeight) * 100 + '%';
                    top = (offset / parentHeight) * 100 + '%';
                    fontScaling = requiredHeight / appHeight;
                    break;
                // Stretch canvas to fill height but constrain width
                case APP_STRETCH.HEIGHT:
                    var requiredWidth = (parentHeight * appAspectRatio);
                    var leftOffset = (parentWidth - requiredWidth) / 2;
                    width = (requiredWidth / parentWidth) * 100 + '%';
                    left = (leftOffset / parentWidth) * 100 + '%';
                    fontScaling = parentHeight / appHeight;
                    break;
            }

            // Apply the new sizing style
            canvas.style.width = width;
            canvas.style.height = height;
            canvas.style.top = top;
            canvas.style.left = left;
            canvas.style.fontSize = (12.5 * fontScaling) + 'px';

            // Set the canvas size
            signsWidgetService.setCanvasSize({width: canvas.offsetWidth, height: canvas.offsetHeight});

            canvasSize = {w: canvas.offsetWidth, h: canvas.offsetHeight};
        }

        function isWidgetInstanceValid(instance) {
            return instance && instance.dom && instance.dom[0];
        }

        function showWidget(widgetId, show) {
            var widgetInstance = findInstance(widgetId);

            if (dataUtil.isNullOrUndefined(widgetInstance) || !widgetInstance.api) {
                return;
            }

            var onsleep = function() {
                if (!isWidgetInstanceValid(widgetInstance)) {
                    return;
                }

                widgetInstance.dom[0].style.display = show ? '' : 'none';
            };

            var onfrozen = function() {
                sleepWidget(widgetInstance).then(onsleep, onsleep);
            };

            freezeWidget(widgetInstance).then(onfrozen, onfrozen);
        }

        function unloadLoadingLayout(layout) {
            // Enumerate through each widget instances and unload
            for (var id in layout.widgetInstances) {
                var wi = layout.widgetInstances[id];
                layout.widgetInstances[id] = undefined;
                unloadWidget(wi);
            }

            layout.widgetInstances = {};
        }

        function unloadWidget(wi) {
            if (!wi) {
                return;
            }

            if (wi.api && wi.api.destroy) {
                wi.api.destroy();
                wi.api = undefined;
            }

            if (wi.containerApi && wi.containerApi.destroy) {
                wi.containerApi.destroy();
                wi.containerApi = undefined;
            }

            // Destroy the widgets scope
            if (wi.scope) {
                wi.scope.$destroy();
                wi.scope = undefined;
            }

            if (wi.widget) {
                // Unregister from scripting service
                scriptingService.unregisterTarget('element_' + wi.widget.elementId);
            }

            if (wi.editorApi) {
                wi.editorApi.dispose();
                wi.editorApi = undefined;
            }

            // Remove the click event
            if (wi.dom) {
                wi.dom.off('click');
                wi.dom.remove();
                wi.dom = undefined;
            }

            // Remove the directive from the canvas
            if (wi.element) {
                wi.element.remove();
                wi.element.empty();
                wi.element = undefined;
            }

            wi = undefined;
        }

        function unload() {
            currentLayout = undefined;
            logService.debug(logKey, 'Layout controller unload');

            // Is the a layout currently loading
            if (layoutLoading) {
                layoutLoading.abort = true;
                // Remove instannces that are not in current load
                for (var i = 0; i < widgetInstances.length; i++) {
                    var widgetInstance = widgetInstances[i];
                    if (!layoutLoading.widgetInstances[widgetInstance.id]) {
                        unloadWidget(widgetInstance);
                    }
                }

                // Block loading for about 2 secs, give time for current layout loads to be aborted
                loadingBlockedUntil = new Date().getTime() + 2000;
                layoutLoading = undefined;
            } else {
                // Enumerate through each widget instances and unload
                for (var j = 0; j < widgetInstances.length; j++) {
                    unloadWidget(widgetInstances[j]);
                }
            }

            widgetInstances = undefined;
            widgetInstances = [];

            // Reset layers
            halVideoLayerManager.resetLayers();
        }

        function preloadWidgetsInApp(app, allWidgets) {
            // Check its a valid app
            if (!app || !app.layouts) {
                return $q.resolve();
            }

            // Enumerate through each layout and check elements
            var widgetsToPreload = [];
            var i;
            // Force load all widgets
            if (allWidgets) {
                for (i = 0; i < app.layouts.length; i++) {
                    var layout = app.layouts[i];
                    // Check which widgets are needed for this load that are not already loaded.
                    for (var k = 0; k < layout.elementPlacements.length; k++) {
                        var e = layout.elementPlacements[k];
                        widgetsToPreload.push(e);
                    }
                }
            } else {
                // default is to only load widgets that have actions target to it
                var triggers = app.triggers;
                for (i = 0; i < triggers.length; i++) {
                    var trigger = triggers[i];
                    var id;
                    // find all widgets that are set as target
                    if (trigger.scope.toLowerCase().indexOf('element_') >= 0) {
                        id = parseInt(trigger.scope.substring(8), 10);
                    }

                    // find all media zones inside loadplaylistfrominfomenuitem
                    if (trigger.actions) {
                        for (var j = 0; j < trigger.actions.length; j++) {
                            var action = trigger.actions[j];
                            if (action.name.toLowerCase() == 'loadplaylistfrominfomenuitem') {
                                id = parseInt(action.mediaZoneId, 10);
                            }
                        }
                    }

                    if (id) {
                        widgetsToPreload.push({elementId: id});
                    }
                }
            }

            return preloadWidgets(widgetsToPreload);
        }

        function updateWidgets() {
            var deferred = $q.defer();

            logService.debug(logKey, 'Begin updating widgets');

            var promises = [];
            // Enumerate through current widget instances
            for (var i = 0; i < widgetInstances.length; i++) {
                promises.push(updateWidget(widgetInstances[i]));
            }

            // Resolve once all promises have complete. Ignore if a few fail to create
            $q.allSettled(promises).then(deferred.resolve, deferred.resolve);
            return deferred.promise;
        }

        function preloadWidgets(widgets) {
            var deferred = $q.defer();
            resizeCanvas();
            var ctime = new Date().getTime();
            showLoadingIcon();

            logService.debug(logKey, 'Begin preloading widgets');

            // Collection of widgets that need to be loaded
            var widgetsToCreate = [];
            var createLookup = {};
            var promises = [];

            // Check which widgets are needed for this load that are not already loaded.
            for (var k = 0; k < widgets.length; k++) {
                var widget = widgets[k];

                // Check are we already creating this widget
                if (createLookup[widget.elementId]) {
                    continue;
                }

                // if we already have an instance of this widget, skip
                var existingInstance = findInstance(widget.elementId);
                if (existingInstance) {
                    continue;
                }

                // Cant find the widget, so add it to the create list
                var newWidgetInstance = createNewWidgetInstance(widget.elementId);
                createLookup[widget.elementId] = true;
                widgetsToCreate.push(newWidgetInstance);
                logService.trace(logKey, 'Preload widget: ' + newWidgetInstance.id);
            }

            // Add the widgets to create to the instance cache
            for (var i = 0; i < widgetsToCreate.length; i++) {
                widgetInstances.push(widgetsToCreate[i]);
                promises.push(createWidget(widgetsToCreate[i]));
            }

            var onWidgetsCreated = function(p) {
                // Widgets loaded
                var elapsed = new Date().getTime() - ctime;
                logService.trace(logKey, 'Preloaded  ' + widgetsToCreate.length + ' widgets in ' + elapsed + 'ms');
                deferred.resolve();
            };

            // Resolve once all primises have complete. Ignore if a few fail to create
            $q.allSettled(promises).then(onWidgetsCreated, onWidgetsCreated);

            return deferred.promise;
        }

        function createNewWidgetInstance(widgetId) {
            return {
                id: widgetId,
                status: 'pending',
                widget: undefined,
                dom: undefined,
                api: undefined,
                containerApi: undefined,
                initPromise: undefined,
                placement: undefined
            };
        }

        function load(layout) {
            resizeCanvas();

            var ctime = new Date().getTime();

            isUsingSynchronizedPlayback = scriptingService.retrieveAppProperty('player.scheduling.mode') == 'synchronized';
            if (ctime < loadingBlockedUntil) {
                logService.debug(logKey, 'Layout currently unloading...');
                return $q.reject('Layout currently unloading..');
            }

            // Assert valid layout provided
            if (!layout) {
                layoutLoading = undefined;
                logService.debug(logKey, 'Load layout: Undefined');
                return $q.reject('Load layout: Undefined');
            }

            if (layoutLoading) {
                logService.debug(logKey, 'Layout already loading...');
                return $q.reject('layout already loading');
            }

            if (isMovingWidget) {
                return $q.reject('User currently moving widget');
            }

            // Only make a clone if this is not already a clone
            if (!layout.widgetInstances) {
                layout = $angular.copy(layout);
            }

            layoutLoading = layout;
            layoutLoading.widgetInstances = {};
            var t = new Date().getTime();

            if (displayLoadingIcon) {
                showLoadingIcon();
            }

            logService.debug(logKey, 'Begin load layout: ' + layout.name);

            // Update viewport
            updateViewport();

            // Collection of widgets that need to be loaded
            var widgetsToCreate = [];

            // Check which widgets are needed for this load that are not already loaded.
            var isSelectedWidgetExists = false;
            for (var k = 0; k < layout.elementPlacements.length; k++) {
                var elementPlacement = layout.elementPlacements[k];
                // Apply viewport transform
                elementPlacement = applyViewportPlacementTransform(elementPlacement);

                // Check if the element is in the viewport
                if (!isElementInViewport(elementPlacement)) {
                    // Dont render if not
                    $log.log('Skip element not in viewport', elementPlacement.elementId);
                    continue;
                }

                if (selectedWidgetId && elementPlacement.elementId == selectedWidgetId) {
                    isSelectedWidgetExists = true;
                }

                // Do we have a widget instance for this element
                var existingInstance = findInstance(elementPlacement.elementId);
                if (existingInstance) {
                    layoutLoading.widgetInstances[elementPlacement.elementId] = existingInstance;
                    continue;
                }

                // Cant find the widget, so add it to the create list
                var newWidgetInstance = createNewWidgetInstance(elementPlacement.elementId);
                layoutLoading.widgetInstances[elementPlacement.elementId] = newWidgetInstance;
                widgetsToCreate.push(newWidgetInstance);
                logService.trace(logKey, 'Create: ' + newWidgetInstance.id);
            }

            // The selected widget has been removed from the current layout in editor mode
            if (!isSelectedWidgetExists) {
                // Deselect the currently selected widget that is no longer valid
                selectWidgetOnLayoutEditor(undefined);
            }

            // Add the widgets to create to the instance cache
            var promises = [];
            for (var i = 0; i < widgetsToCreate.length; i++) {
                var wi = widgetsToCreate[i];
                widgetInstances.push(wi);
                var p = createWidget(wi);
                promises.push(p);
            }

            var deferred = $q.defer();
            var onWidgetsCreated = function() {
                // Check should we abort load
                if (layout.abort) {
                    unloadLoadingLayout(layout);
                    layoutLoading = undefined;
                    deferred.reject('aborted layout load');
                    logService.debug(logKey, 'Layout load abort: ' + layout.name + ' in ' + (new Date().getTime() - t) + 'ms');
                    return;
                }

                // Widgets created
                var onLayoutLoaded = function() {
                    layoutLoading = undefined;
                    currentLayout = layout;
                    // Check should this load be aborted
                    if (layout.abort) {
                        unloadLoadingLayout(layout);
                        deferred.reject('aborted layout load');
                        logService.debug(logKey, 'Layout load abort: ' + layout.name + ' in ' + (new Date().getTime() - t) + 'ms');
                        return;
                    }

                    deferred.resolve(layout);
                    if (displayLoadingIcon) {
                        hideLoadingIcon();
                    }
                };

                var onSleepWidgets = function() {
                    // If there is a pending resize, we run it before loading layout
                    if (resizeNeeded) {
                        resizeCanvas();
                        resizeNeeded = false;
                        positionWidgets(layout).then(onPositionWidgets, deferred.reject);
                        return;
                    }

                    onLayoutLoaded();
                };

                var maxRetries = 50;
                var retryCount = 0;

                var onPositionWidgets = function() {
                    // if canvas height or width still equal to 0, user cannot see the widget
                    // the solution is to do re position again
                    var canvas = $element.children()[0];
                    if ((canvas.offsetWidth == 0 || canvas.offsetHeight == 0) && retryCount < maxRetries) {
                        retryCount++;
                        $timeout(function() {
                            positionWidgets(layout).then(onPositionWidgets, deferred.reject);
                        }, 10);
                    } else {
                        sleepWidgets(layout).then(onSleepWidgets, deferred.reject);
                    }
                };

                var onWidgetsUnfrozen = function() {
                    positionWidgets(layout).then(onPositionWidgets, deferred.reject);
                };

                var onWidgetsWoken = function () {
                    // mute previous layout when load new layout. HDMI-in video stream is unpauseable.
                    mutePreviousLayout(layout);
                    unFreezeWidgets(layout).then(onWidgetsUnfrozen, deferred.reject);
                };

                // Freeze current widgets
                wakeupWidgets(layout).then(onWidgetsWoken, onWidgetsWoken);
            };

            // Freeze all widgets before we start loading the other widgets
            freezeWidgets(layout).then(function() {
                // Resolve once all primises have complete. Ignore if a few fail to create
                $q.allSettled(promises).then(onWidgetsCreated, onWidgetsCreated);
            });

            return deferred.promise;
        }

        function mutePreviousLayout(currentLayout) {
            var widgetsRequired = {};
            // Build required widgets lookup
            for (var k = 0; k < currentLayout.elementPlacements.length; k++) {
                var element = currentLayout.elementPlacements[k];
                widgetsRequired[element.elementId] = true;
            }

            for (var i = 0; i < widgetInstances.length; i++) {
                var widget = widgetInstances[i];

                // Check if this widget is required
                if (widgetsRequired[widget.id]) {
                    // Required widget
                    continue;
                }
                // Mute this widget as its not needed.
                muteWidget(widget);
            }
        }

        function muteWidget(widget) {
            if (!widget.api || !widget.api.mute) {
                return;
            }

            widget.api.mute(true, true);
        }

        function updateViewport() {
            viewportInfo = {
                xTiles: 1,
                yTiles: 1,
                totalTiles: 1,
                tileIndex: 1,
                xTileOffset: 1,
                yTileOffset: 1,
                bezelComp: {
                    l: 0,
                    t: 0,
                    r: 0,
                    b: 0
                }
            };

            // Retrieve property
            var tileMatrix = layoutUtil.getTileMatrix();
            var tileIndex = layoutUtil.getTileIndex();
            var bezels = syncService.getCurrentAppProperty('player.videowall.bezels');
            if (!tileIndex || !tileMatrix) {
                return;
            }

            // Split tile matrix
            tileMatrix = tileMatrix.split(',');
            if (tileMatrix.length != 2) {
                return;
            }

            // Parse to number
            tileIndex = parseInt(tileIndex);
            var tileMatrixX = parseInt(tileMatrix[0]);
            var tileMatrixY = parseInt(tileMatrix[1]);
            if (isNaN(tileIndex) || isNaN(tileMatrixX) || isNaN(tileMatrixY)) {
                return;
            }

            var totalTiles = tileMatrixX * tileMatrixY;
            // Check we have a valid number of total tiles
            if (totalTiles <= 1) {
                return;
            }

            // Tile index is zero based, but the property starts from 1
            tileIndex--;
            if (tileIndex >= totalTiles || tileIndex < 0) {
                tileIndex = 0;
            }

            var xTileOffset = tileIndex % tileMatrixX;
            var yTileOffset = Math.floor(tileIndex / tileMatrixX);

            // Update viewport info
            viewportInfo.xTiles = tileMatrixX;
            viewportInfo.yTiles = tileMatrixY;
            viewportInfo.tileIndex = tileIndex;
            viewportInfo.totalTiles = totalTiles;
            viewportInfo.xTileOffset = xTileOffset;
            viewportInfo.yTileOffset = yTileOffset;

            var tileLog = 'Videowall Tiling: \n\n';
            var tileOffset = 0;
            for (var i = 0; i < tileMatrixY; i++) {
                for (var y = 0; y < tileMatrixX; y++) {
                    tileLog += tileOffset == tileIndex ? '[X]' : '[ ]';
                    tileOffset++;
                }

                tileLog += '\n';
            }

            $log.log(tileLog);

            // Split bezels
            if (!bezels) {
                return;
            }

            bezels = bezels.split(',');
            if (bezels.length !== 2) {
                return;
            }

            var horizontalBezel = parseFloat(bezels[0]);
            var verticalBezel = parseFloat(bezels[1]);
            if (xTileOffset >= 0 && xTileOffset < tileMatrixX - 1) {
                viewportInfo.bezelComp.r = horizontalBezel;
            }

            if (xTileOffset <= tileMatrixX && xTileOffset > 0) {
                viewportInfo.bezelComp.l = horizontalBezel;
            }

            if (yTileOffset >= 0 && yTileOffset < tileMatrixY - 1) {
                viewportInfo.bezelComp.b = verticalBezel;
            }

            if (yTileOffset <= tileMatrixY && yTileOffset > 0) {
                viewportInfo.bezelComp.t = verticalBezel;
            }
        }

        function isElementInViewport(ep) {
            // No tile matrix setup
            if (viewportInfo.totalTiles == 1) {
                return true;
            }

            if ((ep.x >= .999 || ep.y >= .999) || // Is the element past the right or bottom of the viewport
                (ep.x + ep.width <= 0.001 || ep.y + ep.height <= 0.001)) { // Is the element before the left or top of the viewport
                return false;
            }

            return true;
        }

        // Apply a view transformation on element placements based on the video wall tile matrix
        function applyViewportPlacementTransform(elementPlacement) {
            // No tile matrix setup
            if (viewportInfo.totalTiles == 1) {
                return elementPlacement;
            }

            var transformed = $angular.copy(elementPlacement);
            // Scale element based on number of tiles
            transformed.width *= viewportInfo.xTiles;
            transformed.height *= viewportInfo.yTiles;

            // Offset transform based on number of tiles
            transformed.x *= viewportInfo.xTiles;
            transformed.x -= viewportInfo.xTileOffset;
            transformed.y *= viewportInfo.yTiles;
            transformed.y -= viewportInfo.yTileOffset;

            var getAdjustedSize = function(bezelSize, tileCount) {
                return (bezelSize * 2 / 100) * tileCount;
            };

            // Adjust for bezels
            if (viewportInfo.bezelComp.l) {
                transformed.x -= getAdjustedSize(viewportInfo.bezelComp.l, viewportInfo.xTiles);
                transformed.width += getAdjustedSize(viewportInfo.bezelComp.l, viewportInfo.xTiles);
            }

            if (viewportInfo.bezelComp.t) {
                transformed.height += getAdjustedSize(viewportInfo.bezelComp.t, viewportInfo.yTiles);
                transformed.y -= getAdjustedSize(viewportInfo.bezelComp.t, viewportInfo.yTiles);
            }

            if (viewportInfo.bezelComp.r && !viewportInfo.bezelComp.l) {
                transformed.width += getAdjustedSize(viewportInfo.bezelComp.r, viewportInfo.xTiles);
            }

            if (viewportInfo.bezelComp.b && viewportInfo.bezelComp.t) {
                transformed.height += getAdjustedSize(viewportInfo.bezelComp.b, viewportInfo.yTiles);
            }

            return transformed;
        }

        function determineOffscreenPlacement(placement, show) {
            placement = placement || {x: 0, y: 0, width: 100, height: 100};
            var res = {};
            var direction = 'auto';
            if (placement.transitionProperties) {
                direction = placement.transitionProperties[show ? 'TransitionIn' : 'TransitionOut'] || 'auto';
            }

            // Check if the platform support transitions
            if (isUsingSynchronizedPlayback || !useCssPlacement || !halPlatformService.getTransitionSupport(direction)) {
                logService.debug(logKey, 'Device doesn\'t support transition ' + direction);
                direction = 'none';
            }

            if (direction == 'auto') {
                var bottomEdge = false;
                var rightEdge = false;
                var getCenterPoint = function(length, placement) {
                    return length / 2 + placement;
                };

                // Find the center point of the widget
                var middleX = getCenterPoint(placement.width, placement.x);
                var middleY = getCenterPoint(placement.height, placement.y);
                // Determine distance from side edge
                if (middleX > 0.5) {
                    middleX = 1 - middleX; // We want the value to be 0 on the edge, N is distance away
                    rightEdge = true;
                }

                // Determine distance from side edge
                if (middleY > 0.5) {
                    middleY = 1 - middleY;
                    bottomEdge = true;
                }

                // Are we close to the left or right edge
                if (middleX < middleY) {
                    direction = rightEdge ? 'right' : 'left';
                } else {
                    direction = bottomEdge ? 'bottom' : 'top';
                }
            }

            switch (direction) {
                case 'left':
                    // Close to the left edge
                    res = {
                        left: -100,
                        top: placement.y * 100,
                    };
                    break;
                case 'right':
                    // Close to the right edge
                    res = {
                        left: 100,
                        top: placement.y * 100,
                    };
                    break;
                case 'top':
                    // Close to the top edge
                    res = {
                        left: placement.x * 100,
                        top: -100,
                    };
                    break;
                case 'bottom':
                    // Close to the bottom edge
                    res = {
                        left: placement.x * 100,
                        top: 100,
                    };
                    break;
                case 'zoomfade':
                    res = {
                        left: 0,
                        top: 0,
                        width: placement.width,
                        height: placement.height
                    };

                    // make the width & height bigger than canvas
                    while (res.width < 1 || res.height < 1) {
                        res.width += placement.width;
                        res.height += placement.height;
                    }

                    res.width *= 100;
                    res.height *= 100;
                    // make the zoom center
                    res.left = 0 - ((res.width - 100) / 2);
                    res.top = 0 - ((res.height - 100) / 2);
                    res.opacity = (show == true) ? 1 : 0;
                    break;
                case 'fade':
                    res = {
                        left: placement.x * 100,
                        top: placement.y * 100,
                        opacity: (show == true) ? 1 : 0
                    };
                    break;
                case 'center1':// center of the app
                    res = {
                        left: 50,
                        top: 50,
                        width: 0,
                        height: 0
                    };
                    break;
                case 'center': // center of the widget
                    res = {
                        left: (placement.x + (placement.width / 2)) * 100,
                        top: (placement.y + (placement.height / 2)) * 100,
                        width: 0,
                        height: 0
                    };
                    break;
                case 'none':
                default:
                    return undefined;
            }

            return res;
        }

        function animateWidget(wi, position, targetOpacity) {
            var transition = {};
            if (position.left != undefined && position.top != undefined) {
                transition.translateX = Math.round(position.left) + 'px';
                transition.translateY = Math.round(position.top) + 'px';
            }

            if (position.width != undefined && position.height != undefined) {
                transition.width = Math.round(position.width) + 'px';
                transition.height = Math.round(position.height) + 'px';
            }

            if (!dataUtil.isNullOrUndefined(targetOpacity)) {
                transition.opacity = targetOpacity;
            }

            // before we animate, we need to ensure that widgets use transforms for placement
            convertWidgetPlacementByTransform(wi.dom);

            $(wi.dom[0]).velocity(transition, {
                duration: 0,
                queue: false,
                complete: function() {
                    // Once the transition is complete, convert the placement into coodinates
                    convertWidgetPlacementByCoordinates($(wi.dom[0]));
                }
            });
        }

        function placeWidget(wi, position, opacity) {
            // Build a cSS transition
            if (position.left != undefined && position.top != undefined) {
                wi.dom[0].style.left = (position.left | 0) + 'px';
                wi.dom[0].style.top = (position.top | 0) + 'px';
            }

            if (position.width != undefined && position.height != undefined) {
                wi.dom[0].style.width = (position.width | 0) + 'px';
                wi.dom[0].style.height = (position.height | 0) + 'px';
            }

            if (opacity != undefined) {
                wi.dom[0].style.opacity = opacity;
            }
        }

        function animateVelocity(wi, transition, transitionProperties) {
            var promise = $q.defer();
            var animationTimeout;
            var useTransformPlacement = true;
            if (wi.dom[0].offsetLeft + 'px' == transition.translateX && wi.dom[0].offsetTop + 'px' == transition.translateY) {
                delete transition.translateX;
                delete transition.translateY;
                useTransformPlacement = false;
            }

            // before we animate using velocity, we need to ensure that widgets use transforms for placement
            if (useTransformPlacement) {
                convertWidgetPlacementByTransform(wi.dom);
            }

            transitionProperties.complete = function() {
                cancelAnimationTimeout();
                if (!wi || !wi.dom) {
                    logService.warn(logKey, 'Widget animation completed after element has been removed');
                    return;
                }

                // Once the transition is complete, convert the placement into coordinates
                if (useTransformPlacement) {
                    convertWidgetPlacementByCoordinates(wi.dom);
                }

                promise.resolve();
            };

            var cancelAnimationTimeout = function() {
                if (!animationTimeout) {
                    return;
                }

                $timeout.cancel(animationTimeout);
                animationTimeout = undefined;
            };

            var onTransitionHangs = function() {
                if (!animationTimeout) {
                    return;
                }

                cancelAnimationTimeout();
                promise.resolve();
            };

            var delayTransitionDuration = (transitionProperties.duration || 0) + 500;
            wi.dom.velocity(transition, transitionProperties);
            animationTimeout = $timeout(onTransitionHangs, delayTransitionDuration);
            return promise.promise;
        }

        function positionWidgets(layout) {
            resizeCanvas();
            if (layout.abort) {
                unloadLoadingLayout(layout);
                return $q.reject(layout);
            }

            var promise = $q.defer();
            var widgetsRequired = {};
            var layoutTransitionDuration = currentLayout && currentLayout.layoutId == layout.layoutId ? 0 : 700;
            var k;
            var elementPlacement;
            var widgetInstance;

            // Go through each widget in layout and position it
            for (k = 0; k < layout.elementPlacements.length; k++) {
                elementPlacement = layout.elementPlacements[k];
                // Apply viewport transform
                elementPlacement = applyViewportPlacementTransform(elementPlacement);

                // Do we have a widget instance for this element
                widgetInstance = findInstance(elementPlacement.elementId);
                if (dataUtil.isNullOrUndefined(widgetInstance)) {
                    logService.warn(logKey, 'Unable to position widget, no instance found: ' + elementPlacement.elementId);
                    continue; // We didnt find a widget instance, must be a bug somewhere
                }

                // Store the widgets that we need
                widgetsRequired[elementPlacement.elementId] = true;
                // Confirm its ready to be positioned
                if (widgetInstance.status.toLowerCase() != 'unfrozen') {
                    logService.warn(logKey, 'Unable to position widget, not in the unfrozen state: ' + elementPlacement.elementId + ' ' + widgetInstance.status);
                    continue; // This should never be the case...
                }

                // Set visibility and z order
                var currentDisplay = widgetInstance.dom[0].style.display;
                widgetInstance.dom[0].style.display = '';
                widgetInstance.dom[0].style.zIndex = 100 + k;
                widgetInstance.placement = elementPlacement;
                var onWidgetPositionedOnScreen = function(res) {
                    if (!res.targetPosition || !layoutEditorApi) {
                        return;
                    }

                    layoutEditorApi.updateSelectedWidgetPosition(res.widgetInstance.id, res.targetPosition);
                };

                var onWidgetPositionedOffScreen = function(res) {
                    positionWidgetOnScreen(res.widgetInstance,
                        res.needsAnimating,
                        res.width,
                        res.height,
                        layoutTransitionDuration).then(onWidgetPositionedOnScreen);
                };

                // Position widget off screen first
                positionWidgetOffScreen(widgetInstance,
                    currentDisplay).then(onWidgetPositionedOffScreen);
            }

            // Go through widgets in layout and position them behind the ones coming in
            if (currentLayout) {
                var allNewWidgets = true;
                for (k = 0; k < currentLayout.elementPlacements.length; k++) {
                    elementPlacement = currentLayout.elementPlacements[k];
                    if (widgetsRequired[elementPlacement.elementId]) {
                        allNewWidgets = false;
                        break;
                    }
                }

                // If no widgets from current layout exist on the new, ensure the z index of new widgets are higher than the last
                if (allNewWidgets) {
                    for (k = 0; k < currentLayout.elementPlacements.length; k++) {
                        elementPlacement = currentLayout.elementPlacements[k];
                        // Do we have a widget instance for this element
                        widgetInstance = findInstance(elementPlacement.elementId);
                        if (dataUtil.isNullOrUndefined(widgetInstance)) {
                            continue; // We didnt find a widget instance, must be a bug somewhere
                        }

                        // Set its z index so that its guaratneed to be above the new widgets transitioning in
                        widgetInstance.dom[0].style.zIndex = widgetInstance.dom[0].style.zIndex - 100;
                    }
                }
            }

            // Go through widgets not in the layout and remove them
            var widgetsToHideDisplay = [];
            for (var i = 0; i < widgetInstances.length; i++) {
                widgetInstance = widgetInstances[i];
                // Check of this widget is required
                if (widgetsRequired[widgetInstance.id]) {
                    // Required widget
                    continue;
                }

                var widgetInstancePlacement = widgetInstance.placement;
                widgetInstance.placement = undefined;
                // if widget is not visible, skip
                if (!widgetInstance.dom || widgetInstance.dom[0].style.display === 'none') {
                    continue;
                }

                // Position  offscreen
                var offScreenPlacement = determineOffscreenPlacement(widgetInstancePlacement, false);

                // if we don't have offscreen placement, set element to display none directly
                if (dataUtil.isNullOrUndefined(offScreenPlacement)) {
                    widgetInstance.dom[0].style.display = 'none';
                    continue;
                }

                widgetsToHideDisplay.push(widgetInstance);
                if (isUsingSynchronizedPlayback || !useCssPlacement) {
                    placeWidget(widgetInstance, {
                        left: (offScreenPlacement.left / 100) * canvasSize.w,
                        top: (offScreenPlacement.top / 100) * canvasSize.h,
                    }, 0);
                    continue;
                }

                var targetWidth = dataUtil.isNullOrUndefined(offScreenPlacement.width) ? undefined : (offScreenPlacement.width / 100) * canvasSize.w;
                var targetHeight = dataUtil.isNullOrUndefined(offScreenPlacement.height) ? undefined : (offScreenPlacement.height / 100) * canvasSize.h;
                var exitTransition = {
                    translateX: Math.round((offScreenPlacement.left / 100) * canvasSize.w) + 'px',
                    translateY: Math.round((offScreenPlacement.top / 100) * canvasSize.h) + 'px'
                };

                if (!dataUtil.isNullOrUndefined(offScreenPlacement.opacity)) {
                    exitTransition.opacity = offScreenPlacement.opacity;
                }

                if (!dataUtil.isNullOrUndefined(targetWidth)) {
                    exitTransition.width = targetWidth;
                }

                if (!dataUtil.isNullOrUndefined(targetHeight)) {
                    exitTransition.height = targetHeight;
                }

                animateVelocity(widgetInstance, exitTransition, {
                    duration: layoutTransitionDuration,
                    easing: animationEaseType
                });
            }

            previousBackgroundColor = layout.backgroundColor;
            setBackgroundColor(layout.backgroundColor);

            // Resolve the widget positioning after the transitions have completed
            var resolveTimeoutDuration = layoutTransitionDuration ? layoutTransitionDuration + 100 : 0;
            $timeout(function() {
                // we clear all widget transforms
                clearWidgetTransforms();
                // set widgets to display none
                for (var i = 0; i < widgetsToHideDisplay.length; i++) {
                    if (!isWidgetInstanceValid(widgetsToHideDisplay[i])) {
                        continue;
                    }

                    widgetsToHideDisplay[i].dom[0].style.display = 'none';
                }

                widgetsToHideDisplay = [];
                // For now resolve, once transitions are complete
                promise.resolve(layout);
            }, resolveTimeoutDuration);

            logService.trace(logKey, 'All widgets positioned for layout: ' + layout.layoutId);
            return promise.promise;
        }

        function positionWidgetOffScreen(widgetInstance, currentDisplay) {
            var currentWidth = widgetInstance.dom[0].style.width;
            var currentHeight = widgetInstance.dom[0].style.height;
            var getResolveObject = function(width, height, needsAnimating) {
                return {
                    width: width,
                    height: height,
                    needsAnimating: needsAnimating,
                    widgetInstance: widgetInstance
                };
            };

            // Check is the widget already visible
            if (currentDisplay !== 'none') {
                return $q.resolve(getResolveObject(currentWidth, currentHeight, false));
            }

            // Determine offscreen placement
            var elementPlacement = widgetInstance.placement;
            var offScreenPlacement = determineOffscreenPlacement(elementPlacement, true);
            if (!offScreenPlacement) {
                return $q.resolve(getResolveObject(currentWidth, currentHeight, false));
            }

            // Position starting point offscreen if it has a offscreen placement position
            var placementWidth = dataUtil.isNullOrUndefined(offScreenPlacement.width) ? elementPlacement.width : (offScreenPlacement.width / 100);
            var width = Math.round(placementWidth * canvasSize.w);
            var placementHeight = dataUtil.isNullOrUndefined(offScreenPlacement.height) ? elementPlacement.height : (offScreenPlacement.height / 100);
            var height = Math.round(placementHeight * canvasSize.h);

            // if there's setting to slowly fade the element into view, set the initial opacity to 0
            var opacity = offScreenPlacement.opacity == 1 ? 0 : elementPlacement.opacity;
            var pos = {
                left: Math.round((offScreenPlacement.left / 100) * canvasSize.w),
                top: Math.round((offScreenPlacement.top / 100) * canvasSize.h),
                width: width,
                height: height,
            };
            if (isUsingSynchronizedPlayback || !useCssPlacement) {
                placeWidget(widgetInstance, pos, opacity);
                return $q.resolve(getResolveObject(currentWidth, currentHeight, true));
            }

            var promise = $q.defer();
            animateVelocity(widgetInstance, {
                translateX: pos.left + 'px',
                translateY: pos.top + 'px',
                width: pos.width + 'px',
                height: pos.height + 'px',
                opacity: opacity
            }, {duration: 0}).then(function() {
                promise.resolve(getResolveObject(pos.width, pos.height, true));
            });
            return promise.promise;
        }

        function positionWidgetOnScreen(widgetInstance, needsAnimating, currentWidgetWidth, currentWidgetHeight, layoutTransitionDuration) {
            var elementPlacement = widgetInstance.placement;
            var targetPosition = {
                left: Math.round(elementPlacement.x * canvasSize.w),
                top: Math.round(elementPlacement.y * canvasSize.h),
                width: Math.round(elementPlacement.width * canvasSize.w),
                height: Math.round(elementPlacement.height * canvasSize.h)
            };

            var getResolveObject = function(position) {
                return {widgetInstance: widgetInstance, targetPosition: position};
            };

            // CSS translate not supported
            if (isUsingSynchronizedPlayback || !useCssPlacement) {
                // Move object to its final position
                placeWidget(widgetInstance, targetPosition, elementPlacement.opacity);
                return $q.resolve(getResolveObject(targetPosition));
            }

            if (!needsAnimating) {
                animateWidget(widgetInstance, targetPosition, elementPlacement.opacity);
                return $q.resolve(getResolveObject(targetPosition));
            }

            var targetWidth = targetPosition.width + 'px';
            var targetHeight = targetPosition.height + 'px';
            var targetLeft = targetPosition.left + 'px';
            var targetTop = targetPosition.top + 'px';
            var targetOpacity = dataUtil.getValidValue(elementPlacement.opacity, '').toString();
            // if placement is the same as previous, don't do anything (e.g. loading the same layout)
            var currentStyle = widgetInstance.dom[0].style;
            if (currentStyle.left == targetLeft &&
                currentStyle.top == targetTop &&
                currentStyle.width == targetWidth &&
                currentStyle.height == targetHeight &&
                currentStyle.opacity == targetOpacity) {
                return $q.resolve(getResolveObject());
            }

            var enterTransition = {
                translateX: targetLeft,
                translateY: targetTop,
                opacity: targetOpacity
            };
            if (currentWidgetWidth != targetPosition.width) {
                enterTransition.width = targetWidth;
            }

            if (currentWidgetHeight != targetPosition.height) {
                enterTransition.height = targetHeight;
            }

            var promise = $q.defer();
            animateVelocity(
                widgetInstance,
                enterTransition, {
                duration: layoutTransitionDuration,
                easing: animationEaseType
            }).then(function() {
                promise.resolve(getResolveObject(targetPosition));
            });
            return promise.promise;
        }

        function onBackmostVideoLayerEventFired(event, state) {
            var color = previousBackgroundColor;
            // reset the forced flag
            forcedBackgroundColor = undefined;
            if (state.enable) {
                // set the color to forced flag to ensure any layout load will use this color
                forcedBackgroundColor = SYSTEM_DATA.LAYOUT_BACKGROUND_TRANSPARENT_COLOR;
                color = forcedBackgroundColor;
            }

            setBackgroundColor(color);
        }

        function setCanvasBackground(color) {
            if (dataUtil.isNullOrUndefined(color)) {
                return;
            }

            canvasContainer.css('backgroundColor', color);
        }

        function setBackgroundColor(hexColor) {
            // Set background color for layout, if none, just set to black
            var colorUtil = new ColorUtil();
            var backgroundRGBA = colorUtil.convertHexToRGBA(hexColor) || {};
            if (halPlatformService.isTransparentRequired(backgroundRGBA.c)) {
                setCanvasBackground('transparent');
                pageUtil.setAppBackgroundColor('transparent');
                return;
            }

            canvasContainer.velocity({
                backgroundColor: backgroundRGBA.c || appBackgroundColor || DEFAULT_COLOR.BACKGROUND,
                backgroundColorAlpha: dataUtil.isNullOrUndefined(backgroundRGBA.a) ?
                    DEFAULT_COLOR.ALPHA : backgroundRGBA.a
            }, {duration: 700});
        }

        function convertWidgetPlacementByTransform(element) {
            var matrix = $(element).css('transform');
            if (matrix.length == 0 || matrix == 'none') {
                var x = $(element).css('left');
                var y = $(element).css('top');
                // Reset coords to zero
                $(element)[0].style.left = '0px';
                $(element)[0].style.top = '0px';
                // Set transform
                $(element).css('transform', 'translateXY(' + x + 'px' + ', ' + y + 'px)');
            }
        }

        function convertWidgetPlacementByCoordinates(element) {
            var matrix = $(element).css('transform');
            if (matrix.length > 0 && matrix != 'none') {
                var translateVal = matrix.match(/-?[\d\.]+/g); // eslint-disable-line no-useless-escape
                var x = translateVal[4];
                var y = translateVal[5];
                // Reset transform
                $(element).css('transform', '');
                // Set coords
                $(element).css('left', x + 'px');
                $(element).css('top', y + 'px');
            }
        }

        function clearWidgetTransforms() {
            var widgetElements = $($element.children()[0]).children();
            for (var i = 0; i < widgetElements.length; i++) {
                var widgetElement = widgetElements[i];
                // convert each widget placement to use coords
                convertWidgetPlacementByCoordinates(widgetElement);
            }
        }

        function wakeupWidgets(layout) {
            var deferred = $q.defer();
            if (layout.abort) {
                unloadLoadingLayout(layout);
                deferred.reject(layout);
                return deferred.promise;
            }

            var promises = [];
            var statusToWakeup = ['ready', 'frozen', 'sleep'];
            // Check which widgets need to be wokenup
            for (var k = 0; k < layout.elementPlacements.length; k++) {
                var e = layout.elementPlacements[k];
                // Do we have a widget instance for this element
                var widgetInstance = findInstance(e.elementId);
                // We didnt find a widget instance, must be a bug somewhere
                if (dataUtil.isNullOrUndefined(widgetInstance)) {
                    logService.warn(logKey, 'Unable to unfreeze widget, no instance found: ' + e.elementId);
                    continue;
                }

                var status = widgetInstance.status.toLowerCase();
                // dont process if
                // wake up is not implemented OR
                // status is not ready, frozen and sleep
                if (!widgetInstance.api.wakeup || statusToWakeup.indexOf(status) == -1) {
                    continue;
                }

                if (widgetInstance.api.setLayout) {
                    widgetInstance.api.setLayout(layout);
                }

                promises.push(widgetInstance.api.wakeup());
            }

            var asyncTimeout = $timeout(function() {
                deferred.resolve(layout);
            }, loadTimeoutDuration);

            var cancelAsyncTimeout = function() {
                TimeoutUtil.cancel(asyncTimeout);
                asyncTimeout = undefined;
            };

            // Wait until all widgets are wokenup
            $q.allSettled(promises).then(function() {
                cancelAsyncTimeout();
                logService.trace(logKey, 'All widgets wokenup for layout: ' + layout.layoutId + ' ' + promises.length + ' frozen');
                deferred.resolve(layout);
            }, function(err) {
                cancelAsyncTimeout();
                // We still resovle if a few widgets didnt wokenup
                deferred.resolve(layout);
            });

            return deferred.promise;
        }

        function unFreezeWidgets(layout) {
            var deferred = $q.defer();
            if (layout.abort) {
                unloadLoadingLayout(layout);
                deferred.reject(layout);
                return deferred.promise;
            }

            var promises = [];
            // Check which widgets need to be unfrozen
            for (var k = 0; k < layout.elementPlacements.length; k++) {
                var e = layout.elementPlacements[k];
                // Do we have a widget instance for this element
                var widgetInstance = findInstance(e.elementId);
                if (dataUtil.isNullOrUndefined(widgetInstance)) {
                    logService.warn(logKey, 'Unable to unfreeze widget, no instance found: ' + e.elementId);
                    continue; // We didnt find a widget instance, must be a bug somewhere
                }

                switch (widgetInstance.status.toLowerCase()) {
                    case 'ready':
                        widgetInstance.status = 'unfrozen';
                        promises.push(widgetInstance.api.unFreeze());
                        break;
                    case 'frozen':
                    case 'sleep':
                        widgetInstance.status = 'unfrozen';
                        promises.push(widgetInstance.api.unFreeze());
                        break;
                }
            }

            var asyncTimeout = $timeout(function() {
                logService.warn(logKey, 'Not all widgets unfronzen for layout: ' + layout.layoutId);
                deferred.resolve(layout);
            }, loadTimeoutDuration);

            var cancelAsyncTimeout = function() {
                TimeoutUtil.cancel(asyncTimeout);
                asyncTimeout = undefined;
            };

            // Wait until all widgets are unfrozen
            $q.allSettled(promises).then(function() {
                cancelAsyncTimeout();
                logService.trace(logKey, 'All widgets unfrozen for layout: ' + layout.layoutId + ' ' + promises.length + ' frozen');
                // Widgets created
                deferred.resolve(layout);
            }, function(err) {
                cancelAsyncTimeout();
                // We still resovle if a few widgets didnt unfreeze
                logService.warn(logKey, 'Not all widgets unfronzen for layout: ' + layout.layoutId);
                deferred.resolve(layout);
            });

            return deferred.promise;
        }

        function sleepWidgets(layout) {
            var deferred = $q.defer();
            if (layout.abort) {
                unloadLoadingLayout(layout);
                deferred.reject(layout);
                return deferred.promise;
            }

            var promises = [];

            var widgetsRequired = {};
            // Build required widgets lookup
            for (var k = 0; k < layout.elementPlacements.length; k++) {
                var e = layout.elementPlacements[k];
                widgetsRequired[e.elementId] = true;
            }

            // Go through widgets and sleep the ones not needed
            for (var i = 0; i < widgetInstances.length; i++) {
                var wi = widgetInstances[i];

                // Check if this widget is required
                if (widgetsRequired[wi.id] == true) {
                    // Required widget
                    continue;
                }

                // Sleep this widget as its not needed.
                promises.push(sleepWidget(wi));
            }

            // Wait until all widgets are a sleep
            $q.allSettled(promises).then(function() {
                logService.trace(logKey, 'All widgets put to sleep for layout: ' + layout.layoutId + ' ' + promises.length + ' frozen');
                // Widgets created
                deferred.resolve(layout);
            }, function(err) {
                logService.warn(logKey, 'Not all widgets put to sleep for layout: ' + layout.layoutId);
                // We still resovle if a few widgets didnt unfreeze
                deferred.resolve(layout);
            });

            return deferred.promise;
        }

        function changeWidgetState(wi, stateToCheck, methodToCall, targetState) {
            if (!wi || (wi.status || '').toLowerCase() != stateToCheck || !wi.api || !wi.api[methodToCall]) {
                return $q.resolve();
            }

            var promise = $q.defer();
            wi.status = targetState;
            var resolve = function() {
                TimeoutUtil.cancel(asyncTimeout);
                promise.resolve();
            };

            var asyncTimeout = $timeout(resolve, 5000);
            wi.api[methodToCall]().then(resolve, resolve);

            return promise.promise;
        }

        function sleepWidget(wi) {
            return changeWidgetState(wi, 'frozen', 'sleep', 'sleep');
        }

        function freezeWidgets(layout) {
            if (layout.abort) {
                return $q.reject(layout);
            }

            var deferred = $q.defer();
            var promises = [];
            var widgetsRequired = {};
            // Build required widgets lookup
            for (var k = 0; k < layout.elementPlacements.length; k++) {
                var placement = layout.elementPlacements[k];
                widgetsRequired[placement.elementId] = true;
            }

            // Go through widgets and freeze the ones not needed
            for (var i = 0; i < widgetInstances.length; i++) {
                var wi = widgetInstances[i];

                // If we require this widget, skip freeze
                if (widgetsRequired[wi.id] == true) {
                    continue;
                }

                // Freeze this widget as its not needed.
                promises.push(freezeWidget(wi));
            }

            // Wait until all widgets are unfrozen
            $q.allSettled(promises).then(function() {
                logService.trace(logKey, 'All widgets frozen for layout: ' + layout.layoutId + ' ' + promises.length + ' frozen');
                // Widgets created
                deferred.resolve(layout);
            }, function(err) {
                logService.warn(logKey, 'Not all widgets fronzen for layout: ' + layout.layoutId);
                // We still resovle if a few widgets didnt unfreeze
                deferred.resolve(layout);
            });

            return deferred.promise;
        }

        function freezeWidget(wi) {
            return changeWidgetState(wi, 'unfrozen', 'freeze', 'frozen');
        }

        function findInstance(id) {
            // Enumerate through current widget instances
            for (var i = 0; i < widgetInstances.length; i++) {
                var wi = widgetInstances[i];
                // Found the widget in the instance cache
                if (wi.id == id) {
                    return wi;
                }
            }

            return undefined;
        }

        $scope.onWidgetStartMove = function() {
            isMovingWidget = true;
        };

        $scope.onWidgetStopMove = function() {
            isMovingWidget = false;
        };

        $scope.onWidgetSelected = function(event) {
            selectedWidgetId = event.id;
            observable.notifyObservers({
                type: LAYOUT_EVENT.WIDGET_SELECTED,
                id: event.id
            });
        };

        $scope.onWidgetDoubleClicked = function(event) {
            observable.notifyObservers({
                type: LAYOUT_EVENT.WIDGET_DOUBLE_CLICKED,
                id: event.id
            });
        };

        $scope.onContentLoaded = function(content) {
            observable.notifyObservers({
                type: LAYOUT_EVENT.ON_CONTENT_LOADED,
                content: content
            });
        };

        $scope.onWidgetPositionChanged = function(event) {
            var newPlacement = {
                x: event.position.x / canvasSize.w,
                y: event.position.y / canvasSize.h,
                width: event.position.width / canvasSize.w,
                height: event.position.height / canvasSize.h
            };
            observable.notifyObservers({
                type: LAYOUT_EVENT.WIDGET_POSITION_CHANGED,
                id: event.id,
                placement: newPlacement
            });
            // update internal collection position
            if (!currentLayout || !currentLayout.elementPlacements || !currentLayout.elementPlacements.length) {
                return;
            }

            for (var i = 0; i < currentLayout.elementPlacements.length; i++) {
                var placement = currentLayout.elementPlacements[i];
                if (placement.elementId != event.id) {
                    continue;
                }

                for (var keyPlacement in newPlacement) {
                    placement[keyPlacement] = newPlacement[keyPlacement];
                }

                break;
            }
        };

        function createWidget(wi) {
            var deferred = $q.defer();
            // Store the deferred promise so we can resolve or reject late
            wi.initPromise = deferred;

            var onWidgetRetrieved = function(w) {
                wi.widget = w.widget;
                wi.datasource = w.datasource;
                wi.scope = $scope.$new(); // create a new scope for this widget
                var template = w.template; // "<div style='position:absolute; display:none; width:100px; height:100px; backface-visibility:hidden'>" + w.template + "</div>";
                var el = $compile(template)(wi.scope);
                wi.dom = $(el.children()[0]);
                wi.element = el;
                wi.onclick = function(e) {
                    $scope.onWidgetClick(wi.widget.elementId);
                };

                wi.dom.click(wi.onclick);
                wi.dom[0].style.position = 'absolute';
                wi.dom[0].style.display = 'none';
                var placement = {
                    left: -300,
                    top: -300,
                    width: 200,
                    height: 200
                };
                // if we're currently loading a layout, use the element placement for that layout to create the widget
                if (layoutLoading) {
                    for (var i = 0; i < layoutLoading.elementPlacements.length; i++) {
                        var elementPlacement = layoutLoading.elementPlacements[i];
                        // Apply viewport transform
                        elementPlacement = applyViewportPlacementTransform(elementPlacement);

                        if (elementPlacement.elementId != wi.id) {
                            continue;
                        }

                        var offscreenPlacement = determineOffscreenPlacement(elementPlacement, false);
                        if (!dataUtil.isNullOrUndefined(offscreenPlacement)) {
                            placement = offscreenPlacement;
                            placement.width = canvasSize.w * (placement.width ? (placement.width / 100) : elementPlacement.width);
                            placement.height = canvasSize.h * (placement.height ? (placement.height / 100) : elementPlacement.height);
                            placement.left = (placement.left / 100) * canvasSize.w;
                            placement.top = (placement.top / 100) * canvasSize.h;
                        }

                        break;
                    }
                }

                placeWidget(wi, placement);
                // Add the widget directive to the element
                $($element.children()[0]).append(el.children()[0]);
            };

            var onWidgetFail = function(err) {
                wi.status = 'fail';
                deferred.reject(err);
            };

            signsWidgetService.retrieveWidget(wi.id).then(onWidgetRetrieved, onWidgetFail);
            return deferred.promise;
        }

        // Updates an existing widget's data and configuration
        function updateWidget(wi) {
            var deferred = $q.defer();

            var onWidgetRetrieved = function(w) {
                wi.widget = w.widget;
                wi.datasource = w.datasource;

                contentStateService.updateZone(wi);

                // if driver doesn't support script transparent update, don't set config
                initializeWidget(wi, !halPlatformService.getFeatureSupport('support.script.transparent.update'))
                    .then(deferred.resolve, onWidgetFail);
            };

            var onWidgetFail = function(err) {
                wi.status = 'fail';
                deferred.reject(err);
            };

            signsWidgetService.retrieveWidget(wi.id).then(onWidgetRetrieved, onWidgetFail);
            return deferred.promise;
        }

        // Enumerates through the config and resolves any expressions that are based on a property e.g. {$property.player.name}
        function resolvePropertyExpressions(config) {
            var resolvedConfig = {};
            for (var key in config) {
                resolvedConfig[key] = scriptingService.resolvePropertyExpression(config[key]);
            }

            return resolvedConfig;
        }

        // Sets the configuration for a widget instance
        function setWidgetConfig(wi) {
            var deferred = $q.defer();
            // Resolved the properties
            var config = resolvePropertyExpressions(wi.widget.properties);

            var onContainerConfig = function() {
                // Resolved the properties
                wi.api.setConfiguration(config).then(onWidgetConfig, onWidgetConfigFail);
            };

            var onContainerConfigFail = function(err) {
                logService.error(logKey, 'widget container failed to set configuration: ' + wi.id);
                wi.api.setConfiguration(config).then(onWidgetConfig, onWidgetConfigFail);
            };

            var cancelAsyncTimeout = function() {
                TimeoutUtil.cancel(asyncTimeout);
                asyncTimeout = undefined;
            };

            var onWidgetConfig = function() {
                cancelAsyncTimeout();
                deferred.resolve();
                // Widget configuration now set
            };

            var onWidgetConfigFail = function(err) {
                cancelAsyncTimeout();
                logService.error(logKey, 'widget failed to set configuration: ' + wi.id);
                deferred.reject(err);
            };

            var asyncTimeout = $timeout(onWidgetConfigFail, 5000);
            wi.containerApi.setConfiguration(config).then(onContainerConfig, onContainerConfigFail);
            return deferred.promise;
        }

        // Sets the configuration for a widget instance
        function setWidgetDatasource(wi) {
            // Check if the datsource has been updated.
            var newHash;
            var newBackground;
            if (wi.datasource && wi.datasource.result) {
                newHash = wi.datasource.result.hash;
                newBackground = wi.datasource.result.widgetBackgroundImage;
            }

            if (wi.datasourceHash == newHash && wi.backgroundImage == newBackground) {
                // No changes in the datasource, just resolve
                return $q.resolve();
            }

            var start = new Date().getTime();
            var deferred = $q.defer();
            var processWidgetDatasourceLoaded = function(method, message) {
                var currentTime = new Date().getTime();
                logService[method](logKey, 'widget datasource ' + (wi.id) + ' ' + message + ' in ' + (currentTime - start) + 'ms');
                TimeoutUtil.cancel(asyncTimeout);
            };

            var onWidgetDatasource = function() {
                processWidgetDatasourceLoaded('trace', 'loaded');
                deferred.resolve();
            };

            var onWidgetDatasourceFail = function(err) {
                processWidgetDatasourceLoaded('warn', 'failed');
                deferred.reject(err);
            };

            var onContainerDatasourceSet = function() {
                wi.api.setDatasource(wi.datasource).then(onWidgetDatasource, onWidgetDatasourceFail);
            };

            var setWidgetDataSourceTimeout = halPlatformService.getFeatureSupport('widget.datasource.maxtimeout');
            var asyncTimeout = $timeout(function() {
                logService.error(logKey, 'unable to load datasource within ' + setWidgetDataSourceTimeout + 'ms: ' + wi.id);
                onWidgetDatasourceFail();
            }, setWidgetDataSourceTimeout);

            // Update the Datasource hash
            wi.datasourceHash = newHash;
            wi.backgroundImage = newBackground;
            wi.containerApi.setDatasource(wi.datasource).then(onContainerDatasourceSet, onContainerDatasourceSet);
            return deferred.promise;
        }

        function initializeWidget(wi, ignoreSetConfig) {
            var deferred = $q.defer();
            var onDataSource = function() {
                wi.status = wi.status.toLowerCase() != 'unfrozen' ? 'ready' : 'unfrozen';
                deferred.resolve(wi);
                wi.initPromise.resolve(wi);
            };

            var onDataSourceFail = function(err) {
                wi.status = 'fail';
                logService.error(logKey, 'widget failed to initialise due to datasource : ' + wi.id);
                wi.initPromise.reject(wi);
                deferred.reject(wi);
            };

            var onWidgetConfig = function() {
                setWidgetDatasource(wi).then(onDataSource, onDataSourceFail);
            };

            var onWidgetConfigFail = function(err) {
                wi.status = 'fail';
                logService.error(logKey, 'widget failed to initialise due to config : ' + wi.id);
                wi.initPromise.reject(wi);
                deferred.reject(wi);
            };

            if (ignoreSetConfig) {
                onWidgetConfig();
            } else {
                setWidgetConfig(wi).then(onWidgetConfig, onWidgetConfigFail);
            }

            return deferred.promise;
        }

        function moveSelectedWidget(direction, value) {
            return layoutEditorApi.moveSelectedWidget(direction, value);
        }

        function scaleSelectedWidget(option) {
            return layoutEditorApi.scaleSelectedWidget(option);
        }

        function enableWidgetEditor(enable) {
            layoutEditorApi.enableWidgetEditor(enable);
        }

        function isWidgetEditorEnabled() {
            return layoutEditorApi.isWidgetEditorEnabled();
        }

        function selectWidget(widgetId) {
            selectWidgetOnLayoutEditor(findInstance(widgetId));
        }

        function setEditorScale(scale) {
            editorScale = scale || EDITOR_SETTINGS.DEFAULT_EDITOR_SCALE;
            if (gridEditorApi) {
                gridEditorApi.setVisualScale(editorScale);
            }

            if (layoutEditorApi) {
                layoutEditorApi.setVisualScale(editorScale);
            }
        }

        // Scope Methods
        //* *********************************************
        $scope.onWidgetClick = function(widgetId) {
            if ($scope.isEditingMode) {
                logService.debug(logKey, 'Widget clicking is not supported in editor mode');
                return;
            }

            // Find the widget instance
            if (findInstance(widgetId)) {
                scriptingService.raiseEvent('Element_' + widgetId, 'onclick');
                return;
            }

            logService.warn(logKey, 'Could not match the widget id on click');
        };

        $scope.onWidgetApiReady = function(api, widgetId) {
            // Find the widget instance
            var widgetInstance = findInstance(widgetId);
            if (widgetInstance) {
                widgetInstance.api = api;
                // Register the widget as a target
                scriptingService.registerTarget('element_' + widgetInstance.widget.elementId, api);
                return;
            }

            logService.warn(logKey, 'Could not match the widget id on api ready');
        };

        $scope.onWidgetContainerApiReady = function(api, widgetId) {
            // Find the widget instance
            var widgetInstance = findInstance(widgetId);
            if (widgetInstance) {
                widgetInstance.containerApi = api;
                initializeWidget(widgetInstance);
                return;
            }

            logService.warn(logKey, 'Could not match the widget container id on api ready');
        };

        $scope.onMouseOver = function(widgetId) {
            var widgetInstance = findInstance(widgetId);
            if (!isContentNavigationEnabled(widgetInstance) ||
                !widgetInstance ||
                !widgetInstance.containerApi ||
                !widgetInstance.containerApi.showNavigationButtons) {
                return;
            }

            widgetInstance.containerApi.showNavigationButtons();
        };

        $scope.onMouseLeave = function(widgetId) {
            var widgetInstance = findInstance(widgetId);
            if (!widgetInstance ||
                !widgetInstance.containerApi ||
                !widgetInstance.containerApi.hideNavigationButtons) {
                return;
            }

            widgetInstance.containerApi.hideNavigationButtons();
        };

        $scope.onPreviousContentClicked = function(widgetId) {
            var widgetInstance = findInstance(widgetId);
            if (!isContentNavigationEnabled(widgetInstance) ||
                !widgetInstance ||
                !widgetInstance.api.previousContent) {
                return;
            }

            widgetInstance.api.previousContent();
        };

        $scope.onNextContentClicked = function(widgetId) {
            var widgetInstance = findInstance(widgetId);
            if (!isContentNavigationEnabled(widgetInstance) ||
                !widgetInstance ||
                !widgetInstance.api.nextContent) {
                return;
            }

            widgetInstance.api.nextContent();
        };

        $scope.onNoContentAvailable = function(widgetId) {
            var widgetInstance = findInstance(widgetId);
            contentStateService.updateContentState({
                widgetId: widgetId,
                content: widgetInstance,
                state: CONTENT_STATE_EVENT.NO_CONTENT_AVAILABLE
            });
            if (!isNoContentMessageEnabled(widgetInstance)) {
                return;
            }

            widgetInstance.containerApi.showNoContentMessage(
                embedProfileService.getNoContentMessage());
        };

        $scope.onContentAvailable = function(widgetId) {
            var widgetInstance = findInstance(widgetId);
            contentStateService.updateContentState({
                widgetId: widgetId,
                content: widgetInstance,
                state: CONTENT_STATE_EVENT.CONTENT_AVAILABLE
            });
            if (!isNoContentMessageEnabled(widgetInstance)) {
                return;
            }

            widgetInstance.containerApi.hideNoContentMessage();
        };

        function isNoContentMessageEnabled(widgetInstance) {
            return embedProfileService.isNoContentMessageEnabled() &&
                widgetInstance.containerApi &&
                widgetInstance.containerApi.showNoContentMessage &&
                widgetInstance.containerApi.hideNoContentMessage;
        }

        function isContentNavigationEnabled(widgetInstance) {
            return embedProfileService.isContentNavigationEnabled() &&
                widgetInstance &&
                widgetInstance.api &&
                widgetInstance.api.isNavigationEnabled &&
                widgetInstance.api.isNavigationEnabled();
        }

        function subscribe(callback) {
            observable.registerObserver(callback);
        }

        function unsubscribe(callback) {
            observable.removeObserver(callback);
        }

        $scope.onLayoutEditorApiReady = function(api) {
            layoutEditorApi = api;
            layoutEditorApi.initialize();
        };

        function setGridConfiguration(config) {
            layoutEditorApi.setGridConfiguration(config);
        }

        $scope.onGridConfigured = function(config) {
            if (!gridEditorApi) {
                return $q.resolve();
            }

            config.visualScale = editorScale;
            return gridEditorApi.setGridConfiguration(config);
        };

        $scope.onGridEditorApiReady = function(api) {
            gridEditorApi = api;
        };

        $scope.getAllDisplayedWidgetsPosition = function() {
            if (!currentLayout) {
                return [];
            }

            var displayedWidgets = [];
            for (var k = currentLayout.elementPlacements.length - 1; k >= 0; k--) {
                var elementPlacement = currentLayout.elementPlacements[k];
                // Apply viewport transform
                elementPlacement = applyViewportPlacementTransform(elementPlacement);

                // Do we have a widget instance for this element
                var widgetInstance = findInstance(elementPlacement.elementId);
                var dom = widgetInstance.dom;

                displayedWidgets.push({
                    instance: widgetInstance,
                    position: {
                        width: dom.outerWidth(),
                        height: dom.outerHeight(),
                        x: dom.offset().left,
                        y: dom.offset().top
                    }
                });
            }

            return displayedWidgets;
        };

        function selectWidgetOnLayoutEditor(widgetInstance) {
            if (!layoutEditorApi) {
                return;
            }

            return layoutEditorApi.setSelectedWidget(widgetInstance);
        }

        init();
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    // Reads file into string
    app.factory('layoutUtil', ["syncService", "dataUtil", function(syncService, dataUtil) {
        function getTileIndex() {
            return syncService.getCurrentAppProperty('player.videowall.tileid');
        }

        function getTileMatrix() {
            return syncService.getCurrentAppProperty('player.videowall.tilematrix');
        }

        function isVideoWallMode() {
            return !dataUtil.isNullOrUndefined(getTileIndex()) && !dataUtil.isNullOrUndefined(getTileMatrix());
        }

        function findLayoutTarget(currentChannel, defaultLayout, currentWidth, currentHeight) {
            if (!currentChannel || !currentChannel.layoutTargets || !currentChannel.layoutTargets.length) {
                return defaultLayout;
            }

            var layoutTargets = currentChannel.layoutTargets;
            var currentAspectRatio = currentWidth/currentHeight;
            var layoutsToCheck = [];
            for (var i = 0; i < layoutTargets.length; i++) {
                var layoutTarget = layoutTargets[i];
                if (layoutTarget.layoutId !== defaultLayout.layoutId) {
                    continue;
                }

                var widthDifference = layoutTarget.targetWidth - currentWidth;
                var heightDifference = layoutTarget.targetHeight - currentHeight;
                // if we manage to find exact match, return directly
                if (!widthDifference && !heightDifference) {
                    return layoutTarget;
                }

                var aspectRatioDifference = layoutTarget.targetAspectRatio - currentAspectRatio;
                layoutsToCheck.push({
                    layoutTarget: layoutTarget,
                    widthDifference: widthDifference,
                    widthDifferenceAbsolute: Math.abs(widthDifference),
                    aspectRatioDifference: aspectRatioDifference,
                    aspectRatioDifferenceAbsolute: Math.abs(aspectRatioDifference)
                });
            }

            if (!layoutsToCheck.length) {
                return defaultLayout;
            }

            // sort array by layouts that closest to aspect ratio first, followed by width
            // check absolute values first, before actual values, for both aspect ratio and width
            layoutsToCheck.sort(function(firstLayout, secondLayout) {
                if (firstLayout.aspectRatioDifferenceAbsolute !== secondLayout.aspectRatioDifferenceAbsolute) {
                    return firstLayout.aspectRatioDifferenceAbsolute - secondLayout.aspectRatioDifferenceAbsolute;
                }

                if (firstLayout.widthDifferenceAbsolute !== secondLayout.widthDifferenceAbsolute) {
                    return firstLayout.widthDifferenceAbsolute - secondLayout.widthDifferenceAbsolute;
                }

                return firstLayout.aspectRatioDifference !== secondLayout.aspectRatioDifference ?
                    secondLayout.aspectRatioDifference - firstLayout.aspectRatioDifference :
                    secondLayout.widthDifference - firstLayout.widthDifference;
            });

            return layoutsToCheck[0].layoutTarget;
        }

        return {
            getTileIndex: getTileIndex,
            getTileMatrix: getTileMatrix,
            isVideoWallMode: isVideoWallMode,
            findLayoutTarget: findLayoutTarget
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var getElement = angular.element;
    var app = angular.module('signsHTML5');
    app.controller('AppInitController', ["$scope", "$window", "$timeout", "$interval", "$q", "$location", "broadcastAlertService", "syncService", "channelService", "logService", "deviceAPIService", "deviceStateService", "coreFrameworkService", "storageLocalStorageService", "halMediaService", "halRemoteService", "REMOTEEVENTTYPE", "REMOTEVCODE", "ASERRORCODE", "SYNC_EVENT", "SYSTEM_DATA", "sessionService", "halPlatformService", "deviceRegistrationService", "embedProfileService", "lowBandwidthService", "contentStateService", "CONTENT_STATE_EVENT", "deviceIdentityService", "AppScriptParser", "STORAGE_COLLECTION_KEY", "STORAGE_KEY", function($scope,
        $window,
        $timeout,
        $interval,
        $q,
        $location,
        broadcastAlertService,
        syncService,
        channelService,
        logService,
        deviceAPIService,
        deviceStateService,
        coreFrameworkService,
        storageLocalStorageService,
        halMediaService,
        halRemoteService,
        REMOTEEVENTTYPE,
        REMOTEVCODE,
        ASERRORCODE,
        SYNC_EVENT,
        SYSTEM_DATA,
        sessionService,
        halPlatformService,
        deviceRegistrationService,
        embedProfileService,
        lowBandwidthService,
        contentStateService,
        CONTENT_STATE_EVENT,
        deviceIdentityService,
        AppScriptParser,
        STORAGE_COLLECTION_KEY,
        STORAGE_KEY) {
        var progressInterval = null;
        $scope.message = 'Initializing device content';
        $scope.timerMessage = '';
        $scope.progress = 0;
        $scope.progressStyle = {
            'width': 0 + '%'
        };
        $scope.progressMessage = 'Starting sync...';
        $scope.progressText = '';
        var genericErrorMessage = 'Unable to sync files. Retrying again automatically.';
        var initializationTimeout;
        var LOG_KEY = 'appspace.signs.app';
        var INITIALIZATION_TIMEOUT_DURATION = 3000;

        function init() {
            clearInitializationTimeout();
            lowBandwidthService.resetInteraction();

            if (sessionService.get('crestronMode') === 'standby') {
                logService.debug(LOG_KEY, 'Loading in standby mode');
                goTo('/tv/standby');
                return;
            }

            var redirectUrl = embedProfileService.getRedirectUrl();
            if (redirectUrl) {
                goTo(redirectUrl);
                return;
            }

            if (!deviceRegistrationService.getPlayerId()) {
                goTo('/tv/registration');
                return;
            }

            var displayRotationPromise = halMediaService.setDisplayRotation('unlock');
            if (displayRotationPromise) {
                displayRotationPromise.then($angular.noop, $angular.noop);
            }

            // reset play customization
            var playStyle = getElement('#playOverwriteStyle')[0];
            if (playStyle) {
                playStyle.href = '';
            }

            var overwriteStyleProp = getElement('#overwriteStyleProperties')[0];
            if (overwriteStyleProp) {
                overwriteStyleProp.innerHTML = '';
            }

            // Handle view destroy
            $scope.$on('$destroy', function() {
                halRemoteService.unRegisterEvents(onRemoteEvent);
                // clean up stuff
                logService.debug(LOG_KEY, 'AppInitController controller destroyed');
                syncService.unsubscribe(onSyncServiceNotification);
                // cancel progress interval
                if (progressInterval) {
                    $interval.cancel(progressInterval);
                }
            });

            halRemoteService.registerEvents(onRemoteEvent);
            // subscribe to sync service events
            syncService.subscribe(onSyncServiceNotification);

            // Fallback content should start displaying in case of version upsert failure
            initializationTimeout = $timeout(onInitializationTimeout, INITIALIZATION_TIMEOUT_DURATION);
            updateVersionToAppspace().then(function(needReboot) {
                clearInitializationTimeout();
                if (needReboot == true) {
                    logService.info(LOG_KEY, 'App update requires a restart. Reloading the splash page.');
                    goTo('/tv/splash');
                    return;
                }

                // Starts the sync service
                syncService.startSync();

                // Reset the channel service so there are no channels in memory
                channelService.reset();

                // Check for app availability
                checkAppAvailability().then(onAppAvailable, onNoAppAvailable);

                // Start the progress timer
                progressInterval = $interval(updateSyncProgress, 1000);
            });
        }

        var onRemoteEvent = function(remoteEvent) {
            // Only listen to down button events
            if (remoteEvent.eventType != REMOTEEVENTTYPE.buttonDown) {
                return;
            }

            if (remoteEvent.vcode != REMOTEVCODE.select) {
                return;
            }

            if (!$scope.progressText) {
                return;
            }

            remoteEvent.preventDefault();
            $scope.syncInBackground();
        };

        function updateState(state) {
            clearInitializationTimeout();
            deviceStateService.setCurrentState(state);
        }

        function clearInitializationTimeout() {
            if (initializationTimeout) {
                $timeout.cancel(initializationTimeout);
            }

            initializationTimeout = undefined;
        }

        function onInitializationTimeout() {
            var message = 'Channel initialization timed out after ' + INITIALIZATION_TIMEOUT_DURATION + 'ms';
            logService.warn(LOG_KEY, message);
            updateState({
                code: ASERRORCODE.CHANNEL_INITIALIZATION_TIMEOUT,
                message: message,
            });
        }

        function goTo(path) {
            updateState();
            clearInitializationTimeout();
            $location.path(path);
        }

        function onSyncServiceNotification(event) {
            // console.log(event.event);
            logService.debug(LOG_KEY, 'on received sync service notification:' + event.event);
            switch (event.event) {
                case SYNC_EVENT.NO_APP:
                    onNoAppAvailable();
                    break;
                case SYNC_EVENT.RETRIEVING_TIMESTAMP:
                    onAppSyncing();
                    break;
                case SYNC_EVENT.RETRIEVING_SCRIPT:
                    onAppAvailable(false);
                    break;
                case SYNC_EVENT.APP_UPDATED:
                    checkAppAvailability().then(onAppAvailable, onNoAppAvailable);
                    break;
                case SYNC_EVENT.IN_SYNC:
                    checkAppAvailability().then(onAppAvailable, onNoAppAvailable);
                    break;
                case SYNC_EVENT.SYNC_ERROR:
                    logService.debug(LOG_KEY, 'Sync error, will try again');
                    $scope.message = event.type === 'cache-error' ? 'Failed to download some file(s), will retry momentarily.' : genericErrorMessage;
                    $scope.timerMessage = '';
                    break;
            }
        }

        // Called when an app is available
        function onAppAvailable(available) {
            function onStartupApp(startUp) {
                if (broadcastAlertService.isFullscreenBroadcastActive()) {
                    return;
                }

                // to remove indication of remote event if being set during
                // manual registration (using on-device keyboard on touch devices)
                halRemoteService.resetLastEventTime();
                if (startUp.type == 'play') {
                    logService.info(LOG_KEY, 'Loading startup app: Play');
                    goTo('/tv/channelbrowser');
                } else {
                    logService.info(LOG_KEY, 'Loading startup app: Signs - ' + startUp.sign.name);
                    goTo('/tv/appdisplay');
                }
            }

            function onStartupAppFailed() {
                logService.error(LOG_KEY, 'Unable to retrieve startup app. Possible issue with the script itself.');
            }

            function getStartupApp() {
                logService.debug(LOG_KEY, 'Retrieving app startup information');
                syncService.getStartupApp().then(onStartupApp, onStartupAppFailed);
            }

            if (available) {
                $scope.message = 'Device is loading the channel';
                $scope.timerMessage = '';
                logService.info(LOG_KEY, 'App available');
                logService.debug(LOG_KEY, 'Processing device identity');
                processDeviceIdentity().then(getStartupApp);
            } else {
                var p = syncService.getSyncProgress();
                if (p && p.total > 0) {
                    onAppSyncing();
                } else {
                    onNoAppAvailable();
                }
            }
        }

        function processDeviceIdentity() {
            var promise = $q.defer();
            // if device identity is not valid anymore (directly after registration)
            // restart the device identity service
            deviceIdentityService.isDeviceIdentityValid().then(function(isValid) {
                if (isValid) {
                    promise.resolve();
                    return;
                }

                deviceIdentityService.restart();
                promise.resolve();
            });
            return promise.promise;
        }

        // send version information to Appspace
        function updateVersionToAppspace() {
            if (halPlatformService.getFeatureSupport('bypass.check.app.version')) {
                return $q.resolve(false);
            }

            var appVersion = coreFrameworkService.getAppVersion() || '';
            var buildVersion = coreFrameworkService.getBuildVersion() || '';
            var appScriptParserVersion = AppScriptParser.version;
            var cacheVersion = storageLocalStorageService.get(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.CACHE_VERSION) || '';
            var cacheBuildVersion = storageLocalStorageService.get(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.CACHE_BUILD_VERSION) || '';
            var cacheAppScriptParserVersion = storageLocalStorageService.get(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.CACHE_APP_SCRIPT_PARSER_VERSION) || '';

            logService.debug(LOG_KEY, 'Checking App version');
            logService.debug(LOG_KEY, 'App version:' + appVersion);
            logService.debug(LOG_KEY, 'Cached app version:' + cacheVersion);
            logService.debug(LOG_KEY, 'Build version:' + buildVersion);
            logService.debug(LOG_KEY, 'Cached build version:' + cacheBuildVersion);
            logService.debug(LOG_KEY, 'App script parser version:' + appScriptParserVersion);
            logService.debug(LOG_KEY, 'Cached app script parser version:' + cacheAppScriptParserVersion);
            if (appVersion == cacheVersion && buildVersion == cacheBuildVersion) {
                return $q.resolve(false);
            }

            var promise = $q.defer();
            var requireReboot = false;
            var onVersionProcessed = function() {
                // save the new version
                logService.debug(LOG_KEY, 'Saving App, build and parser versions to cache');
                storageLocalStorageService.set(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.CACHE_VERSION, appVersion);
                storageLocalStorageService.set(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.CACHE_BUILD_VERSION, buildVersion);
                storageLocalStorageService.set(STORAGE_COLLECTION_KEY.APPSPACE, STORAGE_KEY.CACHE_APP_SCRIPT_PARSER_VERSION, appScriptParserVersion);
                promise.resolve(requireReboot);
            };

            var processAppParserVersion = function() {
                // if we don't have a cached parser version, or
                // app parser version is the same as cached,
                // just proceed normally (no resync)
                if (!cacheAppScriptParserVersion || appScriptParserVersion === cacheAppScriptParserVersion) {
                    return;
                }

                requireReboot = true;
                logService.info(LOG_KEY, 'Application script parser version is different from cached version. Removing timestamp to force app update.');
                storageLocalStorageService.removeKey(STORAGE_COLLECTION_KEY.DEVICE_SYNC, STORAGE_KEY.PLAYER_TIMESTAMP);
            };

            var processAppVersion = function() {
                if (appVersion == cacheVersion) {
                    return $q.resolve();
                }

                var sendVersionPromise = $q.defer();
                var sendVersionTimeoutHandler;
                var doSendVersion = function() {
                    if (sendVersionTimeoutHandler) {
                        $timeout.cancel(sendVersionTimeoutHandler);
                        sendVersionTimeoutHandler = null;
                    }

                    logService.debug(LOG_KEY, 'Sending version ' + appVersion + ' to Appspace');
                    deviceAPIService.sendPlayerVersion(appVersion).then(function() {
                        logService.debug(LOG_KEY, 'Successfully send version to Appspace');
                        sendVersionPromise.resolve();
                    }, function(err) {
                        // retry send on failure
                        logService.error(LOG_KEY, 'Unable to send version to Appspace. Retrying in 5 seconds.');
                        sendVersionTimeoutHandler = $timeout(doSendVersion, 5000);
                    });
                };

                // if app version is not the same as cache version, send back to appspace
                logService.debug(LOG_KEY, 'Updating app version on Appspace');
                doSendVersion();
                return sendVersionPromise.promise;
            };

            processAppParserVersion();
            processAppVersion().then(onVersionProcessed);

            return promise.promise;
        }

        // Calls when no app is available
        function onNoAppAvailable() {
            var syncState = syncService.getSyncState();

            if (syncState.syncing) {
                $window.console.log('hold on, still syncing');
                onAppSyncing();
                return;
            }

            $scope.timerMessage = '';
            if (syncState.error) {
                logService.debug(LOG_KEY, 'No app available: ', syncState.error);
                $scope.message = genericErrorMessage;
            } else {
                logService.debug(LOG_KEY, 'No app is assigned to your device');
                $scope.message = SYSTEM_DATA.ERROR_MESSAGES.NO_APP;
                if (!syncService.isAppUnassigned()) {
                    return;
                }

                $scope.message = SYSTEM_DATA.ERROR_MESSAGES.APP_UNASSIGNED;
                contentStateService.updateContentState({
                    widgetId: null,
                    content: null,
                    state: CONTENT_STATE_EVENT.NO_CHANNEL_ASSIGNED
                });
            }
        }

        // Calls when app is syncing
        function onAppSyncing() {
            logService.trace(LOG_KEY, 'App is syncing with appspace');
            $scope.message = '<div class="inline-msg">Device is syncing with Appspace</div> <div class="la-ball-pulse"><div></div><div></div><div></div></div>';
            $scope.timerMessage = 'Depending on your network speed this may take a few minutes.';
        }

        // Update sync progress
        function updateSyncProgress() {
            var p = syncService.getSyncProgress();
            if (p && p.total > 0) {
                $scope.progressText = 'Remaining: ' + (p.total - p.cached - p.failed) + ' files';
                $scope.message = 'Device is syncing with Appspace<div class="la-ball-pulse"><div></div><div></div><div></div></div>';
                $scope.timerMessage = 'Depending on your network speed this may take a few minutes.';
                getElement('#progressContainer').css('display', '');
                $scope.progress = ((p.cached + p.failed) / p.total) * 100;
                $scope.progressStyle = {
                    'width': $scope.progress + '%'
                };
                $scope.progressMessage = p.cached + ' downloaded | ' + p.failed + ' failed | ' + p.queued + ' remaining';
            } else {
                getElement('#progressContainer').css('display', 'none');
                $scope.progressText = '';
            }
        }

        // Checks if there is an app available for this device
        function checkAppAvailability() {
            var deferred = $q.defer();
            $scope.message = 'Searching for a default channel assigned to your device.';
            $scope.timerMessage = 'Depending on your network speed this may take a few minutes.';
            logService.debug(LOG_KEY, 'Checking app availability.');
            syncService.isAppAvailable().then(function(available) {
                logService.debug(LOG_KEY, 'App available? ' + available);
                deferred.resolve(available);
            }, function(err) {
                logService.error(LOG_KEY, 'unable to check app availability.');
                deferred.reject();
            });
            return deferred.promise;
        }

        $scope.syncInBackground = function() {
            syncService.saveScriptToLocal();
            onAppAvailable(true);
        };

        coreFrameworkService.isReady().then(init);
        clearInitializationTimeout();
        // Fallback content should start displaying if core framework takes too long to be ready
        initializationTimeout = $timeout(onInitializationTimeout, INITIALIZATION_TIMEOUT_DURATION);
    }]);
})(window.angular);

(function ($angular) {
	'use strict';
	var angular = $angular;
	var app = angular.module('signsHTML5');
	app.controller('WatchdogCtrl', ["$scope", "$interval", "$location", "syncService", "logService", "coreFrameworkService", "watchdogService", function ($scope, $interval, $location, syncService, logService, coreFrameworkService, watchdogService) {
		var checkErrorInterval = null;
		$scope.message = "";

		function init() {
			//Handle view destroy
			$scope.$on('$destroy', function() {
				// clean up stuff
				logService.debug("appspace.signs.sync", "WatchdogController controller destroyed");

                //cancel interval
                if (checkErrorInterval) {
                    $interval.cancel(checkErrorInterval);
                    checkErrorInterval = null;
                }
			});

            syncService.stopSync();

            checkErrorInterval = $interval(checkForError, 1000);
		}

        function checkForError() {
            var e = watchdogService.getError();
            if(e) {
                $scope.message = e;
            }
            else {
                $location.url('/tv/splash');
            }
        }

		coreFrameworkService.isReady().then(init);

	}]);
})(window.angular);
(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('signsHTML5');

    app.controller('SplashCtrl', ["$scope", "$location", "logService", "$timeout", "syncService", "pageUtil", "deviceStateService", "halPlatformService", "halNetworkService", "halFileService", "coreFrameworkService", "coreConfigurationService", "alertsService", "embedProfileService", "lowBandwidthService", function($scope, $location, logService, $timeout,
        syncService, pageUtil, deviceStateService,
        halPlatformService, halNetworkService, halFileService, coreFrameworkService,
        coreConfigurationService, alertsService, embedProfileService, lowBandwidthService) {
        var waitInterval = 3 * 1000;
        var animationDuration = 800;
        var animationDelay = 400;
        var SHOW_CONNECTION_MESSAGE_DELAY = 5000;
        var readyTimeoutPromise;
        var reloadTimeoutPromise;
        var showConnectionMessageTimeout;
        var checkMinFirmwarePromise;
        var logKey = 'appspace.signs';
        var isDestroyed;
        var LOW_BANDWIDTH_ALERT_MESSAGE = 'The device is streaming and may incur increased bandwidth costs.';
        var LOW_BANDWIDTH_WARNING_MESSAGE = 'The device is in low bandwidth mode, which limits video playback functionality.';
        var CONNECTION_INFO_MESSAGE = 'Please wait while this device establishes an internet connection...';
        var WARNING_COLOR = '#E49700';
        var ALERT_COLOR = '#932323';
        var INFO_COLOR = '#085CEA';
        var SPLASH_CONTAINER_ID = 'splash-container';
        var MESSAGE_BOX_ID = 'message-box';
        var INFO_MESSAGE_BOX_ID = 'info-message-box';
        $scope.ipAddress = '-';
        $scope.softwareVer = '-';
        $scope.firmwareVer = '-';
        var defaultDrive = undefined;
        $scope.$on('$destroy', function() {
            $scope.lowBandwidthMessage = undefined;
            $scope.lowBandwidthColor = undefined;
            cancelTimeoutPromise(readyTimeoutPromise);
            cancelTimeoutPromise(reloadTimeoutPromise);
            cancelTimeoutPromise(checkMinFirmwarePromise);
            isDestroyed = true;
        });
        $scope.lowBandwidthMessage;
        $scope.lowBandwidthColor;
        $scope.infoMessage;
        $scope.infoMessageColor;

        function showMessageBox(id) {
            animate(id, 1, animationDuration, animationDelay);
        }

        function showLogo() {
            // Fade in the container
            animate(SPLASH_CONTAINER_ID, 1, animationDuration, animationDelay);
        }

        function processLowBandwidthMessage() {
            lowBandwidthService.isDeviceLowBandwidth().then(function(isLowBandwidthDevice) {
                // ignore if this is not a low bandwidth device
                if (!isLowBandwidthDevice) {
                    return;
                }

                lowBandwidthService.isEnabled().then(function(enabled) {
                    var message = LOW_BANDWIDTH_ALERT_MESSAGE;
                    var color = ALERT_COLOR;
                    if (enabled) {
                        message = LOW_BANDWIDTH_WARNING_MESSAGE;
                        color = WARNING_COLOR;
                    }

                    $scope.lowBandwidthMessage = message;
                    $scope.lowBandwidthColor = color;

                    showMessageBox(MESSAGE_BOX_ID);
                });
            });
        }

        function hideLogo() {
            animate(SPLASH_CONTAINER_ID, 0, animationDuration);
        }

        function animate(id, opacity, duration, delay) {
            var config = {
                duration: duration
            };
            if (delay) {
                config.delay = delay;
            }

            $('#' + id).velocity({
                opacity: opacity
            }, config);
        }

        function init() {
            if (embedProfileService.shouldSkipSplash()) {
                waitInterval = 0;
                animationDuration = 0;
            } else {
                showLogo();
                processLowBandwidthMessage();
            }

            retrieveInfo();
            // Stops the sync service
            syncService.stopSync();
        }

        $scope.onMediaComplete = ready;

        function cancelTimeoutPromise(timeoutPromise) {
            if (!timeoutPromise) {
                return;
            }

            $timeout.cancel(timeoutPromise);
            timeoutPromise = undefined;
        }

        function cancelShowConnectionMessageTimeout() {
            if (!showConnectionMessageTimeout) {
                return;
            }

            $timeout.cancel(showConnectionMessageTimeout);
            showConnectionMessageTimeout = undefined;
        }

        function showConnectionMessage() {
            $scope.infoMessage = CONNECTION_INFO_MESSAGE;
            $scope.infoMessageColor = INFO_COLOR;
            showMessageBox(INFO_MESSAGE_BOX_ID);
        }

        function retrieveInfo() {
            var finalize = function() {
                readyTimeoutPromise = $timeout(ready, waitInterval);
            };

            $scope.firmwareVer = halPlatformService.getBrowserVersion();
            $scope.softwareVer = coreFrameworkService.getAppVersion();
            logService.debug(logKey, 'Splash screen loaded');
            cancelTimeoutPromise(readyTimeoutPromise);
            halFileService.getDefaultDrive().then(function(res) {
                defaultDrive = res;
                halNetworkService.getIpAddress().then(function(res) {
                    $scope.ipAddress = res.address;
                }, function(errRes) {
                    $scope.ipAddress = '-';
                }).finally(function() {
                    cancelShowConnectionMessageTimeout();
                    showConnectionMessageTimeout = $timeout(showConnectionMessage, SHOW_CONNECTION_MESSAGE_DELAY);
                    deviceStateService.isPlaybackReady().then(function() {
                        cancelShowConnectionMessageTimeout();
                        finalize();
                    });
                });
            });
        }

        function goTo(path) {
            // if controller has been destroyed, avoid redirection
            if (isDestroyed) {
                return;
            }

            $location.path(path);
        }

        function ready() {
            hideLogo();
            var serverUrl = coreConfigurationService.get('appspace', 'appspaceURL');
            var playerGuid = coreConfigurationService.get('device', 'playerguid');
            logService.info(logKey,
                'Software version: ' + $scope.softwareVer +
                ', Firmware version: ' + $scope.firmwareVer +
                ', IP: ' + $scope.ipAddress +
                (defaultDrive ? ', Default drive: ' + defaultDrive : '')
            );
            logService.info(logKey, 'Appspace URL: ' + serverUrl + ', Player GUID: ' + playerGuid);

            if (alertsService.isAlertActive()) {
                return;
            }

            var reloadTimer = 20 * 1000;
            var forceReloadApp = function() {
                logService.warn(logKey, 'Timeout max out, forcing to reload app');
                halPlatformService.reload();
            };

            var retryCount = 0;
            var checkMinFirmware = function() {
                logService.debug(logKey, 'Initialising reload timeout ', reloadTimer);
                reloadTimeoutPromise = $timeout(forceReloadApp, reloadTimer);
                halPlatformService.isMeetMinFirmware().then(function(res) {
                    cancelTimeoutPromise(reloadTimeoutPromise);
                    // if the minimum firmware is met
                    goTo('/tv/appinit');
                }, function(err) {
                    cancelTimeoutPromise(reloadTimeoutPromise);
                    if (err.currentFWVersion == '-') {
                        retryCount++;
                        logService.warn(logKey, 'Failed to get firmware, retry... ' + retryCount);
                        $scope.message = 'Still retrieving device firmware...';
                        checkMinFirmwarePromise = $timeout(checkMinFirmware, 2000);
                    } else {
                        $scope.message = 'Please update your firmware. Minimum firmware version required is ' + err.minFWVersion + '. Current firmware version is ' + err.currentFWVersion;
                    }
                });
            };

            checkMinFirmwarePromise = $timeout(checkMinFirmware, 0);
        }

        pageUtil.setAppBackgroundColor(embedProfileService.getBackgroundColor());
        coreFrameworkService.isReady().then(init);
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('signsHTML5');

    app.controller('UnloadCtrl', ["$scope", "coreFrameworkService", "syncService", "deviceStateService", "postMessagingService", "POST_MESSAGES", function($scope, coreFrameworkService, syncService,
        deviceStateService, postMessagingService, POST_MESSAGES) {
        $scope.message = 'The Appspace app has unloaded.';

        function init() {
            // Stops the sync service
            syncService.stopSync();
            deviceStateService.stop();
            deviceStateService.stopAllServices('App Unloaded');
            postMessagingService.raiseMessage({
                message: POST_MESSAGES.NAME,
                type: POST_MESSAGES.UNLOADED
            });
        }

        coreFrameworkService.isReady().then(init);
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('signsHTML5');
    app.component('progressDonut', {
        templateUrl: 'tv/progress-donut/progress-donut.html',
        bindings: {
            progression: '<'
        },
        controller: ["$scope", "dataUtil", "$document", function($scope, dataUtil, $document) {
            var $ctrl = this;
            var progressValueElement = $document[0].querySelector('.progress-value');
            var RADIUS = 54;
            var CIRCUMFERENCE = 2 * Math.PI * RADIUS;

            $scope.$watch('$ctrl.progression', function(value) {
                progress(value);
            });
            $scope.complete = false;

            function progress(value) {
                value = dataUtil.isNumber(value) ? parseInt(value, 10) : 0;
                $scope.complete = value === 100;
                var progress = value / 100;
                progressValueElement.style.strokeDashoffset = CIRCUMFERENCE * (1 - progress);
            }

            progressValueElement.style.strokeDasharray = CIRCUMFERENCE;
            progress(0);
        }]
    });
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var getElement = angular.element;
    var app = angular.module('signsHTML5');

    app.controller('ChannelBrowserCtrl', ["$scope", "$window", "$q", "$location", "$route", "$timeout", "logService", "UrlUtil", "coreFrameworkService", "channelService", "halRemoteService", "REMOTEVCODE", "REMOTEEVENTTYPE", "syncService", "coreConfigurationService", "navigationHelper", "dataUtil", "publicService", "sessionService", "channelsHelper", "themeHelper", "scrollToHelper", "contentTileTemplateHelper", "postMessagingService", "POST_MESSAGES_ERROR", "postMessagingUtil", "halPlatformService", "FEATURE_CODE", "deviceAlertService", function(
        $scope, $window, $q, $location, $route, $timeout,
        logService, UrlUtil, coreFrameworkService, channelService,
        halRemoteService, REMOTEVCODE, REMOTEEVENTTYPE, syncService,
        coreConfigurationService, navigationHelper, dataUtil,
        publicService, sessionService, channelsHelper, themeHelper, scrollToHelper,
        contentTileTemplateHelper, postMessagingService, POST_MESSAGES_ERROR,
        postMessagingUtil, halPlatformService, FEATURE_CODE,
        deviceAlertService
    ) {
        var homeUrl = '/tv/channelbrowser';
        var queries = {
            folderId: null,
            filter: null,
            search: null,
            groupId: null
        };
        var channelSelectTimer = null;
        var contentGridApis = {};
        var idlePromise = null;
        var doIdle = false;
        var channelSort = 'title';
        var pressedNumber;
        var pressedNumberHandler;
        var isNumberBlocked;
        var windowWidth = getElement(window).width();
        var windowHeight = getElement(window).height();
        var resizeWindowTimeout;
        var resizeContentTilesTimeout;
        var channelsLoaded = false;
        var selectedItem = null;
        var idleBehaviourMap = {
            live: 'generateUrlByChannelNumber',
            liveid: 'generateUrlByLiveId',
            channelid: 'generateUrlByChannelId',
            sign: 'generateUrlByChannelId',
            ondemand: 'generateUrlByChannelPath',
            channel: 'generateUrlByChannelFullPath',
            recent: 'generateUrlByRecent'
        };
        var isCrestronMode;
        var lastTouchEvent = new Date().getTime();
        var isSideMenuOpenInSearch = false;
        var LOG_KEY = 'appspace.play';
        $scope.sideMenuApi = {};
        $scope.singleLayout = false;
        $scope.singleChannelGroup = false;
        $scope.sidebarMenu = [];
        $scope.selectedItemValue = {value: 'home'};
        $scope.channelGroups = [];
        $scope.showLoader = true;
        $scope.groupCount = 1;
        $scope.channelCount = 0;
        $scope.showSearch = false;
        $scope.noChannelsMessage = 'Sorry, you currently have no channels assigned to your device';
        $scope.customUI = false;
        $scope.noChannels = false;
        $scope.blurBackground = false;

        // Background properties
        var backgroundLoaded = false;

        // Handle view destroy
        $scope.$on('$destroy', function() {
            // clean up stuff
            halRemoteService.unRegisterEvents(onChannelBrowserRemoteEvent);
            channelService.unsubscribeFromUpdates(onChannelServiceUpdate);
            deviceAlertService.unsubscribeExclusiveChannelStart(onExclusiveChannelStarted);

            // cancel timers
            if (channelSelectTimer) {
                $timeout.cancel(channelSelectTimer);
            }

            if (resizeContentTilesTimeout) {
                $timeout.cancel(resizeContentTilesTimeout);
            }

            unsubscribeToMouseMoveEvents();
            cancelIdleTimer();
        });

        var startLoadProfiling;

        var sessionUrlIsProcessed = function() {
            var channelSessionUrl = channelService.generateUrlFromSession();
            if (channelSessionUrl && channelSessionUrl != $location.url()) {
                $location.url(channelSessionUrl);
                return true;
            }

            return false;
        };

        function init() {
            startLoadProfiling = new Date().getTime();
            deviceAlertService.subscribeExclusiveChannelStart(onExclusiveChannelStarted);
            isCrestronMode = postMessagingUtil.isCrestronModeApplicable(sessionService.get('crestronMode'));
            if (sessionUrlIsProcessed()) {
                return;
            }

            // Get app properties and check if a custom UI is being used.
            var useCustomUI = syncService.getCurrentAppProperty('player.play.themesettings.home.customui');
            if (useCustomUI && useCustomUI.toLowerCase() == 'true') {
                $scope.customUI = true;
                return;
            }

            // Register for remote input
            halRemoteService.registerEvents(onChannelBrowserRemoteEvent);

            // Subscribe to channel service updates
            channelService.subscribeToUpdates(onChannelServiceUpdate);
            // Set the flag to true so that it knows play app is currently running.
            channelService.setPlayApp(true);

            // init channelGroups - give sometime for the page layout to calculate
            channelService.isReady().then(function() {
                // apply theme settings
                themeHelper.applyTheme().then(function() {
                    $scope.theme = themeHelper.getThemeSettings();
                    var query = $location.search(); // To prioritize query in query string over the last saved query
                    if (!query || dataUtil.isObjectEmpty(query)) {
                        query = channelService.getLastQuery();
                    }

                    // clear url params after executing a network command so that future network commands will be executed
                    // note: angularJS will not process a url unless it's different from the current one
                    UrlUtil.clearParams();
                    // init channelGroups
                    initChannelGroups(query);
                    // Init sidebar
                    initSidebar();
                    // Signal its loaded
                    onCheckPageLoaded();
                    // subscribe to mouse events to prevent idle timeout
                    subscribeToMouseMoveEvents();
                });
            });
        }

        function onExclusiveChannelStarted(channel) {
            if (!channel) {
                return;
            }

            var url = channelService.generateExclusiveUrlByChannelId(channel.id);
            logService.debug(LOG_KEY, 'Load exclusive channel:' + url);
            loadPage(url);
        }

        function unsubscribeToMouseMoveEvents() {
            // remove time for mouse events
            $window.removeEventListener('mousedown', onTouchMouseEvent);
            $window.removeEventListener('mousemove', onTouchMouseEvent);
            $window.removeEventListener('wheel', onTouchMouseEvent);
            $window.removeEventListener('touchstart', onTouchMouseEvent);
            $window.removeEventListener('touchmove', onTouchMouseEvent);
            $window.removeEventListener('touchend', onTouchMouseEvent);
            getElement('.main-container').off('scroll', onTouchMouseEvent);

            // remove the resize/orientation event
            $window.removeEventListener('resize', onResizeWindow);
            $window.removeEventListener('orientationchange', onResizeWindow);
        }

        function subscribeToMouseMoveEvents() {
            // restart time for mouse movements
            $window.addEventListener('mousedown', onTouchMouseEvent);
            $window.addEventListener('mousemove', onTouchMouseEvent);
            $window.addEventListener('wheel', onTouchMouseEvent);
            $window.addEventListener('touchstart', onTouchMouseEvent);
            $window.addEventListener('touchmove', onTouchMouseEvent);
            $window.addEventListener('touchend', onTouchMouseEvent);
            getElement('.main-container').on('scroll', onTouchMouseEvent);
            // handle the resize/orientation event
            $window.addEventListener('resize', onResizeWindow);
            $window.addEventListener('orientationchange', onResizeWindow);
        }

        // Set side menu item and reset side menu open in search flag
        function setSideMenuItem(item) {
            isSideMenuOpenInSearch = false;
            if (!$scope.sideMenuApi || !$scope.sideMenuApi.set) {
                return;
            }

            // Clear selected item
            selectedItem = undefined;
            $scope.sideMenuApi.set(item);
        }

        $scope.onMainContainerClick = function() {
            $scope.sideMenuApi.close();
        };

        $scope.onSidebarSectionClick = function(item) {
            setSideMenuItem(item);

            switch (item.value) {
                case 'home':
                    toggleSearch(false);
                    initChannelGroups({});
                    navigationHelper.goHome();
                    break;
                case 'search':
                    // show search overlay
                    $scope.onSearchClick();
                    // for search store the last item value
                    break;
                case 'live_channels':
                    toggleSearch(false);
                    initChannelGroups({filter: 'live'});
                    break;
                case 'recent':
                    toggleSearch(false);
                    initChannelGroups({filter: 'recent'});
                    break;
                case 'all_channels':
                    toggleSearch(false);
                    initChannelGroups({filter: 'all_channels'});
                    break;
            }

            // process channel group
            if (item.value.indexOf('cg_') > -1) {
                var channelGroupId = item.value.replace('cg_', '');
                toggleSearch(false);
                initChannelGroups({cgid: channelGroupId});
            }

            $scope.sideMenuApi.close(0);
        };

        function onResizeWindow() {
            if (resizeWindowTimeout) {
                $timeout.cancel(resizeWindowTimeout);
                resizeWindowTimeout = null;
            }

            resizeWindowTimeout = $timeout(function() {
                resizeContentTiles();
                onCheckPageLoaded();
            }, 500);
        }

        function onTouchMouseEvent() {
            var currentTime = new Date().getTime();

            if (currentTime - lastTouchEvent <= 1) {
                return;
            }

            // Restart idle if more than a second
            restartIdleTimer();
            if (!$scope.theme.backgroundScrollBlur) {
                return;
            }

            var scrollTop = $('.main-container').scrollTop();
            lastTouchEvent = currentTime;
            $scope.blurBackground = scrollTop > 0;
        }

        $scope.onBackgroundReady = function() {
            backgroundLoaded = true;
            onCheckPageLoaded();
        };

        function resizeContentTiles() {
            // Update window height
            var xGrids = 0;
            var sizeChanged = false;
            if (windowWidth != getElement(window).width()) {
                windowWidth = getElement(window).width();
                sizeChanged = true;
            }

            if (windowHeight != getElement(window).height()) {
                windowHeight = getElement(window).height();
                sizeChanged = true;
            }

            var ratio = windowWidth / windowHeight;
            if ($scope.theme.numberOfChannelCols > 0) {
                xGrids = $scope.theme.numberOfChannelCols;
            } else {
                if (ratio > 2.3) {
                    xGrids = 6;
                } else if (ratio >= 1.6) {
                    xGrids = 5;
                } else if (ratio > 1.1) {
                    xGrids = 4;
                } else if (ratio <= 0.5) {
                    xGrids = 2;
                } else if (ratio <= 0.7) {
                    xGrids = 3;
                } else if (ratio <= 1.1) {
                    xGrids = 4;
                } else {
                    xGrids = 3;
                }
            }

            if (xGrids < 2) {
                xGrids = 2;
            }

            // Get grid, tile and margin sizes
            var navigationContainer = getElement('.navigation-container');
            // Force DOM Refresh
            navigationContainer[0].offsetHeight;
            navigationContainer[0].offsetWidth;
            if (sizeChanged) {
                // Recalc min width
                navigationContainer.css('min-width', 'initial');
            }

            var firstGridContainer = $(getElement('.content-tile-container')[0]);
            // Force DOM refresh
            if (firstGridContainer.length > 0) {
                firstGridContainer[0].offsetHeight;
                firstGridContainer[0].offsetWidth;
            }

            var contentGrids = $(getElement('content-grid'));
            var firstGrid = contentGrids.length ? $(contentGrids[0]) : $();
            var contentTiles = getElement('content-tile');
            var contentTileMedia = getElement('content-tile .media-container');
            var firstContentTile = $(contentTiles[0]);
            var gridWidth = firstGridContainer.width() - parseFloat(firstGrid.css('padding-left')) - parseFloat(firstGrid.css('padding-right'));
            var contentTileRightMargin = parseFloat(firstContentTile.css('margin-right'));
            // Calculate desired tile size based on grid cols
            var desiredMediaWidth = (gridWidth / xGrids) - contentTileRightMargin;
            var desiredMediaHeight = desiredMediaWidth / (16 / 9);

            contentTiles.css('min-width', (desiredMediaWidth - .5) + 'px');
            contentTiles.css('max-width', (desiredMediaWidth - .5) + 'px');
            contentTileMedia.css('width', (desiredMediaWidth - .5) + 'px');
            contentTileMedia.css('height', desiredMediaHeight + 'px');

            // determine offset based on number of rows
            navigationContainer.css('padding-top', '0px');
            // Reset margin top for our calculations
            navigationContainer.css('margin-top', '0px');
            var navContainerTop = navigationContainer.offset().top;
            if ($scope.theme.numberOfChannelRows >= 0) {
                var navigationContainerHeight = windowHeight - navContainerTop;
                var paddingTop = 0;
                var row = 0;
                var blockHeight = -1;
                // If single group and not on home page
                if ($scope.singleLayout && !$scope.isHome) {
                    paddingTop = $scope.theme.backgroundScroll ? windowHeight : 0;
                } else if ($scope.singleChannelGroup) {
                    blockHeight = 0;
                    if ($scope.theme.numberOfChannelRows > 0) {
                        for (var i = 0; i < contentTiles.length && row < $scope.theme.numberOfChannelRows; i++) {
                            var tile = $(contentTiles[i]);
                            var top = tile.offset().top - navContainerTop;
                            if (top > blockHeight) {
                                blockHeight = top + tile.outerHeight(true);
                                row++;
                            }
                        }
                    }

                    if (blockHeight >= 0) {
                        paddingTop = navigationContainerHeight - blockHeight + ($scope.theme.backgroundScroll ? windowHeight : 0);
                    }
                } else {
                    paddingTop = navigationContainerHeight - (firstGridContainer.height() * $scope.theme.numberOfChannelRows);
                }

                if (paddingTop >= 0) {
                    navigationContainer.css('padding-top', paddingTop + 'px');
                }
            }

            // Set navigation width so that side bar does not cause it to shrink
            navigationContainer.css('min-width', navigationContainer.width() + 'px');
            // Remove the margin-top reset
            navigationContainer.css('margin-top', '');

            // set the row / col index of each tile
            var colOffset = -2;
            var rowOffset = -1;
            var lastTop = -1;
            for (var j = 0; j < contentTiles.length; j++) {
                var tileElm = $(contentTiles[j]);
                var t = tileElm.offset().top;
                if (t > lastTop) {
                    lastTop = t;// + tileElm.outerHeight(true);
                    rowOffset++;
                    colOffset = 0;
                } else {
                    colOffset++;
                }

                tileElm[0]._tileRow = rowOffset;
                tileElm[0]._tileCol = colOffset;
            }
        }

        function initSidebar() {
            $scope.sidebarMenu = [];

            if ($scope.theme.enableSearch) {
                $scope.sidebarMenu.push({
                    label: 'Search',
                    icon: 'icon-search',
                    value: 'search'
                });
            }

            $scope.sidebarMenu.push({
                label: 'Home',
                icon: 'icon-home',
                value: 'home'
            });
            if ($scope.theme.enableRecent) {
                $scope.sidebarMenu.push({
                    label: 'Recently Added',
                    icon: 'icon-recent',
                    value: 'recent'
                });
            }

            if (channelService.getLiveChannels().length) {
                $scope.sidebarMenu.push({
                    label: 'Live Channels',
                    icon: 'icon-live',
                    value: 'live_channels'
                });
            }

            var channelsMenu = {
                label: 'All Channels',
                icon: 'icon-channels',
                value: 'all_channels',
                items: []
            };

            var channelGroups = channelService.getChannelGroups();
            if (channelGroups.length > 0) {
                for (var i = 0; i < channelGroups.length; i++) {
                    var group = channelGroups[i];
                    // skip live channel
                    if (group.id == -1) {
                        continue;
                    }

                    var text = group.name;
                    var value = group.id;
                    channelsMenu.items.push({
                        label: text,
                        value: 'cg_' + value
                    });
                }
            }

            $scope.sidebarMenu.push(channelsMenu);
        }

        function loadHome() {
            $scope.selectedItemValue = {value: 'home'};
            $scope.isHome = true;
            var executeIdle = false;
            if ($scope.theme.idleBehaviour) {
                doIdle = true;
                executeIdle = processIdleBehaviour($scope.theme.idleBehaviour);
            }

            if (!executeIdle && !isCrestronMode) {
                // get all channel groups
                var groups = channelsHelper.sortChannelGroups(channelService.getChannelGroups(), 'name');
                for (var i = 0; i < groups.length; i++) {
                    var group = groups[i];
                    // sort live channel
                    if (group.id != -1) {
                        if ($scope.theme.homeChannelGroupSummary && $scope.theme.homeChannelGroupSummary == 'content') {
                            group.channels = channelsHelper.getContentByFilter('channelgroup:' + group.id + ':16');
                        }

                        continue;
                    }

                    group.channels = channelsHelper.sortLiveChannel(group.channels, $scope.theme.liveChannelSortBy);
                }

                $scope.singleLayout = false;
                $scope.singleChannelGroup = groups.length == 1;
                $scope.channelGroups = groups;
            }
        }

        function initChannelGroups(query) {
            channelSort = 'title';
            var group;
            var channels;
            $scope.singleLayout = true;
            $scope.isHome = false;
            $scope.channelGroups = [];
            channelsLoaded = false;
            contentGridApis = {};
            toggleLoading(true);

            channelService.setLastQuery(query);

            // Reset vertical scroll
            $('.main-container').scrollTop(0);

            // Wrap in a timeout as we want the ng-repeats to clear so that grid are recreated
            $timeout(function() {
                // search string in the query
                if (query.q) {
                    $scope.selectedItemValue = {value: 'search'};
                    queries.search = decodeURIComponent(query.q);
                    channels = channelService.doSearch(queries.search);
                    channels = channelsHelper.getUniqueChannels(channels);
                    group = {
                        id: 1,
                        name: 'Search Results for <span class="title-highlight">"' + queries.search + '"</span>',
                        channels: channels,
                    };
                    $scope.channelGroups.push(group);
                } else if (query.folderid) { // folder id in the query
                    $scope.selectedItemValue = {value: 'channels'};
                    queries.folderId = query.folderid;
                    channels = channelService.getChannelByChannelId(queries.folderId);
                    if (channels) {
                        group = {
                            id: queries.folderId,
                            name: channels.title,
                            channels: channels.subChannels,
                        };
                        $scope.channelGroups.push(group);
                    }
                } else if (query.cgid) {// by channel group id
                    queries.groupId = query.cgid;
                    $scope.selectedItemValue = {value: 'cg_' + query.cgid};
                    channels = channelService.getChannelsByGroup(queries.groupId);
                    var channelGroup = channelService.getChannelGroupById(queries.groupId);
                    if (channelGroup && channels) {
                        group = {
                            id: queries.groupId,
                            name: channelGroup.name,
                            channels: channels,
                        };
                        $scope.channelGroups.push(group);
                    }
                } else if (query.filter) {// filter in the query
                    queries.filter = query.filter;
                    switch (queries.filter) {
                        case 'live':
                            group = getLiveChannels();
                            $scope.channelGroups.push(group);
                            $scope.selectedItemValue = {value: 'live_channels'};
                            break;
                        case 'recent':
                            group = getRecentContent();
                            channelSort = 'date';
                            $scope.channelGroups.push(group);
                            $scope.selectedItemValue = {value: 'recent'};
                            break;
                        case 'all_channels':
                            $scope.selectedItemValue = {value: 'all_channels'};
                            group = getAllChannels();
                            channelSort = 'date';
                            $scope.channelGroups.push(group);
                            break;
                        case 'home':
                            loadHome();
                            break;
                    }
                } else {
                    loadHome();
                }

                // If background show is false, we set background loaded to false;
                if (!$scope.isHome) {
                    backgroundLoaded = false;
                }

                setSideMenuItem($scope.selectedItemValue);
                $scope.groupCount = $scope.channelGroups.length;

                $scope.channelCount = 0;
                for (var cgi = 0; cgi < $scope.channelGroups.length; cgi++) {
                    var cg = $scope.channelGroups[cgi];
                    $scope.channelCount += cg.channels.length;
                }

                channelService.setStartupFlag(true);
                if ($scope.groupCount == 0) {
                    // hide search
                    $scope.noChannels = true;
                    if (isCrestronMode) {
                        $scope.noChannelsMessage = 'Channel not set.';
                        postMessagingService.raiseCrestronLoadedMessage(POST_MESSAGES_ERROR.CHANNEL_NOT_SET);
                    }

                    $scope.theme.enableSidebar = false;
                    channelsLoaded = true;
                    onCheckPageLoaded();
                    return;
                } else {
                    restartIdleTimer();
                }
            }, 0);
        }

        function processIdleBehaviour(behaviour, force) {
            var hasLoaded = channelService.getStartupFlag();
            if (!doIdle || !behaviour || (hasLoaded && !force)) {
                return false;
            }

            if (behaviour.match(/lastused/gi)) {
                if (hasLoaded) {
                    return false;
                }

                var lastUsed = coreConfigurationService.get('play', 'lastused');
                // ignore if we have loaded before (expected behaviour)
                if (!lastUsed) {
                    return false;
                }

                behaviour = lastUsed;
                var channelRegex = /channel=/gi;
                if (behaviour.match(channelRegex)) { // channel
                    behaviour = behaviour.replace(channelRegex, 'channelid=');
                }
            }

            var key = behaviour.split('=')[0];
            var map = idleBehaviourMap[key];
            if (!map) {
                return false;
            }

            var url = channelService[map](behaviour.substring(key.length + 1));
            if (!url) {
                return false;
            }

            url += '/autoplay/true';
            logService.debug(LOG_KEY, 'URL generated: ' + url);
            channelService.setStartupFlag(true);
            loadPage(url);
            return true;
        }

        function restartIdleTimer() {
            cancelIdleTimer();
            // if idle duration is 0 or undefined
            if (!$scope.theme || !$scope.theme.idleDuration || $scope.theme.idleDuration == 0) {
                return;
            }

            doIdle = true;
            idlePromise = $timeout(function() {
                processIdleBehaviour($scope.theme.idleBehaviour, true);
            }, $scope.theme.idleDuration);
        }

        function cancelIdleTimer() {
            doIdle = false;
            if (!idlePromise) {
                return;
            }

            $timeout.cancel(idlePromise);
            idlePromise = null;
        }

        function loadPage(url, searchOptions) {
            cancelIdleTimer();
            var loc = $location.url(url);
            if (searchOptions && searchOptions.key && searchOptions.value) {
                loc.search(searchOptions.key, searchOptions.value);
            } else if (searchOptions && searchOptions.qs) {
                loc.search(searchOptions.qs);
            }
        }

        function getLiveChannels() {
            var g = {
                id: -1,
                name: 'Live Channels',
                channels: [],
            };
            var liveChannels = channelsHelper.sortLiveChannel(channelService.getLiveChannels(), $scope.theme.liveChannelSortBy);
            if (!liveChannels || !liveChannels.length) {
                return g;
            }

            // insert guide channel to the 1st item, if any
            var guideChannel = channelService.getProgramGuide();
            if (guideChannel) {
                liveChannels.unshift(guideChannel);
            }

            g.channels = liveChannels;
            return g;
        }

        function getRecentContent() {
            var channels = channelsHelper.getContentByFilter('recent:40');
            var g = {
                id: -2,
                name: 'Recently Added',
                channels: channels,
            };
            return g;
        }

        function getAllChannels() {
            var channels = channelService.getChannels();
            channels = channelService.sortChannelsBy(channels, 'name');
            channels = channelsHelper.getChannelsOnly(channels);
            var g = {
                id: -2,
                name: 'All Channels',
                channels: channels,
            };

            return g;
        }

        function onChannelServiceUpdate() {
            if (isCrestronMode && sessionUrlIsProcessed()) {
                return;
            }

            logService.debug(LOG_KEY, 'Content updated, refreshing channels');
            $route.reload();
        }

        function onCheckPageLoaded() {
            if (channelsLoaded && (!$scope.isHome || backgroundLoaded)) {
                if (resizeContentTilesTimeout) {
                    $timeout.cancel(resizeContentTilesTimeout);
                }

                resizeContentTilesTimeout = $timeout(function() {
                    resizeContentTiles();
                    if (!selectedItem) {
                        var selectedChannel = channelService.getSelectedChannel($location.url());
                        if (selectedChannel) {
                            selectedItem = getElement('#content-tile-' + selectedChannel.id);
                            highlightItem(selectedItem, false);
                        }
                    }

                    resizeContentTilesTimeout = $timeout(function() {
                        toggleLoading(false);
                    }, 100);
                }, 200);
            }
        }

        function onContentGridLoadCheck() {
            var gridApiCount = Object.keys(contentGridApis).length;
            if (gridApiCount != $scope.groupCount) {
                return;
            }

            channelsLoaded = true;
            $timeout(onCheckPageLoaded, 0);
        }

        $scope.onContentGridApi = function(api, groupIndex, groupId) {
            var i;
            var channels = null;
            for (i = 0; i < $scope.channelGroups.length; i++) {
                var g = $scope.channelGroups[i];
                if (g.id == groupId) {
                    channels = g.channels;
                    break;
                }
            }

            // Add channels to griditems
            if (channels != null) {
                var promises = [];
                if (groupId >= 0) {
                    channels = channelService.sortChannelsBy(channels, channelSort);
                }

                for (i = 0; i < channels.length; i++) {
                    var channel = angular.copy(channels[i]);
                    var promise = contentTileTemplateHelper.createTemplate(channel, $scope);
                    promises.push(promise);
                }

                $q.all(promises).then(function(results) {
                    var items = [];
                    for (i = 0; i < results.length; i++) {
                        var item = results[i];
                        if (item) {
                            items.push(item);
                        }
                    }

                    api.addItems(items);
                    contentGridApis[groupIndex] = api;
                    onContentGridLoadCheck();
                }, function(err) {
                });
            } else {
                contentGridApis[groupIndex] = api;
                onContentGridLoadCheck();
            }
        };

        $scope.onContentTileClick = function($event, item) {
            highlightItem(null);
            loadChannel(item);
        };

        // Event handler for remote events
        var onChannelBrowserRemoteEvent = function(remoteEvent) {
            restartIdleTimer();
            // Only listen to down button events
            if (remoteEvent.eventType != REMOTEEVENTTYPE.buttonDown) {
                return;
            }

            // disable remote if we have no channels
            if ($scope.groupCount == 0) {
                return;
            }

            // if vcode is number, process it
            if (remoteEvent.vcode >= 14 && remoteEvent.vcode <= 23) {
                onNumberPressed(remoteEvent.vcode - 14);
                return;
            }

            // Perform action based on Virtual code
            switch (remoteEvent.vcode) {
                case REMOTEVCODE.up: // Up Key
                    remoteEvent.preventDefault();
                    onActionUp();
                    break;
                case REMOTEVCODE.down: // Down Key
                    remoteEvent.preventDefault();
                    onActionDown();
                    break;
                case REMOTEVCODE.left: // Left Key
                    remoteEvent.preventDefault();
                    onActionLeft();
                    break;
                case REMOTEVCODE.right: // Right Key
                    remoteEvent.preventDefault();
                    onActionRight();
                    break;
                case REMOTEVCODE.select: // enter/option/alt Key
                case REMOTEVCODE.play: // play button
                    remoteEvent.preventDefault();
                    onActionSelect();
                    break;
                case REMOTEVCODE.back: // command/windows Key
                    // Don't do anything if side menu is open in Search page
                    if (isSideMenuOpenInSearch) {
                        return;
                    }

                    if (!publicService.getServices().deviceMenu.isVisible()) {
                        remoteEvent.preventDefault();
                        onActionBack();
                    }

                    break;
                case REMOTEVCODE.home: // command/windows Key
                    if (!publicService.getServices().deviceMenu.isVisible()) {
                        remoteEvent.preventDefault();
                        onActionHome();
                    }

                    break;
                case REMOTEVCODE.guide: // guide
                    remoteEvent.preventDefault();
                    onActionGuide();
                    break;
            }
        };

        function onNumberPressed(num) {
            // if we're inside search view, ignore
            if ($scope.showSearch == true) {
                return;
            }

            // if pressed number in memory never defined, define it
            if (!pressedNumber) {
                pressedNumber = '';
            }

            // cancel number process timeout
            if (pressedNumberHandler) {
                $timeout.cancel(pressedNumberHandler);
            }

            // if number processing is blocked, don't do anything
            if (isNumberBlocked) {
                return;
            }

            pressedNumber += num.toString();

            var process = function() {
                isNumberBlocked = true;

                for (var i = pressedNumber.length; i < 4; i++) {
                    pressedNumber = '0' + pressedNumber;
                }

                var channel = retrieveLiveChannelByNumber(pressedNumber);
                if (channel) {
                    isNumberBlocked = false;
                    // add current page to pageHistory
                    channelService.addHistory($location.url());
                    loadChannel(channel);
                } else {// if channel doesn't exist, continue
                    pressedNumber = '';
                    $timeout(function() {
                        isNumberBlocked = false;
                    }, 1000);
                }
            };

            // if length is 4 process it directly
            if (pressedNumber.length == 4) {
                process();
                return;
            }

            // start number process timeout
            pressedNumberHandler = $timeout(process, 1500);
        }

        function onActionGuide() {
            var groups = $scope.channelGroups;
            // find epg and 'select' it
            for (var i = 0; i < groups.length; i++) {
                var group = groups[i];
                if (group.id == -1) { // live tv
                    var channels = group.channels;
                    for (var j = 0; j < channels.length; j++) {
                        var channel = channels[j];
                        if (channel.type == 'epg') {
                            // add current page to pageHistory
                            channelService.addHistory($location.url());
                            loadChannel(channel);
                            break;
                        }
                    }

                    break;
                }
            }
        }

        // On Select clicked
        function onActionSelect() {
            if ($scope.sideMenuApi.isOpen()) {
                $scope.sideMenuApi.select();
                return;
            } else {
                if (selectedItem && selectedItem.length) {
                    loadChannel(selectedItem[0]._item);
                }

                return;
            }
        }

        // On Back clicked
        function onActionBack() {
            // if side menu is shown, hide it
            if ($scope.sideMenuApi.isOpen()) {
                if (halPlatformService.getFeatureSupport(FEATURE_CODE.APP_EXIT_ON_BACK)) {
                    halPlatformService.closeApplication();
                    return;
                }

                $scope.sideMenuApi.close();
                return;
            } else {
                $scope.sideMenuApi.open();
                return;
            }
        }

        function onActionHome() {
            // if side menu is shown, hide it
            if ($scope.sideMenuApi.isOpen()) {
                $scope.sideMenuApi.close();
            }

            toggleSearch(false);
            initChannelGroups({});
        }

        // On Up clicked
        function onActionUp() {
            // if side menu is shown, do nothing
            if ($scope.sideMenuApi.isOpen()) {
                $scope.sideMenuApi.up();
                return;
            } else {
                if (!selectedItem || !document.contains($(selectedItem)[0])) {
                    selectedItem = $('content-tile')[0];
                    highlightItem(selectedItem, true);
                    return;
                }

                var nextItem = scrollToHelper.getNextElement(selectedItem, 'content-tile', 'above');
                if (nextItem) {
                    highlightItem(nextItem, true);
                } else if (selectedItem) {
                    scrollToHelper.scrollTo(selectedItem, null, 0, true);
                    if (selectedItem && $scope.theme.numberOfChannelRows == 0 && $scope.isHome) {
                        $(selectedItem).removeClass('selected-item');
                        selectedItem = null;
                    }
                }

                return;
            }
        }

        // On Down clicked
        function onActionDown(api) {
            // if side menu is shown, do nothing
            if ($scope.sideMenuApi.isOpen()) {
                $scope.sideMenuApi.down();
                return;
            } else {
                if (!selectedItem || !document.contains($(selectedItem)[0])) {
                    selectedItem = $('content-tile')[0];
                    highlightItem(selectedItem, true);
                    return;
                }

                var nextItem = scrollToHelper.getNextElement(selectedItem, 'content-tile', 'below');
                if (nextItem) {
                    highlightItem(nextItem, true);
                }

                return;
            }
        }

        function onActionLeft() {
            // if side menu is shown, do nothing
            if ($scope.sideMenuApi.isOpen()) {
                return;
            }

            var currentItem = !selectedItem || !document.contains($(selectedItem)[0]) ?
                $('content-tile')[0] :
                scrollToHelper.getNextElement(selectedItem, 'content-tile', 'left');
            if (currentItem) {
                highlightItem(currentItem, true);
            } else if ($scope.theme.enableSidebar) {
                $scope.sideMenuApi.open();
            }
        }

        // On Right clicked
        function onActionRight() {
            // if search is selected, do nothing
            if ($scope.sideMenuApi.isOpen()) {
                // if sideMenu is seleced, hide it
                $scope.sideMenuApi.close();
                if (isSideMenuOpenInSearch) {
                    $scope.searchKeyboardFocus = true;
                    isSideMenuOpenInSearch = false;
                }
            } else {
                if (!selectedItem || !document.contains($(selectedItem)[0])) {
                    selectedItem = $('content-tile')[0];
                    highlightItem(selectedItem, true);
                    return;
                }

                var nextItem = scrollToHelper.getNextElement(selectedItem, 'content-tile', 'right');
                if (nextItem) {
                    highlightItem(nextItem, true);
                }

                return;
            }
        }

        function highlightItem(item, animate) {
            var lastElapse = halRemoteService.getLastEventTime();
            // If the last remote use was less than an hour ago, dont highlight
            if (lastElapse < 60 * 1000 * 60) {
                return;
            }

            if (selectedItem) {
                $(selectedItem).removeClass('selected-item');
            }

            item = $(item);
            if (item.length > 0) {
                selectedItem = item;
                selectedItem.addClass('selected-item');
                scrollToHelper.scrollToElement(selectedItem, 'content-tile', animate);
                if (selectedItem[0]._tileCol == 0) {
                    scrollToHelper.scrollTo(selectedItem, 0, null, true);
                }

                if (selectedItem[0]._tileRow == 0 && ($scope.theme.numberOfChannelRows > 0 || !$scope.isHome)) {
                    scrollToHelper.scrollTo(selectedItem, null, 0, true);
                }
            } else {
                selectedItem = null;
            }
        }

        function retrieveLiveChannelByNumber(number) {
            var res;
            var groups = $scope.channelGroups;
            // find epg and 'select' it
            for (var i = 0; i < groups.length; i++) {
                var group = groups[i];
                if (group.id == -1) { // live tv
                    var channels = group.channels;
                    for (var j = 0; j < channels.length; j++) {
                        var channel = channels[j];
                        if (channel.channelNo == number) {
                            res = channel;
                            break;
                        }
                    }
                }
            }

            return res;
        }

        function loadChannel(channel) {
            var url = null;
            switch (channel.type.toLowerCase()) {
                case 'folder':
                    url = homeUrl + '?folderid=' + channel.id;
                    break;
                case 'epg':
                    url = '/tv/epg';
                    break;
                case 'content':
                    if (channel.channelType == 'livechannel') {
                        url = '/tv/livechannel/' + channel.id;
                    } else {
                        url = '/tv/channel/' + channel.loadFilter + '?position=' + channel.position;
                    }

                    break;
                default:
                    url = '/tv/channel/channel:' + channel.id;
                    break;
            }

            if (url != null) {
                channelService.setSelectedChannel(channel, $location.url());
                logService.trace(LOG_KEY, 'Load Channel', channel);
                loadPage(url);
            }
        }

        // Toggle the loading mask
        function toggleLoading(show) {
            if (!show) {
                var currentTime = new Date().getTime();
                logService.debug(LOG_KEY, 'Main page takes ' + (currentTime - startLoadProfiling) + 'ms to load');
            }

            $scope.showLoader = show;
        }

        // on search icon clicked
        $scope.onSearchClick = function() {
            cancelIdleTimer();
            logService.debug(LOG_KEY, 'Show Search');
            // open search overlay
            toggleSearch(true);
        };

        // on search left-edge clicked
        $scope.onSearchEdgeClick = function() {
            cancelIdleTimer();
            // open side menu
            isSideMenuOpenInSearch = true;
            $scope.sideMenuApi.open();
            $scope.searchKeyboardFocus = false;
        };

        // on search overlay closed
        $scope.onSearchCancel = function() {
            restartIdleTimer();
            // close search overlay
            toggleSearch(false);
            // set currently selected side menu item
            setSideMenuItem($scope.selectedItemValue);
        };

        // on search text returned from search directive
        $scope.onSearch = function(searchText) {
            // close search overlay
            toggleSearch(false);

            // reload the page with the search query
            if (searchText != null && searchText.length > 0) {
                initChannelGroups({q: searchText});
            }
        };

        // on search channel selected
        $scope.onSearchSelectChannel = function(channel) {
            loadChannel(channel);
        };

        function toggleSearch(show) {
            $scope.showSearch = show;
            $scope.searchKeyboardFocus = show;
        }

        $scope.onSideMenuApi = function(api) {
            $scope.sideMenuApi = api;
            setSideMenuItem($scope.selectedItemValue);
            coreFrameworkService.isReady().then(function() {
                // Init page once channel service is ready
                channelService.isReady().then(init);
            });
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';

    var app = $angular.module('signsHTML5');

    /**
     * @directive
     * @type {Function}
     */
    app.directive('channelSearch', ["coreFrameworkService", "dataUtil", function(coreFrameworkService, dataUtil) {
        return {
            restrict: 'EA',
            replace: 'true',
            templateUrl: 'tv/channelbrowser/channelsearch.html',
            scope: {
                show: '@',
                keyboardFocus: '@',
                onSearch: '&',
                onSelectChannel: '&',
                onCancel: '&',
                onEdgeClick: '&'
            },
            link: function(scope, element, attrs) {
                scope.$watch('show', function(newVal) {
                    scope.toggleSearch(newVal);
                });

                scope.$watch('keyboardFocus', function(newVal) {
                    scope.bindRemoteEvent(dataUtil.parseBoolean(newVal));
                });

                scope.$on('$destroy', function() {
                    scope.destroy();
                });

                element.on('$destroy', function() {
                    scope.destroy();
                });

                // Construct the api interface
                scope.api = {

                };
            },
            controller: ["$scope", "$location", "$element", "$timeout", "$compile", "channelService", "halRemoteService", "REMOTEVCODE", "REMOTEEVENTTYPE", "displayUtil", function($scope, $location, $element, $timeout, $compile, channelService, halRemoteService, REMOTEVCODE, REMOTEEVENTTYPE, displayUtil) {
                $scope.displaySearch = 'none';
                $scope.searchText = null;
                var keyboardFocus = false;
                var keyboardApi = null;
                var gridApi = null;
                var searchList = [];
                var selectedSearch = null;
                var lastKey = null;
                var maxResult = 20;
                var isLeftEdgeClicked = false;
                var isRemoteEventBound = false;
                $scope.gridConfig = {
                    padding: {top: 0, left: 0, right: 0, bottom: 0},
                    itemSpacing: {horizontal: 0, vertical: 0},
                    items: 1,
                    itemWidth: 0,
                    itemHeight: 65,
                    scrollDirection: 'vertical',
                };
                function init() {
                    resizeSearchItems();
                }

                function unbindRemoteEvent() {
                    if (!isRemoteEventBound) {
                        return;
                    }
                    isRemoteEventBound = false;
                    halRemoteService.unRegisterEvents(onRemoteEvent);
                    if (keyboardApi) {
                        keyboardApi.unRegisterKey();
                    }
                    $(document).unbind('keydown', keyEventHandler);
                }

                function bindRemoteEvent() {
                    if (isRemoteEventBound) {
                        return;
                    }
                    isRemoteEventBound = true;
                    halRemoteService.registerEvents(onRemoteEvent);
                    if (keyboardApi) {
                        keyboardApi.registerKey();
                    }

                    $(document).bind('keydown', keyEventHandler);
                }

                $scope.destroy = function() {
                    unbindRemoteEvent();
                };

                $scope.bindRemoteEvent = function(bind) {
                    if (bind) {
                        bindRemoteEvent();
                        return;
                    }

                    unbindRemoteEvent();
                };

                function resizeSearchItems() {
                    // Get the Channel Card size
                    var searchItem = $('.search-item-size');
                    if (searchItem.length > 0) {
                        var vpHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
                        // samsung have a css issue in calculate size base on viewport value,
                        // using js to calculate manually is to make sure its working in multi platform
                        var height = vpHeight * (8.333333 / 100);
                        // If portrait, reduce height.
                        if (isPortrait()) {
                            height = vpHeight * (6.3 / 100);
                        }
                        var width = $(searchItem[0]).width();
                        $scope.gridConfig.itemWidth = width;
                        $scope.gridConfig.itemHeight = height;
                    }
                }

                $scope.sideMenuClicked = function() {
                    onActionBack();
                };

                $scope.toggleSearch = function(show) {
                    keyboardApi.focusKey();
                    var el = $element;
                    el.velocity('stop');

                    var duration = 0;
                    if (show == true || show == 'true') {
                        // reset search result
                        gridApi.clearItems();
                        resizeSearchItems();

                        $scope.searchText = '';
                        $scope.displaySearch = '';
                        keyboardApi.clearInputValue();
                        el.velocity({opacity: 1}, {
                            duration: duration, complete: function() {
                            }
                        });

                        // focus on keyboard
                        toggleKeyboardFocus(true);
                        if (!halRemoteService.getLastEventTime()) {
                            // remove existing keyboard highlight
                            keyboardApi.unfocusKey();
                        }
                    } else {
                        isLeftEdgeClicked = false;
                        $scope.displaySearch = 'none';
                        el.velocity({opacity: 0}, {
                            duration: duration, display: 'none',
                            complete: function() {
                            }
                        });
                    }
                };

                // Event handler for remote events
                var onRemoteEvent = function(remoteEvent) {
                    // Only listen to down button events
                    if (remoteEvent.eventType != REMOTEEVENTTYPE.buttonDown) {
                        return;
                    }
                    // Perform action based on Virtual code
                    switch (remoteEvent.vcode) {
                        case REMOTEVCODE.up: // Up Key
                            onActionUp();
                            break;
                        case REMOTEVCODE.down: // Down Key
                            onActionDown();
                            break;
                        case REMOTEVCODE.left: // Left Key
                            onActionLeft();
                            break;
                        case REMOTEVCODE.right: // Right Key
                            onActionRight();
                            break;
                        case REMOTEVCODE.select: // option/alt Key
                            onActionSelect();
                            break;
                        case REMOTEVCODE.home:
                            onActionBack();
                            break;
                        case REMOTEVCODE.back: // command/windows Key
                            if (remoteEvent.code != 8) {
                                onActionBack();
                            }

                            break;
                    }

                    remoteEvent.preventDefault();
                };

                // Prevent the backspace key from navigating back.
                function keyEventHandler(event) {
                    // var doPrevent = false;
                    if (event.keyCode === 8) {
                        event.preventDefault();
                    }
                }

                // On Select clicked
                function onActionSelect() {
                    // if the search list has focus, return the selected search text
                    if (!keyboardFocus) {
                        var gridOffset = gridApi.getCurrentItemOffset();
                        var text = null;
                        if (gridOffset == 0) {
                            // if the first item is selected, get the search text without the '...'
                            text = $scope.searchText;
                            if ($scope.onSearch) {
                                $scope.onSearch({searchText: text});
                            }
                        } else {
                            if ($scope.onSelectChannel) {
                                $scope.onSelectChannel({channel: gridApi.getFocusedItem().channel});
                            }
                        }
                    } else {
                        keyboardApi.selectKey();
                    }
                }

                // On Back clicked or Remote Control "Home" Clicked
                function onActionBack() {
                    // fire onCancel event
                    if ($scope.onCancel) {
                        $scope.onCancel();
                    }
                }

                // On Up clicked
                function onActionUp() {
                    // if focus in not on keyboard
                    if (!keyboardFocus) {
                        // if not on first item on search list, move up
                        var gridOffset = gridApi.getCurrentItemOffset();
                        if (gridOffset > 0) {
                            gridApi.upItem();
                        }
                    } else {
                        keyboardApi.upKey();
                    }
                }

                // On Down clicked
                function onActionDown(api) {
                    // if focus is not on keyboard
                    if (!keyboardFocus) {
                        // if not on last item on search list, move down
                        var gridOffset = gridApi.getCurrentItemOffset();
                        if (gridOffset < searchList.length - 1) {
                            gridApi.downItem();
                        }
                        // we hit the last item & currently in portrait mode, go back to keyboard
                        else if (isPortrait()) {
                            toggleKeyboardFocus(true);
                        }
                    } else {
                        keyboardApi.downKey();
                    }
                }

                // On Left clicked
                function onActionLeft() {
                    // focus on keyboard if not in focus
                    if (!keyboardFocus && !isPortrait()) {
                        // focus on keyboard
                        toggleKeyboardFocus(true);
                    } else {
                        keyboardApi.leftKey();
                    }
                }

                // On Right clicked
                function onActionRight() {
                    if (keyboardFocus) {
                        keyboardApi.rightKey();
                    }
                }

                function isPortrait() {
                    return displayUtil.isScreenPortrait();
                }

                $scope.onKeyboardApi = function(api) {
                    keyboardApi = api;

                    // import layouts into the keyboard
                    var file = 'assets/keyboard-layouts/channel-search.json';
                    var promise = keyboardApi.importLayouts(file);
                    if (promise) {
                        promise.then(function(layouts) {
                            if (layouts && layouts.length > 0) {
                                // load the layout
                                keyboardApi.setLayout(layouts[0]);
                                if (!halRemoteService.getLastEventTime()) {
                                    // remove first key selection
                                    keyboardApi.unfocusKey();
                                }
                            }
                        }, function(err) { });
                    }
                };

                $scope.onKeyboardFocusChange = function(previousKey, currentKey) {
                    lastKey = currentKey;
                };

                $scope.onKeyboardValueChange = function(previousVal, currentVal) {
                    // set the input value
                    $scope.searchText = currentVal;

                    // generate Search Listing
                    generateSearchListing();
                };

                $scope.onKeyboardActionSelected = function(key) {
                    if (key) {
                        switch (key.value) {
                            case 'back':
                                onActionBack();
                                break;
                        }
                    }
                };

                $scope.onKeyboardEdge = function(edge) {
                    var edgeToCheck = isPortrait() ? 'top' : 'right';
                    isLeftEdgeClicked = edge === 'left';
                    if (isLeftEdgeClicked) {
                        $scope.onEdgeClick();
                        return;
                    }

                    if (edge == edgeToCheck && searchList.length) {
                        // move focus away from keyboard
                        toggleKeyboardFocus(false);
                    }
                };

                function toggleKeyboardFocus(focus) {
                    if (focus) {
                        keyboardFocus = true;
                        // highlight last key on focus
                        if (lastKey) {
                            keyboardApi.focusKey(lastKey.value);
                        }
                        // remove search highlight
                        gridApi.removeHighlight();
                        // reset focused item
                        gridApi.resetFocusedItem();
                        selectedSearch = -1;
                        return;
                    }

                    removeKeyboardHighlight();
                    // select the first item in search list
                    gridApi.moveToItem(0);
                }

                function removeKeyboardHighlight() {
                    // remove focus from keyboard
                    keyboardFocus = false;
                    // remove keyboard key highlight
                    keyboardApi.removeHighlight();
                }

                // on api ready
                $scope.onGridApi = function(api) {
                    gridApi = api;
                };

                // on item selected
                $scope.onGridItemFocus = function(api, offset, fromClick) {
                    removeKeyboardHighlight();
                    if (fromClick || selectedSearch == searchList[offset]) {
                        $timeout(function() {
                            // Delay so we can highlight the current item
                            onActionSelect();
                        }, 250);
                    }
                    selectedSearch = searchList[offset];
                };

                function generateSearchListing() {
                    searchList = [];
                    if ($scope.searchText != null && $scope.searchText.length > 0) {
                        // add typed text as the first item in the search list
                        var c = {id: -1, title: $scope.searchText + '...'};
                        searchList.push(c);
                        // get all matches
                        var channels = channelService.quickSearch($scope.searchText);
                        if (channels != undefined) {
                            for (var i = 0; i < channels.length && i < maxResult; i++) {
                                var c = channels[i];
                                searchList.push(c);
                            }
                        }
                    }

                    var items = [];
                    // Add search list to griditems
                    for (var i = 0; i < searchList.length; i++) {
                        var element = createSearchGridElement(searchList[i]);
                        if (element) {
                            items.push(element);
                        }
                    }

                    // clear grid items
                    gridApi.clearItems();
                    // add items to grid
                    gridApi.addItems(items);
                }

                function createSearchGridElement(channel) {
                    if (channel) {
                        var addMarkup = '';
                        if (channel.parentName) {
                            var path = channel.parentName;
                            addMarkup = '<div class="item-title">' + channel.title + '</div><div class="item-parent">' + path + '</div>';
                        } else {
                            addMarkup = channel.title;
                        }
                        var markup = '<div class="list-group-item">' + addMarkup + '</div>';

                        var t = {type: 'markup', markup: markup, channel: channel};
                        return t;
                    }
                    return null;
                }

                function onActionUpSearch() {
                    var currentOffset = gridApi.getCurrentItemOffset();

                    //if not on the first item, move up the grid item
                    if (currentOffset > 0) {
                        gridApi.upItem();
                    }
                }

                function onActionDownSearch() {
                    var length = searchList.length;
                    var currentOffset = gridApi.getCurrentItemOffset();

                    //move down if not on the last item
                    if (currentOffset < length - 1) {
                        gridApi.downItem();
                    }
                }

                coreFrameworkService.isReady().then(function() {
                    // Init page once channel service is ready
                    channelService.isReady().then(function() {
                        init();
                    });
                });
            }]
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('signsHTML5');
    var getElement = angular.element;

    app.service('channelService',
        ["$q", "$location", "$filter", "$timeout", "logService", "syncService", "networkContentRouterService", "displayUtil", "deviceIdentityService", "sessionService", "dataUtil", "applicationCommandService", "FileUtil", "SYNC_EVENT", "CRESTRON_CHANNELS", "postMessagingUtil", "ROUTE", "broadcastAlertService", function($q, $location, $filter, $timeout, logService,
            syncService, networkContentRouterService, displayUtil, deviceIdentityService,
            sessionService, dataUtil, applicationCommandService, FileUtil,
            SYNC_EVENT, CRESTRON_CHANNELS, postMessagingUtil, ROUTE, broadcastAlertService) {
            var groups = [];
            var channels = [];
            var channelsLookup = {};
            var liveChannels = [];
            var readyPromises = [];
            var isReady = false;
            var isInitializing = false;
            var pageHistory = [];
            var selectedChannelHistory = {};
            var lastQuery = {};
            var subscribers = [];
            var startupFlag;
            var isRunningPlayApp = false;
            var themeSettings = {};
            var BACKGROUND_THEME_CHANNEL_NAME = 'interactive-theme-background'; // interactive-theme-background
            var SYSTEM_CHANNELS_NAME = [
                BACKGROUND_THEME_CHANNEL_NAME,
                CRESTRON_CHANNELS.OCCUPIED,
                CRESTRON_CHANNELS.UNOCCUPIED
            ];
            var MAX_QUICK_SEARCH_RESULT = 25;
            var backgroundChannel = null;
            var elementCheck = {};
            var previousPlayData;
            var previousPlayProperties;
            var isCrestronMode;
            var systemChannels = [];

            // subscribe to sync service events
            syncService.subscribe(onSyncServiceNotification);

            function reset() {
                groups = [];
                channels = [];
                liveChannels = [];
                readyPromises = [];
                isReady = false;
                isInitializing = false;
                pageHistory = [];
                selectedChannelHistory = {};
                subscribers = [];
                startupFlag = null;
                themeSettings = {};
                previousPlayData = null;
                previousPlayProperties = null;
            }

            function initVODAppChannels() {
                function onError(err) {
                    onReady();
                }

                function onRetrieveDatasourceByIdError(err) {
                    syncService.retrieveDatasource('vodapp_-1').then(onVodDatasource, onError);
                }

                function onReady() {
                    // remove empty groups
                    for (var i = groups.length - 1; i >= 0; i--) {
                        if (groups[i].channels.length == 0) {
                            groups.splice(i, 1);
                        }
                    }

                    isReady = true;
                    isInitializing = false;

                    for (i = 0; i < readyPromises.length; i++) {
                        readyPromises[i].resolve();
                    }

                    logService.debug('appspace.play', 'Channel service ready: ' + groups.length + ' groups and ' + channels.length + ' channels');

                    // Notify subscribers that an update complete
                    notifySubscribers();
                }

                function onVodDatasource(result) {
                    var channelTitle;
                    // VOD data
                    var play = angular.copy(result.result.play);
                    var playStringify = JSON.stringify(play);

                    // property
                    var playUIPropertyList = JSON.stringify(syncService.getCurrentAppProperties());

                    // if the data the same, don't do anything
                    if (playStringify == previousPlayData && playUIPropertyList == previousPlayProperties) { // eslint-disable-line max-len
                        logService.debug('appspace.play', 'New channel datasource is the same as the previous datasource. Skipping.');
                        isInitializing = false;
                        return;
                    }

                    previousPlayData = playStringify;
                    previousPlayProperties = playUIPropertyList;

                    logService.debug('appspace.play', 'Processing Channel datasource');
                    // init sample channels
                    // play = sampleChannels.init();
                    groups = [];
                    channels = [];
                    themeSettings = play.themeSettings;
                    if (themeSettings) {
                        for (var i = 0; i < themeSettings.themes.length; i++) {
                            var theme = themeSettings.themes[i];
                            if (!theme || !theme.logo) {
                                continue;
                            }

                            // convert default
                            if (theme.isDefaultLogo) {
                                theme.logo = theme.logo.split('/play/DefaultItems/').join('/appspace-extensions/089fd992-bc85-4a03-ab8c-06e77d3197c0/data/VODDefaultItems/Pictures/DefaultLogo/');
                                theme.logo = 'C:/FastNetV2' + theme.logo;
                                theme.logo = theme.logo.split('/').join('\\');
                            }
                        }
                    }

                    // reset background channel and system channels references
                    backgroundChannel = null;
                    systemChannels = [];

                    groups = groups.concat(play.groups);
                    // Remove the system channels from any groups it is present in
                    for (var j = 0; j < groups.length; j++) {
                        var g = groups[j];
                        for (var k = 0; k < g.channels.length; k++) {
                            var channel = g.channels[k];
                            channelTitle = (channel.title || '').toLowerCase();
                            if (SYSTEM_CHANNELS_NAME.indexOf(channelTitle) > -1) {
                                if (channelTitle == BACKGROUND_THEME_CHANNEL_NAME) {
                                    backgroundChannel = channel;
                                }

                                systemChannels.push(channel);
                                // Remove the system channels
                                g.channels.splice(k, 1);
                                k--;
                                continue;
                            }
                        }
                    }

                    // Sort system channels by prioritising playlists for cases where there are a sign and a playlist with the
                    // same name and we want to play the playlist instead of the sign
                    systemChannels = $filter('orderBy')(systemChannels, false, function(currentChannel, nextChannel) {
                        // 0 = both current channel and next channel are of equal order
                        // 1 = current channel is of higher order than the next channel
                        // -1 = current channel is of lower order than the next channel
                        if ((!currentChannel.type || !nextChannel.type) || currentChannel.type == nextChannel.type) {
                            return 0;
                        }

                        if (currentChannel.type == 'playlist') {
                            return 1;
                        }

                        return -1;
                    });

                    // Move the live stream to the last group
                    var liveGroup;
                    for (i = 0; i < groups.length; i++) {
                        g = groups[i];
                        if (g.id == -1) {
                            liveGroup = g;
                            groups.splice(i, 1);
                        }
                    }

                    if (liveGroup) {
                        groups.push(liveGroup);
                    }

                    channels = channels.concat(play.channels);

                    var filteredChannels = [];
                    for (i = 0; i < channels.length; i++) {
                        channel = channels[i];
                        channelTitle = (channel.title || '').toLowerCase();
                        channelsLookup[channel.id] = channel;
                        // Remove system channels and their related contents
                        var matchedSystemChannels = $filter('filter')(systemChannels, function(channel) {
                            return channelTitle == channel.title.toLowerCase() || channel.parentChannelId == channel.id;
                        });

                        if (matchedSystemChannels.length > 0) {
                            continue;
                        }

                        channel.titleLowercase = channelTitle;
                        channel.descriptionLowercase = (channel.description || '').toLowerCase();
                        channel.date = channel.date ? new Date(channel.date) : new Date(2014, 1, 1);
                        channel.updateddate = channel.updateddate ? new Date(channel.updateddate) : new Date(channel.date);
                        filteredChannels.push(channel);
                    }

                    channels = filteredChannels;
                    liveChannels = initLiveChannels();

                    onReady();
                }

                function onAppScript(res) {
                    syncService.retrieveDatasource('vodapp_' + syncService.retrieveAppId()).then(onVodDatasource, onRetrieveDatasourceByIdError);
                }

                syncService.retrieveAppScriptObject().then(onAppScript, onError);
            }

            function initLiveChannels() {
                var lc = [];
                for (var i = 0; i < channels.length; i++) {
                    var c = channels[i];
                    if (c.channelType == 'livechannel') {
                        if (c.epg) {
                            for (var k = 0; k < c.epg.length; k++) {
                                var p = c.epg[k];
                                p.start = moment(p.start);
                                p.stop = moment(p.stop);
                            }

                            // Sort programs based on start time
                            for (var j = 0; j < c.epg.length; j++) {
                                var mov = false;
                                for (k = 0; k < c.epg.length - 1; k++) {
                                    var p1 = c.epg[k];
                                    var p2 = c.epg[k + 1];
                                    if (p1.start > p2.start) {
                                        c.epg[k] = p2;
                                        c.epg[k + 1] = p1;
                                        mov = true;
                                    }
                                }

                                if (!mov) {
                                    break;
                                }
                            }
                        }

                        lc.push(c);
                    }
                }

                return lc;
            }

            function onStartupApp(startUp) {
                if (broadcastAlertService.isFullscreenBroadcastActive()) {
                    return;
                }

                if (startUp.type == 'play') {
                    isRunningPlayApp = true;
                    refreshChannels();
                } else if (isRunningPlayApp == true) {
                    isRunningPlayApp = false;
                    logService.debug('appspace.play', 'Switching to Signs mode');
                    applicationCommandService.unsubscribe(onNetworkCommUpdate);
                    $location.path('/tv/appinit');
                }
            }

            function onSyncServiceNotification(event) {
                switch (event.event) {
                    case SYNC_EVENT.NO_APP:
                        break;
                    case SYNC_EVENT.RETRIEVING_TIMESTAMP:
                        break;
                    case SYNC_EVENT.RETRIEVING_SCRIPT:
                        break;
                    case SYNC_EVENT.APP_UPDATED:
                        syncService.getStartupApp().then(onStartupApp);
                        break;
                    case SYNC_EVENT.IN_SYNC:
                        break;
                    case SYNC_EVENT.SYNC_ERROR:
                        break;
                }
            }

            function notifySubscribers() {
                for (var i = 0; i < subscribers.length; i++) {
                    var sub = subscribers[i];
                    sub();
                }
            }

            function init() {
                if (isInitializing) {
                    return;
                }

                // Starts the sync service
                syncService.startSync();

                isInitializing = true;

                // init VODApp Channels
                initVODAppChannels();

                isCrestronMode = postMessagingUtil.isCrestronModeApplicable(sessionService.get('crestronMode'));
            }

            function generateUrl(event) {
                var filterName = function(collection, name, id) {
                    return $filter('filter')(collection, function(elem) {
                        if (name) {
                            return (elem.name || elem.title || '').toLowerCase() == name;
                        }

                        return elem.id == id;
                    });
                };

                var filterPath = function(collection, path) {
                    return $filter('filter')(collection, function(elem) {
                        return (elem.fullPath || '').toLowerCase() == path;
                    });
                };

                var createUrl = function(prefix, name, id, collection) {
                    if (!name && !id) {
                        return;
                    }

                    var item = filterName(collection, (name || '').toLowerCase(), id)[0];
                    if (!item || !item.id) {
                        return;
                    }

                    return prefix + item.id;
                };

                var homeUrl = '/tv/channelbrowser';
                var fullscreenUrl = '/tv/channel/';
                var liveUrl = '/tv/livechannel/';
                var channel;

                switch (event.action) {
                    case 'loadchannel':
                        return createUrl(
                            fullscreenUrl,
                            event.channelname,
                            event.channelid,
                            getUnfilteredChannels());
                    case 'recent':
                        return fullscreenUrl + 'recent:' + event.filter;
                    case 'search':
                        return event.query ? homeUrl + '?q=' + encodeURIComponent(event.query) : undefined;
                    case 'loadchannelgroup':
                        return createUrl(homeUrl + '?cgid=', event.channelgroupname, event.channelgroupid, groups);
                    case 'loadlivechannel':
                        return createUrl(
                            liveUrl,
                            event.livechannelname,
                            event.livechannelid,
                            liveChannels);
                    case 'loadhome':
                        return homeUrl + '?filter=home';
                    case 'loadepg':
                        return '/tv/epg';
                    case 'loadrecent':
                        return homeUrl + '?filter=recent';
                    case 'loadchannelinchannelgroup':
                        var group = filterName(groups, event.groupname)[0];
                        if (!group || !group.channels || group.channels.length == 0) {
                            return;
                        }

                        channel = filterName(group.channels, event.channelname)[0];
                        if (!channel || !channel.id) {
                            return;
                        }

                        var redirectUrl = channel.channelType == 'livechannel' ? liveUrl : fullscreenUrl;
                        return redirectUrl + channel.id;
                    case 'loadchannelbypath':
                        channel = filterPath(getUnfilteredChannels(), event.folderpath)[0];
                        if (!channel) {
                            return;
                        }

                        return fullscreenUrl + channel.id;
                    case 'loadchannelbynumber':
                        return liveUrl + 'channelnumber/' + event.channelnumber;
                }
            }

            function onNetworkCommUpdate(event) {
                if (!event || event.type !== 'channel' || !event.action) {
                    return;
                }

                logService.debug('appspace.play', 'Generating URL from network command ' + event.action.action);
                var url = generateUrl(event.action);
                if (url) {
                    $location.url(url);
                }
            }

            function generateUrlByChannelFullPath(path) {
                var splits = path.split('/');
                if (splits.length < 3) {
                    return;
                }

                return generateUrlByChannelInGroup(splits[1], splits[2]);
            }

            function generateUrlByChannelInGroup(groupName, channelName) {
                if (!groupName || !channelName) {
                    return;
                }

                logService.debug('appspace.play', 'Generating URL for channel [' + channelName + '] in group [' + groupName + ']');
                return generateUrl({
                    action: 'loadchannelinchannelgroup',
                    groupname: groupName.toLowerCase(),
                    channelname: channelName.toLowerCase()
                });
            }

            function generateUrlByChannelId(channelid) {
                if (!channelid) {
                    return;
                }

                if (!isNaN(channelid)) {
                    channelid = parseInt(channelid, 10);
                }

                logService.debug('appspace.play', 'Generating URL for channel ID [' + channelid + ']');
                return generateUrl({
                    action: 'loadchannel',
                    channelid: channelid
                });
            }

            function generateExclusiveUrlByChannelId(channelid) {
                var url = generateUrlByChannelId(channelid);
                return !url ? url : url + ROUTE.EXCLUSIVE_CHANNEL;
            }

            function generateUrlByRecent(filter) {
                logService.debug('appspace.play', 'Generating URL for recent content [' + filter + ']');
                return generateUrl({
                    action: 'recent',
                    filter: filter
                });
            }

            function generateUrlByChannelPath(channelPath) {
                if (!channelPath) {
                    return;
                }

                logService.debug('appspace.play', 'Generating URL from channel path [' + channelPath + ']');
                var path = channelPath.replace(/\\/g, '/');
                if (path.charAt(path.length - 1) == '/') {
                    path = path.substring(0, path.length - 1);
                }

                return generateUrl({
                    action: 'loadchannelbypath',
                    folderpath: path.toLowerCase()
                });
            }

            function generateUrlByChannelNumber(channelNumber) {
                if (dataUtil.isNullOrUndefined(channelNumber)) {
                    return;
                }

                logService.debug('appspace.play', 'Generating URL from live channel number [' + channelNumber + ']');
                return generateUrl({
                    action: 'loadchannelbynumber',
                    channelnumber: channelNumber
                });
            }

            function generateUrlByLiveId(liveId) {
                liveId = parseInt(liveId, 10);
                if (isNaN(liveId)) {
                    return;
                }

                logService.debug('appspace.play', 'Generating URL from live channel ID [' + liveId + ']');
                return generateUrl({
                    action: 'loadlivechannel',
                    livechannelid: liveId
                });
            }

            function generateUrlFromSession() {
                var urlParameters = sessionService.getUrlParameters();

                var keys = ['channel', 'channelid', 'channelname', 'livechannelid', 'livechannelname'];
                var event = dataUtil.duplicateObject(keys, urlParameters);

                if (!event.channel) {
                    return;
                }

                event.action = event.channel;
                // Don't remove keys from object when it's on crestron mode, so that it doesn't go back
                // to the interactive mode page and will retain the existing urlParameters instead
                if (!isCrestronMode) {
                    urlParameters = dataUtil.removeKeysFromObject(keys, urlParameters);
                    sessionService.set('urlParameters', urlParameters);
                }

                return generateUrl(event);
            }

            function refreshChannels() {
                // Subscribe to network comm updates.
                applicationCommandService.subscribe(onNetworkCommUpdate);

                if (!isInitializing) {
                    checkRotation();
                    init();
                }
            }

            function checkRotation() {
                syncService.getAppProperties().then(function(properties) {
                    displayUtil.applyRotation(properties).then(function(rotation) {
                        deviceIdentityService.restart();
                        logService.debug('appspace.play', 'Screen has been rotated to ' + rotation);
                    }, function(message) {
                        logService.warn('appspace.play', 'Unable to set rotation:', message);
                    });
                });
            }

            function checkIsReady() {
                var deferred = $q.defer();
                if (!isReady) {
                    refreshChannels();
                    readyPromises.push(deferred);
                } else {
                    deferred.resolve();
                }

                return deferred.promise;
            }

            function getChannelByChannelId(channelArray, channelId) {
                for (var i = 0; i < channelArray.length; i++) {
                    var c = channelArray[i];
                    if (c.id == channelId) {
                        return c;
                    }

                    if (c.subChannels.length > 0) {
                        var res = getChannelByChannelId(c.subChannels, channelId);
                        if (res) {
                            return res;
                        }
                    }
                }

                return null;
            }

            function getChannelGroupById(channelgroupId) {
                var res;
                for (var i = 0; i < groups.length; i++) {
                    var g = groups[i];
                    if (g.id == channelgroupId) {
                        res = g;
                    }
                }

                return res;
            }

            function getChannelByPath(channelArray, path) {
                var channel = $filter('filter')(channelArray, function(elem, index) {
                    if (elem.fullPath && elem.fullPath.toLowerCase() == path.toLowerCase()) {
                        return true;
                    }
                })[0];
                return channel;
            }

            function generateAltText(channel) {
                if (!channel || !channel.title) {
                    return '';
                }

                // Remove non AZ chars
                var alt = channel.title.toUpperCase().replace(/[^a-zA-Z\040]+/g, '');

                // Split on spaces
                var segs = alt.split(' ');
                if (segs.length == 1) {
                    alt = segs[0].substring(0, 3);
                } else {
                    alt = '';
                    // Take first char from each seg
                    for (var i = 0; i < segs.length; i++) {
                        alt += segs[i].substring(0, 1);
                        if (alt.length == 3) {
                            break;
                        }
                    }
                }

                return alt;
            }

            function createGridElement(channel, type) {
                var deferred = $q.defer();
                var title = channel.title;
                if (channel.channelType === 'livechannel' && channel.id) {
                    title = parseInt(channel.id, 10) + ' ' + title;
                }

                var altText = generateAltText(channel);
                var icon = '<span class="channel-alttext">' + altText + '</span>';
                var processedThumbnail = false;
                var newId = channel.groupId + '-' + channel.id;
                var resolve = function(markup) {
                    var element = getElement(markup);
                    var checkimageElement = element.find('.imagethumb');
                    if (checkimageElement && checkimageElement.length > 0) {
                        checkimageElement.on('load', function(e) {
                            var channelid = e.target && getElement(e.target).attr('channelid') ? getElement(e.target).attr('channelid') : undefined;
                            if (channelid) {
                                onChannelBGImageLoaded(channelid);
                            }
                        });
                        checkimageElement.on('error', function(e) {
                            var channelid = e.target && getElement(e.target).attr('channelid') ? getElement(e.target).attr('channelid') : undefined;
                            if (channelid) {
                                onChannelBGImageFailed(channelid);
                            }
                        });
                    }

                    var t = {type: 'element', element: element, channel: channel};
                    deferred.resolve(t);
                };

                var getChannelIconColor = function() {
                    return channel.iconColor ? ('background-color:' + channel.iconColor + ';') : '';
                };

                var generateTextBasedThumbnail = function(defaultDisplay, id) {
                    var markup = '<div class="preview textthumb-' + id + '" style="' + getChannelIconColor() + (defaultDisplay ? 'display:' + defaultDisplay : '') + '">' + icon + '</div>';
                    return markup;
                };

                var createTextBasedThumbnail = function() {
                    var markup = '<div class="grid-item">';
                    markup += '<div class="sixteen-nine">';
                    markup += generateTextBasedThumbnail(null, newId);
                    markup += '</div>';
                    markup += addTitle();
                    markup += '</div>';
                    resolve(markup);
                };

                var createImageBasedThumbnail = function(url) {
                    var contentExtension = FileUtil.getFileExtension(url.toLowerCase());
                    // Respect channel logo aspect ratio if it's svg or live channel
                    var cssClass = 'preview imagethumb';
                    if (channel.channelType == 'livechannel' || contentExtension == 'svg') {
                        cssClass += ' channel-logo';
                    }

                    var markup = '<div class="grid-item"';
                    markup += (channel.defaultSequence) ? ' channelGroupSequenceBy="' + channel.defaultSequence + '"' : '';
                    markup += (channel.date) ? ' ChannelCreatedDate="' + $filter('date')(channel.date, 'yyyyMMdd-HH:mm:ss') + '"' : '';
                    markup += (channel.updateddate) ? ' ChannelUpdatedDate="' + $filter('date')(channel.updateddate, 'yyyyMMdd-HH:mm:ss') + '"' : '';
                    markup += (channel.title) ? ' ChannelTitle="' + channel.title + '"' : '';
                    markup += '>';
                    markup += '<div class="sixteen-nine"' + (getChannelIconColor() ? ' style="' + getChannelIconColor() + '"' : '') + '>';
                    markup += generateTextBasedThumbnail('none', newId);
                    markup += '<img id="imagethumb-' + newId + '" channelid="' + newId + '" class="' + cssClass + '" src="' + url + '"/>';
                    markup += '</div>';
                    markup += addTitle();
                    markup += '</div>';
                    resolve(markup);
                };

                var addTitle = function() {
                    // if card, add title
                    var titlemarkup = '';
                    if (type == 'card') {
                        var customStyle = '';
                        // Apply custom styles to the channel title if there are any
                        for (var key in channel.channelTitleStyle) {
                            var style = key + ':' + channel.channelTitleStyle[key] + ' !important;';
                            customStyle += style;
                        }

                        titlemarkup = '<div class="title"' + (customStyle ? ' style="' + customStyle + '"' : '') + '>' + title + '</div>';
                    }

                    return titlemarkup;
                };

                if (channel.assets) {
                    for (var i = 0; i < channel.assets.length; i++) {
                        var asset = channel.assets[i];
                        // if we have thumbnail and the thumbnail is an image
                        if (asset.type.toLowerCase() == 'thumbnail') {
                            processedThumbnail = true;
                            if (asset.mediaUrl && asset.mediaType == 'image') {
                                networkContentRouterService.resolve(asset.mediaUrl, 'play-content-routing-service', {type: asset.mediaType, cache: true})
                                    .then(createImageBasedThumbnail, createTextBasedThumbnail);
                            } else {
                                createTextBasedThumbnail();
                            }

                            break;
                        }
                    }
                }

                if (!processedThumbnail) {
                    deferred.resolve(null);
                }

                return deferred.promise;
            }

            var waitElementExists = function(params, promise) {
                promise = promise ? promise : $q.defer();
                if (params && params.id && params.elementToCheck) {
                    if (elementCheck[params.id] && elementCheck[params.id].timeoutPromise) {
                        $timeout.cancel(elementCheck[params.id].timeoutPromise);
                        elementCheck[params.id].timeoutPromise = null;
                    } else {
                        elementCheck[params.id] = {timeoutPromise: null, waitCount: 0};
                    }

                    if (getElement(params.elementToCheck).length) {
                        logService.trace('appspace.play', 'Element ID: ' + params.id + ' take ' + elementCheck[params.id].waitCount + ' time(s) to resolve.');
                        promise.resolve(params.id);
                    } else {
                        elementCheck[params.id].waitCount++;
                        if (elementCheck[params.id].waitCount <= 50) {
                            elementCheck[params.id].timeoutPromise = $timeout(waitElementExists, 100, true, params, promise); // eslint-disable-line max-len
                        } else {
                            // After 5 seconds, and element still does not exist, reject promise
                            promise.reject();
                        }
                    }
                } else {
                    promise.reject();
                }

                return promise.promise;
            };

            function onChannelBGImageFailed(id) {
                var removeElements = function(id) {
                    getElement('.textthumb-' + id).css('display', 'block');
                    getElement('#imagethumb-' + id).remove();
                };

                var params = {id: id, elementToCheck: '#imagethumb-' + id};
                waitElementExists(params).then(removeElements);
            }

            function onChannelBGImageLoaded(id) {
                var removeElements = function() {
                    getElement('.textthumb-' + id).remove();
                    // Remove the background color once image is loaded, to support transparency
                    getElement('#imagethumb-' + id).css('background-color', 'transparent');
                };

                var params = {id: id, elementToCheck: '.textthumb-' + id};
                waitElementExists(params).then(removeElements);
            }

            function getUnfilteredChannels() {
                return isCrestronMode ? channels.concat(systemChannels) : $angular.copy(channels);
            }

            function getLiveChannelByNumber(channelNumber) {
                var channelNumberInt = parseInt(channelNumber, 10);
                for (var i = 0; i < liveChannels.length; i++) {
                    var liveChannel = liveChannels[i];
                    if (liveChannel.channelNo !== channelNumberInt) {
                        continue;
                    }

                    return liveChannel;
                }
            }

            function searchChannels(query, maxResultCount) {
                if (!query) {
                    return [];
                }

                var result = [];
                var isQueryMatch = function(query, channel, field) {
                    var queryMatch = channel[field].indexOf(query) > -1;
                    if (!queryMatch) {
                        return false;
                    }

                    // skip content (parent is playlist)
                    var parent = channelsLookup[channel.parentChannelId];
                    return !parent || parent.type !== 'playlist';
                };

                query = query.toLowerCase();
                maxResultCount = maxResultCount || channels.length;
                for (var i = 0; i < channels.length && result.length < maxResultCount; i++) {
                    var currentChannel = channels[i];
                    // search in title
                    if (currentChannel.title) {
                        if (isQueryMatch(query, currentChannel, 'titleLowercase')) {
                            result.push(currentChannel);
                        }

                        continue;
                    }

                    // look from description field
                    if (!currentChannel.description || !isQueryMatch(query, currentChannel, 'descriptionLowercase')) {
                        continue;
                    }

                    result.push(currentChannel);
                }

                return result;
            }

            return {
                generateUrlFromSession: generateUrlFromSession,
                isReady: checkIsReady,
                subscribeToUpdates: function(callback) {
                    subscribers.push(callback);
                },
                reset: reset,

                unsubscribeFromUpdates: function(callback) {
                    for (var i = 0; i < subscribers.length; i++) {
                        if (subscribers[i] == callback) {
                            subscribers.splice(i, 1);
                            return;
                        }
                    }
                },

                getLiveChannels: function() {
                    return [].concat(liveChannels);// $angular.copy(liveChannels);
                },
                getProgramGuide: function() {
                    for (var i = 0; i < channels.length; i++) {
                        if (channels[i].type == 'epg') {
                            return channels[i];
                        }
                    }

                    return null;
                },
                getChannels: function() {
                    return [].concat(channels);
                },
                getChannelGroups: function() {
                    return [].concat(groups);
                },
                getChannelsByGroup: function(groupId) {
                    var channelArray = [];
                    for (var i = 0; i < channels.length; i++) {
                        if (channels[i].groupId == groupId) {
                            channelArray.push(channels[i]);
                        }
                    }

                    return channelArray;
                },
                getChannelByChannelId: function(channelId) {
                    var channel = getChannelByChannelId(getUnfilteredChannels(), channelId);
                    return channel;
                },
                searchChannelByChannelField: function(toSearch, fields) {
                    if (!fields || !fields.length) {
                        return undefined;
                    }

                    var search = dataUtil.toLowerCase(toSearch);
                    for (var i = 0; i < fields.length; i++) {
                        var field = fields[i];
                        if (!field) {
                            continue;
                        }

                        var result = $filter('filter')(channels, function(element) {
                            return dataUtil.toLowerCase(element[field]) === search;
                        });

                        if (!result.length) {
                            continue;
                        }

                        return result[0];
                    }

                    return undefined;
                },
                getChannelGroupById: getChannelGroupById,
                getChannelByPath: function(path) {
                    var channel = getChannelByPath(channels, path);
                    return channel;
                },
                sortChannelsBy: function(channels, attribute) {
                    var i;
                    var k;
                    var g1;
                    var g2;
                    var movement = false;
                    if (attribute.toLowerCase() == 'title') {
                        // Sort channels based on name
                        for (i = 0; i < channels.length; i++) {
                            for (k = 0; k < channels.length - 1; k++) {
                                g1 = channels[k];
                                g2 = channels[k + 1];
                                if (g1.title.toLowerCase() > g2.title.toLowerCase()) {
                                    channels[k] = g2;
                                    channels[k + 1] = g1;
                                    movement = true;
                                }
                            }

                            if (!movement) {
                                break;
                            }
                        }
                    } else if (attribute.toLowerCase() == 'date') {
                        // Sort channels based on date
                        for (i = 0; i < channels.length; i++) {
                            for (k = 0; k < channels.length - 1; k++) {
                                g1 = channels[k];
                                g2 = channels[k + 1];
                                g1.time = g1.date.getTime();
                                g2.time = g2.date.getTime();
                                if (g1.date.getTime() < g2.date.getTime()) {
                                    channels[k] = g2;
                                    channels[k + 1] = g1;
                                    movement = true;
                                }
                            }

                            if (!movement) {
                                //  break;
                            }
                        }
                    }

                    return channels;
                },
                doSearch: searchChannels,
                quickSearch: function(query) {
                    return searchChannels(query, MAX_QUICK_SEARCH_RESULT);
                },
                addHistory: function(url) {
                    pageHistory.push(url);
                },
                getHistory: function() {
                    return pageHistory;
                },
                removeHistory: function() {
                    return pageHistory.pop();
                },
                clearHistory: function() {
                    pageHistory = [];
                },
                setSelectedChannel: function(channelId, view) {
                    selectedChannelHistory[view] = channelId;
                },
                getSelectedChannel: function(view) {
                    return selectedChannelHistory[view];
                },
                getBackgroundChannel: function() {
                    return backgroundChannel;
                },
                clearSelectedChannels: function(channelId, view) {
                    selectedChannelHistory = {};
                },
                createGridElement: function(channel, type) {
                    return createGridElement(channel, type);
                },
                setStartupFlag: function(startup) {
                    startupFlag = startup;
                },
                getStartupFlag: function() {
                    return startupFlag;
                },
                getThemeSettings: function() {
                    return themeSettings;
                },
                setPlayApp: function() {
                    isRunningPlayApp = true;
                },
                setLastQuery: function(query) {
                    lastQuery = query;
                },
                getLastQuery: function() {
                    return lastQuery;
                },
                generateUrlByChannelFullPath: generateUrlByChannelFullPath,
                generateUrlByChannelId: generateUrlByChannelId,
                generateExclusiveUrlByChannelId: generateExclusiveUrlByChannelId,
                generateUrlByChannelPath: generateUrlByChannelPath,
                generateUrlByLiveId: generateUrlByLiveId,
                generateUrlByChannelNumber: generateUrlByChannelNumber,
                generateUrlByRecent: generateUrlByRecent,
                getLiveChannelByNumber: getLiveChannelByNumber,
            };
        }]);
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('signsHTML5');
    var getElement = $angular.element;
    app.controller('EPGCtrl', ["$window", "$scope", "$compile", "$document", "$element", "$timeout", "$interval", "$location", "$route", "$q", "$filter", "coreFrameworkService", "channelService", "halRemoteService", "REMOTEVCODE", "navigationHelper", "themeHelper", "REMOTEEVENTTYPE", "networkContentRouterService", "scriptingService", "publicService", "dataUtil", "configurationService", "deviceAlertService", function($window, $scope, $compile, $document,
        $element, $timeout, $interval, $location, $route, $q, $filter,
        coreFrameworkService, channelService, halRemoteService, REMOTEVCODE, navigationHelper, themeHelper,
        REMOTEEVENTTYPE, networkContentRouterService, scriptingService, publicService, dataUtil,
        configurationService, deviceAlertService) {
        var homeUrl = '/tv/channelbrowser';
        var playerApi = null;
        var liveChannels = [];
        var cellWidth = 15;
        var cellHeight = 36;
        var startHour = 0;
        var startMin = 0;
        var startTimeslot = null;
        var endTimeslot = null;
        var currentHour = 0;
        var currentMin = 0;
        var currentTimeslot = null;
        var startVerticalOffset = 0;
        var channelsPerPage = 0;
        var timeslotsPerPage = 0;
        var selectedCell = null;
        var pendingVerticalScroll = null;
        var isVerticalScrolling = false;
        var pendingHorizontalScroll = null;
        var isHorizontalScrolling = false;
        var channelPreviewTimer = null;
        var pageInitTimer = null;
        var resizeWindowTimeout;
        var updateProgramInstructionHandler;
        $scope.currentChannel = null;
        $scope.selectedProgram = null;
        $scope.currentDay = 'Today';
        $scope.currentContent = null;
        $scope.showLoader = true;
        $scope.showContentLoader = true;
        $scope.programInstruction = '';
        $scope.theme = themeHelper.getDefaultTheme();

        function init() {
            // Handle view destroy
            $scope.$on('$destroy', function() {
                halRemoteService.unRegisterEvents(onRemoteEvent);
                if (playerApi) {
                    playerApi.unload();
                }

                configurationService.unsubscribe(applyTheme);
                deviceAlertService.unsubscribeExclusiveChannelStart(onExclusiveChannelStarted);
                channelService.unsubscribeFromUpdates(onChannelServiceUpdate);
                stopUpdateProgramInstruction();
                // cancel timers
                if (channelPreviewTimer != null) {
                    $timeout.cancel(channelPreviewTimer);
                    channelPreviewTimer = null;
                }

                if (pageInitTimer != null) {
                    $timeout.cancel(pageInitTimer);
                }

                if (resizeWindowTimeout) {
                    $timeout.cancel(resizeWindowTimeout);
                    resizeWindowTimeout = null;
                }

                // remove the resize/orientation event
                $window.removeEventListener('resize', onResizeWindow);
                $window.removeEventListener('orientationchange', onResizeWindow);
            });

            // Register for remote input
            halRemoteService.registerEvents(onRemoteEvent);
            deviceAlertService.subscribeExclusiveChannelStart(onExclusiveChannelStarted);

            // Subscribe to channel service updates
            channelService.subscribeToUpdates(onChannelServiceUpdate);

            // set startup to true
            channelService.setStartupFlag(true);
            configurationService.subscribe(applyTheme);
            // Initalise page after 100ms wait
            pageInitTimer = $timeout(function() {
                initTimeslots();
                // init epgs
                initLiveChannels();
                if (liveChannels == null || liveChannels.length == 0) {
                    // remove from page history
                    channelService.removeHistory();
                    $location.url(homeUrl);
                    return;
                } else {
                    // init channel list
                    initChannelListItems();
                    // init programs
                    initPrograms();
                    // uiUpdate
                    uiUpdate();
                }

                startUpdateProgramInstruction();
                toggleLoading(false);
            }, 1000);

            // handle the resize/orientation event
            $window.addEventListener('resize', onResizeWindow);
            $window.addEventListener('orientationchange', onResizeWindow);
        }

        function onExclusiveChannelStarted(channel) {
            if (!channel) {
                return;
            }

            // if we have exclusive channel, go to home first so that it will be redirected
            onActionHome();
        }

        function onResizeWindow(e) {
            toggleLoading(true);

            if (resizeWindowTimeout) {
                $timeout.cancel(resizeWindowTimeout);
                resizeWindowTimeout = null;
            }

            resizeWindowTimeout = $timeout(function() {
                $route.reload();
            }, 500);
        }

        function onChannelServiceUpdate() {
            $route.reload();
        }

        // Init Channel List in the table
        function initLiveChannels() {
            var channels = channelService.getLiveChannels();
            var i;
            // only include channels with epg
            for (i = 0; i < channels.length; i++) {
                var c = channels[i];
                if (c.epg != null && c.epg.length > 0) {
                    liveChannels.push(c);
                }
            }

            // Sort live channels by channel number
            liveChannels = $filter('orderBy')(liveChannels, 'channelNo');
        }

        // Creates the timeslots in the header
        function initTimeslots() {
            // Get Cell height
            var cellSample = getElement('.cell-size');
            if (cellSample.length > 0) {
                cellHeight = getElement(cellSample[0]).height();
            }

            // set timeslots per page
            var vwPerPixel = 100 / $document[0].documentElement.clientWidth;
            var containerWidthPx = getElement('#timeslotsContainer').width();
            var containerWidthVw = containerWidthPx * vwPerPixel;
            cellWidth = containerWidthVw / 4.85;
            timeslotsPerPage = Math.floor(containerWidthVw / cellWidth);

            var time = moment();
            if (time.minute() < 30) {
                time.minute(0);
            } else {
                time.minute(30);
            }

            startHour = currentHour = time.hour();
            startMin = currentMin = time.minute();

            // set start and end timeslot time
            startTimeslot = moment();
            // startTimeslot.subtract(1, 'days');
            startTimeslot.hour(startHour);
            startTimeslot.minute(startMin);
            startTimeslot.second(0);
            startTimeslot.millisecond(0);
            endTimeslot = moment(startTimeslot);
            endTimeslot.add(1, 'days');

            // Set header width
            var headerEl = getElement('#epgHeader');
            headerEl.width((48 * cellWidth) + 'vw');
            // 24 hours in 30min slots
            for (var i = 0; i < 48; i++) {
                var t = time.format('h.mma');
                headerEl.append('<div class="header-cell" style="width: ' + cellWidth + 'vw">' + t + '</div>');
                time.add(30, 'm');
            }

            // set current timeslot
            currentTimeslot = moment(startTimeslot);
        }

        // Init Channel List in the table
        function initChannelListItems() {
            // set number of channels per page
            var pageHeight = getElement('#channelListContainer').height();
            channelsPerPage = Math.floor(pageHeight / cellHeight);

            // resize
            var newHeight = channelsPerPage * cellHeight;
            getElement('#channelListContainer').css('height', newHeight + 'px');
            getElement('#programContainer').css('height', newHeight + 'px');

            // Set container height
            var channelListEl = getElement('#epgChannelList');

            // append channel
            for (var i = 0; i < liveChannels.length; i++) {
                var c = liveChannels[i];

                // select the first channel in the list by default
                if (i == 0) {
                    $scope.currentChannel = c;
                }

                var html = '<div ng-click="onEpgChannelClick(\'' + c.id + '\')" id="' + c.id + '" style="height: ' + cellHeight + 'px">';
                html += '<div class="epg-channel" style="width: 100%;">';
                html += '<div class="channel-no">' + c.channelNo + '</div>';
                html += '<div class="name">' + c.title + '</div>';

                channelListEl.append(html);
            }

            // Set container height
            var channelListHeight = liveChannels.length * cellHeight;
            channelListEl.height(channelListHeight + 'px');

            // set the container height if channel list is less than the container height
            if (channelListHeight < pageHeight) {
                var headerHeight = getElement('.header').children(0).height();
                getElement('#tableContainer').height((channelListHeight + headerHeight) + 'px');
            }

            // compile the dynamic html to work with angular
            $compile(channelListEl)($scope);
        }

        $scope.onEpgChannelClick = function(id) {
            if (id) {
                for (var i = 0; i < liveChannels.length; i++) {
                    var channel = liveChannels[i];
                    if (channel.id == id) {
                        for (var k = 0; k < channel.epg.length; k++) {
                            var p = channel.epg[k];
                            // find the current program
                            if (isProgramValid(p)) {
                                $scope.currentChannel = channel;
                                gotoProgram(p);
                                return;
                            }
                        }
                    }
                }
            }
        };

        // Init program table
        function initPrograms() {
            // Set container height
            var programsEl = getElement('#epgPrograms');
            programsEl.height((liveChannels.length * cellHeight) + 'px');

            // Set container width
            programsEl.width((49 * cellWidth) + 'vw');

            // calculate cell width for every minute
            var widthPerMin = cellWidth / 30;

            // get previously selected channel
            var lastSelectedId = channelService.getSelectedChannel($location.url());

            // set selected channel offset

            // append programs
            var programOffset = 0;
            for (var i = 0; i < liveChannels.length; i++) {
                var c = liveChannels[i];
                if (c.epg == null || c.epg.length == 0) {
                    continue;
                }

                // append row
                var html = [];
                html.push('<div style="height: '); html.push(cellHeight); html.push('px; transform:translateZ(0);'); html.push(' position:relative;">');

                var cls = null;
                programOffset = 0;
                for (var k = 0; k < c.epg.length; k++) {
                    var p = c.epg[k];
                    var endTime;
                    var fillerProgram;
                    // If this is our first program
                    if (programOffset == 0) {
                        // Does it start after the startTimeSlow
                        if (p.start > startTimeslot) {
                            endTime = p.start;// moment(p.stop + moment.duration(30, "minutes"));
                            var startTime = moment(endTime - moment.duration(30, 'minutes'));
                            var endTimeMins = endTime.minutes();
                            if (endTimeMins == 0) { // Set start time to mid point of the hour
                                startTime = moment(endTime - moment.duration(30, 'minutes'));
                            } else if (endTimeMins > 30) { // Set start time to mid point of the hour
                                startTime = moment(endTime - moment.duration(endTimeMins - 30, 'minutes'));
                            } else { // Set start time the end of the hour
                                startTime = moment(endTime - moment.duration(endTimeMins, 'minutes'));
                            }

                            // Create a program to fill gap
                            fillerProgram = {
                                id: c.id + '_filler_' + c.epg.length,
                                channelId: p.channelId,
                                start: startTime,
                                stop: endTime,
                                title: 'No Programming Information',
                                filler: true,
                                desc: c.description
                            };
                            p = fillerProgram;
                            c.epg.unshift(fillerProgram);

                            // Restart loop
                            k = -1;
                            programOffset = 0;
                            continue;
                        }
                    }

                    // default to first program
                    if (i == 0 && programOffset == 0) {
                        $scope.selectedProgram = p;
                    }

                    if (programOffset == 0 && lastSelectedId == c.id) {
                        $scope.selectedProgram = p;
                    }

                    // determine cell width
                    var durationMin = 0;
                    // if the show has already started, calculate duration from the start of timeslot
                    if (p.start < startTimeslot) {
                        durationMin = moment.duration(p.stop.diff(startTimeslot)).asMinutes();
                    } else if (p.stop > endTimeslot) {// if the stop time is bigger than the end timeslot, calculate duration up to the end of timeslot
                        durationMin = moment.duration(endTimeslot.diff(p.start)).asMinutes();
                    } else {
                        durationMin = moment.duration(p.stop.diff(p.start)).asMinutes();
                    }

                    var timeFromStart = moment.duration(p.start.diff(startTimeslot)).asMinutes();
                    var width = widthPerMin * durationMin;
                    var left = widthPerMin * timeFromStart;
                    // assign css class
                    if (programOffset == 0) {
                        cls = 'now';
                        // Clip to the left to the start time
                        if (left < 0) {
                            left = 0;
                        }
                    } else {
                        cls = 'next';
                        // left = 0;
                        if (p.filler) {
                            cls = 'no-program';
                        }
                    }

                    // check that starttime is within the timeslots
                    if (p.stop <= startTimeslot) {
                        // Before our start time
                    } else if (p.start >= endTimeslot) {
                        // Past are end slot
                    } else { // Valid program to display
                        // create the cell
                        html = html.concat(generateProgramElemment(p, cls, left, width));
                        programOffset++;
                    }

                    // Check is tis the last program
                    if (k == c.epg.length - 1) {
                        // Does program end before endTimeSlot
                        if (p.stop < endTimeslot) {
                            endTime = moment(p.stop + moment.duration(30, 'minutes'));
                            var startTimeMins = p.stop.minutes();
                            if (startTimeMins >= 30) { // Set end time to end of the hour
                                endTime = moment(p.stop + moment.duration(60 - startTimeMins, 'minutes'));
                            } else { // Set end time the mid point of the hour
                                endTime = moment(p.stop + moment.duration(30 - startTimeMins, 'minutes'));
                            }

                            // Create a program to fill gap
                            fillerProgram = {
                                id: c.id + '_filler_' + c.epg.length,
                                channelId: p.channelId,
                                start: moment(p.stop),
                                stop: endTime,
                                title: 'No Programming Information',
                                filler: true,
                                desc: c.description
                            };
                            p = fillerProgram;
                            c.epg.push(fillerProgram);
                        }
                    }
                }

                html.push('</div>');
                programsEl.append(html.join(''));
            }

            // compile the dynamic html to work with angular
            $compile(programsEl)($scope);

            // go to the selected program
            gotoProgram($scope.selectedProgram);
        }

        $scope.onEpgPreviewClick = function(force) {
            onSelect(force);
        };

        $scope.onEpgProgramClick = function(e) {
            var id = e.target.id;
            if (id) {
                var channelId = id.split('_')[0];
                if (channelId) {
                    for (var i = 0; i < liveChannels.length; i++) {
                        var channel = liveChannels[i];
                        if (channel.id == channelId) {
                            for (var k = 0; k < channel.epg.length; k++) {
                                var p = channel.epg[k];
                                // find the current program
                                if (p.id == id) {
                                    $scope.currentChannel = channel;
                                    gotoProgram(p);
                                    return;
                                }
                            }
                        }
                    }
                }
            }
        };

        function uiUpdate() {
            var epgContainer = getElement('#epgContainer');
            var selectedProgramDiv = getElement('#selectedProgram');
            var withPaddingHeight = epgContainer[0].offsetHeight;
            var withoutPaddingHeight = epgContainer.height();
            selectedProgramDiv.css('margin-bottom', ((withPaddingHeight - withoutPaddingHeight) / 2) + 'px');
        }

        function generateProgramElemment(program, cls, position, width) {
            // create the cell
            var html = [];
            html.push('<div ng-click="onEpgProgramClick($event)" id="'); html.push(program.id);
            html.push('" class="epg-program '); html.push(cls);
            html.push('" style="position:absolute; height:' + cellHeight + 'px; width: '); html.push(width); html.push('vw;');
            html.push('left:'); html.push(position); html.push('vw;">');
            html.push(program.title); html.push('</div>');
            return html;
        }

        // Scroll to channel
        function scrollToChannel(offset) {
            // Make sure the offset is within bound
            if (offset >= liveChannels.length || offset < 0) {
                return;
            }

            // If currently scrolling, set the new offset to pending
            if (isVerticalScrolling) {
                pendingVerticalScroll = offset;
                return;
            }

            // Reset pending
            pendingVerticalScroll = null;

            // Set that we are currently scrolling
            isVerticalScrolling = true;

            // make sure the last channel sticks to the bottom
            var maxOffset = liveChannels.length - channelsPerPage;
            if (startVerticalOffset > maxOffset) {
                startVerticalOffset = maxOffset;
            }

            // Determine the Y scroll based on the current page
            var yScroll = startVerticalOffset * cellHeight;
            var programsEl = getElement('#epgPrograms');
            var channelListEl = getElement('#epgChannelList');

            // Scroll to the channel
            channelListEl.velocity({translateY: -yScroll + 'px', translateZ: 0}, {duration: 300, queue: false});
            // Scroll the program to the row
            programsEl.velocity({translateY: -yScroll + 'px', translateZ: 0}, {
                duration: 300, queue: false, complete: function() {
                    // Scrolling finished
                    isVerticalScrolling = false;
                    // On complete of scroll, check do we have a pending channel offset
                    if (pendingVerticalScroll) {
                        scrollToChannel(pendingVerticalScroll);
                    }
                }
            });
        }

        // Scroll to timeslot
        function scrollToTime(hour, min) {
            var cellIndex = 0;
            var time = (hour * 60) + min;
            var startCell = (startHour * 60) + startMin;
            var endCell = startCell + 30;
            var found = false;
            for (var i = 0; i < 48; i++) {
                if (time >= startCell && time < endCell) {
                    cellIndex = i;
                    found = true;
                    break;
                }

                // Increment the cell time
                startCell += 30;
                endCell += 30;

                // Wrap time around day boundaries
                if (startCell >= (24 * 60)) {
                    startCell = 0;
                    endCell = 30;
                }
            }

            // Make sure we found a cell that contains the time
            if (!found) {
                return;
            }

            // If currently scrolling, set the new time to pending
            if (isHorizontalScrolling) {
                pendingHorizontalScroll = {hour: hour, min: min};
                return;
            }

            // Reset pending
            pendingHorizontalScroll = null;

            // Set that we are currently scrolling
            isHorizontalScrolling = true;

            // make sure the last program sticks to the right
            var maxCellIndex = 48 - timeslotsPerPage;
            if (cellIndex > maxCellIndex) {
                cellIndex = maxCellIndex;
            }

            // Determine the X scroll based on the slot index
            var containerWidthPx = getElement('#timeslotsContainer').width();
            var xScroll = (cellIndex * (containerWidthPx / 4.85));

            // Scroll the header to the cell
            var headerEl = getElement('#epgHeader');
            headerEl.velocity({translateX: -xScroll + 'px', translateZ: 0}, {duration: 300, queue: false});

            // Scroll the program to the cell
            var programsEl = getElement('#epgPrograms');
            programsEl.velocity({translateX: -xScroll + 'px', translateZ: 0}, {
                duration: 300, queue: false, complete: function() {
                    // Scrolling finished
                    isHorizontalScrolling = false;
                    // On complete of scroll, check do we have a pending horizontal scroll
                    if (pendingHorizontalScroll) {
                        scrollToTime(pendingHorizontalScroll.hour, pendingHorizontalScroll.min);
                    }
                }
            });
        }

        function setCurrentDay() {
            // set current day
            var now = moment();
            if ($scope.selectedProgram.start.isSame(now, 'day')) {
                $scope.currentDay = 'Today';
            } else if ($scope.selectedProgram.start.isAfter(now, 'day')) {
                $scope.currentDay = 'Tomorrow';
            } else {
                $scope.currentDay = 'Today';
            }
        }

        function gotoProgram(program) {
            if (program == null) {
                return;
            }

            // Play the selected channel preview
            if (!$scope.currentContent || ($scope.selectedProgram.channelId != program.channelId)) {
                // toggle loading icon
                toggleContentErrorMessage(false);
                toggleContentLoading(true);

                if (channelPreviewTimer != null) {
                    $timeout.cancel(channelPreviewTimer);
                    channelPreviewTimer = null;
                }

                channelPreviewTimer = $timeout(function() {
                    playChannel(program);
                    channelPreviewTimer = null;
                }, 500);
            }

            // set selected program
            $scope.selectedProgram = program;
            setProgramInstruction();

            // hightlight the program
            highlightProgram(program.id);

            // set current day
            setCurrentDay();

            // Boundary checks
            if (program.stop < startTimeslot || program.start > endTimeslot) {
                return;
            }

            // Check is program fully horizontally visible in view
            var visible = false;
            var currentTimeslotEnd = moment(currentTimeslot).add(timeslotsPerPage * 30, 'minute');
            if (program.start >= currentTimeslot && program.stop <= currentTimeslotEnd) {
                visible = true;
            }

            if (!visible) {
                // Set current timeslot
                var start = program.start;
                // check that start time is within boundary of the timeslots
                if (start < startTimeslot) {
                    start = startTimeslot;
                }

                currentTimeslot = moment(start);
                currentHour = currentTimeslot.hour();
                currentMin = currentTimeslot.minute();

                // Scroll to program
                scrollToTime(currentHour, currentMin);
            }

            // Check is program vertically visible in view
            visible = false;
            var currentRowOffset = 0;
            // Get program row offset
            for (var i = 0; i < liveChannels.length; i++) {
                var e = liveChannels[i];
                if (e.id == program.channelId) {
                    currentRowOffset = i;
                    break;
                }
            }

            // check if program is vertically visible, if not set vertical page offset
            var endVerticalOffset = startVerticalOffset + channelsPerPage - 1;
            if ((currentRowOffset >= startVerticalOffset) &&
                (currentRowOffset <= endVerticalOffset)) {
                visible = true;
            } else {
                if (currentRowOffset > endVerticalOffset) {
                    startVerticalOffset = endVerticalOffset + 1;
                } else {
                    startVerticalOffset = startVerticalOffset - channelsPerPage;
                    if (startVerticalOffset < 0) {
                        startVerticalOffset = 0;
                    }
                }
            }

            if (!visible) {
                // Scroll to channel
                scrollToChannel(currentRowOffset);
            }

            var updateLayoutSizeTimeout = $timeout(function() {
                updateLayoutSize();
                $timeout.cancel(updateLayoutSizeTimeout);
            }, 100);
        }

        // play the selected channel
        function playChannel(program) {
            var currentContent = null;

            // Unload current playig preview
            playerApi.unload();

            // get channel from channelservice
            var channel = channelService.getChannelByChannelId(program.channelId);

            // get content from asset
            if (channel && channel.assets != undefined) {
                for (var i = 0; i < channel.assets.length; i++) {
                    var asset = channel.assets[i];
                    if (asset.type == 'content') {
                        currentContent = $angular.copy(asset);
                        break;
                    }
                }
            }

            if (currentContent == null) {
                toggleContentErrorMessage(true);
                return;
            }

            networkContentRouterService.resolve(currentContent.mediaUrl, 'play-content-routing-service', {type: currentContent.mediaType, cache: true})
                .then(function(url) {
                    currentContent.mediaUrl = url;
                    getAppProperties(currentContent);
                    $scope.currentContent = currentContent;
                }, function(err) {
                    getAppProperties(currentContent);
                    $scope.currentContent = currentContent;
                });
        }

        function getAppProperties(currentContent) {
            currentContent.options = {};
            if (scriptingService.retrieveAppProperty('custom.media.video.cc')) {
                currentContent.options.closecaption = scriptingService.retrieveAppProperty('custom.media.video.cc');
            }

            if (scriptingService.retrieveAppProperty('custom.media.video.cc.brightsign')) {
                currentContent.options.closecaptionInfoBrightsign = scriptingService.retrieveAppProperty('custom.media.video.cc.brightsign');
            }

            if (scriptingService.retrieveAppProperty('custom.media.video.cc.chrome')) {
                currentContent.options.closecaptionInfoChrome = scriptingService.retrieveAppProperty('custom.media.video.cc.chrome');
            }

            if (scriptingService.retrieveAppProperty('custom.media.video.cc.samsung')) {
                currentContent.options.closecaptionInfoSamsung = scriptingService.retrieveAppProperty('custom.media.video.cc.samsung');
            }

            if (scriptingService.retrieveAppProperty('custom.media.video.cc.track')) {
                currentContent.options.closecaptionTrack = scriptingService.retrieveAppProperty('custom.media.video.cc.track');
            }

            if (scriptingService.retrieveAppProperty('player.brightsign.forceac3')) {
                currentContent.options.forceac3audio = scriptingService.retrieveAppProperty('player.brightsign.forceac3');
            }
        }

        // Highlight program
        function highlightProgram(programId) {
            removeHighlight();
            // find the program element
            var programEl = null;
            var rows = getElement('#epgPrograms').children();
            var found = false;
            if (rows) {
                for (var i = 0; i < rows.length; i++) {
                    var cells = rows[i].children;
                    if (cells) {
                        for (var k = 0; k < cells.length; k++) {
                            var c = getElement(cells[k]);
                            if (c.attr('id') == programId) {
                                programEl = c;
                                found = true;
                                break;
                            }
                        }
                    }

                    if (found) {
                        break;
                    }
                }
            }

            if (programEl) {
                // highlight the new program
                programEl.addClass('selected');
                selectedCell = programEl;
            }
        }

        // Remove show highlight
        function removeHighlight() {
            if (selectedCell) {
                selectedCell.removeClass('selected');
            }
        }

        function isProgramValid(prog) {
            if (prog.start.isSame(startTimeslot)) {
                return true;
            } else if (prog.start > startTimeslot && prog.start < endTimeslot) {
                return true;
            } else if (prog.start < startTimeslot && prog.stop > startTimeslot) {
                return true;
            }

            return false;
        }

        function doProgramsOverlap(p1, p2) {
            if (p1.start.isSame(p2.start)) {
                return true;
            } else if (p1.start > p2.start && p1.start < p2.stop) {
                return true;
            } else if (p1.start < p2.start && p1.stop > p2.start) {
                return true;
            }

            return false;
        }

        function getChannelIndex(channelId) {
            for (var i = 0; i < liveChannels.length; i++) {
                var channel = liveChannels[i];
                if (channel.id == channelId) {
                    return i;
                }
            }

            return 0;
        }

        // get program from the prev channel
        function getUpProgram() {
            if ($scope.selectedProgram) {
                var channelIndex = getChannelIndex($scope.selectedProgram.channelId);
                // Check are we already at the top of the list
                if (i == 0) {
                    return null;
                }

                // Itterate down the list until we find a channel with a program that overlaps
                for (var i = channelIndex - 1; i >= 0; i--) {
                    var channel = liveChannels[i];
                    for (var k = 0; k < channel.epg.length; k++) {
                        var prog = channel.epg[k];
                        if (doProgramsOverlap($scope.selectedProgram, prog) &&
                            isProgramValid(prog)) {
                            $scope.currentChannel = channel;
                            return prog;
                        }
                    }
                }
            }

            return null;
        }

        // get program from the next channel
        function getDownProgram() {
            if ($scope.selectedProgram) {
                var channelIndex = getChannelIndex($scope.selectedProgram.channelId);
                // Check are we already at the bottom of the list
                if (i == liveChannels.length - 1) {
                    return null;
                }

                // Itterate up the list until we find a channel with a program that overlaps
                for (var i = channelIndex + 1; i < liveChannels.length; i++) {
                    var channel = liveChannels[i];
                    for (var k = 0; k < channel.epg.length; k++) {
                        var prog = channel.epg[k];
                        if (doProgramsOverlap($scope.selectedProgram, prog)
                            && isProgramValid(prog)) {
                            $scope.currentChannel = channel;
                            return prog;
                        }
                    }
                }
            }

            return null;
        }

        // get next program to the left
        function getLeftProgram() {
            if ($scope.selectedProgram) {
                for (var i = 0; i < liveChannels.length; i++) {
                    var channel = liveChannels[i];
                    if (channel.id == $scope.selectedProgram.channelId) {
                        // check that the current program is not the first one in the timeslot
                        if ($scope.selectedProgram.start <= startTimeslot) {
                            return null;
                        }

                        for (var k = 0; k < channel.epg.length; k++) {
                            var p = channel.epg[k];
                            // find the current program
                            if (p == $scope.selectedProgram) {
                                // if the current program is not the first one, return the previous program
                                if (k > 0) {
                                    return channel.epg[k - 1];
                                }
                            }
                        }
                    }
                }
            }

            return null;
        }

        // get next program to the right
        function getRightProgram() {
            if ($scope.selectedProgram) {
                for (var i = 0; i < liveChannels.length; i++) {
                    var channel = liveChannels[i];
                    if (channel.id == $scope.selectedProgram.channelId) {
                        // check that the current program is not the last one in the timeslot
                        if ($scope.selectedProgram.stop >= endTimeslot) {
                            return null;
                        }

                        for (var k = 0; k < channel.epg.length; k++) {
                            var p = channel.epg[k];
                            // find the current program
                            if (p == $scope.selectedProgram) {
                                // if the current program is not the last one, return the next program
                                if (k < (channel.epg.length - 1)) {
                                    return channel.epg[k + 1];
                                }
                            }
                        }
                    }
                }
            }

            return null;
        }

        function toggleLoading(show) {
            $scope.showLoader = show;
        }

        function toggleContentLoading(show) {
            $scope.showContentLoader = show;
        }

        function toggleContentErrorMessage(show) {
            var errorMessage = $element.find('#loadErrorContent');
            errorMessage.velocity('stop');
            if (show) {
                errorMessage.css('display', '');
                errorMessage.velocity({opacity: 1}, {duration: 300});
            } else {
                errorMessage.velocity({opacity: 0}, {duration: 300, display: 'none'});
            }
        }

        var onRemoteEvent = function(remoteEvent) {
            // Only listen to down button events
            if (remoteEvent.eventType != REMOTEEVENTTYPE.buttonDown) {
                return;
            }

            var selectedProgram;
            // Perform action based on Virtual code
            switch (remoteEvent.vcode) {
                case REMOTEVCODE.up: // Up Key
                    selectedProgram = onActionUp();
                    break;
                case REMOTEVCODE.down: // Down Key
                    selectedProgram = onActionDown();
                    break;
                case REMOTEVCODE.left: // Left Key
                    selectedProgram = onActionLeft();
                    break;
                case REMOTEVCODE.right: // Right Key
                    selectedProgram = onActionRight();
                    break;
                case REMOTEVCODE.select: // option/alt Key
                    selectedProgram = true;
                    onSelect();
                    break;
                case REMOTEVCODE.home:
                    onActionHome();
                    break;
                case REMOTEVCODE.back: // Command/Windows Key
                    if (!publicService.getServices().deviceMenu.isVisible()) {
                        selectedProgram = true;
                        onActionBack();
                        remoteEvent.preventDefault();
                    }

                    break;
            }

            // if we didn't select anything, just update the program instruction
            if (!selectedProgram) {
                setProgramInstruction();
            }
        };

        function onActionHome() {
            navigationHelper.clearHistoryAndGoHome();
        }

        // On Left clicked
        function onActionLeft() {
            // get the correct program
            var p = getLeftProgram();
            if (p) {
                // go to the selected program
                gotoProgram(p);
            }

            return p;
        }

        // On Right clicked
        function onActionRight() {
            // get the correct program
            var p = getRightProgram();
            if (p) {
                // go to the selected program
                gotoProgram(p);
            }

            return p;
        }

        // On Up clicked
        function onActionUp() {
            // get the correct program
            var p = getUpProgram();
            if (p) {
                // go to the selected program
                gotoProgram(p);
            }

            return p;
        }

        // On Down clicked
        function onActionDown() {
            // get the correct program
            var p = getDownProgram();
            if (p) {
                // go to the selected program
                gotoProgram(p);
            }

            return p;
        }

        function onActionBack() {
            // remove from pageHistory
            channelService.removeHistory();
            // set selected channel id
            channelService.setSelectedChannel(null, $location.url());
            $location.url(homeUrl);
        }

        // On Select
        function onSelect(force) {
            // check if selectedProgram is not yet initialized or program is in the future, return.
            if (!force && (dataUtil.isNullOrUndefined($scope.selectedProgram) || $scope.selectedProgram.start > moment())) {
                return;
            }

            // set selected channel id
            channelService.setSelectedChannel($scope.selectedProgram.channelId, $location.url());

            // add current page to pageHistory
            channelService.addHistory($location.url());

            // go to live channel page
            var url = '/tv/livechannel/' + $scope.selectedProgram.channelId;
            $location.url(url);
        }

        function updateLayoutSize() {
            var rightElement = document.getElementsByClassName('right')[0];
            var titleElement = document.getElementsByClassName('title');
            var timeElement = document.getElementsByClassName('time');
            var descElement = document.getElementsByClassName('desc');
            var instructionElement = document.getElementsByClassName('instruction');
            var rightHeight = getElement(rightElement).outerHeight();
            var titleHeight = getElement(titleElement).outerHeight();
            var timeHeight = getElement(timeElement).outerHeight();
            var instructionHeight = getElement(instructionElement).outerHeight();

            // reset
            getElement(descElement).css('max-height', '');

            // calculate max height for desc element
            var descMaxHeight = (rightHeight - (titleHeight + timeHeight + instructionHeight));
            getElement(descElement).css('max-height', descMaxHeight + 'px');
            getElement(descElement).css('position', 'relative');
        }

        $scope.onPlayerApi = function(api) {
            playerApi = api;
            toggleContentLoading(false);
        };

        // On Item load handler
        $scope.onItemLoad = function() {
            // Hide the spinner
            toggleContentLoading(false);
            toggleContentErrorMessage(false);
        };

        // Handle content load error
        $scope.onItemLoadError = function() {
            playerApi.unload();
            toggleContentErrorMessage(true);
            toggleContentLoading(false);
        };

        $scope.onItemComplete = function() {
            if (playerApi) {
                playerApi.setContentItem($scope.currentContent);
            }
        };

        function startUpdateProgramInstruction() {
            stopUpdateProgramInstruction();
            updateProgramInstructionHandler = $interval(setProgramInstruction, 30000);
        }

        function stopUpdateProgramInstruction() {
            if (updateProgramInstructionHandler) {
                $interval.cancel(updateProgramInstructionHandler);
                updateProgramInstructionHandler = null;
            }
        }

        function setProgramInstruction() {
            $scope.programInstruction = getProgramInstruction();
        }

        function getProgramInstruction() {
            if ($scope.selectedProgram) {
                var startMin = Math.ceil(moment.duration($scope.selectedProgram.start.diff(moment())).asMinutes());// eslint-disable-line max-len
                if (startMin <= 0) {
                    // only return message if we're using remote control/keyboard to navigate
                    return halRemoteService.getLastEventTime() ? 'Press SELECT to view' : '';
                } else if (startMin >= 60) {
                    var hours = Math.floor(startMin / 60);
                    var mins = startMin % 60;
                    var text = 'Starting in ' + hours + (hours == 1 ? ' hour' : ' hours');
                    if (mins != 0) {
                        text += ' ' + mins + (mins == 1 ? ' minute' : ' minutes');
                    }

                    return text;
                } else {
                    return 'Starting in ' + startMin + ' minutes';
                }
            }
        }

        function applyTheme() {
            themeHelper.applyTheme().then(applyThemeSetting);
        }

        function applyThemeSetting() {
            var settings = channelService.getThemeSettings();
            $scope.theme = themeHelper.getThemeSettings();

            var applyTag = function() {
                var promise = $q.defer();
                // link
                var playStyle = document.getElementById('playOverwriteStyle');
                if (!playStyle) {
                    playStyle = document.createElement('link');
                    playStyle.id = 'playOverwriteStyle';
                    playStyle.rel = 'stylesheet';
                    document.getElementsByTagName('head')[0].appendChild(playStyle);
                }

                // properties
                var overwriteStyleProp = document.getElementById('overwriteStyleProperties');
                if (!overwriteStyleProp) {
                    overwriteStyleProp = document.createElement('style');
                    overwriteStyleProp.id = 'overwriteStyleProperties';
                    overwriteStyleProp.type = 'text/css';
                    document.getElementsByTagName('head')[0].appendChild(overwriteStyleProp);
                }

                if (playStyle && overwriteStyleProp) {
                    promise.resolve();
                } else {
                    $timeout(function() {
                        promise.resolve();
                    }, 100);
                }

                return promise.promise;
            };

            var applySetting = function(setting) {
                // link
                var playStyle = document.getElementById('playOverwriteStyle');
                if (!setting || (setting && setting.name == 'dark')) {
                    playStyle.href = '';
                } else {
                    playStyle.href = 'assets/css/theme-light.css';
                }

                var overwriteStyleProp = document.getElementById('overwriteStyleProperties');
                // properties
                if (setting) {
                    var theme = setting;
                    // accent
                    // channel browser active item
                    overwriteStyleProp.innerHTML = '.channel-browser .channel-group .channel-list .ui-grid-item-active .title {background-color:' + theme.accent + ';}';
                    // live channel
                    overwriteStyleProp.innerHTML += '.live-channel .epg-overlay .program .program-schedule .now .time {background-color:' + theme.accent + ';}';
                    // left menu
                    overwriteStyleProp.innerHTML += '.theme-dark .list-group-item.active, .theme-dark .list-group-item.focus {background-color:' + theme.accent + ' !important;}';
                    // search icon
                    overwriteStyleProp.innerHTML += '.channel-browser .banner .search:hover, .channel-browser .banner .search:active, .channel-browser .banner .search-selected {background-color:' + theme.accent + ' !important;}';
                    // epg
                    overwriteStyleProp.innerHTML += '.epg .container .selected-program .right .instruction {color:' + theme.accent + ' !important;}';
                    overwriteStyleProp.innerHTML += '.epg .epg-table .selected {background-color:' + theme.accent + ' !important;}';
                } else {
                    overwriteStyleProp.innerHTML = '';
                }
            };

            var resolveSetting = function() {
                var setting;
                if (settings && settings.themes) {
                    for (var i = 0; i < settings.themes.length; i++) {
                        var theme = settings.themes[i];
                        if (settings.default == theme.name) {
                            setting = theme;
                            break;
                        }
                    }
                }

                applySetting(setting);
            };

            applyTag().then(resolveSetting);
        }

        $scope.epgBackClicked = function() {
            onActionBack();
        };

        coreFrameworkService.isReady().then(function() {
            // Init page once channel service is ready
            channelService.isReady().then(function() {
                applyTheme();
                init();
            });
        });
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('signsHTML5');
    var getElement = $angular.element;
    app.controller('LiveChannelCtrl', ["$scope", "$element", "$timeout", "$location", "$routeParams", "$q", "coreFrameworkService", "halPlatformService", "channelService", "halRemoteService", "REMOTEVCODE", "REMOTEEVENTTYPE", "networkContentRouterService", "coreConfigurationService", "navigationHelper", "publicService", "broadcastService", "contentHelper", "ChannelViewUtils", "themeHelper", "configurationService", "deviceAlertService", "logService", function($scope, $element, $timeout, $location, $routeParams, $q, coreFrameworkService,
        halPlatformService, channelService, halRemoteService, REMOTEVCODE, REMOTEEVENTTYPE,
        networkContentRouterService, coreConfigurationService, navigationHelper,
        publicService, broadcastService, contentHelper, ChannelViewUtils,
        themeHelper, configurationService, deviceAlertService, logService) {
        var homeUrl = '/tv/channelbrowser';
        var playerApi = null;
        var liveChannels = [];
        var util = new ChannelViewUtils();
        $scope.currentChannel = null;
        $scope.epgNow = null;
        $scope.epgNext = null;
        $scope.epgLater = null;
        $scope.currentContent = null;
        $scope.paneOpen = false;
        $scope.theme = themeHelper.getDefaultTheme();

        var currentOverlayToggle = null;
        var overlayTimer = null;
        var channelPlayTimeout = null;

        $scope.showLoader = true;
        var itemLoadErrorCounter;
        var errorRetryCount = 0;
        var overlayTransitionSupported = true;
        var previousLiveChannelColor;
        var previousThemeDarkColor;
        var previousBodyColor;
        var previousThemeDarkBackground;
        var autoHidePaneTimeout;
        var errorRetryWorkflowTimeout;
        var AUTO_HIDE_PANEL_TIMEOUT = 10000;
        var ERROR_RETRY_WORKFLOW_TIMEOUT = 30000;
        var LOG_KEY = 'appspace.play';

        function init() {
            itemLoadErrorCounter = 0;
            errorRetryCount = 0;
            // Handle view destroy
            $scope.$on('$destroy', function() {
                stopAutoHidePaneTimer();
                broadcastService.disableBackmostVideoLayer();
                halRemoteService.unRegisterEvents(onRemoteEvent);
                configurationService.unsubscribe(applyTheme);
                deviceAlertService.unsubscribeExclusiveChannelStart(onExclusiveChannelStarted);

                if (overlayTimer) {
                    $timeout.cancel(overlayTimer);
                }

                if (channelPlayTimeout != null) {
                    $timeout.cancel(channelPlayTimeout);
                }

                if (playerApi) {
                    playerApi.unload();
                }
            });

            var fs = halPlatformService.getFeatureSupport('rendering.overlay-transition');
            if (fs = null || fs != 'no') {
                overlayTransitionSupported = true;
            } else {
                overlayTransitionSupported = false;
            }

            // Register for remote input
            halRemoteService.registerEvents(onRemoteEvent);
            deviceAlertService.subscribeExclusiveChannelStart(onExclusiveChannelStarted);
            configurationService.subscribe(applyTheme);

            // init liveChannels
            liveChannels = [].concat(channelService.getLiveChannels());
            liveChannels.sort(function(a, b) {
                if (a.channelNo > b.channelNo) {
                    return 1;
                } else if (a.channelNo < b.channelNo) {
                    return -1;
                } else {
                    return 0;
                }
            });

            // set startup to true
            channelService.setStartupFlag(true);
            // set currentChannel
            $scope.currentChannel = getChannelFromQueryString();

            if ($scope.currentChannel == null) {
                onActionBack();
                return;
            }

            // init current channel epg
            initCurrentEpg();

            // play channel
            // delay if overlay transition is not supported
            var delay = overlayTransitionSupported ? 0 : 500;
            $timeout(function() {
                playChannel();
            }, delay);
        }

        function onExclusiveChannelStarted(channel) {
            if (!channel) {
                return;
            }

            // if we have exclusive channel, go to home first so that it will be redirected
            onActionHome();
        }

        function getChannelFromQueryString() {
            // get currentpath
            var url = $location.url();
            if (!url) {
                return null;
            }

            // get channelId
            var id = $routeParams.id;
            if (id) {
                // get channel
                return channelService.getChannelByChannelId(id);
            }

            var number = $routeParams.number;
            return number ? channelService.getLiveChannelByNumber(number) : null;
        }

        function initCurrentEpg() {
            if ($scope.currentChannel.channelNo == null) {
                $scope.currentChannel.channelNo = '';
            }

            $scope.epgNow = {start: null, elapsed: null, title: null, desc: null};
            $scope.epgNext = {start: null, title: null, desc: null};
            $scope.epgLater = {start: null, title: null, desc: null};
            var date = moment();
            var dur;
            if ($scope.currentChannel && $scope.currentChannel.epg) {
                for (var k = 0; k < $scope.currentChannel.epg.length; k++) {
                    var e = $scope.currentChannel.epg[k];
                    if (date >= e.start && date < e.stop) {
                        // now epg
                        dur = moment.duration(date.diff(e.start)).get('minutes');
                        var elapsed = 'Started ' + dur + (dur > 1 ? ' minutes' : ' minute') + ' ago';
                        $scope.epgNow = {start: moment(e.start).format('h.mma'), elapsed: elapsed, title: e.title, desc: e.desc};

                        // next epg
                        var nextIndex = k + 1;
                        if (nextIndex < $scope.currentChannel.epg.length - 1) {
                            e = $scope.currentChannel.epg[nextIndex];
                            $scope.epgNext = {start: moment(e.start).format('h.mma'), title: e.title, desc: e.desc};
                        }

                        // later epg
                        var laterIndex = k + 2;
                        if (laterIndex < $scope.currentChannel.epg.length - 1) {
                            e = $scope.currentChannel.epg[laterIndex];
                            $scope.epgLater = {start: moment(e.start).format('h.mma'), title: e.title, desc: e.desc};
                        }

                        break;
                    }
                }
            }

            if (!$scope.epgNow.title) {
                $scope.epgNow.title = 'No programming information available';
                $scope.epgNow.desc = $scope.currentChannel.description;
                $scope.epgNow.start = moment().format('h.00a');
                dur = date.get('minutes');
                $scope.epgNow.elapsed = 'Started ' + dur + (dur > 1 ? ' minutes' : ' minute') + ' ago';
            }

            if (!$scope.epgNext.title) {
                $scope.epgNext.title = 'No programming information available';
                $scope.epgNext.start = moment().add(1, 'hours').format('h.00a');
            }

            if (!$scope.epgLater.title) {
                $scope.epgLater.title = 'No programming information available';
                $scope.epgLater.start = moment().add(2, 'hours').format('h.00a');
            }
        }

        function toggleLoading(show) {
            $scope.showLoader = show;
        }

        function toggleErrorMessage(show) {
            var errorMessage = $element.find('#loadError');
            errorMessage.velocity('stop');
            if (show) {
                errorMessage.css('display', '');
                errorMessage.velocity({opacity: 1}, {duration: 300});
            } else {
                errorMessage.velocity({opacity: 0}, {duration: 300, display: 'none'});
            }
        }

        function stopAutoHidePaneTimer() {
            if (!autoHidePaneTimeout) {
                return;
            }

            $timeout.cancel(autoHidePaneTimeout);
            autoHidePaneTimeout = undefined;
        }

        function startAutoHidePaneTimer() {
            stopAutoHidePaneTimer();
            autoHidePaneTimeout = $timeout(function() {
                $scope.paneOpen = false;
            }, AUTO_HIDE_PANEL_TIMEOUT);
        }

        function stopErrorRetryWorkflowTimer() {
            if (!errorRetryWorkflowTimeout) {
                return;
            }

            $timeout.cancel(errorRetryWorkflowTimeout);
            errorRetryWorkflowTimeout = undefined;
        }

        function startErrorRetryWorkflowTimer() {
            stopErrorRetryWorkflowTimer();
            errorRetryWorkflowTimeout = $timeout(function() {
                toggleErrorMessage(false);
                toggleLoading(true);
                playChannel();
            }, ERROR_RETRY_WORKFLOW_TIMEOUT);
        }

        var onRemoteEvent = function(remoteEvent) {
            // Only listen to down button events
            if (remoteEvent.eventType != REMOTEEVENTTYPE.buttonDown) {
                return;
            }

            // Perform action based on Virtual code
            switch (remoteEvent.vcode) {
                case REMOTEVCODE.up: // Up Key
                    $scope.paneOpen = true;
                    stopAutoHidePaneTimer();
                    break;
                case REMOTEVCODE.down: // Down Key
                    $scope.paneOpen = false;
                    stopAutoHidePaneTimer();
                    break;
                case REMOTEVCODE.left: // Left Key
                    onActionLeft();
                    break;
                case REMOTEVCODE.right: // Right Key
                    onActionRight();
                    break;
                case REMOTEVCODE.home:
                    onActionHome();
                    break;
                case REMOTEVCODE.back: // Command/Windows Key
                    if (!publicService.getServices().deviceMenu.isVisible()) {
                        onActionBack();
                        remoteEvent.preventDefault();
                    }

                    break;
            }
        };

        function onActionHome() {
            navigationHelper.clearHistoryAndGoHome();
        }

        // On Left clicked
        function onActionLeft() {
            $scope.paneOpen = true;
            startAutoHidePaneTimer();
            // get prev channel
            var prevChannel = null;
            for (var i = 0; i < liveChannels.length; i++) {
                if (liveChannels[i].id == $scope.currentChannel.id) {
                    if (i == 0) {
                        prevChannel = liveChannels[liveChannels.length - 1];
                    } else {
                        prevChannel = liveChannels[i - 1];
                    }

                    break;
                }
            }

            if (prevChannel) {
                playerApi.unload();
                $scope.currentChannel = prevChannel;

                // init current channel epg
                initCurrentEpg();

                // toggle loading icon
                toggleErrorMessage(false);
                toggleLoading(true);

                if (channelPlayTimeout != null) {
                    $timeout.cancel(channelPlayTimeout);
                    channelPlayTimeout = null;
                }

                channelPlayTimeout = $timeout(function() {
                    // play channel
                    playChannel();
                    channelPlayTimeout = null;
                }, 500);
            }
        }

        // On Right clicked
        function onActionRight() {
            $scope.paneOpen = true;
            startAutoHidePaneTimer();

            // get next channel
            var nextChannel = null;
            for (var i = 0; i < liveChannels.length; i++) {
                if (liveChannels[i].id == $scope.currentChannel.id) {
                    if ((i + 1) == liveChannels.length) {
                        nextChannel = liveChannels[0];
                    } else {
                        nextChannel = liveChannels[i + 1];
                    }

                    break;
                }
            }

            if (nextChannel) {
                playerApi.unload();
                $scope.currentChannel = nextChannel;

                // init current channel epg
                initCurrentEpg();

                // toggle loading icon
                toggleErrorMessage(false);
                toggleLoading(true);

                if (channelPlayTimeout != null) {
                    $timeout.cancel(channelPlayTimeout);
                    channelPlayTimeout = null;
                }

                channelPlayTimeout = $timeout(function() {
                    // play channel
                    playChannel();
                    channelPlayTimeout = null;
                }, 500);
            }
        }

        // On Back
        function onActionBack() {
            // remove page history
            var prevUrl = channelService.removeHistory();
            if (prevUrl == null) {
                prevUrl = homeUrl;
            }

            if (previousLiveChannelColor) {
                getElement('.live-channel').css('backgroundColor', previousLiveChannelColor);
            }

            if (previousThemeDarkColor) {
                getElement('.theme-dark').css('backgroundColor', previousThemeDarkColor);
            }

            if (previousBodyColor) {
                getElement('body,html').css('backgroundColor', previousBodyColor);
            }

            if (previousThemeDarkBackground) {
                getElement('.theme-dark').css('background', previousThemeDarkBackground);
            }

            $location.url(prevUrl);
        }

        function playChannel() {
            var currentContent = null;
            // get content from asset
            if ($scope.currentChannel && $scope.currentChannel.assets != undefined) {
                for (var i = 0; i < $scope.currentChannel.assets.length; i++) {
                    var asset = $scope.currentChannel.assets[i];
                    if (asset.type == 'content') {
                        currentContent = $angular.copy(asset);
                        break;
                    }
                }
            }

            if (!currentContent) {
                return;
            }

            currentContent.options = currentContent.options || {};
            currentContent = util.applyDevicePlaybackConfiguration(currentContent);
            contentHelper.processPlaybackOptions(currentContent);
            coreConfigurationService.set('play', 'lastused', 'liveid=' + $scope.currentChannel.id);
            networkContentRouterService.resolve(currentContent.mediaUrl, 'play-content-routing-service', {type: currentContent.mediaType, cache: true})
                .then(function(url) {
                    currentContent.mediaUrl = url;
                    $scope.currentContent = currentContent;
                }, function(err) {
                    $scope.currentContent = currentContent;
                });
        }

        $scope.onPlayerApi = function(api) {
            playerApi = api;
        };

        $scope.hotSpotClick = function(ev) {
            if (!currentOverlayToggle) {
                if (ev.clientX < window.innerWidth * 0.15) {
                    onActionLeft();
                    ev.preventDefault();
                    return;
                } else if (ev.clientX > window.innerWidth * 0.85) {
                    onActionRight();
                    ev.preventDefault();
                    return;
                }
            }

            $scope.paneOpen = !$scope.paneOpen;
        };

        // On Item load handler
        $scope.onItemLoad = function() {
            // reset the counter if it can load the channel again
            itemLoadErrorCounter = 0;
            // Hide the spinner
            toggleLoading(false);
            toggleErrorMessage(false);
        };

        // Handle content load error
        $scope.onItemLoadError = function() {
            if (playerApi) {
                playerApi.unload();
            }

            if (itemLoadErrorCounter < 3) {
                itemLoadErrorCounter++;
                playChannel();
            } else {
                errorRetryCount++;
                logService.info(LOG_KEY, 'Attempt to retry on playing live channel: ' + errorRetryCount);

                itemLoadErrorCounter = 0; 
                
                toggleErrorMessage(true);
                toggleLoading(false);
        
                startErrorRetryWorkflowTimer();
            }
        };

        $scope.onItemComplete = function() {
            // try again to play the stream
            // via onItemLoadError since the method
            // will handle the playback again.
            $scope.onItemLoadError();
        };

        function applyTheme() {
            themeHelper.applyTheme().then(applyThemeSetting);
        }

        function applyThemeSetting() {
            var settings = channelService.getThemeSettings();
            $scope.theme = themeHelper.getThemeSettings();

            var applyTag = function() {
                var promise = $q.defer();
                // link
                var playStyle = document.getElementById('playOverwriteStyle');
                if (!playStyle) {
                    playStyle = document.createElement('link');
                    playStyle.id = 'playOverwriteStyle';
                    playStyle.rel = 'stylesheet';
                    document.getElementsByTagName('head')[0].appendChild(playStyle);
                }

                // properties
                var overwriteStyleProp = document.getElementById('overwriteStyleProperties');
                if (!overwriteStyleProp) {
                    overwriteStyleProp = document.createElement('style');
                    overwriteStyleProp.id = 'overwriteStyleProperties';
                    overwriteStyleProp.type = 'text/css';
                    document.getElementsByTagName('head')[0].appendChild(overwriteStyleProp);
                }

                if (playStyle && overwriteStyleProp) {
                    promise.resolve();
                } else {
                    $timeout(function() {
                        promise.resolve();
                    }, 100);
                }

                return promise.promise;
            };

            var applySetting = function(setting) {
                // link
                var playStyle = document.getElementById('playOverwriteStyle');
                if (!setting || (setting && setting.name == 'dark')) {
                    playStyle.href = '';
                } else {
                    playStyle.href = 'assets/css/theme-light.css';
                }

                var overwriteStyleProp = document.getElementById('overwriteStyleProperties');

                // properties
                if (setting) {
                    var theme = setting;
                    // accent
                    // channel browser active item
                    overwriteStyleProp.innerHTML = '.channel-browser .channel-group .channel-list .ui-grid-item-active .title {background-color:' + theme.accent + ';}';
                    // live channel
                    overwriteStyleProp.innerHTML += '.live-channel .epg-overlay .program .program-schedule .now .time {background-color:' + theme.accent + ';}';
                    // left menu
                    overwriteStyleProp.innerHTML += '.theme-dark .list-group-item.active, .theme-dark .list-group-item.focus {background-color:' + theme.accent + ' !important;}';
                    // search icon
                    overwriteStyleProp.innerHTML += '.channel-browser .banner .search:hover, .channel-browser .banner .search:active, .channel-browser .banner .search-selected {background-color:' + theme.accent + ' !important;}';
                    // epg
                    overwriteStyleProp.innerHTML += '.epg .container .selected-program .right .instruction {color:' + theme.accent + ' !important;}';
                    overwriteStyleProp.innerHTML += '.live-channel .epg-overlay .program .program-schedule .now .time {background-color:' + theme.accent + ' !important;}';
                } else {
                    overwriteStyleProp.innerHTML = '';
                }
            };

            var resolveSetting = function() {
                var setting;
                if (settings && settings.themes) {
                    for (var i = 0; i < settings.themes.length; i++) {
                        var theme = settings.themes[i];
                        if (settings.default == theme.name) {
                            setting = theme;
                            break;
                        }
                    }
                }

                applySetting(setting);
            };

            applyTag().then(resolveSetting);
        }

        $scope.onBackButtonClicked = function() {
            onActionBack();
        };

        coreFrameworkService.isReady().then(function() {
            // Init page once channel service is ready
            channelService.isReady().then(function() {
                applyTheme();
                init();
            });
        });
    }]);
})(window.angular);

(function ($angular) {
	'use strict';
	var angular = $angular;
	var app = angular.module('signsHTML5');
	
	//Temporary class
	
	app.service('sampleChannels', function () {
		var play = {groups:[], channels:[]}
				
        //Generate random data
		var runningId = 1;
		var assetRunningId = 1;
        var randomThumnails = [
			"http://keyassets.timeincuk.net/inspirewp/live/wp-content/uploads/sites/12/2012/08/coastal-landscape-crop-300x200.jpg",
			"http://images.all-free-download.com/images/graphicthumb/countryside_trail_landscape_220433.jpg",
			"http://www.monjilandscape.com/wp-content/uploads/2011/11/homepage2-Copy-300x200.jpg",
			"http://banamid.ir/wp-content/uploads/2015/08/Mahmoud-Heidarian-Vancouver-modern-architecture-300x200.jpg",
			"http://silcofs.com/wp-content/uploads/2015/02/Data-Center-300x200.jpg",
			"https://www.walldevil.com/wallpapers/w08/thumb/architecture-skyscrapers-reflections-chicago-modern-architecture-monochrome.jpg",
			"http://t13.deviantart.net/82QZKl5LJd4CSriMdvt3w3pCz1M=/300x200/filters:fixed_height(100,100):origin()/pre02/5a9e/th/pre/i/2008/184/d/5/modern_architecture_by_gazpatrick.jpg",
			"http://www.geeky-gadgets.com/wp-content/uploads/2014/10/AeroMobil-3.0-Flying-Car1-300x200.jpg",
			"http://8starmotorsports.com/wp-content/uploads/2013/06/Dagys_-2013_104034-300x200.png",
			"http://beacardinal.lamar.edu/_files/images/recruitment/campus.jpg",
			"http://www.humanresourcesmba.net/wp-content/uploads/2014/02/corporate-trainer-300x200.jpg"
		];
		
		var randomContent = [
			{type: 'image', url: 'http://www.freelargeimages.com/wp-content/uploads/2014/12/Landscape_01.jpg'},
			{type: 'image', url: 'http://wallpapers.ae/wp-content/uploads/2015/01/Amazing-Tree-Landscape-Wallpaper.jpg'},
			{type: 'image', url: 'http://www.hdwallpapersnew.net/wp-content/uploads/2015/03/landscape-collection-road-landscape-hd-best-desktop-wallpapers-of-road-pictures-free.jpg'},
			{type: 'image', url: 'http://img03.deviantart.net/217c/i/2014/015/4/8/create_your_future___wallpaper_by_frankmareno-d72bj3i.png'},
			{type: 'image', url: 'http://i.ytimg.com/vi/YyBMiluuS5M/maxresdefault.jpg'},
			{type: 'video', url: 'http://www.appspace.com/assets/website.mp4'},
			{type: 'image', url: 'http://0.media.dorkly.cvcdn.com/74/68/cc7442387763dc970aca168d9ddbf6f4-the-best-looking-movies-for-the-rest-of-summer-2014.jpg'},
			{type: 'image', url: 'http://www.hdwallpapers.in/walls/9_movie-HD.jpg'},
			{type: 'image', url: 'http://infossible.com/infosdata/wp-content/uploads/2015/08/Earth-From-Space-Space-1080x1920.jpg'},
			{type: 'image', url: 'http://orig08.deviantart.net/5704/f/2014/053/6/5/free_space_galaxy_texture_by_lyshastra-d77gh18.jpg'},
			{type: 'image', url: 'http://www.drodd.com/images10/space-wallpaper19.jpg'},
			{type: 'video', url: 'http://www.appspace.com/assets/website.mp4'},
			{type: 'image', url: 'http://www.gamecrate.com/wp-content/uploads/2014/09/vanishing-of-ethan-carter-dam.jpg'},
			{type: 'image', url: 'http://www.theastronauts.com/wordpress/wp-content/uploads/2014/03/The_Vanishing_of_Ethan_Carter_Church.jpg'},
			{type: 'video', url: 'http://www.w3schools.com/html/mov_bbb.mp4'},
			{type: 'video', url: 'http://vjs.zencdn.net/v/oceans.mp4'},
			{type: 'video', url: 'http://www.appspace.com/assets/website.mp4'},
		];
	
		function createContentChannel(groupId, parentId, position) {
			var thumbnailIndex = Math.floor(Math.random() * randomThumnails.length);
			var contentIndex = Math.floor(Math.random() * randomContent.length);
			
			var c = {
				id: runningId, title: "Content " + runningId, type: "content", position: position, parentChannelId: parentId, groupId: groupId,
				description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In in cursus velit. Morbi efficitur mauris at urna commodo semper. Nam at ante scelerisque, venenatis lectus vel, sodales lorem. Ut suscipit metus vitae justo pulvinar luctus. Donec porttitor justo sed 		dui bibendum, venenatis accumsan velit egestas. Cras sollicitudin tempor nulla, eget euismod nibh mattis auctor. Ut risus arcu, molestie quis ligula vel, finibus ullamcorper eros.",
				assets: [
					{ id: assetRunningId++, type: "thumbnail", position: 0, mediaType: "image", mediaUrl: randomThumnails[thumbnailIndex]},
					{ id: assetRunningId++, type: "poster", position: 0, mediaType: "image", mediaUrl: randomThumnails[thumbnailIndex]},
					{ id: assetRunningId++, type: "banner", position: 0, mediaType: randomContent[contentIndex].type, mediaUrl: randomContent[contentIndex].url},
					{ id: assetRunningId++, type: "content", position: 0, mediaType: randomContent[contentIndex].type, mediaUrl: randomContent[contentIndex].url},
				],
				subChannels: []
			};
			runningId++;
			
			return c;
		}
		
		function createPlaylistChannel(groupId, parentId, position) {
			var thumbnailIndex = Math.floor(Math.random() * randomThumnails.length);
			var contentIndex = Math.floor(Math.random() * randomContent.length);
			var numOfContentItems = Math.floor(Math.random() * 20) + 1;
			
			var c = {
				id: runningId, title: "Playlist" + runningId, type: "playlist", position: position, parentChannelId: parentId, groupId: groupId,
				description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In in cursus velit. Morbi efficitur mauris at urna commodo semper. Nam at ante scelerisque, venenatis lectus vel, sodales lorem. Ut suscipit metus vitae justo pulvinar luctus. Donec porttitor justo sed 		dui bibendum, venenatis accumsan velit egestas. Cras sollicitudin tempor nulla, eget euismod nibh mattis auctor. Ut risus arcu, molestie quis ligula vel, finibus ullamcorper eros.",
				assets: [
					{ id: assetRunningId++, type: "thumbnail", position: 0, mediaType: "image", mediaUrl: randomThumnails[thumbnailIndex]},
					{ id: assetRunningId++, type: "poster", position: 0, mediaType: "image", mediaUrl: randomThumnails[thumbnailIndex]},
					{ id: assetRunningId++, type: "banner", position: 0, mediaType: randomContent[contentIndex].type, mediaUrl: randomContent[contentIndex].url},
					{ id: assetRunningId++, type: "content", position: 0, mediaType: randomContent[contentIndex].type, mediaUrl: randomContent[contentIndex].url},
				],
				subChannels: []
			};
			runningId++;
			
			//Add N number of content items to subchannels
			for(var i = 0; i < numOfContentItems; i++) {
				var subChannel = createContentChannel(null, c.id, i);
				c.subChannels.push(subChannel);
			}
			
			return c;
		}
		
		var maxFolderDepth = 3;
		function createFolderChannel(groupId, parentId, position, folderDepth) {
			var thumbnailIndex = Math.floor(Math.random() * randomThumnails.length);
			var contentIndex = Math.floor(Math.random() * randomContent.length);
			var numOfContentItems = Math.floor(Math.random() * 20) + 1;
			
			var c = {
				id: runningId, title: "Folder " + folderDepth + "-" + runningId, type: "folder", position: position, parentChannelId: parentId, groupId: groupId,
				description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In in cursus velit. Morbi efficitur mauris at urna commodo semper. Nam at ante scelerisque, venenatis lectus vel, sodales lorem. Ut suscipit metus vitae justo pulvinar luctus. Donec porttitor justo sed 		dui bibendum, venenatis accumsan velit egestas. Cras sollicitudin tempor nulla, eget euismod nibh mattis auctor. Ut risus arcu, molestie quis ligula vel, finibus ullamcorper eros.",
				assets: [
					{ id: assetRunningId++, type: "thumbnail", position: 0, mediaType: "image", mediaUrl: randomThumnails[thumbnailIndex]},
					{ id: assetRunningId++, type: "content", position: 0, mediaType: randomContent[contentIndex].type, mediaUrl: randomContent[contentIndex].url},
				],  
				subChannels: []
			};
			runningId++;
			
			//Add N number of content items to subchannels
			for(var i = 0; i < numOfContentItems; i++) {
				var flag = Math.floor(Math.random() * 3);
				var subChannel = null;
				if (flag === 0) {
					//if 0, add content
					subChannel = createContentChannel(null, c.id, i);
				}
				else if (flag === 1) {
					//if 1, add playlist
					subChannel = createPlaylistChannel(null, c.id, i);
				}
				else if (folderDepth < maxFolderDepth) {
					//if 2, create folder
					subChannel = createFolderChannel(null, c.id, i, folderDepth + 1);
				}
				
				if (subChannel != null) {
					c.subChannels.push(subChannel);
				}
			}
			
			//add to channel array if not root folder
			if (folderDepth > 0) {
				play.channels.push(c);
			}
			return c;
		}
		
        function initSampleChannels() {
			//Init groups
			var numOfGroups = 7;
			for(var i = 0; i < numOfGroups; i++) {
				var g = {id: i + 1, name: 'Group ' + (i + 1), position: i, channels: []};
				
				//Init channels
				var numOfChannels = Math.floor(Math.random() * 14);
				if (numOfChannels < 3){
					numOfChannels = 3;
				}
				for(var k = 0; k < numOfChannels; k++) {
					var flag = Math.floor(Math.random() * 3);
					var c;
					if (flag === 0) {
						c = createPlaylistChannel(g.id, 0, k);
					}
					else if (flag === 1) {
						c = createContentChannel(g.id, 0, k);
					}
					else {
						c = createFolderChannel(g.id, 0, k, 0);
					}
					
					play.channels.push(c);
					g.channels.push(c);
				}
				play.groups.push(g);
			}
        }
		
        return {
            init: function() {
				if (play.channels.length === 0) {
					initSampleChannels();
				}
               return play; 
            }
        }
    });
    
})(window.angular);

(function ($angular) {

    "use strict";

    var app = $angular.module('signsHTML5');

    //Reads file into string
    app.factory("ChannelViewUtils", ["scriptingService", function (scriptingService) {
        function ChannelViewUtils() {

        }

        ChannelViewUtils.prototype = {
            applyDevicePlaybackConfiguration : applyDevicePlaybackConfiguration
        }

        function normalizeAspectRatioValues(aspectratio){
            var res = aspectratio;
            switch(aspectratio.toLowerCase()){
                //if we receive uniform fill, convert it to uniformfill
                case 'uniform fill':
                    res = 'uniformfill';
                    break;
                //correct values, don't do anything
                case 'uniform':
                case 'fill':
                case 'uniformfill':
                    break;
                //incorrect values defaulted to fill
                default:
                    res = 'fill';
                    break;
            }
            return res;
        }

        function applyDevicePlaybackConfiguration(currentContent) {

            var stretch = scriptingService.retrieveAppProperty('player.play.video.aspectratio');
            if (stretch) {
                currentContent.stretch = normalizeAspectRatioValues(stretch);
            }

            var closecaption = scriptingService.retrieveAppProperty("custom.media.video.cc");
            if (closecaption) {
                currentContent.options.closecaption = (closecaption.toLowerCase() == 'true');
            }
            var ccBrightsign = scriptingService.retrieveAppProperty("custom.media.video.cc.brightsign");
            if (ccBrightsign) {
                currentContent.options.closecaptionInfoBrightsign = ccBrightsign;
            }
            var ccChrome = scriptingService.retrieveAppProperty("custom.media.video.cc.chrome");
            if (ccChrome) {
                currentContent.options.closecaptionInfoChrome = ccChrome;
            }
            var ccSamsung = scriptingService.retrieveAppProperty("custom.media.video.cc.samsung");
            if (ccSamsung) {
                currentContent.options.closecaptionInfoSamsung = ccSamsung;
            }

            var ccTrack = scriptingService.retrieveAppProperty("custom.media.video.cc.track");
            if (ccTrack != undefined) {
                currentContent.options.closecaptionTrack = parseInt(ccTrack, 10);
            }

            var forceac3 = scriptingService.retrieveAppProperty("player.brightsign.forceac3");
            if (forceac3) {
                currentContent.options.forceac3audio = (forceac3.toLowerCase() == 'true');
            }

            var layerindex = scriptingService.retrieveAppProperty('custom.media.video.layerindex');
            if (layerindex != undefined) {
                currentContent.options.layerindex = parseInt(layerindex, 10);
            }
            //if we enable close caption but the layer index is undefined, set the layer index to -1 (BS logic)
            if (currentContent.options.closecaption && currentContent.options.layerindex == undefined) {
                currentContent.options.layerindex = -1;
            }

            return currentContent;
        }

        return (ChannelViewUtils);
    }]);
})(window.angular);
(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('signsHTML5');
    app.controller('ChannelViewerCtrl', ["$scope", "$timeout", "$filter", "coreConfigurationService", "$routeParams", "$location", "REMOTEEVENTTYPE", "REMOTEVCODE", "halRemoteService", "channelService", "themeHelper", "navigationHelper", "channelsHelper", "halPlatformService", "CONTENT_TYPE", "dataUtil", "TimeoutUtil", "interactivityManager", "lowBandwidthService", "mediaUtil", "configurationService", "deviceAlertService", function($scope, $timeout, $filter,
        coreConfigurationService, $routeParams, $location, REMOTEEVENTTYPE, REMOTEVCODE,
        halRemoteService, channelService, themeHelper, navigationHelper, channelsHelper,
        halPlatformService, CONTENT_TYPE, dataUtil, TimeoutUtil, interactivityManager, lowBandwidthService, mediaUtil,
        configurationService, deviceAlertService) {
        var playerApi = null;
        var loaderDelay = null;
        var thumbnailProcessed = false;
        var DEFAULT_CONTENT_DURATION = 12;
        var isContentInteractive = false;
        var isInteractiveDisabled = false;
        var NO_SCHEDULED_CONTENT_MESSAGE = 'Content not currently scheduled';
        var NEGATIVE = -1;
        var DEBOUNCE_DURATION = 300;
        var supportedInteractiveTags = ['productivity', 'interactive'];
        var isSingleContent = false;
        $scope.requiresOverlay = halPlatformService.getFeatureSupport('channel.viewer.requires.overlay') === true;
        $scope.isContentNavigationDisabled = false;
        $scope.isContentInteractiveEnabled = false;
        $scope.startIndex = 0;
        $scope.showLoader = true;
        $scope.contentItems = [];
        $scope.contentThumbnails = [];
        $scope.paneOpen = false;
        $scope.currentContent = null;
        $scope.contentProgress = null;
        $scope.contentDuration = DEFAULT_CONTENT_DURATION;
        $scope.loop = true;
        $scope.isMuted = true;
        $scope.errorMessage;
        $scope.backPlacement = null;
        $scope.showPlayIcon = true; // To show "play" button by default
        $scope.theme = themeHelper.getDefaultTheme();

        function init() {
            // if we're loaded because of auto play (idle), clear interaction flag in lowBandwidthService
            if (isAutoPlay()) {
                lowBandwidthService.resetInteraction();
            }

            deviceAlertService.subscribeExclusiveChannelStart(onExclusiveChannelStarted);
            deviceAlertService.subscribeExclusiveChannelStop(onExclusiveChannelStopped);
            // mute if user never interact with the screen AND driver requires video to be muted
            $scope.isMuted = !interactivityManager.isInteracted() && halPlatformService.getFeatureSupport('media.video.should.mute') === 'yes';
            isInteractiveDisabled = halPlatformService.getFeatureSupport('channel.viewer.interactive.disable') === true || isExclusiveChannel();
            updateContentInteractiveEnabledFlag();
            if (!processContentItems()) {
                navigationHelper.goHome();
                return;
            }

            if ($routeParams.position != undefined && $routeParams.position >= 0) {
                $scope.startIndex = $routeParams.position < $scope.contentItems.length ? $routeParams.position : $scope.contentItems.length - 1;
            }
 
            // Until the first content item is loaded, delay thumbnails from loading
            for (var i = 0; i < $scope.contentItems.length; i++) {
                $scope.contentItems[i].thumbnailUrlPending = $scope.contentItems[i].thumbnailUrl;
                $scope.contentItems[i].thumbnailUrl = '';
                updateContentScheduleValidity($scope.contentItems[i]);
            }

            $scope.contentThumbnails = $scope.contentItems;

            // Set the current content item to the first
            $scope.currentContent = $scope.contentItems[$scope.startIndex];
            // if content is signs, play forever
            if ($scope.currentContent.mediaType === CONTENT_TYPE.SIGNS) {
                $scope.contentDuration = Number.MAX_SAFE_INTEGER;
            }

            // Register for remote input
            halRemoteService.registerEvents(onRemoteEvent);
            channelService.subscribeToUpdates(onChannelServiceUpdate);
            configurationService.subscribe(applyTheme);

            applyTheme();
            var channel = channelsHelper.getChannelFromFilter(getIdFromParams());
            if (!channel) {
                return;
            }

            coreConfigurationService.set('play', 'lastused', 'channel=' + channel.id);
            halPlatformService.raiseChannelLoaded(channel.title);
        }

        $scope.$onDestroy = function() {
            halRemoteService.unRegisterEvents(onRemoteEvent);
            deviceAlertService.unsubscribeExclusiveChannelStart(onExclusiveChannelStarted);
            deviceAlertService.unsubscribeExclusiveChannelStop(onExclusiveChannelStopped);
            channelService.unsubscribeFromUpdates(onChannelServiceUpdate);
            configurationService.unsubscribe(applyTheme);
            clearLoaderDelay();
        };

        $scope.onEndOfPlaylist = function() {
            navigationHelper.goHome();
        };

        $scope.onPlayerApi = function(api) {
            playerApi = api;
        };

        $scope.onPlayerItemLoad = function(item, presenterItem) {
            $scope.hideContentStrip = false;
            $scope.showLoader = false;
            processThumbnails();
            updateInteractivity();
            clearLoaderDelay();
            $location.search('position', item.position);
            processLowBandwidthModeItem(presenterItem);
        };

        $scope.onPlayerItemBeginLoad = function(item) {
            // Show "pause" icon when item begins loading
            $scope.showPlayIcon = false;
            resetError();
            resetProgress();
            updateThumbnails();
            $scope.currentContent = item;
            updateInteractivity();
        };

        function updateContentScheduleValidity(content) {
            content.isWithinSchedule = channelsHelper.isPlayoutValid(content);
        }
        

        function updateThumbnails() {
            for (var i = 0; i < $scope.contentThumbnails.length; i++) {
                updateContentScheduleValidity($scope.contentThumbnails[i]);
            }
        }

        $scope.backClick = onActionBack;

        $scope.onStripItemClick = function(item) {
            showLoaderDelay(500);
            var position = getCurrentStripItemPosition(item);
            playerApi.gotoItem(position, item.duration);
        };

        // Calculate the position of the current strip item without including the future schedule items
        function getCurrentStripItemPosition(item) {
            var currentValidContentItems = $filter('filter')($scope.contentItems, function(contentItem) {
                return channelsHelper.isPlayoutValid(contentItem);
            });

            return currentValidContentItems.indexOf(item);
        }

        $scope.onPlayerItemProgress = function(progress) {
            $scope.contentProgress = progress;
        };

        $scope.toggleOverlay = function(open) {
            $scope.paneOpen = open === undefined ? !$scope.paneOpen : open;
            updateContentNavigationEnabledFlag();
        };

        $scope.nextItemClick = function($event) {
            nextItem();
            $event.preventDefault();
            $event.stopPropagation();
        };

        $scope.previousItemClick = function($event) {
            previousItem();
            $event.preventDefault();
            $event.stopPropagation();
        };

        $scope.onViewerClicked = function() {
            if (isInteractiveDisabled) {
                return;
            }

            $scope.toggleOverlay();
        };

        $scope.onNoValidPlaylistItems = function() {
            $scope.hideContentStrip = true;
            processThumbnails();
            showNoScheduledContentError();
        };

        $scope.onItemOffsetInvalid = function(offset) {
            $scope.currentContent = $scope.contentItems[offset];
            resetProgress();
            updateInteractivity();
            showNoScheduledContentError();
        };

        function onExclusiveChannelStarted(channel) {
            // don't do anything if it's the same channel and we're already in exclusive channel mode
            var currentChannelId = parseInt(getIdFromParams(), 10);
            if (currentChannelId === channel.id && isExclusiveChannel()) {
                return;
            }

            // if we have exclusive channel, go to home first so that it will be redirected
            onActionHome();
        }

        function onExclusiveChannelStopped() {
            // if page is loaded in exclusive mode and now we don't have channel, go back to home
            if (!isExclusiveChannel()) {
                return;
            }

            onActionHome();
        }

        function showNoScheduledContentError() {
            clearLoaderDelay();
            $scope.showLoader = false;
            $scope.errorMessage = NO_SCHEDULED_CONTENT_MESSAGE;
        }

        function resetProgress() {
            $scope.contentProgress = undefined;
        }

        function resetError() {
            $scope.errorMessage = undefined;
        }

        function processThumbnails() {
            if (thumbnailProcessed) {
                return;
            }

            // Once first item is loaded or no valid items, restore thumbnail URL
            for (var i = 0; i < $scope.contentItems.length; i++) {
                $scope.contentItems[i].thumbnailUrl = $scope.contentItems[i].thumbnailUrlPending;
            }

            thumbnailProcessed = true;
        }

        function updateContentNavigationEnabledFlag() {
            $scope.isContentNavigationDisabled = $scope.paneOpen || isContentInteractive || isSingleContent;
        }

        function updateContentInteractiveEnabledFlag() {
            $scope.isContentInteractiveEnabled = isContentInteractive && !isInteractiveDisabled;
        }

        function updateContentInteractiveFlags() {
            updateContentNavigationEnabledFlag();
            updateContentInteractiveEnabledFlag();
        }

        function processContentItems() {
            $scope.contentItems = getContentItemsById();
            isSingleContent = $scope.contentItems.length === 1;
            for (var i = 0; i < $scope.contentItems.length; i++) {
                updateContentScheduleValidity($scope.contentItems[i]);
            }

            updateContentNavigationEnabledFlag();

            // return true or false based on whether we processed the items or not
            return $scope.contentItems.length > 0;
        }

        function getContentItemsById() {
            var channelId = getIdFromParams();
            if (!channelId) {
                return [];
            }

            // Retrieve the channel content using a filter
            if (dataUtil.isString(channelId) && channelId.indexOf(':') >= 0) {
                return channelsHelper.getContentByFilter(channelId);
            }

            var channel = getChannelById(channelId);
            if (!channel) {
                return [];
            }

            // Retrieve the content in the channel
            return channelsHelper.getChannelContent(channel);
        }

        function getChannelById(channelId) {
            return channelService.getChannelByChannelId(channelId);
        }

        function getIdFromParams() {
            return $routeParams.id;
        }

        function applyTheme() {
            themeHelper.applyTheme().then(function() {
                $scope.theme = themeHelper.getThemeSettings();
                if ($scope.theme.endofplaylistbehaviour == 'goback') {
                    $scope.loop = false;
                }
            });
        }

        function onChannelServiceUpdate() {
            if (!processContentItems()) {
                navigationHelper.goHome();
                return;
            }
            
            $scope.hideContentStrip = false;
            $scope.contentThumbnails = $scope.contentItems;
        }

        function nextItem() {
            showLoaderDelay(500);
            playerApi.nextItem();
        }

        function previousItem() {
            showLoaderDelay(500);
            playerApi.previousItem();
        }

        function clearLoaderDelay() {
            if (loaderDelay) {
                $timeout.cancel(loaderDelay);
            }
        }

        function showLoaderDelay(delay) {
            clearLoaderDelay();
            loaderDelay = $timeout(function() {
                loaderDelay = undefined;
                $scope.showLoader = true;
            }, delay);
        }

        function updateInteractivity() {
            isContentInteractive = false;
            if (!$scope.currentContent) {
                updateContentInteractiveFlags();
                return;
            }

            // force interactive for signs
            // for others, check whether the interactive tags exists
            isContentInteractive =
                $scope.currentContent.mediaType === CONTENT_TYPE.SIGNS || isInteractiveTagExists($scope.currentContent.tags);
            updateContentInteractiveFlags();
        }

        function isInteractiveTagExists(tags) {
            if (!tags || !tags.length) {
                return false;
            }

            for (var i = 0; i < tags.length; i++) {
                if (supportedInteractiveTags.indexOf(tags[i]) === -1) {
                    continue;
                }

                return true;
            }

            return false;
        }

        function onRemoteEvent(remoteEvent) {
            // Only listen to down button events or interactive is disabled
            if (remoteEvent.eventType != REMOTEEVENTTYPE.buttonDown || isInteractiveDisabled) {
                return;
            }

            // Perform action based on Virtual code
            switch (remoteEvent.vcode) {
                case REMOTEVCODE.up: // Up Key
                    $scope.toggleOverlay();
                    break;
                case REMOTEVCODE.down: // Down Key
                    $scope.toggleOverlay();
                    break;
                case REMOTEVCODE.left: // Left Key
                    if (isChannelVideoScrubbingEnabled()) {
                        var rewindCallback = seekItemCallback(NEGATIVE * $scope.theme.channelVideoScrubbingDuration);
                        rewindCallback();
                        break;
                    }

                    if (isSingleContent) {
                        return;
                    }

                    previousItem();
                    break;
                case REMOTEVCODE.right: // Right Key
                    if (isChannelVideoScrubbingEnabled()) {
                        var fowardCallback = seekItemCallback($scope.theme.channelVideoScrubbingDuration);
                        fowardCallback();
                        break;
                    }

                    if (isSingleContent) {
                        return;
                    }

                    nextItem();
                    break;
                case REMOTEVCODE.back: // Command / Windows Key
                    onActionBack();
                    break;
                case REMOTEVCODE.home: // Home
                    onActionHome();
                    break;
                case REMOTEVCODE.play:
                    executeApiMethod('play', false);
                    break;
                case REMOTEVCODE.pause:
                    executeApiMethod('pause', true);
                    break;
                case REMOTEVCODE.pause_toggle:
                    executeApiMethod('togglePause', !$scope.showPlayIcon);
                    break;
            }
        }

        function executeApiMethod(method, showPlayIcon) {
            if (!playerApi) {
                return;
            }

            var apiMethod = playerApi[method];
            if (!apiMethod) {
                return;
            }

            apiMethod();
            if (dataUtil.isNullOrUndefined(showPlayIcon)) {
                return;
            }

            $scope.showPlayIcon = showPlayIcon;
        }

        // On Action Back
        function onActionBack() {
            // remove page history
            var prevUrl = channelService.removeHistory();
            if (prevUrl) {
                $location.url(prevUrl);
                return;
            }

            navigationHelper.goHome();
        }

        function onActionHome() {
            navigationHelper.clearHistoryAndGoHome();
        }

        function processLowBandwidthModeItem(item) {
            // if there's no presenter item
            if (!item || !item.content) {
                return;
            }

            lowBandwidthService.isDeviceLowBandwidth().then(function(isLowBandwidthDevice) {
                // if this is not a low bandwidth device, ignore the rest
                if (!isLowBandwidthDevice) {
                    return;
                }

                var content = item.content;
                // modify the type of current content if we're loading low bandwidth thumbnail
                if (content.mediaType === CONTENT_TYPE.LOW_BANDWIDTH_THUMBNAIL) {
                    $scope.currentContent.mediaType = content.mediaType;
                    return;
                }

                // don't change the loop configuration if we're auto play (idle)
                if (isAutoPlay()) {
                    return;
                }

                // check if we need to stop loop
                lowBandwidthService.isEnabled().then(function(isEnabled) {
                    // if low bandwidth is enabled but we're streaming video from Appspace, disable loop
                    if (!isEnabled ||
                        content.mediaType !== CONTENT_TYPE.VIDEO ||
                        !mediaUtil.isAppspaceContentUrl(content.mediaUrl)) {
                        return;
                    }

                    $scope.loop = false;
                });
            });
        }

        function isAutoPlay() {
            var autoPlay = $routeParams.autoplay;
            return autoPlay && dataUtil.parseBoolean(autoPlay);
        }

        function isExclusiveChannel() {
            var exclusive = $routeParams.exclusive;
            return exclusive && dataUtil.parseBoolean(exclusive);
        }

        function isCardVideoSrubbingEnabled() {
            return $scope.currentContent.mediaType === CONTENT_TYPE.CARD && playerApi.getCurrentItemRuntimeConfiguration().allowVideoScrubbing;
        }

        function isChannelVideoScrubbingEnabled() {
            return !$scope.paneOpen && $scope.theme.enableChannelVideoScrubbing && $scope.currentContent &&
                ($scope.currentContent.mediaType === CONTENT_TYPE.VIDEO || isCardVideoSrubbingEnabled());
        }

        function seekItemCallback(duration) {
            return TimeoutUtil.debounce(function() {
                playerApi.seek(duration);
            }, DEBOUNCE_DURATION, true);
        }

        // Init page once channel service is ready
        channelService.isReady().then(init);
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = $angular.module('signsHTML5');

    app.factory('channelsHelper', ["$filter", "channelService", "CHANNEL_TYPE", "dataUtil", function($filter, channelService, CHANNEL_TYPE, dataUtil) {
        var liveChannelFirstItemValue = {
            title: '',
            channelNo: 0,
            channelCode: ''
        };
        var MAX_RESULT_COUNT = 40;

        function sortChannelGroups(channelGroups, field) {
            field = field || 'position';
            // Sort channel groups by the specified field
            channelGroups = $filter('orderBy')(channelGroups, field);
            for (var i = 0; i < channelGroups.length; i++) {
                var group = channelGroups[i];
                // Check for live channel group (with -1 as id)
                if (group.id !== -1) {
                    continue;
                }

                // Remove live channel group and then reinsert it back to the start of the array
                channelGroups.splice(i, 1);
                channelGroups.unshift(group);
                break;
            }

            return channelGroups;
        }

        function sortLiveChannel(liveChannels, sortBy) {
            return $filter('orderBy')(liveChannels, function(item) {
                return item.id === -1 ? liveChannelFirstItemValue[sortBy] : item[sortBy];
            });
        }

        function getUniqueItems(items, isValidCallback) {
            // Attempt to remove duplicate content
            var nonDuplicateList = [];
            var duplicateLookup = [];
            for (var i = 0; i < items.length; i++) {
                var item = items[i];
                if (!isValidCallback(item)) {
                    continue;
                }

                var key = item.title + item.description;
                // Check if this content already exists
                if (duplicateLookup.indexOf(key) > -1) {
                    continue;
                }

                // Add lookup for this combination
                duplicateLookup.push(key);
                nonDuplicateList.push(item);
            }

            return nonDuplicateList;
        }

        function getUniqueChannels(channels) {
            return getUniqueItems(channels, function(channel) {
                return channel.parentChannelId == 0;
            });
        }

        function getChannelsOnly(channels) {
            return getUniqueItems(channels, function(channel) {
                // grab all root channels excluding live channel
                return channel.parentChannelId == 0 && channel.id !== -1;
            });
        }

        function getUniqueContent(contents, excludeTags) {
            return getUniqueItems(contents, function(content) {
                if (content.parentChannelId === 0) {
                    return false;
                }

                if (!excludeTags || !excludeTags.length) {
                    return true;
                }

                for (var i = 0; i < content.tags; i++) {
                    if (excludeTags.indexOf(content.tags[i].toLowerCase()) !== -1) {
                        return false;
                    }
                }

                return true;
            });
        }

        function sortContent(content, field) {
            content = angular.copy(content);
            field = field || 'updateddate';
            content.sort(function(a, b) {
                if (a[field] > b[field]) {
                    return -1;
                }

                if (a[field] < b[field]) {
                    return 1;
                }

                return 0;
            });
            // Update the position
            for (var i = 0; i < content.length; i++) {
                content[i].position = i;
            }

            return content;
        }

        function convertChannelToContent(channel) {
            var content = {
                title: channel.title,
                description: channel.description,
                id: channel.id,
                contentId: channel.contentId,
                type: 'content',
                playout: channel.playout,
                tags: channel.tags,
                position: channel.position,
                duration: channel.playout.duration,
                updateddate: channel.updateddate,
                assets: channel.assets
            };

            for (var j = 0; j < channel.assets.length; j++) {
                var asset = channel.assets[j];
                switch (asset.type) {
                    case 'thumbnail':
                        content.thumbnailUrl = asset.mediaUrl;
                        break;
                    case 'poster':
                        content.posterUrl = asset.mediaUrl;
                        break;
                    case 'content':
                        content.mediaUrl = asset.mediaUrl;
                        content.mediaType = asset.mediaType;
                        break;
                }
            }

            return content;
        }

        function getChannelGroupContent(group) {
            // Iterate through all subchannels and convert to content
            var contentCollection = [];
            for (var i = 0; i < group.channels.length; i++) {
                var channel = group.channels[i];
                contentCollection = contentCollection.concat(getChannelContent(channel));
            }

            var result = [];
            for (var k = 0; k < contentCollection.length; k++) {
                var content = contentCollection[k];
                if (!isPlayoutValid(content)) {
                    continue;
                }

                content.position = result.length;
                result.push(content);
            }

            return result;
        }

        function isPlayoutValid(content) {
            if (!content) {
                return false;
            }

            // start time always have a value
            // end time can be a datetime or infinity.
            if (!content.playout || !content.playout.start) {
                return true;
            }

            var currentTime = new Date().getTime();
            var startTime = content.playout.start.getTime();

            // future content
            if (currentTime < startTime) {
                return false;
            }

            // Play forever or still within scheduled end time
            return !content.playout.end || currentTime < content.playout.end.getTime();
        }

        function getChannelContent(channel) {
            var contentCollection = [];
            if (!channel) {
                return contentCollection;
            }

            // If this channel has a parent or it's a sign channel, we treat it as a content channel
            if (channel.parentChannelId != 0 || channel.type === CHANNEL_TYPE.SIGN) {
                return [convertChannelToContent(channel)];
            }

            // Itterate through all subchannels and convert to content
            var position = 0;
            for (var i = 0; i < channel.subChannels.length; i++) {
                var subChannel = channel.subChannels[i];
                var content = convertChannelToContent(subChannel);
                if (content.mediaUrl) {
                    content.position = position++;
                    contentCollection.push(content);
                }
            }

            return contentCollection;
        }

        function getRecentContent(maxResults) {
            var channels = channelService.getChannels();
            channels = channelService.sortChannelsBy(channels, 'date');
            var content = getChannelGroupContent({channels: channels});
            content = getUniqueContent(content, ['productivity', 'service']);
            content = sortContent(content, 'updateddate');
            if (!isNaN(maxResults)) {
                content = content.splice(0, maxResults);
            }

            for (var i = 0; i < content.length; i++) {
                content[i].loadFilter = 'recent:' + (maxResults ? maxResults : '');
                content[i].position = i;
            }

            return content;
        }

        function getChannelGroupRecentContent(groupId, maxResults) {
            var channelGroup = channelService.getChannelGroupById(groupId);
            if (!channelGroup) {
                return [];
            }

            var content = getChannelGroupContent({channels: channelGroup.channels});
            content = getUniqueContent(content);
            content = sortContent(content, 'updateddate');
            if (!isNaN(maxResults)) {
                content = content.splice(0, maxResults);
            }

            for (var i = 0; i < content.length; i++) {
                content[i].loadFilter = 'channelgroup:' + groupId + ':' + (maxResults ? maxResults : '');
                content[i].position = i;
            }

            return content;
        }

        function getContentByFilter(filterString) {
            var contentCollection = [];
            if (!filterString) {
                return contentCollection;
            }

            var filterSplits = [];
            if (typeof filterString === 'string' || filterString instanceof String) {
                filterSplits = filterString.toLowerCase().split(':');
            }

            var filterLength = filterSplits.length;
            switch (filterSplits[0]) {
                case 'recent':
                    return getRecentContent(filterLength >= 2 ? parseInt(filterSplits[1], 10) : MAX_RESULT_COUNT);
                case 'channelgroup':
                    if (filterLength >= 2) {
                        return getChannelGroupRecentContent(filterSplits[1],
                            filterLength >= 3 ? parseInt(filterSplits[2], 10) : MAX_RESULT_COUNT);
                    }

                    break;
                case 'channel':
                    if (filterLength >= 2) {
                        return getChannelContent(channelService.getChannelByChannelId(parseInt(filterSplits[1], 10)));
                    }

                    break;
            }

            return contentCollection;
        }

        function getChannelFromFilter(channelIdFilter) {
            if (!channelIdFilter || !dataUtil.isString(channelIdFilter)) {
                return;
            }

            var splits = channelIdFilter.toLowerCase().split(':');
            if (!splits.length || splits[0] !== 'channel') {
                return;
            }

            return channelService.getChannelByChannelId(parseInt(splits[1], 10));
        }

        return {
            getContentByFilter: getContentByFilter,
            sortLiveChannel: sortLiveChannel,
            sortChannelGroups: sortChannelGroups,
            sortContent: sortContent,
            getUniqueChannels: getUniqueChannels,
            getChannelsOnly: getChannelsOnly,
            getUniqueContent: getUniqueContent,
            getChannelContent: getChannelContent,
            getChannelGroupContent: getChannelGroupContent,
            getChannelFromFilter: getChannelFromFilter,
            isPlayoutValid: isPlayoutValid,

        };
    }]
    );
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('signsHTML5');

    app.factory('contentHelper', ["dataUtil", "CONTENT_TYPE", "broadcastService", "halPlatformService", "mediaUtil", "configurationService", "PLAYER_PROPERTY_KEYS", function(dataUtil, CONTENT_TYPE, broadcastService, halPlatformService, mediaUtil, configurationService, PLAYER_PROPERTY_KEYS) {
        var contentsWithVideoLayer = [CONTENT_TYPE.VIDEO, CONTENT_TYPE.SIGNS];
        function processVideoLayering(content) {
            var noSupportVideoLayering = halPlatformService.getFeatureSupport('rendering.video-layering') === 'no';
            if (!noSupportVideoLayering || contentsWithVideoLayer.indexOf(content.mediaType) === -1) {
                broadcastService.disableBackmostVideoLayer();
                return;
            }

            content.options.layerindex = -1;
            broadcastService.enableBackmostVideoLayer();
        }

        function processIframeOption(content) {
            mediaUtil.processForceIframeOption(content.options,
                configurationService.getCurrentProperty(PLAYER_PROPERTY_KEYS.WEB_RENDERER));
        }

        function getContentSchedule(playout) {
            if (!playout.start && !playout.end) {
                return;
            }

            return {
                startDate: playout.start,
                endDate: playout.end
            };
        }

        function getContentRecurrence(playout) {
            if (!playout.daypartings || !playout.daypartings.length) {
                return;
            }

            return {
                days: playout.daypartings
            };
        }

        function applyContentPlayoutProperties(target, playout) {
            if (!playout || !target) {
                return target;
            }

            target.stretch = playout.aspectRatio || undefined;
            target.duration = dataUtil.isNullOrUndefined(playout.duration) ? undefined : playout.duration;
            target.enterTransition = playout.enterTransition || undefined;
            target.schedule = getContentSchedule(playout) || {};
            target.schedule.recur = getContentRecurrence(playout);
            return target;
        }

        function applyContentItemsPlayoutProperties(items) {
            for (var i = 0; i < items.length; i++) {
                var item = items[i];
                if (!item) {
                    continue;
                }

                applyContentPlayoutProperties(item, item.playout);
            }
        }

        function processPlaybackOptions(content) {
            if (!content) {
                return;
            }

            content.options = content.options || {};
            processVideoLayering(content);
            processIframeOption(content);
        }

        return {
            applyContentPlayoutProperties: applyContentPlayoutProperties,
            applyContentItemsPlayoutProperties: applyContentItemsPlayoutProperties,
            processPlaybackOptions: processPlaybackOptions
        };
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;

    angular.module('signsHTML5').factory(
        'navigationHelper',
        ["$location", "channelService", function($location, channelService) {
            var homeUrl = '/tv/channelbrowser';

            function goHome(searchOptions) {
                gotoUrl(homeUrl, searchOptions);
            }

            function clearHistory() {
                channelService.removeHistory();
                channelService.setLastQuery({});
            }

            function clearHistoryAndGoHome() {
                clearHistory();
                goHome();
            }

            function gotoUrl(url, searchOptions) {
                var location = $location.url(url);
                if (!searchOptions) {
                    return;
                }

                if (searchOptions.key && searchOptions.value) {
                    location.search(searchOptions.key, searchOptions.value);
                } else if (searchOptions.qs) {
                    location.search(searchOptions.qs);
                }
            }

            return {
                goHome: goHome,
                clearHistoryAndGoHome: clearHistoryAndGoHome
            };
        }]
    );
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('signsHTML5');
    app.factory('scrollToHelper', function() {
        function isItemEmpty(item) {
            return item && item.length === 0;
        }

        function getElementHorizontalScrollOffset(element) {
            element = $(element);
            if (isItemEmpty(element)) {
                return 0;
            }

            var rect = element[0].getBoundingClientRect();
            // convert rect into a readable object
            rect = {
                left: rect.left,
                right: rect.right
            };
            var marginLeft = element.css('margin-left').replace('px', '');
            var marginRight = element.css('margin-right').replace('px', '');
            if (marginLeft) {
                rect.left -= parseFloat(marginLeft);
            }

            if (marginRight) {
                rect.right += parseFloat(marginRight);
            }

            var windowWidth = $(window).width();
            if (rect.left < 0) {
                return Math.round(rect.left);
            }

            if (rect.left > windowWidth) {
                return Math.round((rect.left - windowWidth) + (rect.right - rect.left));
            }

            if (rect.right > windowWidth) {
                return Math.round(rect.right - windowWidth);
            }

            return 0;
        }

        function getElementVerticalScrollOffset(element, containerElement) {
            element = $(element);
            if (isItemEmpty(element)) {
                return 0;
            }

            var rect = element[0].getBoundingClientRect();
            // convert rect into a readable object
            rect = {
                top: rect.top,
                bottom: rect.bottom
            };
            var marginBottom = element.css('margin-bottom').replace('px', '');
            var marginTop = element.css('margin-top').replace('px', '');
            if (marginBottom) {
                rect.bottom += parseFloat(marginBottom);
            }

            if (marginTop) {
                rect.top -= parseFloat(marginTop);
            }

            var containerTop = 0;
            if (containerElement) {
                containerTop = containerElement[0].getBoundingClientRect().top;
            } else {
                containerElement = $(window);
            }

            var containerHeight = containerElement.height();
            var elementTop = rect.top - containerTop;
            var elementBottom = rect.bottom - containerTop;
            // If the element's top is above the viewport
            if (elementTop < 0) {
                return Math.round(elementTop);
            }

            // If the element's top is below the viewport
            if (elementTop > containerHeight) {
                return Math.round((elementTop - containerHeight) + (elementBottom - elementTop));
            }

            // If the element's top is within the viewport but its bottom exceeds the viewport
            if (elementBottom > containerHeight) {
                return Math.round(elementBottom - containerHeight);
            }

            return 0;
        }

        function isOverflowAutoOrScroll(overflow) {
            return overflow === 'auto' || overflow === 'scroll';
        }

        function getScrollParent(element, overflowProperty, scrollSizeProperty, clientSizeProperty) {
            element = $(element);
            if (isItemEmpty(element)) {
                return null;
            }

            var parent = $(element).parent();
            while (parent[0].parentNode) {
                var overflow = parent.css(overflowProperty);
                if (isOverflowAutoOrScroll(overflow)) {
                    var scrollable = parent[0][scrollSizeProperty] > parent[0][clientSizeProperty];
                    if (scrollable) {
                        return parent;
                    }
                }

                parent = parent.parent();
            }
            return null;
        }

        function getHorizontalScrollParent(element) {
            return getScrollParent(element, 'overflow-x', 'scrollWidth', 'clientWidth');
        }
        
        function getVerticalScrollParent(element) {
            return getScrollParent(element, 'overflow-y', 'scrollHeight', 'clientHeight');
        }

        function scrollTo(element, left, top, animate) {
            element = $(element);
            if (isItemEmpty(element)) {
                return;
            }

            if (left != null) {
                element = getHorizontalScrollParent(element);
                if (element) {
                    element[0].scroll({left: left, behavior: animate ? 'smooth' : undefined});
                }
            }

            if (top != null) {
                element = getVerticalScrollParent(element);
                if (element) {
                    element[0].scroll({top: top, behavior: animate ? 'smooth' : undefined});
                }
            }
        }

        function scrollToElement(element, type, animate, containerElement) {
            // Enumerate through each parent, and scroll to this element
            element = $(element);
            while (element.length) {
                if (!type || element.is(type)) {
                    var horizontalScrollOffset = getElementHorizontalScrollOffset(element);
                    var verticalScrollOffset = getElementVerticalScrollOffset(element, containerElement);
                    var verticalParent = null;
                    var horizontalParent = null;
                    if (verticalScrollOffset != 0) {
                        verticalParent = getVerticalScrollParent(element);
                    }

                    if (horizontalScrollOffset != 0) {
                        horizontalParent = getHorizontalScrollParent(element);
                    }

                    if (verticalParent && verticalParent == horizontalParent) {
                        verticalParent[0].scrollBy({top: verticalScrollOffset, left: horizontalScrollOffset, behavior: animate ? 'smooth' : undefined});
                    } else if (verticalParent) {
                        verticalParent[0].scrollBy({top: verticalScrollOffset, left: 0, behavior: animate ? 'smooth' : undefined});
                    } else if (horizontalParent) {
                        horizontalParent[0].scrollBy({top: 0, left: horizontalScrollOffset, behavior: animate ? 'smooth' : undefined});
                    }

                    return;
                }
                element = element.parent();
            }
        }

        function isPlacementIgnored(placement, offset, sourcePosition) {
            switch (placement) {
                case 'above':
                    if (offset.top >= sourcePosition.top) {
                        return true;
                    }
                    break;
                case 'below':
                    if (offset.top <= sourcePosition.top) {
                        return true;
                    }
                    break;
                case 'left':
                    if (offset.left >= sourcePosition.left || offset.top != sourcePosition.top) {
                        return true;
                    }
                    break;
                case 'right':
                    if (offset.left <= sourcePosition.left || offset.top != sourcePosition.top) {
                        return true;
                    }
                    break;
            }
            return false;
        }

        function getNextElement(sourceElement, type, placement) {
            if (!type || !sourceElement || !placement) {
                return null;
            }

            // Find parent that is of the type requested
            sourceElement = $(sourceElement);
            while (!sourceElement.is(type)) {
                sourceElement = sourceElement.parent();
                if (isItemEmpty(sourceElement)) {
                    return;
                }
            }
            var sourcePosition = sourceElement.offset();
            var elements = $(type);
            var distance = 0;
            var targetElement = null;
            for (var i = 0; i < elements.length; i++) {
                var element = $(elements[i]);
                if (element == sourceElement) {
                    continue;
                }

                var offset = element.offset();

                if (isPlacementIgnored(placement, offset, sourcePosition)) {
                    continue;
                }

                var leftDelta = offset.left - sourcePosition.left;
                var topDelta = offset.top - sourcePosition.top;
                var elementDistance = Math.sqrt(leftDelta * leftDelta + topDelta * topDelta);
                if (!distance || elementDistance < distance) {
                    targetElement = element;
                    distance = elementDistance;
                }
            }
            return targetElement;
        }

        return {
            getNextElement: getNextElement,
            scrollToElement: scrollToElement,
            scrollTo: scrollTo
        };
    }
    );
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;

    angular.module('signsHTML5').factory(
        'themeHelper',
        ["$q", "FontUtil", "dataUtil", "halMediaService", "storageLocalStorageService", "channelService", "syncService", "networkContentRouterService", "UrlUtil", function($q, FontUtil, dataUtil, halMediaService, storageLocalStorageService, channelService,
            syncService, networkContentRouterService, UrlUtil) {
            var themeSettings = {};
            var DEFAULT_BACK_PLACEMENT = 'topleft';
            var DEFAULT_VIDEO_SCRUBBING_DURATION = 10;
            var liveChannelSortByMapping = {
                name: 'title',
                number: 'channelNo',
                code: 'channelCode'
            };

            function resetThemeSettings() {
                themeSettings = {};
                themeSettings.liveChannelSortBy = liveChannelSortByMapping.name;
                themeSettings.accentColorOveride = null;
                themeSettings.showChannelGroupTitle = true;
                themeSettings.enableSearch = true;
                themeSettings.enableSidebar = true;
                themeSettings.enableClock = true;
                themeSettings.enableRecent = true;
                themeSettings.enableLogo = true;
                themeSettings.clockFormat = 'h:mm a';
                themeSettings.clockStyle = {};
                themeSettings.numberOfChannelRows = -1;
                themeSettings.numberOfChannelCols = -1;
                themeSettings.channelTitleStyle = {};
                themeSettings.channelGroupTitleStyle = {};
                themeSettings.homeBackgroundColor = {};
                themeSettings.sidebarStyle = {};
                themeSettings.idleDuration = 0;
                themeSettings.idleBehaviour = null;
                themeSettings.backPlacement = DEFAULT_BACK_PLACEMENT;
                themeSettings.backgroundScroll = undefined;
                themeSettings.backgroundScrollBlur = undefined;
                themeSettings.homeChannelGroupSummary = 'channels';
                themeSettings.persistBackButton = false;
                themeSettings.enableTileDefaultColor = true;
                themeSettings.enableTileShadow = true;
                themeSettings.enableChannelVideoScrubbing = false;
                themeSettings.channelVideoScrubbingDuration = DEFAULT_VIDEO_SCRUBBING_DURATION;
            }

            function loadPlayerSpecificConfigurations() {
                var promise = $q.defer();
                resetThemeSettings();
                syncService.retrieveDatasourceByType('applicationproperties').then(function(res) {
                    if (res && res[0] && res[0].result && res[0].result.properties) {
                        var properties = res[0].result.properties;
                        for (var key in properties) {
                            var value = properties[key];
                            switch (key.toLowerCase()) {
                                case 'player.play.idle.duration':
                                    themeSettings.idleDuration = parseInt(value, 10) * 1000;
                                    break;
                                case 'player.play.idle.behaviour':
                                case 'player.play.idle.behavior':
                                    themeSettings.idleBehaviour = (value || '').trim();
                                    break;
                                case 'player.play.themesettings.home.background.color':
                                    themeSettings.homeBackgroundColor = {
                                        background: 'none',
                                        backgroundColor: value || undefined
                                    };
                                    break;
                                case 'player.play.themesettings.home.sidebar.backgroundcolor':
                                    themeSettings.sidebarStyle.backgroundColor = value || undefined;
                                    break;
                                case 'player.play.themesettings.home.sidebar.color':
                                    themeSettings.sidebarStyle.color = value || undefined;
                                    break;
                                case 'player.play.themesettings.home.search.enable':
                                    themeSettings.enableSearch = dataUtil.parseBoolean(value);
                                    break;
                                case 'player.play.themesettings.home.logo.enable':
                                    themeSettings.enableLogo = dataUtil.parseBoolean(value);
                                    break;
                                case 'player.play.themesettings.home.clock.enable':
                                    themeSettings.enableClock = dataUtil.parseBoolean(value);
                                    break;
                                case 'player.play.themesettings.home.clock.format':
                                    themeSettings.clockFormat = value;
                                    break;
                                case 'player.play.themesettings.home.clock.color':
                                    themeSettings.clockStyle = {color: value || undefined};
                                    break;
                                case 'player.play.themesettings.home.sidebar.enable':
                                    themeSettings.enableSidebar = dataUtil.parseBoolean(value);
                                    break;
                                case 'player.play.themesettings.home.recent.enable':
                                    themeSettings.enableRecent = dataUtil.parseBoolean(value);
                                    break;
                                case 'player.play.themesettings.home.channelgroup.summary':
                                    themeSettings.homeChannelGroupSummary = dataUtil.toLowerCase(value);
                                    break;
                                case 'player.play.themesettings.home.channelgroup.title.enable':
                                    themeSettings.showChannelGroupTitle = dataUtil.parseBoolean(value);
                                    break;
                                case 'player.play.themesettings.home.channel.title.color':
                                    // Override the channel title style color if contains a value,
                                    // otherwise Reset the channel title style color
                                    themeSettings.channelTitleStyle = {color: value || undefined};
                                    break;
                                case 'player.play.themesettings.home.channelgroup.title.color':
                                    // Override the channel group title style color if contains a value
                                    // otherwise Reset the channel group title style color
                                    themeSettings.channelGroupTitleStyle = {color: value || undefined};
                                    break;
                                case 'player.play.themesettings.home.channelrows.count':
                                    if (!isNaN(value)) {
                                        themeSettings.numberOfChannelRows = parseFloat(value);
                                    }

                                    break;
                                case 'player.play.themesettings.home.channelcolumns.count':
                                    if (!isNaN(value)) {
                                        themeSettings.numberOfChannelCols = parseFloat(value);
                                    }

                                    break;
                                case 'player.play.themesettings.colors.accent':
                                    themeSettings.accentColorOveride = value;
                                    break;
                                case 'player.play.themesettings.home.livechannels.sortby':
                                    themeSettings.liveChannelSortBy = liveChannelSortByMapping[value] || liveChannelSortByMapping.name;
                                    break;
                                case 'player.play.themesettings.channel.fullscreen.back':
                                    themeSettings.persistBackButton = dataUtil.parseBoolean(value);
                                    break;
                                case 'player.play.themesettings.channel.fullscreen.back.placement':
                                    themeSettings.backPlacement = dataUtil.toLowerCase(value) || DEFAULT_BACK_PLACEMENT;
                                    break;
                                case 'player.play.themesettings.playlist.endofplaylistbehavior':
                                case 'player.play.themesettings.playlist.endofplaylistbehaviour':
                                    themeSettings.endofplaylistbehaviour = value;
                                    break;
                                case 'player.play.themesettings.home.background.behavior':
                                case 'player.play.themesettings.home.background.behaviour':
                                    value = dataUtil.toLowerCase(value);
                                    themeSettings.backgroundScroll = value === 'scroll';
                                    themeSettings.backgroundScrollBlur = value === 'blur';
                                    break;
                                case 'player.play.themesettings.home.background.scroll':
                                    // background scroll value in background behavior is prioritized
                                    if (dataUtil.isNullOrUndefined(themeSettings.backgroundScroll)) {
                                        themeSettings.backgroundScroll = dataUtil.parseBoolean(value);
                                    }

                                    break;
                                case 'player.play.themesettings.home.background.scroll.blur':
                                    // background scroll blur value in background behavior is prioritized
                                    if (dataUtil.isNullOrUndefined(themeSettings.backgroundScrollBlur)) {
                                        themeSettings.backgroundScrollBlur = dataUtil.parseBoolean(value);
                                    }

                                    break;
                                case 'player.play.themesettings.home.tile.default.color.enable':
                                    // disabling this removes default content tile background color
                                    themeSettings.enableTileDefaultColor = dataUtil.parseBoolean(value);
                                    break;
                                case 'player.play.themesettings.home.tile.shadow.enable':
                                    // disabling this removes content tile box shadow
                                    themeSettings.enableTileShadow = dataUtil.parseBoolean(value);
                                    break;
                                case 'player.play.themesettings.playback.video.scrubbing.enable':
                                    // enable video playback on channel browsing mode
                                    themeSettings.enableChannelVideoScrubbing = dataUtil.parseBoolean(value);
                                    break;
                                case 'player.play.themesettings.playback.video.scrubbing.interval':
                                    // duration for fast forward/rewind of video playback
                                    var duration = DEFAULT_VIDEO_SCRUBBING_DURATION;
                                    if (dataUtil.isNumber(value)) {
                                        var playbackDuration = Math.abs(value);
                                        if (dataUtil.isMoreThanZero(playbackDuration)) {
                                            duration = playbackDuration;
                                        }
                                    }

                                    themeSettings.channelVideoScrubbingDuration = duration;
                                    break;
                            }
                        }

                        FontUtil.installCustomFonts(FontUtil.convertPropertiesToFonts(properties));
                    }

                    promise.resolve();
                }, function(err) {
                    promise.resolve();
                });
                return promise.promise;
            }

            function applyTag() {
                // link
                var playStyle = document.getElementById('playOverwriteStyle');
                if (!playStyle) {
                    playStyle = document.createElement('link');
                    playStyle.id = 'playOverwriteStyle';
                    playStyle.rel = 'stylesheet';
                    document.getElementsByTagName('head')[0].appendChild(playStyle);
                }

                // properties
                var overwriteStyleProp = document.getElementById('overwriteStyleProperties');
                if (!overwriteStyleProp) {
                    overwriteStyleProp = document.createElement('style');
                    overwriteStyleProp.id = 'overwriteStyleProperties';
                    overwriteStyleProp.type = 'text/css';
                    document.getElementsByTagName('head')[0].appendChild(overwriteStyleProp);
                }

                return $q.resolve();
            }

            function applySetting(setting) {
                // link
                var playStyle = document.getElementById('playOverwriteStyle');
                playStyle.href = !setting || setting.name == 'dark' ? '' : 'assets/css/theme-light.css';
                var overwriteStyleProp = document.getElementById('overwriteStyleProperties');
                overwriteStyleProp.innerHTML = '';

                // properties
                if (!setting) {
                    return;
                }

                var theme = setting;
                // logo
                themeSettings.logo = setting.logo;
                themeSettings.isDefaultLogo = setting.isDefaultLogo;

                // Override theme accent color if player property present
                if (themeSettings.accentColorOveride) {
                    theme.accent = themeSettings.accentColorOveride;
                }

                themeSettings.accent = theme.accent;
                // accent
                // channel browser active item
                overwriteStyleProp.innerHTML += '.channel-browser .channel-group .channel-list .ui-grid-item-active .title {background-color:' + theme.accent + ';}';
                // video playback progress
                overwriteStyleProp.innerHTML += 'video-playback-bar .progress-bar-container .current-progress {background-color:' + theme.accent + ';}';
                // live channel
                overwriteStyleProp.innerHTML += '.live-channel .epg-overlay .program .program-schedule .now .time {background-color:' + theme.accent + ';}';
                // left menu
                overwriteStyleProp.innerHTML += '.theme-dark .list-group-item.active, .theme-dark .list-group-item.focus {background-color:' + theme.accent + ' !important;}';
                // search icon
                overwriteStyleProp.innerHTML += '.channel-browser .search-overlay .content .keyboard .as-keyboard .key-focus {background-color:' + theme.accent + ' !important;}';
                overwriteStyleProp.innerHTML += '.channel-browser .search-overlay .content .results .search-list .ui-grid-item-active {background-color:' + theme.accent + ' !important;}';
                overwriteStyleProp.innerHTML += '.channel-browser .banner .search:hover, .channel-browser .banner .search:active, .channel-browser .banner .search-selected {background-color:' + theme.accent + ' !important;}';
                // epg
                overwriteStyleProp.innerHTML += '.epg .container .selected-program .right .instruction {color:' + theme.accent + ' !important;}';
                overwriteStyleProp.innerHTML += '.epg .epg-table .selected {background-color:' + theme.accent + ' !important;}';
                // side menu
                overwriteStyleProp.innerHTML += '.side-menu .as-side-menu .list-group .ui-grid-item-active {background-color:' + theme.accent + ' !important;}';
                // content tile aspect ratio
                overwriteStyleProp.innerHTML += '.media-container {background-size: ' + (themeSettings.homeChannelGroupSummary === 'content' ? 'cover' : 'contain') + ' !important;}';
            }

            function applyTheme(settings) {
                var defer = $q.defer();
                loadPlayerSpecificConfigurations().then(function() {
                    // If no setting is supplied, query from channel service
                    if (!settings) {
                        settings = channelService.getThemeSettings();
                        // If there is a theme setting, store for future lookup
                        if (settings) {
                            storageLocalStorageService.set('appspace.interactive', 'theme', JSON.stringify(settings));
                        } else {
                            // Remove preious cache
                            storageLocalStorageService.removeKey('appspace.interactive', 'theme');
                        }
                    }

                    var resolveSetting = function() {
                        var setting;
                        if (settings && settings.themes) {
                            for (var i = 0; i < settings.themes.length; i++) {
                                var theme = settings.themes[i];
                                if (settings.default == theme.name) {
                                    setting = theme;
                                    break;
                                }
                            }
                        }

                        var applyAndResolve = function() {
                            applySetting(setting);
                            defer.resolve();
                        };

                        var onLogoResolved = function(newLogoPath) {
                            setting.logo = newLogoPath;
                            applyAndResolve();
                        };

                        var resolveLogo = function() {
                            // resolve logo path
                            halMediaService.resolveContentPath(setting.logo).then(onLogoResolved);
                        };

                        if (!setting || !setting.logo) {
                            applyAndResolve();
                            return;
                        }

                        networkContentRouterService.resolve(setting.logo, 'play-content-routing-service', {
                            type: 'image'
                        }).then(function(url) {
                            if (!UrlUtil.isUrlHttpOrHttps(url)) {
                                setting.logo = url;
                                resolveLogo();
                                return;
                            }

                            networkContentRouterService.resolve(url, 'framework.media.image', {
                                type: 'image'
                            }).then(function(url) {
                                setting.logo = url;
                                resolveLogo();
                            }, resolveLogo);
                        }, resolveLogo);
                    };

                    applyTag().then(resolveSetting);
                });

                return defer.promise;
            }

            function getThemeSettings() {
                return themeSettings;
            }

            function applyCachedTheme() {
                var themeObj = storageLocalStorageService.get('appspace.interactive', 'theme');
                if (!themeObj) {
                    return;
                }

                themeObj = JSON.parse(themeObj);
                applyTheme(themeObj);
            }

            return {
                loadPlayerSpecificConfigurations: loadPlayerSpecificConfigurations,
                applyCachedTheme: applyCachedTheme,
                getThemeSettings: getThemeSettings,
                applyTheme: applyTheme,
                getDefaultTheme: function() {
                    return {
                        backPlacement: DEFAULT_BACK_PLACEMENT
                    };
                }
            };
        }]
    );
})(window.angular);

(function($angular) {
'use strict';
    var angular = $angular;
    angular.module('signsHTML5').component('loader', {
        templateUrl: 'tv/channelbrowser/loader/loader.html'
    });
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('signsHTML5');
    app.component('backButton', {
        templateUrl: 'tv/channelbrowser/channel-browsing/back-button/back-button.html',
        bindings: {
            show: '<',
            onBack: '&'
        },
        controller: function() {
            var $ctrl = this;
            $ctrl.onClick = function($event) {
                if ($ctrl.onBack) {
                    $ctrl.onBack();
                }

                $event.stopPropagation();
                $event.preventDefault();
            };
        }
    });
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('signsHTML5');
    app.component('backgroundChannel', {
        templateUrl: 'tv/channelbrowser/channel-browsing/background-channel/background-channel.html',
        bindings: {
            onReady: '&'
        },
        controller: ["$timeout", "channelService", "LinearPlaylist", "syncService", "networkContentRouterService", "halPlatformService", "contentHelper", "COMMON", "broadcastService", "lowBandwidthService", function($timeout, channelService, LinearPlaylist, syncService, networkContentRouterService,
            halPlatformService, contentHelper, COMMON, broadcastService, lowBandwidthService) {
            var $ctrl = this;
            $ctrl.currentContent = null;
            $ctrl.contentDuration = COMMON.DEFAULT_SIGNS_CONTENT_DURATION;
            $ctrl.scale = 'uniformfill';
            var presenterApi = null;
            var fireReady = false;
            var backgroundPlaylist = new LinearPlaylist(false, true);
            var DEFAULT_LONG_DURATION = 99999999;
            var checkBackgroundPlaylistValidityPromise;
            var CHECK_PLAYLIST_VALIDITY_TIMEOUT = 5000;

            $ctrl.$onInit = function() {
                channelService.isReady().then(function() {
                    var backgroundChannelPosition = halPlatformService.getFeatureSupport('background.channel.position');
                    if (backgroundChannelPosition) {
                        $ctrl.customStyle = {
                            position: backgroundChannelPosition
                        };
                    }

                    loadPlaylist();
                    nextItem();
                    if (!$ctrl.onReady || isBackgroundPlaylistValid()) {
                        return;
                    }

                    // if we have playlist items without active items, check again later
                    if (!isBackgroundPlaylistEmpty()) {
                        startCheckBackgroundPlaylistValidity();
                    }

                    // There are no items, just fire ready
                    $ctrl.onReady();
                    fireReady = true;
                });
            };

            $ctrl.$onDestroy = unloadPlayback;

            $ctrl.onPresenterApi = function(api) {
                presenterApi = api;
            };

            $ctrl.onItemLoad = function() {
                if (!$ctrl.onReady || fireReady) {
                    return;
                }

                $ctrl.onReady();
                fireReady = true;
            };

            $ctrl.onLoadError = nextItem;

            $ctrl.onMediaComplete = nextItem;

            function startCheckBackgroundPlaylistValidity() {
                clearCheckBackgroundPlaylistValidity();
                checkBackgroundPlaylistValidityPromise = $timeout(checkBackgroundPlaylistValidity, CHECK_PLAYLIST_VALIDITY_TIMEOUT);
            }

            function clearCheckBackgroundPlaylistValidity() {
                if (!checkBackgroundPlaylistValidityPromise) {
                    return;
                }

                $timeout.cancel(checkBackgroundPlaylistValidityPromise);
            }

            function checkBackgroundPlaylistValidity() {
                clearCheckBackgroundPlaylistValidity();
                backgroundPlaylist.updateActiveItems();
                if (isBackgroundPlaylistValid()) {
                    nextItem();
                    return;
                }

                // if playlist empty, stop checking
                if (isBackgroundPlaylistEmpty()) {
                    return;
                }

                // if no valid item, restart checking
                startCheckBackgroundPlaylistValidity();
            }

            function isBackgroundPlaylistValid() {
                return backgroundPlaylist.activeItems && backgroundPlaylist.activeItems.length;
            }

            function isBackgroundPlaylistEmpty() {
                return !backgroundPlaylist.items || !backgroundPlaylist.items.length;
            }

            function unloadPlayback() {
                broadcastService.disableBackmostVideoLayer();
                if (!presenterApi || !presenterApi.unload) {
                    return;
                }
                presenterApi.unload();
            }

            function nextItem() {
                var next = backgroundPlaylist.next();
                if (!next) {
                    unloadPlayback();
                    checkBackgroundPlaylistValidity();
                    return;
                }

                // if this first item, disable transition in
                if (!$ctrl.currentContent) {
                    next.enterTransition = '';
                }

                // Get player properties
                next.data = {
                    properties: syncService.getCurrentAppProperties()
                };

                var processNextItem = function() {
                    contentHelper.processPlaybackOptions(next);
                    var onResolveError = function(err) {
                        $ctrl.currentContent = next;
                    };
                    var onWpgRoutingResolved = function(url) {
                        next.mediaUrl = url;
                        $ctrl.currentContent = next;
                    };
                    var resolveWpgRouting = function(url) {
                        networkContentRouterService.resolve(url, 'signs-wpg-routing-service').then(onWpgRoutingResolved, function() {
                            // if we receive error while resolving wpg, just resolve it directly
                            onWpgRoutingResolved(url);
                        });
                    };
                    networkContentRouterService.resolve(next.mediaUrl, 'signs-content-routing-service', {
                        type: next.mediaType,
                        cache: true
                    }).then(resolveWpgRouting, onResolveError);
                };

                var checkLowBandwidth = function() {
                    lowBandwidthService.isEnabled().then(function(isEnabled) {
                        if (!isEnabled) {
                            processNextItem();
                            return;
                        }

                        next.options = next.options || {};
                        next.options.lowBandwidthMask = false;
                        processNextItem();
                    });
                };

                checkLowBandwidth();
            }

            function loadPlaylist() {
                var backgroundPlaylistItems = [];
                // Check is there a theme background channel
                var backgroundChannel = channelService.getBackgroundChannel();
                if (backgroundChannel && backgroundChannel.subChannels) {
                    $ctrl.contentDuration = backgroundChannel.defaultContentDuration || COMMON.DEFAULT_SIGNS_CONTENT_DURATION;
                    var bgContent = null;
                    // Find the first content item for this channel that is an image
                    for (var i = 0; i < backgroundChannel.subChannels.length && !bgContent; i++) {
                        var subChannel = backgroundChannel.subChannels[i];
                        for (var k = 0; k < subChannel.assets.length; k++) {
                            var asset = subChannel.assets[k];
                            // Add the asset to the background playlist
                            if (asset.type === 'content') {
                                var playlistItem = angular.copy(asset);
                                contentHelper.applyContentPlayoutProperties(playlistItem, subChannel.playout);
                                backgroundPlaylistItems.push(playlistItem);
                            }
                        }
                    }
                }
                // Check if we only have one item, and its not video, set the playout duration to a long time
                if (backgroundPlaylistItems.length === 1) {
                    var item = backgroundPlaylistItems[0];
                    if (item.mediaType.toLowerCase() != 'video') {
                        $ctrl.contentDuration = DEFAULT_LONG_DURATION;
                    }
                }

                backgroundPlaylist.setItems(backgroundPlaylistItems);
            }
        }]
    });
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('signsHTML5');
    app.component('banner', {
        templateUrl: 'tv/channelbrowser/channel-browsing/banner/banner.html',
        bindings: {
            theme: '<'
        },
        controller: ["$element", "mediaUtil", function($element, mediaUtil) {
            var $ctrl = this;
            $ctrl.$onChanges = function() {
                if (!$ctrl.theme || !$ctrl.theme.logo) {
                    return;
                }

                updateLogo();
            };

            function updateLogo() {
                $element.find('.banner-logo').css('background-image', mediaUtil.toCssUrl($ctrl.theme.logo));
            }
        }]
    });
})(window.angular);

(function($angular) {
'use strict';
    var angular = $angular;
    var app = angular.module('signsHTML5');
    app.component('contentGrid', {
        templateUrl: 'tv/channelbrowser/channel-browsing/content-grid/content-grid.html',
        bindings: {
            selected: '<',
            onApiReady: '&'
        },
        transclude: true,
        controller: ["$element", "$scope", "$interval", function($element, $scope, $interval) {
            var $ctrl = this;
            $ctrl.$onInit = function() {
                $ctrl.api = {
                    addItems: $ctrl.addItems
                };
                if ($ctrl.onApiReady) {
                    $ctrl.onApiReady({api: $ctrl.api});
                }
            };

            $ctrl.addItems = function(items) {
                var transclude = $element.find('ng-transclude');
                for (var i = 0; i < items.length; i++) {
                    transclude.append(items[i].element);
                }
            };
        }]
    });
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('signsHTML5');
    app.component('clock', {
        templateUrl: 'tv/channelbrowser/channel-browsing/clock/clock.html',
        bindings: {
            format: '<'
        },
        controller: ["$interval", function($interval) {
            var $ctrl = this;
            var updateInterval;
            var UPDATE_INTERVAL_DURATION = 1000;
            var updateClock = function() {
                $ctrl.time = moment(new Date()).format($ctrl.format || 'h:mm a');
            };

            $ctrl.$onInit = function() {
                updateClock();
                updateInterval = $interval(updateClock, UPDATE_INTERVAL_DURATION);
            };

            $ctrl.$onDestroy = function() {
                $interval.cancel(updateInterval);
            };
        }]
    });
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('signsHTML5');
    app.factory('contentTileTemplateHelper', ["$q", "$compile", "FileUtil", "dataUtil", function($q, $compile, FileUtil, dataUtil) {
        var SVG = 'svg';
        var LIVE_CHANNEL = 'livechannel';

        function getAssetThumbnailImage(item) {
            // Check does this item have an assets collection
            var imageSource = '';
            if (!item.assets) {
                return;
            }

            for (var i = 0; i < item.assets.length; i++) {
                var asset = item.assets[i];
                // if we have thumbnail and the thumbnail is an image
                if (asset.type.toLowerCase() !== 'thumbnail') {
                    continue;
                }

                if (asset.mediaUrl && asset.mediaType == 'image') {
                    imageSource = asset.mediaUrl;
                }

                break;
            }
            return imageSource;
        }

        function createTemplate(item, scope) {
            var title = item.title;
            if (item.channelCode) {
                title = item.channelCode;
            }

            var channelNo = parseInt(item.channelNo, 10);
            if (dataUtil.isNumber(channelNo)) {
                title += ' #' + channelNo;
            }

            title = dataUtil.sanitizeString(title);
            var mediaSrc = getAssetThumbnailImage(item);
            var contentExtension = FileUtil.getFileExtension(mediaSrc.toLowerCase());
            // Respect channel logo aspect ratio if it's svg or live channel
            var stretch = item.channelType == LIVE_CHANNEL || contentExtension == SVG ? 'contain' : 'cover';
            var template = '<content-tile id="content-tile-' + item.id + '"ng-click="onContentTileClick($event,item)" title="\'' + title + '\'" color="item.iconColor" alt-text="altText" media-src="mediaSrc" stretch="stretch"> ' +
                '</content-tile>';
            var tileScope = scope.$new();
            tileScope.title = title;
            tileScope.stretch = stretch;
            tileScope.altText = item.channelCode || generateAlternateText(title);
            if (mediaSrc) {
                tileScope.mediaSrc = mediaSrc;
            }

            tileScope.onContentTileClick = scope.onContentTileClick;
            tileScope.item = item;
            // create the tile component
            var tile = $compile(template)(tileScope);
            tile[0]._item = item;
            return $q.resolve({type: 'element', element: tile});
        }

        function generateAlternateText(text) {
            if (!text) {
                return '';
            }

            // Remove non AZ chars
            var alt = text.toUpperCase().replace(/[^A-Z\040]+/g, '');

            // Split on spaces
            var segs = alt.split(' ');
            if (segs.length == 1) {
                return segs[0].substring(0, 3);
            }

            alt = '';
            // Take first char from each seg
            for (var i = 0; i < segs.length; i++) {
                alt += segs[i].substring(0, 1);
                if (alt.length == 3) {
                    break;
                }
            }
            return alt;
        }

        function isImageValid(source) {
            if (!source) {
                return $q.reject('Empty source');
            }

            var defer = $q.defer();
            var image = new Image();
            var disposeImage = function() {
                image.onload = undefined;
                image.onerror = undefined;
                image = undefined;
            };
            image.src = source;
            image.onload = function() {
                disposeImage();
                defer.resolve(source);
            };
            image.onerror = function(error) {
                disposeImage();
                defer.reject(error);
            };
            return defer.promise;
        }

        return {
            createTemplate: createTemplate,
            isImageValid: isImageValid,
            generateAlternateText: generateAlternateText
        };
    }]
    );
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('signsHTML5');
    app.component('contentTile', {
        templateUrl: 'tv/channelbrowser/channel-browsing/content-tile/content-tile.html',
        bindings: {
            mediaSrc: '<',
            title: '<',
            color: '<',
            altText: '<',
            stretch: '<'
        },
        controller: ["networkContentRouterService", "contentTileTemplateHelper", "halMediaService", "mediaUtil", function(networkContentRouterService, contentTileTemplateHelper, halMediaService, mediaUtil) {
            var $ctrl = this;
            $ctrl.mediaStyle = {};
            $ctrl.alternateText;
            $ctrl.$onChanges = function() {
                if ($ctrl.mediaSrc) {
                    // remove alternate text
                    $ctrl.alternateText = '';
                    networkContentRouterService.resolve(
                        $ctrl.mediaSrc,
                        'play-content-routing-service',
                        {type: 'image', cache: true}).then(trySetBackground);
                } else {
                    setAlternateText();
                }

                if ($ctrl.color) {
                    $ctrl.mediaStyle['background-color'] = $ctrl.color;
                }
            };

            function onContentResolved(url) {
                contentTileTemplateHelper.isImageValid(url)
                    .then(setBackground, setAlternateText);
            }

            function trySetBackground(url) {
                halMediaService.resolveContentPath(url).then(onContentResolved);
            }

            function setBackground(url) {
                $ctrl.mediaStyle['background-image'] = mediaUtil.toCssUrl(url);
            }

            function setAlternateText() {
                $ctrl.alternateText = $ctrl.altText;
            }
        }]
    });
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('signsHTML5');
    app.component('channelContentStrip', {
        templateUrl: 'tv/channelbrowser/channel-viewer/channel-content-strip/channel-content-strip.html',
        bindings: {
            contentItems: '<',
            currentItem: '<',
            currentProgress: '<',
            showPlayIcon: '<',
            onItemClick: '&'
        },
        controller: ["$timeout", "$element", "$window", "contentTileTemplateHelper", "dataUtil", function($timeout, $element, $window, contentTileTemplateHelper, dataUtil) {
            var SCROLL_DELAY = 10;
            var DURATION_INSTANT = 0;
            var DURATION_SCROLLING = 600;
            var $ctrl = this;
            $ctrl.$onChanges = function(changes) {
                if (!changes || !changes.currentItem || !changes.currentItem.currentValue) {
                    return;
                }

                $ctrl.title = $ctrl.currentItem.title;
                var channelNo = parseInt($ctrl.currentItem.channelNo, 10);
                if (dataUtil.isNumber(channelNo)) {
                    $ctrl.title += ' #' + channelNo;
                }
                $timeout(scrollToCurrentItem, SCROLL_DELAY);
            };

            $ctrl.onContentItemClick = function($event, content) {
                if ($ctrl.onItemClick) {
                    $ctrl.onItemClick({item: content});
                }

                $event.preventDefault();
                $event.stopPropagation();
            };

            $ctrl.generateAlternateText = contentTileTemplateHelper.generateAlternateText;

            function scrollToCurrentItem() {
                var contentGrid = $element.find('content-grid');
                var currentContentTile = contentGrid.find('.tile-selected');
                if (!currentContentTile.length) {
                    return;
                }

                var tile = $(currentContentTile[0]);
                var windowHeight = $($window).height();
                var duration = $element.offset().top >= windowHeight ? DURATION_INSTANT : DURATION_SCROLLING;
                tile.velocity('scroll', {axis: 'x', container: contentGrid, duration: duration});
            }
        }]
    });
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('signsHTML5');
    app.component('channelContentPlayer', {
        templateUrl: 'tv/channelbrowser/channel-viewer/channel-content-player/channel-content-player.html',
        bindings: {
            mute: '<',
            contentItems: '<',
            startIndex: '<',
            contentDuration: '<',
            loop: '<',
            onApiReady: '&',
            onItemBeginLoad: '&',
            onItemLoad: '&',
            onItemProgress: '&',
            onPlaylistComplete: '&',
            onNoValidPlaylistItems: '&',
            onItemOffsetInvalid: '&'
        },
        controller: ["$timeout", "LinearPlaylist", "syncService", "networkContentRouterService", "halPlatformService", "COMMON", "CONTENT_TYPE", "contentHelper", "broadcastService", function($timeout, LinearPlaylist, syncService, networkContentRouterService,
            halPlatformService, COMMON, CONTENT_TYPE, contentHelper, broadcastService) {
            var $ctrl = this;
            $ctrl.currentContent = null;
            $ctrl.contentDuration = null;
            $ctrl.currentItem = null;
            $ctrl.currentProgress = null;
            var presenterApi = null;
            var fireLoaded = false;
            var playlist = new LinearPlaylist(false, true);
            var checkPlaylistValidityPromise;
            var CHECK_PLAYLIST_VALIDITY_TIMEOUT = 5000;
            $ctrl.scale = 'uniformfill';

            $ctrl.$onInit = function() {
                // Construct the api interface
                var api = {
                    nextItem: nextItem,
                    previousItem: previousItem,
                    gotoItem: gotoItem,
                    play: play,
                    pause: pause,
                    seek: seek,
                    getCurrentItemRuntimeConfiguration: getCurrentItemRuntimeConfiguration,
                    togglePause: togglePause
                };

                if (!$ctrl.contentDuration) {
                    $ctrl.contentDuration = COMMON.DEFAULT_SIGNS_CONTENT_DURATION;
                }

                if ($ctrl.onApiReady) {
                    $ctrl.onApiReady({api: api});
                }
            };

            $ctrl.$onDestroy = unloadPlayback;

            $ctrl.$onChanges = function(changes) {
                if (!changes || !changes.contentItems || !changes.contentItems.currentValue) {
                    return;
                }

                loadPlaylist();
                if (processPlaylistItemsInvalid()) {
                    return;
                }

                if ($ctrl.startIndex) {
                    gotoItem($ctrl.startIndex);
                    return;
                }

                nextItem();
            };

            $ctrl.onPresenterApi = function(api) {
                presenterApi = api;
            };

            $ctrl.onContentBeginTransition = fireLoadedEvent;

            $ctrl.onContentLoad = fireLoadedEvent;

            $ctrl.onContentLoadError = function(item) {
                nextItem();
            };

            $ctrl.onContentComplete = function(item) {
                if ($ctrl.loop === false && playlist.isLastItem()) {
                    if ($ctrl.onPlaylistComplete) {
                        $ctrl.onPlaylistComplete();
                    }

                    return true;
                }

                $ctrl.currentItem = null;
                $ctrl.currentProgress = null;
                nextItem();
                return false;
            };

            $ctrl.onContentProgress = function(progress, item) {
                if (!$ctrl.onItemProgress) {
                    return;
                }

                $ctrl.currentProgress = progress;
                // if item has its own duration (set via Appspace), use that
                if (item.duration) {
                    progress.duration = item.duration;
                }

                $ctrl.onItemProgress({progress: progress});
            };

            function fireLoadedEvent(item) {
                if (fireLoaded) {
                    return;
                }

                fireLoaded = true;
                $ctrl.currentProgress = null;
                $ctrl.currentItem = item;
                if ($ctrl.onItemLoad) {
                    $ctrl.onItemLoad({item: $ctrl.currentContent, presenterItem: item});
                }
            }

            function clearCheckPlaylistValidity() {
                if (!checkPlaylistValidityPromise) {
                    return;
                }

                $timeout.cancel(checkPlaylistValidityPromise);
            }

            function startCheckPlaylistValidity() {
                clearCheckPlaylistValidity();
                $timeout(checkPlaylistValidity, CHECK_PLAYLIST_VALIDITY_TIMEOUT);
            }

            function checkPlaylistValidity() {
                // if playlist valid, go to next item
                if (isPlaylistItemsValid()) {
                    nextItem();
                    return;
                }

                // playlist doesn't have any valid items, recheck again later
                startCheckPlaylistValidity();
            }

            function isPlaylistItemsValid() {
                playlist.updateActiveItems();
                return playlist.activeItems.length && playlist.items.length;
            }

            function processPlaylistItemsInvalid() {
                if (isPlaylistItemsValid()) {
                    return false;
                }

                if ($ctrl.onNoValidPlaylistItems) {
                    $ctrl.onNoValidPlaylistItems();
                }

                startCheckPlaylistValidity();
                return true;
            }

            function unloadPlayback() {
                broadcastService.disableBackmostVideoLayer();
                executePresenterApiMethod('unload');
            }

            function gotoItem(itemOffset, playoutDuration) {
                var item = playlist.seek(itemOffset);
                // trying to seek an invalid offset (thumbnails is showing but currently the item is invalid)
                if (!item) {
                    unloadPlayback();
                    if ($ctrl.onItemOffsetInvalid) {
                        $ctrl.onItemOffsetInvalid({offset: itemOffset});
                    }

                    return;
                }

                item = angular.copy(item);
                if (playoutDuration) {
                    item.duration = playoutDuration;
                }

                loadContentItem(item);
            }

            function nextItem() {
                var next = playlist.next();
                if (!next) {
                    unloadPlayback();
                    processPlaylistItemsInvalid();
                    return;
                }

                next = angular.copy(next);
                loadContentItem(next);
            }

            function previousItem() {
                var previous = playlist.previous();
                if (!previous) {
                    unloadPlayback();
                    processPlaylistItemsInvalid();
                    return;
                }

                previous = angular.copy(previous);
                loadContentItem(previous);
            }

            function loadContentItem(item) {
                if (!item) {
                    return;
                }

                fireLoaded = false;
                if ($ctrl.onItemBeginLoad) {
                    $ctrl.onItemBeginLoad({item: item});
                }

                // if this first item, disable transition in
                if (!$ctrl.currentContent) {
                    item.enterTransition = '';
                }

                // Get player properties
                item.data = {
                    properties: syncService.getCurrentAppProperties()
                };

                // if content has specific duration, set it in playback.contentDuration
                // so that content presenter can use it for cards with natural duration
                if (item.duration) {
                    item.playback = {
                        contentDuration: item.duration
                    };
                }

                var processNextItem = function() {
                    contentHelper.processPlaybackOptions(item);
                    var playItem = function() {
                        executePresenterApiMethod('resetPause');
                        $ctrl.currentContent = item;
                    };

                    var onWpgRoutingResolved = function(url) {
                        item.mediaUrl = url;
                        playItem();
                    };

                    var resolveWpgRouting = function(url) {
                        networkContentRouterService.resolve(url, 'signs-wpg-routing-service').then(onWpgRoutingResolved, function() {
                            // if we receive error while resolving wpg, just resolve it directly
                            onWpgRoutingResolved(url);
                        });
                    };

                    networkContentRouterService.resolve(item.mediaUrl, 'signs-content-routing-service', {
                        type: item.mediaType,
                        cache: true
                    }).then(resolveWpgRouting, playItem);
                };

                processNextItem();
            }

            function loadPlaylist() {
                $ctrl.contentItems = $ctrl.contentItems || [];
                setContentItemsPlaybackProperties();
                playlist.setItems($ctrl.contentItems);
            }

            function setContentItemsPlaybackProperties() {
                for (var i = 0; i < $ctrl.contentItems.length; i++) {
                    var item = $ctrl.contentItems[i];
                    contentHelper.applyContentPlayoutProperties(item, item.playout);
                }
            }

            function play() {
                executePresenterApiMethod('play');
            }

            function pause() {
                executePresenterApiMethod('pause');
            }

            function togglePause() {
                executePresenterApiMethod('togglePause');
            }

            function seek(offsetTime) {
                if (!isCardVideoScrubbingEnabled() && !isVideoContent()) {
                    return;
                 }

                 var currentTime = $ctrl.currentProgress ? $ctrl.currentProgress.position : 0;
                 contentPresenterSeek(currentTime + offsetTime);
            }

            function contentPresenterSeek(duration) {
                presenterApi.seek($ctrl.currentItem, duration);
            }

            function isCardVideoScrubbingEnabled() {
                return $ctrl.currentContent.mediaType === CONTENT_TYPE.CARD && $ctrl.currentItem.runtimeConfiguration.allowVideoScrubbing;
            }

            function isVideoContent() {
                return $ctrl.currentContent.mediaType === CONTENT_TYPE.VIDEO && $ctrl.currentItem && $ctrl.currentProgress;
            }

            function getCurrentItemRuntimeConfiguration() {
                return presenterApi.getCurrentItemRuntimeConfiguration();
            }

            function executePresenterApiMethod(methodName) {
                if (!methodName || !presenterApi || !presenterApi[methodName]) {
                    return;
                }

                presenterApi[methodName]();
            }
        }]
    });
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('signsHTML5');
    app.component('sidebar', {
        templateUrl: 'tv/channelbrowser/channel-browsing/sidebar/sidebar.html',
        bindings: {
            menu: '<',
            selected: '<',
            theme: '<',
            onItemClick: '&',
            onApiReady: '&',
        },
        controller: ["$element", "scrollToHelper", "$timeout", "$scope", function($element, scrollToHelper, $timeout, $scope) {
            var $ctrl = this;
            var menuLocalFlat = [];
            var selectedItem;
            $ctrl.selected = {};
            $ctrl.menuLocal = [];

            $scope.$watch('$ctrl.open', function(value) {
                if (value) {
                    return;
                }

                $ctrl.set(selectedItem);
            });

            $ctrl.$onInit = function() {
                $ctrl.open = false;
                // Construct the api interface
                $ctrl.api = {
                    up: $ctrl.up,
                    down: $ctrl.down,
                    set: $ctrl.set,
                    select: $ctrl.select,
                    close: $ctrl.closeSubmenu,
                    open: $ctrl.openSubmenu,
                    isOpen: $ctrl.isOpen
                };
                if ($ctrl.onApiReady) {
                    $ctrl.onApiReady({api: $ctrl.api});
                }
            };

            $ctrl.isOpen = function() {
                return $ctrl.open;
            };

            $ctrl.closeSubmenu = function() {
                $ctrl.open = false;
            };

            $ctrl.openSubmenu = function() {
                $ctrl.open = true;
            };

            $ctrl.up = function() {
                selectItemByIndex($ctrl.selected.flatIndex - 1);
            };

            $ctrl.down = function() {
                selectItemByIndex($ctrl.selected.flatIndex + 1);
            };

            $ctrl.set = function(value) {
                $ctrl.selected = getItemByValue(value);
                selectedItem = $ctrl.selected;
                setSelectedItem();
            };

            $ctrl.select = function() {
                if (!$ctrl.onItemClick) {
                    return;
                }

                $ctrl.onItemClick({item: $ctrl.selected});
            };

            $ctrl.$onChanges = function(changes) {
                if (changes.menu && $ctrl.menu) {
                    rebuildLocalMenu();
                }

                if (!$ctrl.theme || !$ctrl.theme.sidebarStyle) {
                    return;
                }

                // Override the background color and accent color based on theme
                $ctrl.accentOveride = {};
                if ($ctrl.theme.sidebarStyle.backgroundColor) {
                    $element.css('background-color', $ctrl.theme.sidebarStyle.backgroundColor);
                    $element.find('.icon-container-slide').css('background-color', $ctrl.theme.sidebarStyle.backgroundColor);
                    $element.find('.icon-container').css('background-color', $ctrl.theme.sidebarStyle.backgroundColor);
                }

                if ($ctrl.theme.sidebarStyle.color) {
                    $element.css('color', $ctrl.theme.sidebarStyle.color);
                }

                if ($ctrl.theme.accent) {
                    $ctrl.accentOveride = {
                        backgroundColor: $ctrl.theme.accent
                    };
                }
            };

            $ctrl.onSidebarClick = function() {
                $ctrl.open = !$ctrl.open;
            };

            $ctrl.onIconClick = function($event, item) {
                $ctrl.selected = item;
                selectedItem = item;
                if ($ctrl.onItemClick) {
                    $ctrl.onItemClick({item: item});
                }

                $event.preventDefault();
                $event.stopPropagation();
            };

            function pushToMenuLocalFlat(item) {
                item.flatIndex = menuLocalFlat.length;
                item.id = 'span-' + item.flatIndex;
                menuLocalFlat.push(item);
            }

            function isItemSelected(item) {
                return $ctrl.selected && item.flatIndex === $ctrl.selected.flatIndex;
            }

            function setSelectedItem() {
                for (var i = 0; i < $ctrl.menuLocal.length; i++) {
                    var menuItem = $ctrl.menuLocal[i];
                    menuItem.selected = isItemSelected(menuItem);
                    if (!menuItem.items) {
                        continue;
                    }

                    var isSubSelected = false;

                    // Check sub items
                    for (var j = 0; j < menuItem.items.length; j++) {
                        var subItem = menuItem.items[j];
                        // If the main menu item is selected, all submenu items should not be selected
                        if (menuItem.selected) {
                            subItem.selected = false;
                            continue;
                        }

                        subItem.selected = isItemSelected(subItem);
                        // Set parent menu item to selected
                        if (subItem.selected) {
                            isSubSelected = true;
                        }
                    }

                    menuItem.subSelected = isSubSelected;
                }
                $timeout(scrollToSelectedItem);
            }

            function scrollToSelectedItem() {
                if (!$ctrl.selected) {
                    return;
                }

                scrollToHelper.scrollToElement(
                    angular.element('#' + $ctrl.selected.id),
                    'span',
                    true,
                    angular.element('.sub-menu-label-collection'));
            }

            function rebuildLocalMenu() {
                $ctrl.menuLocal = angular.copy($ctrl.menu);
                menuLocalFlat = [];
                for (var i = 0; i < $ctrl.menuLocal.length; i++) {
                    var menuItem = $ctrl.menuLocal[i];
                    pushToMenuLocalFlat(menuItem);
                    if (!menuItem.items) {
                        continue;
                    }

                    // Check sub items
                    for (var j = 0; j < menuItem.items.length; j++) {
                        var subItem = menuItem.items[j];
                        pushToMenuLocalFlat(subItem);
                    }
                }
            }

            function getItemByValue(item) {
                for (var i = 0; i < menuLocalFlat.length; i++) {
                    var menuItem = menuLocalFlat[i];
                    if (item.value !== menuItem.value) {
                        continue;
                    }

                    return menuItem;
                }
            }

            function selectItemByIndex(targetIndex) {
                if (targetIndex < 0 || targetIndex >= menuLocalFlat.length) {
                    return;
                }

                $ctrl.selected = menuLocalFlat[targetIndex];
                setSelectedItem();
            }
        }]
    });
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('signsHTML5');
    app.component('videoPlaybackBar', {
        templateUrl: 'tv/channelbrowser/channel-viewer/video-playback-bar/video-playback-bar.html',
        bindings: {
            currentProgress: '<',
            showPlayIcon: '<',
            onPlay: '&',
            onPause: '&',
        },
        controller: ["$element", function($element) {
            var $ctrl = this;
            var DEFAULT_CURRENT_TIME = '00:02';
            var DEFAULT_TOTAL_TIME = '00:12';
            var VIDEO_DURATION_DISPLAY_BUFFER = .3;
            $ctrl.currentTime = DEFAULT_CURRENT_TIME;
            $ctrl.totalTime = DEFAULT_TOTAL_TIME;

            $ctrl.$onChanges = function(changes) {
                if (!changes || !changes.currentProgress) {
                    return;
                }

                updateCurrentTime();
            };

            function generateTimeString(time) {
                return moment().startOf('day')
                    .seconds(time)
                    .format('HH:mm:ss');
            }

            function updateCurrentTime() {
                var time = 0;
                var duration = 1;
                if ($ctrl.currentProgress) {
                    time = $ctrl.currentProgress.position;
                    duration = $ctrl.currentProgress.duration;
                }

                $ctrl.currentTime = generateTimeString(time);
                $ctrl.totalTime = generateTimeString(duration);

                duration += VIDEO_DURATION_DISPLAY_BUFFER;
                if (duration == 0) {
                    $element.find('.current-progress').width(0);
                }

                var progressPercent = (time / duration) * 100;
                $element.find('.current-progress').width(progressPercent + '%');
            }
        }]
    });
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('signsHTML5');
    app.controller('SleepController', ["$scope", "logService", "coreFrameworkService", "halPlatformService", function($scope, logService, coreFrameworkService, halPlatformService) {
        $scope.sleepMessage = 'Device is in sleep mode';
        $scope.isFullOverlayShown = true;
        function init() {
            $scope.isFullOverlayShown = halPlatformService.getFeatureSupport('sleep.full.black.overlay') == 'yes';
        }
        coreFrameworkService.isReady().then(init);
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var angular = $angular;
    var app = angular.module('signsHTML5');

    app.controller('InvalidCtrl', ["$scope", "$routeParams", "coreFrameworkService", "deviceStateService", "SYSTEM_DATA", "INVALID_PAGE_ERROR", function($scope, $routeParams, coreFrameworkService, deviceStateService, SYSTEM_DATA, INVALID_PAGE_ERROR) {
        var DEFAULT_ERROR_MESSAGE = 'Invalid ID injected';
        var DEFAULT_DISPLAY_MESSAGE = SYSTEM_DATA.ERROR_MESSAGES.INVALID_ID;
        var ERROR_CONFIG = {};
        ERROR_CONFIG[INVALID_PAGE_ERROR.WEB_PUBLISHING] = {
            errorMessage: 'Web publishing is restricted',
            displayMessage: 'This device must first be registered in Appspace in order to display content.<br>Go to the Appspace console to register this device.'
        };
        ERROR_CONFIG[INVALID_PAGE_ERROR.CDN_FAILURE] = {
            displayMessage: 'Unable to connect to the Content Delivery Network. Please contact your administrator.',
            skipServiceStopping: true,
            displayIcon: 'assets/images/cdn-error.svg'
        };
        ERROR_CONFIG[INVALID_PAGE_ERROR.CDN_UNCONFIGURED] = {
            displayMessage: 'The Content Delivery Network is not configured. Please contact your administrator.',
            skipServiceStopping: true,
            displayIcon: 'assets/images/cdn-error.svg'
        };

        function init() {
            var error = ($routeParams || {}).error;
            var errorConfig = ERROR_CONFIG[error] || {};
            $scope.message = errorConfig.displayMessage || DEFAULT_DISPLAY_MESSAGE;
            $scope.displayIcon = errorConfig.displayIcon;
            if (errorConfig.skipServiceStopping) {
                return;
            }

            deviceStateService.stopAllServices(errorConfig.errorMessage || DEFAULT_ERROR_MESSAGE);
        }

        coreFrameworkService.isReady().then(init);
    }]);
})(window.angular);

(function($angular) {
    'use strict';
    var app = $angular.module('signsHTML5');
    app.controller('StandbyController', ["$scope", "coreFrameworkService", "deviceStateService", "postMessagingService", "halPlatformService", "FEATURE_CODE", "POST_MESSAGES", "CRESTRON_INTENT_MESSAGE", function(
        $scope,
        coreFrameworkService,
        deviceStateService,
        postMessagingService,
        halPlatformService,
        FEATURE_CODE,
        POST_MESSAGES,
        CRESTRON_INTENT_MESSAGE) {
        var DEFAULT_OVERLAY_BACKGROUND_COLOR = '#000000';
        var standbyConfiguration = halPlatformService.getFeatureSupport(FEATURE_CODE.STANDBY_CONFIGURATION) || {};
        $scope.standbyMessage = 'Device is in standby mode';
        $scope.isFullOverlay = Boolean(standbyConfiguration.fullOverlay);
        $scope.overlayBackgroundColor = standbyConfiguration.overlayColor || DEFAULT_OVERLAY_BACKGROUND_COLOR;

        function init() {
            deviceStateService.stopAllServices('device-standby-mode');
            // refactor to make it driver based operations
            halPlatformService.sendPlatformMessage({
                type: CRESTRON_INTENT_MESSAGE.PAUSED_APP
            });
            postMessagingService.raiseMessage({
                message: POST_MESSAGES.NAME,
                type: POST_MESSAGES.STANDBY_LOADED
            });
        }

        coreFrameworkService.isReady().then(init);
    }]);
})(window.angular);

(function(angular) {
    'use strict';
    var app = angular.module('signsHTML5');

    app.filter('asTrustedHtml', ["$sce", function($sce) {
        return function(someHtml) {
            return $sce.trustAsHtml(someHtml);
        };
    }]);
})(window.angular);


angular.module("signsHTML5").run(["$templateCache", function($templateCache) {$templateCache.put("tv/alerts/view.html","<as-alerts-view class=\"alerts-view\" current-alert=\"currentAlert\" on-api-ready=\"onAlertsViewApiReady(api)\" on-alerts-loaded=\"alertsLoaded(alert)\" on-alerts-removed=\"alertsRemoved()\">&nbsp;</as-alerts-view>");
$templateCache.put("tv/applicationdisplay/view.html","<div ng-controller=\"AppDisplayController\">\n	<div ng-controller=\"LayoutController\" class=\"default-signs\">\n		<div style=\"width:100%; height:100%; font-size:16px; position:absolute; overflow:hidden;\">\n			<as-editor-grid ng-if=\"isEditingMode\" on-api-ready=\"onGridEditorApiReady\"></as-editor-grid>\n			<as-layout-editor\n				ng-if=\"isEditingMode\"\n				get-all-displayed-widgets-position=\"getAllDisplayedWidgetsPosition\"\n				on-api-ready=\"onLayoutEditorApiReady\"\n				on-widget-position-changed=\"onWidgetPositionChanged\"\n				on-widget-start-move=\"onWidgetStartMove\"\n				on-widget-stop-move=\"onWidgetStopMove\"\n				on-grid-configured=\"onGridConfigured\"\n				on-widget-selected=\"onWidgetSelected\"\n				on-widget-double-clicked=\"onWidgetDoubleClicked\">\n            </as-layout-editor>\n		</div>\n		<loader ng-if=\"showLoader\" class=\"app-display-loader\"></loader>\n		<as-social-bar id=\"social-bar\" ng-if=\"isWithinOperationHours && currentPost && currentPost.isReactionBarEnabled\" current-post=\"currentPost\"></as-social-bar>\n	</div>\n</div>");
$templateCache.put("tv/applicationinit/view.html","<div ng-controller=\"AppInitController\" class=\"app-init\">\n    <div class=\"container-vcenter\">\n      <div class=\"container-vcenter-inner\">\n        <div class=\"main-container\">\n          <div ng-bind-html=\"message\" class=\"app-init-message\"></div>\n          <div class=\"progress\" id=\"progressContainer\" style=\"display: none;width:80%; margin-left: 10%;\">\n            <div class=\"progress-bar\" role=\"progressbar\" aria-valuenow=\"{{progress}}\" aria-valuemin=\"0\" aria-valuemax=\"100\" ng-style=\"progressStyle\"></div>\n          </div>\n          <span ng-if=\"progressText\">\n            <div class =\"reg-sync-progress\">{{progressText}}</div>\n            <button type=\"button\" id=\"syncInBackground\" class=\"btn as-btn sync-in-background-button\" autofocus ng-click=\"syncInBackground()\">SYNC IN BACKGROUND</button>\n          </span>\n        </div>\n      </div>\n  </div>\n  <as-device-menu-icon class=\"device-menu-icon-holder device-menu-icon-cog-holder\"></as-device-menu-icon>\n</div>");
$templateCache.put("tv/broadcasts/view.html","<as-broadcast-alert notify-current-broadcast-complete=\"notifyCurrentBroadcastComplete\" current-broadcast=\"currentBroadcast\" on-current-broadcast-complete=\"onCurrentBroadcastComplete()\"></as-broadcast-alert>");
$templateCache.put("tv/channelbrowser/channelbrowser.html","<div ng-controller=\"ChannelBrowserCtrl\" class=\"play-view noselect play-browser-main\">\n    <div ng-if=\"customUI\" class=\"custom-ui-container\">\n        <background-channel></background-channel>\n    </div>\n    <div ng-show=\"!customUI\" class=\"channel-browser-container\">\n        <div id=\"channelBrowser\" class=\"channel-browser\" ng-style=\"theme.homeBackgroundColor\"\n            ng-class=\"{\'single-group-layout\' : singleLayout, \'single-channel-group\' : singleChannelGroup}\">\n            <sidebar ng-hide=\"!theme.enableSidebar\" menu=\"sidebarMenu\" theme=\"theme\" on-api-ready=\"onSideMenuApi(api)\"\n                on-item-click=\"onSidebarSectionClick(item)\">\n            </sidebar>\n            <div class=\"main-container\" ng-click=\"onMainContainerClick()\"\n                ng-class=\"{\'background-channel-scroll\' : theme.backgroundScroll}\">\n                <background-channel ng-if=\"isHome\" ng-hide=\"showSearch\"\n                    ng-class=\"{\'background-channel-blur-off\' :  !theme.backgroundScrollBlur, \'background-channel-blur-on\' : blurBackground, \'invisible\': showLoader}\"\n                    on-ready=\"onBackgroundReady()\">\n                </background-channel>\n                <banner ng-hide=\"showSearch || selectedItemValue.value !=\'home\'\" ng-class=\"{\'invisible\': showLoader}\"\n                    theme=\"theme\" ng-click=\"gotoHomepage()\"></banner>\n                <loader ng-if=\"showLoader\"></loader>\n                <channel-search ng-class=\"{\'invisible\': showLoader}\" show=\"{{showSearch}}\"\n                    on-search=\"onSearch(searchText)\" on-select-channel=\"onSearchSelectChannel(channel)\"\n                    on-cancel=\"onSearchCancel()\" on-edge-click=\"onSearchEdgeClick()\" keyboard-focus=\"{{searchKeyboardFocus}}\">\n                </channel-search>\n                <div class=\"no-channels-message\" ng-if=\"noChannels\">{{noChannelsMessage}}</div>\n                <div ng-hide=\"showSearch\" class=\"navigation-container\" ng-class=\"{\'invisible\': showLoader, \'elevate-navigation-container\': theme.numberOfChannelRows >= 0 && (theme.backgroundScroll || theme.backgroundScrollBlur)}\">\n                    <div class=\"content-tile-container\" ng-repeat=\"channelGroup in channelGroups\" id=\"group_{{channelGroup.id}}\"\n                        ng-class=\"{\'tile-default-color-disabled\': !theme.enableTileDefaultColor, \'tile-shadow-disabled\': !theme.enableTileShadow}\">\n                        <span ng-show=\"theme.showChannelGroupTitle\" ng-style=\"theme.channelGroupTitleStyle\"\n                            class=\"channel-group-title\" ng-bind-html=\"channelGroup.name\"></span>\n                        <content-grid id=\"cg-{{channelGroup.id}}\" ng-style=\"theme.channelTitleStyle\"\n                            on-api-ready=\"onContentGridApi(api, $index, channelGroup.id)\">\n                            <div class=\"filter-no-channels-message\" ng-if=\"channelGroup.channels.length == 0\">\n                                Your search did not match any channels.\n                                <br><br>\n                                Suggestions:\n                                <br><br>\n                                <ul>\n                                    <li>Make sure that all words are spelled correctly.</li>\n                                    <li>Try different keywords.</li>\n                                    <li>Try more general keywords.</li>\n                                </ul>\n                            </div>\n                        </content-grid>\n                    </div>\n                </div>\n            </div>\n        </div>\n    </div>\n</div>");
$templateCache.put("tv/channelbrowser/channelsearch.html","<div id=\"channelSearchOverlay\" class=\"search-overlay play-view\" ng-attr-style=\"display:{{displaySearch}};\">\n  <div class=\"search-banner\">\n    <div class=\"search-text\">{{searchText}}</div>\n  </div>\n  <div class=\"content\">\n    <div class=\"search-item-size\"></div>\n    <div class=\"keyboard\">\n      <div as-keyboard show=\"true\" on-api-ready=\"onKeyboardApi(api)\" on-value-change=\"onKeyboardValueChange(previousValue, currentValue)\"\n        on-action-selected=\"onKeyboardActionSelected(key)\" on-keyboard-edge=\"onKeyboardEdge(edge)\" on-focus-change=\"onKeyboardFocusChange(previousKey, currentKey)\"></div>\n    </div>\n    <div class=\"results\">\n      <div class=\"search-list\">\n        <as-ui-grid style=\"height:inherit;\" items=\"gridItems\" config=\"gridConfig\" on-api-ready=\"onGridApi(api)\" on-item-focus=\"onGridItemFocus(api, offset, fromClick)\">\n        </as-ui-grid>\n      </div>\n    </div>\n  </div>\n</div>");
$templateCache.put("tv/channelbrowser/epg.html","<div class=\"epg play-view\" ng-controller=\"EPGCtrl\" ng-style=\"theme.homeBackgroundColor\">\n    <div class=\"sidebar\" as-click=\"epgBackClicked()\" ng-class=\"{ \'sidebar-bottom-align\' : theme.backPlacement == \'bottomleft\' || theme.backPlacement == \'bottomright\'}\">\n        <div class=\"epg-back-text sidebar-container\">\n            <i class=\"icon icon-back\"></i>\n        </div>\n    </div>\n    <div class=\"epg-page\">\n        <div id=\"loadError\" class=\"load-error\" style=\"display: none\">\n            <div>Error loading the program guide. Go back to reload.</div>\n        </div>\n        <div class=\"container\" id=\"epgContainer\">\n            <div class=\"selected-program\" id=\"selectedProgram\">\n                <div class=\"left\">\n                    <div class=\"left-content-holder\">\n                        <as-ui-content-presenter on-api-ready=\"onPlayerApi(api)\" content-item=\"currentContent\"\n                            default-stretch=\"uniform\" on-item-load=\"onItemLoad()\" on-item-load-error=\"onItemLoadError()\"\n                            on-item-complete=\"onItemComplete()\" style=\"width:100%;height:100%;position:absolute;top:0\"\n                            custom-container-style=\"{height:\'inherit\'}\"></as-ui-content-presenter>\n                        <loader ng-if=\"showContentLoader\"></loader>\n                        <div id=\"loadErrorContent\" class=\"load-error\" style=\"display: none\">\n                            <div>Error loading the content</div>\n                        </div>\n                        <div class=\"hotarea-info-overlay\" ng-click=\"onEpgPreviewClick(true)\">&nbsp;</div>\n                    </div>\n                </div>\n                <div class=\"right\" ng-hide=\"showLoader\">\n                    <div class=\"title\">{{selectedProgram.title}}</div>\n                    <div class=\"time\">{{currentChannel.title}} &nbsp; | &nbsp;\n                        {{selectedProgram.start.format(\'h.mma\') + \' - \' + selectedProgram.stop.format(\'h.mma\')}}</div>\n                    <div class=\"desc\">\n                        {{selectedProgram.desc}}\n                    </div>\n                    <div ng-click=\"onEpgPreviewClick()\" class=\"instruction\" ng-show=\"programInstruction != \'\'\">\n                        {{programInstruction}}</div>\n                </div>\n            </div>\n            <div class=\"timetable\" id=\"tableContainer\">\n                <div class=\"cell-size\"></div>\n                <div class=\"header\">\n                    <div class=\"left\">\n                        <div class=\"inner\">\n                            <div class=\"header-cell\" style=\"width: 100%;\">{{currentDay}}</div>\n                        </div>\n                    </div>\n                    <div class=\"right\" id=\"timeslotsContainer\">\n                        <div id=\"epgHeader\" class=\"inner\"></div>\n                    </div>\n                </div>\n                <div class=\"content\">\n                    <div class=\"left\" id=\"channelListContainer\">\n                        <div class=\"epg-table\" id=\"epgChannelList\"></div>\n                    </div>\n                    <div class=\"right\" id=\"programContainer\">\n                        <div style=\"width:100%; height:inherit; overflow:hidden;\">\n                            <div id=\"epgPrograms\" class=\"epg-table\"></div>\n                            <div>\n                            </div>\n                        </div>\n                    </div>\n                </div>\n            </div>\n            <loader ng-if=\"showLoader\"></loader>\n        </div>");
$templateCache.put("tv/channelbrowser/livechannel.html","<channel-viewer ng-controller=\"LiveChannelCtrl\" class=\"live-channel channel-viewer-back-placement-{{theme.backPlacement}}\">\n    <loader ng-if=\"showLoader\"></loader>\n    <back-button ng-if=\"theme.backPlacement == \'topleft\' || theme.backPlacement == \'topright\'\" show=\"theme.persistBackButton || paneOpen\" on-back=\"onBackButtonClicked()\"></back-button>\n    <div id=\"loadError\" class=\"load-error\">\n        <div class=\"error-msg\">Error loading the content, retrying in 30 seconds</div>\n    </div>\n    <div class=\"content\">\n        <as-ui-content-presenter on-api-ready=\"onPlayerApi(api)\" default-duration=20 content-item=\"currentContent\"\n            default-stretch=\"uniform\" on-item-load=\"onItemLoad()\" on-item-load-error=\"onItemLoadError()\"\n            on-item-complete=\"onItemComplete()\" style=\"width:100%;height:inherit\"></as-ui-content-presenter>\n    </div>\n    <div as-click=\"hotSpotClick($event)\" class=\"hotarea-info-overlay\"></div>\n    <div class=\"content-bottom-pane epg-overlay channel-content-strip\"\n        ng-class=\"{\'content-bottom-pane-open\': paneOpen}\">\n        <back-button ng-if=\"theme.backPlacement == \'bottomleft\' || theme.backPlacement == \'bottomright\'\" show=\"theme.persistBackButton || paneOpen\" on-back=\"onBackButtonClicked()\"></back-button>\n        <channel-content-strip current-item=\"currentChannel\"></channel-content-strip>\n        <div class=\"program\">\n            <div class=\"header\">\n                <span class=\"program-title\">{{epgNow.title}}</span>\n                <span class=\"clock\">\n                    <as-ui-date-time format=\"h:mma\"></as-ui-date-time>\n                </span>\n            </div>\n            <div class=\"program-schedule\">\n                <div class=\"now\">\n                    <span class=\"time\">{{epgNow.start}} | {{epgNow.elapsed}}</span>\n                    <span class=\"desc\">{{epgNow.desc}}</span>\n                </div>\n                <div class=\"next\">\n                    <span class=\"time\">{{epgNext.start}}</span>\n                    <span class=\"desc\">{{epgNext.title}}</span>\n                </div>\n                <div class=\"later\">\n                    <span class=\"time\">{{epgLater.start}}</span>\n                    <span class=\"desc\">{{epgLater.title}}</span>\n                </div>\n            </div>\n        </div>\n    </div>\n</channel-viewer>");
$templateCache.put("tv/devicemenu/asSignsDeviceMenuTemplate.html","<div id=\"osd\" class=\"osd theme-dark\" ng-attr-style=\"display:{{displayOsd}}\">\n	<!-- passcode Overlay -->\n	<div class=\"device-menu-passcode\" ng-attr-style=\"display:{{showpasscode}};\">\n		<div class=\"container\">\n			<div class=\"subcontainer\">\n				<div class=\"header\">\n					<div style=\"float:left;\">Enter Passcode</div>\n					<div style=\"float:right;color:#bebfc1;\"></div>\n					<div style=\"overflow: auto; width:100%;\"></div>\n				</div>\n				<div class=\"content\">\n					<input class=\"textinput\" ng-repeat=\"n in passcodeArr track by $index\" tabindex={{$index+1}} type=\"password\" ng-change=\"txtInputChange($index+1, passcodeArr[$index])\"\n						maxlength=\"1\" ng-model=\"passcodeArr[$index]\" />\n				</div>\n				<div class=\"error-txt\" ng-attr-style=\"display:{{showError}}\">Invalid Passcode</div>\n				<a class=\"btn as-btn\" style=\"width:100%;\" ; ng-click=\"submitpasscode()\" tabindex=\"5\">OK</a>\n			</div>\n		</div>\n		<div class=\"keyboard\">\n			<div as-keyboard show=\"{{showKeyboard}}\" on-api-ready=\"onKeyboardApi(api)\" on-value-change=\"onKeyboardValueChange(previousValue, currentValue)\"\n				on-action-selected=\"onKeyboardActionSelected(key)\" on-keyboard-edge=\"onKeyboardEdge(edge)\" on-focus-change=\"onKeyboardFocusChange(previousKey, currentKey)\"></div>\n		</div>\n	</div>\n\n	<!-- Menu -->\n    <div ng-attr-style=\"display:{{displayMenu}}\" class=\"device-menu-div theme-dark\">\n        <div class=\"sidebar\">\n            <div class=\"sidebar-container\">\n                <div class=\"device-menu-container\">\n					<div class=\"operation-error-container\" ng-if=\"isSingleAppSupported && isSingleAppAllowed && showOperationError\">\n						<div class=\"operation-error-label\">\n							Operation could not be completed. Please contact your MDM administrator\n						</div>\n					</div>\n                    <h1>{{title}}</h1>\n                    <div class=\"list-group\">\n                        <a ng-repeat=\"item in items\" ng-click=\"$parent.onDeviceMenuItemSelected(item, true)\" class=\"list-group-item\" ng-class=\"{active:isSelected(item)}\">{{item.text}}</a>\n                    </div>\n                </div>\n            </div>\n        </div>\n		<footer class=\"main-footer\">\n			<div class=\"device-menu-footer\">Appspace App: {{softwareVer}} | IP: {{ipAddress}} | Device: {{deviceModel}}</div>\n		</footer>\n		<as-device-menu-icon class=\"device-menu-icon-holder device-menu-icon-close-holder\"></as-device-menu-icon>\n	</div>\n\n	<!-- Log Overlay -->\n	<div class=\"log-view\" ng-attr-style=\"display:{{showLog ? \'\':\'none\'}}\">\n		<div class=\"header\">\n			<button type=\"button\" class=\"close\" ng-click=\"closeLog()\">&times;</button>\n			<button type=\"button\" class=\"close\" style=\"right: 30px;\" ng-click=\"minimizeLog()\">&minus;</button>\n		</div>\n		<as-ui-log-overlay enable-updates=\"{{enableLogUpdates}}\" />\n	</div>\n\n	<!-- Log minimized -->\n	<div class=\"log-view-minimize\" ng-attr-style=\"display:{{minimized}};\" ng-click=\"maximizeLog()\">\n		...\n	</div>\n\n	<!-- Device Info Overlay -->\n	<div class=\"device-info-view\" ng-attr-style=\"display:{{showDeviceInfo ? \'\': \'none\'}};\">\n		<h1>Device Info</h1>\n		<div class=\"header\">\n			<button type=\"button\" class=\"close\" ng-click=\"closeDeviceInfo()\">&times;</button>\n		</div>\n		<div class=\"info\">\n			<div class=\"general group\">\n				<h2>General</h2>\n				<div class=\"group-info\">\n					<ul>\n						<li>Device Name : {{deviceName}}</li>\n						<li>Device GUID : {{deviceGuid}}</li>\n						<li>Fingerprint : {{deviceFingerprint}}</li>\n						<li>Device IP : {{deviceIpAddress}}</li>\n						<li>Model : {{deviceModel}}</li>\n						<li>Device Firmware Version : {{deviceFirmware}}</li>\n						<li>Display : {{displayInfo}}</li>\n						<li>Server URL : {{serverUrl}}</li>\n						<li>Version : {{appVersion}}</li>\n					</ul>\n				</div>\n			</div>\n			<div class=\"status group\">\n				<h2>Status</h2>\n				<div class=\"group-info\">\n					<ul>\n						<li>CPU Usage : {{cpuUsage}}</li>\n						<li>Memory Usage : {{memoryUsage}}</li>\n						<li>Storage Usage : {{storageUsage}}</li>\n						<li>Current Channel : {{currentChannel}}</li>\n						<li>Server Connection : {{serverConnection}}</li>\n						<li>Active CDN/Smarthub : {{activeCdn}}</li>\n						<li>Application Timestamp : {{deviceTimeStamp}}</li>\n						<li>Device Local Time : {{deviceLocalTime}}</li>\n						<li>Device Up Time : {{deviceUpTime}}</li>\n						<li>Sync Status : {{syncStatus}}</li>\n						<li ng-if=\"isSingleAppSupported && isSingleAppAllowed\">Single App Mode : {{singleAppModeStatus}}</li>\n					</ul>\n				</div>\n			</div>\n		</div>\n	</div>\n</div>");
$templateCache.put("tv/devicemenu/deviceMenuInvoker.html","<div class=\"hot-area\" ng-style=\"$ctrl.hotAreaStyle\" ng-if=\"$ctrl.isHotAreaEnabled && !$ctrl.isMenuOpen\"></div>\n");
$templateCache.put("tv/devicemenu/deviceoperation.html","<div ng-controller=\"DeviceOperationCtrl\" class=\"device-menu\">\n    <div class=\"container-vcenter\">\n      <div class=\"container-vcenter-inner\">\n        <loader ng-if=\"showLoader\"></loader>\n        <span ng-if=\"!!operationMsg\">{{operationMsg}}</span>        \n        <div id=\"resMsg\" style=\"display:none;\">{{errorMsg}}<div>Press \'Enter\' to go back to the device menu.</div></div>\n      </div>\n  </div>\n</div>");
$templateCache.put("tv/invalid/view.html","<div ng-controller=\"InvalidCtrl\" class=\"unload-landing-page\">\n  <div class=\"container-vcenter\">\n      <div class=\"container-vcenter-inner\">\n        <div class=\"icon\" ng-if=\"displayIcon\">\n            <img ng-src=\"{{displayIcon}}\">\n        </div>\n        <div ng-bind-html=\"message\" class=\"unload-msg\"></div>\n      </div>\n  </div>");
$templateCache.put("tv/progress-donut/progress-donut.html","<div class=\"registration-progress-container\">\n    <div class=\"progress-radial-container\">\n        <svg class=\"registration-progress\" viewBox=\"0 0 120 120\" id=\"progress-svg\">\n            <circle class=\"progress-meter\" cx=\"60\" cy=\"60\" r=\"54\" stroke-width=\"5\" />\n            <circle class=\"progress-value\" cx=\"60\" cy=\"60\" r=\"54\" stroke-width=\"5\" />\n        </svg>\n    </div>\n    <i class=\"icon icon-check\" ng-class=\"{\'registration-complete\' : complete}\"></i>\n</div>");
$templateCache.put("tv/registration/account.html","<div class=\"base-panel\" ng-controller=\"RegAccountCtrl\" style=\"overflow:hidden\">\n  <div class=\"left-panel\" ng-click=\"onLeftPanelClicked($event)\">\n    <div class=\"main-container\"  ng-mousemove=\"onMouseMove()\">\n        <div class=\"reg-content\">\n             <!--<div <div class=\"reg-logo\"><img src=\"assets/images/connect-logo.svg\" /></div>-->\n\n            <h2 class=\"reg-title text-center\">To register your device, sign into your Appspace account</h2>\n            <div class=\"divform\">\n                <div class=\"reg-input-group {{urlFocus ? \'reg-input-group-focus\': \'\'}}\">\n                    <div class=\"reg-input-icon\"><i class=\"fa fa-cloud\"></i></div>\n                    <input ng-focus=\"onInputFocus($event)\" type=\"text\" class=\"reg-textbox\" id=\"appspaceUrl\" tabindex=\"1\" placeholder=\"https://cloud.appspace.com\" ng-model=\"appspaceAcc.appspaceUrl\">\n                </div>\n                <div class=\"reg-input-group {{usernameFocus ? \'reg-input-group-focus\': \'\'}}\">\n                    <div class=\"reg-input-icon\"><i class=\"fa fa-user\"></i></div>\n                    <input ng-focus=\"onInputFocus($event)\" type=\"text\" class=\"reg-textbox\" id=\"username\" tabindex=\"2\" placeholder=\"Username\" ng-model=\"appspaceAcc.username\">\n                </div>\n                <div class=\"reg-input-group {{passwordFocus ? \'reg-input-group-focus\': \'\'}}\">\n                    <div class=\"reg-input-icon\"><i class=\"fa fa-lock\"></i></div>\n                    <input ng-focus=\"onInputFocus($event)\" type=\"password\" class=\"reg-textbox\" id=\"password\" tabindex=\"3\" placeholder=\"Password\" ng-model=\"appspaceAcc.password\">\n                </div>\n                <div id=\"failMessage\" class=\"error\" style=\"text-align: left;\"></div>\n                <div class=\"btn-container\">\n                    <button type=\"button\" id=\"btnBack\" class=\"btn as-btn\" tabindex=\"5\" style=\"float: left;\" ng-click=\"back($event)\" ng-focus=\"onInputFocus($event)\">Cancel</button>\n                    <button type=\"button\" id=\"submitBtn\" class=\"btn as-btn\" style=\"float: right;\" tabindex=\"4\" ng-click=\"submit($event)\" ng-focus=\"onInputFocus($event)\" ng-disabled=\"isSubmitDisabled()\">Next</button>\n                </div>\n            </div>\n\n        </div>\n    </div>\n    <as-device-menu-icon class=\"device-menu-icon-holder device-menu-icon-cog-holder\"></as-device-menu-icon>\n  </div>\n  <div class=\"right-panel\">\n      <div as-keyboard show=\"true\" avoid-prevent-default=\"false\" on-api-ready=\"onKeyboardApi(api)\" on-value-change=\"onKeyboardValueChange(previousValue, currentValue)\" on-action-selected=\"onKeyboardActionSelected(key)\"></div>\n  </div>\n</div>");
$templateCache.put("tv/registration/channel.html","<div ng-controller=\"RegChannelCtrl\" class=\"channel-listing-page\">\n    <div class=\"left-panel\">\n        <div class=\"main-container\">\n            <div class=\"reg-content\">\n                <h2 ng-if=\"!isPassive\" class=\"text-center\">Nice work. Your device is now\n                    registered. If you already have a channel in mind, pick from below or select channel browsing\n                    mode. Otherwise, skip to select the default channel.</h2>\n                <div class=\"reg-channel-container\">\n                    <div ng-attr-style=\"display:{{displaySpinner}}\" class=\"reg-inline-msg\" ng-bind-html=\"message\"></div>\n                    <div class=\"reg-channel-list\">\n                        <as-ui-grid items=\"gridItems\" config=\"gridConfig\" on-api-ready=\"onGridApi(api)\"\n                            on-item-focus=\"onGridItem(api, offset, fromClick)\">\n                        </as-ui-grid>\n                    </div>\n                </div>\n                <form ng-if=\"!isPassive\" name=\"form1\">\n                    <div class=\"btn-container\">\n                        <div style=\"margin-bottom:10px;\"><button type=\"button\" id=\"interactiveBtn\" class=\"btn as-btn\"\n                                tabindex=\"1\" ng-click=\"assignInteractiveMode()\" ng-disabled=\"interactiveModeDisable\"\n                                ng-hide=\"btnHide\">Channel Browsing</button> </div>\n                        <div><button type=\"button\" id=\"skipBtn\" class=\"btn as-btn\" tabindex=\"2\" ng-click=\"skip()\"\n                                ng-disabled=\"skipDisabled\" ng-hide=\"btnHide\">Skip</button></div>\n                    </div>\n                </form>\n            </div>\n        </div>\n        <as-device-menu-icon class=\"device-menu-icon-holder device-menu-icon-cog-holder\"></as-device-menu-icon>\n    </div>\n</div>");
$templateCache.put("tv/registration/devicename.html","<div class=\"base-panel\" ng-controller=\"RegDeviceNameCtrl\">\n  <div class=\"left-panel\" ng-click=\"onLeftPanelClicked($event)\">\n    <div class=\"main-container\">\n      <div class=\"reg-content\">\n         <!--<div <div class=\"reg-logo\"><img src=\"assets/images/connect-logo.svg\" /></div>-->\n        <h2 class=\"text-center name-device\" >Next, change the name of your device.</h2>\n        <div class=\"divform\">\n            <div class=\"reg-input-group {{deviceNameFocus ? \'reg-input-group-focus\': \'\'}}\">\n                <div class=\"reg-input-icon\" ><i class=\"fa fa-pencil\"></i></div>\n                <input ng-focus=\"onInputFocus($event)\" id=\"deviceName\" type=\"text\" class=\"reg-textbox\" placeholder=\"{{deviceNamePlaceholder}}\" ng-model=\"deviceName\" tabindex=\"1\">\n            </div>\n            <div id=\"failMessage\" ng-if=\"state==\'fail\'\" class=\"error\" role=\"alert\"><i class=\"fa fa-exclamation-triangle\"></i> Error</div>\n            <div class=\"btn-container\">\n                <button type=\"button\" ng-focus=\"onInputFocus($event)\" id=\"btnBack\" class=\"btn as-btn\" style=\"float: left;\" tabindex=\"3\" ng-click=\"cancel()\">Back</button>\n                <button type=\"button\" ng-focus=\"onInputFocus($event)\" id=\"submitBtn\" ng-click=\"submit()\" class=\"btn as-btn\" style=\"float: right;\" tabindex=\"2\" ng-disabled=\"submitDisabled\">Register</button>\n            </div>\n        </div>\n      </div>\n    </div>\n    <as-device-menu-icon class=\"device-menu-icon-holder device-menu-icon-cog-holder\"></as-device-menu-icon>\n  </div>\n  <div class=\"right-panel\">\n      <div as-keyboard show=\"true\" avoid-prevent-default=\"false\" on-api-ready=\"onKeyboardApi(api)\" on-value-change=\"onKeyboardValueChange(previousValue, currentValue)\" on-action-selected=\"onKeyboardActionSelected(key)\"></div>\n  </div>\n</div>");
$templateCache.put("tv/registration/fail.html","<div ng-controller=\"RegFailCtrl\" class=\"registration-fail-page\">\n	<div class=\"container-vcenter\">\n		<div class=\"container-vcenter-inner\">\n			<div class=\"main-container\">\n				<h2>{{errorTitle}}</h2>\n				<div class=\"text-small\" style=\"margin-top: 8px;\">{{errorMessage}}</div>\n				<div ng-if=\"isButtonVisible\" style=\"margin-top: 40px; width:50%; left:25%; position:relative;\">\n					<button type=\"button\" autofocus class=\"btn as-btn\" tabindex=\"1\" ng-click=\"retryRegistration()\" ng-disabled=\"disableretry\">RETRY</button>\n					<button type=\"button\" class=\"btn as-btn\" tabindex=\"2\" ng-click=\"cancelRegistration()\" style=\"float: right; \">CANCEL</button>\n				</div>\n                <loader ng-if=\"showLoader\" class=\"app-display-loader\"></loader>\n			</div>\n		</div>\n	</div>\n	<as-device-menu-icon class=\"device-menu-icon-holder device-menu-icon-cog-holder\"></as-device-menu-icon>\n</div>");
$templateCache.put("tv/registration/index.html","<div ng-controller=\"RegCtrl\" class=\"registration-progress-page\">\n    <span ng-if=\"message\" class=\"registration-provision-messsage\">{{message}}</span>\n</div>");
$templateCache.put("tv/registration/instance.html","<div ng-controller=\"RegInstanceCtrl\" class=\"registration-instance-page\">\n	<div class=\"left-panel\">\n		<div class=\"main-container\">\n			<div class=\"reg-content\">\n				<h2 class=\"text-center\">Looks like you have access to more than one account, go ahead and select which one you want to register your device to.</h2>\n				<div class=\"reg-channel-container\">\n					<div ng-attr-style=\"display:{{displaySpinner}}\" class=\"reg-inline-msg\" ng-bind-html=\"message\"></div>\n					<div class=\"reg-channel-list\">\n						<as-ui-grid items=\"gridItems\" config=\"gridConfig\" on-api-ready=\"onGridApi(api)\" on-item-focus=\"onGridItem(api, offset, fromClick)\">\n						</as-ui-grid>\n					</div>\n				</div>\n				<form name=\"form1\">\n					<div class=\"btn-container\">\n						<button type=\"button\" autofocus id=\"backBtn\" class=\"btn as-btn\" tabindex=\"1\" ng-click=\"cancel()\" ng-disabled=\"skipDisabled\">BACK</button>\n					</div>\n				</form>\n			</div>\n		</div>\n		<as-device-menu-icon class=\"device-menu-icon-holder device-menu-icon-cog-holder\"></as-device-menu-icon>\n	</div>\n</div>");
$templateCache.put("tv/registration/language.html","<div ng-controller=\"RegLanguageCtrl\">\n    <div class=\"left-panel\">\n        <div class=\"registration-landing-page\">\n            <div class=\"main-container\" ng-mousemove=\"onMouseMove()\">\n                <div class=\"reg-banner\">\n                    <span>Register your device</span>\n                    <img src=\"assets/images/appspace-logo.svg\" />\n                </div>\n                <qrcode class=\"reg-code-qr\" data=\"{{qrCodeClaimUrl}}\" ng-if=\"!isPolicyBased && !!qrCodeClaimUrl && !codeHidden\" version=\"4\"></qrcode>\n                <div class=\"reg-container\">\n                    <div class=\"reg-container-primary\">\n                        <div class=\"code-container\">\n                            <img class=\"reg-code-display\" src=\"assets/images/tv-laptop.svg\" />\n                            <span class=\"reg-code\" ng-show=\"!codeHidden\">{{!isPolicyBased ? autoCode : countdown}}</span>\n                            <span class=\"reg-code-message\" ng-if=\"(!autoCode && !isPolicyBased) || codeHidden\" ng-bind-html=\"getInfoMessage()\"></span>\n                        </div>\n                        <span ng-if=\"statusMessage\" class=\"reg-container-primary-message\">{{statusMessage}}</span>\n                    </div>\n                    <div class=\"reg-container-secondary\" id=\"reg-container-secondary\">\n                        <span class=\"register-headline\" ng-class=\"{\'policy-registration\': isPolicyBased}\">\n                            <span ng-bind-html=\"headline\"></span>\n                        </span>\n                        <span ng-if=\"isInteractiveRegistration()\" class=\"register-summary\">\n                            Alternatively, login to your Appspace account below and follow the onscreen instructions.\n                        </span>\n                        <span ng-if=\"isPolicyBased\" class=\"register-summary\">\n                            {{getInfoMessage()}}\n                        </span>\n                        <button ng-if=\"isInteractiveRegistration()\" type=\"button\" id=\"skipBtn\" class=\"btn as-btn\" tabindex=\"0\" ng-click=\"submit()\">Log in</button>\n                        <button ng-if=\"isPolicyBased\" type=\"button\" id=\"cancelBtn\" class=\"btn as-btn\" tabindex=\"0\" ng-click=\"cancel()\">Cancel</button>\n                    </div>\n                </div>\n                <div class=\"v-inner\">\n                    <div class=\"reg-header-main\">\n                        <div class=\"reg-logo\"><img src=\"assets/images/appspace-logo.svg\" /></div>\n                        <h2 class=\"text-center\">Let\'s get started. {{ registrationOptionMessage }}</h2>\n                    </div>\n                    <div class=\"reg-options-main\">\n                        <div ng-if=\"!isPassive\" class=\"reg-options-left col-lg-5 col-md-5 col-sm-5 col-xs-12\">\n                             <div class=\"left-inner\">\n                                <h6 class=\"text-middle\">\n                                    Register this device locally using a remote control or keyboard and mouse.\n                                    <div class=\"btn-container\">\n                                        <button type=\"button\" id=\"skipBtn\" class=\"btn as-btn\" tabindex=\"0\" ng-click=\"submit()\">REGISTER LOCALLY</button>\n                                    </div>\n                                </h6>\n                             </div>\n                        </div>\n                        <div ng-if=\"!isPassive\" class=\"reg-options-middle col-lg-2 col-md-2 col-sm-2 col-xs-12\">\n                            <div class=\"or\"><h6>OR</h6></div>\n                        </div>\n                        <div class=\"reg-options-right col-xs-12 col-lg-5 col-md-5 col-sm-5\" ng-class=\"{ \'passive-registration\': isPassive }\">\n                            <div class=\"right-inner\">\n                                <h6 class=\"text-middle\">\n                                    <div ng-bind-html=\"getInfoMessage();\"></div>\n                                    <div class=\"code\" ng-show=\"showCode\">{{autoCode}}</div>\n                                </h6>\n                            </div>\n                        </div>\n                    </div>\n                    <div ng-if=\"!isPassive\" class=\"registration-switchmode-link\" ng-show=\"showSwitchMode\"><a ng-click=\'switchMode()\'>Switch to user mode</a></div>\n                </div>\n                <as-device-menu-icon ng-if=\"!isPassive\" class=\"device-menu-icon-holder device-menu-icon-cog-holder\"></as-device-menu-icon>\n            </div>\n        </div>\n    </div>");
$templateCache.put("tv/registration/progress.html","<div ng-controller=\"RegProgressCtrl\" class=\"unregistration-progress-page\">\n    <div class=\"main-container\">\n        <div class=\"reg-banner\">\n            <span>Register your device</span>\n            <img src=\"assets/images/appspace-logo.svg\" />\n        </div>\n        <div class=\"reg-container\">\n            <div class=\"reg-container-primary\">\n                <progress-donut progression=\"registrationProgress\"></progress-donut>\n            </div>\n            <div class=\"reg-container-secondary\">\n                <span class=\"register-headline\">\n                    <span>Hold tight, we are registering this device to your account.</span>\n                </span>\n            </div>\n        </div>\n        <as-device-menu-icon class=\"device-menu-icon-holder device-menu-icon-cog-holder\"></as-device-menu-icon>\n    </div>\n</div>");
$templateCache.put("tv/registration/silentreg.html","<div class=\"registration-progress-page registration-landing-page\">\n    <div ng-controller=\"SilentRegCtrl\" class=\"main-container\">\n        <div class=\"reg-banner\">\n            <span>Register your device</span>\n            <img src=\"assets/images/appspace-logo.svg\" />\n        </div>\n        <div class=\"reg-container\">\n            <div class=\"reg-container-primary\">\n                <progress-donut progression=\"registrationProgress\"></progress-donut>\n            </div>\n            <div class=\"reg-container-secondary\">\n                <span class=\"register-headline\">\n                    <span ng-bind-html=\"getRegMessage()\"></span>\n                </span>\n            </div>\n        </div>\n        <as-device-menu-icon class=\"device-menu-icon-holder device-menu-icon-cog-holder\"></as-device-menu-icon>\n    </div>\n</div>");
$templateCache.put("tv/registration/success.html","<div ng-controller=\"RegSuccessCtrl\" class=\"registration-success-page\">\n	<div class=\"container-vcenter\">\n		<div class=\"container-vcenter-inner\">\n			<div class=\"main-container\">\n				<h2>Your device was successfully registered.</h2>\n			</div>\n		</div>\n	</div>\n	<as-device-menu-icon class=\"device-menu-icon-holder device-menu-icon-cog-holder\"></as-device-menu-icon>\n</div>");
$templateCache.put("tv/registration/successrestore.html","<div ng-controller=\"RegSuccessCtrl\" class=\"registration-success-page\">\n	<div class=\"container-vcenter\">\n		<div class=\"container-vcenter-inner\">\n			<div class=\"main-container\">\n				<h2>Your registration information was successfully restored.</h2>\n			</div>\n		</div>\n	</div>\n	<as-device-menu-icon class=\"device-menu-icon-holder device-menu-icon-cog-holder\"></as-device-menu-icon>\n</div>");
$templateCache.put("tv/registration/unregister.html","<div ng-controller=\"UnregisterCtrl\" class=\"unregistration-progress-page\">\n    <div class=\"main-container\">\n        <div class=\"reg-banner\">\n            <span>Unregister your device</span>\n            <img src=\"assets/images/appspace-logo.svg\" />\n        </div>\n        <div class=\"reg-container\">\n            <div class=\"reg-container-primary\">\n                <progress-donut progression=\"unregistrationProgress\"></progress-donut>\n            </div>\n            <div class=\"reg-container-secondary\">\n                <span class=\"register-headline\">\n                    <span>Give us a few moments while we unregister this device from your account.</span>\n                </span>\n            </div>\n        </div>\n        <as-device-menu-icon class=\"device-menu-icon-holder device-menu-icon-cog-holder\"></as-device-menu-icon>\n    </div>\n</div>");
$templateCache.put("tv/sleep/view.html","<div ng-controller=\"SleepController\">\n  <div class=\"container-vcenter sleep-background\" ng-if=\"!isFullOverlayShown\">\n    <div class=\"container-vcenter-inner\">\n      <div class=\"main-container\">\n        <div ng-bind-html=\"message\"></div>\n        <div class=\"sleep-message\">{{sleepMessage}}</div>\n      </div>\n    </div>\n  </div>\n  <div class=\"sleep-full-overlay\" ng-if=\"isFullOverlayShown\"></div>\n</div>");
$templateCache.put("tv/splash/view.html","<div ng-controller=\"SplashCtrl\" class=\"splash-landing-page\">\n    <div id=\"splash-container\" style=\"opacity:0\">\n        <div class=\"container-vcenter\">\n            <div class=\"container-vcenter-inner\">\n                <div class=\"main-container\">\n                    <img src=\"assets/images/appspace-logo.svg\" />\n                </div>\n            </div>\n        </div>\n    </div>\n    <div class=\"container-vcenter\">\n        <div class=\"container-vcenter-inner\">\n            <div ng-bind-html=\"message\" class=\"splash-msg\"></div>\n        </div>\n    </div>\n    <div class=\"message-box-wrapper\">\n        <span id=\"info-message-box\" class=\"message-box-default message-box-splash\" ng-show=\"infoMessage\">\n            <as-message-box\n                ng-if=\"infoMessage\" message=\"infoMessage\"\n                background-color=\"infoMessageColor\"></as-message-box>\n        </span>\n        <span id=\"message-box\" class=\"message-box-default message-box-splash\" ng-show=\"lowBandwidthMessage\">\n            <as-message-box\n                ng-if=\"lowBandwidthMessage\" message=\"lowBandwidthMessage\"\n                background-color=\"lowBandwidthColor\"></as-message-box>\n        </span>\n    </div>\n</div>");
$templateCache.put("tv/standby/view.html","<div ng-controller=\"StandbyController\">\n    <div ng-switch=\"isFullOverlay\">\n        <div ng-switch-when=\"false\" class=\"container-vcenter sleep-background\">\n            <div class=\"container-vcenter-inner\">\n                <div class=\"main-container\">\n                    <div ng-bind-html=\"message\"></div>\n                    <div class=\"sleep-message\">{{standbyMessage}}</div>\n                </div>\n            </div>\n        </div>\n        <div ng-switch-when=\"true\" class=\"sleep-full-overlay\" ng-style=\"{\'background\': overlayBackgroundColor}\"></div>\n    </div>\n</div>");
$templateCache.put("tv/unload/view.html","<div ng-controller=\"UnloadCtrl\" class=\"unload-landing-page\">\n  <div class=\"container-vcenter\">\n      <div class=\"container-vcenter-inner\">\n        <div ng-bind-html=\"message\" class=\"unload-msg\"></div>\n      </div>\n  </div>\n</div>");
$templateCache.put("tv/watchdog/view.html","<div ng-controller=\"WatchdogCtrl\">\n    <div class=\"container-vcenter\">\n        <div class=\"container-vcenter-inner\">\n            <div class=\"main-container watchdog-container\">\n                <img src=\"assets/images/appspace-logo.svg\" />\n                <div ng-if=\"message\">\n                    <div class=\"message\">\n                        {{message}}\n                        <div class=\"la-ball-pulse\">\n                            <div></div>\n                            <div></div>\n                            <div></div>\n                        </div>\n                    </div>\n                    <div class=\"sub-message\">\n                        <p>If the date and time cannot be verified, your content may not run as scheduled.</p>\n                        <p>To resolve this, please ensure your device has a working network connection.</p>\n                    </div>\n                </div>\n            </div>\n        </div>\n    </div>\n</div>");
$templateCache.put("tv/channelbrowser/channel-viewer/channel-viewer.html","<channel-viewer ng-controller=\"ChannelViewerCtrl\" ng-click=\"onViewerClicked()\" class=\"channel-viewer-back-placement-{{theme.backPlacement}}\">\n	<loader ng-if=\"showLoader\"></loader>\n	<back-button ng-if=\"theme.backPlacement == \'topleft\' || theme.backPlacement == \'topright\'\" show=\"theme.persistBackButton || paneOpen\" on-back=\"backClick()\"></back-button>\n	<div class=\"content-bottom-pane\" ng-class=\"{\'content-bottom-pane-open\': paneOpen}\">\n		<back-button ng-if=\"theme.backPlacement == \'bottomleft\' || theme.backPlacement == \'bottomright\'\" show=\"theme.persistBackButton || paneOpen\" on-back=\"backClick()\"></back-button>\n        <div ng-if=\"isContentInteractiveEnabled\" class=\"content-bottom-pane-interactive\">\n            <i ng-if=\"!paneOpen\" class=\"fa fa-chevron-up\"></i>\n            <i ng-if=\"paneOpen\" class=\"fa fa-chevron-down\"></i>\n        </div>\n        <channel-content-strip\n			ng-hide=\"hideContentStrip\"\n			content-items=\"contentThumbnails\"\n			current-item=\"currentContent\"\n			current-progress=\"contentProgress\"\n            on-item-click=\"onStripItemClick(item)\"\n            show-play-icon=\"showPlayIcon\">\n		</channel-content-strip>\n	</div>\n	<channel-content-player\n		ng-class=\"{\'channel-content-player-interactive-off\': isContentInteractiveEnabled, \'channel-content-player-interactive-on\': !paneOpen && isContentInteractiveEnabled}\"\n		content-duration=\"contentDuration\"\n		start-index = \"startIndex\"\n		loop=\"loop\"\n		mute=\"isMuted\"\n		on-api-ready=\"onPlayerApi(api)\"\n		on-item-load=\"onPlayerItemLoad(item, presenterItem)\"\n		on-item-begin-load=\"onPlayerItemBeginLoad(item)\"\n		on-item-progress=\"onPlayerItemProgress(progress)\"\n		on-item-offset-invalid=\"onItemOffsetInvalid(offset)\"\n		on-playlist-complete=\"onEndOfPlaylist()\"\n		on-no-valid-playlist-items=\"onNoValidPlaylistItems()\"\n		content-items=\"contentItems\">\n	</channel-content-player>\n    <div ng-if=\"requiresOverlay && !isContentInteractiveEnabled\" class=\"channel-viewer-overlay\">&nbsp;</div>\n    <div ng-hide=\"isContentNavigationDisabled\" class=\"previous-content\" ng-click=\"previousItemClick($event)\"></div>\n	<div ng-hide=\"isContentNavigationDisabled\" class=\"next-content\" ng-click=\"nextItemClick($event)\"></div>\n	<div ng-if=\"errorMessage\" class=\"channel-viewer-error\">{{errorMessage}}</div>\n</channel-viewer>");
$templateCache.put("tv/channelbrowser/loader/loader.html","<div class=\"spinner-fade-line\">\n    <div class=\"bounce1\"></div>\n    <div class=\"bounce2\"></div>\n    <div class=\"bounce3\"></div>\n</div>");
$templateCache.put("tv/channelbrowser/channel-browsing/back-button/back-button.html","<div class=\"back-button-container\" ng-class=\"{\'back-button-show\': $ctrl.show}\">\n    <i ng-click=\"$ctrl.onClick($event)\" class=\'icon icon-back\'></i>\n</div>");
$templateCache.put("tv/channelbrowser/channel-browsing/banner/banner.html","<div ng-if=\"$ctrl.theme.enableLogo\" class=\"banner-logo\" ng-class=\"{\'default-logo\' : $ctrl.theme.isDefaultLogo}\"></div>\n<clock ng-if=\"$ctrl.theme.enableClock\" ng-style=\"$ctrl.theme.clockStyle\" format=\"$ctrl.theme.clockFormat\"></clock>");
$templateCache.put("tv/channelbrowser/channel-browsing/background-channel/background-channel.html","<as-ui-content-presenter \n    mute=true \n    default-duration=\"{{$ctrl.contentDuration}}\" \n    default-stretch=\"{{$ctrl.scale}}\"\n    on-api-ready=\"$ctrl.onPresenterApi(api)\" \n    content-item=\"$ctrl.currentContent\" \n    on-item-load=\"$ctrl.onItemLoad()\"\n    on-item-load-error=\"$ctrl.onLoadError()\"\n    on-item-complete=\"$ctrl.onMediaComplete()\"\n    custom-container-style=\"$ctrl.customStyle\">\n</as-ui-content-presenter>");
$templateCache.put("tv/channelbrowser/channel-browsing/content-grid/content-grid.html","<ng-transclude></ng-transclude>");
$templateCache.put("tv/channelbrowser/channel-browsing/sidebar/sidebar.html","<div class=\"sidebar-container\" ng-class=\"{\'sidebar-container-open\': $ctrl.open}\" ng-click=\"$ctrl.onSidebarClick()\">\n    <div class=\"icon-container\">\n        <div class=\"menu-icon\">\n            <i class=\"icon icon-menu\"></i>\n        </div>  \n        <div class=\"menu-icon-container\" ng-repeat=\"item in $ctrl.menuLocal\"> \n            <div class=\"menu-icon\" ng-click=\"$ctrl.onIconClick($event,item)\" ng-class=\"{\'menu-icon-selected\': item.selected || item.subSelected}\">\n                <div ng-style=\"$ctrl.accentOveride\" class=\"menu-selected-edge\"></div>\n                <i class=\"icon {{item.icon}}\" ></i>\n                <span class=\"menu-label\" ng-class=\"{\'menu-label-selected\': item.selected}\">{{item.label}}</span>\n            </div>\n        </div>\n    </div>\n\n    <div class=\"icon-container icon-container-slide\">\n        <div class=\"menu-icon\">\n            <i class=\"icon icon-menu\"></i>\n        </div>  \n        <div class=\"menu-icon-container\" ng-repeat=\"item in $ctrl.menuLocal\"> \n            <div class=\"menu-icon\" ng-click=\"$ctrl.onIconClick($event,item)\" ng-class=\"{\'menu-icon-selected\': item.selected || item.subSelected}\">\n                <div ng-style=\"$ctrl.accentOveride\" class=\"menu-selected-edge\"></div>\n                <i class=\"icon {{item.icon}}\" ></i>\n                <span class=\"menu-label\" ng-class=\"{\'menu-label-selected\': item.selected}\">{{item.label}}</span>\n            </div>\n            <div ng-if=\"item.items.length\" class=\"sub-menu-label-collection\">\n                <span ng-repeat=\"subitem in item.items\" \n                    class=\"menu-label sub-menu-label\" \n                    ng-class=\"{\'menu-label-selected\': subitem.selected}\" \n                    ng-click=\"$ctrl.onIconClick($event,subitem)\"\n                    id=\"{{subitem.id}}\">\n                    {{subitem.label}}\n                </span>\n            </div>\n        </div>\n    </div>\n</div>");
$templateCache.put("tv/channelbrowser/channel-browsing/content-tile/content-tile.html","<div class=\"media-container\" ng-style=\"$ctrl.mediaStyle\">\n    <span ng-if=\"$ctrl.alternateText\">{{$ctrl.alternateText}}</span>\n</div>\n<div ng-if=\"$ctrl.title\" class=\"title-container\">\n    <div>{{$ctrl.title}}</div>\n</div>");
$templateCache.put("tv/channelbrowser/channel-viewer/channel-content-player/channel-content-player.html","<as-ui-content-presenter \n    mute=\"{{$ctrl.mute}}\"\n    default-duration=\"{{$ctrl.contentDuration}}\" \n    default-stretch=\"{{$ctrl.scale}}\"\n    on-api-ready=\"$ctrl.onPresenterApi(api)\" \n    content-item=\"$ctrl.currentContent\" \n    on-item-load=\"$ctrl.onContentLoad(item)\"\n    on-item-begin-transition=\"$ctrl.onContentBeginTransition(item)\"\n    on-item-load-error=\"$ctrl.onContentLoadError(item)\"\n    on-item-playback-progress=\"$ctrl.onContentProgress(progress, item)\"\n    on-item-complete=\"$ctrl.onContentComplete(item)\">\n</as-ui-content-presenter>");
$templateCache.put("tv/channelbrowser/channel-viewer/channel-content-strip/channel-content-strip.html","<video-playback-bar ng-if=\"$ctrl.currentItem.mediaType === \'video\'\" current-progress=\"$ctrl.currentProgress\" show-play-icon=\"$ctrl.showPlayIcon\"></video-playback-bar>\n<span class=\"current-content-title\">\n    {{$ctrl.title}}\n</span>\n<span ng-if=\"$ctrl.currentItem.description\" class=\"current-content-description\">\n    {{$ctrl.currentItem.description}}\n</span>\n<content-grid ng-show=\"$ctrl.contentItems.length > 1\" on-api-ready=\"onContentGridApi(api)\">\n    <content-tile ng-repeat=\"content in $ctrl.contentItems\" ng-if=\"content.isWithinSchedule\" ng-click=\"$ctrl.onContentItemClick($event, content)\"\n        stretch=\"uniformfill\"\n        ng-class=\"{\'tile-selected\': $ctrl.currentItem.position == content.position, \'tile-not-selected\': $ctrl.currentItem.position != content.position}\"\n        alt-text=\"content.channelCode || $ctrl.generateAlternateText(content.title)\"\n        media-src=\"content.thumbnailUrl\">\n    </content-tile>\n</content-grid>");
$templateCache.put("tv/channelbrowser/channel-browsing/clock/clock.html","{{$ctrl.time}}");
$templateCache.put("tv/channelbrowser/channel-viewer/video-playback-bar/video-playback-bar.html","<i class=\"{{$ctrl.showPlayIcon ? \'fa fa-play\': \'fa fa-pause\'}}\"></i>\n<span>{{$ctrl.currentTime}}</span>\n<div class=\"progress-bar-container\">\n    <div class=\"current-progress\"></div>\n</div> \n<span>{{$ctrl.totalTime}}</span>");
$templateCache.put("commondirectives/asBroadcastAlert.html","<div ng-if=\"currentBroadcast && currentBroadcast.content\" class=\"broadcast-container\" ng-class=\"{\'fullscreen-broadcast\': !currentBroadcast.isNotification, \'fullscreen-broadcast-content\': currentBroadcast.isFullscreenContent}\">\n    <div ng-switch=\"currentBroadcast.isFullscreenContent.toString()\" class=\"broadcast-wrapper\" ng-class=\"{\'critical-broadcast\': currentBroadcast.isCritical}\">\n        <div ng-switch-when=\"true\" ng-if=\"currentItem\" class=\"broadcast-content-presenter\" as-ui-content-presenter content-item=\"currentItem\" on-api-ready=\"onPresenterApiReady(api)\" on-item-complete=\"onItemComplete(item, isEndOfPlaylist)\" on-item-load-error=\"onItemLoadError()\"></div>\n        <div ng-switch-when=\"false\" class=\"broadcast-content\">\n            <div class=\"broadcast-priority\" ng-if=\"currentBroadcast.isCritical\">\n                <img src=\"assets/images/priority-alert.svg\">\n            </div>\n            <div class=\"broadcast-text\">\n                <div class=\"broadcast-title\">{{currentBroadcast.content.title}}\n                </div>\n                <div class=\"broadcast-message\">\n                    <div ng-bind-html=\"currentBroadcast.content.message | asTrustedHtml\"></div>\n                </div>\n            </div>\n            <div class=\"broadcast-media\" ng-if=\"currentItem\" custom-container-style=\"customStyle\" as-ui-content-presenter content-item=\"currentItem\" on-api-ready=\"onPresenterApiReady(api)\" on-item-complete=\"onItemComplete()\" on-item-load-error=\"onItemLoadError()\" default-duration=\"{{DEFAULT_PLAYBACK_DURATION}}\"></div>\n        </div>\n    </div>\n</div>");
$templateCache.put("commondirectives/asFallbackContent.html","<div ng-if=\"content && content.mediaContentItems\"\n    class=\"broadcast-container fullscreen-broadcast fullscreen-broadcast-content fallback-content\">\n    <div class=\"broadcast-wrapper\">\n        <div ng-if=\"currentItem\" class=\"broadcast-content-presenter\" as-ui-content-presenter content-item=\"currentItem\"\n            on-api-ready=\"onPresenterApiReady(api)\" on-item-complete=\"onItemComplete(item, isEndOfPlaylist)\"\n            on-item-load-error=\"onItemLoadError()\" default-stretch=\"{{defaultStretch}}\"></div>\n    </div>\n</div>\n<as-registration-bar ng-if=\"isUnregistered\"></as-registration-bar>");
$templateCache.put("commondirectives/asRegistrationBar.html","<div id=\"reaction-container\">\n    <div class=\"qr-container\" ng-if=\"registrationQrLink\">\n        <qrcode class=\"social-qr\" \n            data=\"{{registrationQrLink}}\"\n            error-correction-level=\"L\"\n            version=\"5\"></qrcode>\n    </div>\n    <div id=\"left-reaction-column\">\n        <p class=\"registration-code\" ng-if=\"registrationCode\">{{registrationCode}}</p>\n        <p class=\"registration-message\" ng-if=\"registrationMessage\">{{registrationMessage}}</p>\n    </div>\n    <as-device-menu-icon class=\"device-menu-icon-holder device-menu-icon-cog-holder\"></as-device-menu-icon>\n</div>");
$templateCache.put("commondirectives/asSocialBar.html","<div id=\"reaction-container\" ng-if=\"currentPost && currentPost.isReactionBarEnabled\">\n    <div id=\"left-reaction-column\" ng-if=\"currentPost.isReactionEnabled\">\n        <div id=\"reaction-column\" aria-label=\"reaction\" ng-if=\"currentPost.reactionCount > 0\">\n            <ul id=\"reaction-bar\">\n                <li ng-repeat=\"reaction in reactionIcons\" ng-if=\"currentPost.reactionCount > 0\" ng-style=\"reaction.style\">\n                    <div class=\"reaction-icon\" style=\"background-image: url({{reaction.icon}})\" alt=\"{{reaction.name}}\" title=\"{{reaction.name}}\">\n                </li>\n            </ul>\n            <div id=\"reaction-amount\">{{reactionCount}}</div>\n        </div>\n        <div class=\"reaction-separator\">{{currentPost.reactionCount > 0 && currentPost.commentCount > 0 ? \'·\' : \'\'}}</div>\n        <div id=\"comment-column\" aria-label=\"comment\" ng-if=\"currentPost.commentCount > 0\">{{commentCount}}</div>\n    </div>\n    <qrcode class=\"social-qr\" \n        data=\"{{actionLink}}\"\n        error-correction-level=\"L\"\n        version=\"5\"\n        ng-if=\"isQrCodeEnabled && actionLink != undefined\"></qrcode>\n</div>");
$templateCache.put("commondirectives/asdevicemenuicon.html","<div as-click=\"onDeviceMenuClick()\" ng-class=\"{ \'hide-device-menu\' : isDeviceMenuHidden}\">\n	<div ng-if=\"!isPassive\" class=\"device-menu-icon-text\">\n		<i id=\"menuCog\" class=\"icon icon-device-menu\" aria-hidden=\"true\"></i>\n        <i id=\"menuClose\" class=\"icon icon-left-open-big\" aria-hidden=\"true\"></i>\n	</div>\n</div>");
$templateCache.put("commondirectives/asfullscreenspinnertemplate.html","<div id=\"spinner\" class=\"spinner-container\" ng-attr-style=\"display:{{displaySpinner}};\">\n    <div class=\"spinner-outer\" >\n        <div class=\"spinner\" ></div>\n    </div>\n    <div class=\"spinner-message\">{{message}}</div>\n</div>");
$templateCache.put("commondirectives/askeyboard.html","<div id=\"keyboardContainer\" class=\"as-keyboard\" ng-attr-style=\"display:{{displayKeyboard}};\">\n    \n</div>");
$templateCache.put("commondirectives/assidemenu.html","<div id=\"sideMenuContainer\" class=\"as-side-menu\">\n	<div class=\"list-group\" id=\"sideMenuListGroup\">\n		<as-ui-grid items=\"gridItems\" config=\"gridConfig\" on-api-ready=\"onGridApi(api)\" on-item-focus=\"onGridItem(api, offset, fromClick)\">\n		</as-ui-grid>\n	</div>\n</div>");
$templateCache.put("commondirectives/test.html","<div ng-controller=\"TestCtrl\">\n<input type=\"text\" id=\"text\" value=\"{{inputValue}}\" />\n<as-keyboard show=\"true\" on-api-ready=\"onKeyboardApi(api)\" on-focus-change=\"onFocusChange(previousKey, currentKey)\" on-value-change=\"onValueChange(previousValue, currentValue)\" on-key-selected=\"onKeySelected(key)\"></as-keyboard>\n</div>");
$templateCache.put("commondirectives/contentNavigationButtons/asContentNavigationButtons.html","<div class=\"container-item-holder\">\r\n    <div class=\"content-navigation-buttons content-navigation-previous\" ng-click=\"$ctrl.onContentPreviousClicked($event)\">\r\n        <i class=\"icon-lg icon icon-chevron-left\"></i>\r\n    </div>\r\n    <div class=\"content-navigation-buttons\" ng-click=\"$ctrl.onContentNextClicked($event)\">\r\n        <i class=\"icon-lg icon icon-chevron-right\"></i>\r\n    </div>\r\n</div>");}]);